diff options
author | Chong Zhang <chz@google.com> | 2013-10-21 17:24:41 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2013-10-21 17:24:41 -0700 |
commit | 58ef2c388c9d1e7a27dfdbe84e90456cb1d2d157 (patch) | |
tree | 49913b34f47d4fb04aec4ffc4542ec246b5dc37c | |
parent | 10f225872313bd36188eaeec5c2417a8cb9ce1da (diff) | |
parent | fd6d06a19ce06d0f3e4fa8c58c283be2f07aabfc (diff) | |
download | android_development-58ef2c388c9d1e7a27dfdbe84e90456cb1d2d157.tar.gz android_development-58ef2c388c9d1e7a27dfdbe84e90456cb1d2d157.tar.bz2 android_development-58ef2c388c9d1e7a27dfdbe84e90456cb1d2d157.zip |
am fd6d06a1: mrp sample: refactor and use helper class
* commit 'fd6d06a19ce06d0f3e4fa8c58c283be2f07aabfc':
mrp sample: refactor and use helper class
10 files changed, 1947 insertions, 1692 deletions
diff --git a/samples/Support7Demos/res/values/strings.xml b/samples/Support7Demos/res/values/strings.xml index cbbc64076..1c6efc971 100644 --- a/samples/Support7Demos/res/values/strings.xml +++ b/samples/Support7Demos/res/values/strings.xml @@ -32,7 +32,9 @@ <string name="sample_media_route_provider_service">Media Route Provider Service Support Library Sample</string> <string name="fixed_volume_route_name">Fixed Volume Remote Playback Route</string> - <string name="variable_volume_route_name">Variable Volume Remote Playback Route</string> + <string name="variable_volume_basic_route_name">Variable Volume (Basic) Remote Playback Route</string> + <string name="variable_volume_queuing_route_name">Variable Volume (Queuing) Remote Playback Route</string> + <string name="variable_volume_session_route_name">Variable Volume (Session) Remote Playback Route</string> <string name="sample_route_description">Sample route from Support7Demos</string> <!-- GridLayout --> diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/LocalPlayer.java b/samples/Support7Demos/src/com/example/android/supportv7/media/LocalPlayer.java new file mode 100644 index 000000000..57cdd88b2 --- /dev/null +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/LocalPlayer.java @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.supportv7.media; + +import android.app.Activity; +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.support.v7.media.MediaRouter.RouteInfo; +import android.support.v7.media.MediaItemStatus; +import android.util.Log; +import android.view.Display; +import android.view.Gravity; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.example.android.supportv7.R; + +import java.io.IOException; + +/** + * Handles playback of a single media item using MediaPlayer. + */ +public abstract class LocalPlayer extends Player implements + MediaPlayer.OnPreparedListener, + MediaPlayer.OnCompletionListener, + MediaPlayer.OnErrorListener, + MediaPlayer.OnSeekCompleteListener { + private static final String TAG = "LocalPlayer"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final int STATE_IDLE = 0; + private static final int STATE_PLAY_PENDING = 1; + private static final int STATE_READY = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + + private final Context mContext; + private final Handler mHandler = new Handler(); + private MediaPlayer mMediaPlayer; + private int mState = STATE_IDLE; + private int mSeekToPos; + private int mVideoWidth; + private int mVideoHeight; + private Surface mSurface; + private SurfaceHolder mSurfaceHolder; + + public LocalPlayer(Context context) { + mContext = context; + + // reset media player + reset(); + } + + @Override + public boolean isRemotePlayback() { + return false; + } + + @Override + public boolean isQueuingSupported() { + return false; + } + + @Override + public void connect(RouteInfo route) { + if (DEBUG) { + Log.d(TAG, "connecting to: " + route); + } + } + + @Override + public void release() { + if (DEBUG) { + Log.d(TAG, "releasing"); + } + // release media player + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + + // Player + @Override + public void play(final PlaylistItem item) { + if (DEBUG) { + Log.d(TAG, "play: item=" + item); + } + reset(); + mSeekToPos = (int)item.getPosition(); + try { + mMediaPlayer.setDataSource(mContext, item.getUri()); + mMediaPlayer.prepareAsync(); + } catch (IllegalStateException e) { + Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri()); + } catch (IOException e) { + Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri()); + } catch (IllegalArgumentException e) { + Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri()); + } catch (SecurityException e) { + Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri()); + } + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) { + resume(); + } else { + pause(); + } + } + + @Override + public void seek(final PlaylistItem item) { + if (DEBUG) { + Log.d(TAG, "seek: item=" + item); + } + int pos = (int)item.getPosition(); + if (mState == STATE_PLAYING || mState == STATE_PAUSED) { + mMediaPlayer.seekTo(pos); + mSeekToPos = pos; + } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) { + // Seek before onPrepared() arrives, + // need to performed delayed seek in onPrepared() + mSeekToPos = pos; + } + } + + @Override + public void getStatus(final PlaylistItem item, final boolean update) { + if (mState == STATE_PLAYING || mState == STATE_PAUSED) { + // use mSeekToPos if we're currently seeking (mSeekToPos is reset + // when seeking is completed) + item.setDuration(mMediaPlayer.getDuration()); + item.setPosition(mSeekToPos > 0 ? + mSeekToPos : mMediaPlayer.getCurrentPosition()); + item.setTimestamp(SystemClock.elapsedRealtime()); + } + if (update && mCallback != null) { + mCallback.onPlaylistReady(); + } + } + + @Override + public void pause() { + if (DEBUG) { + Log.d(TAG, "pause"); + } + if (mState == STATE_PLAYING) { + mMediaPlayer.pause(); + mState = STATE_PAUSED; + } + } + + @Override + public void resume() { + if (DEBUG) { + Log.d(TAG, "resume"); + } + if (mState == STATE_READY || mState == STATE_PAUSED) { + mMediaPlayer.start(); + mState = STATE_PLAYING; + } else if (mState == STATE_IDLE){ + mState = STATE_PLAY_PENDING; + } + } + + @Override + public void stop() { + if (DEBUG) { + Log.d(TAG, "stop"); + } + if (mState == STATE_PLAYING || mState == STATE_PAUSED) { + mMediaPlayer.stop(); + mState = STATE_IDLE; + } + } + + @Override + public void enqueue(final PlaylistItem item) { + throw new UnsupportedOperationException("LocalPlayer doesn't support enqueue!"); + } + + @Override + public PlaylistItem remove(String iid) { + throw new UnsupportedOperationException("LocalPlayer doesn't support remove!"); + } + + //MediaPlayer Listeners + @Override + public void onPrepared(MediaPlayer mp) { + if (DEBUG) { + Log.d(TAG, "onPrepared"); + } + mHandler.post(new Runnable() { + @Override + public void run() { + if (mState == STATE_IDLE) { + mState = STATE_READY; + updateVideoRect(); + } else if (mState == STATE_PLAY_PENDING) { + mState = STATE_PLAYING; + updateVideoRect(); + if (mSeekToPos > 0) { + if (DEBUG) { + Log.d(TAG, "seek to initial pos: " + mSeekToPos); + } + mMediaPlayer.seekTo(mSeekToPos); + } + mMediaPlayer.start(); + } + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + }); + } + + @Override + public void onCompletion(MediaPlayer mp) { + if (DEBUG) { + Log.d(TAG, "onCompletion"); + } + mHandler.post(new Runnable() { + @Override + public void run() { + if (mCallback != null) { + mCallback.onCompletion(); + } + } + }); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + if (DEBUG) { + Log.d(TAG, "onError"); + } + mHandler.post(new Runnable() { + @Override + public void run() { + if (mCallback != null) { + mCallback.onError(); + } + } + }); + // return true so that onCompletion is not called + return true; + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + if (DEBUG) { + Log.d(TAG, "onSeekComplete"); + } + mHandler.post(new Runnable() { + @Override + public void run() { + mSeekToPos = 0; + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + }); + } + + protected Context getContext() { return mContext; } + protected MediaPlayer getMediaPlayer() { return mMediaPlayer; } + protected int getVideoWidth() { return mVideoWidth; } + protected int getVideoHeight() { return mVideoHeight; } + protected void setSurface(Surface surface) { + mSurface = surface; + mSurfaceHolder = null; + updateSurface(); + } + + protected void setSurface(SurfaceHolder surfaceHolder) { + mSurface = null; + mSurfaceHolder = surfaceHolder; + updateSurface(); + } + + protected void removeSurface(SurfaceHolder surfaceHolder) { + if (surfaceHolder == mSurfaceHolder) { + setSurface((SurfaceHolder)null); + } + } + + protected void updateSurface() { + if (mMediaPlayer == null) { + // just return if media player is already gone + return; + } + if (mSurface != null) { + // The setSurface API does not exist until V14+. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + ICSMediaPlayer.setSurface(mMediaPlayer, mSurface); + } else { + throw new UnsupportedOperationException("MediaPlayer does not support " + + "setSurface() on this version of the platform."); + } + } else if (mSurfaceHolder != null) { + mMediaPlayer.setDisplay(mSurfaceHolder); + } else { + mMediaPlayer.setDisplay(null); + } + } + + protected abstract void updateSize(); + + private void reset() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnPreparedListener(this); + mMediaPlayer.setOnCompletionListener(this); + mMediaPlayer.setOnErrorListener(this); + mMediaPlayer.setOnSeekCompleteListener(this); + updateSurface(); + mState = STATE_IDLE; + mSeekToPos = 0; + } + + private void updateVideoRect() { + if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) { + int width = mMediaPlayer.getVideoWidth(); + int height = mMediaPlayer.getVideoHeight(); + if (width > 0 && height > 0) { + mVideoWidth = width; + mVideoHeight = height; + updateSize(); + } else { + Log.e(TAG, "video rect is 0x0!"); + mVideoWidth = mVideoHeight = 0; + } + } + } + + private static final class ICSMediaPlayer { + public static final void setSurface(MediaPlayer player, Surface surface) { + player.setSurface(surface); + } + } + + /** + * Handles playback of a single media item using MediaPlayer in SurfaceView + */ + public static class SurfaceViewPlayer extends LocalPlayer implements + SurfaceHolder.Callback { + private static final String TAG = "SurfaceViewPlayer"; + private RouteInfo mRoute; + private final SurfaceView mSurfaceView; + private final FrameLayout mLayout; + private DemoPresentation mPresentation; + + public SurfaceViewPlayer(Context context) { + super(context); + + mLayout = (FrameLayout)((Activity)context).findViewById(R.id.player); + mSurfaceView = (SurfaceView)((Activity)context).findViewById(R.id.surface_view); + + // add surface holder callback + SurfaceHolder holder = mSurfaceView.getHolder(); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + holder.addCallback(this); + } + + @Override + public void connect(RouteInfo route) { + super.connect(route); + mRoute = route; + } + + @Override + public void release() { + super.release(); + + // dismiss presentation display + if (mPresentation != null) { + Log.i(TAG, "Dismissing presentation because the activity is no longer visible."); + mPresentation.dismiss(); + mPresentation = null; + } + + // remove surface holder callback + SurfaceHolder holder = mSurfaceView.getHolder(); + holder.removeCallback(this); + + // hide the surface view when SurfaceViewPlayer is destroyed + mSurfaceView.setVisibility(View.GONE); + mLayout.setVisibility(View.GONE); + } + + @Override + public void updatePresentation() { + // Get the current route and its presentation display. + Display presentationDisplay = mRoute != null ? mRoute.getPresentationDisplay() : null; + + // Dismiss the current presentation if the display has changed. + if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { + Log.i(TAG, "Dismissing presentation because the current route no longer " + + "has a presentation display."); + mPresentation.dismiss(); + mPresentation = null; + } + + // Show a new presentation if needed. + if (mPresentation == null && presentationDisplay != null) { + Log.i(TAG, "Showing presentation on display: " + presentationDisplay); + mPresentation = new DemoPresentation(getContext(), presentationDisplay); + mPresentation.setOnDismissListener(mOnDismissListener); + try { + mPresentation.show(); + } catch (WindowManager.InvalidDisplayException ex) { + Log.w(TAG, "Couldn't show presentation! Display was removed in " + + "the meantime.", ex); + mPresentation = null; + } + } + + updateContents(); + } + + // SurfaceHolder.Callback + @Override + public void surfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + if (DEBUG) { + Log.d(TAG, "surfaceChanged: " + width + "x" + height); + } + setSurface(holder); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + if (DEBUG) { + Log.d(TAG, "surfaceCreated"); + } + setSurface(holder); + updateSize(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (DEBUG) { + Log.d(TAG, "surfaceDestroyed"); + } + removeSurface(holder); + } + + @Override + protected void updateSize() { + int width = getVideoWidth(); + int height = getVideoHeight(); + if (width > 0 && height > 0) { + if (mPresentation == null) { + int surfaceWidth = mLayout.getWidth(); + int surfaceHeight = mLayout.getHeight(); + + // Calculate the new size of mSurfaceView, so that video is centered + // inside the framelayout with proper letterboxing/pillarboxing + ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); + if (surfaceWidth * height < surfaceHeight * width) { + // Black bars on top&bottom, mSurfaceView has full layout width, + // while height is derived from video's aspect ratio + lp.width = surfaceWidth; + lp.height = surfaceWidth * height / width; + } else { + // Black bars on left&right, mSurfaceView has full layout height, + // while width is derived from video's aspect ratio + lp.width = surfaceHeight * width / height; + lp.height = surfaceHeight; + } + Log.i(TAG, "video rect is " + lp.width + "x" + lp.height); + mSurfaceView.setLayoutParams(lp); + } else { + mPresentation.updateSize(width, height); + } + } + } + + private void updateContents() { + // Show either the content in the main activity or the content in the presentation + if (mPresentation != null) { + mLayout.setVisibility(View.GONE); + mSurfaceView.setVisibility(View.GONE); + } else { + mLayout.setVisibility(View.VISIBLE); + mSurfaceView.setVisibility(View.VISIBLE); + } + } + + // Listens for when presentations are dismissed. + private final DialogInterface.OnDismissListener mOnDismissListener = + new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (dialog == mPresentation) { + Log.i(TAG, "Presentation dismissed."); + mPresentation = null; + updateContents(); + } + } + }; + + // Presentation + private final class DemoPresentation extends Presentation { + private SurfaceView mPresentationSurfaceView; + + public DemoPresentation(Context context, Display display) { + super(context, display); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Inflate the layout. + setContentView(R.layout.sample_media_router_presentation); + + // Set up the surface view. + mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view); + SurfaceHolder holder = mPresentationSurfaceView.getHolder(); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + holder.addCallback(SurfaceViewPlayer.this); + Log.i(TAG, "Presentation created"); + } + + public void updateSize(int width, int height) { + int surfaceHeight = getWindow().getDecorView().getHeight(); + int surfaceWidth = getWindow().getDecorView().getWidth(); + ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams(); + if (surfaceWidth * height < surfaceHeight * width) { + lp.width = surfaceWidth; + lp.height = surfaceWidth * height / width; + } else { + lp.width = surfaceHeight * width / height; + lp.height = surfaceHeight; + } + Log.i(TAG, "Presentation video rect is " + lp.width + "x" + lp.height); + mPresentationSurfaceView.setLayoutParams(lp); + } + } + } + + /** + * Handles playback of a single media item using MediaPlayer in + * OverlayDisplayWindow. + */ + public static class OverlayPlayer extends LocalPlayer implements + OverlayDisplayWindow.OverlayWindowListener { + private static final String TAG = "OverlayPlayer"; + private final OverlayDisplayWindow mOverlay; + + public OverlayPlayer(Context context) { + super(context); + + mOverlay = OverlayDisplayWindow.create(getContext(), + getContext().getResources().getString( + R.string.sample_media_route_provider_remote), + 1024, 768, Gravity.CENTER); + + mOverlay.setOverlayWindowListener(this); + } + + @Override + public void connect(RouteInfo route) { + super.connect(route); + mOverlay.show(); + } + + @Override + public void release() { + super.release(); + mOverlay.dismiss(); + } + + @Override + protected void updateSize() { + int width = getVideoWidth(); + int height = getVideoHeight(); + if (width > 0 && height > 0) { + mOverlay.updateAspectRatio(width, height); + } + } + + // OverlayDisplayWindow.OverlayWindowListener + @Override + public void onWindowCreated(Surface surface) { + setSurface(surface); + } + + @Override + public void onWindowCreated(SurfaceHolder surfaceHolder) { + setSurface(surfaceHolder); + } + + @Override + public void onWindowDestroyed() { + setSurface((SurfaceHolder)null); + } + } +} diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaPlayerWrapper.java b/samples/Support7Demos/src/com/example/android/supportv7/media/MediaPlayerWrapper.java deleted file mode 100644 index 8b82631be..000000000 --- a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaPlayerWrapper.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.supportv7.media; - -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.util.Log; -import android.media.MediaPlayer; -import android.view.Surface; -import android.view.SurfaceHolder; -import java.io.IOException; - -/** - * MediaPlayerWrapper handles playback of a single media item, and is used for - * both local and remote playback. - */ -public class MediaPlayerWrapper implements - MediaPlayer.OnPreparedListener, - MediaPlayer.OnCompletionListener, - MediaPlayer.OnErrorListener, - MediaPlayer.OnSeekCompleteListener, - MediaSessionManager.Callback { - private static final String TAG = "MediaPlayerWrapper"; - private static final boolean DEBUG = false; - - private static final int STATE_IDLE = 0; - private static final int STATE_PLAY_PENDING = 1; - private static final int STATE_READY = 2; - private static final int STATE_PLAYING = 3; - private static final int STATE_PAUSED = 4; - - private final Context mContext; - private final Handler mHandler = new Handler(); - private MediaPlayer mMediaPlayer; - private int mState = STATE_IDLE; - private Callback mCallback; - private Surface mSurface; - private SurfaceHolder mSurfaceHolder; - private int mSeekToPos; - - public MediaPlayerWrapper(Context context) { - mContext = context; - reset(); - } - - public void release() { - onStop(); - mMediaPlayer.release(); - } - - public void setCallback(Callback cb) { - mCallback = cb; - } - - // MediaSessionManager.Callback - @Override - public void onNewItem(Uri uri) { - reset(); - try { - mMediaPlayer.setDataSource(mContext, uri); - mMediaPlayer.prepareAsync(); - } catch (IllegalStateException e) { - Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + uri); - } catch (IOException e) { - Log.e(TAG, "MediaPlayer throws IOException, uri=" + uri); - } catch (IllegalArgumentException e) { - Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + uri); - } catch (SecurityException e) { - Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + uri); - } - } - - @Override - public void onStart() { - if (mState == STATE_READY || mState == STATE_PAUSED) { - mMediaPlayer.start(); - mState = STATE_PLAYING; - } else if (mState == STATE_IDLE){ - mState = STATE_PLAY_PENDING; - } - } - - @Override - public void onStop() { - if (mState == STATE_PLAYING || mState == STATE_PAUSED) { - mMediaPlayer.stop(); - mState = STATE_IDLE; - } - } - - @Override - public void onPause() { - if (mState == STATE_PLAYING) { - mMediaPlayer.pause(); - mState = STATE_PAUSED; - } - } - - @Override - public void onSeek(long pos) { - if (DEBUG) { - Log.d(TAG, "onSeek: pos=" + pos); - } - if (mState == STATE_PLAYING || mState == STATE_PAUSED) { - mMediaPlayer.seekTo((int)pos); - mSeekToPos = (int)pos; - } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) { - // Seek before onPrepared() arrives, - // need to performed delayed seek in onPrepared() - mSeekToPos = (int)pos; - } - } - - @Override - public void onGetStatus(MediaQueueItem item) { - if (mState == STATE_PLAYING || mState == STATE_PAUSED) { - // use mSeekToPos if we're currently seeking (mSeekToPos is reset - // when seeking is completed) - item.setContentDuration(mMediaPlayer.getDuration()); - item.setContentPosition(mSeekToPos > 0 ? - mSeekToPos : mMediaPlayer.getCurrentPosition()); - } - } - - public void setSurface(Surface surface) { - mSurface = surface; - mSurfaceHolder = null; - updateSurface(); - } - - public void setSurface(SurfaceHolder surfaceHolder) { - mSurface = null; - mSurfaceHolder = surfaceHolder; - updateSurface(); - } - - //MediaPlayer Listeners - @Override - public void onPrepared(MediaPlayer mp) { - if (DEBUG) { - Log.d(TAG,"onPrepared"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - if (mState == STATE_IDLE) { - mState = STATE_READY; - updateVideoRect(); - } else if (mState == STATE_PLAY_PENDING) { - mState = STATE_PLAYING; - updateVideoRect(); - if (mSeekToPos > 0) { - Log.d(TAG, "Seeking to initial pos " + mSeekToPos); - mMediaPlayer.seekTo((int)mSeekToPos); - } - mMediaPlayer.start(); - } - if (mCallback != null) { - mCallback.onStatusChanged(); - } - } - }); - } - - @Override - public void onCompletion(MediaPlayer mp) { - if (DEBUG) { - Log.d(TAG,"onCompletion"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - if (mCallback != null) { - mCallback.onCompletion(); - } - } - }); - } - - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - if (DEBUG) { - Log.d(TAG,"onError"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - if (mCallback != null) { - mCallback.onError(); - } - } - }); - // return true so that onCompletion is not called - return true; - } - - @Override - public void onSeekComplete(MediaPlayer mp) { - if (DEBUG) { - Log.d(TAG, "onSeekComplete"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - mSeekToPos = 0; - if (mCallback != null) { - mCallback.onStatusChanged(); - } - } - }); - } - - public void reset() { - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnCompletionListener(this); - mMediaPlayer.setOnErrorListener(this); - mMediaPlayer.setOnSeekCompleteListener(this); - updateSurface(); - mState = STATE_IDLE; - mSeekToPos = 0; - } - - private void updateVideoRect() { - if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) { - int videoWidth = mMediaPlayer.getVideoWidth(); - int videoHeight = mMediaPlayer.getVideoHeight(); - if (videoWidth > 0 && videoHeight > 0) { - if (mCallback != null) { - mCallback.onSizeChanged(videoWidth, videoHeight); - } - } else { - Log.e(TAG, "video rect is 0x0!"); - } - } - } - - private void updateSurface() { - if (mSurface != null) { - // The setSurface API does not exist until V14+. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - ICSMediaPlayer.setSurface(mMediaPlayer, mSurface); - } else { - throw new UnsupportedOperationException("MediaPlayer does not support " - + "setSurface() on this version of the platform."); - } - } else if (mSurfaceHolder != null) { - mMediaPlayer.setDisplay(mSurfaceHolder); - } else { - mMediaPlayer.setDisplay(null); - } - } - - public static abstract class Callback { - public void onError() {} - public void onCompletion() {} - public void onStatusChanged() {} - public void onSizeChanged(int width, int height) {} - } - - private static final class ICSMediaPlayer { - public static final void setSurface(MediaPlayer player, Surface surface) { - player.setSurface(surface); - } - } -} diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaSessionManager.java b/samples/Support7Demos/src/com/example/android/supportv7/media/MediaSessionManager.java deleted file mode 100644 index 925899824..000000000 --- a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaSessionManager.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.supportv7.media; - -import java.util.List; -import java.util.ArrayList; -import android.util.Log; -import android.net.Uri; -import android.app.PendingIntent; -import android.support.v7.media.MediaItemStatus; - -/** - * MediaSessionManager manages a media session as a queue. It supports common - * queuing behaviors such as enqueue/remove of media items, pause/resume/stop, - * etc. - * - * Actual playback of a single media item is abstracted into a set of - * callbacks MediaSessionManager.Callback, and is handled outside this class. - */ -public class MediaSessionManager { - private static final String TAG = "MediaSessionManager"; - - private String mSessionId; - private String mItemId; - private String mCurItemId; - private boolean mIsPlaying = true; - private Callback mCallback; - private List<MediaQueueItem> mQueue = new ArrayList<MediaQueueItem>(); - - public MediaSessionManager() { - } - - // Queue item (this maps to the ENQUEUE in the API which queues the item) - public MediaQueueItem enqueue(String sid, Uri uri, PendingIntent receiver) { - // fail if queue id is invalid - if (sid != null && !sid.equals(mSessionId)) { - Log.d(TAG, "invalid session id, mSessionId="+mSessionId+", sid="+sid); - return null; - } - - // if queue id is unspecified, invalidate current queue - if (sid == null) { - invalidate(); - } - - mQueue.add(new MediaQueueItem(mSessionId, mItemId, uri, receiver)); - - if (updatePlaybackState()) { - MediaQueueItem item = findItem(mItemId); - mItemId = inc(mItemId); - if (item == null) { - Log.d(TAG, "item not found after it's added"); - } - return item; - } - - removeItem(mItemId, MediaItemStatus.PLAYBACK_STATE_ERROR); - return null; - } - - public MediaQueueItem remove(String sid, String iid) { - if (sid == null || !sid.equals(mSessionId)) { - return null; - } - return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED); - } - - // handles ERROR / COMPLETION - public MediaQueueItem finish(boolean error) { - return removeItem(mCurItemId, error ? MediaItemStatus.PLAYBACK_STATE_ERROR : - MediaItemStatus.PLAYBACK_STATE_FINISHED); - } - - public MediaQueueItem seek(String sid, String iid, long pos) { - if (sid == null || !sid.equals(mSessionId)) { - return null; - } - for (int i = 0; i < mQueue.size(); i++) { - MediaQueueItem item = mQueue.get(i); - if (iid.equals(item.getItemId())) { - if (pos != item.getContentPosition()) { - item.setContentPosition(pos); - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - if (mCallback != null) { - mCallback.onSeek(pos); - } - } - } - return item; - } - } - return null; - } - - public MediaQueueItem getCurrentItem() { - return getStatus(mSessionId, mCurItemId); - } - - public MediaQueueItem getStatus(String sid, String iid) { - if (sid == null || !sid.equals(mSessionId)) { - return null; - } - for (int i = 0; i < mQueue.size(); i++) { - MediaQueueItem item = mQueue.get(i); - if (iid.equals(item.getItemId())) { - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - if (mCallback != null) { - mCallback.onGetStatus(item); - } - } - return item; - } - } - return null; - } - - public boolean pause(String sid) { - if (sid == null || !sid.equals(mSessionId)) { - return false; - } - mIsPlaying = false; - return updatePlaybackState(); - } - - public boolean resume(String sid) { - if (sid == null || !sid.equals(mSessionId)) { - return false; - } - mIsPlaying = true; - return updatePlaybackState(); - } - - public boolean stop(String sid) { - if (sid == null || !sid.equals(mSessionId)) { - return false; - } - clear(); - return true; - } - - public void setCallback(Callback cb) { - mCallback = cb; - } - - @Override - public String toString() { - String result = "Media Queue: "; - if (!mQueue.isEmpty()) { - for (MediaQueueItem item : mQueue) { - result += "\n" + item.toString(); - } - } else { - result += "<empty>"; - } - return result; - } - - private String inc(String id) { - return (id == null) ? "0" : Integer.toString(Integer.parseInt(id)+1); - } - - // play the item at queue head - private void play() { - MediaQueueItem item = mQueue.get(0); - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - mCurItemId = item.getItemId(); - if (mCallback != null) { - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) { - mCallback.onNewItem(item.getUri()); - } - mCallback.onStart(); - } - item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING); - } - } - - // stop the currently playing item - private void stop() { - MediaQueueItem item = mQueue.get(0); - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - if (mCallback != null) { - mCallback.onStop(); - } - item.setState(MediaItemStatus.PLAYBACK_STATE_FINISHED); - } - } - - // pause the currently playing item - private void pause() { - MediaQueueItem item = mQueue.get(0); - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) { - if (mCallback != null) { - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) { - mCallback.onNewItem(item.getUri()); - } else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) { - mCallback.onPause(); - } - } - item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED); - } - } - - private void clear() { - if (mQueue.size() > 0) { - stop(); - mQueue.clear(); - } - } - - private void invalidate() { - clear(); - mSessionId = inc(mSessionId); - mItemId = "0"; - mIsPlaying = true; - } - - private boolean updatePlaybackState() { - if (mQueue.isEmpty()) { - return true; - } - - if (mIsPlaying) { - play(); - } else { - pause(); - } - return true; - } - - private MediaQueueItem findItem(String iid) { - for (MediaQueueItem item : mQueue) { - if (iid.equals(item.getItemId())) { - return item; - } - } - return null; - } - - private MediaQueueItem removeItem(String iid, int state) { - List<MediaQueueItem> queue = - new ArrayList<MediaQueueItem>(mQueue.size()); - MediaQueueItem found = null; - for (MediaQueueItem item : mQueue) { - if (iid.equals(item.getItemId())) { - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - stop(); - } - item.setState(state); - found = item; - } else { - queue.add(item); - } - } - if (found != null) { - mQueue = queue; - updatePlaybackState(); - } - return found; - } - - public interface Callback { - public void onStart(); - public void onPause(); - public void onStop(); - public void onSeek(long pos); - public void onGetStatus(MediaQueueItem item); - public void onNewItem(Uri uri); - } -} diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/Player.java b/samples/Support7Demos/src/com/example/android/supportv7/media/Player.java new file mode 100644 index 000000000..b230c1d4a --- /dev/null +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/Player.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.supportv7.media; + +import android.net.Uri; +import android.content.Context; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouter.RouteInfo; + +/** + * Abstraction of common playback operations of media items, such as play, + * seek, etc. Used by PlaybackManager as a backend to handle actual playback + * of media items. + */ +public abstract class Player { + protected Callback mCallback; + + public abstract boolean isRemotePlayback(); + public abstract boolean isQueuingSupported(); + + public abstract void connect(RouteInfo route); + public abstract void release(); + + // basic operations that are always supported + public abstract void play(final PlaylistItem item); + public abstract void seek(final PlaylistItem item); + public abstract void getStatus(final PlaylistItem item, final boolean update); + public abstract void pause(); + public abstract void resume(); + public abstract void stop(); + + // advanced queuing (enqueue & remove) are only supported + // if isQueuingSupported() returns true + public abstract void enqueue(final PlaylistItem item); + public abstract PlaylistItem remove(String iid); + + // route statistics + public void updateStatistics() {} + public String getStatistics() { return ""; } + + // presentation display + public void updatePresentation() {} + + public void setCallback(Callback callback) { + mCallback = callback; + } + + public static Player create(Context context, RouteInfo route) { + Player player; + if (route != null && route.supportsControlCategory( + MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { + player = new RemotePlayer(context); + } else if (route != null) { + player = new LocalPlayer.SurfaceViewPlayer(context); + } else { + player = new LocalPlayer.OverlayPlayer(context); + } + player.connect(route); + return player; + } + + public interface Callback { + void onError(); + void onCompletion(); + void onPlaylistChanged(); + void onPlaylistReady(); + } +}
\ No newline at end of file diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaQueueItem.java b/samples/Support7Demos/src/com/example/android/supportv7/media/PlaylistItem.java index 5440d86e9..9a12d5919 100644 --- a/samples/Support7Demos/src/com/example/android/supportv7/media/MediaQueueItem.java +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/PlaylistItem.java @@ -16,40 +16,54 @@ package com.example.android.supportv7.media; -import android.support.v7.media.MediaItemStatus; -import android.net.Uri; import android.app.PendingIntent; +import android.net.Uri; +import android.os.SystemClock; +import android.support.v7.media.MediaItemStatus; /** - * MediaQueueItem helps keep track of the current status of an media item. + * PlaylistItem helps keep track of the current status of an media item. */ -final class MediaQueueItem { +final class PlaylistItem { // immutables private final String mSessionId; private final String mItemId; private final Uri mUri; + private final String mMime; private final PendingIntent mUpdateReceiver; // changeable states private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING; private long mContentPosition; private long mContentDuration; + private long mTimestamp; + private String mRemoteItemId; - public MediaQueueItem(String qid, String iid, Uri uri, PendingIntent pi) { + public PlaylistItem(String qid, String iid, Uri uri, String mime, PendingIntent pi) { mSessionId = qid; mItemId = iid; mUri = uri; + mMime = mime; mUpdateReceiver = pi; + setTimestamp(SystemClock.elapsedRealtime()); + } + + public void setRemoteItemId(String riid) { + mRemoteItemId = riid; } public void setState(int state) { mPlaybackState = state; } - public void setContentPosition(long pos) { + public void setPosition(long pos) { mContentPosition = pos; } - public void setContentDuration(long duration) { + public void setTimestamp(long ts) { + mTimestamp = ts; + } + + public void setDuration(long duration) { mContentDuration = duration; } @@ -61,6 +75,10 @@ final class MediaQueueItem { return mItemId; } + public String getRemoteItemId() { + return mRemoteItemId; + } + public Uri getUri() { return mUri; } @@ -73,18 +91,23 @@ final class MediaQueueItem { return mPlaybackState; } - public long getContentPosition() { + public long getPosition() { return mContentPosition; } - public long getContentDuration() { + public long getDuration() { return mContentDuration; } + public long getTimestamp() { + return mTimestamp; + } + public MediaItemStatus getStatus() { return new MediaItemStatus.Builder(mPlaybackState) .setContentPosition(mContentPosition) .setContentDuration(mContentDuration) + .setTimestamp(mTimestamp) .build(); } @@ -101,6 +124,7 @@ final class MediaQueueItem { "ERROR" }; return "[" + mSessionId + "|" + mItemId + "|" + + (mRemoteItemId != null ? mRemoteItemId : "-") + "|" + state[mPlaybackState] + "] " + mUri.toString(); } }
\ No newline at end of file diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/RemotePlayer.java b/samples/Support7Demos/src/com/example/android/supportv7/media/RemotePlayer.java new file mode 100644 index 000000000..d25f6b3bc --- /dev/null +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/RemotePlayer.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.supportv7.media; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.media.MediaItemStatus; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouter.ControlRequestCallback; +import android.support.v7.media.MediaRouter.RouteInfo; +import android.support.v7.media.MediaSessionStatus; +import android.support.v7.media.RemotePlaybackClient; +import android.support.v7.media.RemotePlaybackClient.ItemActionCallback; +import android.support.v7.media.RemotePlaybackClient.SessionActionCallback; +import android.support.v7.media.RemotePlaybackClient.StatusCallback; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles playback of media items using a remote route. + * + * This class is used as a backend by PlaybackManager to feed media items to + * the remote route. When the remote route doesn't support queuing, media items + * are fed one-at-a-time; otherwise media items are enqueued to the remote side. + */ +public class RemotePlayer extends Player { + private static final String TAG = "RemotePlayer"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private Context mContext; + private RouteInfo mRoute; + private boolean mEnqueuePending; + private String mStatsInfo = ""; + private List<PlaylistItem> mTempQueue = new ArrayList<PlaylistItem>(); + + private RemotePlaybackClient mClient; + private StatusCallback mStatusCallback = new StatusCallback() { + @Override + public void onItemStatusChanged(Bundle data, + String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("onItemStatusChanged", sessionId, sessionStatus, itemId, itemStatus); + if (mCallback != null) { + if (itemStatus.getPlaybackState() == + MediaItemStatus.PLAYBACK_STATE_FINISHED) { + mCallback.onCompletion(); + } else if (itemStatus.getPlaybackState() == + MediaItemStatus.PLAYBACK_STATE_ERROR) { + mCallback.onError(); + } + } + } + + @Override + public void onSessionStatusChanged(Bundle data, + String sessionId, MediaSessionStatus sessionStatus) { + logStatus("onSessionStatusChanged", sessionId, sessionStatus, null, null); + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onSessionChanged(String sessionId) { + if (DEBUG) { + Log.d(TAG, "onSessionChanged: sessionId=" + sessionId); + } + } + }; + + public RemotePlayer(Context context) { + mContext = context; + } + + @Override + public boolean isRemotePlayback() { + return true; + } + + @Override + public boolean isQueuingSupported() { + return mClient.isQueuingSupported(); + } + + @Override + public void connect(RouteInfo route) { + mRoute = route; + mClient = new RemotePlaybackClient(mContext, route); + mClient.setStatusCallback(mStatusCallback); + + if (DEBUG) { + Log.d(TAG, "connected to: " + route + + ", isRemotePlaybackSupported: " + mClient.isRemotePlaybackSupported() + + ", isQueuingSupported: "+ mClient.isQueuingSupported()); + } + } + + @Override + public void release() { + mClient.release(); + + if (DEBUG) { + Log.d(TAG, "released."); + } + } + + // basic playback operations that are always supported + @Override + public void play(final PlaylistItem item) { + if (DEBUG) { + Log.d(TAG, "play: item=" + item); + } + mClient.play(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("play: succeeded", sessionId, sessionStatus, itemId, itemStatus); + item.setRemoteItemId(itemId); + if (item.getPosition() > 0) { + seekInternal(item); + } + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { + pause(); + } + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("play: failed", error, code); + } + }); + } + + @Override + public void seek(final PlaylistItem item) { + seekInternal(item); + } + + @Override + public void getStatus(final PlaylistItem item, final boolean update) { + if (!mClient.hasSession() || item.getRemoteItemId() == null) { + // if session is not valid or item id not assigend yet. + // just return, it's not fatal + return; + } + + if (DEBUG) { + Log.d(TAG, "getStatus: item=" + item + ", update=" + update); + } + mClient.getStatus(item.getRemoteItemId(), null, new ItemActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("getStatus: succeeded", sessionId, sessionStatus, itemId, itemStatus); + int state = itemStatus.getPlaybackState(); + if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING + || state == MediaItemStatus.PLAYBACK_STATE_PAUSED + || state == MediaItemStatus.PLAYBACK_STATE_PENDING) { + item.setState(state); + item.setPosition(itemStatus.getContentPosition()); + item.setDuration(itemStatus.getContentDuration()); + item.setTimestamp(itemStatus.getTimestamp()); + } + if (update && mCallback != null) { + mCallback.onPlaylistReady(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("getStatus: failed", error, code); + if (update && mCallback != null) { + mCallback.onPlaylistReady(); + } + } + }); + } + + @Override + public void pause() { + if (!mClient.hasSession()) { + // ignore if no session + return; + } + if (DEBUG) { + Log.d(TAG, "pause"); + } + mClient.pause(null, new SessionActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) { + logStatus("pause: succeeded", sessionId, sessionStatus, null, null); + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("pause: failed", error, code); + } + }); + } + + @Override + public void resume() { + if (!mClient.hasSession()) { + // ignore if no session + return; + } + if (DEBUG) { + Log.d(TAG, "resume"); + } + mClient.resume(null, new SessionActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) { + logStatus("resume: succeeded", sessionId, sessionStatus, null, null); + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("resume: failed", error, code); + } + }); + } + + @Override + public void stop() { + if (!mClient.hasSession()) { + // ignore if no session + return; + } + if (DEBUG) { + Log.d(TAG, "stop"); + } + mClient.stop(null, new SessionActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) { + logStatus("stop: succeeded", sessionId, sessionStatus, null, null); + if (mClient.isSessionManagementSupported()) { + endSession(); + } + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("stop: failed", error, code); + } + }); + } + + // enqueue & remove are only supported if isQueuingSupported() returns true + @Override + public void enqueue(final PlaylistItem item) { + throwIfQueuingUnsupported(); + + if (!mClient.hasSession() && !mEnqueuePending) { + mEnqueuePending = true; + if (mClient.isSessionManagementSupported()) { + startSession(item); + } else { + enqueueInternal(item); + } + } else if (mEnqueuePending){ + mTempQueue.add(item); + } else { + enqueueInternal(item); + } + } + + @Override + public PlaylistItem remove(String itemId) { + throwIfNoSession(); + throwIfQueuingUnsupported(); + + if (DEBUG) { + Log.d(TAG, "remove: itemId=" + itemId); + } + mClient.remove(itemId, null, new ItemActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("remove: succeeded", sessionId, sessionStatus, itemId, itemStatus); + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("remove: failed", error, code); + } + }); + + return null; + } + + @Override + public void updateStatistics() { + // clear stats info first + mStatsInfo = ""; + + Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS); + intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE); + + if (mRoute != null && mRoute.supportsControlRequest(intent)) { + ControlRequestCallback callback = new ControlRequestCallback() { + @Override + public void onResult(Bundle data) { + if (DEBUG) { + Log.d(TAG, "getStatistics: succeeded: data=" + data); + } + if (data != null) { + int playbackCount = data.getInt( + SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1); + mStatsInfo = "Total playback count: " + playbackCount; + } + } + + @Override + public void onError(String error, Bundle data) { + Log.d(TAG, "getStatistics: failed: error=" + error + ", data=" + data); + } + }; + + mRoute.sendControlRequest(intent, callback); + } + } + + @Override + public String getStatistics() { + return mStatsInfo; + } + + private void enqueueInternal(final PlaylistItem item) { + throwIfQueuingUnsupported(); + + if (DEBUG) { + Log.d(TAG, "enqueue: item=" + item); + } + mClient.enqueue(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("enqueue: succeeded", sessionId, sessionStatus, itemId, itemStatus); + item.setRemoteItemId(itemId); + if (item.getPosition() > 0) { + seekInternal(item); + } + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { + pause(); + } + if (mEnqueuePending) { + mEnqueuePending = false; + for (PlaylistItem item : mTempQueue) { + enqueueInternal(item); + } + mTempQueue.clear(); + } + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("enqueue: failed", error, code); + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + }); + } + + private void seekInternal(final PlaylistItem item) { + throwIfNoSession(); + + if (DEBUG) { + Log.d(TAG, "seek: item=" + item); + } + mClient.seek(item.getRemoteItemId(), item.getPosition(), null, new ItemActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + logStatus("seek: succeeded", sessionId, sessionStatus, itemId, itemStatus); + if (mCallback != null) { + mCallback.onPlaylistChanged(); + } + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("seek: failed", error, code); + } + }); + } + + private void startSession(final PlaylistItem item) { + mClient.startSession(null, new SessionActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) { + logStatus("startSession: succeeded", sessionId, sessionStatus, null, null); + enqueueInternal(item); + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("startSession: failed", error, code); + } + }); + } + + private void endSession() { + mClient.endSession(null, new SessionActionCallback() { + @Override + public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) { + logStatus("endSession: succeeded", sessionId, sessionStatus, null, null); + } + + @Override + public void onError(String error, int code, Bundle data) { + logError("endSession: failed", error, code); + } + }); + } + + private void logStatus(String message, + String sessionId, MediaSessionStatus sessionStatus, + String itemId, MediaItemStatus itemStatus) { + if (DEBUG) { + String result = ""; + if (sessionId != null && sessionStatus != null) { + result += "sessionId=" + sessionId + ", sessionStatus=" + sessionStatus; + } + if (itemId != null & itemStatus != null) { + result += (result.isEmpty() ? "" : ", ") + + "itemId=" + itemId + ", itemStatus=" + itemStatus; + } + Log.d(TAG, message + ": " + result); + } + } + + private void logError(String message, String error, int code) { + Log.d(TAG, message + ": error=" + error + ", code=" + code); + } + + private void throwIfNoSession() { + if (!mClient.hasSession()) { + throw new IllegalStateException("Session is invalid"); + } + } + + private void throwIfQueuingUnsupported() { + if (!isQueuingSupported()) { + throw new UnsupportedOperationException("Queuing is unsupported"); + } + } +} diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouteProvider.java b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouteProvider.java index 04416c483..c1cc3c008 100644 --- a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouteProvider.java +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouteProvider.java @@ -34,6 +34,7 @@ import android.support.v7.media.MediaRouteProvider; import android.support.v7.media.MediaRouter.ControlRequestCallback; import android.support.v7.media.MediaRouteProviderDescriptor; import android.support.v7.media.MediaRouteDescriptor; +import android.support.v7.media.MediaSessionStatus; import android.util.Log; import android.view.Gravity; import android.view.Surface; @@ -50,7 +51,9 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private static final String TAG = "SampleMediaRouteProvider"; private static final String FIXED_VOLUME_ROUTE_ID = "fixed"; - private static final String VARIABLE_VOLUME_ROUTE_ID = "variable"; + private static final String VARIABLE_VOLUME_BASIC_ROUTE_ID = "variable_basic"; + private static final String VARIABLE_VOLUME_QUEUING_ROUTE_ID = "variable_queuing"; + private static final String VARIABLE_VOLUME_SESSION_ROUTE_ID = "variable_session"; private static final int VOLUME_MAX = 10; /** @@ -80,15 +83,10 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { public static final String DATA_PLAYBACK_COUNT = "com.example.android.supportv7.media.EXTRA_PLAYBACK_COUNT"; - /* - * Set ENABLE_QUEUEING to true to test queuing on MRP. This will make - * MRP expose the following two experimental hidden APIs: - * ACTION_ENQUEUE - * ACTION_REMOVE - */ - public static final boolean ENABLE_QUEUEING = false; + private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC; + private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING; + private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION; - private static final ArrayList<IntentFilter> CONTROL_FILTERS; static { IntentFilter f1 = new IntentFilter(); f1.addCategory(CATEGORY_SAMPLE_ROUTE); @@ -124,14 +122,25 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); f5.addAction(MediaControlIntent.ACTION_REMOVE); - CONTROL_FILTERS = new ArrayList<IntentFilter>(); - CONTROL_FILTERS.add(f1); - CONTROL_FILTERS.add(f2); - CONTROL_FILTERS.add(f3); - if (ENABLE_QUEUEING) { - CONTROL_FILTERS.add(f4); - CONTROL_FILTERS.add(f5); - } + IntentFilter f6 = new IntentFilter(); + f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + f6.addAction(MediaControlIntent.ACTION_START_SESSION); + f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); + f6.addAction(MediaControlIntent.ACTION_END_SESSION); + + CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>(); + CONTROL_FILTERS_BASIC.add(f1); + CONTROL_FILTERS_BASIC.add(f2); + CONTROL_FILTERS_BASIC.add(f3); + + CONTROL_FILTERS_QUEUING = + new ArrayList<IntentFilter>(CONTROL_FILTERS_BASIC); + CONTROL_FILTERS_QUEUING.add(f4); + CONTROL_FILTERS_QUEUING.add(f5); + + CONTROL_FILTERS_SESSION = + new ArrayList<IntentFilter>(CONTROL_FILTERS_QUEUING); + CONTROL_FILTERS_SESSION.add(f6); } private static void addDataTypeUnchecked(IntentFilter filter, String type) { @@ -163,7 +172,7 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { FIXED_VOLUME_ROUTE_ID, r.getString(R.string.fixed_volume_route_name)) .setDescription(r.getString(R.string.sample_route_description)) - .addControlFilters(CONTROL_FILTERS) + .addControlFilters(CONTROL_FILTERS_BASIC) .setPlaybackStream(AudioManager.STREAM_MUSIC) .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) @@ -171,10 +180,34 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { .build(); MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder( - VARIABLE_VOLUME_ROUTE_ID, - r.getString(R.string.variable_volume_route_name)) + VARIABLE_VOLUME_BASIC_ROUTE_ID, + r.getString(R.string.variable_volume_basic_route_name)) + .setDescription(r.getString(R.string.sample_route_description)) + .addControlFilters(CONTROL_FILTERS_BASIC) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) + .setVolumeMax(VOLUME_MAX) + .setVolume(mVolume) + .build(); + + MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder( + VARIABLE_VOLUME_QUEUING_ROUTE_ID, + r.getString(R.string.variable_volume_queuing_route_name)) .setDescription(r.getString(R.string.sample_route_description)) - .addControlFilters(CONTROL_FILTERS) + .addControlFilters(CONTROL_FILTERS_QUEUING) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) + .setVolumeMax(VOLUME_MAX) + .setVolume(mVolume) + .build(); + + MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder( + VARIABLE_VOLUME_SESSION_ROUTE_ID, + r.getString(R.string.variable_volume_session_route_name)) + .setDescription(r.getString(R.string.sample_route_description)) + .addControlFilters(CONTROL_FILTERS_SESSION) .setPlaybackStream(AudioManager.STREAM_MUSIC) .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) @@ -186,70 +219,57 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { new MediaRouteProviderDescriptor.Builder() .addRoute(routeDescriptor1) .addRoute(routeDescriptor2) + .addRoute(routeDescriptor3) + .addRoute(routeDescriptor4) .build(); setDescriptor(providerDescriptor); } private final class SampleRouteController extends MediaRouteProvider.RouteController { private final String mRouteId; - private final OverlayDisplayWindow mOverlay; - private final MediaPlayerWrapper mMediaPlayer; - private final MediaSessionManager mSessionManager; + private final SessionManager mSessionManager = new SessionManager("mrp"); + private final Player mPlayer; + private PendingIntent mSessionReceiver; public SampleRouteController(String routeId) { mRouteId = routeId; - mMediaPlayer = new MediaPlayerWrapper(getContext()); - mSessionManager = new MediaSessionManager(); - mSessionManager.setCallback(mMediaPlayer); - - // Create an overlay display window (used for simulating the remote playback only) - mOverlay = OverlayDisplayWindow.create(getContext(), - getContext().getResources().getString( - R.string.sample_media_route_provider_remote), - 1024, 768, Gravity.CENTER); - mOverlay.setOverlayWindowListener(new OverlayDisplayWindow.OverlayWindowListener() { + mPlayer = Player.create(getContext(), null); + mSessionManager.setPlayer(mPlayer); + mSessionManager.setCallback(new SessionManager.Callback() { @Override - public void onWindowCreated(Surface surface) { - mMediaPlayer.setSurface(surface); + public void onStatusChanged() { } @Override - public void onWindowCreated(SurfaceHolder surfaceHolder) { - mMediaPlayer.setSurface(surfaceHolder); - } - - @Override - public void onWindowDestroyed() { + public void onItemChanged(PlaylistItem item) { + handleStatusChange(item); } }); - - mMediaPlayer.setCallback(new MediaPlayerCallback()); Log.d(TAG, mRouteId + ": Controller created"); } @Override public void onRelease() { Log.d(TAG, mRouteId + ": Controller released"); - mMediaPlayer.release(); + mPlayer.release(); } @Override public void onSelect() { Log.d(TAG, mRouteId + ": Selected"); - mOverlay.show(); + mPlayer.connect(null); } @Override public void onUnselect() { Log.d(TAG, mRouteId + ": Unselected"); - mMediaPlayer.onStop(); - mOverlay.dismiss(); + mPlayer.release(); } @Override public void onSetVolume(int volume) { Log.d(TAG, mRouteId + ": Set volume to " + volume); - if (mRouteId.equals(VARIABLE_VOLUME_ROUTE_ID)) { + if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) { setVolumeInternal(volume); } } @@ -257,7 +277,7 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { @Override public void onUpdateVolume(int delta) { Log.d(TAG, mRouteId + ": Update volume by " + delta); - if (mRouteId.equals(VARIABLE_VOLUME_ROUTE_ID)) { + if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) { setVolumeInternal(mVolume + delta); } } @@ -284,6 +304,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { success = handleResume(intent, callback); } else if (action.equals(MediaControlIntent.ACTION_STOP)) { success = handleStop(intent, callback); + } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) { + success = handleStartSession(intent, callback); + } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) { + success = handleGetSessionStatus(intent, callback); + } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) { + success = handleEndSession(intent, callback); } Log.d(TAG, mSessionManager.toString()); return success; @@ -314,23 +340,31 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handlePlay(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); - if (sid == null || mSessionManager.stop(sid)) { - Log.d(TAG, "handleEnqueue"); - return handleEnqueue(intent, callback); + if (sid != null && !sid.equals(mSessionManager.getSessionId())) { + Log.d(TAG, "handlePlay fails because of bad sid="+sid); + return false; } - return false; + if (mSessionManager.hasSession()) { + mSessionManager.stop(); + } + return handleEnqueue(intent, callback); } private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) { - if (intent.getData() == null) { + String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + if (sid != null && !sid.equals(mSessionManager.getSessionId())) { + Log.d(TAG, "handleEnqueue fails because of bad sid="+sid); return false; } - mEnqueueCount +=1; + Uri uri = intent.getData(); + if (uri == null) { + Log.d(TAG, "handleEnqueue fails because of bad uri="+uri); + return false; + } boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE); - Uri uri = intent.getData(); - String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + String mime = intent.getType(); long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0); Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA); Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS); @@ -339,12 +373,13 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request" + ", uri=" + uri + + ", mime=" + mime + ", sid=" + sid + ", pos=" + pos + ", metadata=" + metadata + ", headers=" + headers + ", receiver=" + receiver); - MediaQueueItem item = mSessionManager.enqueue(sid, uri, receiver); + PlaylistItem item = mSessionManager.add(uri, mime, receiver); if (callback != null) { if (item != null) { Bundle result = new Bundle(); @@ -357,13 +392,18 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { callback.onError("Failed to open " + uri.toString(), null); } } + mEnqueueCount +=1; return true; } private boolean handleRemove(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + if (sid == null || !sid.equals(mSessionManager.getSessionId())) { + return false; + } + String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); - MediaQueueItem item = mSessionManager.remove(sid, iid); + PlaylistItem item = mSessionManager.remove(iid); if (callback != null) { if (item != null) { Bundle result = new Bundle(); @@ -380,10 +420,14 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handleSeek(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + if (sid == null || !sid.equals(mSessionManager.getSessionId())) { + return false; + } + String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0); Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos); - MediaQueueItem item = mSessionManager.seek(sid, iid, pos); + PlaylistItem item = mSessionManager.seek(iid, pos); if (callback != null) { if (item != null) { Bundle result = new Bundle(); @@ -401,7 +445,8 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); - MediaQueueItem item = mSessionManager.getStatus(sid, iid); + Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid); + PlaylistItem item = mSessionManager.getStatus(iid); if (callback != null) { if (item != null) { Bundle result = new Bundle(); @@ -418,10 +463,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handlePause(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); - boolean success = mSessionManager.pause(sid); + boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId()); + mSessionManager.pause(); if (callback != null) { if (success) { - callback.onResult(null); + callback.onResult(new Bundle()); + handleSessionStatusChange(sid); } else { callback.onError("Failed to pause, sid=" + sid, null); } @@ -431,10 +478,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handleResume(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); - boolean success = mSessionManager.resume(sid); + boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId()); + mSessionManager.resume(); if (callback != null) { if (success) { - callback.onResult(null); + callback.onResult(new Bundle()); + handleSessionStatusChange(sid); } else { callback.onError("Failed to resume, sid=" + sid, null); } @@ -444,10 +493,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { private boolean handleStop(Intent intent, ControlRequestCallback callback) { String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); - boolean success = mSessionManager.stop(sid); + boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId()); + mSessionManager.stop(); if (callback != null) { if (success) { - callback.onResult(null); + callback.onResult(new Bundle()); + handleSessionStatusChange(sid); } else { callback.onError("Failed to stop, sid=" + sid, null); } @@ -455,14 +506,64 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { return success; } - private void handleFinish(boolean error) { - MediaQueueItem item = mSessionManager.finish(error); - if (item != null) { - handleStatusChange(item); + private boolean handleStartSession(Intent intent, ControlRequestCallback callback) { + String sid = mSessionManager.startSession(); + Log.d(TAG, "StartSession returns sessionId "+sid); + if (callback != null) { + if (sid != null) { + Bundle result = new Bundle(); + result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid); + result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, + mSessionManager.getSessionStatus(sid).asBundle()); + callback.onResult(result); + mSessionReceiver = (PendingIntent)intent.getParcelableExtra( + MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER); + handleSessionStatusChange(sid); + } else { + callback.onError("Failed to start session.", null); + } + } + return (sid != null); + } + + private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) { + String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + + MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid); + if (callback != null) { + if (sessionStatus != null) { + Bundle result = new Bundle(); + result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, + mSessionManager.getSessionStatus(sid).asBundle()); + callback.onResult(result); + } else { + callback.onError("Failed to get session status, sid=" + sid, null); + } } + return (sessionStatus != null); } - private void handleStatusChange(MediaQueueItem item) { + private boolean handleEndSession(Intent intent, ControlRequestCallback callback) { + String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); + boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId()) + && mSessionManager.endSession(); + if (callback != null) { + if (success) { + Bundle result = new Bundle(); + MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder( + MediaSessionStatus.SESSION_STATE_ENDED).build(); + result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle()); + callback.onResult(result); + handleSessionStatusChange(sid); + mSessionReceiver = null; + } else { + callback.onError("Failed to end session, sid=" + sid, null); + } + } + return success; + } + + private void handleStatusChange(PlaylistItem item) { if (item == null) { item = mSessionManager.getCurrentItem(); } @@ -484,25 +585,18 @@ final class SampleMediaRouteProvider extends MediaRouteProvider { } } - private final class MediaPlayerCallback extends MediaPlayerWrapper.Callback { - @Override - public void onError() { - handleFinish(true); - } - - @Override - public void onCompletion() { - handleFinish(false); - } - - @Override - public void onStatusChanged() { - handleStatusChange(null); - } - - @Override - public void onSizeChanged(int width, int height) { - mOverlay.updateAspectRatio(width, height); + private void handleSessionStatusChange(String sid) { + if (mSessionReceiver != null) { + Intent intent = new Intent(); + intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid); + intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS, + mSessionManager.getSessionStatus(sid).asBundle()); + try { + mSessionReceiver.send(getContext(), 0, intent); + Log.d(TAG, mRouteId + ": Sending session status update from provider"); + } catch (PendingIntent.CanceledException e) { + Log.d(TAG, mRouteId + ": Failed to send session status update!"); + } } } } diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java index cad1584d3..b1c8c9c49 100644 --- a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java @@ -21,12 +21,8 @@ import com.example.android.supportv7.R; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.BroadcastReceiver; import android.content.res.Resources; -import android.content.DialogInterface; import android.app.PendingIntent; -import android.app.Presentation; import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; import android.media.MediaMetadataRetriever; @@ -50,32 +46,23 @@ import android.support.v7.media.MediaRouter.ProviderInfo; import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaItemStatus; import android.util.Log; -import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.Display; -import android.view.SurfaceView; -import android.view.SurfaceHolder; -import android.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.ListView; import android.widget.TextView; -import android.widget.Toast; -import android.widget.FrameLayout; import android.widget.TabHost; import android.widget.TabHost.TabSpec; import android.widget.TabHost.OnTabChangeListener; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; - - import java.io.File; /** @@ -88,10 +75,8 @@ import java.io.File; * </p> */ public class SampleMediaRouterActivity extends ActionBarActivity { - private static final String TAG = "MediaRouterSupport"; + private static final String TAG = "SampleMediaRouterActivity"; private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment"; - private static final String ACTION_STATUS_CHANGE = - "com.example.android.supportv7.media.ACTION_STATUS_CHANGE"; private MediaRouter mMediaRouter; private MediaRouteSelector mSelector; @@ -103,53 +88,22 @@ public class SampleMediaRouterActivity extends ActionBarActivity { private ImageButton mPauseResumeButton; private ImageButton mStopButton; private SeekBar mSeekBar; - private String mStatsInfo; private boolean mPaused; private boolean mNeedResume; private boolean mSeeking; - private long mLastStatusTime; - private PlaylistAdapter mSavedPlaylist; private final Handler mHandler = new Handler(); private final Runnable mUpdateSeekRunnable = new Runnable() { @Override public void run() { - updateProgress(getCheckedMediaQueueItem()); + updateProgress(); // update Ui every 1 second mHandler.postDelayed(this, 1000); } }; - private final MediaPlayerWrapper mMediaPlayer = new MediaPlayerWrapper(this); - private final MediaPlayerWrapper.Callback mMediaPlayerCB = - new MediaPlayerWrapper.Callback() { - @Override - public void onError() { - mPlayer.onFinish(true); - } - - @Override - public void onCompletion() { - mPlayer.onFinish(false); - } - - @Override - public void onSizeChanged(int width, int height) { - mPlayer.updateSize(width, height); - } - - @Override - public void onStatusChanged() { - if (!mSeeking) { - updateUi(); - } - } - }; - - private final RemotePlayer mRemotePlayer = new RemotePlayer(); - private final LocalPlayer mLocalPlayer = new LocalPlayer(); + private final SessionManager mSessionManager = new SessionManager("app"); private Player mPlayer; - private MediaSessionManager.Callback mPlayerCB; private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() { // Return a custom callback that will simply log all of the route events @@ -162,7 +116,6 @@ public class SampleMediaRouterActivity extends ActionBarActivity { @Override public void onRouteChanged(MediaRouter router, RouteInfo route) { Log.d(TAG, "onRouteChanged: route=" + route); - mPlayer.showStatistics(); } @Override @@ -174,82 +127,28 @@ public class SampleMediaRouterActivity extends ActionBarActivity { public void onRouteSelected(MediaRouter router, RouteInfo route) { Log.d(TAG, "onRouteSelected: route=" + route); - Player player = mPlayer; - MediaSessionManager.Callback playerCB = mPlayerCB; - - if (route.supportsControlCategory( - MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { - Intent enqueueIntent = new Intent(MediaControlIntent.ACTION_ENQUEUE); - enqueueIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - enqueueIntent.setDataAndType(Uri.parse("http://"), "video/mp4"); - - Intent removeIntent = new Intent(MediaControlIntent.ACTION_REMOVE); - removeIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + mPlayer = Player.create(SampleMediaRouterActivity.this, route); + mPlayer.updatePresentation(); + mSessionManager.setPlayer(mPlayer); + mSessionManager.unsuspend(); - // Remote Playback: - // If route supports remote queuing, let it manage the queue; - // otherwise, manage the queue locally and feed it one item at a time - if (route.supportsControlRequest(enqueueIntent) - && route.supportsControlRequest(removeIntent)) { - player = mRemotePlayer; - } else { - player = mLocalPlayer; - } - playerCB = mRemotePlayer; - mRemotePlayer.reset(); - - } else { - // Local Playback: - // Use local player and feed media player one item at a time - player = mLocalPlayer; - playerCB = mMediaPlayer; - } - - if (player != mPlayer || playerCB != mPlayerCB) { - // save current playlist - PlaylistAdapter playlist = new PlaylistAdapter(); - for (int i = 0; i < mPlayListItems.getCount(); i++) { - MediaQueueItem item = mPlayListItems.getItem(i); - if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING - || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - long position = item.getContentPosition(); - long timeDelta = mPaused ? 0 : - (SystemClock.elapsedRealtime() - mLastStatusTime); - item.setContentPosition(position + timeDelta); - } - playlist.add(item); - } - - // switch players - mPlayer.stop(); - mPaused = false; - mLocalPlayer.setCallback(playerCB); - mPlayerCB = playerCB; - mPlayer = player; - mPlayer.showStatistics(); - mLocalPlayer.updatePresentation(); - - // migrate playlist to new route - int count = playlist.getCount(); - if (isRemoteQueue()) { - // if queuing is managed remotely, only enqueue the first - // item, as we need to have the returned session id to - // enqueue the rest of the playlist items - mSavedPlaylist = playlist; - count = 1; - } - for (int i = 0; i < count; i++) { - final MediaQueueItem item = playlist.getItem(i); - mPlayer.enqueue(item.getUri(), item.getContentPosition()); - } - } + registerRCC(); updateUi(); } @Override public void onRouteUnselected(MediaRouter router, RouteInfo route) { Log.d(TAG, "onRouteUnselected: route=" + route); - mPlayer.showStatistics(); + unregisterRCC(); + + PlaylistItem item = getCheckedPlaylistItem(); + if (item != null) { + long pos = item.getPosition() + (mPaused ? + 0 : (SystemClock.elapsedRealtime() - item.getTimestamp())); + mSessionManager.suspend(pos); + } + mPlayer.updatePresentation(); + mPlayer.release(); } @Override @@ -261,6 +160,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { public void onRoutePresentationDisplayChanged( MediaRouter router, RouteInfo route) { Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route); + mPlayer.updatePresentation(); } @Override @@ -279,33 +179,6 @@ public class SampleMediaRouterActivity extends ActionBarActivity { } }; - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "Received status update: " + intent); - if (intent.getAction().equals(ACTION_STATUS_CHANGE)) { - String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); - String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); - MediaItemStatus status = MediaItemStatus.fromBundle( - intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_STATUS)); - - if (status.getPlaybackState() == - MediaItemStatus.PLAYBACK_STATE_FINISHED) { - mPlayer.onFinish(false); - } else if (status.getPlaybackState() == - MediaItemStatus.PLAYBACK_STATE_ERROR) { - mPlayer.onFinish(true); - showToast("Error while playing item" + - ", sid " + sid + ", iid " + iid); - } else { - if (!mSeeking) { - updateUi(); - } - } - } - } - }; - private RemoteControlClient mRemoteControlClient; private ComponentName mEventReceiver; private AudioManager mAudioManager; @@ -363,7 +236,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { mLibraryItems = new LibraryAdapter(); for (int i = 0; i < mediaNames.length; i++) { mLibraryItems.add(new MediaItem( - "[streaming] "+mediaNames[i], Uri.parse(mediaUris[i]))); + "[streaming] "+mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4")); } // Scan local external storage directory for media files. @@ -375,7 +248,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { String filename = list[i].getName(); if (filename.matches(".*\\.(m4v|mp4)")) { mLibraryItems.add(new MediaItem("[local] " + filename, - Uri.fromFile(list[i]))); + Uri.fromFile(list[i]), "video/mp4")); } } } @@ -409,10 +282,6 @@ public class SampleMediaRouterActivity extends ActionBarActivity { tabHost.setOnTabChangedListener(new OnTabChangeListener() { @Override public void onTabChanged(String arg0) { - if (arg0.equals(getResources().getString( - R.string.statistics_tab_text))) { - mPlayer.showStatistics(); - } updateUi(); } }); @@ -443,10 +312,11 @@ public class SampleMediaRouterActivity extends ActionBarActivity { mPauseResumeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (!mPaused) { - mPlayer.pause(); + mPaused = !mPaused; + if (mPaused) { + mSessionManager.pause(); } else { - mPlayer.resume(); + mSessionManager.resume(); } } }); @@ -455,8 +325,8 @@ public class SampleMediaRouterActivity extends ActionBarActivity { mStopButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - mPlayer.stop(); - clearContent(); + mPaused = false; + mSessionManager.stop(); } }); @@ -464,12 +334,12 @@ public class SampleMediaRouterActivity extends ActionBarActivity { mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - MediaQueueItem item = getCheckedMediaQueueItem(); - if (fromUser && item != null && item.getContentDuration() > 0) { - long pos = progress * item.getContentDuration() / 100; - mPlayer.seek(item.getSessionId(), item.getItemId(), pos); - item.setContentPosition(pos); - mLastStatusTime = SystemClock.elapsedRealtime(); + PlaylistItem item = getCheckedPlaylistItem(); + if (fromUser && item != null && item.getDuration() > 0) { + long pos = progress * item.getDuration() / 100; + mSessionManager.seek(item.getItemId(), pos); + item.setPosition(pos); + item.setTimestamp(SystemClock.elapsedRealtime()); } } @Override @@ -486,18 +356,6 @@ public class SampleMediaRouterActivity extends ActionBarActivity { // Schedule Ui update mHandler.postDelayed(mUpdateSeekRunnable, 1000); - // Use local playback with media player by default - mLocalPlayer.onCreate(); - mMediaPlayer.setCallback(mMediaPlayerCB); - mLocalPlayer.setCallback(mMediaPlayer); - mPlayerCB = mMediaPlayer; - mPlayer = mLocalPlayer; - - // Register broadcast receiver to receive status update from MRP - IntentFilter filter = new IntentFilter(); - filter.addAction(SampleMediaRouterActivity.ACTION_STATUS_CHANGE); - registerReceiver(mReceiver, filter); - // Build the PendingIntent for the remote control client mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mEventReceiver = new ComponentName(getPackageName(), @@ -508,6 +366,23 @@ public class SampleMediaRouterActivity extends ActionBarActivity { // Create and register the remote control client registerRCC(); + + // Set up playback manager and player + mPlayer = Player.create(SampleMediaRouterActivity.this, + mMediaRouter.getSelectedRoute()); + mSessionManager.setPlayer(mPlayer); + mSessionManager.setCallback(new SessionManager.Callback() { + @Override + public void onStatusChanged() { + updateUi(); + } + + @Override + public void onItemChanged(PlaylistItem item) { + } + }); + + updateUi(); } private void registerRCC() { @@ -546,10 +421,11 @@ public class SampleMediaRouterActivity extends ActionBarActivity { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: { Log.d(TAG, "Received Play/Pause event from RemoteControlClient"); - if (!mPaused) { - mPlayer.pause(); + mPaused = !mPaused; + if (mPaused) { + mSessionManager.pause(); } else { - mPlayer.resume(); + mSessionManager.resume(); } return true; } @@ -557,7 +433,8 @@ public class SampleMediaRouterActivity extends ActionBarActivity { { Log.d(TAG, "Received Play event from RemoteControlClient"); if (mPaused) { - mPlayer.resume(); + mPaused = false; + mSessionManager.resume(); } return true; } @@ -565,15 +442,16 @@ public class SampleMediaRouterActivity extends ActionBarActivity { { Log.d(TAG, "Received Pause event from RemoteControlClient"); if (!mPaused) { - mPlayer.pause(); + mPaused = true; + mSessionManager.pause(); } return true; } case KeyEvent.KEYCODE_MEDIA_STOP: { Log.d(TAG, "Received Stop event from RemoteControlClient"); - mPlayer.stop(); - clearContent(); + mPaused = false; + mSessionManager.stop(); return true; } default: @@ -597,15 +475,14 @@ public class SampleMediaRouterActivity extends ActionBarActivity { public void onStart() { // Be sure to call the super class. super.onStart(); - mPlayer.showStatistics(); } @Override public void onPause() { // pause media player for local playback case only - if (!isRemotePlayback() && !mPaused) { + if (!mPlayer.isRemotePlayback() && !mPaused) { mNeedResume = true; - mPlayer.pause(); + mSessionManager.pause(); } super.onPause(); } @@ -613,8 +490,8 @@ public class SampleMediaRouterActivity extends ActionBarActivity { @Override public void onResume() { // resume media player for local playback case only - if (!isRemotePlayback() && mNeedResume) { - mPlayer.resume(); + if (!mPlayer.isRemotePlayback() && mNeedResume) { + mSessionManager.resume(); mNeedResume = false; } super.onResume(); @@ -625,11 +502,9 @@ public class SampleMediaRouterActivity extends ActionBarActivity { // Unregister the remote control client unregisterRCC(); - // Unregister broadcast receiver - unregisterReceiver(mReceiver); - mPlayer.stop(); - mMediaPlayer.release(); - + mPaused = false; + mSessionManager.stop(); + mPlayer.release(); super.onDestroy(); } @@ -650,52 +525,24 @@ public class SampleMediaRouterActivity extends ActionBarActivity { return true; } - private void updateRouteDescription() { - RouteInfo route = mMediaRouter.getSelectedRoute(); - mInfoTextView.setText("Currently selected route:" - + "\nName: " + route.getName() - + "\nProvider: " + route.getProvider().getPackageName() - + "\nDescription: " + route.getDescription() - + "\nStatistics: " + mStatsInfo); - updateButtons(); - mLocalPlayer.updatePresentation(); - } - - private void clearContent() { - //TO-DO: clear surface view - } - - private void updateButtons() { - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - // show pause or resume icon depending on current state - mPauseResumeButton.setImageResource(mPaused ? - R.drawable.ic_media_play : R.drawable.ic_media_pause); - // only enable seek bar when duration is known - MediaQueueItem item = getCheckedMediaQueueItem(); - mSeekBar.setEnabled(item != null && item.getContentDuration() > 0); - if (mRemoteControlClient != null) { - mRemoteControlClient.setPlaybackState(mPaused ? - RemoteControlClient.PLAYSTATE_PAUSED : RemoteControlClient.PLAYSTATE_PLAYING); - } - } - - private void updateProgress(MediaQueueItem queueItem) { + private void updateProgress() { // Estimate content position from last status time and elapsed time. // (Note this might be slightly out of sync with remote side, however // it avoids frequent polling the MRP.) int progress = 0; - if (queueItem != null) { - int state = queueItem.getState(); - long duration = queueItem.getContentDuration(); + PlaylistItem item = getCheckedPlaylistItem(); + if (item != null) { + int state = item.getState(); + long duration = item.getDuration(); if (duration <= 0) { if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING || state == MediaItemStatus.PLAYBACK_STATE_PAUSED) { - updateUi(); + mSessionManager.updateStatus(); } } else { - long position = queueItem.getContentPosition(); + long position = item.getPosition(); long timeDelta = mPaused ? 0 : - (SystemClock.elapsedRealtime() - mLastStatusTime); + (SystemClock.elapsedRealtime() - item.getTimestamp()); progress = (int)(100.0 * (position + timeDelta) / duration); } } @@ -704,37 +551,47 @@ public class SampleMediaRouterActivity extends ActionBarActivity { private void updateUi() { updatePlaylist(); + updateRouteDescription(); updateButtons(); } private void updatePlaylist() { - Log.d(TAG, "updatePlaylist"); - final PlaylistAdapter playlist = new PlaylistAdapter(); - // make a copy of current playlist - for (int i = 0; i < mPlayListItems.getCount(); i++) { - playlist.add(mPlayListItems.getItem(i)); - } - // clear mPlayListItems first, items will be added back when we get - // status back from provider. mPlayListItems.clear(); + for (PlaylistItem item : mSessionManager.getPlaylist()) { + mPlayListItems.add(item); + } mPlayListView.invalidate(); + } - for (int i = 0; i < playlist.getCount(); i++) { - final MediaQueueItem item = playlist.getItem(i); - final boolean update = (i == playlist.getCount() - 1); - mPlayer.getStatus(item, update); - } + + private void updateRouteDescription() { + RouteInfo route = mMediaRouter.getSelectedRoute(); + mInfoTextView.setText("Currently selected route:" + + "\nName: " + route.getName() + + "\nProvider: " + route.getProvider().getPackageName() + + "\nDescription: " + route.getDescription() + + "\nStatistics: " + mSessionManager.getStatistics()); } - private MediaItem getCheckedMediaItem() { - int index = mLibraryView.getCheckedItemPosition(); - if (index >= 0 && index < mLibraryItems.getCount()) { - return mLibraryItems.getItem(index); + private void updateButtons() { + MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); + // show pause or resume icon depending on current state + mPauseResumeButton.setImageResource(mPaused ? + R.drawable.ic_media_play : R.drawable.ic_media_pause); + // disable pause/resume/stop if no session + mPauseResumeButton.setEnabled(mSessionManager.hasSession()); + mStopButton.setEnabled(mSessionManager.hasSession()); + // only enable seek bar when duration is known + PlaylistItem item = getCheckedPlaylistItem(); + mSeekBar.setEnabled(item != null && item.getDuration() > 0); + if (mRemoteControlClient != null) { + mRemoteControlClient.setPlaybackState(mPaused ? + RemoteControlClient.PLAYSTATE_PAUSED : + RemoteControlClient.PLAYSTATE_PLAYING); } - return null; } - private MediaQueueItem getCheckedMediaQueueItem() { + private PlaylistItem getCheckedPlaylistItem() { int count = mPlayListView.getCount(); int index = mPlayListView.getCheckedItemPosition(); if (count > 0) { @@ -747,768 +604,6 @@ public class SampleMediaRouterActivity extends ActionBarActivity { return null; } - private void enqueuePlaylist() { - if (mSavedPlaylist != null) { - final PlaylistAdapter playlist = mSavedPlaylist; - mSavedPlaylist = null; - // migrate playlist (except for the 1st item) to new route - for (int i = 1; i < playlist.getCount(); i++) { - final MediaQueueItem item = playlist.getItem(i); - mPlayer.enqueue(item.getUri(), item.getContentPosition()); - } - } - } - - private boolean isRemoteQueue() { - return mPlayer == mRemotePlayer; - } - - private boolean isRemotePlayback() { - return mPlayerCB == mRemotePlayer; - } - - private void showToast(String msg) { - Toast toast = Toast.makeText(SampleMediaRouterActivity.this, - "[app] " + msg, Toast.LENGTH_LONG); - toast.setGravity(Gravity.TOP, 0, 100); - toast.show(); - } - - private interface Player { - void enqueue(final Uri uri, long pos); - void remove(final MediaQueueItem item); - void seek(String sid, String iid, long pos); - void getStatus(final MediaQueueItem item, final boolean update); - void pause(); - void resume(); - void stop(); - void showStatistics(); - void onFinish(boolean error); - void updateSize(int width, int height); - } - - private class LocalPlayer implements Player, SurfaceHolder.Callback { - private final MediaSessionManager mSessionManager = new MediaSessionManager(); - private String mSessionId; - // The presentation to show on the secondary display. - private DemoPresentation mPresentation; - private SurfaceView mSurfaceView; - private FrameLayout mLayout; - private int mVideoWidth; - private int mVideoHeight; - - public void onCreate() { - mLayout = (FrameLayout)findViewById(R.id.player); - mSurfaceView = (SurfaceView)findViewById(R.id.surface_view); - SurfaceHolder holder = mSurfaceView.getHolder(); - holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - holder.addCallback(this); - } - - public void setCallback(MediaSessionManager.Callback cb) { - mSessionManager.setCallback(cb); - } - - @Override - public void enqueue(final Uri uri, long pos) { - Log.d(TAG, "LocalPlayer: enqueue, uri=" + uri + ", pos=" + pos); - MediaQueueItem playlistItem = mSessionManager.enqueue(mSessionId, uri, null); - mSessionId = playlistItem.getSessionId(); - // Set remote control client title - if (mPlayListItems.getCount() == 0 && mRemoteControlClient != null) { - RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true); - ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - playlistItem.toString()); - ed.apply(); - } - mPlayListItems.add(playlistItem); - if (pos > 0) { - // Seek to initial position if needed - mPlayer.seek(mSessionId, playlistItem.getItemId(), pos); - } - updateUi(); - } - - @Override - public void remove(final MediaQueueItem item) { - Log.d(TAG, "LocalPlayer: remove, item=" + item); - mSessionManager.remove(item.getSessionId(), item.getItemId()); - updateUi(); - } - - @Override - public void seek(String sid, String iid, long pos) { - Log.d(TAG, "LocalPlayer: seek, sid=" + sid + ", iid=" + iid); - mSessionManager.seek(sid, iid, pos); - } - - @Override - public void getStatus(final MediaQueueItem item, final boolean update) { - Log.d(TAG, "LocalPlayer: getStatus, item=" + item + ", update=" + update); - MediaQueueItem playlistItem = - mSessionManager.getStatus(item.getSessionId(), item.getItemId()); - if (playlistItem != null) { - mLastStatusTime = playlistItem.getStatus().getTimestamp(); - mPlayListItems.add(item); - mPlayListView.invalidate(); - } - if (update) { - clearContent(); - updateButtons(); - } - } - - @Override - public void pause() { - Log.d(TAG, "LocalPlayer: pause"); - mSessionManager.pause(mSessionId); - mPaused = true; - updateUi(); - } - - @Override - public void resume() { - Log.d(TAG, "LocalPlayer: resume"); - mSessionManager.resume(mSessionId); - mPaused = false; - updateUi(); - } - - @Override - public void stop() { - Log.d(TAG, "LocalPlayer: stop"); - mSessionManager.stop(mSessionId); - mSessionId = null; - mPaused = false; - // For demo purpose, invalidate remote session when local session - // is stopped (note this is not necessary, remote player could reuse - // the same session) - mRemotePlayer.reset(); - updateUi(); - } - - @Override - public void showStatistics() { - Log.d(TAG, "LocalPlayer: showStatistics"); - mStatsInfo = null; - if (isRemotePlayback()) { - mRemotePlayer.showStatistics(); - } - updateRouteDescription(); - } - - @Override - public void onFinish(boolean error) { - MediaQueueItem item = mSessionManager.finish(error); - updateUi(); - if (error && item != null) { - showToast("Failed to play item " + item.getUri()); - } - } - - // SurfaceHolder.Callback - @Override - public void surfaceChanged(SurfaceHolder holder, int format, - int width, int height) { - Log.d(TAG, "surfaceChanged "+width+"x"+height); - mMediaPlayer.setSurface(holder); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.d(TAG, "surfaceCreated"); - mMediaPlayer.setSurface(holder); - updateSize(mVideoWidth, mVideoHeight); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.d(TAG, "surfaceDestroyed"); - } - - @Override - public void updateSize(int width, int height) { - if (width > 0 && height > 0) { - if (mPresentation == null) { - int surfaceWidth = mLayout.getWidth(); - int surfaceHeight = mLayout.getHeight(); - - // Calculate the new size of mSurfaceView, so that video is centered - // inside the framelayout with proper letterboxing/pillarboxing - ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); - if (surfaceWidth * height < surfaceHeight * width) { - // Black bars on top&bottom, mSurfaceView has full layout width, - // while height is derived from video's aspect ratio - lp.width = surfaceWidth; - lp.height = surfaceWidth * height / width; - } else { - // Black bars on left&right, mSurfaceView has full layout height, - // while width is derived from video's aspect ratio - lp.width = surfaceHeight * width / height; - lp.height = surfaceHeight; - } - Log.d(TAG, "video rect is "+lp.width+"x"+lp.height); - mSurfaceView.setLayoutParams(lp); - } else { - mPresentation.updateSize(width, height); - } - mVideoWidth = width; - mVideoHeight = height; - } else { - mVideoWidth = mVideoHeight = 0; - } - } - - private void updatePresentation() { - // Get the current route and its presentation display. - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Display presentationDisplay = route != null ? route.getPresentationDisplay() : null; - - // Dismiss the current presentation if the display has changed. - if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { - Log.i(TAG, "Dismissing presentation because the current route no longer " - + "has a presentation display."); - mPresentation.dismiss(); - mPresentation = null; - } - - // Show a new presentation if needed. - if (mPresentation == null && presentationDisplay != null) { - Log.i(TAG, "Showing presentation on display: " + presentationDisplay); - mPresentation = new DemoPresentation( - SampleMediaRouterActivity.this, presentationDisplay); - mPresentation.setOnDismissListener(mOnDismissListener); - try { - mPresentation.show(); - } catch (WindowManager.InvalidDisplayException ex) { - Log.w(TAG, "Couldn't show presentation! Display was removed in " - + "the meantime.", ex); - mPresentation = null; - } - } - - if (mPresentation != null || route.supportsControlCategory( - MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { - mMediaPlayer.setSurface((SurfaceHolder)null); - mMediaPlayer.reset(); - mSurfaceView.setVisibility(View.GONE); - mLayout.setVisibility(View.GONE); - } else { - mLayout.setVisibility(View.VISIBLE); - mSurfaceView.setVisibility(View.VISIBLE); - } - } - - // Listens for when presentations are dismissed. - private final DialogInterface.OnDismissListener mOnDismissListener = - new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - if (dialog == mPresentation) { - Log.i(TAG, "Presentation was dismissed."); - mPresentation = null; - updatePresentation(); - } - } - }; - - private final class DemoPresentation extends Presentation { - private SurfaceView mPresentationSurfaceView; - - public DemoPresentation(Context context, Display display) { - super(context, display); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - // Be sure to call the super class. - super.onCreate(savedInstanceState); - - // Get the resources for the context of the presentation. - // Notice that we are getting the resources from the context - // of the presentation. - Resources r = getContext().getResources(); - - // Inflate the layout. - setContentView(R.layout.sample_media_router_presentation); - - // Set up the surface view. - mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view); - SurfaceHolder holder = mPresentationSurfaceView.getHolder(); - holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - holder.addCallback(LocalPlayer.this); - } - - public void updateSize(int width, int height) { - int surfaceHeight = getWindow().getDecorView().getHeight(); - int surfaceWidth = getWindow().getDecorView().getWidth(); - ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams(); - if (surfaceWidth * height < surfaceHeight * width) { - lp.width = surfaceWidth; - lp.height = surfaceWidth * height / width; - } else { - lp.width = surfaceHeight * width / height; - lp.height = surfaceHeight; - } - Log.d(TAG, "video rect is " + lp.width + "x" + lp.height); - mPresentationSurfaceView.setLayoutParams(lp); - } - } - } - - private class RemotePlayer implements Player, MediaSessionManager.Callback { - private MediaQueueItem mQueueItem; - private MediaQueueItem mPlaylistItem; - private String mSessionId; - private String mItemId; - private long mPosition; - - public void reset() { - mQueueItem = null; - mPlaylistItem = null; - mSessionId = null; - mItemId = null; - mPosition = 0; - } - - // MediaSessionManager.Callback - @Override - public void onStart() { - resume(); - } - - @Override - public void onPause() { - pause(); - } - - @Override - public void onStop() { - stop(); - } - - @Override - public void onSeek(long pos) { - // If we're currently performing a Play/Enqueue, do not seek - // until we get the result back (or we may not have valid session - // and item ids); otherwise do the seek now - if (mSessionId != null) { - seek(mSessionId, mItemId, pos); - } - // Set current position to seek-to position, actual position will - // be updated when next getStatus is completed. - mPosition = pos; - } - - @Override - public void onGetStatus(MediaQueueItem item) { - if (mQueueItem != null) { - mPlaylistItem = item; - getStatus(mQueueItem, false); - } - } - - @Override - public void onNewItem(Uri uri) { - mPosition = 0; - play(uri, false, 0); - } - - // Player API - @Override - public void enqueue(final Uri uri, long pos) { - play(uri, true, pos); - } - - @Override - public void remove(final MediaQueueItem item) { - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Intent intent = makeRemoveIntent(item); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - MediaItemStatus status = MediaItemStatus.fromBundle( - data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); - Log.d(TAG, "Remove request succeeded: status=" + status.toString()); - updateUi(); - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Remove request failed: error=" + error + ", data=" + data); - } - }; - - Log.d(TAG, "Sending remove request: intent=" + intent); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Remove request not supported!"); - } - } - - @Override - public void seek(String sid, String iid, long pos) { - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Intent intent = makeSeekIntent(sid, iid, pos); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - MediaItemStatus status = MediaItemStatus.fromBundle( - data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); - Log.d(TAG, "Seek request succeeded: status=" + status.toString()); - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Seek request failed: error=" + error + ", data=" + data); - } - }; - - Log.d(TAG, "Sending seek request: intent=" + intent); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Seek request not supported!"); - } - } - - @Override - public void getStatus(final MediaQueueItem item, final boolean update) { - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Intent intent = makeGetStatusIntent(item); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - if (data != null) { - String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID); - String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID); - MediaItemStatus status = MediaItemStatus.fromBundle( - data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); - Log.d(TAG, "GetStatus request succeeded: status=" + status.toString()); - //showToast("GetStatus request succeeded " + item.mName); - if (isRemoteQueue()) { - int state = status.getPlaybackState(); - if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING - || state == MediaItemStatus.PLAYBACK_STATE_PAUSED - || state == MediaItemStatus.PLAYBACK_STATE_PENDING) { - item.setState(state); - item.setContentPosition(status.getContentPosition()); - item.setContentDuration(status.getContentDuration()); - mLastStatusTime = status.getTimestamp(); - mPlayListItems.add(item); - mPlayListView.invalidate(); - // update buttons as the queue count might have changed - if (update) { - clearContent(); - updateButtons(); - } - } - } else { - if (mPlaylistItem != null) { - mPlaylistItem.setContentPosition(status.getContentPosition()); - mPlaylistItem.setContentDuration(status.getContentDuration()); - mPlaylistItem = null; - updateButtons(); - } - } - } - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "GetStatus request failed: error=" + error + ", data=" + data); - //showToast("Unable to get status "); - if (isRemoteQueue()) { - if (update) { - clearContent(); - updateButtons(); - } - } - } - }; - - Log.d(TAG, "Sending GetStatus request: intent=" + intent); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "GetStatus request not supported!"); - } - } - - @Override - public void pause() { - Intent intent = makePauseIntent(); - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - Log.d(TAG, "Pause request succeeded"); - if (isRemoteQueue()) { - mPaused = true; - updateUi(); - } - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Pause request failed: error=" + error); - } - }; - - Log.d(TAG, "Sending pause request"); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Pause request not supported!"); - } - } - - @Override - public void resume() { - Intent intent = makeResumeIntent(); - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - Log.d(TAG, "Resume request succeeded"); - if (isRemoteQueue()) { - mPaused = false; - updateUi(); - } - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Resume request failed: error=" + error); - } - }; - - Log.d(TAG, "Sending resume request"); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Resume request not supported!"); - } - } - - @Override - public void stop() { - Intent intent = makeStopIntent(); - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - Log.d(TAG, "Stop request succeeded"); - if (isRemoteQueue()) { - // Reset mSessionId, so that next Play/Enqueue - // starts a new session - mQueueItem = null; - mSessionId = null; - mPaused = false; - updateUi(); - } - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Stop request failed: error=" + error); - } - }; - - Log.d(TAG, "Sending stop request"); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Stop request not supported!"); - } - } - - @Override - public void showStatistics() { - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Intent intent = makeStatisticsIntent(); - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - Log.d(TAG, "Statistics request succeeded: data=" + data); - if (data != null) { - int playbackCount = data.getInt( - SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1); - mStatsInfo = "Total playback count: " + playbackCount; - } else { - showToast("Statistics query did not return any data"); - } - updateRouteDescription(); - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, "Statistics request failed: error=" + error + ", data=" + data); - showToast("Unable to query statistics, error: " + error); - updateRouteDescription(); - } - }; - - Log.d(TAG, "Sent statistics request: intent=" + intent); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, "Statistics request not supported!"); - } - - } - - @Override - public void onFinish(boolean error) { - updateUi(); - } - - @Override - public void updateSize(int width, int height) { - // nothing to do - } - - private void play(final Uri uri, boolean enqueue, final long pos) { - // save the initial seek position - mPosition = pos; - MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); - Intent intent = makePlayIntent(uri, enqueue); - final String request = enqueue ? "Enqueue" : "Play"; - if (route.supportsControlRequest(intent)) { - MediaRouter.ControlRequestCallback callback = - new MediaRouter.ControlRequestCallback() { - @Override - public void onResult(Bundle data) { - if (data != null) { - String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID); - String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID); - MediaItemStatus status = MediaItemStatus.fromBundle( - data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); - Log.d(TAG, request + " request succeeded: data=" + data + - ", sid=" + sid + ", iid=" + iid); - - // perform delayed initial seek - if (mSessionId == null && mPosition > 0) { - seek(sid, iid, mPosition); - } - - mSessionId = sid; - mItemId = iid; - mQueueItem = new MediaQueueItem(sid, iid, null, null); - - if (isRemoteQueue()) { - MediaQueueItem playlistItem = - new MediaQueueItem(sid, iid, uri, null); - playlistItem.setState(status.getPlaybackState()); - mPlayListItems.add(playlistItem); - updateUi(); - enqueuePlaylist(); - } - } - } - - @Override - public void onError(String error, Bundle data) { - Log.d(TAG, request + " request failed: error=" + error + ", data=" + data); - showToast("Unable to " + request + uri + ", error: " + error); - } - }; - - Log.d(TAG, "Sending " + request + " request: intent=" + intent); - route.sendControlRequest(intent, callback); - } else { - Log.d(TAG, request + " request not supported!"); - } - } - - private Intent makePlayIntent(Uri uri, boolean enqueue) { - Intent intent = new Intent( - enqueue ? MediaControlIntent.ACTION_ENQUEUE - : MediaControlIntent.ACTION_PLAY); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - intent.setDataAndType(uri, "video/mp4"); - - // Provide a valid session id, or none (which starts a new session) - if (mSessionId != null) { - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); - } - - // PendingIntent for receiving status update from MRP - Intent statusIntent = new Intent(SampleMediaRouterActivity.ACTION_STATUS_CHANGE); - intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER, - PendingIntent.getBroadcast(SampleMediaRouterActivity.this, - 0, statusIntent, 0)); - - return intent; - } - - private Intent makeRemoveIntent(MediaQueueItem item) { - Intent intent = new Intent(MediaControlIntent.ACTION_REMOVE); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); - intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); - return intent; - } - - private Intent makeSeekIntent(String sid, String iid, long pos) { - Intent intent = new Intent(MediaControlIntent.ACTION_SEEK); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid); - intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, iid); - intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, pos); - return intent; - } - - private Intent makePauseIntent() { - Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - if (mSessionId != null) { - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); - } - return intent; - } - - private Intent makeResumeIntent() { - Intent intent = new Intent(MediaControlIntent.ACTION_RESUME); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - if (mSessionId != null) { - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); - } - return intent; - } - - private Intent makeStopIntent() { - Intent intent = new Intent(MediaControlIntent.ACTION_STOP); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - if (mSessionId != null) { - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); - } - return intent; - } - - private Intent makeGetStatusIntent(MediaQueueItem item) { - Intent intent = new Intent(MediaControlIntent.ACTION_GET_STATUS); - intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); - intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); - return intent; - } - - private Intent makeStatisticsIntent() { - Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS); - intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE); - return intent; - } - } - public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment { private static final String TAG = "DiscoveryFragment"; private Callback mCallback; @@ -1544,10 +639,12 @@ public class SampleMediaRouterActivity extends ActionBarActivity { private static final class MediaItem { public final String mName; public final Uri mUri; + public final String mMime; - public MediaItem(String name, Uri uri) { + public MediaItem(String name, Uri uri, String mime) { mName = name; mUri = uri; + mMime = mime; } @Override @@ -1582,7 +679,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { @Override public void onClick(View v) { if (item != null) { - mPlayer.enqueue(item.mUri, 0); + mSessionManager.add(item.mUri, item.mMime); } } }); @@ -1591,7 +688,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { } } - private final class PlaylistAdapter extends ArrayAdapter<MediaQueueItem> { + private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> { public PlaylistAdapter() { super(SampleMediaRouterActivity.this, R.layout.media_item); } @@ -1605,7 +702,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { v = convertView; } - final MediaQueueItem item = getItem(position); + final PlaylistItem item = getItem(position); TextView tv = (TextView)v.findViewById(R.id.item_text); tv.setText(item.toString()); @@ -1617,7 +714,7 @@ public class SampleMediaRouterActivity extends ActionBarActivity { @Override public void onClick(View v) { if (item != null) { - mPlayer.remove(item); + mSessionManager.remove(item.getItemId()); } } }); diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/SessionManager.java b/samples/Support7Demos/src/com/example/android/supportv7/media/SessionManager.java new file mode 100644 index 000000000..23f2a89c7 --- /dev/null +++ b/samples/Support7Demos/src/com/example/android/supportv7/media/SessionManager.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.supportv7.media; + +import android.app.PendingIntent; +import android.net.Uri; +import android.support.v7.media.MediaItemStatus; +import android.support.v7.media.MediaSessionStatus; +import android.util.Log; + +import java.util.List; +import java.util.ArrayList; + +/** + * SessionManager manages a media session as a queue. It supports common + * queuing behaviors such as enqueue/remove of media items, pause/resume/stop, + * etc. + * + * Actual playback of a single media item is abstracted into a Player interface, + * and is handled outside this class. + */ +public class SessionManager implements Player.Callback { + private static final String TAG = "SessionManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private String mName; + private int mSessionId; + private int mItemId; + private boolean mPaused; + private boolean mSessionValid; + private Player mPlayer; + private Callback mCallback; + private List<PlaylistItem> mPlaylist = new ArrayList<PlaylistItem>(); + + public SessionManager(String name) { + mName = name; + } + + public boolean hasSession() { + return mSessionValid; + } + + public String getSessionId() { + return mSessionValid ? Integer.toString(mSessionId) : null; + } + + public PlaylistItem getCurrentItem() { + return mPlaylist.isEmpty() ? null : mPlaylist.get(0); + } + + // Get the cached statistic info from the player (will not update it) + public String getStatistics() { + checkPlayer(); + return mPlayer.getStatistics(); + } + + // Returns the cached playlist (note this is not responsible for updating it) + public List<PlaylistItem> getPlaylist() { + return mPlaylist; + } + + // Updates the playlist asynchronously, calls onPlaylistReady() when finished. + public void updateStatus() { + if (DEBUG) { + log("updateStatus"); + } + checkPlayer(); + // update the statistics first, so that the stats string is valid when + // onPlaylistReady() gets called in the end + mPlayer.updateStatistics(); + + if (mPlaylist.isEmpty()) { + // If queue is empty, don't forget to call onPlaylistReady()! + onPlaylistReady(); + } else if (mPlayer.isQueuingSupported()) { + // If player supports queuing, get status of each item. Player is + // responsible to call onPlaylistReady() after last getStatus(). + // (update=1 requires player to callback onPlaylistReady()) + for (int i = 0; i < mPlaylist.size(); i++) { + PlaylistItem item = mPlaylist.get(i); + mPlayer.getStatus(item, (i == mPlaylist.size() - 1) /* update */); + } + } else { + // Otherwise, only need to get status for current item. Player is + // responsible to call onPlaylistReady() when finished. + mPlayer.getStatus(getCurrentItem(), true /* update */); + } + } + + public PlaylistItem add(Uri uri, String mime) { + return add(uri, mime, null); + } + + public PlaylistItem add(Uri uri, String mime, PendingIntent receiver) { + if (DEBUG) { + log("add: uri=" + uri + ", receiver=" + receiver); + } + // create new session if needed + startSession(); + checkPlayerAndSession(); + + // append new item with initial status PLAYBACK_STATE_PENDING + PlaylistItem item = new PlaylistItem( + Integer.toString(mSessionId), Integer.toString(mItemId), uri, mime, receiver); + mPlaylist.add(item); + mItemId++; + + // if player supports queuing, enqueue the item now + if (mPlayer.isQueuingSupported()) { + mPlayer.enqueue(item); + } + updatePlaybackState(); + return item; + } + + public PlaylistItem remove(String iid) { + if (DEBUG) { + log("remove: iid=" + iid); + } + checkPlayerAndSession(); + return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED); + } + + public PlaylistItem seek(String iid, long pos) { + if (DEBUG) { + log("seek: iid=" + iid +", pos=" + pos); + } + checkPlayerAndSession(); + // seeking on pending items are not yet supported + checkItemCurrent(iid); + + PlaylistItem item = getCurrentItem(); + if (pos != item.getPosition()) { + item.setPosition(pos); + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING + || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { + mPlayer.seek(item); + } + } + return item; + } + + public PlaylistItem getStatus(String iid) { + checkPlayerAndSession(); + + // This should only be called for local player. Remote player is + // asynchronous, need to use updateStatus() instead. + if (mPlayer.isRemotePlayback()) { + throw new IllegalStateException( + "getStatus should not be called on remote player!"); + } + + for (PlaylistItem item : mPlaylist) { + if (item.getItemId().equals(iid)) { + if (item == getCurrentItem()) { + mPlayer.getStatus(item, false); + } + return item; + } + } + return null; + } + + public void pause() { + if (DEBUG) { + log("pause"); + } + checkPlayerAndSession(); + mPaused = true; + updatePlaybackState(); + } + + public void resume() { + if (DEBUG) { + log("resume"); + } + checkPlayerAndSession(); + mPaused = false; + updatePlaybackState(); + } + + public void stop() { + if (DEBUG) { + log("stop"); + } + checkPlayerAndSession(); + mPlayer.stop(); + mPlaylist.clear(); + mPaused = false; + updateStatus(); + } + + public String startSession() { + if (!mSessionValid) { + mSessionId++; + mItemId = 0; + mPaused = false; + mSessionValid = true; + return Integer.toString(mSessionId); + } + return null; + } + + public boolean endSession() { + if (mSessionValid) { + mSessionValid = false; + return true; + } + return false; + } + + MediaSessionStatus getSessionStatus(String sid) { + int sessionState = (sid != null && sid.equals(mSessionId)) ? + MediaSessionStatus.SESSION_STATE_ACTIVE : + MediaSessionStatus.SESSION_STATE_INVALIDATED; + + return new MediaSessionStatus.Builder(sessionState) + .setQueuePaused(mPaused) + .build(); + } + + // Suspend the playback manager. Put the current item back into PENDING + // state, and remember the current playback position. Called when switching + // to a different player (route). + public void suspend(long pos) { + for (PlaylistItem item : mPlaylist) { + item.setRemoteItemId(null); + item.setDuration(0); + } + PlaylistItem item = getCurrentItem(); + if (DEBUG) { + log("suspend: item=" + item + ", pos=" + pos); + } + if (item != null) { + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING + || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { + item.setState(MediaItemStatus.PLAYBACK_STATE_PENDING); + item.setPosition(pos); + } + } + } + + // Unsuspend the playback manager. Restart playback on new player (route). + // This will resume playback of current item. Furthermore, if the new player + // supports queuing, playlist will be re-established on the remote player. + public void unsuspend() { + if (DEBUG) { + log("unsuspend"); + } + if (mPlayer.isQueuingSupported()) { + for (PlaylistItem item : mPlaylist) { + mPlayer.enqueue(item); + } + } + updatePlaybackState(); + } + + // Player.Callback + @Override + public void onError() { + finishItem(true); + } + + @Override + public void onCompletion() { + finishItem(false); + } + + @Override + public void onPlaylistChanged() { + // Playlist has changed, update the cached playlist + updateStatus(); + } + + @Override + public void onPlaylistReady() { + // Notify activity to update Ui + if (mCallback != null) { + mCallback.onStatusChanged(); + } + } + + private void log(String message) { + Log.d(TAG, mName + ": " + message); + } + + private void checkPlayer() { + if (mPlayer == null) { + throw new IllegalStateException("Player not set!"); + } + } + + private void checkSession() { + if (!mSessionValid) { + throw new IllegalStateException("Session not set!"); + } + } + + private void checkPlayerAndSession() { + checkPlayer(); + checkSession(); + } + + private void checkItemCurrent(String iid) { + PlaylistItem item = getCurrentItem(); + if (item == null || !item.getItemId().equals(iid)) { + throw new IllegalArgumentException("Item is not current!"); + } + } + + private void updatePlaybackState() { + PlaylistItem item = getCurrentItem(); + if (item != null) { + if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) { + item.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED + : MediaItemStatus.PLAYBACK_STATE_PLAYING); + if (!mPlayer.isQueuingSupported()) { + mPlayer.play(item); + } + } else if (mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) { + mPlayer.pause(); + item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED); + } else if (!mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { + mPlayer.resume(); + item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING); + } + // notify client that item playback status has changed + if (mCallback != null) { + mCallback.onItemChanged(item); + } + } + updateStatus(); + } + + private PlaylistItem removeItem(String iid, int state) { + checkPlayerAndSession(); + List<PlaylistItem> queue = + new ArrayList<PlaylistItem>(mPlaylist.size()); + PlaylistItem found = null; + for (PlaylistItem item : mPlaylist) { + if (iid.equals(item.getItemId())) { + if (mPlayer.isQueuingSupported()) { + mPlayer.remove(item.getRemoteItemId()); + } else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING + || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED){ + mPlayer.stop(); + } + item.setState(state); + found = item; + // notify client that item is now removed + if (mCallback != null) { + mCallback.onItemChanged(found); + } + } else { + queue.add(item); + } + } + if (found != null) { + mPlaylist = queue; + updatePlaybackState(); + } else { + log("item not found"); + } + return found; + } + + private void finishItem(boolean error) { + PlaylistItem item = getCurrentItem(); + if (item != null) { + removeItem(item.getItemId(), error ? + MediaItemStatus.PLAYBACK_STATE_ERROR : + MediaItemStatus.PLAYBACK_STATE_FINISHED); + updateStatus(); + } + } + + // set the Player that this playback manager will interact with + public void setPlayer(Player player) { + mPlayer = player; + checkPlayer(); + mPlayer.setCallback(this); + } + + // provide a callback interface to tell the UI when significant state changes occur + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public String toString() { + String result = "Media Queue: "; + if (!mPlaylist.isEmpty()) { + for (PlaylistItem item : mPlaylist) { + result += "\n" + item.toString(); + } + } else { + result += "<empty>"; + } + return result; + } + + public interface Callback { + void onStatusChanged(); + void onItemChanged(PlaylistItem item); + } +} |