am 5f4e7a10: Merge "Support7Demos: media router sample" into jb-mr2-ub-dev

* commit '5f4e7a104b764f07bdc963aa7e668cc3913f85e3':
  Support7Demos: media router sample
This commit is contained in:
Jeff Brown
2013-08-02 17:37:35 -07:00
committed by Android Git Automerger
23 changed files with 2851 additions and 288 deletions

View File

@@ -21,6 +21,12 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.supportv7">
<!-- Permission for INTERNET is required for streaming video content
from the web, it's not required otherwise. -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Permission for SYSTEM_ALERT_WINDOW is only required for emulating
remote display using system alert window. -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<!-- Layout for list item in Library or Playlist view. Displays ImageButton
instead of radio button to the right of the item. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageButton android:id="@+id/item_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"/>
<TextView android:id="@+id/item_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/item_action"
android:layout_gravity="left"
android:gravity="left"/>
</RelativeLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<TextureView android:id="@+id/overlay_display_window_texture"
android:layout_width="0px"
android:layout_height="0px" />
<TextView android:id="@+id/overlay_display_window_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal" />
</FrameLayout>

View File

@@ -20,45 +20,118 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Message to show to use. -->
<TextView android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_vertical|center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/sample_media_router_text"/>
<!-- Some information about what's going on. -->
<TextView android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_vertical|center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<!-- Some media to play. -->
<ListView android:id="@+id/media"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<!-- Control buttons for the currently selected route. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0">
<Button android:id="@+id/play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/play_button_text" />
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<!-- Tabs for media library, playlist and statistics -->
<TabHost android:id="@+id/tabHost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button android:id="@+id/statistics_button"
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/tab1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ListView android:id="@+id/media"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout android:id="@+id/tab2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ListView android:id="@+id/playlist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout android:id="@+id/tab3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>
<!-- Control buttons for the currently selected route. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0">
<SeekBar android:id="@+id/seekbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:progress="0"
android:layout_gravity="center"
android:layout_weight="1"/>
<ImageButton android:id="@+id/pause_resume_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="right"
android:minWidth="48dp"
android:minHeight="48dp"
android:background="@null"
android:src="@drawable/ic_media_pause" />
<ImageButton android:id="@+id/stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="right"
android:minWidth="48dp"
android:minHeight="48dp"
android:background="@null"
android:src="@drawable/ic_media_stop" />
</LinearLayout>
</LinearLayout>
<!-- Some content for visual interest in the case where no presentation is showing. -->
<FrameLayout android:id="@+id/player"
android:background="#ff000000"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<SurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<TextView
android:textColor="#ffaaaaaa"
android:text="@string/sample_media_route_activity_local"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/statistics_button_text" />
</LinearLayout>
android:layout_gravity="top|center_horizontal" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<!-- The content that we show on secondary displays.
See corresponding Java code PresentationWithMediaRouterActivity.java. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<SurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<TextView
android:textColor="#ffaaaaaa"
android:text="@string/sample_media_route_activity_presentation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal" />
</FrameLayout>

View File

@@ -16,18 +16,16 @@
<resources>
<string-array name="media_names">
<item>My favorite video of cats</item>
<item>Cats on parade</item>
<item>Cats with hats</item>
<item>Hats on cats</item>
<item>Cats on mats</item>
<item>Big Buck Bunny</item>
<item>Elephants Dream</item>
<item>Sintel</item>
<item>Tears of Steel</item>
</string-array>
<string-array name="media_uris">
<item>http://cats.example.com/favorite-cats.mp4</item>
<item>http://cats.example.com/cats-on-parade.mp4</item>
<item>http://cats.example.com/cats-with-hats.mp4</item>
<item>http://cats.example.com/hats-on-cats.mp4</item>
<item>http://cats.example.com/cats-on-mats.mp4</item>
<item>http://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4</item>
<item>http://archive.org/download/ElephantsDream_277/elephant_dreams_640_512kb.mp4</item>
<item>http://archive.org/download/Sintel/sintel-2048-stereo_512kb.mp4</item>
<item>http://archive.org/download/Tears-of-Steel/tears_of_steel_720p.mp4</item>
</string-array>
</resources>

View File

@@ -26,8 +26,9 @@
use MediaRouter from the support library. Select a route from the action bar.</string>
<string name="media_route_menu_title">Play on...</string>
<string name="play_button_text">Play</string>
<string name="statistics_button_text">Show Statistics</string>
<string name="library_tab_text">Library</string>
<string name="playlist_tab_text">Playlist</string>
<string name="statistics_tab_text">Statistics</string>
<string name="sample_media_route_provider_service">Media Route Provider Service Support Library Sample</string>
<string name="fixed_volume_route_name">Fixed Volume Remote Playback Route</string>
@@ -88,4 +89,8 @@
<string name="action_bar_fragment_has_options_menu">Set has options menu to true</string>
<string name="action_bar_fragment_menu_visibility">Set menu visibility to true</string>
<string name="sample_media_route_provider_remote">Remote Playback (Simulated)</string>
<string name="sample_media_route_activity_local">Local Playback</string>
<string name="sample_media_route_activity_presentation">Local Playback on Presentation Display</string>
</resources>

View File

@@ -0,0 +1,274 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.supportv7.media;
import com.example.android.supportv7.R;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.media.MediaPlayer;
import android.view.Surface;
import android.view.Gravity;
import android.graphics.SurfaceTexture;
import java.io.IOException;
/**
* MediaPlayerWrapper handles playback of a single media item, and is used for
* both local and remote playback.
*/
public class MediaPlayerWrapper implements
MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnSeekCompleteListener,
OverlayDisplayWindow.OverlayWindowListener,
MediaSessionManager.Callback {
private static final String TAG = "MediaPlayerWrapper";
private static final boolean DEBUG = false;
private static final int STATE_IDLE = 0;
private static final int STATE_PLAY_PENDING = 1;
private static final int STATE_READY = 2;
private static final int STATE_PLAYING = 3;
private static final int STATE_PAUSED = 4;
private final Context mContext;
private final Handler mHandler = new Handler();
private MediaPlayer mMediaPlayer;
private int mState = STATE_IDLE;
private Callback mCallback;
private Surface mSurface;
private int mSeekToPos;
public MediaPlayerWrapper(Context context) {
mContext = context;
reset();
}
public void release() {
onStop();
mMediaPlayer.release();
}
public void setCallback(Callback cb) {
mCallback = cb;
}
// MediaSessionManager.Callback
@Override
public void onNewItem(Uri uri) {
reset();
try {
mMediaPlayer.setDataSource(mContext, uri);
mMediaPlayer.prepareAsync();
} catch (IllegalStateException e) {
Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + uri);
} catch (IOException e) {
Log.e(TAG, "MediaPlayer throws IOException, uri=" + uri);
} catch (IllegalArgumentException e) {
Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + uri);
} catch (SecurityException e) {
Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + uri);
}
}
@Override
public void onStart() {
if (mState == STATE_READY || mState == STATE_PAUSED) {
mMediaPlayer.start();
mState = STATE_PLAYING;
} else if (mState == STATE_IDLE){
mState = STATE_PLAY_PENDING;
}
}
@Override
public void onStop() {
if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
mMediaPlayer.stop();
mState = STATE_IDLE;
}
}
@Override
public void onPause() {
if (mState == STATE_PLAYING) {
mMediaPlayer.pause();
mState = STATE_PAUSED;
}
}
@Override
public void onSeek(long pos) {
if (DEBUG) {
Log.d(TAG, "onSeek: pos=" + pos);
}
if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
mMediaPlayer.seekTo((int)pos);
mSeekToPos = (int)pos;
} else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) {
// Seek before onPrepared() arrives,
// need to performed delayed seek in onPrepared()
mSeekToPos = (int)pos;
}
}
@Override
public void onGetStatus(MediaQueueItem item) {
if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
// use mSeekToPos if we're currently seeking (mSeekToPos is reset
// when seeking is completed)
item.setContentDuration(mMediaPlayer.getDuration());
item.setContentPosition(mSeekToPos > 0 ?
mSeekToPos : mMediaPlayer.getCurrentPosition());
}
}
//OverlayDisplayWindow listeners
@Override
public void onWindowCreated(Surface surface) {
if (DEBUG) {
Log.d(TAG, "onWindowCreated");
}
mSurface = surface;
mMediaPlayer.setSurface(surface);
}
@Override
public void onWindowDestroyed() {
if (DEBUG) {
Log.d(TAG, "onWindowDestroyed");
}
}
//MediaPlayer Listeners
@Override
public void onPrepared(MediaPlayer mp) {
if (DEBUG) {
Log.d(TAG,"onPrepared");
}
mHandler.post(new Runnable() {
@Override
public void run() {
if (mState == STATE_IDLE) {
mState = STATE_READY;
updateVideoRect();
} else if (mState == STATE_PLAY_PENDING) {
mState = STATE_PLAYING;
updateVideoRect();
if (mSeekToPos > 0) {
Log.d(TAG, "Seeking to initial pos " + mSeekToPos);
mMediaPlayer.seekTo((int)mSeekToPos);
}
mMediaPlayer.start();
}
if (mCallback != null) {
mCallback.onStatusChanged();
}
}
});
}
@Override
public void onCompletion(MediaPlayer mp) {
if (DEBUG) {
Log.d(TAG,"onCompletion");
}
mHandler.post(new Runnable() {
@Override
public void run() {
if (mCallback != null) {
mCallback.onCompletion();
}
}
});
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if (DEBUG) {
Log.d(TAG,"onError");
}
mHandler.post(new Runnable() {
@Override
public void run() {
if (mCallback != null) {
mCallback.onError();
}
}
});
// return true so that onCompletion is not called
return true;
}
@Override
public void onSeekComplete(MediaPlayer mp) {
if (DEBUG) {
Log.d(TAG, "onSeekComplete");
}
mHandler.post(new Runnable() {
@Override
public void run() {
mSeekToPos = 0;
if (mCallback != null) {
mCallback.onStatusChanged();
}
}
});
}
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);
if (mSurface != null) {
mMediaPlayer.setSurface(mSurface);
}
mState = STATE_IDLE;
mSeekToPos = 0;
}
private void updateVideoRect() {
if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) {
int videoWidth = mMediaPlayer.getVideoWidth();
int videoHeight = mMediaPlayer.getVideoHeight();
if (videoWidth > 0 && videoHeight > 0) {
if (mCallback != null) {
mCallback.onSizeChanged(videoWidth, videoHeight);
}
} else {
Log.e(TAG, "video rect is 0x0!");
}
}
}
public static abstract class Callback {
public void onError() {}
public void onCompletion() {}
public void onStatusChanged() {}
public void onSizeChanged(int width, int height) {}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.supportv7.media;
import android.support.v7.media.MediaItemStatus;
import android.net.Uri;
import android.app.PendingIntent;
/**
* MediaQueueItem helps keep track of the current status of an media item.
*/
final class MediaQueueItem {
// immutables
private final String mSessionId;
private final String mItemId;
private final Uri mUri;
private final PendingIntent mUpdateReceiver;
// changeable states
private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
private long mContentPosition;
private long mContentDuration;
public MediaQueueItem(String qid, String iid, Uri uri, PendingIntent pi) {
mSessionId = qid;
mItemId = iid;
mUri = uri;
mUpdateReceiver = pi;
}
public void setState(int state) {
mPlaybackState = state;
}
public void setContentPosition(long pos) {
mContentPosition = pos;
}
public void setContentDuration(long duration) {
mContentDuration = duration;
}
public String getSessionId() {
return mSessionId;
}
public String getItemId() {
return mItemId;
}
public Uri getUri() {
return mUri;
}
public PendingIntent getUpdateReceiver() {
return mUpdateReceiver;
}
public int getState() {
return mPlaybackState;
}
public long getContentPosition() {
return mContentPosition;
}
public long getContentDuration() {
return mContentDuration;
}
public MediaItemStatus getStatus() {
return new MediaItemStatus.Builder(mPlaybackState)
.setContentPosition(mContentPosition)
.setContentDuration(mContentDuration)
.build();
}
@Override
public String toString() {
String state[] = {
"PENDING",
"PLAYING",
"PAUSED",
"BUFFERING",
"FINISHED",
"CANCELED",
"INVALIDATED",
"ERROR"
};
return "[" + mSessionId + "|" + mItemId + "|"
+ state[mPlaybackState] + "] " + mUri.toString();
}
}

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.supportv7.media;
import java.util.List;
import java.util.ArrayList;
import android.util.Log;
import android.net.Uri;
import android.app.PendingIntent;
import android.support.v7.media.MediaItemStatus;
/**
* MediaSessionManager manages a media session as a queue. It supports common
* queuing behaviors such as enqueue/remove of media items, pause/resume/stop,
* etc.
*
* Actual playback of a single media item is abstracted into a set of
* callbacks MediaSessionManager.Callback, and is handled outside this class.
*/
public class MediaSessionManager {
private static final String TAG = "MediaSessionManager";
private String mSessionId;
private String mItemId;
private String mCurItemId;
private boolean mIsPlaying = true;
private Callback mCallback;
private List<MediaQueueItem> mQueue = new ArrayList<MediaQueueItem>();
public MediaSessionManager() {
}
// Queue item (this maps to the ENQUEUE in the API which queues the item)
public MediaQueueItem enqueue(String sid, Uri uri, PendingIntent receiver) {
// fail if queue id is invalid
if (sid != null && !sid.equals(mSessionId)) {
Log.d(TAG, "invalid session id, mSessionId="+mSessionId+", sid="+sid);
return null;
}
// if queue id is unspecified, invalidate current queue
if (sid == null) {
invalidate();
}
mQueue.add(new MediaQueueItem(mSessionId, mItemId, uri, receiver));
if (updatePlaybackState()) {
MediaQueueItem item = findItem(mItemId);
mItemId = inc(mItemId);
if (item == null) {
Log.d(TAG, "item not found after it's added");
}
return item;
}
removeItem(mItemId, MediaItemStatus.PLAYBACK_STATE_ERROR);
return null;
}
public MediaQueueItem remove(String sid, String iid) {
if (sid == null || !sid.equals(mSessionId)) {
return null;
}
return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED);
}
// handles ERROR / COMPLETION
public MediaQueueItem finish(boolean error) {
return removeItem(mCurItemId, error ? MediaItemStatus.PLAYBACK_STATE_ERROR :
MediaItemStatus.PLAYBACK_STATE_FINISHED);
}
public MediaQueueItem seek(String sid, String iid, long pos) {
if (sid == null || !sid.equals(mSessionId)) {
return null;
}
for (int i = 0; i < mQueue.size(); i++) {
MediaQueueItem item = mQueue.get(i);
if (iid.equals(item.getItemId())) {
if (pos != item.getContentPosition()) {
item.setContentPosition(pos);
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
if (mCallback != null) {
mCallback.onSeek(pos);
}
}
}
return item;
}
}
return null;
}
public MediaQueueItem getCurrentItem() {
return getStatus(mSessionId, mCurItemId);
}
public MediaQueueItem getStatus(String sid, String iid) {
if (sid == null || !sid.equals(mSessionId)) {
return null;
}
for (int i = 0; i < mQueue.size(); i++) {
MediaQueueItem item = mQueue.get(i);
if (iid.equals(item.getItemId())) {
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
if (mCallback != null) {
mCallback.onGetStatus(item);
}
}
return item;
}
}
return null;
}
public boolean pause(String sid) {
if (sid == null || !sid.equals(mSessionId)) {
return false;
}
mIsPlaying = false;
return updatePlaybackState();
}
public boolean resume(String sid) {
if (sid == null || !sid.equals(mSessionId)) {
return false;
}
mIsPlaying = true;
return updatePlaybackState();
}
public boolean stop(String sid) {
if (sid == null || !sid.equals(mSessionId)) {
return false;
}
clear();
return true;
}
public void setCallback(Callback cb) {
mCallback = cb;
}
@Override
public String toString() {
String result = "Media Queue: ";
if (!mQueue.isEmpty()) {
for (MediaQueueItem item : mQueue) {
result += "\n" + item.toString();
}
} else {
result += "<empty>";
}
return result;
}
private String inc(String id) {
return (id == null) ? "0" : Integer.toString(Integer.parseInt(id)+1);
}
// play the item at queue head
private void play() {
MediaQueueItem item = mQueue.get(0);
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
mCurItemId = item.getItemId();
if (mCallback != null) {
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
mCallback.onNewItem(item.getUri());
}
mCallback.onStart();
}
item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
}
}
// stop the currently playing item
private void stop() {
MediaQueueItem item = mQueue.get(0);
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
if (mCallback != null) {
mCallback.onStop();
}
item.setState(MediaItemStatus.PLAYBACK_STATE_FINISHED);
}
}
// pause the currently playing item
private void pause() {
MediaQueueItem item = mQueue.get(0);
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
if (mCallback != null) {
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
mCallback.onNewItem(item.getUri());
} else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
mCallback.onPause();
}
}
item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
}
}
private void clear() {
if (mQueue.size() > 0) {
stop();
mQueue.clear();
}
}
private void invalidate() {
clear();
mSessionId = inc(mSessionId);
mItemId = "0";
mIsPlaying = true;
}
private boolean updatePlaybackState() {
if (mQueue.isEmpty()) {
return true;
}
if (mIsPlaying) {
play();
} else {
pause();
}
return true;
}
private MediaQueueItem findItem(String iid) {
for (MediaQueueItem item : mQueue) {
if (iid.equals(item.getItemId())) {
return item;
}
}
return null;
}
private MediaQueueItem removeItem(String iid, int state) {
List<MediaQueueItem> queue =
new ArrayList<MediaQueueItem>(mQueue.size());
MediaQueueItem found = null;
for (MediaQueueItem item : mQueue) {
if (iid.equals(item.getItemId())) {
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
stop();
}
item.setState(state);
found = item;
} else {
queue.add(item);
}
}
if (found != null) {
mQueue = queue;
updatePlaybackState();
}
return found;
}
public interface Callback {
public void onStart();
public void onPause();
public void onStop();
public void onSeek(long pos);
public void onGetStatus(MediaQueueItem item);
public void onNewItem(Uri uri);
}
}

View File

@@ -0,0 +1,362 @@
/*
* 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.supportv7.media;
import com.example.android.supportv7.R;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
import android.util.Log;
import android.view.Display;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.TextureView;
import android.view.View;
import android.view.Surface;
import android.view.WindowManager;
import android.view.TextureView.SurfaceTextureListener;
import android.widget.TextView;
/**
* Manages an overlay display window, used for simulating remote playback.
*/
public class OverlayDisplayWindow {
private static final String TAG = "OverlayDisplayWindow";
private static final boolean DEBUG = false;
private final float INITIAL_SCALE = 0.5f;
private final float MIN_SCALE = 0.3f;
private final float MAX_SCALE = 1.0f;
private final float WINDOW_ALPHA = 0.8f;
// 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 final boolean DISABLE_MOVE_AND_RESIZE = false;
private final Context mContext;
private final int mWidth;
private final int mHeight;
private final int mGravity;
private OverlayWindowListener mListener;
private final String mTitle;
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 mTitleTextView;
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 OverlayDisplayWindow(Context context, String name,
int width, int height, int gravity) {
mContext = context;
mWidth = width;
mHeight = height;
mGravity = gravity;
mTitle = name;
mDisplayManager = (DisplayManager)context.getSystemService(
Context.DISPLAY_SERVICE);
mWindowManager = (WindowManager)context.getSystemService(
Context.WINDOW_SERVICE);
mDefaultDisplay = mWindowManager.getDefaultDisplay();
updateDefaultDisplayInfo();
createWindow();
}
public void setOverlayWindowListener(OverlayWindowListener listener) {
mListener = listener;
}
public Context getContext() {
return mContext;
}
public void show() {
if (!mWindowVisible) {
mDisplayManager.registerDisplayListener(mDisplayListener, null);
if (!updateDefaultDisplayInfo()) {
mDisplayManager.unregisterDisplayListener(mDisplayListener);
return;
}
clearLiveState();
updateWindowParams();
mWindowManager.addView(mWindowContent, mWindowParams);
mWindowVisible = true;
}
}
public void dismiss() {
if (mWindowVisible) {
mDisplayManager.unregisterDisplayListener(mDisplayListener);
mWindowManager.removeView(mWindowContent);
mWindowVisible = false;
}
}
public void relayout() {
if (mWindowVisible) {
updateWindowParams();
mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
}
}
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 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);
mTitleTextView = (TextView)mWindowContent.findViewById(
R.id.overlay_display_window_title);
mTitleTextView.setText(mTitle);
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(mTitle);
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;
}
};
// Watches for significant changes in the overlay display window lifecycle.
public interface OverlayWindowListener {
public void onWindowCreated(Surface surface);
public void onWindowDestroyed();
}
}

View File

@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.media.MediaRouter;
import android.net.Uri;
import android.os.Bundle;
import android.app.PendingIntent;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaItemStatus;
import android.support.v7.media.MediaRouteProvider;
@@ -34,9 +35,8 @@ import android.support.v7.media.MediaRouteProviderDescriptor;
import android.support.v7.media.MediaRouteDescriptor;
import android.util.Log;
import android.widget.Toast;
import android.view.Gravity;
import java.util.ArrayList;
import java.util.UUID;
/**
* Demonstrates how to create a custom media route provider.
@@ -77,6 +77,14 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
public static final String DATA_PLAYBACK_COUNT =
"com.example.android.supportv7.media.EXTRA_PLAYBACK_COUNT";
/*
* Set ENABLE_QUEUEING to true to test queuing on MRP. This will make
* MRP expose the following two experimental hidden APIs:
* ACTION_ENQUEUE
* ACTION_REMOVE
*/
public static final boolean ENABLE_QUEUEING = false;
private static final ArrayList<IntentFilter> CONTROL_FILTERS;
static {
IntentFilter f1 = new IntentFilter();
@@ -88,11 +96,39 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
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);
CONTROL_FILTERS = new ArrayList<IntentFilter>();
CONTROL_FILTERS.add(f1);
CONTROL_FILTERS.add(f2);
CONTROL_FILTERS.add(f3);
if (ENABLE_QUEUEING) {
CONTROL_FILTERS.add(f4);
CONTROL_FILTERS.add(f5);
}
}
private static void addDataTypeUnchecked(IntentFilter filter, String type) {
@@ -104,7 +140,7 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
}
private int mVolume = 5;
private int mPlaybackCount;
private int mEnqueueCount;
public SampleMediaRouteProvider(Context context) {
super(context);
@@ -149,31 +185,47 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
setDescriptor(providerDescriptor);
}
private String generateStreamId() {
return UUID.randomUUID().toString();
private void showToast(String msg) {
Toast toast = Toast.makeText(getContext(),
"[provider] " + msg, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
private final class SampleRouteController extends MediaRouteProvider.RouteController {
private final String mRouteId;
// Create an overlay display window (used for simulating the remote playback only)
private final OverlayDisplayWindow mOverlay = new OverlayDisplayWindow(getContext(),
getContext().getResources().getString(R.string.sample_media_route_provider_remote),
1024, 768, Gravity.CENTER);
private final MediaPlayerWrapper mMediaPlayer = new MediaPlayerWrapper(getContext());
private final MediaSessionManager mSessionManager = new MediaSessionManager();
public SampleRouteController(String routeId) {
mRouteId = routeId;
mSessionManager.setCallback(mMediaPlayer);
mOverlay.setOverlayWindowListener(mMediaPlayer);
mMediaPlayer.setCallback(new MediaPlayerCallback());
Log.d(TAG, mRouteId + ": Controller created");
}
@Override
public void onRelease() {
Log.d(TAG, mRouteId + ": Controller released");
mMediaPlayer.release();
}
@Override
public void onSelect() {
Log.d(TAG, mRouteId + ": Selected");
mOverlay.show();
}
@Override
public void onUnselect() {
Log.d(TAG, mRouteId + ": Unselected");
mMediaPlayer.onStop();
mOverlay.dismiss();
}
@Override
@@ -192,6 +244,45 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
}
}
@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);
}
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;
@@ -200,63 +291,198 @@ final class SampleMediaRouteProvider extends MediaRouteProvider {
}
}
@Override
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
Log.d(TAG, mRouteId + ": Received control request " + intent);
if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)
&& intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
&& intent.getData() != null) {
mPlaybackCount +=1;
// TODO: Handle queue ids.
Uri uri = intent.getData();
long contentPositionMillis = 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);
Log.d(TAG, mRouteId + ": Received play request, uri=" + uri
+ ", contentPositionMillis=" + contentPositionMillis
+ ", metadata=" + metadata
+ ", headers=" + headers);
if (uri.toString().contains("hats")) {
// Simulate generating an error whenever the uri contains the word 'hats'.
Toast.makeText(getContext(), "Route rejected play request: uri=" + uri
+ ", no hats allowed!", Toast.LENGTH_LONG).show();
if (callback != null) {
callback.onError("Simulated error. No hats allowed!", null);
}
} else {
Toast.makeText(getContext(), "Route received play request: uri=" + uri,
Toast.LENGTH_LONG).show();
String streamId = generateStreamId();
if (callback != null) {
MediaItemStatus status = new MediaItemStatus.Builder(
MediaItemStatus.PLAYBACK_STATE_PLAYING)
.setContentPosition(contentPositionMillis)
.build();
Bundle result = new Bundle();
result.putString(MediaControlIntent.EXTRA_ITEM_ID, streamId);
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, status.asBundle());
callback.onResult(result);
}
}
return true;
}
if (intent.getAction().equals(ACTION_GET_STATISTICS)
&& intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) {
Bundle data = new Bundle();
data.putInt(DATA_PLAYBACK_COUNT, mPlaybackCount);
if (callback != null) {
callback.onResult(data);
}
return true;
private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
if (sid == null || mSessionManager.stop(sid)) {
Log.d(TAG, "handleEnqueue");
return handleEnqueue(intent, callback);
}
return false;
}
private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) {
if (intent.getData() == null) {
return false;
}
mEnqueueCount +=1;
boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE);
Uri uri = intent.getData();
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
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
+ ", sid=" + sid
+ ", pos=" + pos
+ ", metadata=" + metadata
+ ", headers=" + headers
+ ", receiver=" + receiver);
MediaQueueItem item = mSessionManager.enqueue(sid, uri, 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);
}
}
return true;
}
private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
MediaQueueItem item = mSessionManager.remove(sid, 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);
String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
MediaQueueItem item = mSessionManager.seek(sid, iid, pos);
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);
MediaQueueItem item = mSessionManager.getStatus(sid, 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 = mSessionManager.pause(sid);
if (callback != null) {
if (success) {
callback.onResult(null);
} 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 = mSessionManager.resume(sid);
if (callback != null) {
if (success) {
callback.onResult(null);
} 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 = mSessionManager.stop(sid);
if (callback != null) {
if (success) {
callback.onResult(null);
} else {
callback.onError("Failed to stop, sid=" + sid, null);
}
}
return success;
}
private void handleFinish(boolean error) {
MediaQueueItem item = mSessionManager.finish(error);
if (item != null) {
handleStatusChange(item);
}
}
private void handleStatusChange(MediaQueueItem 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 final class MediaPlayerCallback extends MediaPlayerWrapper.Callback {
@Override
public void onError() {
handleFinish(true);
}
@Override
public void onCompletion() {
handleFinish(false);
}
@Override
public void onStatusChanged() {
handleStatusChange(null);
}
@Override
public void onSizeChanged(int width, int height) {
mOverlay.updateAspectRatio(width, height);
}
}
}
}