am 5f4e7a10: Merge "Support7Demos: media router sample" into jb-mr2-ub-dev
* commit '5f4e7a104b764f07bdc963aa7e668cc3913f85e3': Support7Demos: media router sample
@@ -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" />
|
||||
|
||||
|
||||
BIN
samples/Support7Demos/res/drawable-hdpi/ic_media_pause.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
samples/Support7Demos/res/drawable-hdpi/ic_media_play.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
samples/Support7Demos/res/drawable-hdpi/ic_media_stop.png
Normal file
|
After Width: | Height: | Size: 553 B |
BIN
samples/Support7Demos/res/drawable-hdpi/ic_menu_add.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
samples/Support7Demos/res/drawable-hdpi/ic_menu_delete.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
samples/Support7Demos/res/drawable-mdpi/ic_media_pause.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
samples/Support7Demos/res/drawable-mdpi/ic_media_play.png
Normal file
|
After Width: | Height: | Size: 897 B |
BIN
samples/Support7Demos/res/drawable-mdpi/ic_media_stop.png
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
samples/Support7Demos/res/drawable-mdpi/ic_menu_add.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
samples/Support7Demos/res/drawable-mdpi/ic_menu_delete.png
Normal file
|
After Width: | Height: | Size: 967 B |
43
samples/Support7Demos/res/layout/media_item.xml
Normal 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>
|
||||
28
samples/Support7Demos/res/layout/overlay_display_window.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||