Chromium Code Reviews| 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..893293af04c405f5a9227d74caa3b053d65cc389 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java |
| @@ -0,0 +1,352 @@ |
| +// 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 org.chromium.chrome.R; |
|
whywhat
2015/02/25 16:31:31
nit: ditto
aberent
2015/03/11 18:29:57
Done.
|
| +import com.google.android.gms.cast.CastMediaControlIntent; |
| + |
| +import org.chromium.base.ApiCompatibilityUtils; |
| +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 {@link 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(); |
| + mMediaRouteController.prepareMediaRoute(); |
| + |
| + ImageView iv = (ImageView) findViewById(R.id.cast_background_image); |
| + if (iv == null) return; |
| + Bitmap posterBitmap = mMediaRouteController.getMediaStateListener().getPosterBitmap(); |
| + 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.athome_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); |
| + } |
| +} |