mrp sample: refactor and use helper class
Bug: 10955351 Change-Id: Ib607d27bc93a35460c6acd295d29afad3f255e25
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="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="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>
|
<string name="sample_route_description">Sample route from Support7Demos</string>
|
||||||
|
|
||||||
<!-- GridLayout -->
|
<!-- 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;
|
package com.example.android.supportv7.media;
|
||||||
|
|
||||||
import android.support.v7.media.MediaItemStatus;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.app.PendingIntent;
|
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
|
// immutables
|
||||||
private final String mSessionId;
|
private final String mSessionId;
|
||||||
private final String mItemId;
|
private final String mItemId;
|
||||||
private final Uri mUri;
|
private final Uri mUri;
|
||||||
|
private final String mMime;
|
||||||
private final PendingIntent mUpdateReceiver;
|
private final PendingIntent mUpdateReceiver;
|
||||||
// changeable states
|
// changeable states
|
||||||
private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
|
private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
|
||||||
private long mContentPosition;
|
private long mContentPosition;
|
||||||
private long mContentDuration;
|
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;
|
mSessionId = qid;
|
||||||
mItemId = iid;
|
mItemId = iid;
|
||||||
mUri = uri;
|
mUri = uri;
|
||||||
|
mMime = mime;
|
||||||
mUpdateReceiver = pi;
|
mUpdateReceiver = pi;
|
||||||
|
setTimestamp(SystemClock.elapsedRealtime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoteItemId(String riid) {
|
||||||
|
mRemoteItemId = riid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(int state) {
|
public void setState(int state) {
|
||||||
mPlaybackState = state;
|
mPlaybackState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContentPosition(long pos) {
|
public void setPosition(long pos) {
|
||||||
mContentPosition = pos;
|
mContentPosition = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContentDuration(long duration) {
|
public void setTimestamp(long ts) {
|
||||||
|
mTimestamp = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(long duration) {
|
||||||
mContentDuration = duration;
|
mContentDuration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +75,10 @@ final class MediaQueueItem {
|
|||||||
return mItemId;
|
return mItemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRemoteItemId() {
|
||||||
|
return mRemoteItemId;
|
||||||
|
}
|
||||||
|
|
||||||
public Uri getUri() {
|
public Uri getUri() {
|
||||||
return mUri;
|
return mUri;
|
||||||
}
|
}
|
||||||
@@ -73,18 +91,23 @@ final class MediaQueueItem {
|
|||||||
return mPlaybackState;
|
return mPlaybackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getContentPosition() {
|
public long getPosition() {
|
||||||
return mContentPosition;
|
return mContentPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getContentDuration() {
|
public long getDuration() {
|
||||||
return mContentDuration;
|
return mContentDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return mTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
public MediaItemStatus getStatus() {
|
public MediaItemStatus getStatus() {
|
||||||
return new MediaItemStatus.Builder(mPlaybackState)
|
return new MediaItemStatus.Builder(mPlaybackState)
|
||||||
.setContentPosition(mContentPosition)
|
.setContentPosition(mContentPosition)
|
||||||
.setContentDuration(mContentDuration)
|
.setContentDuration(mContentDuration)
|
||||||
|
.setTimestamp(mTimestamp)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +124,7 @@ final class MediaQueueItem {
|
|||||||
"ERROR"
|
"ERROR"
|
||||||
};
|
};
|
||||||
return "[" + mSessionId + "|" + mItemId + "|"
|
return "[" + mSessionId + "|" + mItemId + "|"
|
||||||
|
+ (mRemoteItemId != null ? mRemoteItemId : "-") + "|"
|
||||||
+ state[mPlaybackState] + "] " + mUri.toString();
|
+ 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.MediaRouter.ControlRequestCallback;
|
||||||
import android.support.v7.media.MediaRouteProviderDescriptor;
|
import android.support.v7.media.MediaRouteProviderDescriptor;
|
||||||
import android.support.v7.media.MediaRouteDescriptor;
|
import android.support.v7.media.MediaRouteDescriptor;
|
||||||
|
import android.support.v7.media.MediaSessionStatus;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@@ -50,7 +51,9 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
private static final String TAG = "SampleMediaRouteProvider";
|
private static final String TAG = "SampleMediaRouteProvider";
|
||||||
|
|
||||||
private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
|
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;
|
private static final int VOLUME_MAX = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +83,10 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
public static final String DATA_PLAYBACK_COUNT =
|
public static final String DATA_PLAYBACK_COUNT =
|
||||||
"com.example.android.supportv7.media.EXTRA_PLAYBACK_COUNT";
|
"com.example.android.supportv7.media.EXTRA_PLAYBACK_COUNT";
|
||||||
|
|
||||||
/*
|
private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
|
||||||
* Set ENABLE_QUEUEING to true to test queuing on MRP. This will make
|
private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING;
|
||||||
* MRP expose the following two experimental hidden APIs:
|
private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION;
|
||||||
* ACTION_ENQUEUE
|
|
||||||
* ACTION_REMOVE
|
|
||||||
*/
|
|
||||||
public static final boolean ENABLE_QUEUEING = false;
|
|
||||||
|
|
||||||
private static final ArrayList<IntentFilter> CONTROL_FILTERS;
|
|
||||||
static {
|
static {
|
||||||
IntentFilter f1 = new IntentFilter();
|
IntentFilter f1 = new IntentFilter();
|
||||||
f1.addCategory(CATEGORY_SAMPLE_ROUTE);
|
f1.addCategory(CATEGORY_SAMPLE_ROUTE);
|
||||||
@@ -124,14 +122,25 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
f5.addAction(MediaControlIntent.ACTION_REMOVE);
|
f5.addAction(MediaControlIntent.ACTION_REMOVE);
|
||||||
|
|
||||||
CONTROL_FILTERS = new ArrayList<IntentFilter>();
|
IntentFilter f6 = new IntentFilter();
|
||||||
CONTROL_FILTERS.add(f1);
|
f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
CONTROL_FILTERS.add(f2);
|
f6.addAction(MediaControlIntent.ACTION_START_SESSION);
|
||||||
CONTROL_FILTERS.add(f3);
|
f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
|
||||||
if (ENABLE_QUEUEING) {
|
f6.addAction(MediaControlIntent.ACTION_END_SESSION);
|
||||||
CONTROL_FILTERS.add(f4);
|
|
||||||
CONTROL_FILTERS.add(f5);
|
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) {
|
private static void addDataTypeUnchecked(IntentFilter filter, String type) {
|
||||||
@@ -163,7 +172,7 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
FIXED_VOLUME_ROUTE_ID,
|
FIXED_VOLUME_ROUTE_ID,
|
||||||
r.getString(R.string.fixed_volume_route_name))
|
r.getString(R.string.fixed_volume_route_name))
|
||||||
.setDescription(r.getString(R.string.sample_route_description))
|
.setDescription(r.getString(R.string.sample_route_description))
|
||||||
.addControlFilters(CONTROL_FILTERS)
|
.addControlFilters(CONTROL_FILTERS_BASIC)
|
||||||
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
|
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
|
||||||
@@ -171,10 +180,34 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
|
MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
|
||||||
VARIABLE_VOLUME_ROUTE_ID,
|
VARIABLE_VOLUME_BASIC_ROUTE_ID,
|
||||||
r.getString(R.string.variable_volume_route_name))
|
r.getString(R.string.variable_volume_basic_route_name))
|
||||||
.setDescription(r.getString(R.string.sample_route_description))
|
.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)
|
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||||
@@ -186,70 +219,57 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
new MediaRouteProviderDescriptor.Builder()
|
new MediaRouteProviderDescriptor.Builder()
|
||||||
.addRoute(routeDescriptor1)
|
.addRoute(routeDescriptor1)
|
||||||
.addRoute(routeDescriptor2)
|
.addRoute(routeDescriptor2)
|
||||||
|
.addRoute(routeDescriptor3)
|
||||||
|
.addRoute(routeDescriptor4)
|
||||||
.build();
|
.build();
|
||||||
setDescriptor(providerDescriptor);
|
setDescriptor(providerDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class SampleRouteController extends MediaRouteProvider.RouteController {
|
private final class SampleRouteController extends MediaRouteProvider.RouteController {
|
||||||
private final String mRouteId;
|
private final String mRouteId;
|
||||||
private final OverlayDisplayWindow mOverlay;
|
private final SessionManager mSessionManager = new SessionManager("mrp");
|
||||||
private final MediaPlayerWrapper mMediaPlayer;
|
private final Player mPlayer;
|
||||||
private final MediaSessionManager mSessionManager;
|
private PendingIntent mSessionReceiver;
|
||||||
|
|
||||||
public SampleRouteController(String routeId) {
|
public SampleRouteController(String routeId) {
|
||||||
mRouteId = routeId;
|
mRouteId = routeId;
|
||||||
mMediaPlayer = new MediaPlayerWrapper(getContext());
|
mPlayer = Player.create(getContext(), null);
|
||||||
mSessionManager = new MediaSessionManager();
|
mSessionManager.setPlayer(mPlayer);
|
||||||
mSessionManager.setCallback(mMediaPlayer);
|
mSessionManager.setCallback(new SessionManager.Callback() {
|
||||||
|
|
||||||
// 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() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onWindowCreated(Surface surface) {
|
public void onStatusChanged() {
|
||||||
mMediaPlayer.setSurface(surface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWindowCreated(SurfaceHolder surfaceHolder) {
|
public void onItemChanged(PlaylistItem item) {
|
||||||
mMediaPlayer.setSurface(surfaceHolder);
|
handleStatusChange(item);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowDestroyed() {
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mMediaPlayer.setCallback(new MediaPlayerCallback());
|
|
||||||
Log.d(TAG, mRouteId + ": Controller created");
|
Log.d(TAG, mRouteId + ": Controller created");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRelease() {
|
public void onRelease() {
|
||||||
Log.d(TAG, mRouteId + ": Controller released");
|
Log.d(TAG, mRouteId + ": Controller released");
|
||||||
mMediaPlayer.release();
|
mPlayer.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelect() {
|
public void onSelect() {
|
||||||
Log.d(TAG, mRouteId + ": Selected");
|
Log.d(TAG, mRouteId + ": Selected");
|
||||||
mOverlay.show();
|
mPlayer.connect(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnselect() {
|
public void onUnselect() {
|
||||||
Log.d(TAG, mRouteId + ": Unselected");
|
Log.d(TAG, mRouteId + ": Unselected");
|
||||||
mMediaPlayer.onStop();
|
mPlayer.release();
|
||||||
mOverlay.dismiss();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetVolume(int volume) {
|
public void onSetVolume(int volume) {
|
||||||
Log.d(TAG, mRouteId + ": Set volume to " + 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);
|
setVolumeInternal(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +277,7 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void onUpdateVolume(int delta) {
|
public void onUpdateVolume(int delta) {
|
||||||
Log.d(TAG, mRouteId + ": Update volume by " + 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);
|
setVolumeInternal(mVolume + delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +304,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
success = handleResume(intent, callback);
|
success = handleResume(intent, callback);
|
||||||
} else if (action.equals(MediaControlIntent.ACTION_STOP)) {
|
} else if (action.equals(MediaControlIntent.ACTION_STOP)) {
|
||||||
success = handleStop(intent, callback);
|
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());
|
Log.d(TAG, mSessionManager.toString());
|
||||||
return success;
|
return success;
|
||||||
@@ -314,23 +340,31 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
|
|
||||||
private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
|
private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||||
if (sid == null || mSessionManager.stop(sid)) {
|
if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
|
||||||
Log.d(TAG, "handleEnqueue");
|
Log.d(TAG, "handlePlay fails because of bad sid="+sid);
|
||||||
return handleEnqueue(intent, callback);
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
if (mSessionManager.hasSession()) {
|
||||||
|
mSessionManager.stop();
|
||||||
|
}
|
||||||
|
return handleEnqueue(intent, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleEnqueue(Intent intent, ControlRequestCallback 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;
|
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);
|
boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE);
|
||||||
Uri uri = intent.getData();
|
String mime = intent.getType();
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
|
||||||
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
||||||
Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
|
Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
|
||||||
Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
|
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"
|
Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request"
|
||||||
+ ", uri=" + uri
|
+ ", uri=" + uri
|
||||||
|
+ ", mime=" + mime
|
||||||
+ ", sid=" + sid
|
+ ", sid=" + sid
|
||||||
+ ", pos=" + pos
|
+ ", pos=" + pos
|
||||||
+ ", metadata=" + metadata
|
+ ", metadata=" + metadata
|
||||||
+ ", headers=" + headers
|
+ ", headers=" + headers
|
||||||
+ ", receiver=" + receiver);
|
+ ", receiver=" + receiver);
|
||||||
MediaQueueItem item = mSessionManager.enqueue(sid, uri, receiver);
|
PlaylistItem item = mSessionManager.add(uri, mime, receiver);
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
@@ -357,13 +392,18 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
callback.onError("Failed to open " + uri.toString(), null);
|
callback.onError("Failed to open " + uri.toString(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mEnqueueCount +=1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
|
private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
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);
|
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||||
MediaQueueItem item = mSessionManager.remove(sid, iid);
|
PlaylistItem item = mSessionManager.remove(iid);
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
@@ -380,10 +420,14 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
|
|
||||||
private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
|
private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
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);
|
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||||
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
||||||
Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
|
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 (callback != null) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
@@ -401,7 +445,8 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
|
private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||||
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_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 (callback != null) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
@@ -418,10 +463,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
|
|
||||||
private boolean handlePause(Intent intent, ControlRequestCallback callback) {
|
private boolean handlePause(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
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 (callback != null) {
|
||||||
if (success) {
|
if (success) {
|
||||||
callback.onResult(null);
|
callback.onResult(new Bundle());
|
||||||
|
handleSessionStatusChange(sid);
|
||||||
} else {
|
} else {
|
||||||
callback.onError("Failed to pause, sid=" + sid, null);
|
callback.onError("Failed to pause, sid=" + sid, null);
|
||||||
}
|
}
|
||||||
@@ -431,10 +478,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
|
|
||||||
private boolean handleResume(Intent intent, ControlRequestCallback callback) {
|
private boolean handleResume(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
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 (callback != null) {
|
||||||
if (success) {
|
if (success) {
|
||||||
callback.onResult(null);
|
callback.onResult(new Bundle());
|
||||||
|
handleSessionStatusChange(sid);
|
||||||
} else {
|
} else {
|
||||||
callback.onError("Failed to resume, sid=" + sid, null);
|
callback.onError("Failed to resume, sid=" + sid, null);
|
||||||
}
|
}
|
||||||
@@ -444,10 +493,12 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
|
|
||||||
private boolean handleStop(Intent intent, ControlRequestCallback callback) {
|
private boolean handleStop(Intent intent, ControlRequestCallback callback) {
|
||||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
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 (callback != null) {
|
||||||
if (success) {
|
if (success) {
|
||||||
callback.onResult(null);
|
callback.onResult(new Bundle());
|
||||||
|
handleSessionStatusChange(sid);
|
||||||
} else {
|
} else {
|
||||||
callback.onError("Failed to stop, sid=" + sid, null);
|
callback.onError("Failed to stop, sid=" + sid, null);
|
||||||
}
|
}
|
||||||
@@ -455,14 +506,64 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFinish(boolean error) {
|
private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
|
||||||
MediaQueueItem item = mSessionManager.finish(error);
|
String sid = mSessionManager.startSession();
|
||||||
if (item != null) {
|
Log.d(TAG, "StartSession returns sessionId "+sid);
|
||||||
handleStatusChange(item);
|
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) {
|
if (item == null) {
|
||||||
item = mSessionManager.getCurrentItem();
|
item = mSessionManager.getCurrentItem();
|
||||||
}
|
}
|
||||||
@@ -484,25 +585,18 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class MediaPlayerCallback extends MediaPlayerWrapper.Callback {
|
private void handleSessionStatusChange(String sid) {
|
||||||
@Override
|
if (mSessionReceiver != null) {
|
||||||
public void onError() {
|
Intent intent = new Intent();
|
||||||
handleFinish(true);
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
|
||||||
}
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
|
||||||
|
mSessionManager.getSessionStatus(sid).asBundle());
|
||||||
@Override
|
try {
|
||||||
public void onCompletion() {
|
mSessionReceiver.send(getContext(), 0, intent);
|
||||||
handleFinish(false);
|
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 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