SampleMediaRouteProvider.java revision 7578fad97dcd3b4b390652f37a30d805538980f5
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.example.android.supportv7.media; 18 19import com.example.android.supportv7.R; 20 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.IntentFilter.MalformedMimeTypeException; 25import android.content.res.Resources; 26import android.media.AudioManager; 27import android.media.MediaRouter; 28import android.net.Uri; 29import android.os.Bundle; 30import android.app.PendingIntent; 31import android.support.v7.media.MediaControlIntent; 32import android.support.v7.media.MediaItemStatus; 33import android.support.v7.media.MediaRouteProvider; 34import android.support.v7.media.MediaRouter.ControlRequestCallback; 35import android.support.v7.media.MediaRouteProviderDescriptor; 36import android.support.v7.media.MediaRouteDescriptor; 37import android.util.Log; 38import android.widget.Toast; 39import android.view.Gravity; 40import java.util.ArrayList; 41 42/** 43 * Demonstrates how to create a custom media route provider. 44 * 45 * @see SampleMediaRouteProviderService 46 */ 47final class SampleMediaRouteProvider extends MediaRouteProvider { 48 private static final String TAG = "SampleMediaRouteProvider"; 49 50 private static final String FIXED_VOLUME_ROUTE_ID = "fixed"; 51 private static final String VARIABLE_VOLUME_ROUTE_ID = "variable"; 52 private static final int VOLUME_MAX = 10; 53 54 /** 55 * A custom media control intent category for special requests that are 56 * supported by this provider's routes. 57 */ 58 public static final String CATEGORY_SAMPLE_ROUTE = 59 "com.example.android.supportv7.media.CATEGORY_SAMPLE_ROUTE"; 60 61 /** 62 * A custom media control intent action for special requests that are 63 * supported by this provider's routes. 64 * <p> 65 * This particular request is designed to return a bundle of not very 66 * interesting statistics for demonstration purposes. 67 * </p> 68 * 69 * @see #DATA_PLAYBACK_COUNT 70 */ 71 public static final String ACTION_GET_STATISTICS = 72 "com.example.android.supportv7.media.ACTION_GET_STATISTICS"; 73 74 /** 75 * {@link #ACTION_GET_STATISTICS} result data: Number of times the 76 * playback action was invoked. 77 */ 78 public static final String DATA_PLAYBACK_COUNT = 79 "com.example.android.supportv7.media.EXTRA_PLAYBACK_COUNT"; 80 81 /* 82 * Set ENABLE_QUEUEING to true to test queuing on MRP. This will make 83 * MRP expose the following two experimental hidden APIs: 84 * ACTION_ENQUEUE 85 * ACTION_REMOVE 86 */ 87 public static final boolean ENABLE_QUEUEING = false; 88 89 private static final ArrayList<IntentFilter> CONTROL_FILTERS; 90 static { 91 IntentFilter f1 = new IntentFilter(); 92 f1.addCategory(CATEGORY_SAMPLE_ROUTE); 93 f1.addAction(ACTION_GET_STATISTICS); 94 95 IntentFilter f2 = new IntentFilter(); 96 f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 97 f2.addAction(MediaControlIntent.ACTION_PLAY); 98 f2.addDataScheme("http"); 99 f2.addDataScheme("https"); 100 f2.addDataScheme("rtsp"); 101 f2.addDataScheme("file"); 102 addDataTypeUnchecked(f2, "video/*"); 103 104 IntentFilter f3 = new IntentFilter(); 105 f3.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 106 f3.addAction(MediaControlIntent.ACTION_SEEK); 107 f3.addAction(MediaControlIntent.ACTION_GET_STATUS); 108 f3.addAction(MediaControlIntent.ACTION_PAUSE); 109 f3.addAction(MediaControlIntent.ACTION_RESUME); 110 f3.addAction(MediaControlIntent.ACTION_STOP); 111 112 IntentFilter f4 = new IntentFilter(); 113 f4.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 114 f4.addAction(MediaControlIntent.ACTION_ENQUEUE); 115 f4.addDataScheme("http"); 116 f4.addDataScheme("https"); 117 f4.addDataScheme("rtsp"); 118 f4.addDataScheme("file"); 119 addDataTypeUnchecked(f4, "video/*"); 120 121 IntentFilter f5 = new IntentFilter(); 122 f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 123 f5.addAction(MediaControlIntent.ACTION_REMOVE); 124 125 CONTROL_FILTERS = new ArrayList<IntentFilter>(); 126 CONTROL_FILTERS.add(f1); 127 CONTROL_FILTERS.add(f2); 128 CONTROL_FILTERS.add(f3); 129 if (ENABLE_QUEUEING) { 130 CONTROL_FILTERS.add(f4); 131 CONTROL_FILTERS.add(f5); 132 } 133 } 134 135 private static void addDataTypeUnchecked(IntentFilter filter, String type) { 136 try { 137 filter.addDataType(type); 138 } catch (MalformedMimeTypeException ex) { 139 throw new RuntimeException(ex); 140 } 141 } 142 143 private int mVolume = 5; 144 private int mEnqueueCount; 145 146 public SampleMediaRouteProvider(Context context) { 147 super(context); 148 149 publishRoutes(); 150 } 151 152 @Override 153 public RouteController onCreateRouteController(String routeId) { 154 return new SampleRouteController(routeId); 155 } 156 157 private void publishRoutes() { 158 Resources r = getContext().getResources(); 159 160 MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder( 161 FIXED_VOLUME_ROUTE_ID, 162 r.getString(R.string.fixed_volume_route_name)) 163 .setDescription(r.getString(R.string.sample_route_description)) 164 .addControlFilters(CONTROL_FILTERS) 165 .setPlaybackStream(AudioManager.STREAM_MUSIC) 166 .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) 167 .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) 168 .setVolume(VOLUME_MAX) 169 .build(); 170 171 MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder( 172 VARIABLE_VOLUME_ROUTE_ID, 173 r.getString(R.string.variable_volume_route_name)) 174 .setDescription(r.getString(R.string.sample_route_description)) 175 .addControlFilters(CONTROL_FILTERS) 176 .setPlaybackStream(AudioManager.STREAM_MUSIC) 177 .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) 178 .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) 179 .setVolumeMax(VOLUME_MAX) 180 .setVolume(mVolume) 181 .build(); 182 183 MediaRouteProviderDescriptor providerDescriptor = 184 new MediaRouteProviderDescriptor.Builder() 185 .addRoute(routeDescriptor1) 186 .addRoute(routeDescriptor2) 187 .build(); 188 setDescriptor(providerDescriptor); 189 } 190 191 private void showToast(String msg) { 192 Toast toast = Toast.makeText(getContext(), 193 "[provider] " + msg, Toast.LENGTH_LONG); 194 toast.setGravity(Gravity.TOP, 0, 100); 195 toast.show(); 196 } 197 198 private final class SampleRouteController extends MediaRouteProvider.RouteController { 199 private final String mRouteId; 200 // Create an overlay display window (used for simulating the remote playback only) 201 private final OverlayDisplayWindow mOverlay = new OverlayDisplayWindow(getContext(), 202 getContext().getResources().getString(R.string.sample_media_route_provider_remote), 203 1024, 768, Gravity.CENTER); 204 private final MediaPlayerWrapper mMediaPlayer = new MediaPlayerWrapper(getContext()); 205 private final MediaSessionManager mSessionManager = new MediaSessionManager(); 206 207 public SampleRouteController(String routeId) { 208 mRouteId = routeId; 209 mSessionManager.setCallback(mMediaPlayer); 210 mOverlay.setOverlayWindowListener(mMediaPlayer); 211 mMediaPlayer.setCallback(new MediaPlayerCallback()); 212 Log.d(TAG, mRouteId + ": Controller created"); 213 } 214 215 @Override 216 public void onRelease() { 217 Log.d(TAG, mRouteId + ": Controller released"); 218 mMediaPlayer.release(); 219 } 220 221 @Override 222 public void onSelect() { 223 Log.d(TAG, mRouteId + ": Selected"); 224 mOverlay.show(); 225 } 226 227 @Override 228 public void onUnselect() { 229 Log.d(TAG, mRouteId + ": Unselected"); 230 mMediaPlayer.onStop(); 231 mOverlay.dismiss(); 232 } 233 234 @Override 235 public void onSetVolume(int volume) { 236 Log.d(TAG, mRouteId + ": Set volume to " + volume); 237 if (mRouteId.equals(VARIABLE_VOLUME_ROUTE_ID)) { 238 setVolumeInternal(volume); 239 } 240 } 241 242 @Override 243 public void onUpdateVolume(int delta) { 244 Log.d(TAG, mRouteId + ": Update volume by " + delta); 245 if (mRouteId.equals(VARIABLE_VOLUME_ROUTE_ID)) { 246 setVolumeInternal(mVolume + delta); 247 } 248 } 249 250 @Override 251 public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { 252 Log.d(TAG, mRouteId + ": Received control request " + intent); 253 String action = intent.getAction(); 254 if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 255 boolean success = false; 256 if (action.equals(MediaControlIntent.ACTION_PLAY)) { 257 success = handlePlay(intent, callback); 258 } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) { 259 success = handleEnqueue(intent, callback); 260 } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) { 261 success = handleRemove(intent, callback); 262 } else if (action.equals(MediaControlIntent.ACTION_SEEK)) { 263 success = handleSeek(intent, callback); 264 } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) { 265 success = handleGetStatus(intent, callback); 266 } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) { 267 success = handlePause(intent, callback); 268 } else if (action.equals(MediaControlIntent.ACTION_RESUME)) { 269 success = handleResume(intent, callback); 270 } else if (action.equals(MediaControlIntent.ACTION_STOP)) { 271 success = handleStop(intent, callback); 272 } 273 Log.d(TAG, mSessionManager.toString()); 274 return success; 275 } 276 277 if (action.equals(ACTION_GET_STATISTICS) 278 && intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) { 279 Bundle data = new Bundle(); 280 data.putInt(DATA_PLAYBACK_COUNT, mEnqueueCount); 281 if (callback != null) { 282 callback.onResult(data); 283 } 284 return true; 285 } 286 return false; 287 } 288 289 private void setVolumeInternal(int volume) { 290 if (volume >= 0 && volume <= VOLUME_MAX) { 291 mVolume = volume; 292 Log.d(TAG, mRouteId + ": New volume is " + mVolume); 293 AudioManager audioManager = 294 (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE); 295 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); 296 publishRoutes(); 297 } 298 } 299 300 private boolean handlePlay(Intent intent, ControlRequestCallback callback) { 301 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 302 if (sid == null || mSessionManager.stop(sid)) { 303 Log.d(TAG, "handleEnqueue"); 304 return handleEnqueue(intent, callback); 305 } 306 return false; 307 } 308 309 private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) { 310 if (intent.getData() == null) { 311 return false; 312 } 313 314 mEnqueueCount +=1; 315 316 boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE); 317 Uri uri = intent.getData(); 318 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 319 long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0); 320 Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA); 321 Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS); 322 PendingIntent receiver = (PendingIntent)intent.getParcelableExtra( 323 MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER); 324 325 Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request" 326 + ", uri=" + uri 327 + ", sid=" + sid 328 + ", pos=" + pos 329 + ", metadata=" + metadata 330 + ", headers=" + headers 331 + ", receiver=" + receiver); 332 MediaQueueItem item = mSessionManager.enqueue(sid, uri, receiver); 333 if (callback != null) { 334 if (item != null) { 335 Bundle result = new Bundle(); 336 result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); 337 result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); 338 result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, 339 item.getStatus().asBundle()); 340 callback.onResult(result); 341 } else { 342 callback.onError("Failed to open " + uri.toString(), null); 343 } 344 } 345 return true; 346 } 347 348 private boolean handleRemove(Intent intent, ControlRequestCallback callback) { 349 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 350 String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); 351 MediaQueueItem item = mSessionManager.remove(sid, iid); 352 if (callback != null) { 353 if (item != null) { 354 Bundle result = new Bundle(); 355 result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, 356 item.getStatus().asBundle()); 357 callback.onResult(result); 358 } else { 359 callback.onError("Failed to remove" + 360 ", sid=" + sid + ", iid=" + iid, null); 361 } 362 } 363 return (item != null); 364 } 365 366 private boolean handleSeek(Intent intent, ControlRequestCallback callback) { 367 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 368 String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); 369 long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0); 370 Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos); 371 MediaQueueItem item = mSessionManager.seek(sid, iid, pos); 372 if (callback != null) { 373 if (item != null) { 374 Bundle result = new Bundle(); 375 result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, 376 item.getStatus().asBundle()); 377 callback.onResult(result); 378 } else { 379 callback.onError("Failed to seek" + 380 ", sid=" + sid + ", iid=" + iid + ", pos=" + pos, null); 381 } 382 } 383 return (item != null); 384 } 385 386 private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) { 387 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 388 String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); 389 MediaQueueItem item = mSessionManager.getStatus(sid, iid); 390 if (callback != null) { 391 if (item != null) { 392 Bundle result = new Bundle(); 393 result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, 394 item.getStatus().asBundle()); 395 callback.onResult(result); 396 } else { 397 callback.onError("Failed to get status" + 398 ", sid=" + sid + ", iid=" + iid, null); 399 } 400 } 401 return (item != null); 402 } 403 404 private boolean handlePause(Intent intent, ControlRequestCallback callback) { 405 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 406 boolean success = mSessionManager.pause(sid); 407 if (callback != null) { 408 if (success) { 409 callback.onResult(null); 410 } else { 411 callback.onError("Failed to pause, sid=" + sid, null); 412 } 413 } 414 return success; 415 } 416 417 private boolean handleResume(Intent intent, ControlRequestCallback callback) { 418 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 419 boolean success = mSessionManager.resume(sid); 420 if (callback != null) { 421 if (success) { 422 callback.onResult(null); 423 } else { 424 callback.onError("Failed to resume, sid=" + sid, null); 425 } 426 } 427 return success; 428 } 429 430 private boolean handleStop(Intent intent, ControlRequestCallback callback) { 431 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 432 boolean success = mSessionManager.stop(sid); 433 if (callback != null) { 434 if (success) { 435 callback.onResult(null); 436 } else { 437 callback.onError("Failed to stop, sid=" + sid, null); 438 } 439 } 440 return success; 441 } 442 443 private void handleFinish(boolean error) { 444 MediaQueueItem item = mSessionManager.finish(error); 445 if (item != null) { 446 handleStatusChange(item); 447 } 448 } 449 450 private void handleStatusChange(MediaQueueItem item) { 451 if (item == null) { 452 item = mSessionManager.getCurrentItem(); 453 } 454 if (item != null) { 455 PendingIntent receiver = item.getUpdateReceiver(); 456 if (receiver != null) { 457 Intent intent = new Intent(); 458 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); 459 intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); 460 intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS, 461 item.getStatus().asBundle()); 462 try { 463 receiver.send(getContext(), 0, intent); 464 Log.d(TAG, mRouteId + ": Sending status update from provider"); 465 } catch (PendingIntent.CanceledException e) { 466 Log.d(TAG, mRouteId + ": Failed to send status update!"); 467 } 468 } 469 } 470 } 471 472 private final class MediaPlayerCallback extends MediaPlayerWrapper.Callback { 473 @Override 474 public void onError() { 475 handleFinish(true); 476 } 477 478 @Override 479 public void onCompletion() { 480 handleFinish(false); 481 } 482 483 @Override 484 public void onStatusChanged() { 485 handleStatusChange(null); 486 } 487 488 @Override 489 public void onSizeChanged(int width, int height) { 490 mOverlay.updateAspectRatio(width, height); 491 } 492 } 493 } 494}