Adding browsable prebuilt samples for march push

Change-Id: I952db10d9c9acb4940db08a07789347ea2effe4d
This commit is contained in:
Alexander Lucas
2014-03-07 13:34:45 -08:00
parent 7cd4524c3b
commit a780ba4b15
213 changed files with 15681 additions and 93 deletions

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
};
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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");
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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!");
}
}
}
}
}

View File

@@ -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);
}
}