am fd6d06a1: mrp sample: refactor and use helper class
* commit 'fd6d06a19ce06d0f3e4fa8c58c283be2f07aabfc': mrp sample: refactor and use helper class
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
.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_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;
|
||||
}
|
||||
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 void handleStatusChange(MediaQueueItem item) {
|
||||
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 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);
|
||||
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!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompletion() {
|
||||
handleFinish(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged() {
|
||||
handleStatusChange(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeChanged(int width, int height) {
|
||||
mOverlay.updateAspectRatio(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user