Index: chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f452fc1b97e06538e138454635563f4a75a20a65 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java |
@@ -0,0 +1,353 @@ |
+// 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.content.Context; |
+import android.content.Intent; |
+import android.graphics.Bitmap; |
+import android.graphics.Color; |
+import android.os.Bundle; |
+import android.os.Handler; |
+import android.support.v4.app.FragmentActivity; |
+import android.support.v4.media.TransportMediator; |
+import android.support.v4.media.TransportPerformer; |
+import android.text.TextUtils; |
+import android.view.KeyEvent; |
+import android.view.View; |
+import android.view.ViewGroup; |
+import android.view.Window; |
+import android.view.WindowManager; |
+import android.widget.ImageView; |
+import android.widget.TextView; |
+ |
+import com.google.android.gms.cast.CastMediaControlIntent; |
+ |
+import org.chromium.base.ApiCompatibilityUtils; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState; |
+import org.chromium.third_party.android.media.MediaController; |
+ |
+/** |
+ * The activity that's opened by clicking the video flinging (casting) notification. |
+ * |
+ * TODO(cimamoglu): Refactor to merge some common logic with {@link TransportControl}. |
+ */ |
+public class ExpandedControllerActivity |
+ extends FragmentActivity implements MediaRouteController.UiListener { |
+ private static final int PROGRESS_UPDATE_PERIOD_IN_MS = 1000; |
+ // The alpha value for the poster/placeholder image, an integer between 0 and 256 (opaque). |
+ private static final int POSTER_IMAGE_ALPHA = 200; |
+ |
+ private Handler mHandler; |
+ // We don't use the standard android.media.MediaController, but a custom one. |
+ // See the class itself for details. |
+ private MediaController mMediaController; |
+ private FullscreenMediaRouteButton mMediaRouteButton; |
+ private MediaRouteController mMediaRouteController; |
+ private RemoteVideoInfo mVideoInfo; |
+ private String mScreenName; |
+ private TransportMediator mTransportMediator; |
+ |
+ /** |
+ * Handle actions from on-screen media controls. |
+ */ |
+ private TransportPerformer mTransportPerformer = new TransportPerformer() { |
+ @Override |
+ public void onStart() { |
+ mMediaRouteController.resume(); |
+ } |
+ |
+ @Override |
+ public void onStop() { |
+ onPause(); |
+ mMediaRouteController.release(); |
+ } |
+ |
+ @Override |
+ public void onPause() { |
+ mMediaRouteController.pause(); |
+ } |
+ |
+ @Override |
+ public long onGetDuration() { |
+ return mMediaRouteController.getDuration(); |
+ } |
+ |
+ @Override |
+ public long onGetCurrentPosition() { |
+ return mMediaRouteController.getPosition(); |
+ } |
+ |
+ @Override |
+ public void onSeekTo(long pos) { |
+ mMediaRouteController.seekTo((int) pos); |
+ } |
+ |
+ @Override |
+ public boolean onIsPlaying() { |
+ return mMediaRouteController.isPlaying(); |
+ } |
+ |
+ @Override |
+ public int onGetTransportControlFlags() { |
+ int flags = TransportMediator.FLAG_KEY_MEDIA_REWIND |
+ | TransportMediator.FLAG_KEY_MEDIA_FAST_FORWARD; |
+ if (mMediaRouteController.isPlaying()) { |
+ flags |= TransportMediator.FLAG_KEY_MEDIA_PAUSE; |
+ } else { |
+ flags |= TransportMediator.FLAG_KEY_MEDIA_PLAY; |
+ } |
+ return flags; |
+ } |
+ }; |
+ |
+ private Runnable mProgressUpdater = new Runnable() { |
+ @Override |
+ public void run() { |
+ if (mMediaRouteController.isPlaying()) { |
+ mMediaController.updateProgress(); |
+ mHandler.postDelayed(this, PROGRESS_UPDATE_PERIOD_IN_MS); |
+ } else { |
+ mHandler.removeCallbacks(this); |
+ } |
+ } |
+ }; |
+ |
+ @Override |
+ protected void onCreate(Bundle savedInstanceState) { |
+ super.onCreate(savedInstanceState); |
+ |
+ mMediaRouteController = |
+ RemoteMediaPlayerController.instance().getCurrentlyPlayingMediaRouteController(); |
+ |
+ if (mMediaRouteController == null || mMediaRouteController.routeIsDefaultRoute()) { |
+ // We don't want to do anything for the default (local) route |
+ finish(); |
+ return; |
+ } |
+ |
+ // Make the activity full screen. |
+ requestWindowFeature(Window.FEATURE_NO_TITLE); |
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, |
+ WindowManager.LayoutParams.FLAG_FULLSCREEN); |
+ |
+ // requestWindowFeature must be called before adding content. |
+ setContentView(R.layout.expanded_cast_controller); |
+ mHandler = new Handler(); |
+ |
+ ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content); |
+ rootView.setBackgroundColor(Color.BLACK); |
+ |
+ mMediaRouteController.addUiListener(this); |
+ |
+ // Create transport controller to control video, giving the callback |
+ // interface to receive actions from. |
+ mTransportMediator = new TransportMediator(this, mTransportPerformer); |
+ |
+ // Create and initialize the media control UI. |
+ mMediaController = (MediaController) findViewById(R.id.cast_media_controller); |
+ mMediaController.setMediaPlayer(mTransportMediator); |
+ |
+ View button = getLayoutInflater().inflate(R.layout.cast_controller_media_route_button, |
+ rootView, false); |
+ |
+ if (button instanceof FullscreenMediaRouteButton) { |
+ mMediaRouteButton = (FullscreenMediaRouteButton) button; |
+ rootView.addView(mMediaRouteButton); |
+ mMediaRouteButton.bringToFront(); |
+ mMediaRouteButton.initialize(mMediaRouteController); |
+ } else { |
+ mMediaRouteButton = null; |
+ } |
+ |
+ // Initialize the video info. |
+ setVideoInfo(new RemoteVideoInfo(null, 0, RemoteVideoInfo.PlayerState.STOPPED, 0, null)); |
+ |
+ mMediaController.refresh(); |
+ |
+ scheduleProgressUpdate(); |
+ } |
+ |
+ @Override |
+ protected void onResume() { |
+ super.onResume(); |
+ if (mVideoInfo.state == PlayerState.FINISHED) finish(); |
+ if (mMediaRouteController == null) return; |
+ mMediaRouteController.prepareMediaRoute(); |
+ |
+ ImageView iv = (ImageView) findViewById(R.id.cast_background_image); |
+ if (iv == null) return; |
+ Bitmap posterBitmap = mMediaRouteController.getPoster(); |
+ if (posterBitmap != null) iv.setImageBitmap(posterBitmap); |
+ ApiCompatibilityUtils.setImageAlpha(iv, POSTER_IMAGE_ALPHA); |
+ } |
+ |
+ @Override |
+ protected void onDestroy() { |
+ cleanup(); |
+ super.onDestroy(); |
+ } |
+ |
+ @Override |
+ public boolean dispatchKeyEvent(KeyEvent event) { |
+ int keyCode = event.getKeyCode(); |
+ if ((keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP) |
+ || mVideoInfo.state == PlayerState.FINISHED) { |
+ return super.dispatchKeyEvent(event); |
+ } |
+ |
+ return handleVolumeKeyEvent(mMediaRouteController, event); |
+ } |
+ |
+ private void cleanup() { |
+ if (mHandler != null) mHandler.removeCallbacks(mProgressUpdater); |
+ if (mMediaRouteController != null) mMediaRouteController.removeUiListener(this); |
+ mMediaRouteController = null; |
+ mProgressUpdater = null; |
+ } |
+ |
+ /** |
+ * Sets the remote's video information to display. |
+ */ |
+ private final void setVideoInfo(RemoteVideoInfo videoInfo) { |
+ if ((mVideoInfo == null) ? (videoInfo == null) : mVideoInfo.equals(videoInfo)) return; |
+ |
+ mVideoInfo = videoInfo; |
+ onVideoInfoChanged(); |
+ } |
+ |
+ private void scheduleProgressUpdate() { |
+ mHandler.removeCallbacks(mProgressUpdater); |
+ if (mMediaRouteController.isPlaying()) { |
+ mHandler.post(mProgressUpdater); |
+ } |
+ } |
+ |
+ /** |
+ * Sets the name to display for the device. |
+ */ |
+ private void setScreenName(String screenName) { |
+ if (TextUtils.equals(mScreenName, screenName)) return; |
+ |
+ mScreenName = screenName; |
+ onScreenNameChanged(); |
+ } |
+ |
+ private void onVideoInfoChanged() { |
+ updateUi(); |
+ } |
+ |
+ private void onScreenNameChanged() { |
+ updateUi(); |
+ } |
+ |
+ private void updateUi() { |
+ if (mMediaController == null || mMediaRouteController == null) return; |
+ |
+ String deviceName = mMediaRouteController.getRouteName(); |
+ String castText = ""; |
+ if (deviceName != null) { |
+ castText = getResources().getString(R.string.cast_casting_video, deviceName); |
+ } |
+ TextView castTextView = (TextView) findViewById(R.id.cast_screen_title); |
+ castTextView.setText(castText); |
+ |
+ mMediaController.refresh(); |
+ } |
+ |
+ @Override |
+ public void onRouteSelected(String name, MediaRouteController mediaRouteController) { |
+ setScreenName(name); |
+ } |
+ |
+ @Override |
+ public void onRouteUnselected(MediaRouteController mediaRouteController) { |
+ finish(); |
+ } |
+ |
+ @Override |
+ public void onPrepared(MediaRouteController mediaRouteController) { |
+ // No implementation. |
+ } |
+ |
+ @Override |
+ public void onError(int error, String message) { |
+ if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED) finish(); |
+ } |
+ |
+ @Override |
+ public void onPlaybackStateChanged(PlayerState oldState, PlayerState newState) { |
+ RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo); |
+ videoInfo.state = newState; |
+ setVideoInfo(videoInfo); |
+ |
+ scheduleProgressUpdate(); |
+ |
+ if (newState == PlayerState.FINISHED || newState == PlayerState.INVALIDATED) { |
+ // If we are switching to a finished state, stop the notifications. |
+ finish(); |
+ } |
+ } |
+ |
+ @Override |
+ public void onDurationUpdated(int durationMillis) { |
+ RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo); |
+ videoInfo.durationMillis = durationMillis; |
+ setVideoInfo(videoInfo); |
+ } |
+ |
+ @Override |
+ public void onPositionChanged(int positionMillis) { |
+ RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo); |
+ videoInfo.currentTimeMillis = positionMillis; |
+ setVideoInfo(videoInfo); |
+ } |
+ |
+ @Override |
+ public void onTitleChanged(String title) { |
+ RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo); |
+ videoInfo.title = title; |
+ setVideoInfo(videoInfo); |
+ } |
+ |
+ /** |
+ * Modify remote volume by handling volume keys. |
+ * |
+ * @param controller The remote controller through which the volume will be modified. |
+ * @param event The key event. Its keycode needs to be either {@code KEYCODE_VOLUME_DOWN} or |
+ * {@code KEYCODE_VOLUME_UP} otherwise this method will return false. |
+ * @return True if the event is handled. |
+ */ |
+ private boolean handleVolumeKeyEvent(MediaRouteController controller, KeyEvent event) { |
+ if (!controller.isBeingCast()) return false; |
+ |
+ int action = event.getAction(); |
+ int keyCode = event.getKeyCode(); |
+ // Intercept the volume keys to affect only remote volume. |
+ switch (keyCode) { |
+ case KeyEvent.KEYCODE_VOLUME_DOWN: |
+ if (action == KeyEvent.ACTION_DOWN) controller.setRemoteVolume(-1); |
+ return true; |
+ case KeyEvent.KEYCODE_VOLUME_UP: |
+ if (action == KeyEvent.ACTION_DOWN) controller.setRemoteVolume(1); |
+ return true; |
+ default: |
+ return false; |
+ } |
+ } |
+ |
+ /** |
+ * Launches the ExpandedControllerActivity as a new task. |
+ * |
+ * @param context the Context to start this activity within. |
+ */ |
+ public static void startActivity(Context context) { |
+ if (context == null) return; |
+ |
+ Intent intent = new Intent(context, ExpandedControllerActivity.class); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ context.startActivity(intent); |
+ } |
+} |