Commit 278cb45f authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Video Tutorials: Added video player UI

This CL adds the video player UI.
1 - Added VideoPlayerActivity at chrome layer, which creates
VideoPlayerCoordinator and passes a WebContent supplier in order to
create ThinWebView.
2 - Added video player MVC, the mediator handles display logic between
loading screen, controls, thinwebview, and language picker.
It also handles callbacks for control buttons, and observes media
session through a PlaybackStateObserver.

TODO :
Add screenshot. Add UI polish. Define share/watchnext behavior.
Polish media session logic.

Bug: 1117172
Binary-Size: Unavoidable method count increase (includes methods from previous CL).
Change-Id: Ide7c0d7bf4fa20a9bfbe0c10717e6983d0da88dc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2415672
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811094}
parent 72389d56
...@@ -1635,6 +1635,7 @@ chrome_java_sources = [ ...@@ -1635,6 +1635,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java", "java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java",
"java/src/org/chromium/chrome/browser/usage_stats/WebsiteEvent.java", "java/src/org/chromium/chrome/browser/usage_stats/WebsiteEvent.java",
"java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java", "java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java",
"java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java",
"java/src/org/chromium/chrome/browser/vr/ArDelegate.java", "java/src/org/chromium/chrome/browser/vr/ArDelegate.java",
"java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java", "java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java",
"java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java", "java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java",
......
...@@ -514,6 +514,11 @@ ...@@ -514,6 +514,11 @@
android:noHistory="true" android:noHistory="true"
android:theme="@style/Theme.AppCompat"> android:theme="@style/Theme.AppCompat">
</activity> # DIFF-ANCHOR: 43bfa5de </activity> # DIFF-ANCHOR: 43bfa5de
<activity # DIFF-ANCHOR: bf27023e
android:exported="false"
android:name="org.chromium.chrome.browser.video_tutorials.VideoPlayerActivity"
android:theme="@style/Theme.Chromium.Activity.Fullscreen">
</activity> # DIFF-ANCHOR: bf27023e
<activity # DIFF-ANCHOR: b007dcaa <activity # DIFF-ANCHOR: b007dcaa
android:enableVrMode="@string/gvr_vr_mode_component" android:enableVrMode="@string/gvr_vr_mode_component"
android:excludeFromRecents="true" android:excludeFromRecents="true"
......
...@@ -487,6 +487,11 @@ ...@@ -487,6 +487,11 @@
android:noHistory="true" android:noHistory="true"
android:theme="@style/Theme.AppCompat"> android:theme="@style/Theme.AppCompat">
</activity> # DIFF-ANCHOR: 43bfa5de </activity> # DIFF-ANCHOR: 43bfa5de
<activity # DIFF-ANCHOR: bf27023e
android:exported="false"
android:name="org.chromium.chrome.browser.video_tutorials.VideoPlayerActivity"
android:theme="@style/Theme.Chromium.Activity.Fullscreen">
</activity> # DIFF-ANCHOR: bf27023e
<activity # DIFF-ANCHOR: b007dcaa <activity # DIFF-ANCHOR: b007dcaa
android:enableVrMode="@string/gvr_vr_mode_component" android:enableVrMode="@string/gvr_vr_mode_component"
android:excludeFromRecents="true" android:excludeFromRecents="true"
......
...@@ -644,6 +644,12 @@ by a child template that "extends" this file. ...@@ -644,6 +644,12 @@ by a child template that "extends" this file.
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize"> android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize">
</activity> </activity>
<!-- Activities for video tutorials. -->
<activity android:name="org.chromium.chrome.browser.video_tutorials.VideoPlayerActivity"
android:theme="@style/Theme.Chromium.Activity.Fullscreen"
android:exported="false">
</activity>
<!-- Activities for history. --> <!-- Activities for history. -->
<activity android:name="org.chromium.chrome.browser.history.HistoryActivity" <activity android:name="org.chromium.chrome.browser.history.HistoryActivity"
android:theme="@style/Theme.Chromium.Activity.Fullscreen" android:theme="@style/Theme.Chromium.Activity.Fullscreen"
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials;
import android.app.Activity;
import android.os.Bundle;
import android.util.Pair;
import android.view.WindowManager;
import org.chromium.base.IntentUtils;
import org.chromium.chrome.browser.WebContentsFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.video_tutorials.player.VideoPlayerCoordinator;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.components.version_info.VersionConstants;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
/**
* Java interface for interacting with the native video tutorial service. Responsible for
* initializing and fetching data fo be shown on the UI.
*/
public class VideoPlayerActivity extends Activity {
public static final String EXTRA_VIDEO_TUTORIAL = "extra_video_tutorial";
private WindowAndroid mWindowAndroid;
private VideoPlayerCoordinator mCoordinator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
VideoTutorialService videoTutorialService =
VideoTutorialServiceFactory.getForProfile(Profile.getLastUsedRegularProfile());
mWindowAndroid = new ActivityWindowAndroid(this);
mCoordinator = new VideoPlayerCoordinator(
this, videoTutorialService, this::createWebContents, this::finish);
setContentView(mCoordinator.getView());
int featureType = IntentUtils.safeGetIntExtra(getIntent(), EXTRA_VIDEO_TUTORIAL, 0);
videoTutorialService.getTutorial(
featureType, tutorial -> { mCoordinator.playVideoTutorial(tutorial); });
}
private Pair<WebContents, ContentView> createWebContents() {
Profile profile = Profile.getLastUsedRegularProfile();
WebContents webContents = WebContentsFactory.createWebContents(profile, false);
ContentView contentView =
ContentView.createContentView(this, null /* eventOffsetHandler */, webContents);
webContents.initialize(VersionConstants.PRODUCT_VERSION,
ViewAndroidDelegate.createBasicDelegate(contentView), contentView, mWindowAndroid,
WebContents.createDefaultInternalsHolder());
return Pair.create(webContents, contentView);
}
@Override
protected void onDestroy() {
mCoordinator.destroy();
super.onDestroy();
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {$} from 'chrome://resources/js/util.m.js'; import {$} from 'chrome-untrusted://resources/js/util.m.js';
function onDocumentLoaded() { function onDocumentLoaded() {
// Find out the video, image, and caption urls from the url params. // Find out the video, image, and caption urls from the url params.
......
...@@ -2745,7 +2745,7 @@ In Incognito, your activity might still be visible to websites that you visit, y ...@@ -2745,7 +2745,7 @@ In Incognito, your activity might still be visible to websites that you visit, y
Watch next video Watch next video
</message> </message>
<message name="IDS_VIDEO_TUTORIALS_CHANGE_LANGUAGE" desc="Button text in the video player prompting user to change the language."> <message name="IDS_VIDEO_TUTORIALS_CHANGE_LANGUAGE" desc="Button text in the video player prompting user to change the language.">
Change Change <ph name="LANGUAGE">%1$s<ex>Hindi</ex></ph>?
</message> </message>
<message name="IDS_VIDEO_TUTORIALS_ACCESSIBILITY_SHARE" desc="Accessibility text in the video player describing that users can tap on this icon to share the video."> <message name="IDS_VIDEO_TUTORIALS_ACCESSIBILITY_SHARE" desc="Accessibility text in the video player describing that users can tap on this icon to share the video.">
Share Share
......
1e72344d7da7da2d784cff5bb65dbdceba59f684 1e72344d7da7da2d784cff5bb65dbdceba59f684
\ No newline at end of file
...@@ -27,7 +27,8 @@ content::WebUIDataSource* CreateVideoPlayerUntrustedDataSource() { ...@@ -27,7 +27,8 @@ content::WebUIDataSource* CreateVideoPlayerUntrustedDataSource() {
source->OverrideContentSecurityPolicy( source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::StyleSrc, "style-src 'self';"); network::mojom::CSPDirectiveName::StyleSrc, "style-src 'self';");
source->OverrideContentSecurityPolicy( source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ScriptSrc, "script-src 'self';"); network::mojom::CSPDirectiveName::ScriptSrc,
"script-src chrome-untrusted://resources/ 'self';");
return source; return source;
} }
......
...@@ -55,8 +55,10 @@ source_set("factory") { ...@@ -55,8 +55,10 @@ source_set("factory") {
if (is_android) { if (is_android) {
android_library("java") { android_library("java") {
sources = [ sources = [
"android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/Tutorial.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/Tutorial.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialService.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialService.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinator.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinator.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHProperties.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHProperties.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHView.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHView.java",
...@@ -67,6 +69,12 @@ if (is_android) { ...@@ -67,6 +69,12 @@ if (is_android) {
"android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerProperties.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerProperties.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerView.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerView.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguageUtils.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguageUtils.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinator.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerURLBuilder.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerView.java",
"android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerViewBinder.java",
] ]
deps = [ deps = [
...@@ -74,7 +82,13 @@ if (is_android) { ...@@ -74,7 +82,13 @@ if (is_android) {
"//base:base_java", "//base:base_java",
"//chrome/browser/image_fetcher:java", "//chrome/browser/image_fetcher:java",
"//components/browser_ui/widget/android:java", "//components/browser_ui/widget/android:java",
"//components/embedder_support/android:content_view_java",
"//components/embedder_support/android:web_contents_delegate_java",
"//components/feature_engagement/public:public_java", "//components/feature_engagement/public:public_java",
"//components/thin_webview:factory_java",
"//components/thin_webview:java",
"//content/public/android:content_java",
"//services/media_session/public/cpp/android:media_session_java",
"//third_party/android_deps:androidx_annotation_annotation_java", "//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:androidx_recyclerview_recyclerview_java", "//third_party/android_deps:androidx_recyclerview_recyclerview_java",
"//ui/android:ui_java", "//ui/android:ui_java",
...@@ -92,6 +106,8 @@ if (is_android) { ...@@ -92,6 +106,8 @@ if (is_android) {
"android/java/res/drawable/radio_button_selected.xml", "android/java/res/drawable/radio_button_selected.xml",
"android/java/res/layout/language_card.xml", "android/java/res/layout/language_card.xml",
"android/java/res/layout/language_picker.xml", "android/java/res/layout/language_picker.xml",
"android/java/res/layout/video_player_controls.xml",
"android/java/res/layout/video_player_loading.xml",
"android/java/res/layout/video_tutorial_iph_card.xml", "android/java/res/layout/video_tutorial_iph_card.xml",
] ]
......
...@@ -2,4 +2,6 @@ include_rules = [ ...@@ -2,4 +2,6 @@ include_rules = [
"+components/browser_ui/widget", "+components/browser_ui/widget",
"+components/keyed_service", "+components/keyed_service",
"+components/leveldb_proto", "+components/leveldb_proto",
"+components/thin_webview/java",
"+content/public/android/java/src/org/chromium/content_public",
] ]
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/player_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<org.chromium.ui.widget.ChromeImageButton
android:id="@+id/close_button"
android:layout_height="24dp"
android:layout_width="24dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/close"
android:src="@drawable/btn_close" />
<org.chromium.ui.widget.ChromeImageButton
android:id="@+id/share_button"
android:layout_height="24dp"
android:layout_width="24dp"
android:layout_toStartOf="@id/close_button"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/share"
android:src="@drawable/ic_share_white_24dp" />
<View
android:id="@+id/dummy_center"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerInParent="true" />
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/try_now"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/dummy_center"
android:layout_marginTop="60dp"
android:text="@string/video_tutorials_try_now"
style="@style/FilledButton.Flat" />
<TextView
android:id="@+id/watch_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/try_now"
android:layout_marginTop="28dp"
android:textAppearance="@style/TextAppearance.TextMedium.Primary.Light"
android:text="@string/video_tutorials_watch_next_video" />
<TextView
android:id="@+id/change_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/watch_next"
android:layout_marginTop="28dp"
android:textAppearance="@style/TextAppearance.TextMedium.Primary.Light" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/background_dark">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="10dp"
android:indeterminate="true"
android:layout_gravity="center"
style="?android:attr/progressBarStyleHorizontal" />
</FrameLayout>
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials;
import org.chromium.content_public.browser.MediaSession;
import org.chromium.content_public.browser.MediaSessionObserver;
import org.chromium.content_public.browser.WebContents;
import org.chromium.services.media_session.MediaPosition;
/**
* Responsible for observing a media session and notifying the observer about play/pause/end media
* events.
*/
public class PlaybackStateObserver extends MediaSessionObserver {
/**
* Interface to be notified of playback state updates.
*/
public interface Observer {
/** Called when the player has started playing or resumed. */
void onPlay();
/** Called when the player has been paused. */
void onPause();
/** Called when the player has completed playing the video. */
void onEnded();
}
private final Observer mObserver;
private MediaPosition mMediaPosition;
/** Constructor. */
public PlaybackStateObserver(WebContents webContents, Observer observer) {
super(MediaSession.fromWebContents(webContents));
mObserver = observer;
}
@Override
public void mediaSessionPositionChanged(MediaPosition position) {
if (position == null) return;
mMediaPosition = position;
}
@Override
public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) {
boolean playerEnded = !isControllable && isSuspended && mMediaPosition != null
&& mMediaPosition.getPosition() > 0.5 * mMediaPosition.getDuration();
boolean playerPaused = isControllable && isSuspended;
boolean isPlaying = isControllable && !isSuspended;
// TODO(shaktisahu): Fix these signals and logic in another CL.
if (isPlaying) {
mObserver.onPlay();
}
if (playerPaused) {
mObserver.onPause();
}
if (playerEnded) {
mObserver.onEnded();
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials;
import org.chromium.base.Callback;
import java.util.List;
/**
* Handles various feature utility functions associated with video tutorials UI.
*/
public class VideoTutorialUtils {
/**
* Finds the next video tutorial to be presented to the user after the user has completed one.
*/
public static void getNextTutorial(VideoTutorialService videoTutorialService, Tutorial tutorial,
Callback<Tutorial> callback) {
videoTutorialService.getTutorials(tutorials -> {
Tutorial nextTutorial = VideoTutorialUtils.getNextTutorial(tutorials, tutorial);
callback.onResult(nextTutorial);
});
}
private static Tutorial getNextTutorial(List<Tutorial> tutorials, Tutorial currentTutorial) {
int currentTutorialIndex = 0;
for (int i = 0; i < tutorials.size(); i++) {
if (tutorials.get(i).featureType == currentTutorial.featureType) break;
}
return currentTutorialIndex < tutorials.size() - 1 ? tutorials.get(currentTutorialIndex + 1)
: null;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import android.content.Context;
import android.util.Pair;
import android.view.View;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver;
import org.chromium.chrome.browser.video_tutorials.R;
import org.chromium.chrome.browser.video_tutorials.Tutorial;
import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
import org.chromium.chrome.browser.video_tutorials.languages.LanguagePickerCoordinator;
import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.components.thinwebview.ThinWebView;
import org.chromium.components.thinwebview.ThinWebViewConstraints;
import org.chromium.components.thinwebview.ThinWebViewFactory;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* The top level coordinator for the video player.
*/
public class VideoPlayerCoordinator {
private final Context mContext;
private final PropertyModel mModel;
private final VideoPlayerView mView;
private final VideoPlayerMediator mMediator;
private final VideoTutorialService mVideoTutorialService;
private final LanguagePickerCoordinator mLanguagePicker;
private WebContents mWebContents;
private WebContentsDelegateAndroid mWebContentsDelegate;
private PlaybackStateObserver mMediaSessionObserver;
/**
* Constructor.
* @param context The activity context.
* @param videoTutorialService The backend for serving video tutorials.
* @param webContentsFactory A supplier to supply WebContents and ContentView.
* @param closeCallback Callback to be invoked when this UI is closed.
*/
public VideoPlayerCoordinator(Context context, VideoTutorialService videoTutorialService,
Supplier<Pair<WebContents, ContentView>> webContentsFactory, Runnable closeCallback) {
mContext = context;
mVideoTutorialService = videoTutorialService;
mModel = new PropertyModel(VideoPlayerProperties.ALL_KEYS);
ThinWebView thinWebView = createThinWebView(webContentsFactory);
mView = new VideoPlayerView(context, mModel, thinWebView);
mLanguagePicker = new LanguagePickerCoordinator(
mView.getView().findViewById(R.id.language_picker), mVideoTutorialService);
mMediator = new VideoPlayerMediator(mContext, mModel, videoTutorialService, mLanguagePicker,
mWebContents, closeCallback);
PropertyModelChangeProcessor.create(mModel, mView, new VideoPlayerViewBinder());
}
/**
* Entry point for playing a video tutorial.
*/
public void playVideoTutorial(Tutorial tutorial) {
mMediator.playVideoTutorial(tutorial);
}
/** @return The Android {@link View} representing this widget. */
public View getView() {
return mView.getView();
}
/** Tears down this coordinator. */
public void destroy() {
mMediaSessionObserver.stopObserving();
mView.destroy();
mWebContents.destroy();
}
private ThinWebView createThinWebView(
Supplier<Pair<WebContents, ContentView>> webContentsFactory) {
Pair<WebContents, ContentView> pair = webContentsFactory.get();
mWebContents = pair.first;
ContentView webContentView = pair.second;
mWebContentsDelegate = new WebContentsDelegateAndroid();
mMediaSessionObserver = new PlaybackStateObserver(mWebContents, mMediator);
ThinWebView thinWebView = ThinWebViewFactory.create(mContext, new ThinWebViewConstraints());
thinWebView.attachWebContents(mWebContents, webContentView, mWebContentsDelegate);
return thinWebView;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import android.content.Context;
import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver;
import org.chromium.chrome.browser.video_tutorials.R;
import org.chromium.chrome.browser.video_tutorials.Tutorial;
import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
import org.chromium.chrome.browser.video_tutorials.VideoTutorialUtils;
import org.chromium.chrome.browser.video_tutorials.languages.LanguagePickerCoordinator;
import org.chromium.chrome.browser.video_tutorials.languages.LanguageUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modelutil.PropertyModel;
/**
* The mediator for the video player UI, responsible for changing the state of UI based on user
* interaction events, and player state.
*/
class VideoPlayerMediator implements PlaybackStateObserver.Observer {
private final Context mContext;
private final VideoTutorialService mVideoTutorialService;
private final PropertyModel mModel;
private final LanguagePickerCoordinator mLanguagePicker;
private final WebContents mWebContents;
private Tutorial mTutorial;
private final Runnable mCloseCallback;
/** Constructor. */
public VideoPlayerMediator(Context context, PropertyModel model,
VideoTutorialService videoTutorialService, LanguagePickerCoordinator languagePicker,
WebContents webContents, Runnable closeCallback) {
mContext = context;
mModel = model;
mVideoTutorialService = videoTutorialService;
mLanguagePicker = languagePicker;
mWebContents = webContents;
mCloseCallback = closeCallback;
mModel.set(VideoPlayerProperties.SHOW_LOADING_SCREEN, false);
mModel.set(VideoPlayerProperties.SHOW_LANGUAGE_PICKER, false);
mModel.set(VideoPlayerProperties.SHOW_MEDIA_CONTROLS, false);
mModel.set(VideoPlayerProperties.CALLBACK_WATCH_NEXT, this::onWatchNextClicked);
mModel.set(VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE, this::changeLanguage);
mModel.set(VideoPlayerProperties.CALLBACK_TRY_NOW, this::tryNow);
mModel.set(VideoPlayerProperties.CALLBACK_SHARE, this::share);
mModel.set(VideoPlayerProperties.CALLBACK_CLOSE, closeCallback);
}
/**
* Entry point for playing a tutorial video. Shows the language picker if it is the very first
* time.
*/
void playVideoTutorial(Tutorial tutorial) {
mTutorial = tutorial;
if (mVideoTutorialService.getPreferredLocale() == null) {
mModel.set(VideoPlayerProperties.SHOW_LANGUAGE_PICKER, true);
mLanguagePicker.showLanguagePicker(this::onLanguageSelected, mCloseCallback);
} else {
startVideo(tutorial);
}
}
@Override
public void onPlay() {
mModel.set(VideoPlayerProperties.SHOW_LOADING_SCREEN, false);
mModel.set(VideoPlayerProperties.SHOW_MEDIA_CONTROLS, false);
}
@Override
public void onPause() {
mModel.set(VideoPlayerProperties.SHOW_MEDIA_CONTROLS, true);
mModel.set(VideoPlayerProperties.SHOW_WATCH_NEXT, false);
mModel.set(VideoPlayerProperties.SHOW_CHANGE_LANGUAGE, false);
}
@Override
public void onEnded() {
mModel.set(VideoPlayerProperties.SHOW_MEDIA_CONTROLS, true);
mModel.set(VideoPlayerProperties.SHOW_WATCH_NEXT, true);
mModel.set(VideoPlayerProperties.SHOW_CHANGE_LANGUAGE, true);
updateChangeLanguageButtonText();
VideoTutorialUtils.getNextTutorial(mVideoTutorialService, mTutorial, nextTutorial -> {
mModel.set(VideoPlayerProperties.SHOW_WATCH_NEXT, nextTutorial != null);
});
}
private void changeLanguage() {
mModel.set(VideoPlayerProperties.SHOW_LANGUAGE_PICKER, true);
mLanguagePicker.showLanguagePicker(this::onLanguageSelected, () -> {} /* closeCallback */);
}
private void updateChangeLanguageButtonText() {
String language = LanguageUtils.getLanguageForLocale(
mContext.getResources(), mVideoTutorialService.getPreferredLocale());
String buttonText = mContext.getResources().getString(
R.string.video_tutorials_change_language, language == null ? "" : language);
mModel.set(VideoPlayerProperties.CHANGE_LANGUAGE_BUTTON_TEXT, buttonText);
}
private void onLanguageSelected() {
mModel.set(VideoPlayerProperties.SHOW_LANGUAGE_PICKER, false);
updateChangeLanguageButtonText();
mVideoTutorialService.getTutorial(mTutorial.featureType, this::startVideo);
}
private void tryNow() {}
private void share() {}
private void startVideo(Tutorial tutorial) {
LoadUrlParams loadUrlParams =
new LoadUrlParams(VideoPlayerURLBuilder.buildFromTutorial(tutorial));
loadUrlParams.setHasUserGesture(true);
mWebContents.getNavigationController().loadUrl(loadUrlParams);
mModel.set(VideoPlayerProperties.SHOW_LOADING_SCREEN, false);
mModel.set(VideoPlayerProperties.SHOW_MEDIA_CONTROLS, false);
}
private void onWatchNextClicked() {
VideoTutorialUtils.getNextTutorial(mVideoTutorialService, mTutorial, this::startVideo);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
/**
* The properties required to build the video player which primarily contains (1) the screen being
* shown on the player, e.g. loading view, language picker, or video player. (2) callbacks
* associated with various buttons.
*/
interface VideoPlayerProperties {
WritableBooleanPropertyKey SHOW_LOADING_SCREEN = new WritableBooleanPropertyKey();
WritableBooleanPropertyKey SHOW_MEDIA_CONTROLS = new WritableBooleanPropertyKey();
WritableBooleanPropertyKey SHOW_LANGUAGE_PICKER = new WritableBooleanPropertyKey();
WritableBooleanPropertyKey SHOW_WATCH_NEXT = new WritableBooleanPropertyKey();
WritableBooleanPropertyKey SHOW_CHANGE_LANGUAGE = new WritableBooleanPropertyKey();
WritableObjectPropertyKey<String> CHANGE_LANGUAGE_BUTTON_TEXT =
new WritableObjectPropertyKey<>();
WritableObjectPropertyKey<Runnable> CALLBACK_WATCH_NEXT = new WritableObjectPropertyKey<>();
WritableObjectPropertyKey<Runnable> CALLBACK_CHANGE_LANGUAGE =
new WritableObjectPropertyKey<>();
WritableObjectPropertyKey<Runnable> CALLBACK_TRY_NOW = new WritableObjectPropertyKey<>();
WritableObjectPropertyKey<Runnable> CALLBACK_SHARE = new WritableObjectPropertyKey<>();
WritableObjectPropertyKey<Runnable> CALLBACK_CLOSE = new WritableObjectPropertyKey<>();
PropertyKey[] ALL_KEYS = new PropertyKey[] {SHOW_LOADING_SCREEN, SHOW_MEDIA_CONTROLS,
SHOW_LANGUAGE_PICKER, SHOW_WATCH_NEXT, SHOW_CHANGE_LANGUAGE,
CHANGE_LANGUAGE_BUTTON_TEXT, CALLBACK_WATCH_NEXT, CALLBACK_CHANGE_LANGUAGE,
CALLBACK_TRY_NOW, CALLBACK_SHARE, CALLBACK_CLOSE};
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import org.chromium.chrome.browser.video_tutorials.Tutorial;
/**
* Creates the player URL for a video tutorial.
*/
class VideoPlayerURLBuilder {
// TODO(shaktisahu): Move this to UrlConstants.
private static final String VIDEO_PLAYER_URL = "chrome-untrusted://video-tutorials/";
/** Constructs the player URL for a given video tutorial. */
public static String buildFromTutorial(Tutorial tutorial) {
StringBuilder builder = new StringBuilder();
builder.append(VIDEO_PLAYER_URL);
builder.append("?");
builder.append("video_url=");
builder.append(tutorial.videoUrl);
builder.append("&poster_url=");
builder.append(tutorial.posterUrl);
builder.append("&caption_url=");
builder.append(tutorial.captionUrl);
return builder.toString();
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import org.chromium.chrome.browser.video_tutorials.R;
import org.chromium.components.thinwebview.ThinWebView;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Represents the view component of the media player. Contains loading screen, language picker, and
* media controls.
*/
class VideoPlayerView {
private final PropertyModel mModel;
private final FrameLayout mFrameLayout;
private final ThinWebView mThinWebView;
private final View mLoadingView;
private final View mControls;
private final View mLanguagePickerView;
/** Constructor. */
public VideoPlayerView(Context context, PropertyModel model, ThinWebView thinWebView) {
mModel = model;
mThinWebView = thinWebView;
mFrameLayout = new FrameLayout(context);
mFrameLayout.addView(mThinWebView.getView());
mControls = LayoutInflater.from(context).inflate(R.layout.video_player_controls, null);
mLoadingView = LayoutInflater.from(context).inflate(R.layout.video_player_loading, null);
mLanguagePickerView = LayoutInflater.from(context).inflate(R.layout.language_picker, null);
mFrameLayout.addView(mControls);
mFrameLayout.addView(mLoadingView);
mFrameLayout.addView(mLanguagePickerView);
}
View getView() {
return mFrameLayout;
}
void destroy() {
mThinWebView.destroy();
}
void showLoadingAnimation(boolean show) {
mLoadingView.setVisibility(show ? View.VISIBLE : View.GONE);
}
void showMediaControls(boolean show) {
mControls.setVisibility(show ? View.VISIBLE : View.GONE);
}
void showLanguagePicker(boolean show) {
mLanguagePickerView.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.video_tutorials.player;
import android.view.View;
import android.widget.TextView;
import org.chromium.chrome.browser.video_tutorials.R;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;
/**
* The binder to bind the video player property model with the {@link VideoPlayerView}.
*/
class VideoPlayerViewBinder implements ViewBinder<PropertyModel, VideoPlayerView, PropertyKey> {
@Override
public void bind(PropertyModel model, VideoPlayerView view, PropertyKey propertyKey) {
if (propertyKey == VideoPlayerProperties.SHOW_LOADING_SCREEN) {
view.showLoadingAnimation(model.get(VideoPlayerProperties.SHOW_LOADING_SCREEN));
} else if (propertyKey == VideoPlayerProperties.SHOW_MEDIA_CONTROLS) {
view.showMediaControls(model.get(VideoPlayerProperties.SHOW_MEDIA_CONTROLS));
} else if (propertyKey == VideoPlayerProperties.SHOW_LANGUAGE_PICKER) {
view.showLanguagePicker(model.get(VideoPlayerProperties.SHOW_LANGUAGE_PICKER));
} else if (propertyKey == VideoPlayerProperties.SHOW_WATCH_NEXT) {
view.getView()
.findViewById(R.id.watch_next)
.setVisibility(model.get(VideoPlayerProperties.SHOW_WATCH_NEXT) ? View.VISIBLE
: View.GONE);
} else if (propertyKey == VideoPlayerProperties.SHOW_CHANGE_LANGUAGE) {
view.getView()
.findViewById(R.id.change_language)
.setVisibility(model.get(VideoPlayerProperties.SHOW_CHANGE_LANGUAGE)
? View.VISIBLE
: View.GONE);
} else if (propertyKey == VideoPlayerProperties.CHANGE_LANGUAGE_BUTTON_TEXT) {
TextView textView = view.getView().findViewById(R.id.change_language);
textView.setText(model.get(VideoPlayerProperties.CHANGE_LANGUAGE_BUTTON_TEXT));
} else if (propertyKey == VideoPlayerProperties.CALLBACK_CLOSE) {
view.getView().findViewById(R.id.close_button).setOnClickListener(v -> {
model.get(VideoPlayerProperties.CALLBACK_CLOSE).run();
});
} else if (propertyKey == VideoPlayerProperties.CALLBACK_SHARE) {
view.getView().findViewById(R.id.share_button).setOnClickListener(v -> {
model.get(VideoPlayerProperties.CALLBACK_SHARE).run();
});
} else if (propertyKey == VideoPlayerProperties.CALLBACK_WATCH_NEXT) {
view.getView().findViewById(R.id.watch_next).setOnClickListener(v -> {
model.get(VideoPlayerProperties.CALLBACK_WATCH_NEXT).run();
});
} else if (propertyKey == VideoPlayerProperties.CALLBACK_TRY_NOW) {
view.getView().findViewById(R.id.try_now).setOnClickListener(v -> {
model.get(VideoPlayerProperties.CALLBACK_TRY_NOW).run();
});
} else if (propertyKey == VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE) {
view.getView().findViewById(R.id.change_language).setOnClickListener(v -> {
model.get(VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE).run();
});
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment