summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChong Zhang <chz@google.com>2013-10-21 17:24:41 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2013-10-21 17:24:41 -0700
commit58ef2c388c9d1e7a27dfdbe84e90456cb1d2d157 (patch)
tree49913b34f47d4fb04aec4ffc4542ec246b5dc37c
parent10f225872313bd36188eaeec5c2417a8cb9ce1da (diff)
parentfd6d06a19ce06d0f3e4fa8c58c283be2f07aabfc (diff)
downloadandroid_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
-rw-r--r--samples/Support7Demos/res/values/strings.xml4
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/LocalPlayer.java632
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/MediaPlayerWrapper.java287
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/MediaSessionManager.java289
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/Player.java82
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/PlaylistItem.java (renamed from samples/Support7Demos/src/com/example/android/supportv7/media/MediaQueueItem.java)42
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/RemotePlayer.java480
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouteProvider.java278
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java1125
-rw-r--r--samples/Support7Demos/src/com/example/android/supportv7/media/SessionManager.java420
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);
+ }
+}