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