Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5483)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java

Issue 928643003: Upstream Chrome for Android Cast. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix dependencies (second attempt). Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c6284dcf91c1160fbc5cc259dae35a064280e21
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java
@@ -0,0 +1,450 @@
+// Copyright 2013 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.media.remote;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.app.MediaRouteChooserDialogFragment;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+import android.support.v7.app.MediaRouteDialogFactory;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.android.gms.cast.CastMediaControlIntent;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.ChromeSwitches;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The singleton responsible managing the global resources for remote media playback (cast)
+ */
+public class RemoteMediaPlayerController implements MediaRouteController.UiListener {
+ // Singleton instance of the class. May only be accessed from UI thread.
+ private static RemoteMediaPlayerController sInstance;
+
+ private static final String TAG = "VideoFling";
+
+ private static final String DEFAULT_CASTING_MESSAGE = "Casting to Chromecast";
+
+ private TransportControl mNotificationControl;
+ private TransportControl mLockScreenControl;
+
+ private Context mCastContextApplicationContext;
+ // The Activity that was in the foreground when the video was cast.
+ private WeakReference<Activity> mChromeVideoActivity;
+
+ private List<MediaRouteController> mMediaRouteControllers;
+
+ // points to mDefaultRouteSelector, mYouTubeRouteSelector or null
+ private MediaRouteController mCurrentRouteController;
+
+ private boolean mFirstConnection = true;
+
+ // This is a key for meta-data in the package manifest.
+ private static final String REMOTE_MEDIA_PLAYERS_KEY =
+ "org.chromium.content.browser.REMOTE_MEDIA_PLAYERS";
+
+ /**
+ * The private constructor to make sure the object is only created by the instance() method.
+ */
+ private RemoteMediaPlayerController() {
+ mChromeVideoActivity = new WeakReference<Activity>(null);
+ mMediaRouteControllers = new ArrayList<MediaRouteController>();
+ }
+
+ /**
+ * @return Whether the remote playback is enabled.
+ */
+ public static boolean isRemotePlaybackEnabled() {
+ return !CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_CAST);
+ }
+
+ /**
+ * @return The poster image for the currently playing remote video, null if there's none.
+ */
+ public Bitmap getPoster() {
+ if (mCurrentRouteController == null) return null;
+ return mCurrentRouteController.getPoster();
+ }
+
+ /**
+ * The singleton instance access method for native objects. Must be called on the UI thread
+ * only.
+ */
+ public static RemoteMediaPlayerController instance() {
+ ThreadUtils.assertOnUiThread();
+
+ if (sInstance == null) sInstance = new RemoteMediaPlayerController();
+ if (sInstance.mChromeVideoActivity.get() == null) sInstance.linkToBrowserActivity();
+
+ return sInstance;
+ }
+
+ /**
+ * Gets the MediaRouteController for a video, creating it if necessary.
+ * @param frameUrl The Url of the frame containing the video
+ * @return the MediaRouteController, or null if none.
+ */
+ public MediaRouteController getMediaRouteController(String sourceUrl, String frameUrl) {
+ for (MediaRouteController controller: mMediaRouteControllers) {
+ if (controller.canPlayMedia(sourceUrl, frameUrl)) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the default MediaRouteController, creating it if necessary.
+ * @return the default MediaRouteController.
+ */
+ public List<MediaRouteController> getMediaRouteControllers() {
+ return mMediaRouteControllers;
+ }
+
+ /**
+ * Links this object to the Activity that owns the video, if it exists.
+ *
+ */
+ private void linkToBrowserActivity() {
+
+ Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
+ if (currentActivity != null) {
+ mChromeVideoActivity = new WeakReference<Activity>(currentActivity);
+
+ mCastContextApplicationContext = currentActivity.getApplicationContext();
+ createMediaRouteControllers(currentActivity);
+ }
+ }
+
+ /**
+ * Create the mediaRouteControllers
+ * @param context - the current Android Context
+ */
+ public void createMediaRouteControllers(Context context) {
+ // We only need to do this once
+ if (!mMediaRouteControllers.isEmpty()) return;
+ try {
+ ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
+ context.getPackageName(), PackageManager.GET_META_DATA);
+ Bundle bundle = ai.metaData;
+ String classNameString = bundle.getString(REMOTE_MEDIA_PLAYERS_KEY);
+
+ if (classNameString != null) {
+ String[] classNames = classNameString.split(",");
+ for (String className : classNames) {
+ Log.i(TAG, "Adding remote media route controller " + className.trim());
+ Class<?> mediaRouteControllerClass = Class.forName(className.trim());
+ Object mediaRouteController = mediaRouteControllerClass.newInstance();
+ assert mediaRouteController instanceof MediaRouteController;
+ mMediaRouteControllers.add((MediaRouteController) mediaRouteController);
+ }
+ }
+ } catch (NameNotFoundException | ClassNotFoundException | SecurityException
+ | InstantiationException | IllegalAccessException | IllegalArgumentException e) {
+ // Should never happen, implies corrupt AndroidManifest
+ Log.e(TAG, "Couldn't instatiate MediaRouteControllers", e);
+ assert false;
+ }
+ }
+
+ private void onStateReset(MediaRouteController controller) {
+
+ if (!controller.initialize()) return;
+
+ if (mFirstConnection) {
+ controller.reconnectAnyExistingRoute();
+ mFirstConnection = false;
+ }
+
+ if (mNotificationControl != null) {
+ mNotificationControl.setRouteController(controller);
+ }
+ if (mLockScreenControl != null) {
+ mLockScreenControl.setRouteController(controller);
+ }
+ controller.prepareMediaRoute();
+
+ controller.addUiListener(this);
+ }
+
+ /**
+ * Called when a lower layer requests that a video be cast. This will typically be a request
+ * from Blink when the cast button is pressed on the default video controls.
+ * @param player the player for which cast is being requested
+ * @param frameUrl the URL of the frame containing the video, needed for YouTube videos
+ */
+ public void requestRemotePlayback(
+ MediaRouteController.MediaStateListener player, MediaRouteController controller) {
+ Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
+ mChromeVideoActivity = new WeakReference<Activity>(currentActivity);
+
+ if (mCurrentRouteController != null && controller != mCurrentRouteController) {
+ mCurrentRouteController.release();
+ }
+
+ onStateReset(controller);
+ if (controller.shouldResetState(player)) {
+ controller.setMediaStateListener(player);
+ showMediaRouteDialog(controller, currentActivity);
+ }
+
+ }
+
+ /**
+ * Called when a lower layer requests control of a video that is being cast.
+ * @param player The player for which remote playback control is being requested.
+ */
+ public void requestRemotePlaybackControl(MediaRouteController.MediaStateListener player) {
+ // Player should match currently remotely played item, but there
+ // can be a race between various
+ // ways that the a video can stop playing remotely. Check that the
+ // player is current, and ignore if not.
+
+ if (mCurrentRouteController == null) return;
+ if (mCurrentRouteController.getMediaStateListener() != player) return;
+
+ showMediaRouteControlDialog(mCurrentRouteController,
+ ApplicationStatus.getLastTrackedFocusedActivity());
+ }
+
+ private void showMediaRouteDialog(MediaRouteController controller, Activity activity) {
+
+ FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
+ if (fm == null) {
+ throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
+ }
+
+ MediaRouteDialogFactory factory = new ChromeMediaRouteDialogFactory();
+
+ if (fm.findFragmentByTag(
+ "android.support.v7.mediarouter:MediaRouteChooserDialogFragment") != null) {
+ Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+ return;
+ }
+ MediaRouteChooserDialogFragment f = factory.onCreateChooserDialogFragment();
+
+ f.setRouteSelector(controller.buildMediaRouteSelector());
+ f.show(fm, "android.support.v7.mediarouter:MediaRouteChooserDialogFragment");
+ }
+
+ private void showMediaRouteControlDialog(MediaRouteController controller, Activity activity) {
+
+ FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
+ if (fm == null) {
+ throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
+ }
+ MediaRouteDialogFactory factory = new ChromeMediaRouteDialogFactory();
+
+ if (fm.findFragmentByTag(
+ "android.support.v7.mediarouter:MediaRouteControllerDialogFragment") != null) {
+ Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+ return;
+ }
+ MediaRouteControllerDialogFragment f = factory.onCreateControllerDialogFragment();
+
+ f.show(fm, "android.support.v7.mediarouter:MediaRouteControllerDialogFragment");
+ }
+
+ /**
+ * Starts up the notification and lock screen with the given playback state.
+ *
+ * @param initialState the initial state of the notification
+ * @param mediaRouteController the mediaRouteController for which these are needed
+ */
+ public void startNotificationAndLockScreen(PlayerState initialState,
+ MediaRouteController mediaRouteController) {
+ mCurrentRouteController = mediaRouteController;
+ createNotificationControl();
+ getNotification().show(initialState);
+ createLockScreen();
+ TransportControl lockScreen = getLockScreen();
+ if (lockScreen != null) lockScreen.show(initialState);
+ }
+
+ /**
+ * Returns the current remote playback position.
+ *
+ * @return The current position of the remote playback in milliseconds.
+ */
+ public int getPosition() {
+ if (mCurrentRouteController == null) return -1;
+ return mCurrentRouteController.getPosition();
+ }
+
+ /**
+ * @return The stream duration in milliseconds.
+ */
+ public int getDuration() {
+ if (mCurrentRouteController == null) return 0;
+ return mCurrentRouteController.getDuration();
+ }
+
+ /**
+ * @return Whether the video is currently being played.
+ */
+ public boolean isPlaying() {
+ return mCurrentRouteController != null && mCurrentRouteController.isPlaying();
+ }
+
+ /**
+ * Initiates a seek request for the remote playback device to the specified position.
+ *
+ * @param msec The position to seek to, in milliseconds.
+ */
+ public void seekTo(int msec) {
+ if (mCurrentRouteController == null) return;
+ mCurrentRouteController.seekTo(msec);
+ }
+
+ /**
+ * @return the currently playing MediaRouteController
+ */
+ public MediaRouteController getCurrentlyPlayingMediaRouteController() {
+ return mCurrentRouteController;
+ }
+
+ /**
+ * Set the current MediaRouteController
+ * @param controller the controller
+ */
+ public void setCurrentMediaRouteController(MediaRouteController controller) {
+ mCurrentRouteController = controller;
+ }
+
+ private TransportControl getNotification() {
+ return mNotificationControl;
+ }
+
+ /**
+ *
+ */
+ private void createNotificationControl() {
+ mNotificationControl = NotificationTransportControl.getOrCreate(
+ mChromeVideoActivity.get(), mCurrentRouteController);
+ mNotificationControl.setError(null);
+ mNotificationControl.setScreenName(mCurrentRouteController.getRouteName());
+ mNotificationControl.addListener(mCurrentRouteController);
+ }
+
+ private TransportControl getLockScreen() {
+ return mLockScreenControl;
+ }
+
+ /**
+ *
+ */
+ private void createLockScreen() {
+ mLockScreenControl = LockScreenTransportControl.getOrCreate(
+ mChromeVideoActivity.get(), mCurrentRouteController);
+ if (mLockScreenControl != null) {
+ mLockScreenControl.setError(null);
+ mLockScreenControl.setScreenName(mCurrentRouteController.getRouteName());
+ mLockScreenControl.addListener(mCurrentRouteController);
+ }
+ if (mLockScreenControl != null) mLockScreenControl.setPosterBitmap(getPoster());
+ }
+
+ @Override
+ public void onPrepared(MediaRouteController mediaRouteController) {
+
+ startNotificationAndLockScreen(PlayerState.PLAYING, mediaRouteController);
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlayerState oldState, PlayerState newState) {
+ if (newState == PlayerState.PLAYING || newState == PlayerState.LOADING
+ || newState == PlayerState.PAUSED) {
+ TransportControl notificationControl = getNotification();
+ if (notificationControl != null) notificationControl.show(newState);
+ TransportControl lockScreen = getLockScreen();
+ if (lockScreen != null) lockScreen.show(newState);
+ }
+ }
+
+ @Override
+ public void onError(int error, String errorMessage) {
+ if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED) {
+ showMessageToast(errorMessage);
+ }
+ }
+
+ @Override
+ public void onDurationUpdated(int durationMillis) {}
+
+ @Override
+ public void onPositionChanged(int positionMillis) {}
+
+ @Override
+ public void onTitleChanged(String title) {}
+
+ @Override
+ public void onRouteSelected(String routeName, MediaRouteController mediaRouteController) {
+ if (mCurrentRouteController != mediaRouteController) {
+ mCurrentRouteController = mediaRouteController;
+ resetPlayingVideo();
+ }
+ }
+
+ /**
+ * Gets some text to tell the user that the video is being cast.
+ * @param routeName The name of the route on which the video is being cast.
+ * @return A String to be shown to the user.
+ */
+ public String getCastingMessage(String routeName) {
+ String castingMessage = DEFAULT_CASTING_MESSAGE;
+ if (mCastContextApplicationContext != null) {
+ castingMessage = mCastContextApplicationContext.getString(
+ R.string.cast_casting_video, routeName);
+ }
+ return castingMessage;
+ }
+
+ // Note that, after switching MediaRouteControllers onRouteUnselected may be called for
+ // the old media route controller, so this should not do anything to
+ // mCurrentRouteController
+ @Override
+ public void onRouteUnselected(MediaRouteController mediaRouteController) {
+ if (mediaRouteController == mCurrentRouteController) {
+ mCurrentRouteController = null;
+ }
+ }
+
+ private void showMessageToast(String message) {
+ Toast toast = Toast.makeText(mCastContextApplicationContext, message, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ private void resetPlayingVideo() {
+ if (mNotificationControl != null) {
+ mNotificationControl.setRouteController(mCurrentRouteController);
+ }
+ if (mLockScreenControl != null) {
+ mLockScreenControl.setRouteController(mCurrentRouteController);
+ }
+ }
+
+ @VisibleForTesting
+ static RemoteMediaPlayerController getIfExists() {
+ return sInstance;
+ }
+
+}

Powered by Google App Engine
This is Rietveld 408576698