Adding browsable prebuilt samples for march push
Change-Id: I952db10d9c9acb4940db08a07789347ea2effe4d
This commit is contained in:
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Presentation;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
import android.support.v7.media.MediaRouter.RouteInfo;
|
||||
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.mediarouter.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,724 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioManager.OnAudioFocusChangeListener;
|
||||
import android.media.RemoteControlClient;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.MediaRouteActionProvider;
|
||||
import android.support.v7.app.MediaRouteDiscoveryFragment;
|
||||
import android.support.v7.media.MediaControlIntent;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
import android.support.v7.media.MediaRouteSelector;
|
||||
import android.support.v7.media.MediaRouter;
|
||||
import android.support.v7.media.MediaRouter.Callback;
|
||||
import android.support.v7.media.MediaRouter.ProviderInfo;
|
||||
import android.support.v7.media.MediaRouter.RouteInfo;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.OnTabChangeListener;
|
||||
import android.widget.TabHost.TabSpec;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.android.mediarouter.R;
|
||||
import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <h3>Media Router Support Activity</h3>
|
||||
* <p/>
|
||||
* <p>
|
||||
* This demonstrates how to use the {@link MediaRouter} API to build an
|
||||
* application that allows the user to send content to various rendering
|
||||
* targets.
|
||||
* </p>
|
||||
*/
|
||||
public class MainActivity extends ActionBarActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
|
||||
|
||||
private MediaRouter mMediaRouter;
|
||||
private MediaRouteSelector mSelector;
|
||||
private LibraryAdapter mLibraryItems;
|
||||
private PlaylistAdapter mPlayListItems;
|
||||
private TextView mInfoTextView;
|
||||
private ListView mLibraryView;
|
||||
private ListView mPlayListView;
|
||||
private ImageButton mPauseResumeButton;
|
||||
private ImageButton mStopButton;
|
||||
private SeekBar mSeekBar;
|
||||
private boolean mPaused;
|
||||
private boolean mNeedResume;
|
||||
private boolean mSeeking;
|
||||
|
||||
private RemoteControlClient mRemoteControlClient;
|
||||
private ComponentName mEventReceiver;
|
||||
private AudioManager mAudioManager;
|
||||
private PendingIntent mMediaPendingIntent;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
private final Runnable mUpdateSeekRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateProgress();
|
||||
// update UI every 1 second
|
||||
mHandler.postDelayed(this, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
private final SessionManager mSessionManager = new SessionManager("app");
|
||||
private Player mPlayer;
|
||||
|
||||
private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
|
||||
// Return a custom callback that will simply log all of the route events
|
||||
// for demonstration purposes.
|
||||
@Override
|
||||
public void onRouteAdded(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteAdded: route=" + route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteChanged(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteChanged: route=" + route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteRemoved: route=" + route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteSelected: route=" + route);
|
||||
|
||||
mPlayer = Player.create(MainActivity.this, route);
|
||||
mPlayer.updatePresentation();
|
||||
mSessionManager.setPlayer(mPlayer);
|
||||
mSessionManager.unsuspend();
|
||||
|
||||
registerRemoteControlClient();
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteUnselected(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteUnselected: route=" + route);
|
||||
unregisterRemoteControlClient();
|
||||
|
||||
PlaylistItem item = getCheckedPlaylistItem();
|
||||
if (item != null) {
|
||||
long pos = item.getPosition() +
|
||||
(mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp()));
|
||||
mSessionManager.suspend(pos);
|
||||
}
|
||||
mPlayer.updatePresentation();
|
||||
mPlayer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRouteVolumeChanged: route=" + route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
|
||||
Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
|
||||
mPlayer.updatePresentation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
|
||||
Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
|
||||
Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
|
||||
Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnAudioFocusChangeListener mAfChangeListener = new OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
|
||||
Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
|
||||
Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||
Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// Be sure to call the super class.
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mPlayer = (Player) savedInstanceState.getSerializable("mPlayer");
|
||||
}
|
||||
|
||||
// Get the media router service.
|
||||
mMediaRouter = MediaRouter.getInstance(this);
|
||||
|
||||
// Create a route selector for the type of routes that we care about.
|
||||
mSelector =
|
||||
new MediaRouteSelector.Builder().addControlCategory(MediaControlIntent
|
||||
.CATEGORY_LIVE_AUDIO).addControlCategory(MediaControlIntent
|
||||
.CATEGORY_LIVE_VIDEO).addControlCategory(MediaControlIntent
|
||||
.CATEGORY_REMOTE_PLAYBACK).addControlCategory(SampleMediaRouteProvider
|
||||
.CATEGORY_SAMPLE_ROUTE).build();
|
||||
|
||||
// Add a fragment to take care of media route discovery.
|
||||
// This fragment automatically adds or removes a callback whenever the activity
|
||||
// is started or stopped.
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
DiscoveryFragment fragment =
|
||||
(DiscoveryFragment) fm.findFragmentByTag(DISCOVERY_FRAGMENT_TAG);
|
||||
if (fragment == null) {
|
||||
fragment = new DiscoveryFragment(mMediaRouterCB);
|
||||
fragment.setRouteSelector(mSelector);
|
||||
fm.beginTransaction().add(fragment, DISCOVERY_FRAGMENT_TAG).commit();
|
||||
} else {
|
||||
fragment.setCallback(mMediaRouterCB);
|
||||
fragment.setRouteSelector(mSelector);
|
||||
}
|
||||
|
||||
// Populate an array adapter with streaming media items.
|
||||
String[] mediaNames = getResources().getStringArray(R.array.media_names);
|
||||
String[] mediaUris = getResources().getStringArray(R.array.media_uris);
|
||||
mLibraryItems = new LibraryAdapter();
|
||||
for (int i = 0; i < mediaNames.length; i++) {
|
||||
mLibraryItems.add(new MediaItem(
|
||||
"[streaming] " + mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4"));
|
||||
}
|
||||
|
||||
// Scan local external storage directory for media files.
|
||||
File externalDir = Environment.getExternalStorageDirectory();
|
||||
if (externalDir != null) {
|
||||
File list[] = externalDir.listFiles();
|
||||
if (list != null) {
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
String filename = list[i].getName();
|
||||
if (filename.matches(".*\\.(m4v|mp4)")) {
|
||||
mLibraryItems.add(new MediaItem(
|
||||
"[local] " + filename, Uri.fromFile(list[i]), "video/mp4"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPlayListItems = new PlaylistAdapter();
|
||||
|
||||
// Initialize the layout.
|
||||
setContentView(R.layout.sample_media_router);
|
||||
|
||||
TabHost tabHost = (TabHost) findViewById(R.id.tabHost);
|
||||
tabHost.setup();
|
||||
String tabName = getResources().getString(R.string.library_tab_text);
|
||||
TabSpec spec1 = tabHost.newTabSpec(tabName);
|
||||
spec1.setContent(R.id.tab1);
|
||||
spec1.setIndicator(tabName);
|
||||
|
||||
tabName = getResources().getString(R.string.playlist_tab_text);
|
||||
TabSpec spec2 = tabHost.newTabSpec(tabName);
|
||||
spec2.setIndicator(tabName);
|
||||
spec2.setContent(R.id.tab2);
|
||||
|
||||
tabName = getResources().getString(R.string.statistics_tab_text);
|
||||
TabSpec spec3 = tabHost.newTabSpec(tabName);
|
||||
spec3.setIndicator(tabName);
|
||||
spec3.setContent(R.id.tab3);
|
||||
|
||||
tabHost.addTab(spec1);
|
||||
tabHost.addTab(spec2);
|
||||
tabHost.addTab(spec3);
|
||||
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
|
||||
@Override
|
||||
public void onTabChanged(String arg0) {
|
||||
updateUi();
|
||||
}
|
||||
});
|
||||
|
||||
mLibraryView = (ListView) findViewById(R.id.media);
|
||||
mLibraryView.setAdapter(mLibraryItems);
|
||||
mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
mLibraryView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
updateButtons();
|
||||
}
|
||||
});
|
||||
|
||||
mPlayListView = (ListView) findViewById(R.id.playlist);
|
||||
mPlayListView.setAdapter(mPlayListItems);
|
||||
mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
mPlayListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
updateButtons();
|
||||
}
|
||||
});
|
||||
|
||||
mInfoTextView = (TextView) findViewById(R.id.info);
|
||||
|
||||
mPauseResumeButton = (ImageButton) findViewById(R.id.pause_resume_button);
|
||||
mPauseResumeButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mPaused = !mPaused;
|
||||
if (mPaused) {
|
||||
mSessionManager.pause();
|
||||
} else {
|
||||
mSessionManager.resume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mStopButton = (ImageButton) findViewById(R.id.stop_button);
|
||||
mStopButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mPaused = false;
|
||||
mSessionManager.stop();
|
||||
}
|
||||
});
|
||||
|
||||
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
|
||||
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
PlaylistItem item = getCheckedPlaylistItem();
|
||||
if (fromUser && item != null && item.getDuration() > 0) {
|
||||
long pos = progress * item.getDuration() / 100;
|
||||
mSessionManager.seek(item.getItemId(), pos);
|
||||
item.setPosition(pos);
|
||||
item.setTimestamp(SystemClock.elapsedRealtime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
mSeeking = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
mSeeking = false;
|
||||
updateUi();
|
||||
}
|
||||
});
|
||||
|
||||
// Schedule Ui update
|
||||
mHandler.postDelayed(mUpdateSeekRunnable, 1000);
|
||||
|
||||
// Build the PendingIntent for the remote control client
|
||||
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
mEventReceiver =
|
||||
new ComponentName(getPackageName(), SampleMediaButtonReceiver.class.getName());
|
||||
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
mediaButtonIntent.setComponent(mEventReceiver);
|
||||
mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
|
||||
|
||||
// Create and register the remote control client
|
||||
registerRemoteControlClient();
|
||||
|
||||
// Set up playback manager and player
|
||||
mPlayer = Player.create(MainActivity.this, mMediaRouter.getSelectedRoute());
|
||||
mSessionManager.setPlayer(mPlayer);
|
||||
mSessionManager.setCallback(new SessionManager.Callback() {
|
||||
@Override
|
||||
public void onStatusChanged() {
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemChanged(PlaylistItem item) {
|
||||
}
|
||||
});
|
||||
|
||||
updateUi();
|
||||
}
|
||||
|
||||
private void registerRemoteControlClient() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
// Create the RCC and register with AudioManager and MediaRouter
|
||||
mAudioManager.requestAudioFocus(mAfChangeListener, AudioManager.STREAM_MUSIC,
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
mAudioManager.registerMediaButtonEventReceiver(mEventReceiver);
|
||||
mRemoteControlClient = new RemoteControlClient(mMediaPendingIntent);
|
||||
mAudioManager.registerRemoteControlClient(mRemoteControlClient);
|
||||
mMediaRouter.addRemoteControlClient(mRemoteControlClient);
|
||||
SampleMediaButtonReceiver.setActivity(MainActivity.this);
|
||||
mRemoteControlClient.setTransportControlFlags(RemoteControlClient
|
||||
.FLAG_KEY_MEDIA_PLAY_PAUSE);
|
||||
mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterRemoteControlClient() {
|
||||
// Unregister the RCC with AudioManager and MediaRouter
|
||||
if (mRemoteControlClient != null) {
|
||||
mRemoteControlClient.setTransportControlFlags(0);
|
||||
mAudioManager.abandonAudioFocus(mAfChangeListener);
|
||||
mAudioManager.unregisterMediaButtonEventReceiver(mEventReceiver);
|
||||
mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
|
||||
mMediaRouter.removeRemoteControlClient(mRemoteControlClient);
|
||||
SampleMediaButtonReceiver.setActivity(null);
|
||||
mRemoteControlClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleMediaKey(KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: {
|
||||
Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
|
||||
mPaused = !mPaused;
|
||||
if (mPaused) {
|
||||
mSessionManager.pause();
|
||||
} else {
|
||||
mSessionManager.resume();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY: {
|
||||
Log.d(TAG, "Received Play event from RemoteControlClient");
|
||||
if (mPaused) {
|
||||
mPaused = false;
|
||||
mSessionManager.resume();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE: {
|
||||
Log.d(TAG, "Received Pause event from RemoteControlClient");
|
||||
if (!mPaused) {
|
||||
mPaused = true;
|
||||
mSessionManager.pause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP: {
|
||||
Log.d(TAG, "Received Stop event from RemoteControlClient");
|
||||
mPaused = false;
|
||||
mSessionManager.stop();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
return handleMediaKey(event) || super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
return handleMediaKey(event) || super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
// Be sure to call the super class.
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
// pause media player for local playback case only
|
||||
if (!mPlayer.isRemotePlayback() && !mPaused) {
|
||||
mNeedResume = true;
|
||||
mSessionManager.pause();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
// resume media player for local playback case only
|
||||
if (!mPlayer.isRemotePlayback() && mNeedResume) {
|
||||
mSessionManager.resume();
|
||||
mNeedResume = false;
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Unregister the remote control client
|
||||
unregisterRemoteControlClient();
|
||||
|
||||
mPaused = false;
|
||||
mSessionManager.stop();
|
||||
mPlayer.release();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Be sure to call the super class.
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
// Inflate the menu and configure the media router action provider.
|
||||
getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
|
||||
|
||||
MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
|
||||
MediaRouteActionProvider mediaRouteActionProvider =
|
||||
(MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem);
|
||||
mediaRouteActionProvider.setRouteSelector(mSelector);
|
||||
|
||||
// Return true to show the menu.
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
// Estimate content position from last status time and elapsed time.
|
||||
// (Note this might be slightly out of sync with remote side, however
|
||||
// it avoids frequent polling the MRP.)
|
||||
int progress = 0;
|
||||
PlaylistItem item = getCheckedPlaylistItem();
|
||||
if (item != null) {
|
||||
int state = item.getState();
|
||||
long duration = item.getDuration();
|
||||
if (duration <= 0) {
|
||||
if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING ||
|
||||
state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
|
||||
mSessionManager.updateStatus();
|
||||
}
|
||||
} else {
|
||||
long position = item.getPosition();
|
||||
long timeDelta =
|
||||
mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp());
|
||||
progress = (int) (100.0 * (position + timeDelta) / duration);
|
||||
}
|
||||
}
|
||||
mSeekBar.setProgress(progress);
|
||||
}
|
||||
|
||||
private void updateUi() {
|
||||
updatePlaylist();
|
||||
updateRouteDescription();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updatePlaylist() {
|
||||
mPlayListItems.clear();
|
||||
for (PlaylistItem item : mSessionManager.getPlaylist()) {
|
||||
mPlayListItems.add(item);
|
||||
}
|
||||
mPlayListView.invalidate();
|
||||
}
|
||||
|
||||
|
||||
private void updateRouteDescription() {
|
||||
RouteInfo route = mMediaRouter.getSelectedRoute();
|
||||
mInfoTextView.setText(
|
||||
"Currently selected route:" + "\nName: " + route.getName() + "\nProvider: " +
|
||||
route.getProvider().getPackageName() + "\nDescription: " +
|
||||
route.getDescription() + "\nStatistics: " +
|
||||
mSessionManager.getStatistics());
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
|
||||
// show pause or resume icon depending on current state
|
||||
mPauseResumeButton.setImageResource(
|
||||
mPaused ? R.drawable.ic_action_play : R.drawable.ic_action_pause);
|
||||
// disable pause/resume/stop if no session
|
||||
mPauseResumeButton.setEnabled(mSessionManager.hasSession());
|
||||
mStopButton.setEnabled(mSessionManager.hasSession());
|
||||
// only enable seek bar when duration is known
|
||||
PlaylistItem item = getCheckedPlaylistItem();
|
||||
mSeekBar.setEnabled(item != null && item.getDuration() > 0);
|
||||
if (mRemoteControlClient != null) {
|
||||
mRemoteControlClient.setPlaybackState(mPaused ? RemoteControlClient.PLAYSTATE_PAUSED :
|
||||
RemoteControlClient.PLAYSTATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
private PlaylistItem getCheckedPlaylistItem() {
|
||||
int count = mPlayListView.getCount();
|
||||
int index = mPlayListView.getCheckedItemPosition();
|
||||
if (count > 0) {
|
||||
if (index < 0 || index >= count) {
|
||||
index = 0;
|
||||
mPlayListView.setItemChecked(0, true);
|
||||
}
|
||||
return mPlayListItems.getItem(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
|
||||
private static final String TAG = "DiscoveryFragment";
|
||||
private Callback mCallback;
|
||||
|
||||
public DiscoveryFragment() {
|
||||
mCallback = null;
|
||||
}
|
||||
|
||||
public DiscoveryFragment(Callback cb) {
|
||||
mCallback = cb;
|
||||
}
|
||||
|
||||
public void setCallback(Callback cb) {
|
||||
mCallback = cb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Callback onCreateCallback() {
|
||||
return mCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onPrepareCallbackFlags() {
|
||||
// Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
|
||||
// observe and log all route events including those that are for routes
|
||||
// that do not match our selector. This is only for demonstration purposes
|
||||
// and should not be needed by most applications.
|
||||
return super.onPrepareCallbackFlags() | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MediaItem {
|
||||
public final String mName;
|
||||
public final Uri mUri;
|
||||
public final String mMime;
|
||||
|
||||
public MediaItem(String name, Uri uri, String mime) {
|
||||
mName = name;
|
||||
mUri = uri;
|
||||
mMime = mime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
|
||||
private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
|
||||
public LibraryAdapter() {
|
||||
super(MainActivity.this, R.layout.media_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View v;
|
||||
if (convertView == null) {
|
||||
v = getLayoutInflater().inflate(R.layout.media_item, null);
|
||||
} else {
|
||||
v = convertView;
|
||||
}
|
||||
|
||||
final MediaItem item = getItem(position);
|
||||
|
||||
TextView tv = (TextView) v.findViewById(R.id.item_text);
|
||||
tv.setText(item.mName);
|
||||
|
||||
ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
|
||||
b.setImageResource(R.drawable.ic_suggestions_add);
|
||||
b.setTag(item);
|
||||
b.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (item != null) {
|
||||
mSessionManager.add(item.mUri, item.mMime);
|
||||
Toast.makeText(MainActivity.this, R.string.playlist_item_added_text,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> {
|
||||
public PlaylistAdapter() {
|
||||
super(MainActivity.this, R.layout.media_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View v;
|
||||
if (convertView == null) {
|
||||
v = getLayoutInflater().inflate(R.layout.media_item, null);
|
||||
} else {
|
||||
v = convertView;
|
||||
}
|
||||
|
||||
final PlaylistItem item = getItem(position);
|
||||
|
||||
TextView tv = (TextView) v.findViewById(R.id.item_text);
|
||||
tv.setText(item.toString());
|
||||
|
||||
ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
|
||||
b.setImageResource(R.drawable.ic_suggestions_delete);
|
||||
b.setTag(item);
|
||||
b.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (item != null) {
|
||||
mSessionManager.remove(item.getItemId());
|
||||
Toast.makeText(MainActivity.this, R.string.playlist_item_removed_text,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.mediarouter.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import android.view.TextureView.SurfaceTextureListener;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.example.android.mediarouter.R;
|
||||
|
||||
/**
|
||||
* Manages an overlay display window, used for simulating remote playback.
|
||||
*/
|
||||
public abstract class OverlayDisplayWindow {
|
||||
private static final String TAG = "OverlayDisplayWindow";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final float WINDOW_ALPHA = 0.8f;
|
||||
private static final float INITIAL_SCALE = 0.5f;
|
||||
private static final float MIN_SCALE = 0.3f;
|
||||
private static final float MAX_SCALE = 1.0f;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final String mName;
|
||||
protected final int mWidth;
|
||||
protected final int mHeight;
|
||||
protected final int mGravity;
|
||||
protected OverlayWindowListener mListener;
|
||||
|
||||
protected OverlayDisplayWindow(Context context, String name,
|
||||
int width, int height, int gravity) {
|
||||
mContext = context;
|
||||
mName = name;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mGravity = gravity;
|
||||
}
|
||||
|
||||
public static OverlayDisplayWindow create(Context context, String name,
|
||||
int width, int height, int gravity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return new JellybeanMr1Impl(context, name, width, height, gravity);
|
||||
} else {
|
||||
return new LegacyImpl(context, name, width, height, gravity);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOverlayWindowListener(OverlayWindowListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public abstract void show();
|
||||
|
||||
public abstract void dismiss();
|
||||
|
||||
public abstract void updateAspectRatio(int width, int height);
|
||||
|
||||
// Watches for significant changes in the overlay display window lifecycle.
|
||||
public interface OverlayWindowListener {
|
||||
public void onWindowCreated(Surface surface);
|
||||
public void onWindowCreated(SurfaceHolder surfaceHolder);
|
||||
public void onWindowDestroyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for older versions.
|
||||
*/
|
||||
private static final class LegacyImpl extends OverlayDisplayWindow {
|
||||
private final WindowManager mWindowManager;
|
||||
|
||||
private boolean mWindowVisible;
|
||||
private SurfaceView mSurfaceView;
|
||||
|
||||
public LegacyImpl(Context context, String name,
|
||||
int width, int height, int gravity) {
|
||||
super(context, name, width, height, gravity);
|
||||
|
||||
mWindowManager = (WindowManager)context.getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (!mWindowVisible) {
|
||||
mSurfaceView = new SurfaceView(mContext);
|
||||
|
||||
Display display = mWindowManager.getDefaultDisplay();
|
||||
|
||||
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
|
||||
params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
||||
params.alpha = WINDOW_ALPHA;
|
||||
params.gravity = Gravity.LEFT | Gravity.BOTTOM;
|
||||
params.setTitle(mName);
|
||||
|
||||
int width = (int)(display.getWidth() * INITIAL_SCALE);
|
||||
int height = (int)(display.getHeight() * INITIAL_SCALE);
|
||||
if (mWidth > mHeight) {
|
||||
height = mHeight * width / mWidth;
|
||||
} else {
|
||||
width = mWidth * height / mHeight;
|
||||
}
|
||||
params.width = width;
|
||||
params.height = height;
|
||||
|
||||
mWindowManager.addView(mSurfaceView, params);
|
||||
mWindowVisible = true;
|
||||
|
||||
SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||
mListener.onWindowCreated(holder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
if (mWindowVisible) {
|
||||
mListener.onWindowDestroyed();
|
||||
|
||||
mWindowManager.removeView(mSurfaceView);
|
||||
mWindowVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAspectRatio(int width, int height) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for API version 17+.
|
||||
*/
|
||||
private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
|
||||
// When true, disables support for moving and resizing the overlay.
|
||||
// The window is made non-touchable, which makes it possible to
|
||||
// directly interact with the content underneath.
|
||||
private static final boolean DISABLE_MOVE_AND_RESIZE = false;
|
||||
|
||||
private final DisplayManager mDisplayManager;
|
||||
private final WindowManager mWindowManager;
|
||||
|
||||
private final Display mDefaultDisplay;
|
||||
private final DisplayMetrics mDefaultDisplayMetrics = new DisplayMetrics();
|
||||
|
||||
private View mWindowContent;
|
||||
private WindowManager.LayoutParams mWindowParams;
|
||||
private TextureView mTextureView;
|
||||
private TextView mNameTextView;
|
||||
|
||||
private GestureDetector mGestureDetector;
|
||||
private ScaleGestureDetector mScaleGestureDetector;
|
||||
|
||||
private boolean mWindowVisible;
|
||||
private int mWindowX;
|
||||
private int mWindowY;
|
||||
private float mWindowScale;
|
||||
|
||||
private float mLiveTranslationX;
|
||||
private float mLiveTranslationY;
|
||||
private float mLiveScale = 1.0f;
|
||||
|
||||
public JellybeanMr1Impl(Context context, String name,
|
||||
int width, int height, int gravity) {
|
||||
super(context, name, width, height, gravity);
|
||||
|
||||
mDisplayManager = (DisplayManager)context.getSystemService(
|
||||
Context.DISPLAY_SERVICE);
|
||||
mWindowManager = (WindowManager)context.getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
|
||||
mDefaultDisplay = mWindowManager.getDefaultDisplay();
|
||||
updateDefaultDisplayInfo();
|
||||
|
||||
createWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (!mWindowVisible) {
|
||||
mDisplayManager.registerDisplayListener(mDisplayListener, null);
|
||||
if (!updateDefaultDisplayInfo()) {
|
||||
mDisplayManager.unregisterDisplayListener(mDisplayListener);
|
||||
return;
|
||||
}
|
||||
|
||||
clearLiveState();
|
||||
updateWindowParams();
|
||||
mWindowManager.addView(mWindowContent, mWindowParams);
|
||||
mWindowVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
if (mWindowVisible) {
|
||||
mDisplayManager.unregisterDisplayListener(mDisplayListener);
|
||||
mWindowManager.removeView(mWindowContent);
|
||||
mWindowVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAspectRatio(int width, int height) {
|
||||
if (mWidth * height < mHeight * width) {
|
||||
mTextureView.getLayoutParams().width = mWidth;
|
||||
mTextureView.getLayoutParams().height = mWidth * height / width;
|
||||
} else {
|
||||
mTextureView.getLayoutParams().width = mHeight * width / height;
|
||||
mTextureView.getLayoutParams().height = mHeight;
|
||||
}
|
||||
relayout();
|
||||
}
|
||||
|
||||
private void relayout() {
|
||||
if (mWindowVisible) {
|
||||
updateWindowParams();
|
||||
mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateDefaultDisplayInfo() {
|
||||
mDefaultDisplay.getMetrics(mDefaultDisplayMetrics);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void createWindow() {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
|
||||
mWindowContent = inflater.inflate(
|
||||
R.layout.overlay_display_window, null);
|
||||
mWindowContent.setOnTouchListener(mOnTouchListener);
|
||||
|
||||
mTextureView = (TextureView)mWindowContent.findViewById(
|
||||
R.id.overlay_display_window_texture);
|
||||
mTextureView.setPivotX(0);
|
||||
mTextureView.setPivotY(0);
|
||||
mTextureView.getLayoutParams().width = mWidth;
|
||||
mTextureView.getLayoutParams().height = mHeight;
|
||||
mTextureView.setOpaque(false);
|
||||
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
|
||||
|
||||
mNameTextView = (TextView)mWindowContent.findViewById(
|
||||
R.id.overlay_display_window_title);
|
||||
mNameTextView.setText(mName);
|
||||
|
||||
mWindowParams = new WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
|
||||
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
|
||||
if (DISABLE_MOVE_AND_RESIZE) {
|
||||
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
||||
}
|
||||
mWindowParams.alpha = WINDOW_ALPHA;
|
||||
mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
|
||||
mWindowParams.setTitle(mName);
|
||||
|
||||
mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
|
||||
mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
|
||||
|
||||
// Set the initial position and scale.
|
||||
// The position and scale will be clamped when the display is first shown.
|
||||
mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
|
||||
0 : mDefaultDisplayMetrics.widthPixels;
|
||||
mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
|
||||
0 : mDefaultDisplayMetrics.heightPixels;
|
||||
Log.d(TAG, mDefaultDisplayMetrics.toString());
|
||||
mWindowScale = INITIAL_SCALE;
|
||||
|
||||
// calculate and save initial settings
|
||||
updateWindowParams();
|
||||
saveWindowParams();
|
||||
}
|
||||
|
||||
private void updateWindowParams() {
|
||||
float scale = mWindowScale * mLiveScale;
|
||||
scale = Math.min(scale, (float)mDefaultDisplayMetrics.widthPixels / mWidth);
|
||||
scale = Math.min(scale, (float)mDefaultDisplayMetrics.heightPixels / mHeight);
|
||||
scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
|
||||
|
||||
float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
|
||||
int width = (int)(mWidth * scale);
|
||||
int height = (int)(mHeight * scale);
|
||||
int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
|
||||
int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
|
||||
x = Math.max(0, Math.min(x, mDefaultDisplayMetrics.widthPixels - width));
|
||||
y = Math.max(0, Math.min(y, mDefaultDisplayMetrics.heightPixels - height));
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "updateWindowParams: scale=" + scale
|
||||
+ ", offsetScale=" + offsetScale
|
||||
+ ", x=" + x + ", y=" + y
|
||||
+ ", width=" + width + ", height=" + height);
|
||||
}
|
||||
|
||||
mTextureView.setScaleX(scale);
|
||||
mTextureView.setScaleY(scale);
|
||||
|
||||
mTextureView.setTranslationX(
|
||||
(mWidth - mTextureView.getLayoutParams().width) * scale / 2);
|
||||
mTextureView.setTranslationY(
|
||||
(mHeight - mTextureView.getLayoutParams().height) * scale / 2);
|
||||
|
||||
mWindowParams.x = x;
|
||||
mWindowParams.y = y;
|
||||
mWindowParams.width = width;
|
||||
mWindowParams.height = height;
|
||||
}
|
||||
|
||||
private void saveWindowParams() {
|
||||
mWindowX = mWindowParams.x;
|
||||
mWindowY = mWindowParams.y;
|
||||
mWindowScale = mTextureView.getScaleX();
|
||||
clearLiveState();
|
||||
}
|
||||
|
||||
private void clearLiveState() {
|
||||
mLiveTranslationX = 0f;
|
||||
mLiveTranslationY = 0f;
|
||||
mLiveScale = 1.0f;
|
||||
}
|
||||
|
||||
private final DisplayManager.DisplayListener mDisplayListener =
|
||||
new DisplayManager.DisplayListener() {
|
||||
@Override
|
||||
public void onDisplayAdded(int displayId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int displayId) {
|
||||
if (displayId == mDefaultDisplay.getDisplayId()) {
|
||||
if (updateDefaultDisplayInfo()) {
|
||||
relayout();
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayRemoved(int displayId) {
|
||||
if (displayId == mDefaultDisplay.getDisplayId()) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final SurfaceTextureListener mSurfaceTextureListener =
|
||||
new SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
|
||||
int width, int height) {
|
||||
if (mListener != null) {
|
||||
mListener.onWindowCreated(new Surface(surfaceTexture));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
||||
if (mListener != null) {
|
||||
mListener.onWindowDestroyed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
|
||||
int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
||||
}
|
||||
};
|
||||
|
||||
private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent event) {
|
||||
// Work in screen coordinates.
|
||||
final float oldX = event.getX();
|
||||
final float oldY = event.getY();
|
||||
event.setLocation(event.getRawX(), event.getRawY());
|
||||
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mScaleGestureDetector.onTouchEvent(event);
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
saveWindowParams();
|
||||
break;
|
||||
}
|
||||
|
||||
// Revert to window coordinates.
|
||||
event.setLocation(oldX, oldY);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private final GestureDetector.OnGestureListener mOnGestureListener =
|
||||
new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
||||
float distanceX, float distanceY) {
|
||||
mLiveTranslationX -= distanceX;
|
||||
mLiveTranslationY -= distanceY;
|
||||
relayout();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
|
||||
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
mLiveScale *= detector.getScaleFactor();
|
||||
relayout();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
|
||||
/**
|
||||
* PlaylistItem helps keep track of the current status of an media item.
|
||||
*/
|
||||
public final class PlaylistItem {
|
||||
// immutables
|
||||
private final String mSessionId;
|
||||
private final String mItemId;
|
||||
private final Uri mUri;
|
||||
private final String mMime;
|
||||
private final PendingIntent mUpdateReceiver;
|
||||
// changeable states
|
||||
private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
|
||||
private long mContentPosition;
|
||||
private long mContentDuration;
|
||||
private long mTimestamp;
|
||||
private String mRemoteItemId;
|
||||
|
||||
public PlaylistItem(String qid, String iid, Uri uri, String mime, PendingIntent pi) {
|
||||
mSessionId = qid;
|
||||
mItemId = iid;
|
||||
mUri = uri;
|
||||
mMime = mime;
|
||||
mUpdateReceiver = pi;
|
||||
setTimestamp(SystemClock.elapsedRealtime());
|
||||
}
|
||||
|
||||
public void setRemoteItemId(String riid) {
|
||||
mRemoteItemId = riid;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
mPlaybackState = state;
|
||||
}
|
||||
|
||||
public void setPosition(long pos) {
|
||||
mContentPosition = pos;
|
||||
}
|
||||
|
||||
public void setTimestamp(long ts) {
|
||||
mTimestamp = ts;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
mContentDuration = duration;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return mSessionId;
|
||||
}
|
||||
|
||||
public String getItemId() {
|
||||
return mItemId;
|
||||
}
|
||||
|
||||
public String getRemoteItemId() {
|
||||
return mRemoteItemId;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
public PendingIntent getUpdateReceiver() {
|
||||
return mUpdateReceiver;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return mPlaybackState;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return mContentPosition;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return mContentDuration;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
public MediaItemStatus getStatus() {
|
||||
return new MediaItemStatus.Builder(mPlaybackState)
|
||||
.setContentPosition(mContentPosition)
|
||||
.setContentDuration(mContentDuration)
|
||||
.setTimestamp(mTimestamp)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String state[] = {
|
||||
"PENDING",
|
||||
"PLAYING",
|
||||
"PAUSED",
|
||||
"BUFFERING",
|
||||
"FINISHED",
|
||||
"CANCELED",
|
||||
"INVALIDATED",
|
||||
"ERROR"
|
||||
};
|
||||
return "[" + mSessionId + "|" + mItemId + "|"
|
||||
+ (mRemoteItemId != null ? mRemoteItemId : "-") + "|"
|
||||
+ state[mPlaybackState] + "] " + mUri.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
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 com.example.android.mediarouter.player.Player;
|
||||
import com.example.android.mediarouter.player.PlaylistItem;
|
||||
import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
/**
|
||||
* Broadcast receiver for handling ACTION_MEDIA_BUTTON.
|
||||
*
|
||||
* This is needed to create the RemoteControlClient for controlling
|
||||
* remote route volume in lock screen. It routes media key events back
|
||||
* to main app activity MainActivity.
|
||||
*/
|
||||
public class SampleMediaButtonReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "SampleMediaButtonReceiver";
|
||||
private static MainActivity mActivity;
|
||||
|
||||
public static void setActivity(MainActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mActivity != null && Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
|
||||
mActivity.handleMediaKey(
|
||||
(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* 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.mediarouter.player;
|
||||
|
||||
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 com.example.android.mediarouter.player.Player.Callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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 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");
|
||||
}
|
||||
mPaused = true;
|
||||
updatePlaybackState();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (DEBUG) {
|
||||
log("resume");
|
||||
}
|
||||
mPaused = false;
|
||||
updatePlaybackState();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (DEBUG) {
|
||||
log("stop");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* 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.mediarouter.provider;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.IntentFilter.MalformedMimeTypeException;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaRouter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.media.MediaControlIntent;
|
||||
import android.support.v7.media.MediaRouteDescriptor;
|
||||
import android.support.v7.media.MediaRouteProvider;
|
||||
import android.support.v7.media.MediaRouteProviderDescriptor;
|
||||
import android.support.v7.media.MediaRouter.ControlRequestCallback;
|
||||
import android.support.v7.media.MediaSessionStatus;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.mediarouter.player.Player;
|
||||
import com.example.android.mediarouter.player.PlaylistItem;
|
||||
import com.example.android.mediarouter.R;
|
||||
import com.example.android.mediarouter.player.SessionManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Demonstrates how to create a custom media route provider.
|
||||
*
|
||||
* @see SampleMediaRouteProviderService
|
||||
*/
|
||||
public final class SampleMediaRouteProvider extends MediaRouteProvider {
|
||||
private static final String TAG = "SampleMediaRouteProvider";
|
||||
|
||||
private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
|
||||
private static final String VARIABLE_VOLUME_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;
|
||||
|
||||
/**
|
||||
* A custom media control intent category for special requests that are
|
||||
* supported by this provider's routes.
|
||||
*/
|
||||
public static final String CATEGORY_SAMPLE_ROUTE =
|
||||
"com.example.android.mediarouteprovider.CATEGORY_SAMPLE_ROUTE";
|
||||
|
||||
/**
|
||||
* A custom media control intent action for special requests that are
|
||||
* supported by this provider's routes.
|
||||
* <p>
|
||||
* This particular request is designed to return a bundle of not very
|
||||
* interesting statistics for demonstration purposes.
|
||||
* </p>
|
||||
*
|
||||
* @see #DATA_PLAYBACK_COUNT
|
||||
*/
|
||||
public static final String ACTION_GET_STATISTICS =
|
||||
"com.example.android.mediarouteprovider.ACTION_GET_STATISTICS";
|
||||
|
||||
/**
|
||||
* {@link #ACTION_GET_STATISTICS} result data: Number of times the
|
||||
* playback action was invoked.
|
||||
*/
|
||||
public static final String DATA_PLAYBACK_COUNT =
|
||||
"com.example.android.mediarouteprovider.EXTRA_PLAYBACK_COUNT";
|
||||
|
||||
private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
|
||||
private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING;
|
||||
private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION;
|
||||
|
||||
static {
|
||||
IntentFilter f1 = new IntentFilter();
|
||||
f1.addCategory(CATEGORY_SAMPLE_ROUTE);
|
||||
f1.addAction(ACTION_GET_STATISTICS);
|
||||
|
||||
IntentFilter f2 = new IntentFilter();
|
||||
f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
f2.addAction(MediaControlIntent.ACTION_PLAY);
|
||||
f2.addDataScheme("http");
|
||||
f2.addDataScheme("https");
|
||||
f2.addDataScheme("rtsp");
|
||||
f2.addDataScheme("file");
|
||||
addDataTypeUnchecked(f2, "video/*");
|
||||
|
||||
IntentFilter f3 = new IntentFilter();
|
||||
f3.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
f3.addAction(MediaControlIntent.ACTION_SEEK);
|
||||
f3.addAction(MediaControlIntent.ACTION_GET_STATUS);
|
||||
f3.addAction(MediaControlIntent.ACTION_PAUSE);
|
||||
f3.addAction(MediaControlIntent.ACTION_RESUME);
|
||||
f3.addAction(MediaControlIntent.ACTION_STOP);
|
||||
|
||||
IntentFilter f4 = new IntentFilter();
|
||||
f4.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
f4.addAction(MediaControlIntent.ACTION_ENQUEUE);
|
||||
f4.addDataScheme("http");
|
||||
f4.addDataScheme("https");
|
||||
f4.addDataScheme("rtsp");
|
||||
f4.addDataScheme("file");
|
||||
addDataTypeUnchecked(f4, "video/*");
|
||||
|
||||
IntentFilter f5 = new IntentFilter();
|
||||
f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
f5.addAction(MediaControlIntent.ACTION_REMOVE);
|
||||
|
||||
IntentFilter f6 = new IntentFilter();
|
||||
f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
f6.addAction(MediaControlIntent.ACTION_START_SESSION);
|
||||
f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
|
||||
f6.addAction(MediaControlIntent.ACTION_END_SESSION);
|
||||
|
||||
CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
|
||||
CONTROL_FILTERS_BASIC.add(f1);
|
||||
CONTROL_FILTERS_BASIC.add(f2);
|
||||
CONTROL_FILTERS_BASIC.add(f3);
|
||||
|
||||
CONTROL_FILTERS_QUEUING =
|
||||
new ArrayList<IntentFilter>(CONTROL_FILTERS_BASIC);
|
||||
CONTROL_FILTERS_QUEUING.add(f4);
|
||||
CONTROL_FILTERS_QUEUING.add(f5);
|
||||
|
||||
CONTROL_FILTERS_SESSION =
|
||||
new ArrayList<IntentFilter>(CONTROL_FILTERS_QUEUING);
|
||||
CONTROL_FILTERS_SESSION.add(f6);
|
||||
}
|
||||
|
||||
private static void addDataTypeUnchecked(IntentFilter filter, String type) {
|
||||
try {
|
||||
filter.addDataType(type);
|
||||
} catch (MalformedMimeTypeException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int mVolume = 5;
|
||||
private int mEnqueueCount;
|
||||
|
||||
public SampleMediaRouteProvider(Context context) {
|
||||
super(context);
|
||||
|
||||
publishRoutes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteController onCreateRouteController(String routeId) {
|
||||
return new SampleRouteController(routeId);
|
||||
}
|
||||
|
||||
private void publishRoutes() {
|
||||
Resources r = getContext().getResources();
|
||||
|
||||
MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder(
|
||||
FIXED_VOLUME_ROUTE_ID,
|
||||
r.getString(R.string.fixed_volume_route_name))
|
||||
.setDescription(r.getString(R.string.sample_route_description))
|
||||
.addControlFilters(CONTROL_FILTERS_BASIC)
|
||||
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
|
||||
.setVolume(VOLUME_MAX)
|
||||
.build();
|
||||
|
||||
MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
|
||||
VARIABLE_VOLUME_BASIC_ROUTE_ID,
|
||||
r.getString(R.string.variable_volume_basic_route_name))
|
||||
.setDescription(r.getString(R.string.sample_route_description))
|
||||
.addControlFilters(CONTROL_FILTERS_BASIC)
|
||||
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolumeMax(VOLUME_MAX)
|
||||
.setVolume(mVolume)
|
||||
.build();
|
||||
|
||||
MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder(
|
||||
VARIABLE_VOLUME_QUEUING_ROUTE_ID,
|
||||
r.getString(R.string.variable_volume_queuing_route_name))
|
||||
.setDescription(r.getString(R.string.sample_route_description))
|
||||
.addControlFilters(CONTROL_FILTERS_QUEUING)
|
||||
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolumeMax(VOLUME_MAX)
|
||||
.setVolume(mVolume)
|
||||
.build();
|
||||
|
||||
MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder(
|
||||
VARIABLE_VOLUME_SESSION_ROUTE_ID,
|
||||
r.getString(R.string.variable_volume_session_route_name))
|
||||
.setDescription(r.getString(R.string.sample_route_description))
|
||||
.addControlFilters(CONTROL_FILTERS_SESSION)
|
||||
.setPlaybackStream(AudioManager.STREAM_MUSIC)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolumeMax(VOLUME_MAX)
|
||||
.setVolume(mVolume)
|
||||
.build();
|
||||
|
||||
MediaRouteProviderDescriptor providerDescriptor =
|
||||
new MediaRouteProviderDescriptor.Builder()
|
||||
.addRoute(routeDescriptor1)
|
||||
.addRoute(routeDescriptor2)
|
||||
.addRoute(routeDescriptor3)
|
||||
.addRoute(routeDescriptor4)
|
||||
.build();
|
||||
setDescriptor(providerDescriptor);
|
||||
}
|
||||
|
||||
private final class SampleRouteController extends MediaRouteProvider.RouteController {
|
||||
private final String mRouteId;
|
||||
private final SessionManager mSessionManager = new SessionManager("mrp");
|
||||
private final Player mPlayer;
|
||||
private PendingIntent mSessionReceiver;
|
||||
|
||||
public SampleRouteController(String routeId) {
|
||||
mRouteId = routeId;
|
||||
mPlayer = Player.create(getContext(), null);
|
||||
mSessionManager.setPlayer(mPlayer);
|
||||
mSessionManager.setCallback(new SessionManager.Callback() {
|
||||
@Override
|
||||
public void onStatusChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemChanged(PlaylistItem item) {
|
||||
handleStatusChange(item);
|
||||
}
|
||||
});
|
||||
Log.d(TAG, mRouteId + ": Controller created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {
|
||||
Log.d(TAG, mRouteId + ": Controller released");
|
||||
mPlayer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelect() {
|
||||
Log.d(TAG, mRouteId + ": Selected");
|
||||
mPlayer.connect(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnselect() {
|
||||
Log.d(TAG, mRouteId + ": Unselected");
|
||||
mPlayer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetVolume(int volume) {
|
||||
Log.d(TAG, mRouteId + ": Set volume to " + volume);
|
||||
if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
|
||||
setVolumeInternal(volume);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateVolume(int delta) {
|
||||
Log.d(TAG, mRouteId + ": Update volume by " + delta);
|
||||
if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
|
||||
setVolumeInternal(mVolume + delta);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
|
||||
Log.d(TAG, mRouteId + ": Received control request " + intent);
|
||||
String action = intent.getAction();
|
||||
if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
|
||||
boolean success = false;
|
||||
if (action.equals(MediaControlIntent.ACTION_PLAY)) {
|
||||
success = handlePlay(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
|
||||
success = handleEnqueue(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
|
||||
success = handleRemove(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
|
||||
success = handleSeek(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
|
||||
success = handleGetStatus(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
|
||||
success = handlePause(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
|
||||
success = handleResume(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_STOP)) {
|
||||
success = handleStop(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
|
||||
success = handleStartSession(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
|
||||
success = handleGetSessionStatus(intent, callback);
|
||||
} else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
|
||||
success = handleEndSession(intent, callback);
|
||||
}
|
||||
Log.d(TAG, mSessionManager.toString());
|
||||
return success;
|
||||
}
|
||||
|
||||
if (action.equals(ACTION_GET_STATISTICS)
|
||||
&& intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) {
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(DATA_PLAYBACK_COUNT, mEnqueueCount);
|
||||
if (callback != null) {
|
||||
callback.onResult(data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setVolumeInternal(int volume) {
|
||||
if (volume >= 0 && volume <= VOLUME_MAX) {
|
||||
mVolume = volume;
|
||||
Log.d(TAG, mRouteId + ": New volume is " + mVolume);
|
||||
AudioManager audioManager =
|
||||
(AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
|
||||
publishRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
|
||||
Log.d(TAG, "handlePlay fails because of bad sid="+sid);
|
||||
return false;
|
||||
}
|
||||
if (mSessionManager.hasSession()) {
|
||||
mSessionManager.stop();
|
||||
}
|
||||
return handleEnqueue(intent, callback);
|
||||
}
|
||||
|
||||
private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
String mime = intent.getType();
|
||||
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
||||
Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
|
||||
Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
|
||||
PendingIntent receiver = (PendingIntent)intent.getParcelableExtra(
|
||||
MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER);
|
||||
|
||||
Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request"
|
||||
+ ", uri=" + uri
|
||||
+ ", mime=" + mime
|
||||
+ ", sid=" + sid
|
||||
+ ", pos=" + pos
|
||||
+ ", metadata=" + metadata
|
||||
+ ", headers=" + headers
|
||||
+ ", receiver=" + receiver);
|
||||
PlaylistItem item = mSessionManager.add(uri, mime, receiver);
|
||||
if (callback != null) {
|
||||
if (item != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
|
||||
result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
|
||||
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
|
||||
item.getStatus().asBundle());
|
||||
callback.onResult(result);
|
||||
} else {
|
||||
callback.onError("Failed to open " + uri.toString(), null);
|
||||
}
|
||||
}
|
||||
mEnqueueCount +=1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||
PlaylistItem item = mSessionManager.remove(iid);
|
||||
if (callback != null) {
|
||||
if (item != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
|
||||
item.getStatus().asBundle());
|
||||
callback.onResult(result);
|
||||
} else {
|
||||
callback.onError("Failed to remove" +
|
||||
", sid=" + sid + ", iid=" + iid, null);
|
||||
}
|
||||
}
|
||||
return (item != null);
|
||||
}
|
||||
|
||||
private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
|
||||
Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
|
||||
PlaylistItem item = mSessionManager.seek(iid, pos);
|
||||
if (callback != null) {
|
||||
if (item != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
|
||||
item.getStatus().asBundle());
|
||||
callback.onResult(result);
|
||||
} else {
|
||||
callback.onError("Failed to seek" +
|
||||
", sid=" + sid + ", iid=" + iid + ", pos=" + pos, null);
|
||||
}
|
||||
}
|
||||
return (item != null);
|
||||
}
|
||||
|
||||
private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||
Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid);
|
||||
PlaylistItem item = mSessionManager.getStatus(iid);
|
||||
if (callback != null) {
|
||||
if (item != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
|
||||
item.getStatus().asBundle());
|
||||
callback.onResult(result);
|
||||
} else {
|
||||
callback.onError("Failed to get status" +
|
||||
", sid=" + sid + ", iid=" + iid, null);
|
||||
}
|
||||
}
|
||||
return (item != null);
|
||||
}
|
||||
|
||||
private boolean handlePause(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
|
||||
mSessionManager.pause();
|
||||
if (callback != null) {
|
||||
if (success) {
|
||||
callback.onResult(new Bundle());
|
||||
handleSessionStatusChange(sid);
|
||||
} else {
|
||||
callback.onError("Failed to pause, sid=" + sid, null);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean handleResume(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
|
||||
mSessionManager.resume();
|
||||
if (callback != null) {
|
||||
if (success) {
|
||||
callback.onResult(new Bundle());
|
||||
handleSessionStatusChange(sid);
|
||||
} else {
|
||||
callback.onError("Failed to resume, sid=" + sid, null);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean handleStop(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
|
||||
mSessionManager.stop();
|
||||
if (callback != null) {
|
||||
if (success) {
|
||||
callback.onResult(new Bundle());
|
||||
handleSessionStatusChange(sid);
|
||||
} else {
|
||||
callback.onError("Failed to stop, sid=" + sid, null);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = mSessionManager.startSession();
|
||||
Log.d(TAG, "StartSession returns sessionId "+sid);
|
||||
if (callback != null) {
|
||||
if (sid != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid);
|
||||
result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
|
||||
mSessionManager.getSessionStatus(sid).asBundle());
|
||||
callback.onResult(result);
|
||||
mSessionReceiver = (PendingIntent)intent.getParcelableExtra(
|
||||
MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER);
|
||||
handleSessionStatusChange(sid);
|
||||
} else {
|
||||
callback.onError("Failed to start session.", null);
|
||||
}
|
||||
}
|
||||
return (sid != null);
|
||||
}
|
||||
|
||||
private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
|
||||
MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid);
|
||||
if (callback != null) {
|
||||
if (sessionStatus != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
|
||||
mSessionManager.getSessionStatus(sid).asBundle());
|
||||
callback.onResult(result);
|
||||
} else {
|
||||
callback.onError("Failed to get session status, sid=" + sid, null);
|
||||
}
|
||||
}
|
||||
return (sessionStatus != null);
|
||||
}
|
||||
|
||||
private boolean handleEndSession(Intent intent, ControlRequestCallback callback) {
|
||||
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId())
|
||||
&& mSessionManager.endSession();
|
||||
if (callback != null) {
|
||||
if (success) {
|
||||
Bundle result = new Bundle();
|
||||
MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder(
|
||||
MediaSessionStatus.SESSION_STATE_ENDED).build();
|
||||
result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle());
|
||||
callback.onResult(result);
|
||||
handleSessionStatusChange(sid);
|
||||
mSessionReceiver = null;
|
||||
} else {
|
||||
callback.onError("Failed to end session, sid=" + sid, null);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private void handleStatusChange(PlaylistItem item) {
|
||||
if (item == null) {
|
||||
item = mSessionManager.getCurrentItem();
|
||||
}
|
||||
if (item != null) {
|
||||
PendingIntent receiver = item.getUpdateReceiver();
|
||||
if (receiver != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
|
||||
intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
|
||||
intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS,
|
||||
item.getStatus().asBundle());
|
||||
try {
|
||||
receiver.send(getContext(), 0, intent);
|
||||
Log.d(TAG, mRouteId + ": Sending status update from provider");
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.d(TAG, mRouteId + ": Failed to send status update!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSessionStatusChange(String sid) {
|
||||
if (mSessionReceiver != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
|
||||
mSessionManager.getSessionStatus(sid).asBundle());
|
||||
try {
|
||||
mSessionReceiver.send(getContext(), 0, intent);
|
||||
Log.d(TAG, mRouteId + ": Sending session status update from provider");
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.d(TAG, mRouteId + ": Failed to send session status update!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.mediarouter.provider;
|
||||
|
||||
import android.support.v7.media.MediaRouteProvider;
|
||||
import android.support.v7.media.MediaRouteProviderService;
|
||||
|
||||
import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
|
||||
|
||||
/**
|
||||
* Demonstrates how to register a custom media route provider service
|
||||
* using the support library.
|
||||
*
|
||||
* @see com.example.android.mediarouter.provider.SampleMediaRouteProvider
|
||||
*/
|
||||
public class SampleMediaRouteProviderService extends MediaRouteProviderService {
|
||||
@Override
|
||||
public MediaRouteProvider onCreateMediaRouteProvider() {
|
||||
return new SampleMediaRouteProvider(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user