1 /* 2 * Copyright 2017 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 17 package android.media; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.StringDef; 24 import android.annotation.TestApi; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.res.AssetFileDescriptor; 28 import android.graphics.Rect; 29 import android.graphics.SurfaceTexture; 30 import android.media.MediaDrm.KeyRequest; 31 import android.media.MediaPlayer2.DrmInfo; 32 import android.media.MediaPlayer2Proto.PlayerMessage; 33 import android.media.MediaPlayer2Proto.Value; 34 import android.media.protobuf.InvalidProtocolBufferException; 35 import android.net.Uri; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.Looper; 39 import android.os.Message; 40 import android.os.ParcelFileDescriptor; 41 import android.os.PersistableBundle; 42 import android.os.PowerManager; 43 import android.util.Log; 44 import android.util.Pair; 45 import android.util.Size; 46 import android.view.Surface; 47 import android.view.SurfaceHolder; 48 49 import com.android.internal.annotations.GuardedBy; 50 51 import java.io.ByteArrayOutputStream; 52 import java.io.File; 53 import java.io.FileDescriptor; 54 import java.io.FileInputStream; 55 import java.io.IOException; 56 import java.io.InputStream; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.lang.ref.WeakReference; 60 import java.net.HttpCookie; 61 import java.net.HttpURLConnection; 62 import java.net.URL; 63 import java.nio.ByteOrder; 64 import java.util.ArrayList; 65 import java.util.Arrays; 66 import java.util.Collection; 67 import java.util.Collections; 68 import java.util.HashMap; 69 import java.util.Iterator; 70 import java.util.LinkedList; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.Queue; 74 import java.util.UUID; 75 import java.util.concurrent.CompletableFuture; 76 import java.util.concurrent.ConcurrentLinkedQueue; 77 import java.util.concurrent.ExecutionException; 78 import java.util.concurrent.Executor; 79 import java.util.concurrent.ExecutorService; 80 import java.util.concurrent.Executors; 81 import java.util.concurrent.Future; 82 import java.util.concurrent.RejectedExecutionException; 83 import java.util.concurrent.TimeUnit; 84 import java.util.concurrent.TimeoutException; 85 import java.util.concurrent.atomic.AtomicInteger; 86 import java.util.concurrent.atomic.AtomicLong; 87 88 /** 89 * MediaPlayer2 class can be used to control playback of audio/video files and streams. 90 * 91 * <p> 92 * This API is not generally intended for third party application developers. 93 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 94 * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> 95 * for consistent behavior across all devices. 96 * 97 * <p>Topics covered here are: 98 * <ol> 99 * <li><a href="#PlayerStates">Player states</a> 100 * <li><a href="#InvalidStates">Invalid method calls</a> 101 * <li><a href="#Permissions">Permissions</a> 102 * <li><a href="#Callbacks">Callbacks</a> 103 * </ol> 104 * 105 * 106 * <h3 id="PlayerStates">Player states</h3> 107 * 108 * <p>The playback control of audio/video files is managed as a state machine.</p> 109 * <p><div style="text-align:center;"><img src="../../../images/mediaplayer2_state_diagram.png" 110 * alt="MediaPlayer2 State diagram" 111 * border="0" /></div></p> 112 * <p>The MediaPlayer2 object has five states:</p> 113 * <ol> 114 * <li><p>{@link #PLAYER_STATE_IDLE}: MediaPlayer2 is in the <strong>Idle</strong> 115 * state after it's created, or after calling {@link #reset()}.</p> 116 * 117 * <p>While in this state, you should call 118 * {@link #setDataSource setDataSource}. It is a good 119 * programming practice to register an {@link EventCallback#onCallCompleted onCallCompleted} 120 * <a href="#Callbacks">callback</a> and watch for {@link #CALL_STATUS_BAD_VALUE} and 121 * {@link #CALL_STATUS_ERROR_IO}, which might be caused by <code>setDataSource</code>. 122 * </p> 123 * 124 * <p>Calling {@link #prepare()} transfers a MediaPlayer2 object to 125 * the <strong>Prepared</strong> state. Note 126 * that {@link #prepare()} is asynchronous. When the preparation completes, 127 * if you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>, 128 * the player executes the callback 129 * with {@link #MEDIA_INFO_PREPARED} and transitions to the 130 * <strong>Prepared</strong> state.</p> 131 * </li> 132 * 133 * <li>{@link #PLAYER_STATE_PREPARED}: A MediaPlayer object must be in the 134 * <strong>Prepared</strong> state before playback can be started for the first time. 135 * While in this state, you can set player properties 136 * such as audio/sound volume and looping by invoking the corresponding set methods. 137 * Calling {@link #play()} transfers a MediaPlayer2 object to 138 * the <strong>Playing</strong> state. 139 * </li> 140 * 141 * <li>{@link #PLAYER_STATE_PLAYING}: 142 * <p>The player plays the data source while in this state. 143 * If you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>, 144 * the player regularly executes the callback with 145 * {@link #MEDIA_INFO_BUFFERING_UPDATE}. 146 * This allows applications to keep track of the buffering status 147 * while streaming audio/video.</p> 148 * 149 * <p> When the playback reaches the end of stream, the behavior depends on whether or 150 * not you've enabled looping by calling {@link #loopCurrent}:</p> 151 * <ul> 152 * <li>If the looping mode was set to <code>false</code>, the player will transfer 153 * to the <strong>Paused</strong> state. If you registered an {@link EventCallback#onInfo 154 * onInfo} <a href="#Callbacks">callback</a> 155 * the player calls the callback with {@link #MEDIA_INFO_DATA_SOURCE_END} and enters 156 * the <strong>Paused</strong> state. 157 * </li> 158 * <li>If the looping mode was set to <code>true</code>, 159 * the MediaPlayer2 object remains in the <strong>Playing</strong> state and replays its 160 * data source from the beginning.</li> 161 * </ul> 162 * </li> 163 * 164 * <li>{@link #PLAYER_STATE_PAUSED}: Audio/video playback pauses while in this state. 165 * Call {@link #play()} to resume playback from the position where it paused.</li> 166 * 167 * <li>{@link #PLAYER_STATE_ERROR}: <p>In general, playback might fail due to various 168 * reasons such as unsupported audio/video format, poorly interleaved 169 * audio/video, resolution too high, streaming timeout, and others. 170 * In addition, due to programming errors, a playback 171 * control operation might be performed from an <a href="#InvalidStates">invalid state</a>. 172 * In these cases the player transitions to the <strong>Error</strong> state.</p> 173 * 174 * <p>If you register an {@link EventCallback#onError onError}} 175 * <a href="#Callbacks">callback</a>, 176 * the callback will be performed when entering the state. When programming errors happen, 177 * such as calling {@link #prepare()} and 178 * {@link #setDataSource} methods 179 * from an <a href="#InvalidStates">invalid state</a>, the callback is called with 180 * {@link #CALL_STATUS_INVALID_OPERATION}. The MediaPlayer2 object enters the 181 * <strong>Error</strong> state whether or not a callback exists. </p> 182 * 183 * <p>To recover from an error and reuse a MediaPlayer2 object that is in the <strong> 184 * Error</strong> state, 185 * call {@link #reset()}. The object will return to the <strong>Idle</strong> 186 * state and all state information will be lost.</p> 187 * </li> 188 * </ol> 189 * 190 * <p>You should follow these best practices when coding an app that uses MediaPlayer2:</p> 191 * 192 * <ul> 193 * 194 * <li>Use <a href="#Callbacks">callbacks</a> to respond to state changes and errors.</li> 195 * 196 * <li>When a MediaPlayer2 object is no longer being used, call {@link #close()} as soon as 197 * possible to release the resources used by the internal player engine associated with the 198 * MediaPlayer2. Failure to call {@link #close()} may cause subsequent instances of 199 * MediaPlayer2 objects to fallback to software implementations or fail altogether. 200 * You cannot use MediaPlayer2 201 * after you call {@link #close()}. There is no way to bring it back to any other state.</li> 202 * 203 * <li>The current playback position can be retrieved with a call to 204 * {@link #getCurrentPosition()}, 205 * which is helpful for applications such as a Music player that need to keep track of the playback 206 * progress.</li> 207 * 208 * <li>The playback position can be adjusted with a call to {@link #seekTo}. Although the 209 * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a 210 * while to finish, especially for audio/video being streamed. If you register an 211 * {@link EventCallback#onCallCompleted onCallCompleted} <a href="#Callbacks">callback</a>, 212 * the callback is 213 * called When the seek operation completes with {@link #CALL_COMPLETED_SEEK_TO}.</li> 214 * 215 * <li>You can call {@link #seekTo} from the <strong>Paused</strong> state. 216 * In this case, if you are playing a video stream and 217 * the requested position is valid one video frame is displayed.</li> 218 * 219 * </ul> 220 * 221 * <h3 id="InvalidStates">Invalid method calls</h3> 222 * 223 * <p>The only methods you safely call from the <strong>Error</strong> state are 224 * {@link #close}, 225 * {@link #reset}, 226 * {@link #notifyWhenCommandLabelReached}, 227 * {@link #clearPendingCommands}, 228 * {@link #registerEventCallback}, 229 * {@link #unregisterEventCallback} 230 * and {@link #getState}. 231 * Any other methods might throw an exception, return meaningless data, or invoke a 232 * {@link EventCallback#onCallCompleted onCallCompleted} with an error code.</p> 233 * 234 * <p>Most methods can be called from any non-Error state. They will either perform their work or 235 * silently have no effect. The following table lists the methods that will invoke a 236 * {@link EventCallback#onCallCompleted onCallCompleted} with an error code 237 * or throw an exception when they are called from the associated invalid states.</p> 238 * 239 * <table border="0" cellspacing="0" cellpadding="0"> 240 * <tr><th>Method Name</th> 241 * <th>Invalid States</th></tr> 242 * 243 * <tr><td>setDataSource</td> <td>{Prepared, Paused, Playing}</td></tr> 244 * <tr><td>prepare</td> <td>{Prepared, Paused, Playing}</td></tr> 245 * <tr><td>play</td> <td>{Idle}</td></tr> 246 * <tr><td>pause</td> <td>{Idle}</td></tr> 247 * <tr><td>seekTo</td> <td>{Idle}</td></tr> 248 * <tr><td>getCurrentPosition</td> <td>{Idle}</td></tr> 249 * <tr><td>getDuration</td> <td>{Idle}</td></tr> 250 * <tr><td>getBufferedPosition</td> <td>{Idle}</td></tr> 251 * <tr><td>getTrackInfo</td> <td>{Idle}</td></tr> 252 * <tr><td>getSelectedTrack</td> <td>{Idle}</td></tr> 253 * <tr><td>selectTrack</td> <td>{Idle}</td></tr> 254 * <tr><td>deselectTrack</td> <td>{Idle}</td></tr> 255 * </table> 256 * 257 * <h3 id="Permissions">Permissions</h3> 258 * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission 259 * when used with network-based content. 260 * 261 * <h3 id="Callbacks">Callbacks</h3> 262 * <p>Many errors do not result in a transition to the <strong>Error</strong> state. 263 * It is good programming practice to register callback listeners using 264 * {@link #registerEventCallback}. 265 * You can receive a callback at any time and from any state.</p> 266 * 267 * <p>If it's important for your app to respond to state changes (for instance, to update the 268 * controls on a transport UI), you should register an 269 * {@link EventCallback#onCallCompleted onCallCompleted} and 270 * detect state change commands by testing the <code>what</code> parameter for a callback from one 271 * of the state transition methods: {@link #CALL_COMPLETED_PREPARE}, {@link #CALL_COMPLETED_PLAY}, 272 * and {@link #CALL_COMPLETED_PAUSE}. 273 * Then check the <code>status</code> parameter. The value {@link #CALL_STATUS_NO_ERROR} indicates a 274 * successful transition. Any other value will be an error. Call {@link #getState()} to 275 * determine the current state. </p> 276 * 277 * @hide 278 */ 279 public class MediaPlayer2 implements AutoCloseable, AudioRouting { 280 static { 281 System.loadLibrary("media2_jni"); native_init()282 native_init(); 283 } 284 native_init()285 private static native void native_init(); 286 287 private static final int NEXT_SOURCE_STATE_ERROR = -1; 288 private static final int NEXT_SOURCE_STATE_INIT = 0; 289 private static final int NEXT_SOURCE_STATE_PREPARING = 1; 290 private static final int NEXT_SOURCE_STATE_PREPARED = 2; 291 292 private static final String TAG = "MediaPlayer2"; 293 294 private Context mContext; 295 296 private long mNativeContext; // accessed by native methods 297 private long mNativeSurfaceTexture; // accessed by native methods 298 private int mListenerContext; // accessed by native methods 299 private SurfaceHolder mSurfaceHolder; 300 private PowerManager.WakeLock mWakeLock = null; 301 private boolean mScreenOnWhilePlaying; 302 private boolean mStayAwake; 303 304 private final Object mSrcLock = new Object(); 305 //--- guarded by |mSrcLock| start 306 private SourceInfo mCurrentSourceInfo; 307 private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>(); 308 //--- guarded by |mSrcLock| end 309 private final AtomicLong mSrcIdGenerator = new AtomicLong(0); 310 311 private volatile float mVolume = 1.0f; 312 private Size mVideoSize = new Size(0, 0); 313 314 private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool(); 315 316 // Creating a dummy audio track, used for keeping session id alive 317 private final Object mSessionIdLock = new Object(); 318 @GuardedBy("mSessionIdLock") 319 private AudioTrack mDummyAudioTrack; 320 321 private HandlerThread mHandlerThread; 322 private final TaskHandler mTaskHandler; 323 private final Object mTaskLock = new Object(); 324 @GuardedBy("mTaskLock") 325 private final List<Task> mPendingTasks = new LinkedList<>(); 326 @GuardedBy("mTaskLock") 327 private Task mCurrentTask; 328 private final AtomicLong mTaskIdGenerator = new AtomicLong(0); 329 330 @GuardedBy("mTaskLock") 331 boolean mIsPreviousCommandSeekTo = false; 332 // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo| 333 // is true, and they are accessed on |mHandlerThread| only. 334 long mPreviousSeekPos = -1; 335 int mPreviousSeekMode = SEEK_PREVIOUS_SYNC; 336 337 @GuardedBy("this") 338 private boolean mReleased; 339 340 private final CloseGuard mGuard = CloseGuard.get(); 341 342 /** 343 * Default constructor. 344 * <p>When done with the MediaPlayer2, you should call {@link #close()}, 345 * to free the resources. If not released, too many MediaPlayer2 instances may 346 * result in an exception.</p> 347 */ MediaPlayer2(@onNull Context context)348 public MediaPlayer2(@NonNull Context context) { 349 mGuard.open("close"); 350 351 mContext = context; 352 mHandlerThread = new HandlerThread("MediaPlayer2TaskThread"); 353 mHandlerThread.start(); 354 Looper looper = mHandlerThread.getLooper(); 355 mTaskHandler = new TaskHandler(this, looper); 356 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 357 int sessionId = am.generateAudioSessionId(); 358 keepAudioSessionIdAlive(sessionId); 359 360 /* Native setup requires a weak reference to our object. 361 * It's easier to create it here than in C++. 362 */ 363 native_setup(sessionId, new WeakReference<MediaPlayer2>(this)); 364 } 365 native_setup(int sessionId, Object mediaplayer2This)366 private native void native_setup(int sessionId, Object mediaplayer2This); 367 368 /** 369 * Releases the resources held by this {@code MediaPlayer2} object. 370 * 371 * It is considered good practice to call this method when you're 372 * done using the MediaPlayer2. In particular, whenever an Activity 373 * of an application is paused (its onPause() method is called), 374 * or stopped (its onStop() method is called), this method should be 375 * invoked to release the MediaPlayer2 object, unless the application 376 * has a special need to keep the object around. In addition to 377 * unnecessary resources (such as memory and instances of codecs) 378 * being held, failure to call this method immediately if a 379 * MediaPlayer2 object is no longer needed may also lead to 380 * continuous battery consumption for mobile devices, and playback 381 * failure for other applications if no multiple instances of the 382 * same codec are supported on a device. Even if multiple instances 383 * of the same codec are supported, some performance degradation 384 * may be expected when unnecessary multiple instances are used 385 * at the same time. 386 * 387 * {@code close()} may be safely called after a prior {@code close()}. 388 * This class implements the Java {@code AutoCloseable} interface and 389 * may be used with try-with-resources. 390 */ 391 // This is a synchronous call. 392 @Override close()393 public void close() { 394 synchronized (mGuard) { 395 mGuard.close(); 396 } 397 release(); 398 } 399 release()400 private synchronized void release() { 401 if (mReleased) { 402 return; 403 } 404 stayAwake(false); 405 updateSurfaceScreenOn(); 406 synchronized (mEventCbLock) { 407 mEventCallbackRecords.clear(); 408 } 409 if (mHandlerThread != null) { 410 mHandlerThread.quitSafely(); 411 mHandlerThread = null; 412 } 413 414 clearSourceInfos(); 415 416 // Modular DRM clean up 417 synchronized (mDrmEventCallbackLock) { 418 mDrmEventCallback = null; 419 } 420 clearMediaDrmObjects(); 421 422 native_release(); 423 424 synchronized (mSessionIdLock) { 425 mDummyAudioTrack.release(); 426 } 427 428 mReleased = true; 429 } 430 clearMediaDrmObjects()431 void clearMediaDrmObjects() { 432 Collection<MediaDrm> drmObjs = mDrmObjs.values(); 433 synchronized (mDrmObjs) { 434 for (MediaDrm drmObj : drmObjs) { 435 drmObj.close(); 436 } 437 mDrmObjs.clear(); 438 } 439 } 440 native_release()441 private native void native_release(); 442 443 // Have to declare protected for finalize() since it is protected 444 // in the base class Object. 445 @Override finalize()446 protected void finalize() throws Throwable { 447 if (mGuard != null) { 448 mGuard.warnIfOpen(); 449 } 450 451 close(); 452 native_finalize(); 453 } 454 native_finalize()455 private native void native_finalize(); 456 457 /** 458 * Resets the MediaPlayer2 to its uninitialized state. After calling 459 * this method, you will have to initialize it again by setting the 460 * data source and calling prepare(). 461 */ 462 // This is a synchronous call. reset()463 public void reset() { 464 clearSourceInfos(); 465 clearMediaDrmObjects(); 466 467 stayAwake(false); 468 native_reset(); 469 470 AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 471 int sessionId = am.generateAudioSessionId(); 472 keepAudioSessionIdAlive(sessionId); 473 474 // make sure none of the listeners get called anymore 475 if (mTaskHandler != null) { 476 mTaskHandler.removeCallbacksAndMessages(null); 477 } 478 479 } 480 native_reset()481 private native void native_reset(); 482 483 /** 484 * Starts or resumes playback. If playback had previously been paused, 485 * playback will continue from where it was paused. If playback had 486 * reached end of stream and been paused, or never started before, 487 * playback will start at the beginning. 488 * 489 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 490 */ 491 // This is an asynchronous call. play()492 public @NonNull Object play() { 493 return addTask(new Task(CALL_COMPLETED_PLAY, false) { 494 @Override 495 void process() { 496 stayAwake(true); 497 native_start(); 498 } 499 }); 500 } 501 502 private native void native_start() throws IllegalStateException; 503 504 /** 505 * Prepares the player for playback, asynchronously. 506 * 507 * After setting the datasource and the display surface, you need to call prepare(). 508 * 509 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 510 */ 511 // This is an asynchronous call. 512 public @NonNull Object prepare() { 513 return addTask(new Task(CALL_COMPLETED_PREPARE, true) { 514 @Override 515 void process() { 516 native_prepare(); 517 } 518 }); 519 } 520 521 private native void native_prepare(); 522 523 /** 524 * Pauses playback. Call play() to resume. 525 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 526 */ 527 // This is an asynchronous call. 528 public @NonNull Object pause() { 529 return addTask(new Task(CALL_COMPLETED_PAUSE, false) { 530 @Override 531 void process() { 532 stayAwake(false); 533 534 native_pause(); 535 } 536 }); 537 } 538 539 private native void native_pause() throws IllegalStateException; 540 541 /** 542 * Tries to play next data source if applicable. 543 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 544 */ 545 // This is an asynchronous call. 546 public @NonNull Object skipToNext() { 547 return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { 548 @Override 549 void process() { 550 if (getState() == PLAYER_STATE_PLAYING) { 551 native_pause(); 552 } 553 playNextDataSource(); 554 } 555 }); 556 } 557 558 /** 559 * Gets the current playback position. 560 * 561 * @return the current position in milliseconds 562 */ 563 public native long getCurrentPosition(); 564 565 /** 566 * Gets the duration of the current data source. 567 * Same as {@link #getDuration(DataSourceDesc)} with 568 * {@code dsd = getCurrentDataSource()}. 569 * 570 * @return the duration in milliseconds, if no duration is available 571 * (for example, if streaming live content), -1 is returned. 572 * @throws NullPointerException if current data source is null 573 */ 574 public long getDuration() { 575 return getDuration(getCurrentDataSource()); 576 } 577 578 /** 579 * Gets the duration of the dsd. 580 * 581 * @param dsd the descriptor of data source of which you want to get duration 582 * @return the duration in milliseconds, if no duration is available 583 * (for example, if streaming live content), -1 is returned. 584 * @throws NullPointerException if dsd is null 585 */ 586 public long getDuration(@NonNull DataSourceDesc dsd) { 587 if (dsd == null) { 588 throw new NullPointerException("non-null dsd is expected"); 589 } 590 SourceInfo sourceInfo = getSourceInfo(dsd); 591 if (sourceInfo == null) { 592 return -1; 593 } 594 595 return native_getDuration(sourceInfo.mId); 596 } 597 598 private native long native_getDuration(long srcId); 599 600 /** 601 * Gets the buffered media source position of current data source. 602 * Same as {@link #getBufferedPosition(DataSourceDesc)} with 603 * {@code dsd = getCurrentDataSource()}. 604 * 605 * @return the current buffered media source position in milliseconds 606 * @throws NullPointerException if current data source is null 607 */ 608 public long getBufferedPosition() { 609 return getBufferedPosition(getCurrentDataSource()); 610 } 611 612 /** 613 * Gets the buffered media source position of given dsd. 614 * For example a buffering update of 8000 milliseconds when 5000 milliseconds of the content 615 * has already been played indicates that the next 3000 milliseconds of the 616 * content to play has been buffered. 617 * 618 * @param dsd the descriptor of data source of which you want to get buffered position 619 * @return the current buffered media source position in milliseconds 620 * @throws NullPointerException if dsd is null 621 */ 622 public long getBufferedPosition(@NonNull DataSourceDesc dsd) { 623 if (dsd == null) { 624 throw new NullPointerException("non-null dsd is expected"); 625 } 626 SourceInfo sourceInfo = getSourceInfo(dsd); 627 if (sourceInfo == null) { 628 return 0; 629 } 630 631 // Use cached buffered percent for now. 632 int bufferedPercentage = sourceInfo.mBufferedPercentage.get(); 633 634 long duration = getDuration(dsd); 635 if (duration < 0) { 636 duration = 0; 637 } 638 639 return duration * bufferedPercentage / 100; 640 } 641 642 /** 643 * MediaPlayer2 has not been prepared or just has been reset. 644 * In this state, MediaPlayer2 doesn't fetch data. 645 */ 646 public static final int PLAYER_STATE_IDLE = 1001; 647 648 /** 649 * MediaPlayer2 has been just prepared. 650 * In this state, MediaPlayer2 just fetches data from media source, 651 * but doesn't actively render data. 652 */ 653 public static final int PLAYER_STATE_PREPARED = 1002; 654 655 /** 656 * MediaPlayer2 is paused. 657 * In this state, MediaPlayer2 has allocated resources to construct playback 658 * pipeline, but it doesn't actively render data. 659 */ 660 public static final int PLAYER_STATE_PAUSED = 1003; 661 662 /** 663 * MediaPlayer2 is actively playing back data. 664 */ 665 public static final int PLAYER_STATE_PLAYING = 1004; 666 667 /** 668 * MediaPlayer2 has hit some fatal error and cannot continue playback. 669 */ 670 public static final int PLAYER_STATE_ERROR = 1005; 671 672 /** 673 * @hide 674 */ 675 @IntDef(flag = false, prefix = "MEDIAPLAYER2_STATE", value = { 676 PLAYER_STATE_IDLE, 677 PLAYER_STATE_PREPARED, 678 PLAYER_STATE_PAUSED, 679 PLAYER_STATE_PLAYING, 680 PLAYER_STATE_ERROR }) 681 @Retention(RetentionPolicy.SOURCE) 682 public @interface MediaPlayer2State {} 683 684 /** 685 * Gets the current player state. 686 * 687 * @return the current player state. 688 */ 689 public @MediaPlayer2State int getState() { 690 return native_getState(); 691 } 692 693 private native int native_getState(); 694 695 /** 696 * Sets the audio attributes for this MediaPlayer2. 697 * See {@link AudioAttributes} for how to build and configure an instance of this class. 698 * You must call this method before {@link #play()} and {@link #pause()} in order 699 * for the audio attributes to become effective thereafter. 700 * @param attributes a non-null set of audio attributes 701 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 702 */ 703 // This is an asynchronous call. 704 public @NonNull Object setAudioAttributes(@NonNull AudioAttributes attributes) { 705 return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) { 706 @Override 707 void process() { 708 if (attributes == null) { 709 final String msg = "Cannot set AudioAttributes to null"; 710 throw new IllegalArgumentException(msg); 711 } 712 native_setAudioAttributes(attributes); 713 } 714 }); 715 } 716 717 // return true if the parameter is set successfully, false otherwise 718 private native boolean native_setAudioAttributes(AudioAttributes audioAttributes); 719 720 /** 721 * Gets the audio attributes for this MediaPlayer2. 722 * @return attributes a set of audio attributes 723 */ 724 public @NonNull AudioAttributes getAudioAttributes() { 725 return native_getAudioAttributes(); 726 } 727 728 private native AudioAttributes native_getAudioAttributes(); 729 730 /** 731 * Sets the data source as described by a DataSourceDesc. 732 * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor} 733 * in the {@link FileDataSourceDesc} will be closed by the player. 734 * 735 * @param dsd the descriptor of data source you want to play 736 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 737 */ 738 // This is an asynchronous call. 739 public @NonNull Object setDataSource(@NonNull DataSourceDesc dsd) { 740 return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { 741 @Override 742 void process() throws IOException { 743 checkDataSourceDesc(dsd); 744 int state = getState(); 745 try { 746 if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) { 747 throw new IllegalStateException("called in wrong state " + state); 748 } 749 750 synchronized (mSrcLock) { 751 setCurrentSourceInfo_l(new SourceInfo(dsd)); 752 handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId); 753 } 754 } finally { 755 dsd.close(); 756 } 757 } 758 759 }); 760 } 761 762 /** 763 * Sets a single data source as described by a DataSourceDesc which will be played 764 * after current data source is finished. 765 * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor} 766 * in the {@link FileDataSourceDesc} will be closed by the player. 767 * 768 * @param dsd the descriptor of data source you want to play after current one 769 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 770 */ 771 // This is an asynchronous call. 772 public @NonNull Object setNextDataSource(@NonNull DataSourceDesc dsd) { 773 return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { 774 @Override 775 void process() { 776 checkDataSourceDesc(dsd); 777 synchronized (mSrcLock) { 778 clearNextSourceInfos_l(); 779 mNextSourceInfos.add(new SourceInfo(dsd)); 780 } 781 prepareNextDataSource(); 782 } 783 }); 784 } 785 786 /** 787 * Sets a list of data sources to be played sequentially after current data source is done. 788 * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor} 789 * in the {@link FileDataSourceDesc} will be closed by the player. 790 * 791 * @param dsds the list of data sources you want to play after current one 792 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 793 */ 794 // This is an asynchronous call. 795 public @NonNull Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) { 796 return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) { 797 @Override 798 void process() { 799 if (dsds == null || dsds.size() == 0) { 800 throw new IllegalArgumentException("data source list cannot be null or empty."); 801 } 802 boolean hasError = false; 803 for (DataSourceDesc dsd : dsds) { 804 if (dsd == null) { 805 hasError = true; 806 continue; 807 } 808 if (dsd instanceof FileDataSourceDesc) { 809 FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd; 810 if (fdsd.isPFDClosed()) { 811 hasError = true; 812 continue; 813 } 814 815 fdsd.incCount(); 816 } 817 } 818 if (hasError) { 819 for (DataSourceDesc dsd : dsds) { 820 if (dsd != null) { 821 dsd.close(); 822 } 823 } 824 throw new IllegalArgumentException("invalid data source list"); 825 } 826 827 synchronized (mSrcLock) { 828 clearNextSourceInfos_l(); 829 for (DataSourceDesc dsd : dsds) { 830 mNextSourceInfos.add(new SourceInfo(dsd)); 831 } 832 } 833 prepareNextDataSource(); 834 } 835 }); 836 } 837 838 // throws IllegalArgumentException if dsd is null or underline PFD of dsd has been closed. 839 private void checkDataSourceDesc(DataSourceDesc dsd) { 840 if (dsd == null) { 841 throw new IllegalArgumentException("dsd is expected to be non null"); 842 } 843 if (dsd instanceof FileDataSourceDesc) { 844 FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd; 845 if (fdsd.isPFDClosed()) { 846 throw new IllegalArgumentException("the underline FileDescriptor has been closed"); 847 } 848 fdsd.incCount(); 849 } 850 } 851 852 /** 853 * Removes all data sources pending to be played. 854 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 855 */ 856 // This is an asynchronous call. 857 public @NonNull Object clearNextDataSources() { 858 return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) { 859 @Override 860 void process() { 861 synchronized (mSrcLock) { 862 clearNextSourceInfos_l(); 863 } 864 } 865 }); 866 } 867 868 /** 869 * Gets the current data source as described by a DataSourceDesc. 870 * 871 * @return the current DataSourceDesc 872 */ 873 public @Nullable DataSourceDesc getCurrentDataSource() { 874 synchronized (mSrcLock) { 875 return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD; 876 } 877 } 878 879 private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId) 880 throws IOException { 881 Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null"); 882 883 if (dsd instanceof FileDataSourceDesc) { 884 FileDataSourceDesc fileDSD = (FileDataSourceDesc) dsd; 885 ParcelFileDescriptor pfd = fileDSD.getParcelFileDescriptor(); 886 if (pfd.getStatSize() == -1) { 887 // Underlying pipeline doesn't understand '-1' size. Create a wrapper for 888 // translation. 889 // TODO: Make native code handle '-1' size. 890 handleDataSource(isCurrent, 891 srcId, 892 new ProxyDataSourceCallback(pfd), 893 fileDSD.getStartPosition(), 894 fileDSD.getEndPosition()); 895 } else { 896 handleDataSource(isCurrent, 897 srcId, 898 pfd, 899 fileDSD.getOffset(), 900 fileDSD.getLength(), 901 fileDSD.getStartPosition(), 902 fileDSD.getEndPosition()); 903 } 904 } else if (dsd instanceof UriDataSourceDesc) { 905 UriDataSourceDesc uriDSD = (UriDataSourceDesc) dsd; 906 handleDataSource(isCurrent, 907 srcId, 908 mContext, 909 uriDSD.getUri(), 910 uriDSD.getHeaders(), 911 uriDSD.getCookies(), 912 uriDSD.getStartPosition(), 913 uriDSD.getEndPosition()); 914 } else { 915 throw new IllegalArgumentException("Unsupported DataSourceDesc. " + dsd.toString()); 916 } 917 } 918 919 /** 920 * To provide cookies for the subsequent HTTP requests, you can install your own default cookie 921 * handler and use other variants of setDataSource APIs instead. Alternatively, you can use 922 * this API to pass the cookies as a list of HttpCookie. If the app has not installed 923 * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with 924 * the provided cookies. If the app has installed its own handler already, this API requires the 925 * handler to be of CookieManager type such that the API can update the manager’s CookieStore. 926 * 927 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 928 * but that can be changed with key/value pairs through the headers parameter with 929 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 930 * disallow or allow cross domain redirection. 931 * 932 * @throws IllegalArgumentException if cookies are provided and the installed handler is not 933 * a CookieManager 934 * @throws IllegalStateException if it is called in an invalid state 935 * @throws NullPointerException if context or uri is null 936 * @throws IOException if uri has a file scheme and an I/O error occurs 937 */ 938 private void handleDataSource( 939 boolean isCurrent, long srcId, 940 @NonNull Context context, @NonNull Uri uri, 941 @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies, 942 long startPos, long endPos) 943 throws IOException { 944 // The context and URI usually belong to the calling user. Get a resolver for that user. 945 final ContentResolver resolver = context.getContentResolver(); 946 final String scheme = uri.getScheme(); 947 if (ContentResolver.SCHEME_FILE.equals(scheme)) { 948 handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos); 949 return; 950 } 951 952 final int ringToneType = RingtoneManager.getDefaultType(uri); 953 try { 954 AssetFileDescriptor afd; 955 // Try requested Uri locally first 956 if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) { 957 afd = RingtoneManager.openDefaultRingtoneUri(context, uri); 958 if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) { 959 return; 960 } 961 final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri( 962 context, ringToneType); 963 afd = resolver.openAssetFileDescriptor(actualUri, "r"); 964 } else { 965 afd = resolver.openAssetFileDescriptor(uri, "r"); 966 } 967 if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) { 968 return; 969 } 970 } catch (NullPointerException | SecurityException | IOException ex) { 971 Log.w(TAG, "Couldn't open " + uri == null ? "null uri" : uri.toSafeString(), ex); 972 // Fallback to media server 973 } 974 handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos); 975 } 976 977 private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd, 978 long startPos, long endPos) throws IOException { 979 try { 980 if (afd.getDeclaredLength() < 0) { 981 handleDataSource(isCurrent, 982 srcId, 983 ParcelFileDescriptor.dup(afd.getFileDescriptor()), 984 0, 985 DataSourceDesc.LONG_MAX, 986 startPos, 987 endPos); 988 } else { 989 handleDataSource(isCurrent, 990 srcId, 991 ParcelFileDescriptor.dup(afd.getFileDescriptor()), 992 afd.getStartOffset(), 993 afd.getDeclaredLength(), 994 startPos, 995 endPos); 996 } 997 return true; 998 } catch (NullPointerException | SecurityException | IOException ex) { 999 Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex); 1000 return false; 1001 } finally { 1002 if (afd != null) { 1003 afd.close(); 1004 } 1005 } 1006 } 1007 1008 private void handleDataSource( 1009 boolean isCurrent, long srcId, 1010 String path, Map<String, String> headers, List<HttpCookie> cookies, 1011 long startPos, long endPos) 1012 throws IOException { 1013 String[] keys = null; 1014 String[] values = null; 1015 1016 if (headers != null) { 1017 keys = new String[headers.size()]; 1018 values = new String[headers.size()]; 1019 1020 int i = 0; 1021 for (Map.Entry<String, String> entry: headers.entrySet()) { 1022 keys[i] = entry.getKey(); 1023 values[i] = entry.getValue(); 1024 ++i; 1025 } 1026 } 1027 handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos); 1028 } 1029 1030 private void handleDataSource(boolean isCurrent, long srcId, 1031 String path, String[] keys, String[] values, List<HttpCookie> cookies, 1032 long startPos, long endPos) 1033 throws IOException { 1034 final Uri uri = Uri.parse(path); 1035 final String scheme = uri.getScheme(); 1036 if ("file".equals(scheme)) { 1037 path = uri.getPath(); 1038 } else if (scheme != null) { 1039 // handle non-file sources 1040 Media2Utils.storeCookies(cookies); 1041 nativeHandleDataSourceUrl( 1042 isCurrent, 1043 srcId, 1044 Media2HTTPService.createHTTPService(path), 1045 path, 1046 keys, 1047 values, 1048 startPos, 1049 endPos); 1050 return; 1051 } 1052 1053 final File file = new File(path); 1054 if (file.exists()) { 1055 FileInputStream is = new FileInputStream(file); 1056 FileDescriptor fd = is.getFD(); 1057 handleDataSource(isCurrent, srcId, ParcelFileDescriptor.dup(fd), 1058 0, DataSourceDesc.LONG_MAX, startPos, endPos); 1059 is.close(); 1060 } else { 1061 throw new IOException("handleDataSource failed."); 1062 } 1063 } 1064 1065 private native void nativeHandleDataSourceUrl( 1066 boolean isCurrent, long srcId, 1067 Media2HTTPService httpService, String path, String[] keys, String[] values, 1068 long startPos, long endPos) 1069 throws IOException; 1070 1071 /** 1072 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 1073 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 1074 * to close the file descriptor. It is safe to do so as soon as this call returns. 1075 * 1076 * @throws IllegalStateException if it is called in an invalid state 1077 * @throws IllegalArgumentException if fd is not a valid FileDescriptor 1078 * @throws IOException if fd can not be read 1079 */ 1080 private void handleDataSource( 1081 boolean isCurrent, long srcId, 1082 ParcelFileDescriptor pfd, long offset, long length, 1083 long startPos, long endPos) throws IOException { 1084 nativeHandleDataSourceFD(isCurrent, srcId, pfd.getFileDescriptor(), offset, length, 1085 startPos, endPos); 1086 } 1087 1088 private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId, 1089 FileDescriptor fd, long offset, long length, 1090 long startPos, long endPos) throws IOException; 1091 1092 /** 1093 * @throws IllegalStateException if it is called in an invalid state 1094 * @throws IllegalArgumentException if dataSource is not a valid DataSourceCallback 1095 */ 1096 private void handleDataSource(boolean isCurrent, long srcId, DataSourceCallback dataSource, 1097 long startPos, long endPos) { 1098 nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos); 1099 } 1100 1101 private native void nativeHandleDataSourceCallback( 1102 boolean isCurrent, long srcId, DataSourceCallback dataSource, 1103 long startPos, long endPos); 1104 1105 // return true if there is a next data source, false otherwise. 1106 // This function should be always called on |mHandlerThread|. 1107 private boolean prepareNextDataSource() { 1108 HandlerThread handlerThread = mHandlerThread; 1109 if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) { 1110 Log.e(TAG, "prepareNextDataSource: called on wrong looper"); 1111 } 1112 1113 boolean hasNextDSD; 1114 int state = getState(); 1115 synchronized (mSrcLock) { 1116 hasNextDSD = !mNextSourceInfos.isEmpty(); 1117 if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) { 1118 // Current source has not been prepared yet. 1119 return hasNextDSD; 1120 } 1121 1122 SourceInfo nextSource = mNextSourceInfos.peek(); 1123 if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) { 1124 // There is no next source or it's in preparing or prepared state. 1125 return hasNextDSD; 1126 } 1127 1128 try { 1129 nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING; 1130 handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId); 1131 } catch (Exception e) { 1132 Message msg = mTaskHandler.obtainMessage( 1133 MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null); 1134 mTaskHandler.handleMessage(msg, nextSource.mId); 1135 1136 SourceInfo nextSourceInfo = mNextSourceInfos.poll(); 1137 if (nextSource != null) { 1138 nextSourceInfo.close(); 1139 } 1140 return prepareNextDataSource(); 1141 } 1142 } 1143 return hasNextDSD; 1144 } 1145 1146 // This function should be always called on |mHandlerThread|. 1147 private void playNextDataSource() { 1148 HandlerThread handlerThread = mHandlerThread; 1149 if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) { 1150 Log.e(TAG, "playNextDataSource: called on wrong looper"); 1151 } 1152 1153 boolean hasNextDSD = false; 1154 synchronized (mSrcLock) { 1155 if (!mNextSourceInfos.isEmpty()) { 1156 hasNextDSD = true; 1157 SourceInfo nextSourceInfo = mNextSourceInfos.peek(); 1158 if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) { 1159 // Switch to next source only when it has been prepared. 1160 setCurrentSourceInfo_l(mNextSourceInfos.poll()); 1161 1162 long srcId = mCurrentSourceInfo.mId; 1163 try { 1164 nativePlayNextDataSource(srcId); 1165 } catch (Exception e) { 1166 Message msg2 = mTaskHandler.obtainMessage( 1167 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 1168 mTaskHandler.handleMessage(msg2, srcId); 1169 // Keep |mNextSourcePlayPending| 1170 hasNextDSD = prepareNextDataSource(); 1171 } 1172 if (hasNextDSD) { 1173 stayAwake(true); 1174 1175 // Now a new current src is playing. 1176 // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source. 1177 } 1178 } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) { 1179 hasNextDSD = prepareNextDataSource(); 1180 } 1181 } 1182 } 1183 1184 if (!hasNextDSD) { 1185 sendEvent(new EventNotifier() { 1186 @Override 1187 public void notify(EventCallback callback) { 1188 callback.onInfo( 1189 MediaPlayer2.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0); 1190 } 1191 }); 1192 } 1193 } 1194 1195 private native void nativePlayNextDataSource(long srcId); 1196 1197 /** 1198 * Configures the player to loop on the current data source. 1199 * @param loop true if the current data source is meant to loop. 1200 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1201 */ 1202 // This is an asynchronous call. 1203 public @NonNull Object loopCurrent(boolean loop) { 1204 return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { 1205 @Override 1206 void process() { 1207 setLooping(loop); 1208 } 1209 }); 1210 } 1211 1212 private native void setLooping(boolean looping); 1213 1214 /** 1215 * Sets the volume of the audio of the media to play, expressed as a linear multiplier 1216 * on the audio samples. 1217 * Note that this volume is specific to the player, and is separate from stream volume 1218 * used across the platform.<br> 1219 * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified 1220 * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player. 1221 * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. 1222 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1223 */ 1224 // This is an asynchronous call. 1225 public @NonNull Object setPlayerVolume(float volume) { 1226 return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { 1227 @Override 1228 void process() { 1229 mVolume = volume; 1230 native_setVolume(volume); 1231 } 1232 }); 1233 } 1234 1235 private native void native_setVolume(float volume); 1236 1237 /** 1238 * Returns the current volume of this player. 1239 * Note that it does not take into account the associated stream volume. 1240 * @return the player volume. 1241 */ 1242 public float getPlayerVolume() { 1243 return mVolume; 1244 } 1245 1246 /** 1247 * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}. 1248 */ 1249 public float getMaxPlayerVolume() { 1250 return 1.0f; 1251 } 1252 1253 /** 1254 * Insert a task in the command queue to help the client to identify whether a batch 1255 * of commands has been finished. When this command is processed, a notification 1256 * {@link EventCallback#onCommandLabelReached onCommandLabelReached} will be fired with the 1257 * given {@code label}. 1258 * 1259 * @see EventCallback#onCommandLabelReached 1260 * 1261 * @param label An application specific Object used to help to identify the completeness 1262 * of a batch of commands. 1263 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1264 */ 1265 // This is an asynchronous call. 1266 public @NonNull Object notifyWhenCommandLabelReached(@NonNull Object label) { 1267 return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { 1268 @Override 1269 void process() { 1270 sendEvent(new EventNotifier() { 1271 @Override 1272 public void notify(EventCallback callback) { 1273 callback.onCommandLabelReached( 1274 MediaPlayer2.this, label); 1275 } 1276 }); 1277 } 1278 }); 1279 } 1280 1281 /** 1282 * Sets the {@link SurfaceHolder} to use for displaying the video 1283 * portion of the media. 1284 * 1285 * Either a surface holder or surface must be set if a display or video sink 1286 * is needed. Not calling this method or {@link #setSurface(Surface)} 1287 * when playing back a video will result in only the audio track being played. 1288 * A null surface holder or surface will result in only the audio track being 1289 * played. 1290 * 1291 * @param sh the SurfaceHolder to use for video display 1292 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1293 */ 1294 public @NonNull Object setDisplay(@Nullable SurfaceHolder sh) { 1295 return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) { 1296 @Override 1297 void process() { 1298 mSurfaceHolder = sh; 1299 Surface surface; 1300 if (sh != null) { 1301 surface = sh.getSurface(); 1302 } else { 1303 surface = null; 1304 } 1305 native_setVideoSurface(surface); 1306 updateSurfaceScreenOn(); 1307 } 1308 }); 1309 } 1310 1311 /** 1312 * Sets the {@link Surface} to be used as the sink for the video portion of 1313 * the media. Setting a 1314 * Surface will un-set any Surface or SurfaceHolder that was previously set. 1315 * A null surface will result in only the audio track being played. 1316 * 1317 * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps 1318 * returned from {@link SurfaceTexture#getTimestamp()} will have an 1319 * unspecified zero point. These timestamps cannot be directly compared 1320 * between different media sources, different instances of the same media 1321 * source, or multiple runs of the same program. The timestamp is normally 1322 * monotonically increasing and is unaffected by time-of-day adjustments, 1323 * but it is reset when the position is set. 1324 * 1325 * @param surface The {@link Surface} to be used for the video portion of 1326 * the media. 1327 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1328 */ 1329 // This is an asynchronous call. 1330 public @NonNull Object setSurface(@Nullable Surface surface) { 1331 return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) { 1332 @Override 1333 void process() { 1334 if (mScreenOnWhilePlaying && surface != null) { 1335 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); 1336 } 1337 mSurfaceHolder = null; 1338 native_setVideoSurface(surface); 1339 updateSurfaceScreenOn(); 1340 } 1341 }); 1342 } 1343 1344 private native void native_setVideoSurface(Surface surface); 1345 1346 /** 1347 * Set the low-level power management behavior for this MediaPlayer2. This 1348 * can be used when the MediaPlayer2 is not playing through a SurfaceHolder 1349 * set with {@link #setDisplay(SurfaceHolder)} and thus can use the 1350 * high-level {@link #setScreenOnWhilePlaying(boolean)} feature. 1351 * 1352 * <p>This function has the MediaPlayer2 access the low-level power manager 1353 * service to control the device's power usage while playing is occurring. 1354 * The parameter is a {@link android.os.PowerManager.WakeLock}. 1355 * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} 1356 * permission. 1357 * By default, no attempt is made to keep the device awake during playback. 1358 * 1359 * @param wakeLock the power wake lock used during playback. 1360 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1361 * @see android.os.PowerManager 1362 */ 1363 // This is an asynchronous call. 1364 public @NonNull Object setWakeLock(@NonNull PowerManager.WakeLock wakeLock) { 1365 return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) { 1366 @Override 1367 void process() { 1368 boolean wasHeld = false; 1369 1370 if (mWakeLock != null) { 1371 if (mWakeLock.isHeld()) { 1372 wasHeld = true; 1373 mWakeLock.release(); 1374 } 1375 } 1376 1377 mWakeLock = wakeLock; 1378 if (mWakeLock != null) { 1379 mWakeLock.setReferenceCounted(false); 1380 if (wasHeld) { 1381 mWakeLock.acquire(); 1382 } 1383 } 1384 } 1385 }); 1386 } 1387 1388 /** 1389 * Control whether we should use the attached SurfaceHolder to keep the 1390 * screen on while video playback is occurring. This is the preferred 1391 * method over {@link #setWakeLock} where possible, since it doesn't 1392 * require that the application have permission for low-level wake lock 1393 * access. 1394 * 1395 * @param screenOn Supply true to keep the screen on, false to allow it to turn off. 1396 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1397 */ 1398 // This is an asynchronous call. 1399 public @NonNull Object setScreenOnWhilePlaying(boolean screenOn) { 1400 return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) { 1401 @Override 1402 void process() { 1403 if (mScreenOnWhilePlaying != screenOn) { 1404 if (screenOn && mSurfaceHolder == null) { 1405 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective" 1406 + " without a SurfaceHolder"); 1407 } 1408 mScreenOnWhilePlaying = screenOn; 1409 updateSurfaceScreenOn(); 1410 } 1411 } 1412 }); 1413 } 1414 1415 private void stayAwake(boolean awake) { 1416 if (mWakeLock != null) { 1417 if (awake && !mWakeLock.isHeld()) { 1418 mWakeLock.acquire(); 1419 } else if (!awake && mWakeLock.isHeld()) { 1420 mWakeLock.release(); 1421 } 1422 } 1423 mStayAwake = awake; 1424 updateSurfaceScreenOn(); 1425 } 1426 1427 private void updateSurfaceScreenOn() { 1428 if (mSurfaceHolder != null) { 1429 mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); 1430 } 1431 } 1432 1433 /** 1434 * Cancels a pending command. 1435 * 1436 * @param token the command to be canceled. This is the returned Object when command is issued. 1437 * @return {@code false} if the task could not be cancelled; {@code true} otherwise. 1438 * @throws IllegalArgumentException if argument token is null. 1439 */ 1440 // This is a synchronous call. 1441 public boolean cancelCommand(@NonNull Object token) { 1442 if (token == null) { 1443 throw new IllegalArgumentException("command token should not be null"); 1444 } 1445 synchronized (mTaskLock) { 1446 return mPendingTasks.remove(token); 1447 } 1448 } 1449 1450 /** 1451 * Discards all pending commands. 1452 */ 1453 // This is a synchronous call. 1454 public void clearPendingCommands() { 1455 synchronized (mTaskLock) { 1456 mPendingTasks.clear(); 1457 } 1458 } 1459 1460 //-------------------------------------------------------------------------- 1461 // Explicit Routing 1462 //-------------------- 1463 private AudioDeviceInfo mPreferredDevice = null; 1464 1465 /** 1466 * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route 1467 * the output from this MediaPlayer2. 1468 * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source. 1469 * If deviceInfo is null, default routing is restored. 1470 * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and 1471 * does not correspond to a valid audio device. 1472 */ 1473 // This is a synchronous call. 1474 @Override 1475 public boolean setPreferredDevice(@Nullable AudioDeviceInfo deviceInfo) { 1476 boolean status = native_setPreferredDevice(deviceInfo); 1477 if (status) { 1478 synchronized (this) { 1479 mPreferredDevice = deviceInfo; 1480 } 1481 } 1482 return status; 1483 } 1484 1485 private native boolean native_setPreferredDevice(AudioDeviceInfo device); 1486 1487 /** 1488 * Returns the selected output specified by {@link #setPreferredDevice}. Note that this 1489 * is not guaranteed to correspond to the actual device being used for playback. 1490 */ 1491 @Override 1492 public @Nullable AudioDeviceInfo getPreferredDevice() { 1493 synchronized (this) { 1494 return mPreferredDevice; 1495 } 1496 } 1497 1498 /** 1499 * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2 1500 * Note: The query is only valid if the MediaPlayer2 is currently playing. 1501 * If the player is not playing, the returned device can be null or correspond to previously 1502 * selected device when the player was last active. 1503 */ 1504 @Override 1505 public @Nullable native AudioDeviceInfo getRoutedDevice(); 1506 1507 /** 1508 * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing 1509 * changes on this MediaPlayer2. 1510 * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive 1511 * notifications of rerouting events. 1512 * @param handler Specifies the {@link Handler} object for the thread on which to execute 1513 * the callback. If <code>null</code>, the handler on the main looper will be used. 1514 */ 1515 // This is a synchronous call. 1516 @Override 1517 public void addOnRoutingChangedListener(@NonNull AudioRouting.OnRoutingChangedListener listener, 1518 @Nullable Handler handler) { 1519 if (listener == null) { 1520 throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL"); 1521 } 1522 RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler); 1523 native_addDeviceCallback(routingDelegate); 1524 } 1525 1526 private native void native_addDeviceCallback(RoutingDelegate rd); 1527 1528 /** 1529 * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added 1530 * to receive rerouting notifications. 1531 * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface 1532 * to remove. 1533 */ 1534 // This is a synchronous call. 1535 @Override 1536 public void removeOnRoutingChangedListener( 1537 @NonNull AudioRouting.OnRoutingChangedListener listener) { 1538 if (listener == null) { 1539 throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL"); 1540 } 1541 native_removeDeviceCallback(listener); 1542 } 1543 1544 private native void native_removeDeviceCallback( 1545 AudioRouting.OnRoutingChangedListener listener); 1546 1547 /** 1548 * Returns the size of the video. 1549 * 1550 * @return the size of the video. The width and height of size could be 0 if there is no video, 1551 * or the size has not been determined yet. 1552 * The {@code EventCallback} can be registered via 1553 * {@link #registerEventCallback(Executor, EventCallback)} to provide a 1554 * notification {@code EventCallback.onVideoSizeChanged} when the size 1555 * is available. 1556 */ 1557 public @NonNull Size getVideoSize() { 1558 return mVideoSize; 1559 } 1560 1561 /** 1562 * Return Metrics data about the current player. 1563 * 1564 * @return a {@link PersistableBundle} containing the set of attributes and values 1565 * available for the media being handled by this instance of MediaPlayer2 1566 * The attributes are descibed in {@link MetricsConstants}. 1567 * 1568 * Additional vendor-specific fields may also be present in the return value. 1569 */ 1570 public @Nullable PersistableBundle getMetrics() { 1571 PersistableBundle bundle = native_getMetrics(); 1572 return bundle; 1573 } 1574 1575 private native PersistableBundle native_getMetrics(); 1576 1577 /** 1578 * Gets the current buffering management params used by the source component. 1579 * Calling it only after {@code setDataSource} has been called. 1580 * Each type of data source might have different set of default params. 1581 * 1582 * @return the current buffering management params used by the source component. 1583 * @throws IllegalStateException if the internal player engine has not been 1584 * initialized, or {@code setDataSource} has not been called. 1585 */ 1586 // TODO: make it public when ready 1587 @NonNull 1588 native BufferingParams getBufferingParams(); 1589 1590 /** 1591 * Sets buffering management params. 1592 * The object sets its internal BufferingParams to the input, except that the input is 1593 * invalid or not supported. 1594 * Call it only after {@code setDataSource} has been called. 1595 * The input is a hint to MediaPlayer2. 1596 * 1597 * @param params the buffering management params. 1598 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1599 */ 1600 // TODO: make it public when ready 1601 // This is an asynchronous call. 1602 @NonNull Object setBufferingParams(@NonNull BufferingParams params) { 1603 return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) { 1604 @Override 1605 void process() { 1606 Media2Utils.checkArgument(params != null, "the BufferingParams cannot be null"); 1607 native_setBufferingParams(params); 1608 } 1609 }); 1610 } 1611 1612 private native void native_setBufferingParams(@NonNull BufferingParams params); 1613 1614 /** 1615 * Sets playback rate using {@link PlaybackParams}. The object sets its internal 1616 * PlaybackParams to the input. This allows the object to resume at previous speed 1617 * when play() is called. Speed of zero is not allowed. Calling it does not change 1618 * the object state. 1619 * 1620 * @param params the playback params. 1621 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1622 */ 1623 // This is an asynchronous call. 1624 public @NonNull Object setPlaybackParams(@NonNull PlaybackParams params) { 1625 return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { 1626 @Override 1627 void process() { 1628 Media2Utils.checkArgument(params != null, "the PlaybackParams cannot be null"); 1629 native_setPlaybackParams(params); 1630 } 1631 }); 1632 } 1633 1634 private native void native_setPlaybackParams(@NonNull PlaybackParams params); 1635 1636 /** 1637 * Gets the playback params, containing the current playback rate. 1638 * 1639 * @return the playback params. 1640 * @throws IllegalStateException if the internal player engine has not been initialized. 1641 */ 1642 @NonNull 1643 public native PlaybackParams getPlaybackParams(); 1644 1645 /** 1646 * Sets A/V sync mode. 1647 * 1648 * @param params the A/V sync params to apply 1649 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1650 */ 1651 // This is an asynchronous call. 1652 public @NonNull Object setSyncParams(@NonNull SyncParams params) { 1653 return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { 1654 @Override 1655 void process() { 1656 Media2Utils.checkArgument(params != null, "the SyncParams cannot be null"); 1657 native_setSyncParams(params); 1658 } 1659 }); 1660 } 1661 1662 private native void native_setSyncParams(@NonNull SyncParams params); 1663 1664 /** 1665 * Gets the A/V sync mode. 1666 * 1667 * @return the A/V sync params 1668 * @throws IllegalStateException if the internal player engine has not been initialized. 1669 */ 1670 @NonNull 1671 public native SyncParams getSyncParams(); 1672 1673 /** 1674 * Moves the media to specified time position. 1675 * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}. 1676 * 1677 * @param msec the offset in milliseconds from the start to seek to 1678 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1679 */ 1680 // This is an asynchronous call. 1681 public @NonNull Object seekTo(long msec) { 1682 return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */); 1683 } 1684 1685 /** 1686 * Seek modes used in method seekTo(long, int) to move media position 1687 * to a specified location. 1688 * 1689 * Do not change these mode values without updating their counterparts 1690 * in include/media/IMediaSource.h! 1691 */ 1692 /** 1693 * This mode is used with {@link #seekTo(long, int)} to move media position to 1694 * a sync (or key) frame associated with a data source that is located 1695 * right before or at the given time. 1696 * 1697 * @see #seekTo(long, int) 1698 */ 1699 public static final int SEEK_PREVIOUS_SYNC = 0x00; 1700 /** 1701 * This mode is used with {@link #seekTo(long, int)} to move media position to 1702 * a sync (or key) frame associated with a data source that is located 1703 * right after or at the given time. 1704 * 1705 * @see #seekTo(long, int) 1706 */ 1707 public static final int SEEK_NEXT_SYNC = 0x01; 1708 /** 1709 * This mode is used with {@link #seekTo(long, int)} to move media position to 1710 * a sync (or key) frame associated with a data source that is located 1711 * closest to (in time) or at the given time. 1712 * 1713 * @see #seekTo(long, int) 1714 */ 1715 public static final int SEEK_CLOSEST_SYNC = 0x02; 1716 /** 1717 * This mode is used with {@link #seekTo(long, int)} to move media position to 1718 * a frame (not necessarily a key frame) associated with a data source that 1719 * is located closest to or at the given time. 1720 * 1721 * @see #seekTo(long, int) 1722 */ 1723 public static final int SEEK_CLOSEST = 0x03; 1724 1725 /** @hide */ 1726 @IntDef(flag = false, prefix = "SEEK", value = { 1727 SEEK_PREVIOUS_SYNC, 1728 SEEK_NEXT_SYNC, 1729 SEEK_CLOSEST_SYNC, 1730 SEEK_CLOSEST, 1731 }) 1732 @Retention(RetentionPolicy.SOURCE) 1733 public @interface SeekMode {} 1734 1735 /** 1736 * Moves the media to specified time position by considering the given mode. 1737 * <p> 1738 * When seekTo is finished, the user will be notified via 1739 * {@link EventCallback#onCallCompleted} with {@link #CALL_COMPLETED_SEEK_TO}. 1740 * There is at most one active seekTo processed at any time. If there is a to-be-completed 1741 * seekTo, new seekTo requests will be queued in such a way that only the last request 1742 * is kept. When current seekTo is completed, the queued request will be processed if 1743 * that request is different from just-finished seekTo operation, i.e., the requested 1744 * position or mode is different. 1745 * 1746 * @param msec the offset in milliseconds from the start to seek to. 1747 * When seeking to the given time position, there is no guarantee that the data source 1748 * has a frame located at the position. When this happens, a frame nearby will be rendered. 1749 * If msec is negative, time position zero will be used. 1750 * If msec is larger than duration, duration will be used. 1751 * @param mode the mode indicating where exactly to seek to. 1752 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1753 */ 1754 // This is an asynchronous call. 1755 public @NonNull Object seekTo(long msec, @SeekMode int mode) { 1756 return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) { 1757 @Override 1758 void process() { 1759 if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { 1760 final String msg = "Illegal seek mode: " + mode; 1761 throw new IllegalArgumentException(msg); 1762 } 1763 // TODO: pass long to native, instead of truncating here. 1764 long posMs = msec; 1765 if (posMs > Integer.MAX_VALUE) { 1766 Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to " 1767 + Integer.MAX_VALUE); 1768 posMs = Integer.MAX_VALUE; 1769 } else if (posMs < Integer.MIN_VALUE) { 1770 Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to " 1771 + Integer.MIN_VALUE); 1772 posMs = Integer.MIN_VALUE; 1773 } 1774 1775 synchronized (mTaskLock) { 1776 if (mIsPreviousCommandSeekTo 1777 && mPreviousSeekPos == posMs 1778 && mPreviousSeekMode == mode) { 1779 throw new CommandSkippedException( 1780 "same as previous seekTo"); 1781 } 1782 } 1783 1784 native_seekTo(posMs, mode); 1785 1786 synchronized (mTaskLock) { 1787 mIsPreviousCommandSeekTo = true; 1788 mPreviousSeekPos = posMs; 1789 mPreviousSeekMode = mode; 1790 } 1791 } 1792 }); 1793 } 1794 1795 private native void native_seekTo(long msec, int mode); 1796 1797 /** 1798 * Get current playback position as a {@link MediaTimestamp}. 1799 * <p> 1800 * The MediaTimestamp represents how the media time correlates to the system time in 1801 * a linear fashion using an anchor and a clock rate. During regular playback, the media 1802 * time moves fairly constantly (though the anchor frame may be rebased to a current 1803 * system time, the linear correlation stays steady). Therefore, this method does not 1804 * need to be called often. 1805 * <p> 1806 * To help users get current playback position, this method always anchors the timestamp 1807 * to the current {@link System#nanoTime system time}, so 1808 * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. 1809 * 1810 * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp 1811 * is available, e.g. because the media player has not been initialized. 1812 * 1813 * @see MediaTimestamp 1814 */ 1815 @Nullable 1816 public MediaTimestamp getTimestamp() { 1817 try { 1818 // TODO: get the timestamp from native side 1819 return new MediaTimestamp( 1820 getCurrentPosition() * 1000L, 1821 System.nanoTime(), 1822 getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f); 1823 } catch (IllegalStateException e) { 1824 return null; 1825 } 1826 } 1827 1828 /** 1829 * Checks whether the MediaPlayer2 is looping or non-looping. 1830 * 1831 * @return true if the MediaPlayer2 is currently looping, false otherwise 1832 */ 1833 // This is a synchronous call. 1834 public native boolean isLooping(); 1835 1836 /** 1837 * Sets the audio session ID. 1838 * 1839 * @param sessionId the audio session ID. 1840 * The audio session ID is a system wide unique identifier for the audio stream played by 1841 * this MediaPlayer2 instance. 1842 * The primary use of the audio session ID is to associate audio effects to a particular 1843 * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, 1844 * this effect will be applied only to the audio content of media players within the same 1845 * audio session and not to the output mix. 1846 * When created, a MediaPlayer2 instance automatically generates its own audio session ID. 1847 * However, it is possible to force this player to be part of an already existing audio session 1848 * by calling this method. 1849 * This method must be called when player is in {@link #PLAYER_STATE_IDLE} or 1850 * {@link #PLAYER_STATE_PREPARED} state in order to have sessionId take effect. 1851 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1852 */ 1853 // This is an asynchronous call. 1854 public @NonNull Object setAudioSessionId(int sessionId) { 1855 final AudioTrack dummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 1856 AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2, 1857 AudioTrack.MODE_STATIC, sessionId); 1858 return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) { 1859 @Override 1860 void process() { 1861 keepAudioSessionIdAlive(dummyAudioTrack); 1862 native_setAudioSessionId(sessionId); 1863 } 1864 }); 1865 } 1866 1867 private native void native_setAudioSessionId(int sessionId); 1868 1869 /** 1870 * Returns the audio session ID. 1871 * 1872 * @return the audio session ID. {@see #setAudioSessionId(int)} 1873 * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was 1874 * contructed. 1875 */ 1876 // This is a synchronous call. 1877 public native int getAudioSessionId(); 1878 1879 /** 1880 * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation 1881 * effect which can be applied on any sound source that directs a certain amount of its 1882 * energy to this effect. This amount is defined by setAuxEffectSendLevel(). 1883 * See {@link #setAuxEffectSendLevel(float)}. 1884 * <p>After creating an auxiliary effect (e.g. 1885 * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with 1886 * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method 1887 * to attach the player to the effect. 1888 * <p>To detach the effect from the player, call this method with a null effect id. 1889 * <p>This method must be called after one of the overloaded <code> setDataSource </code> 1890 * methods. 1891 * @param effectId system wide unique id of the effect to attach 1892 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1893 */ 1894 // This is an asynchronous call. 1895 public @NonNull Object attachAuxEffect(int effectId) { 1896 return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) { 1897 @Override 1898 void process() { 1899 native_attachAuxEffect(effectId); 1900 } 1901 }); 1902 } 1903 1904 private native void native_attachAuxEffect(int effectId); 1905 1906 /** 1907 * Sets the send level of the player to the attached auxiliary effect. 1908 * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. 1909 * <p>By default the send level is 0, so even if an effect is attached to the player 1910 * this method must be called for the effect to be applied. 1911 * <p>Note that the passed level value is a raw scalar. UI controls should be scaled 1912 * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, 1913 * so an appropriate conversion from linear UI input x to level is: 1914 * x == 0 -> level = 0 1915 * 0 < x <= R -> level = 10^(72*(x-R)/20/R) 1916 * @param level send level scalar 1917 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 1918 */ 1919 // This is an asynchronous call. 1920 public @NonNull Object setAuxEffectSendLevel(float level) { 1921 return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { 1922 @Override 1923 void process() { 1924 native_setAuxEffectSendLevel(level); 1925 } 1926 }); 1927 } 1928 1929 private native void native_setAuxEffectSendLevel(float level); 1930 1931 private static native void native_stream_event_onTearDown( 1932 long nativeCallbackPtr, long userDataPtr); 1933 private static native void native_stream_event_onStreamPresentationEnd( 1934 long nativeCallbackPtr, long userDataPtr); 1935 private static native void native_stream_event_onStreamDataRequest( 1936 long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr); 1937 1938 /* Do not change these values (starting with INVOKE_ID) without updating 1939 * their counterparts in include/media/mediaplayer2.h! 1940 */ 1941 private static final int INVOKE_ID_GET_TRACK_INFO = 1; 1942 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; 1943 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; 1944 private static final int INVOKE_ID_SELECT_TRACK = 4; 1945 private static final int INVOKE_ID_DESELECT_TRACK = 5; 1946 private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; 1947 1948 /** 1949 * Invoke a generic method on the native player using opaque protocol 1950 * buffer message for the request and reply. Both payloads' format is a 1951 * convention between the java caller and the native player. 1952 * 1953 * @param msg PlayerMessage for the extension. 1954 * 1955 * @return PlayerMessage with the data returned by the 1956 * native player. 1957 */ 1958 private PlayerMessage invoke(PlayerMessage msg) { 1959 byte[] ret = native_invoke(msg.toByteArray()); 1960 if (ret == null) { 1961 return null; 1962 } 1963 try { 1964 return PlayerMessage.parseFrom(ret); 1965 } catch (InvalidProtocolBufferException e) { 1966 return null; 1967 } 1968 } 1969 1970 private native byte[] native_invoke(byte[] request); 1971 1972 /** 1973 * @hide 1974 */ 1975 @IntDef(flag = false, prefix = "MEDIA_TRACK_TYPE", value = { 1976 TrackInfo.MEDIA_TRACK_TYPE_VIDEO, 1977 TrackInfo.MEDIA_TRACK_TYPE_AUDIO, 1978 TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE 1979 }) 1980 @Retention(RetentionPolicy.SOURCE) 1981 public @interface TrackType {} 1982 1983 /** 1984 * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. 1985 * 1986 * @see MediaPlayer2#getTrackInfo 1987 */ 1988 public static class TrackInfo { 1989 /** 1990 * Gets the track type. 1991 * @return TrackType which indicates if the track is video, audio, timed text. 1992 */ 1993 public int getTrackType() { 1994 return mTrackType; 1995 } 1996 1997 /** 1998 * Gets the language code of the track. 1999 * @return a language code in either way of ISO-639-1 or ISO-639-2. 2000 * When the language is unknown or could not be determined, 2001 * ISO-639-2 language code, "und", is returned. 2002 */ 2003 public @NonNull String getLanguage() { 2004 String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); 2005 return language == null ? "und" : language; 2006 } 2007 2008 /** 2009 * Gets the {@link MediaFormat} of the track. If the format is 2010 * unknown or could not be determined, null is returned. 2011 */ 2012 public @Nullable MediaFormat getFormat() { 2013 if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT 2014 || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 2015 return mFormat; 2016 } 2017 return null; 2018 } 2019 2020 public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; 2021 public static final int MEDIA_TRACK_TYPE_VIDEO = 1; 2022 public static final int MEDIA_TRACK_TYPE_AUDIO = 2; 2023 2024 /** @hide */ 2025 public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; 2026 2027 public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; 2028 public static final int MEDIA_TRACK_TYPE_METADATA = 5; 2029 2030 final int mId; 2031 final int mTrackType; 2032 final MediaFormat mFormat; 2033 2034 static TrackInfo create(int idx, Iterator<Value> in) { 2035 int trackType = in.next().getInt32Value(); 2036 // TODO: build the full MediaFormat; currently we are using createSubtitleFormat 2037 // even for audio/video tracks, meaning we only set the mime and language. 2038 String mime = in.next().getStringValue(); 2039 String language = in.next().getStringValue(); 2040 MediaFormat format = MediaFormat.createSubtitleFormat(mime, language); 2041 2042 if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { 2043 format.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value()); 2044 format.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value()); 2045 format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value()); 2046 } 2047 return new TrackInfo(idx, trackType, format); 2048 } 2049 2050 /** @hide */ 2051 TrackInfo(int id, int type, MediaFormat format) { 2052 mId = id; 2053 mTrackType = type; 2054 mFormat = format; 2055 } 2056 2057 @Override 2058 public String toString() { 2059 StringBuilder out = new StringBuilder(128); 2060 out.append(getClass().getName()); 2061 out.append('{'); 2062 switch (mTrackType) { 2063 case MEDIA_TRACK_TYPE_VIDEO: 2064 out.append("VIDEO"); 2065 break; 2066 case MEDIA_TRACK_TYPE_AUDIO: 2067 out.append("AUDIO"); 2068 break; 2069 case MEDIA_TRACK_TYPE_TIMEDTEXT: 2070 out.append("TIMEDTEXT"); 2071 break; 2072 case MEDIA_TRACK_TYPE_SUBTITLE: 2073 out.append("SUBTITLE"); 2074 break; 2075 default: 2076 out.append("UNKNOWN"); 2077 break; 2078 } 2079 out.append(", " + mFormat.toString()); 2080 out.append("}"); 2081 return out.toString(); 2082 } 2083 }; 2084 2085 /** 2086 * Returns a List of track information of current data source. 2087 * Same as {@link #getTrackInfo(DataSourceDesc)} with 2088 * {@code dsd = getCurrentDataSource()}. 2089 * 2090 * @return List of track info. The total number of tracks is the array length. 2091 * Must be called again if an external timed text source has been added after 2092 * addTimedTextSource method is called. 2093 * @throws IllegalStateException if it is called in an invalid state. 2094 * @throws NullPointerException if current data source is null 2095 */ 2096 public @NonNull List<TrackInfo> getTrackInfo() { 2097 return getTrackInfo(getCurrentDataSource()); 2098 } 2099 2100 /** 2101 * Returns a List of track information. 2102 * 2103 * @param dsd the descriptor of data source of which you want to get track info 2104 * @return List of track info. The total number of tracks is the array length. 2105 * Must be called again if an external timed text source has been added after 2106 * addTimedTextSource method is called. 2107 * @throws IllegalStateException if it is called in an invalid state. 2108 * @throws NullPointerException if dsd is null 2109 */ 2110 public @NonNull List<TrackInfo> getTrackInfo(@NonNull DataSourceDesc dsd) { 2111 if (dsd == null) { 2112 throw new NullPointerException("non-null dsd is expected"); 2113 } 2114 SourceInfo sourceInfo = getSourceInfo(dsd); 2115 if (sourceInfo == null) { 2116 return new ArrayList<TrackInfo>(0); 2117 } 2118 2119 TrackInfo[] trackInfo = getInbandTrackInfo(sourceInfo); 2120 return (trackInfo != null ? Arrays.asList(trackInfo) : new ArrayList<TrackInfo>(0)); 2121 } 2122 2123 private TrackInfo[] getInbandTrackInfo(SourceInfo sourceInfo) throws IllegalStateException { 2124 PlayerMessage request = PlayerMessage.newBuilder() 2125 .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO)) 2126 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) 2127 .build(); 2128 PlayerMessage response = invoke(request); 2129 if (response == null) { 2130 return null; 2131 } 2132 Iterator<Value> in = response.getValuesList().iterator(); 2133 int size = in.next().getInt32Value(); 2134 if (size == 0) { 2135 return null; 2136 } 2137 TrackInfo[] trackInfo = new TrackInfo[size]; 2138 for (int i = 0; i < size; ++i) { 2139 trackInfo[i] = TrackInfo.create(i, in); 2140 } 2141 return trackInfo; 2142 } 2143 2144 /** 2145 * Returns the index of the audio, video, or subtitle track currently selected for playback. 2146 * The return value is an index into the array returned by {@link #getTrackInfo}, and can 2147 * be used in calls to {@link #selectTrack(TrackInfo)} or {@link #deselectTrack(TrackInfo)}. 2148 * Same as {@link #getSelectedTrack(DataSourceDesc, int)} with 2149 * {@code dsd = getCurrentDataSource()}. 2150 * 2151 * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, 2152 * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or 2153 * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} 2154 * @return metadata corresponding to the audio, video, or subtitle track currently selected for 2155 * playback; {@code null} is returned when there is no selected track for {@code trackType} or 2156 * when {@code trackType} is not one of audio, video, or subtitle. 2157 * @throws IllegalStateException if called after {@link #close()} 2158 * @throws NullPointerException if current data source is null 2159 * 2160 * @see #getTrackInfo() 2161 * @see #selectTrack(TrackInfo) 2162 * @see #deselectTrack(TrackInfo) 2163 */ 2164 @Nullable 2165 public TrackInfo getSelectedTrack(@TrackType int trackType) { 2166 return getSelectedTrack(getCurrentDataSource(), trackType); 2167 } 2168 2169 /** 2170 * Returns the index of the audio, video, or subtitle track currently selected for playback. 2171 * The return value is an index into the array returned by {@link #getTrackInfo}, and can 2172 * be used in calls to {@link #selectTrack(DataSourceDesc, TrackInfo)} or 2173 * {@link #deselectTrack(DataSourceDesc, TrackInfo)}. 2174 * 2175 * @param dsd the descriptor of data source of which you want to get selected track 2176 * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, 2177 * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or 2178 * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} 2179 * @return metadata corresponding to the audio, video, or subtitle track currently selected for 2180 * playback; {@code null} is returned when there is no selected track for {@code trackType} or 2181 * when {@code trackType} is not one of audio, video, or subtitle. 2182 * @throws IllegalStateException if called after {@link #close()} 2183 * @throws NullPointerException if dsd is null 2184 * 2185 * @see #getTrackInfo(DataSourceDesc) 2186 * @see #selectTrack(DataSourceDesc, TrackInfo) 2187 * @see #deselectTrack(DataSourceDesc, TrackInfo) 2188 */ 2189 @Nullable 2190 public TrackInfo getSelectedTrack(@NonNull DataSourceDesc dsd, @TrackType int trackType) { 2191 if (dsd == null) { 2192 throw new NullPointerException("non-null dsd is expected"); 2193 } 2194 SourceInfo sourceInfo = getSourceInfo(dsd); 2195 if (sourceInfo == null) { 2196 return null; 2197 } 2198 2199 PlayerMessage request = PlayerMessage.newBuilder() 2200 .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK)) 2201 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) 2202 .addValues(Value.newBuilder().setInt32Value(trackType)) 2203 .build(); 2204 PlayerMessage response = invoke(request); 2205 if (response == null) { 2206 return null; 2207 } 2208 // TODO: return full TrackInfo data from native player instead of index 2209 final int idx = response.getValues(0).getInt32Value(); 2210 final List<TrackInfo> trackInfos = getTrackInfo(dsd); 2211 return trackInfos.isEmpty() ? null : trackInfos.get(idx); 2212 } 2213 2214 /** 2215 * Selects a track of current data source. 2216 * Same as {@link #selectTrack(DataSourceDesc, TrackInfo)} with 2217 * {@code dsd = getCurrentDataSource()}. 2218 * 2219 * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} 2220 * object can be obtained from {@link #getTrackInfo()}. 2221 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 2222 * 2223 * This is an asynchronous call. 2224 * 2225 * @see MediaPlayer2#getTrackInfo() 2226 */ 2227 @NonNull 2228 public Object selectTrack(@NonNull TrackInfo trackInfo) { 2229 return selectTrack(getCurrentDataSource(), trackInfo); 2230 } 2231 2232 /** 2233 * Selects a track. 2234 * <p> 2235 * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception. 2236 * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately. 2237 * If a MediaPlayer2 is not in Started state, it just marks the track to be played. 2238 * </p> 2239 * <p> 2240 * In any valid state, if it is called multiple times on the same type of track (ie. Video, 2241 * Audio, Timed Text), the most recent one will be chosen. 2242 * </p> 2243 * <p> 2244 * The first audio and video tracks are selected by default if available, even though 2245 * this method is not called. However, no timed text track will be selected until 2246 * this function is called. 2247 * </p> 2248 * <p> 2249 * Currently, only timed text tracks or audio tracks can be selected via this method. 2250 * In addition, the support for selecting an audio track at runtime is pretty limited 2251 * in that an audio track can only be selected in the <em>Prepared</em> state. 2252 * </p> 2253 * @param dsd the descriptor of data source of which you want to select track 2254 * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} 2255 * object can be obtained from {@link #getTrackInfo()}. 2256 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 2257 * 2258 * This is an asynchronous call. 2259 * 2260 * @see MediaPlayer2#getTrackInfo(DataSourceDesc) 2261 */ 2262 @NonNull 2263 public Object selectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) { 2264 return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { 2265 @Override 2266 void process() { 2267 selectOrDeselectTrack(dsd, trackInfo.mId, true /* select */); 2268 } 2269 }); 2270 } 2271 2272 /** 2273 * Deselect a track of current data source. 2274 * Same as {@link #deselectTrack(DataSourceDesc, TrackInfo)} with 2275 * {@code dsd = getCurrentDataSource()}. 2276 * 2277 * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} 2278 * object can be obtained from {@link #getTrackInfo()}. 2279 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 2280 * 2281 * This is an asynchronous call. 2282 * 2283 * @see MediaPlayer2#getTrackInfo() 2284 */ 2285 @NonNull 2286 public Object deselectTrack(@NonNull TrackInfo trackInfo) { 2287 return deselectTrack(getCurrentDataSource(), trackInfo); 2288 } 2289 2290 /** 2291 * Deselect a track. 2292 * <p> 2293 * Currently, the track must be a timed text track and no audio or video tracks can be 2294 * deselected. If the timed text track identified by index has not been 2295 * selected before, it throws an exception. 2296 * </p> 2297 * @param dsd the descriptor of data source of which you want to deselect track 2298 * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} 2299 * object can be obtained from {@link #getTrackInfo()}. 2300 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 2301 * 2302 * This is an asynchronous call. 2303 * 2304 * @see MediaPlayer2#getTrackInfo(DataSourceDesc) 2305 */ 2306 @NonNull 2307 public Object deselectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) { 2308 return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { 2309 @Override 2310 void process() { 2311 selectOrDeselectTrack(dsd, trackInfo.mId, false /* select */); 2312 } 2313 }); 2314 } 2315 2316 private void selectOrDeselectTrack(@NonNull DataSourceDesc dsd, int index, boolean select) { 2317 if (dsd == null) { 2318 throw new IllegalArgumentException("non-null dsd is expected"); 2319 } 2320 SourceInfo sourceInfo = getSourceInfo(dsd); 2321 if (sourceInfo == null) { 2322 return; 2323 } 2324 2325 PlayerMessage request = PlayerMessage.newBuilder() 2326 .addValues(Value.newBuilder().setInt32Value( 2327 select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK)) 2328 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) 2329 .addValues(Value.newBuilder().setInt32Value(index)) 2330 .build(); 2331 invoke(request); 2332 } 2333 2334 /* Do not change these values without updating their counterparts 2335 * in include/media/mediaplayer2.h! 2336 */ 2337 private static final int MEDIA_NOP = 0; // interface test message 2338 private static final int MEDIA_PREPARED = 1; 2339 private static final int MEDIA_PLAYBACK_COMPLETE = 2; 2340 private static final int MEDIA_BUFFERING_UPDATE = 3; 2341 private static final int MEDIA_SEEK_COMPLETE = 4; 2342 private static final int MEDIA_SET_VIDEO_SIZE = 5; 2343 private static final int MEDIA_STARTED = 6; 2344 private static final int MEDIA_PAUSED = 7; 2345 private static final int MEDIA_STOPPED = 8; 2346 private static final int MEDIA_SKIPPED = 9; 2347 private static final int MEDIA_DRM_PREPARED = 10; 2348 private static final int MEDIA_NOTIFY_TIME = 98; 2349 private static final int MEDIA_TIMED_TEXT = 99; 2350 private static final int MEDIA_ERROR = 100; 2351 private static final int MEDIA_INFO = 200; 2352 private static final int MEDIA_SUBTITLE_DATA = 201; 2353 private static final int MEDIA_META_DATA = 202; 2354 private static final int MEDIA_DRM_INFO = 210; 2355 2356 private class TaskHandler extends Handler { 2357 private MediaPlayer2 mMediaPlayer; 2358 2359 TaskHandler(MediaPlayer2 mp, Looper looper) { 2360 super(looper); 2361 mMediaPlayer = mp; 2362 } 2363 2364 @Override 2365 public void handleMessage(Message msg) { 2366 handleMessage(msg, 0); 2367 } 2368 2369 public void handleMessage(Message msg, long srcId) { 2370 if (mMediaPlayer.mNativeContext == 0) { 2371 Log.w(TAG, "mediaplayer2 went away with unhandled events"); 2372 return; 2373 } 2374 final int what = msg.arg1; 2375 final int extra = msg.arg2; 2376 2377 final SourceInfo sourceInfo = getSourceInfo(srcId); 2378 if (sourceInfo == null) { 2379 return; 2380 } 2381 final DataSourceDesc dsd = sourceInfo.mDSD; 2382 2383 switch(msg.what) { 2384 case MEDIA_PREPARED: 2385 case MEDIA_DRM_PREPARED: 2386 { 2387 sourceInfo.mPrepareBarrier--; 2388 if (sourceInfo.mPrepareBarrier > 0) { 2389 break; 2390 } else if (sourceInfo.mPrepareBarrier < 0) { 2391 Log.w(TAG, "duplicated (drm) prepared events"); 2392 break; 2393 } 2394 2395 if (dsd != null) { 2396 sendEvent(new EventNotifier() { 2397 @Override 2398 public void notify(EventCallback callback) { 2399 callback.onInfo( 2400 mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0); 2401 } 2402 }); 2403 } 2404 2405 synchronized (mSrcLock) { 2406 SourceInfo nextSourceInfo = mNextSourceInfos.peek(); 2407 Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId 2408 + ", curSrc=" + mCurrentSourceInfo 2409 + ", nextSrc=" + nextSourceInfo); 2410 2411 if (isCurrentSource(srcId)) { 2412 prepareNextDataSource(); 2413 } else if (isNextSource(srcId)) { 2414 nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED; 2415 if (nextSourceInfo.mPlayPendingAsNextSource) { 2416 playNextDataSource(); 2417 } 2418 } 2419 } 2420 2421 synchronized (mTaskLock) { 2422 if (mCurrentTask != null 2423 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE 2424 && mCurrentTask.mDSD == dsd 2425 && mCurrentTask.mNeedToWaitForEventToComplete) { 2426 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 2427 mCurrentTask = null; 2428 processPendingTask_l(); 2429 } 2430 } 2431 return; 2432 } 2433 2434 case MEDIA_DRM_INFO: 2435 { 2436 if (msg.obj == null) { 2437 Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); 2438 } else if (msg.obj instanceof byte[]) { 2439 // The PlayerMessage was parsed already in postEventFromNative 2440 2441 final DrmInfo drmInfo; 2442 synchronized (sourceInfo) { 2443 if (sourceInfo.mDrmInfo != null) { 2444 drmInfo = sourceInfo.mDrmInfo.makeCopy(); 2445 } else { 2446 drmInfo = null; 2447 } 2448 } 2449 2450 // notifying the client outside the lock 2451 DrmPreparationInfo drmPrepareInfo = null; 2452 if (drmInfo != null) { 2453 try { 2454 drmPrepareInfo = sendDrmEventWait( 2455 new DrmEventNotifier<DrmPreparationInfo>() { 2456 @Override 2457 public DrmPreparationInfo notifyWait( 2458 DrmEventCallback callback) { 2459 return callback.onDrmInfo(mMediaPlayer, dsd, 2460 drmInfo); 2461 } 2462 }); 2463 } catch (InterruptedException | ExecutionException 2464 | TimeoutException e) { 2465 Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e); 2466 } 2467 } 2468 if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) { 2469 sourceInfo.mPrepareBarrier++; 2470 final Task prepareDrmTask; 2471 prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID); 2472 mTaskHandler.post(new Runnable() { 2473 @Override 2474 public void run() { 2475 // Run as simple Runnable, not Task 2476 try { 2477 prepareDrmTask.process(); 2478 } catch (NoDrmSchemeException | IOException e) { 2479 final String errMsg; 2480 errMsg = "Unexpected Exception during prepareDrm"; 2481 throw new RuntimeException(errMsg, e); 2482 } 2483 } 2484 }); 2485 } else { 2486 Log.w(TAG, "No valid DrmPreparationInfo set"); 2487 } 2488 } else { 2489 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); 2490 } 2491 return; 2492 } 2493 2494 case MEDIA_PLAYBACK_COMPLETE: 2495 { 2496 if (isCurrentSource(srcId)) { 2497 sendEvent(new EventNotifier() { 2498 @Override 2499 public void notify(EventCallback callback) { 2500 callback.onInfo( 2501 mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0); 2502 } 2503 }); 2504 stayAwake(false); 2505 2506 synchronized (mSrcLock) { 2507 SourceInfo nextSourceInfo = mNextSourceInfos.peek(); 2508 if (nextSourceInfo != null) { 2509 nextSourceInfo.mPlayPendingAsNextSource = true; 2510 } 2511 Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId 2512 + ", curSrc=" + mCurrentSourceInfo 2513 + ", nextSrc=" + nextSourceInfo); 2514 } 2515 2516 playNextDataSource(); 2517 } 2518 2519 return; 2520 } 2521 2522 case MEDIA_STOPPED: 2523 case MEDIA_STARTED: 2524 case MEDIA_PAUSED: 2525 case MEDIA_SKIPPED: 2526 case MEDIA_NOTIFY_TIME: 2527 { 2528 // Do nothing. The client should have enough information with 2529 // {@link EventCallback#onMediaTimeDiscontinuity}. 2530 break; 2531 } 2532 2533 case MEDIA_BUFFERING_UPDATE: 2534 { 2535 final int percent = msg.arg1; 2536 sendEvent(new EventNotifier() { 2537 @Override 2538 public void notify(EventCallback callback) { 2539 callback.onInfo( 2540 mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent); 2541 } 2542 }); 2543 2544 SourceInfo src = getSourceInfo(srcId); 2545 if (src != null) { 2546 src.mBufferedPercentage.set(percent); 2547 } 2548 2549 return; 2550 } 2551 2552 case MEDIA_SEEK_COMPLETE: 2553 { 2554 synchronized (mTaskLock) { 2555 if (!mPendingTasks.isEmpty() 2556 && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO 2557 && getState() == PLAYER_STATE_PLAYING) { 2558 mIsPreviousCommandSeekTo = false; 2559 } 2560 2561 if (mCurrentTask != null 2562 && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO 2563 && mCurrentTask.mNeedToWaitForEventToComplete) { 2564 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 2565 mCurrentTask = null; 2566 processPendingTask_l(); 2567 } 2568 } 2569 return; 2570 } 2571 2572 case MEDIA_SET_VIDEO_SIZE: 2573 { 2574 final int width = msg.arg1; 2575 final int height = msg.arg2; 2576 2577 mVideoSize = new Size(width, height); 2578 sendEvent(new EventNotifier() { 2579 @Override 2580 public void notify(EventCallback callback) { 2581 callback.onVideoSizeChanged( 2582 mMediaPlayer, dsd, mVideoSize); 2583 } 2584 }); 2585 return; 2586 } 2587 2588 case MEDIA_ERROR: 2589 { 2590 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); 2591 sendEvent(new EventNotifier() { 2592 @Override 2593 public void notify(EventCallback callback) { 2594 callback.onError( 2595 mMediaPlayer, dsd, what, extra); 2596 } 2597 }); 2598 sendEvent(new EventNotifier() { 2599 @Override 2600 public void notify(EventCallback callback) { 2601 callback.onInfo( 2602 mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0); 2603 } 2604 }); 2605 stayAwake(false); 2606 return; 2607 } 2608 2609 case MEDIA_INFO: 2610 { 2611 switch (msg.arg1) { 2612 case MEDIA_INFO_VIDEO_TRACK_LAGGING: 2613 Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); 2614 break; 2615 } 2616 2617 sendEvent(new EventNotifier() { 2618 @Override 2619 public void notify(EventCallback callback) { 2620 callback.onInfo( 2621 mMediaPlayer, dsd, what, extra); 2622 } 2623 }); 2624 2625 if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) { 2626 if (isCurrentSource(srcId)) { 2627 prepareNextDataSource(); 2628 } 2629 } 2630 2631 // No real default action so far. 2632 return; 2633 } 2634 2635 case MEDIA_TIMED_TEXT: 2636 { 2637 final TimedText text; 2638 if (msg.obj instanceof byte[]) { 2639 PlayerMessage playerMsg; 2640 try { 2641 playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj); 2642 } catch (InvalidProtocolBufferException e) { 2643 Log.w(TAG, "Failed to parse timed text.", e); 2644 return; 2645 } 2646 text = TimedTextUtil.parsePlayerMessage(playerMsg); 2647 } else { 2648 text = null; 2649 } 2650 2651 sendEvent(new EventNotifier() { 2652 @Override 2653 public void notify(EventCallback callback) { 2654 callback.onTimedText( 2655 mMediaPlayer, dsd, text); 2656 } 2657 }); 2658 return; 2659 } 2660 2661 case MEDIA_SUBTITLE_DATA: 2662 { 2663 if (msg.obj instanceof byte[]) { 2664 PlayerMessage playerMsg; 2665 try { 2666 playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj); 2667 } catch (InvalidProtocolBufferException e) { 2668 Log.w(TAG, "Failed to parse subtitle data.", e); 2669 return; 2670 } 2671 Iterator<Value> in = playerMsg.getValuesList().iterator(); 2672 final int trackIndex = in.next().getInt32Value(); 2673 TrackInfo trackInfo = getTrackInfo(dsd).get(trackIndex); 2674 final long startTimeUs = in.next().getInt64Value(); 2675 final long durationTimeUs = in.next().getInt64Value(); 2676 final byte[] subData = in.next().getBytesValue().toByteArray(); 2677 SubtitleData data = new SubtitleData(trackInfo, 2678 startTimeUs, durationTimeUs, subData); 2679 sendEvent(new EventNotifier() { 2680 @Override 2681 public void notify(EventCallback callback) { 2682 callback.onSubtitleData( 2683 mMediaPlayer, dsd, data); 2684 } 2685 }); 2686 } 2687 return; 2688 } 2689 2690 case MEDIA_META_DATA: 2691 { 2692 final TimedMetaData data; 2693 if (msg.obj instanceof byte[]) { 2694 PlayerMessage playerMsg; 2695 try { 2696 playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj); 2697 } catch (InvalidProtocolBufferException e) { 2698 Log.w(TAG, "Failed to parse timed meta data.", e); 2699 return; 2700 } 2701 Iterator<Value> in = playerMsg.getValuesList().iterator(); 2702 data = new TimedMetaData( 2703 in.next().getInt64Value(), // timestampUs 2704 in.next().getBytesValue().toByteArray()); // metaData 2705 } else { 2706 data = null; 2707 } 2708 2709 sendEvent(new EventNotifier() { 2710 @Override 2711 public void notify(EventCallback callback) { 2712 callback.onTimedMetaDataAvailable( 2713 mMediaPlayer, dsd, data); 2714 } 2715 }); 2716 return; 2717 } 2718 2719 case MEDIA_NOP: // interface test message - ignore 2720 { 2721 break; 2722 } 2723 2724 default: 2725 { 2726 Log.e(TAG, "Unknown message type " + msg.what); 2727 return; 2728 } 2729 } 2730 } 2731 } 2732 2733 /* 2734 * Called from native code when an interesting event happens. This method 2735 * just uses the TaskHandler system to post the event back to the main app thread. 2736 * We use a weak reference to the original MediaPlayer2 object so that the native 2737 * code is safe from the object disappearing from underneath it. (This is 2738 * the cookie passed to native_setup().) 2739 */ 2740 private static void postEventFromNative(Object mediaplayer2Ref, long srcId, 2741 int what, int arg1, int arg2, byte[] obj) { 2742 final MediaPlayer2 mp = (MediaPlayer2) ((WeakReference) mediaplayer2Ref).get(); 2743 if (mp == null) { 2744 return; 2745 } 2746 2747 final SourceInfo sourceInfo = mp.getSourceInfo(srcId); 2748 switch (what) { 2749 case MEDIA_DRM_INFO: 2750 // We need to derive mDrmInfo before prepare() returns so processing it here 2751 // before the notification is sent to TaskHandler below. TaskHandler runs in the 2752 // notification looper so its handleMessage might process the event after prepare() 2753 // has returned. 2754 Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); 2755 if (obj != null && sourceInfo != null) { 2756 PlayerMessage playerMsg; 2757 try { 2758 playerMsg = PlayerMessage.parseFrom(obj); 2759 } catch (InvalidProtocolBufferException e) { 2760 Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj); 2761 break; 2762 } 2763 DrmInfo drmInfo = DrmInfo.create(playerMsg); 2764 synchronized (sourceInfo) { 2765 sourceInfo.mDrmInfo = drmInfo; 2766 } 2767 } else { 2768 Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo 2769 + " msg.obj of unexpected type " + obj); 2770 } 2771 break; 2772 2773 case MEDIA_PREPARED: 2774 // By this time, we've learned about DrmInfo's presence or absence. This is meant 2775 // mainly for prepare() use case. For prepare(), this still can run to a race 2776 // condition b/c MediaPlayerNative releases the prepare() lock before calling notify 2777 // so we also set mDrmInfoResolved in prepare(). 2778 if (sourceInfo != null) { 2779 synchronized (sourceInfo) { 2780 sourceInfo.mDrmInfoResolved = true; 2781 } 2782 } 2783 break; 2784 } 2785 2786 if (mp.mTaskHandler != null) { 2787 Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj); 2788 2789 mp.mTaskHandler.post(new Runnable() { 2790 @Override 2791 public void run() { 2792 mp.mTaskHandler.handleMessage(m, srcId); 2793 } 2794 }); 2795 } 2796 } 2797 2798 /** 2799 * Class encapsulating subtitle data, as received through the 2800 * {@link EventCallback#onSubtitleData} interface. 2801 * <p> 2802 * A {@link SubtitleData} object includes: 2803 * <ul> 2804 * <li> track metadadta in a {@link TrackInfo} object</li> 2805 * <li> the start time (in microseconds) of the data</li> 2806 * <li> the duration (in microseconds) of the data</li> 2807 * <li> the actual data.</li> 2808 * </ul> 2809 * The data is stored in a byte-array, and is encoded in one of the supported in-band 2810 * subtitle formats. The subtitle encoding is determined by the MIME type of the 2811 * {@link TrackInfo} of the subtitle track, one of 2812 * {@link MediaFormat#MIMETYPE_TEXT_CEA_608}, {@link MediaFormat#MIMETYPE_TEXT_CEA_708}, 2813 * {@link MediaFormat#MIMETYPE_TEXT_VTT}. 2814 */ 2815 public static final class SubtitleData { 2816 2817 private TrackInfo mTrackInfo; 2818 private long mStartTimeUs; 2819 private long mDurationUs; 2820 private byte[] mData; 2821 2822 private SubtitleData(TrackInfo trackInfo, long startTimeUs, long durationUs, byte[] data) { 2823 mTrackInfo = trackInfo; 2824 mStartTimeUs = startTimeUs; 2825 mDurationUs = durationUs; 2826 mData = (data != null ? data : new byte[0]); 2827 } 2828 2829 /** 2830 * @return metadata of track which contains this subtitle data 2831 */ 2832 @NonNull 2833 public TrackInfo getTrackInfo() { 2834 return mTrackInfo; 2835 } 2836 2837 /** 2838 * @return media time at which the subtitle should start to be displayed in microseconds 2839 */ 2840 public long getStartTimeUs() { 2841 return mStartTimeUs; 2842 } 2843 2844 /** 2845 * @return the duration in microsecond during which the subtitle should be displayed 2846 */ 2847 public long getDurationUs() { 2848 return mDurationUs; 2849 } 2850 2851 /** 2852 * Returns the encoded data for the subtitle content. 2853 * Encoding format depends on the subtitle type, refer to 2854 * <a href="https://en.wikipedia.org/wiki/CEA-708">CEA 708</a>, 2855 * <a href="https://en.wikipedia.org/wiki/EIA-608">CEA/EIA 608</a> and 2856 * <a href="https://www.w3.org/TR/webvtt1/">WebVTT</a>, defined by the MIME type 2857 * of the subtitle track. 2858 * @return the encoded subtitle data 2859 */ 2860 @NonNull 2861 public byte[] getData() { 2862 return mData; 2863 } 2864 } 2865 2866 /** 2867 * Interface definition for callbacks to be invoked when the player has the corresponding 2868 * events. 2869 */ 2870 public static class EventCallback { 2871 /** 2872 * Called to indicate the video size 2873 * 2874 * The video size (width and height) could be 0 if there was no video, 2875 * or the value was not determined yet. 2876 * 2877 * @param mp the MediaPlayer2 associated with this callback 2878 * @param dsd the DataSourceDesc of this data source 2879 * @param size the size of the video 2880 */ 2881 public void onVideoSizeChanged( 2882 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull Size size) { } 2883 2884 /** 2885 * Called to indicate an avaliable timed text 2886 * 2887 * @param mp the MediaPlayer2 associated with this callback 2888 * @param dsd the DataSourceDesc of this data source 2889 * @param text the timed text sample which contains the text 2890 * needed to be displayed and the display format. 2891 * @hide 2892 */ 2893 public void onTimedText( 2894 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull TimedText text) { } 2895 2896 /** 2897 * Called to indicate avaliable timed metadata 2898 * <p> 2899 * This method will be called as timed metadata is extracted from the media, 2900 * in the same order as it occurs in the media. The timing of this event is 2901 * not controlled by the associated timestamp. 2902 * <p> 2903 * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates 2904 * {@link TimedMetaData}. 2905 * 2906 * @see MediaPlayer2#selectTrack 2907 * @see MediaPlayer2.OnTimedMetaDataAvailableListener 2908 * @see TimedMetaData 2909 * 2910 * @param mp the MediaPlayer2 associated with this callback 2911 * @param dsd the DataSourceDesc of this data source 2912 * @param data the timed metadata sample associated with this event 2913 */ 2914 public void onTimedMetaDataAvailable( 2915 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 2916 @NonNull TimedMetaData data) { } 2917 2918 /** 2919 * Called to indicate an error. 2920 * 2921 * @param mp the MediaPlayer2 the error pertains to 2922 * @param dsd the DataSourceDesc of this data source 2923 * @param what the type of error that has occurred. 2924 * @param extra an extra code, specific to the error. Typically 2925 * implementation dependent. 2926 */ 2927 public void onError( 2928 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 2929 @MediaError int what, int extra) { } 2930 2931 /** 2932 * Called to indicate an info or a warning. 2933 * 2934 * @param mp the MediaPlayer2 the info pertains to. 2935 * @param dsd the DataSourceDesc of this data source 2936 * @param what the type of info or warning. 2937 * @param extra an extra code, specific to the info. Typically 2938 * implementation dependent. 2939 */ 2940 public void onInfo( 2941 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 2942 @MediaInfo int what, int extra) { } 2943 2944 /** 2945 * Called to acknowledge an API call. 2946 * 2947 * @param mp the MediaPlayer2 the call was made on. 2948 * @param dsd the DataSourceDesc of this data source 2949 * @param what the enum for the API call. 2950 * @param status the returned status code for the call. 2951 */ 2952 public void onCallCompleted( 2953 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @CallCompleted int what, 2954 @CallStatus int status) { } 2955 2956 /** 2957 * Called to indicate media clock has changed. 2958 * 2959 * @param mp the MediaPlayer2 the media time pertains to. 2960 * @param dsd the DataSourceDesc of this data source 2961 * @param timestamp the new media clock. 2962 */ 2963 public void onMediaTimeDiscontinuity( 2964 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 2965 @NonNull MediaTimestamp timestamp) { } 2966 2967 /** 2968 * Called to indicate {@link #notifyWhenCommandLabelReached(Object)} has been processed. 2969 * 2970 * @param mp the MediaPlayer2 {@link #notifyWhenCommandLabelReached(Object)} was called on. 2971 * @param label the application specific Object given by 2972 * {@link #notifyWhenCommandLabelReached(Object)}. 2973 */ 2974 public void onCommandLabelReached(@NonNull MediaPlayer2 mp, @NonNull Object label) { } 2975 2976 /** 2977 * Called when when a player subtitle track has new subtitle data available. 2978 * @param mp the player that reports the new subtitle data 2979 * @param dsd the DataSourceDesc of this data source 2980 * @param data the subtitle data 2981 */ 2982 public void onSubtitleData( 2983 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 2984 @NonNull SubtitleData data) { } 2985 } 2986 2987 private final Object mEventCbLock = new Object(); 2988 private ArrayList<Pair<Executor, EventCallback>> mEventCallbackRecords = 2989 new ArrayList<Pair<Executor, EventCallback>>(); 2990 2991 /** 2992 * Registers the callback to be invoked for various events covered by {@link EventCallback}. 2993 * 2994 * @param executor the executor through which the callback should be invoked 2995 * @param eventCallback the callback that will be run 2996 */ 2997 // This is a synchronous call. 2998 public void registerEventCallback(@NonNull @CallbackExecutor Executor executor, 2999 @NonNull EventCallback eventCallback) { 3000 if (eventCallback == null) { 3001 throw new IllegalArgumentException("Illegal null EventCallback"); 3002 } 3003 if (executor == null) { 3004 throw new IllegalArgumentException( 3005 "Illegal null Executor for the EventCallback"); 3006 } 3007 synchronized (mEventCbLock) { 3008 for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { 3009 if (cb.first == executor && cb.second == eventCallback) { 3010 Log.w(TAG, "The callback has been registered before."); 3011 return; 3012 } 3013 } 3014 mEventCallbackRecords.add(new Pair(executor, eventCallback)); 3015 } 3016 } 3017 3018 /** 3019 * Unregisters the {@link EventCallback}. 3020 * 3021 * @param eventCallback the callback to be unregistered 3022 */ 3023 // This is a synchronous call. 3024 public void unregisterEventCallback(@NonNull EventCallback eventCallback) { 3025 synchronized (mEventCbLock) { 3026 for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { 3027 if (cb.second == eventCallback) { 3028 mEventCallbackRecords.remove(cb); 3029 } 3030 } 3031 } 3032 } 3033 3034 private void sendEvent(final EventNotifier notifier) { 3035 synchronized (mEventCbLock) { 3036 try { 3037 for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { 3038 cb.first.execute(() -> notifier.notify(cb.second)); 3039 } 3040 } catch (RejectedExecutionException e) { 3041 // The executor has been shut down. 3042 Log.w(TAG, "The executor has been shut down. Ignoring event."); 3043 } 3044 } 3045 } 3046 3047 private void sendDrmEvent(final DrmEventNotifier notifier) { 3048 synchronized (mDrmEventCallbackLock) { 3049 try { 3050 Pair<Executor, DrmEventCallback> cb = mDrmEventCallback; 3051 if (cb != null) { 3052 cb.first.execute(() -> notifier.notify(cb.second)); 3053 } 3054 } catch (RejectedExecutionException e) { 3055 // The executor has been shut down. 3056 Log.w(TAG, "The executor has been shut down. Ignoring drm event."); 3057 } 3058 } 3059 } 3060 3061 private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier) 3062 throws InterruptedException, ExecutionException, TimeoutException { 3063 return sendDrmEventWait(notifier, 0); 3064 } 3065 3066 private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs) 3067 throws InterruptedException, ExecutionException, TimeoutException { 3068 synchronized (mDrmEventCallbackLock) { 3069 Pair<Executor, DrmEventCallback> cb = mDrmEventCallback; 3070 if (cb != null) { 3071 CompletableFuture<T> ret = new CompletableFuture<>(); 3072 cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second))); 3073 return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS); 3074 } 3075 } 3076 return null; 3077 } 3078 3079 private interface EventNotifier { 3080 void notify(EventCallback callback); 3081 } 3082 3083 private interface DrmEventNotifier<T> { 3084 default void notify(DrmEventCallback callback) { } 3085 default T notifyWait(DrmEventCallback callback) { 3086 return null; 3087 } 3088 } 3089 3090 /* Do not change these values without updating their counterparts 3091 * in include/media/MediaPlayer2Types.h! 3092 */ 3093 /** Unspecified media player error. 3094 * @see EventCallback#onError 3095 */ 3096 public static final int MEDIA_ERROR_UNKNOWN = 1; 3097 3098 /** 3099 * The video is streamed and its container is not valid for progressive 3100 * playback i.e the video's index (e.g moov atom) is not at the start of the 3101 * file. 3102 * @see EventCallback#onError 3103 */ 3104 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; 3105 3106 /** File or network related operation errors. */ 3107 public static final int MEDIA_ERROR_IO = -1004; 3108 /** Bitstream is not conforming to the related coding standard or file spec. */ 3109 public static final int MEDIA_ERROR_MALFORMED = -1007; 3110 /** Bitstream is conforming to the related coding standard or file spec, but 3111 * the media framework does not support the feature. */ 3112 public static final int MEDIA_ERROR_UNSUPPORTED = -1010; 3113 /** Some operation takes too long to complete, usually more than 3-5 seconds. */ 3114 public static final int MEDIA_ERROR_TIMED_OUT = -110; 3115 3116 /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in 3117 * system/core/include/utils/Errors.h 3118 * @see EventCallback#onError 3119 * @hide 3120 */ 3121 public static final int MEDIA_ERROR_SYSTEM = -2147483648; 3122 3123 /** 3124 * @hide 3125 */ 3126 @IntDef(flag = false, prefix = "MEDIA_ERROR", value = { 3127 MEDIA_ERROR_UNKNOWN, 3128 MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK, 3129 MEDIA_ERROR_IO, 3130 MEDIA_ERROR_MALFORMED, 3131 MEDIA_ERROR_UNSUPPORTED, 3132 MEDIA_ERROR_TIMED_OUT, 3133 MEDIA_ERROR_SYSTEM 3134 }) 3135 @Retention(RetentionPolicy.SOURCE) 3136 public @interface MediaError {} 3137 3138 /* Do not change these values without updating their counterparts 3139 * in include/media/MediaPlayer2Types.h! 3140 */ 3141 /** Unspecified media player info. 3142 * @see EventCallback#onInfo 3143 */ 3144 public static final int MEDIA_INFO_UNKNOWN = 1; 3145 3146 /** The player just started the playback of this datas source. 3147 * @see EventCallback#onInfo 3148 */ 3149 public static final int MEDIA_INFO_DATA_SOURCE_START = 2; 3150 3151 /** The player just pushed the very first video frame for rendering. 3152 * @see EventCallback#onInfo 3153 */ 3154 public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; 3155 3156 /** The player just rendered the very first audio sample. 3157 * @see EventCallback#onInfo 3158 */ 3159 public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; 3160 3161 /** The player just completed the playback of this data source. 3162 * @see EventCallback#onInfo 3163 */ 3164 public static final int MEDIA_INFO_DATA_SOURCE_END = 5; 3165 3166 /** The player just completed the playback of all data sources set by {@link #setDataSource}, 3167 * {@link #setNextDataSource} and {@link #setNextDataSources}. 3168 * @see EventCallback#onInfo 3169 */ 3170 public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6; 3171 3172 /** The player just completed an iteration of playback loop. This event is sent only when 3173 * looping is enabled by {@link #loopCurrent}. 3174 * @see EventCallback#onInfo 3175 */ 3176 public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7; 3177 3178 /** The player just prepared a data source. 3179 * @see EventCallback#onInfo 3180 */ 3181 public static final int MEDIA_INFO_PREPARED = 100; 3182 3183 /** The video is too complex for the decoder: it can't decode frames fast 3184 * enough. Possibly only the audio plays fine at this stage. 3185 * @see EventCallback#onInfo 3186 */ 3187 public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; 3188 3189 /** MediaPlayer2 is temporarily pausing playback internally in order to 3190 * buffer more data. 3191 * @see EventCallback#onInfo 3192 */ 3193 public static final int MEDIA_INFO_BUFFERING_START = 701; 3194 3195 /** MediaPlayer2 is resuming playback after filling buffers. 3196 * @see EventCallback#onInfo 3197 */ 3198 public static final int MEDIA_INFO_BUFFERING_END = 702; 3199 3200 /** Estimated network bandwidth information (kbps) is available; currently this event fires 3201 * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END} 3202 * when playing network files. 3203 * @see EventCallback#onInfo 3204 * @hide 3205 */ 3206 public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; 3207 3208 /** 3209 * Update status in buffering a media source received through progressive downloading. 3210 * The received buffering percentage indicates how much of the content has been buffered 3211 * or played. For example a buffering update of 80 percent when half the content 3212 * has already been played indicates that the next 30 percent of the 3213 * content to play has been buffered. 3214 * 3215 * The {@code extra} parameter in {@code EventCallback.onInfo} is the 3216 * percentage (0-100) of the content that has been buffered or played thus far. 3217 * @see EventCallback#onInfo 3218 */ 3219 public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; 3220 3221 /** Bad interleaving means that a media has been improperly interleaved or 3222 * not interleaved at all, e.g has all the video samples first then all the 3223 * audio ones. Video is playing but a lot of disk seeks may be happening. 3224 * @see EventCallback#onInfo 3225 */ 3226 public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; 3227 3228 /** The media cannot be seeked (e.g live stream) 3229 * @see EventCallback#onInfo 3230 */ 3231 public static final int MEDIA_INFO_NOT_SEEKABLE = 801; 3232 3233 /** A new set of metadata is available. 3234 * @see EventCallback#onInfo 3235 */ 3236 public static final int MEDIA_INFO_METADATA_UPDATE = 802; 3237 3238 /** Informs that audio is not playing. Note that playback of the video 3239 * is not interrupted. 3240 * @see EventCallback#onInfo 3241 */ 3242 public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; 3243 3244 /** Informs that video is not playing. Note that playback of the audio 3245 * is not interrupted. 3246 * @see EventCallback#onInfo 3247 */ 3248 public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; 3249 3250 /** Failed to handle timed text track properly. 3251 * @see EventCallback#onInfo 3252 * 3253 * {@hide} 3254 */ 3255 public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; 3256 3257 /** Subtitle track was not supported by the media framework. 3258 * @see EventCallback#onInfo 3259 */ 3260 public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; 3261 3262 /** Reading the subtitle track takes too long. 3263 * @see EventCallback#onInfo 3264 */ 3265 public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; 3266 3267 /** 3268 * @hide 3269 */ 3270 @IntDef(flag = false, prefix = "MEDIA_INFO", value = { 3271 MEDIA_INFO_UNKNOWN, 3272 MEDIA_INFO_DATA_SOURCE_START, 3273 MEDIA_INFO_VIDEO_RENDERING_START, 3274 MEDIA_INFO_AUDIO_RENDERING_START, 3275 MEDIA_INFO_DATA_SOURCE_END, 3276 MEDIA_INFO_DATA_SOURCE_LIST_END, 3277 MEDIA_INFO_PREPARED, 3278 MEDIA_INFO_VIDEO_TRACK_LAGGING, 3279 MEDIA_INFO_BUFFERING_START, 3280 MEDIA_INFO_BUFFERING_END, 3281 MEDIA_INFO_NETWORK_BANDWIDTH, 3282 MEDIA_INFO_BUFFERING_UPDATE, 3283 MEDIA_INFO_BAD_INTERLEAVING, 3284 MEDIA_INFO_NOT_SEEKABLE, 3285 MEDIA_INFO_METADATA_UPDATE, 3286 MEDIA_INFO_AUDIO_NOT_PLAYING, 3287 MEDIA_INFO_VIDEO_NOT_PLAYING, 3288 MEDIA_INFO_TIMED_TEXT_ERROR, 3289 MEDIA_INFO_UNSUPPORTED_SUBTITLE, 3290 MEDIA_INFO_SUBTITLE_TIMED_OUT 3291 }) 3292 @Retention(RetentionPolicy.SOURCE) 3293 public @interface MediaInfo {} 3294 3295 //-------------------------------------------------------------------------- 3296 /** The player just completed a call {@link #attachAuxEffect}. 3297 * @see EventCallback#onCallCompleted 3298 */ 3299 public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; 3300 3301 /** The player just completed a call {@link #deselectTrack}. 3302 * @see EventCallback#onCallCompleted 3303 */ 3304 public static final int CALL_COMPLETED_DESELECT_TRACK = 2; 3305 3306 /** The player just completed a call {@link #loopCurrent}. 3307 * @see EventCallback#onCallCompleted 3308 */ 3309 public static final int CALL_COMPLETED_LOOP_CURRENT = 3; 3310 3311 /** The player just completed a call {@link #pause}. 3312 * @see EventCallback#onCallCompleted 3313 */ 3314 public static final int CALL_COMPLETED_PAUSE = 4; 3315 3316 /** The player just completed a call {@link #play}. 3317 * @see EventCallback#onCallCompleted 3318 */ 3319 public static final int CALL_COMPLETED_PLAY = 5; 3320 3321 /** The player just completed a call {@link #prepare}. 3322 * @see EventCallback#onCallCompleted 3323 */ 3324 public static final int CALL_COMPLETED_PREPARE = 6; 3325 3326 /** The player just completed a call {@link #seekTo}. 3327 * @see EventCallback#onCallCompleted 3328 */ 3329 public static final int CALL_COMPLETED_SEEK_TO = 14; 3330 3331 /** The player just completed a call {@link #selectTrack}. 3332 * @see EventCallback#onCallCompleted 3333 */ 3334 public static final int CALL_COMPLETED_SELECT_TRACK = 15; 3335 3336 /** The player just completed a call {@link #setAudioAttributes}. 3337 * @see EventCallback#onCallCompleted 3338 */ 3339 public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; 3340 3341 /** The player just completed a call {@link #setAudioSessionId}. 3342 * @see EventCallback#onCallCompleted 3343 */ 3344 public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; 3345 3346 /** The player just completed a call {@link #setAuxEffectSendLevel}. 3347 * @see EventCallback#onCallCompleted 3348 */ 3349 public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; 3350 3351 /** The player just completed a call {@link #setDataSource}. 3352 * @see EventCallback#onCallCompleted 3353 */ 3354 public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; 3355 3356 /** The player just completed a call {@link #setNextDataSource}. 3357 * @see EventCallback#onCallCompleted 3358 */ 3359 public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; 3360 3361 /** The player just completed a call {@link #setNextDataSources}. 3362 * @see EventCallback#onCallCompleted 3363 */ 3364 public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; 3365 3366 /** The player just completed a call {@link #setPlaybackParams}. 3367 * @see EventCallback#onCallCompleted 3368 */ 3369 public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; 3370 3371 /** The player just completed a call {@link #setPlayerVolume}. 3372 * @see EventCallback#onCallCompleted 3373 */ 3374 public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; 3375 3376 /** The player just completed a call {@link #setSurface}. 3377 * @see EventCallback#onCallCompleted 3378 */ 3379 public static final int CALL_COMPLETED_SET_SURFACE = 27; 3380 3381 /** The player just completed a call {@link #setSyncParams}. 3382 * @see EventCallback#onCallCompleted 3383 */ 3384 public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28; 3385 3386 /** The player just completed a call {@link #skipToNext}. 3387 * @see EventCallback#onCallCompleted 3388 */ 3389 public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; 3390 3391 /** The player just completed a call {@link #clearNextDataSources}. 3392 * @see EventCallback#onCallCompleted 3393 */ 3394 public static final int CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES = 30; 3395 3396 /** The player just completed a call {@link #setBufferingParams}. 3397 * @see EventCallback#onCallCompleted 3398 * @hide 3399 */ 3400 public static final int CALL_COMPLETED_SET_BUFFERING_PARAMS = 31; 3401 3402 /** The player just completed a call {@link #setDisplay}. 3403 * @see EventCallback#onCallCompleted 3404 */ 3405 public static final int CALL_COMPLETED_SET_DISPLAY = 33; 3406 3407 /** The player just completed a call {@link #setWakeLock}. 3408 * @see EventCallback#onCallCompleted 3409 */ 3410 public static final int CALL_COMPLETED_SET_WAKE_LOCK = 34; 3411 3412 /** The player just completed a call {@link #setScreenOnWhilePlaying}. 3413 * @see EventCallback#onCallCompleted 3414 */ 3415 public static final int CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING = 35; 3416 3417 /** 3418 * The start of the methods which have separate call complete callback. 3419 * @hide 3420 */ 3421 public static final int SEPARATE_CALL_COMPLETED_CALLBACK_START = 1000; 3422 3423 /** The player just completed a call {@link #notifyWhenCommandLabelReached}. 3424 * @see EventCallback#onCommandLabelReached 3425 * @hide 3426 */ 3427 public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 3428 SEPARATE_CALL_COMPLETED_CALLBACK_START; 3429 3430 /** The player just completed a call {@link #prepareDrm}. 3431 * @see DrmEventCallback#onDrmPrepared 3432 * @hide 3433 */ 3434 public static final int CALL_COMPLETED_PREPARE_DRM = 3435 SEPARATE_CALL_COMPLETED_CALLBACK_START + 1; 3436 3437 /** 3438 * @hide 3439 */ 3440 @IntDef(flag = false, prefix = "CALL_COMPLETED", value = { 3441 CALL_COMPLETED_ATTACH_AUX_EFFECT, 3442 CALL_COMPLETED_DESELECT_TRACK, 3443 CALL_COMPLETED_LOOP_CURRENT, 3444 CALL_COMPLETED_PAUSE, 3445 CALL_COMPLETED_PLAY, 3446 CALL_COMPLETED_PREPARE, 3447 CALL_COMPLETED_SEEK_TO, 3448 CALL_COMPLETED_SELECT_TRACK, 3449 CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, 3450 CALL_COMPLETED_SET_AUDIO_SESSION_ID, 3451 CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, 3452 CALL_COMPLETED_SET_DATA_SOURCE, 3453 CALL_COMPLETED_SET_NEXT_DATA_SOURCE, 3454 CALL_COMPLETED_SET_NEXT_DATA_SOURCES, 3455 CALL_COMPLETED_SET_PLAYBACK_PARAMS, 3456 CALL_COMPLETED_SET_PLAYER_VOLUME, 3457 CALL_COMPLETED_SET_SURFACE, 3458 CALL_COMPLETED_SET_SYNC_PARAMS, 3459 CALL_COMPLETED_SKIP_TO_NEXT, 3460 CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, 3461 CALL_COMPLETED_SET_BUFFERING_PARAMS, 3462 CALL_COMPLETED_SET_DISPLAY, 3463 CALL_COMPLETED_SET_WAKE_LOCK, 3464 CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, 3465 CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, 3466 CALL_COMPLETED_PREPARE_DRM, 3467 }) 3468 @Retention(RetentionPolicy.SOURCE) 3469 public @interface CallCompleted {} 3470 3471 /** Status code represents that call is completed without an error. 3472 * @see EventCallback#onCallCompleted 3473 */ 3474 public static final int CALL_STATUS_NO_ERROR = 0; 3475 3476 /** Status code represents that call is ended with an unknown error. 3477 * @see EventCallback#onCallCompleted 3478 */ 3479 public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE; 3480 3481 /** Status code represents that the player is not in valid state for the operation. 3482 * @see EventCallback#onCallCompleted 3483 */ 3484 public static final int CALL_STATUS_INVALID_OPERATION = 1; 3485 3486 /** Status code represents that the argument is illegal. 3487 * @see EventCallback#onCallCompleted 3488 */ 3489 public static final int CALL_STATUS_BAD_VALUE = 2; 3490 3491 /** Status code represents that the operation is not allowed. 3492 * @see EventCallback#onCallCompleted 3493 */ 3494 public static final int CALL_STATUS_PERMISSION_DENIED = 3; 3495 3496 /** Status code represents a file or network related operation error. 3497 * @see EventCallback#onCallCompleted 3498 */ 3499 public static final int CALL_STATUS_ERROR_IO = 4; 3500 3501 /** Status code represents that the call has been skipped. For example, a {@link #seekTo} 3502 * request may be skipped if it is followed by another {@link #seekTo} request. 3503 * @see EventCallback#onCallCompleted 3504 */ 3505 public static final int CALL_STATUS_SKIPPED = 5; 3506 3507 /** Status code represents that DRM operation is called before preparing a DRM scheme through 3508 * {@code prepareDrm}. 3509 * @see EventCallback#onCallCompleted 3510 */ 3511 // TODO: change @code to @link when DRM is unhidden 3512 public static final int CALL_STATUS_NO_DRM_SCHEME = 6; 3513 3514 /** 3515 * @hide 3516 */ 3517 @IntDef(flag = false, prefix = "CALL_STATUS", value = { 3518 CALL_STATUS_NO_ERROR, 3519 CALL_STATUS_ERROR_UNKNOWN, 3520 CALL_STATUS_INVALID_OPERATION, 3521 CALL_STATUS_BAD_VALUE, 3522 CALL_STATUS_PERMISSION_DENIED, 3523 CALL_STATUS_ERROR_IO, 3524 CALL_STATUS_SKIPPED, 3525 CALL_STATUS_NO_DRM_SCHEME}) 3526 @Retention(RetentionPolicy.SOURCE) 3527 public @interface CallStatus {} 3528 3529 // Modular DRM begin 3530 3531 /** 3532 * An immutable structure per {@link DataSourceDesc} with settings required to initiate a DRM 3533 * protected playback session. 3534 * 3535 * @see DrmPreparationInfo.Builder 3536 */ 3537 public static final class DrmPreparationInfo { 3538 3539 /** 3540 * Mutable builder to create a {@link MediaPlayer2.DrmPreparationInfo} object. 3541 * 3542 * {@link Builder#Builder(UUID) UUID} must not be null; {@link #setKeyType keyType} 3543 * must be one of {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 3544 * <p> 3545 * When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_STREAMING}, 3546 * {@link #setInitData(byte[]) initData} and {@link #setMimeType(String) mimeType} 3547 * must not be null; When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_OFFLINE}, 3548 * {@link #setKeySetId(byte[]) keySetId} must not be null. 3549 */ 3550 public static final class Builder { 3551 3552 private final UUID mUUID; 3553 private byte[] mKeySetId; 3554 private byte[] mInitData; 3555 private String mMimeType; 3556 private int mKeyType; 3557 private Map<String, String> mOptionalParameters; 3558 3559 /** 3560 * @param uuid UUID of the crypto scheme selected to decrypt content. An UUID can be 3561 * retrieved from the source listening to {@link DrmEventCallback#onDrmInfo}. 3562 */ 3563 public Builder(@NonNull UUID uuid) { 3564 this.mUUID = uuid; 3565 } 3566 3567 /** 3568 * Set identifier of a persisted offline key obtained from 3569 * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}. 3570 * 3571 * A {@code keySetId} can be used to restore persisted offline keys into a new playback 3572 * session of a DRM protected data source. When {@code keySetId} is set, 3573 * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are 3574 * ignored. 3575 * 3576 * @param keySetId identifier of a persisted offline key 3577 * @return this 3578 */ 3579 public @NonNull Builder setKeySetId(@Nullable byte[] keySetId) { 3580 this.mKeySetId = keySetId; 3581 return this; 3582 } 3583 3584 /** 3585 * Set container-specific DRM initialization data. Its meaning is interpreted based on 3586 * {@code mimeType}. For example, it could contain the content ID, key ID or other data 3587 * obtained from the content metadata that is required to generate a 3588 * {@link MediaDrm.KeyRequest}. 3589 * 3590 * @param initData container-specific DRM initialization data 3591 * @return this 3592 */ 3593 public @NonNull Builder setInitData(@Nullable byte[] initData) { 3594 this.mInitData = initData; 3595 return this; 3596 } 3597 3598 /** 3599 * Set mime type of the content 3600 * 3601 * @param mimeType mime type to the content 3602 * @return this 3603 */ 3604 public @NonNull Builder setMimeType(@Nullable String mimeType) { 3605 this.mMimeType = mimeType; 3606 return this; 3607 } 3608 3609 /** 3610 * Set type of the key request. The request may be to acquire keys 3611 * for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content, 3612 * {@link MediaDrm#KEY_TYPE_OFFLINE}. Releasing previously acquired keys 3613 * ({@link MediaDrm#KEY_TYPE_RELEASE}) is not allowed. 3614 * 3615 * @param keyType type of the key request 3616 * @return this 3617 */ 3618 public @NonNull Builder setKeyType(@MediaPlayer2.MediaDrmKeyType int keyType) { 3619 this.mKeyType = keyType; 3620 return this; 3621 } 3622 3623 /** 3624 * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent 3625 * to the license server. 3626 * 3627 * @param optionalParameters optional parameters to be included in a key request 3628 * @return this 3629 */ 3630 public @NonNull Builder setOptionalParameters( 3631 @Nullable Map<String, String> optionalParameters) { 3632 this.mOptionalParameters = optionalParameters; 3633 return this; 3634 } 3635 3636 /** 3637 * @return an immutable {@link DrmPreparationInfo} based on settings of this builder 3638 */ 3639 @NonNull 3640 public DrmPreparationInfo build() { 3641 final DrmPreparationInfo info = new DrmPreparationInfo(mUUID, mKeySetId, mInitData, 3642 mMimeType, mKeyType, mOptionalParameters); 3643 if (!info.isValid()) { 3644 throw new IllegalArgumentException("invalid DrmPreparationInfo"); 3645 } 3646 return info; 3647 } 3648 3649 } 3650 3651 private final UUID mUUID; 3652 private final byte[] mKeySetId; 3653 private final byte[] mInitData; 3654 private final String mMimeType; 3655 private final int mKeyType; 3656 private final Map<String, String> mOptionalParameters; 3657 3658 private DrmPreparationInfo(UUID mUUID, byte[] mKeySetId, byte[] mInitData, String mMimeType, 3659 int mKeyType, Map<String, String> optionalParameters) { 3660 this.mUUID = mUUID; 3661 this.mKeySetId = mKeySetId; 3662 this.mInitData = mInitData; 3663 this.mMimeType = mMimeType; 3664 this.mKeyType = mKeyType; 3665 this.mOptionalParameters = optionalParameters; 3666 } 3667 3668 boolean isValid() { 3669 if (mUUID == null) { 3670 return false; 3671 } 3672 if (mKeySetId != null) { 3673 // offline restore case 3674 return true; 3675 } 3676 if (mInitData != null && mMimeType != null) { 3677 // new streaming license case 3678 return true; 3679 } 3680 return false; 3681 } 3682 3683 /** 3684 * @return UUID of the crypto scheme selected to decrypt content. 3685 */ 3686 @NonNull 3687 public UUID getUuid() { 3688 return mUUID; 3689 } 3690 3691 /** 3692 * @return identifier of the persisted offline key. 3693 */ 3694 @Nullable 3695 public byte[] getKeySetId() { 3696 return mKeySetId; 3697 } 3698 3699 /** 3700 * @return container-specific DRM initialization data. 3701 */ 3702 @Nullable 3703 public byte[] getInitData() { 3704 return mInitData; 3705 } 3706 3707 /** 3708 * @return mime type of the content 3709 */ 3710 @Nullable 3711 public String getMimeType() { 3712 return mMimeType; 3713 } 3714 3715 /** 3716 * @return type of the key request. 3717 */ 3718 @MediaPlayer2.MediaDrmKeyType 3719 public int getKeyType() { 3720 return mKeyType; 3721 } 3722 3723 /** 3724 * @return optional parameters to be included in the {@link MediaDrm.KeyRequest}. 3725 */ 3726 @Nullable 3727 public Map<String, String> getOptionalParameters() { 3728 return mOptionalParameters; 3729 } 3730 } 3731 3732 /** 3733 * Interface definition for callbacks to be invoked when the player has the corresponding 3734 * DRM events. 3735 */ 3736 public static abstract class DrmEventCallback { 3737 3738 /** 3739 * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that 3740 * bundles DRM initialization parameters. 3741 * 3742 * @param mp the {@code MediaPlayer2} associated with this callback 3743 * @param dsd the {@link DataSourceDesc} of this data source 3744 * @param drmInfo DRM info of the source including PSSH, and subset of crypto schemes 3745 * supported by this device 3746 * @return a {@link DrmPreparationInfo} object to initialize DRM playback, or null to skip 3747 * DRM initialization 3748 */ 3749 @Nullable 3750 public abstract DrmPreparationInfo onDrmInfo(@NonNull MediaPlayer2 mp, 3751 @NonNull DataSourceDesc dsd, @NonNull DrmInfo drmInfo); 3752 3753 /** 3754 * Called to give the app the opportunity to configure DRM before the session is created. 3755 * 3756 * This facilitates configuration of the properties, like 'securityLevel', which 3757 * has to be set after DRM scheme creation but before the DRM session is opened. 3758 * 3759 * The only allowed DRM calls in this listener are 3760 * {@link MediaDrm#getPropertyString(String)}, 3761 * {@link MediaDrm#getPropertyByteArray(String)}, 3762 * {@link MediaDrm#setPropertyString(String, String)}, 3763 * {@link MediaDrm#setPropertyByteArray(String, byte[])}, 3764 * {@link MediaDrm#setOnExpirationUpdateListener}, 3765 * and {@link MediaDrm#setOnKeyStatusChangeListener}. 3766 * 3767 * @param mp the {@code MediaPlayer2} associated with this callback 3768 * @param dsd the {@link DataSourceDesc} of this data source 3769 * @param drm handle to get/set DRM properties and listeners for this data source 3770 */ 3771 public void onDrmConfig(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 3772 @NonNull MediaDrm drm) { } 3773 3774 /** 3775 * Called to indicate the DRM session for {@code dsd} is ready for key request/response 3776 * 3777 * @param mp the {@code MediaPlayer2} associated with this callback 3778 * @param dsd the {@link DataSourceDesc} of this data source 3779 * @param request a {@link MediaDrm.KeyRequest} prepared using the 3780 * {@link DrmPreparationInfo} returned from 3781 * {@link #onDrmInfo(MediaPlayer2, DataSourceDesc, DrmInfo)} 3782 * @return the response to {@code request} (from license server); returning {@code null} or 3783 * throwing an {@link RuntimeException} from this callback would trigger an 3784 * {@link EventCallback#onError}. 3785 */ 3786 @NonNull 3787 public abstract byte[] onDrmKeyRequest(@NonNull MediaPlayer2 mp, 3788 @NonNull DataSourceDesc dsd, @NonNull MediaDrm.KeyRequest request); 3789 3790 /** 3791 * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source 3792 * {@code dsd} or if there is an error during DRM preparation 3793 * 3794 * @param mp the {@code MediaPlayer2} associated with this callback 3795 * @param dsd the {@link DataSourceDesc} of this data source 3796 * @param status the result of DRM preparation. 3797 * @param keySetId optional identifier that can be used to restore DRM playback initiated 3798 * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request. 3799 * 3800 * @see DrmPreparationInfo.Builder#setKeySetId(byte[]) 3801 */ 3802 public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, 3803 @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { } 3804 3805 } 3806 3807 private final Object mDrmEventCallbackLock = new Object(); 3808 private Pair<Executor, DrmEventCallback> mDrmEventCallback; 3809 3810 /** 3811 * Registers the callback to be invoked for various DRM events. 3812 * 3813 * This is a synchronous call. 3814 * 3815 * @param eventCallback the callback that will be run 3816 * @param executor the executor through which the callback should be invoked 3817 */ 3818 public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor, 3819 @NonNull DrmEventCallback eventCallback) { 3820 if (eventCallback == null) { 3821 throw new IllegalArgumentException("Illegal null EventCallback"); 3822 } 3823 if (executor == null) { 3824 throw new IllegalArgumentException( 3825 "Illegal null Executor for the EventCallback"); 3826 } 3827 synchronized (mDrmEventCallbackLock) { 3828 mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback); 3829 } 3830 } 3831 3832 /** 3833 * Clear the {@link DrmEventCallback}. 3834 * 3835 * This is a synchronous call. 3836 */ 3837 public void clearDrmEventCallback() { 3838 synchronized (mDrmEventCallbackLock) { 3839 mDrmEventCallback = null; 3840 } 3841 } 3842 3843 /** 3844 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3845 * <p> 3846 * 3847 * DRM preparation has succeeded. 3848 */ 3849 public static final int PREPARE_DRM_STATUS_SUCCESS = 0; 3850 3851 /** 3852 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3853 * <p> 3854 * 3855 * The device required DRM provisioning but couldn't reach the provisioning server. 3856 */ 3857 public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; 3858 3859 /** 3860 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3861 * <p> 3862 * 3863 * The device required DRM provisioning but the provisioning server denied the request. 3864 */ 3865 public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; 3866 3867 /** 3868 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3869 * <p> 3870 * 3871 * The DRM preparation has failed . 3872 */ 3873 public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; 3874 3875 /** 3876 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3877 * <p> 3878 * 3879 * The crypto scheme UUID is not supported by the device. 3880 */ 3881 public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4; 3882 3883 /** 3884 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3885 * <p> 3886 * 3887 * The hardware resources are not available, due to being in use. 3888 */ 3889 public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5; 3890 3891 /** 3892 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3893 * <p> 3894 * 3895 * Restoring persisted offline keys failed. 3896 */ 3897 public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6; 3898 3899 /** 3900 * A status code for {@link DrmEventCallback#onDrmPrepared} listener. 3901 * <p> 3902 * 3903 * Error during key request/response exchange with license server. 3904 */ 3905 public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7; 3906 3907 /** @hide */ 3908 @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = { 3909 PREPARE_DRM_STATUS_SUCCESS, 3910 PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, 3911 PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, 3912 PREPARE_DRM_STATUS_PREPARATION_ERROR, 3913 PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, 3914 PREPARE_DRM_STATUS_RESOURCE_BUSY, 3915 PREPARE_DRM_STATUS_RESTORE_ERROR, 3916 PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, 3917 }) 3918 @Retention(RetentionPolicy.SOURCE) 3919 public @interface PrepareDrmStatusCode {} 3920 3921 /** @hide */ 3922 @IntDef({ 3923 MediaDrm.KEY_TYPE_STREAMING, 3924 MediaDrm.KEY_TYPE_OFFLINE, 3925 MediaDrm.KEY_TYPE_RELEASE, 3926 }) 3927 @Retention(RetentionPolicy.SOURCE) 3928 public @interface MediaDrmKeyType {} 3929 3930 /** @hide */ 3931 @StringDef({ 3932 MediaDrm.PROPERTY_VENDOR, 3933 MediaDrm.PROPERTY_VERSION, 3934 MediaDrm.PROPERTY_DESCRIPTION, 3935 MediaDrm.PROPERTY_ALGORITHMS, 3936 }) 3937 @Retention(RetentionPolicy.SOURCE) 3938 public @interface MediaDrmStringProperty {} 3939 3940 /** 3941 * Retrieves the DRM Info associated with the given source 3942 * 3943 * @param dsd The DRM protected data source 3944 * 3945 * @throws IllegalStateException if called before being prepared 3946 * @hide 3947 */ 3948 @TestApi 3949 public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) { 3950 final SourceInfo sourceInfo = getSourceInfo(dsd); 3951 if (sourceInfo != null) { 3952 DrmInfo drmInfo = null; 3953 3954 // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener; 3955 // regardless below returns drmInfo anyway instead of raising an exception 3956 synchronized (sourceInfo) { 3957 if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) { 3958 final String msg = "The Player has not been prepared yet"; 3959 Log.v(TAG, msg); 3960 throw new IllegalStateException(msg); 3961 } 3962 3963 if (sourceInfo.mDrmInfo != null) { 3964 drmInfo = sourceInfo.mDrmInfo.makeCopy(); 3965 } 3966 } // synchronized 3967 3968 return drmInfo; 3969 } 3970 return null; 3971 } 3972 3973 /** 3974 * Prepares the DRM for the given data source 3975 * <p> 3976 * If {@link DrmEventCallback} is registered, it will be called during 3977 * preparation to allow configuration of the DRM properties before opening the 3978 * DRM session. It should be used only for a series of 3979 * {@link #getDrmPropertyString(DataSourceDesc, String)} and 3980 * {@link #setDrmPropertyString(DataSourceDesc, String, String)} calls 3981 * and refrain from any lengthy operation. 3982 * <p> 3983 * If the device has not been provisioned before, this call also provisions the device 3984 * which involves accessing the provisioning server and can take a variable time to 3985 * complete depending on the network connectivity. 3986 * When needed, the provisioning will be launched in the background. 3987 * The listener {@link DrmEventCallback#onDrmPrepared} 3988 * will be called when provisioning and preparation are finished. The application should 3989 * check the status code returned with {@link DrmEventCallback#onDrmPrepared} to proceed. 3990 * <p> 3991 * The registered {@link DrmEventCallback#onDrmPrepared} is called to indicate the DRM 3992 * session being ready. The application should not make any assumption about its call 3993 * sequence (e.g., before or after prepareDrm returns). 3994 * <p> 3995 * 3996 * @param dsd The DRM protected data source 3997 * 3998 * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved 3999 * from the source listening to {@link DrmEventCallback#onDrmInfo}. 4000 * 4001 * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. 4002 * @hide 4003 */ 4004 // This is an asynchronous call. 4005 @TestApi 4006 public @NonNull Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) { 4007 return addTask(newPrepareDrmTask(dsd, uuid)); 4008 } 4009 4010 private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) { 4011 return new Task(CALL_COMPLETED_PREPARE_DRM, true) { 4012 @Override 4013 void process() { 4014 final SourceInfo sourceInfo = getSourceInfo(dsd); 4015 int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; 4016 boolean finishPrepare = true; 4017 4018 if (sourceInfo == null) { 4019 Log.e(TAG, "prepareDrm(): DataSource not found."); 4020 } else if (sourceInfo.mDrmInfo == null) { 4021 // only allowing if tied to a protected source; 4022 // might relax for releasing offline keys 4023 Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and " 4024 + "DRM info be retrieved before this call."); 4025 } else { 4026 status = PREPARE_DRM_STATUS_SUCCESS; 4027 } 4028 4029 try { 4030 if (status == PREPARE_DRM_STATUS_SUCCESS) { 4031 sourceInfo.mDrmHandle.prepare(uuid); 4032 } 4033 } catch (ResourceBusyException e) { 4034 status = PREPARE_DRM_STATUS_RESOURCE_BUSY; 4035 } catch (UnsupportedSchemeException e) { 4036 status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME; 4037 } catch (NotProvisionedException e) { 4038 Log.w(TAG, "prepareDrm: NotProvisionedException"); 4039 4040 // handle provisioning internally; it'll reset mPrepareDrmInProgress 4041 status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId); 4042 4043 if (status == PREPARE_DRM_STATUS_SUCCESS) { 4044 // License will be setup in provisioning 4045 finishPrepare = false; 4046 } else { 4047 synchronized (sourceInfo.mDrmHandle) { 4048 sourceInfo.mDrmHandle.cleanDrmObj(); 4049 } 4050 4051 switch (status) { 4052 case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR: 4053 Log.e(TAG, "prepareDrm: Provisioning was required but failed " 4054 + "due to a network error."); 4055 break; 4056 4057 case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR: 4058 Log.e(TAG, "prepareDrm: Provisioning was required but the request " 4059 + "was denied by the server."); 4060 break; 4061 4062 case PREPARE_DRM_STATUS_PREPARATION_ERROR: 4063 default: 4064 Log.e(TAG, "prepareDrm: Post-provisioning preparation failed."); 4065 break; 4066 } 4067 } 4068 } catch (Exception e) { 4069 status = PREPARE_DRM_STATUS_PREPARATION_ERROR; 4070 } 4071 4072 if (finishPrepare) { 4073 sourceInfo.mDrmHandle.finishPrepare(status); 4074 synchronized (mTaskLock) { 4075 mCurrentTask = null; 4076 processPendingTask_l(); 4077 } 4078 } 4079 4080 } 4081 }; 4082 } 4083 4084 /** 4085 * Releases the DRM session for the given data source 4086 * <p> 4087 * The player has to have an active DRM session and be in stopped, or prepared 4088 * state before this call is made. 4089 * A {@link #reset()} call will release the DRM session implicitly. 4090 * 4091 * @param dsd The DRM protected data source 4092 * 4093 * @throws NoDrmSchemeException if there is no active DRM session to release 4094 * @hide 4095 */ 4096 // This is a synchronous call. 4097 @TestApi 4098 public void releaseDrm(@NonNull DataSourceDesc dsd) 4099 throws NoDrmSchemeException { 4100 final SourceInfo sourceInfo = getSourceInfo(dsd); 4101 if (sourceInfo != null) { 4102 sourceInfo.mDrmHandle.release(); 4103 } 4104 } 4105 4106 private native void native_releaseDrm(long mSrcId); 4107 4108 /** 4109 * A key request/response exchange occurs between the app and a license server 4110 * to obtain or release keys used to decrypt the given data source. 4111 * <p> 4112 * {@code getDrmKeyRequest()} is used to obtain an opaque key request byte array that is 4113 * delivered to the license server. The opaque key request byte array is returned 4114 * in KeyRequest.data. The recommended URL to deliver the key request to is 4115 * returned in {@code KeyRequest.defaultUrl}. 4116 * <p> 4117 * After the app has received the key request response from the server, 4118 * it should deliver to the response to the DRM engine plugin using the method 4119 * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}. 4120 * 4121 * @param dsd the DRM protected data source 4122 * 4123 * @param keySetId is the key-set identifier of the offline keys being released when keyType is 4124 * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when 4125 * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 4126 * 4127 * @param initData is the container-specific initialization data when the keyType is 4128 * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is 4129 * interpreted based on the mime type provided in the mimeType parameter. It could 4130 * contain, for example, the content ID, key ID or other data obtained from the content 4131 * metadata that is required in generating the key request. 4132 * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. 4133 * 4134 * @param mimeType identifies the mime type of the content 4135 * 4136 * @param keyType specifies the type of the request. The request may be to acquire 4137 * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content 4138 * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired 4139 * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. 4140 * 4141 * @param optionalParameters are included in the key request message to 4142 * allow a client application to provide additional message parameters to the server. 4143 * This may be {@code null} if no additional parameters are to be sent. 4144 * 4145 * @throws NoDrmSchemeException if there is no active DRM session 4146 * @hide 4147 */ 4148 @TestApi 4149 public MediaDrm.KeyRequest getDrmKeyRequest( 4150 @NonNull DataSourceDesc dsd, 4151 @Nullable byte[] keySetId, @Nullable byte[] initData, 4152 @Nullable String mimeType, @MediaDrmKeyType int keyType, 4153 @Nullable Map<String, String> optionalParameters) 4154 throws NoDrmSchemeException { 4155 Log.v(TAG, "getDrmKeyRequest: " + 4156 " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + 4157 " keyType: " + keyType + " optionalParameters: " + optionalParameters); 4158 4159 final SourceInfo sourceInfo = getSourceInfo(dsd); 4160 if (sourceInfo != null) { 4161 return sourceInfo.mDrmHandle.getDrmKeyRequest( 4162 keySetId, initData, mimeType, keyType, optionalParameters); 4163 } 4164 return null; 4165 } 4166 4167 /** 4168 * A key response is received from the license server by the app for the given DRM protected 4169 * data source, then provided to the DRM engine plugin using {@code provideDrmKeyResponse}. 4170 * <p> 4171 * When the response is for an offline key request, a key-set identifier is returned that 4172 * can be used to later restore the keys to a new session with the method 4173 * {@link #restoreDrmKeys(DataSourceDesc, byte[])}. 4174 * When the response is for a streaming or release request, null is returned. 4175 * 4176 * @param dsd the DRM protected data source 4177 * 4178 * @param keySetId When the response is for a release request, keySetId identifies the saved 4179 * key associated with the release request (i.e., the same keySetId passed to the earlier 4180 * {@link # getDrmKeyRequest(DataSourceDesc, byte[], byte[], String, int, Map)} call). 4181 * It MUST be null when the response is for either streaming or offline key requests. 4182 * 4183 * @param response the byte array response from the server 4184 * 4185 * @throws NoDrmSchemeException if there is no active DRM session 4186 * @throws DeniedByServerException if the response indicates that the 4187 * server rejected the request 4188 * @hide 4189 */ 4190 // This is a synchronous call. 4191 @TestApi 4192 public byte[] provideDrmKeyResponse( 4193 @NonNull DataSourceDesc dsd, 4194 @Nullable byte[] keySetId, @NonNull byte[] response) 4195 throws NoDrmSchemeException, DeniedByServerException { 4196 Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response); 4197 4198 final SourceInfo sourceInfo = getSourceInfo(dsd); 4199 if (sourceInfo != null) { 4200 return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response); 4201 } 4202 return null; 4203 } 4204 4205 /** 4206 * Restore persisted offline keys into a new session for the given DRM protected data source. 4207 * {@code keySetId} identifies the keys to load, obtained from a prior call to 4208 * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}. 4209 * 4210 * @param dsd the DRM protected data source 4211 * 4212 * @param keySetId identifies the saved key set to restore 4213 * 4214 * @throws NoDrmSchemeException if there is no active DRM session 4215 * @hide 4216 */ 4217 // This is a synchronous call. 4218 @TestApi 4219 public void restoreDrmKeys( 4220 @NonNull DataSourceDesc dsd, 4221 @NonNull byte[] keySetId) 4222 throws NoDrmSchemeException { 4223 Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); 4224 4225 final SourceInfo sourceInfo = getSourceInfo(dsd); 4226 if (sourceInfo != null) { 4227 sourceInfo.mDrmHandle.restoreDrmKeys(keySetId); 4228 } 4229 } 4230 4231 /** 4232 * Read a DRM engine plugin String property value, given the DRM protected data source 4233 * and property name string. 4234 * 4235 * @param dsd the DRM protected data source 4236 * 4237 * @param propertyName the property name 4238 * 4239 * Standard fields names are: 4240 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 4241 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 4242 * 4243 * @throws NoDrmSchemeException if there is no active DRM session 4244 * @hide 4245 */ 4246 @TestApi 4247 public String getDrmPropertyString( 4248 @NonNull DataSourceDesc dsd, 4249 @NonNull @MediaDrmStringProperty String propertyName) 4250 throws NoDrmSchemeException { 4251 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); 4252 4253 final SourceInfo sourceInfo = getSourceInfo(dsd); 4254 if (sourceInfo != null) { 4255 return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName); 4256 } 4257 return null; 4258 } 4259 4260 /** 4261 * Set a DRM engine plugin String property value for the given data source. 4262 * 4263 * @param dsd the DRM protected data source 4264 * @param propertyName the property name 4265 * @param value the property value 4266 * 4267 * Standard fields names are: 4268 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 4269 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 4270 * 4271 * @throws NoDrmSchemeException if there is no active DRM session 4272 * @hide 4273 */ 4274 // This is a synchronous call. 4275 @TestApi 4276 public void setDrmPropertyString( 4277 @NonNull DataSourceDesc dsd, 4278 @NonNull @MediaDrmStringProperty String propertyName, @NonNull String value) 4279 throws NoDrmSchemeException { 4280 // TODO: this implementation only works when dsd is the only data source 4281 Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); 4282 4283 final SourceInfo sourceInfo = getSourceInfo(dsd); 4284 if (sourceInfo != null) { 4285 sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value); 4286 } 4287 } 4288 4289 /** 4290 * Encapsulates the DRM properties of the source. 4291 */ 4292 public static final class DrmInfo { 4293 private Map<UUID, byte[]> mMapPssh; 4294 private UUID[] mSupportedSchemes; 4295 4296 /** 4297 * Returns the PSSH info of the data source for each supported DRM scheme. 4298 */ 4299 public @NonNull Map<UUID, byte[]> getPssh() { 4300 return mMapPssh; 4301 } 4302 4303 /** 4304 * Returns the intersection of the data source and the device DRM schemes. 4305 * It effectively identifies the subset of the source's DRM schemes which 4306 * are supported by the device too. 4307 */ 4308 public @NonNull List<UUID> getSupportedSchemes() { 4309 return Arrays.asList(mSupportedSchemes); 4310 } 4311 4312 private DrmInfo(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) { 4313 mMapPssh = pssh; 4314 mSupportedSchemes = supportedSchemes; 4315 } 4316 4317 private static DrmInfo create(PlayerMessage msg) { 4318 Log.v(TAG, "DrmInfo.create(" + msg + ")"); 4319 4320 Iterator<Value> in = msg.getValuesList().iterator(); 4321 byte[] pssh = in.next().getBytesValue().toByteArray(); 4322 4323 Log.v(TAG, "DrmInfo.create() PSSH: " + DrmInfo.arrToHex(pssh)); 4324 Map<UUID, byte[]> mapPssh = DrmInfo.parsePSSH(pssh, pssh.length); 4325 Log.v(TAG, "DrmInfo.create() PSSH: " + mapPssh); 4326 4327 int supportedDRMsCount = in.next().getInt32Value(); 4328 UUID[] supportedSchemes = new UUID[supportedDRMsCount]; 4329 for (int i = 0; i < supportedDRMsCount; i++) { 4330 byte[] uuid = new byte[16]; 4331 in.next().getBytesValue().copyTo(uuid, 0); 4332 4333 supportedSchemes[i] = DrmInfo.bytesToUUID(uuid); 4334 4335 Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + supportedSchemes[i]); 4336 } 4337 4338 Log.v(TAG, "DrmInfo.create() psshsize: " + pssh.length 4339 + " supportedDRMsCount: " + supportedDRMsCount); 4340 return new DrmInfo(mapPssh, supportedSchemes); 4341 } 4342 4343 private DrmInfo makeCopy() { 4344 return new DrmInfo(this.mMapPssh, this.mSupportedSchemes); 4345 } 4346 4347 private static String arrToHex(byte[] bytes) { 4348 String out = "0x"; 4349 for (int i = 0; i < bytes.length; i++) { 4350 out += String.format("%02x", bytes[i]); 4351 } 4352 4353 return out; 4354 } 4355 4356 private static UUID bytesToUUID(byte[] uuid) { 4357 long msb = 0, lsb = 0; 4358 for (int i = 0; i < 8; i++) { 4359 msb |= (((long) uuid[i] & 0xff) << (8 * (7 - i))); 4360 lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i))); 4361 } 4362 4363 return new UUID(msb, lsb); 4364 } 4365 4366 private static Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { 4367 Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); 4368 4369 final int uuidSize = 16; 4370 final int dataLenSize = 4; 4371 4372 int len = psshsize; 4373 int numentries = 0; 4374 int i = 0; 4375 4376 while (len > 0) { 4377 if (len < uuidSize) { 4378 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 4379 + "UUID: (%d < 16) pssh: %d", len, psshsize)); 4380 return null; 4381 } 4382 4383 byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize); 4384 UUID uuid = bytesToUUID(subset); 4385 i += uuidSize; 4386 len -= uuidSize; 4387 4388 // get data length 4389 if (len < 4) { 4390 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 4391 + "datalen: (%d < 4) pssh: %d", len, psshsize)); 4392 return null; 4393 } 4394 4395 subset = Arrays.copyOfRange(pssh, i, i + dataLenSize); 4396 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) 4397 ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) 4398 | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) : 4399 ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) 4400 | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff); 4401 i += dataLenSize; 4402 len -= dataLenSize; 4403 4404 if (len < datalen) { 4405 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 4406 + "data: (%d < %d) pssh: %d", len, datalen, psshsize)); 4407 return null; 4408 } 4409 4410 byte[] data = Arrays.copyOfRange(pssh, i, i + datalen); 4411 4412 // skip the data 4413 i += datalen; 4414 len -= datalen; 4415 4416 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", 4417 numentries, uuid, arrToHex(data), psshsize)); 4418 numentries++; 4419 result.put(uuid, data); 4420 } 4421 4422 return result; 4423 } 4424 }; // DrmInfo 4425 4426 /** 4427 * Thrown when a DRM method is called when there is no active DRM session. 4428 * Extends MediaDrm.MediaDrmException 4429 */ 4430 public static final class NoDrmSchemeException extends MediaDrmException { 4431 public NoDrmSchemeException(@Nullable String detailMessage) { 4432 super(detailMessage); 4433 } 4434 } 4435 4436 private native void native_prepareDrm( 4437 long srcId, @NonNull byte[] uuid, @NonNull byte[] drmSessionId); 4438 4439 // Instantiated from the native side 4440 @SuppressWarnings("unused") 4441 private static class StreamEventCallback extends AudioTrack.StreamEventCallback { 4442 public long mJAudioTrackPtr; 4443 public long mNativeCallbackPtr; 4444 public long mUserDataPtr; 4445 4446 StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) { 4447 super(); 4448 mJAudioTrackPtr = jAudioTrackPtr; 4449 mNativeCallbackPtr = nativeCallbackPtr; 4450 mUserDataPtr = userDataPtr; 4451 } 4452 4453 @Override 4454 public void onTearDown(AudioTrack track) { 4455 native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr); 4456 } 4457 4458 @Override 4459 public void onPresentationEnded(AudioTrack track) { 4460 native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr); 4461 } 4462 4463 @Override 4464 public void onDataRequest(AudioTrack track, int size) { 4465 native_stream_event_onStreamDataRequest( 4466 mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr); 4467 } 4468 } 4469 4470 /** 4471 * Returns a byte[] containing the remainder of 'in', closing it when done. 4472 */ 4473 private static byte[] readInputStreamFully(InputStream in) throws IOException { 4474 try { 4475 return readInputStreamFullyNoClose(in); 4476 } finally { 4477 in.close(); 4478 } 4479 } 4480 4481 /** 4482 * Returns a byte[] containing the remainder of 'in'. 4483 */ 4484 private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { 4485 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 4486 byte[] buffer = new byte[1024]; 4487 int count; 4488 while ((count = in.read(buffer)) != -1) { 4489 bytes.write(buffer, 0, count); 4490 } 4491 return bytes.toByteArray(); 4492 } 4493 4494 private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) { 4495 long msb = uuid.getMostSignificantBits(); 4496 long lsb = uuid.getLeastSignificantBits(); 4497 4498 byte[] uuidBytes = new byte[16]; 4499 for (int i = 0; i < 8; ++i) { 4500 uuidBytes[i] = (byte) (msb >>> (8 * (7 - i))); 4501 uuidBytes[8 + i] = (byte) (lsb >>> (8 * (7 - i))); 4502 } 4503 4504 return uuidBytes; 4505 } 4506 4507 private static class TimedTextUtil { 4508 // These keys must be in sync with the keys in TextDescription2.h 4509 private static final int KEY_START_TIME = 7; // int 4510 private static final int KEY_STRUCT_TEXT_POS = 14; // TextPos 4511 private static final int KEY_STRUCT_TEXT = 16; // Text 4512 private static final int KEY_GLOBAL_SETTING = 101; 4513 private static final int KEY_LOCAL_SETTING = 102; 4514 4515 private static TimedText parsePlayerMessage(PlayerMessage playerMsg) { 4516 if (playerMsg.getValuesCount() == 0) { 4517 return null; 4518 } 4519 4520 String textChars = null; 4521 Rect textBounds = null; 4522 Iterator<Value> in = playerMsg.getValuesList().iterator(); 4523 int type = in.next().getInt32Value(); 4524 if (type == KEY_LOCAL_SETTING) { 4525 type = in.next().getInt32Value(); 4526 if (type != KEY_START_TIME) { 4527 return null; 4528 } 4529 int startTimeMs = in.next().getInt32Value(); 4530 4531 type = in.next().getInt32Value(); 4532 if (type != KEY_STRUCT_TEXT) { 4533 return null; 4534 } 4535 4536 byte[] text = in.next().getBytesValue().toByteArray(); 4537 if (text == null || text.length == 0) { 4538 textChars = null; 4539 } else { 4540 textChars = new String(text); 4541 } 4542 4543 } else if (type != KEY_GLOBAL_SETTING) { 4544 Log.w(TAG, "Invalid timed text key found: " + type); 4545 return null; 4546 } 4547 if (in.hasNext()) { 4548 type = in.next().getInt32Value(); 4549 if (type == KEY_STRUCT_TEXT_POS) { 4550 int top = in.next().getInt32Value(); 4551 int left = in.next().getInt32Value(); 4552 int bottom = in.next().getInt32Value(); 4553 int right = in.next().getInt32Value(); 4554 textBounds = new Rect(left, top, right, bottom); 4555 } 4556 } 4557 return null; 4558 /* TimedText c-tor usage is temporarily commented out. 4559 * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track 4560 * and remove TimedText path from MediaPlayer2. 4561 return new TimedText(textChars, textBounds); 4562 */ 4563 } 4564 } 4565 4566 private Object addTask(Task task) { 4567 synchronized (mTaskLock) { 4568 mPendingTasks.add(task); 4569 processPendingTask_l(); 4570 } 4571 return task; 4572 } 4573 4574 @GuardedBy("mTaskLock") 4575 private void processPendingTask_l() { 4576 if (mCurrentTask != null) { 4577 return; 4578 } 4579 if (!mPendingTasks.isEmpty()) { 4580 Task task = mPendingTasks.remove(0); 4581 mCurrentTask = task; 4582 mTaskHandler.post(task); 4583 } 4584 } 4585 4586 private abstract class Task implements Runnable { 4587 final long mTaskId = mTaskIdGenerator.getAndIncrement(); 4588 private final int mMediaCallType; 4589 private final boolean mNeedToWaitForEventToComplete; 4590 private DataSourceDesc mDSD; 4591 4592 Task(int mediaCallType, boolean needToWaitForEventToComplete) { 4593 mMediaCallType = mediaCallType; 4594 mNeedToWaitForEventToComplete = needToWaitForEventToComplete; 4595 } 4596 4597 abstract void process() throws IOException, NoDrmSchemeException; 4598 4599 @Override 4600 public void run() { 4601 int status = CALL_STATUS_NO_ERROR; 4602 try { 4603 if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED 4604 && getState() == PLAYER_STATE_ERROR) { 4605 status = CALL_STATUS_INVALID_OPERATION; 4606 } else { 4607 if (mMediaCallType == CALL_COMPLETED_SEEK_TO) { 4608 synchronized (mTaskLock) { 4609 if (!mPendingTasks.isEmpty()) { 4610 Task nextTask = mPendingTasks.get(0); 4611 if (nextTask.mMediaCallType == mMediaCallType) { 4612 throw new CommandSkippedException( 4613 "consecutive seekTo is skipped except last one"); 4614 } 4615 } 4616 } 4617 } 4618 process(); 4619 } 4620 } catch (IllegalStateException e) { 4621 status = CALL_STATUS_INVALID_OPERATION; 4622 } catch (IllegalArgumentException e) { 4623 status = CALL_STATUS_BAD_VALUE; 4624 } catch (SecurityException e) { 4625 status = CALL_STATUS_PERMISSION_DENIED; 4626 } catch (IOException e) { 4627 status = CALL_STATUS_ERROR_IO; 4628 } catch (NoDrmSchemeException e) { 4629 status = CALL_STATUS_NO_DRM_SCHEME; 4630 } catch (CommandSkippedException e) { 4631 status = CALL_STATUS_SKIPPED; 4632 } catch (Exception e) { 4633 status = CALL_STATUS_ERROR_UNKNOWN; 4634 } 4635 mDSD = getCurrentDataSource(); 4636 4637 if (mMediaCallType != CALL_COMPLETED_SEEK_TO) { 4638 synchronized (mTaskLock) { 4639 mIsPreviousCommandSeekTo = false; 4640 } 4641 } 4642 4643 // TODO: Make native implementations asynchronous and let them send notifications. 4644 if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) { 4645 4646 sendCompleteNotification(status); 4647 4648 synchronized (mTaskLock) { 4649 mCurrentTask = null; 4650 processPendingTask_l(); 4651 } 4652 } 4653 } 4654 4655 private void sendCompleteNotification(int status) { 4656 // In {@link #notifyWhenCommandLabelReached} case, a separate callback 4657 // {@link #onCommandLabelReached} is already called in {@code process()}. 4658 // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared 4659 if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED 4660 || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) { 4661 return; 4662 } 4663 sendEvent(new EventNotifier() { 4664 @Override 4665 public void notify(EventCallback callback) { 4666 callback.onCallCompleted( 4667 MediaPlayer2.this, mDSD, mMediaCallType, status); 4668 } 4669 }); 4670 } 4671 }; 4672 4673 private final class CommandSkippedException extends RuntimeException { 4674 CommandSkippedException(String detailMessage) { 4675 super(detailMessage); 4676 } 4677 }; 4678 4679 // Modular DRM 4680 private final Map<UUID, MediaDrm> mDrmObjs = Collections.synchronizedMap(new HashMap<>()); 4681 private class DrmHandle { 4682 4683 static final int PROVISION_TIMEOUT_MS = 60000; 4684 4685 final DataSourceDesc mDSD; 4686 final long mSrcId; 4687 4688 //--- guarded by |this| start 4689 MediaDrm mDrmObj; 4690 byte[] mDrmSessionId; 4691 UUID mActiveDrmUUID; 4692 boolean mDrmConfigAllowed; 4693 boolean mDrmProvisioningInProgress; 4694 boolean mPrepareDrmInProgress; 4695 Future<?> mProvisionResult; 4696 DrmPreparationInfo mPrepareInfo; 4697 //--- guarded by |this| end 4698 4699 DrmHandle(DataSourceDesc dsd, long srcId) { 4700 mDSD = dsd; 4701 mSrcId = srcId; 4702 } 4703 4704 void prepare(UUID uuid) throws UnsupportedSchemeException, 4705 ResourceBusyException, NotProvisionedException, InterruptedException, 4706 ExecutionException, TimeoutException { 4707 Log.v(TAG, "prepareDrm: uuid: " + uuid); 4708 4709 synchronized (this) { 4710 if (mActiveDrmUUID != null) { 4711 final String msg = "prepareDrm(): Wrong usage: There is already " 4712 + "an active DRM scheme with " + uuid; 4713 Log.e(TAG, msg); 4714 throw new IllegalStateException(msg); 4715 } 4716 4717 if (mPrepareDrmInProgress) { 4718 final String msg = "prepareDrm(): Wrong usage: There is already " 4719 + "a pending prepareDrm call."; 4720 Log.e(TAG, msg); 4721 throw new IllegalStateException(msg); 4722 } 4723 4724 if (mDrmProvisioningInProgress) { 4725 final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress"; 4726 Log.e(TAG, msg); 4727 throw new IllegalStateException(msg); 4728 } 4729 4730 // shouldn't need this; just for safeguard 4731 cleanDrmObj(); 4732 4733 mPrepareDrmInProgress = true; 4734 4735 try { 4736 // only creating the DRM object to allow pre-openSession configuration 4737 prepareDrm_createDrmStep(uuid); 4738 } catch (Exception e) { 4739 Log.w(TAG, "prepareDrm(): Exception ", e); 4740 mPrepareDrmInProgress = false; 4741 throw e; 4742 } 4743 4744 mDrmConfigAllowed = true; 4745 } // synchronized 4746 4747 // call the callback outside the lock 4748 sendDrmEventWait(new DrmEventNotifier<Void>() { 4749 @Override 4750 public Void notifyWait(DrmEventCallback callback) { 4751 callback.onDrmConfig(MediaPlayer2.this, mDSD, mDrmObj); 4752 return null; 4753 } 4754 }); 4755 4756 synchronized (this) { 4757 mDrmConfigAllowed = false; 4758 boolean earlyExit = false; 4759 4760 try { 4761 prepareDrm_openSessionStep(uuid); 4762 4763 this.mActiveDrmUUID = uuid; 4764 mPrepareDrmInProgress = false; 4765 } catch (IllegalStateException e) { 4766 final String msg = "prepareDrm(): Wrong usage: The player must be " 4767 + "in the prepared state to call prepareDrm()."; 4768 Log.e(TAG, msg); 4769 earlyExit = true; 4770 mPrepareDrmInProgress = false; 4771 throw new IllegalStateException(msg); 4772 } catch (NotProvisionedException e) { 4773 Log.w(TAG, "prepareDrm: NotProvisionedException", e); 4774 throw e; 4775 } catch (Exception e) { 4776 Log.e(TAG, "prepareDrm: Exception " + e); 4777 earlyExit = true; 4778 mPrepareDrmInProgress = false; 4779 throw e; 4780 } finally { 4781 if (earlyExit) { // clean up object if didn't succeed 4782 cleanDrmObj(); 4783 } 4784 } // finally 4785 } // synchronized 4786 } 4787 4788 void prepareDrm_createDrmStep(UUID uuid) 4789 throws UnsupportedSchemeException { 4790 Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); 4791 4792 try { 4793 mDrmObj = mDrmObjs.computeIfAbsent(uuid, scheme -> { 4794 try { 4795 return new MediaDrm(scheme); 4796 } catch (UnsupportedSchemeException e) { 4797 throw new IllegalArgumentException(e); 4798 } 4799 }); 4800 Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); 4801 } catch (Exception e) { // UnsupportedSchemeException 4802 Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); 4803 throw e; 4804 } 4805 } 4806 4807 void prepareDrm_openSessionStep(UUID uuid) 4808 throws NotProvisionedException, ResourceBusyException { 4809 Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); 4810 4811 // TODO: 4812 // don't need an open session for a future specialKeyReleaseDrm mode but we should do 4813 // it anyway so it raises provisioning error if needed. We'd rather handle provisioning 4814 // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse 4815 try { 4816 mDrmSessionId = mDrmObj.openSession(); 4817 Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); 4818 4819 // Sending it down to native/mediaserver to create the crypto object 4820 // This call could simply fail due to bad player state, e.g., after play(). 4821 final MediaPlayer2 mp2 = MediaPlayer2.this; 4822 mp2.native_prepareDrm(mSrcId, getByteArrayFromUUID(uuid), mDrmSessionId); 4823 Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded"); 4824 4825 } catch (Exception e) { //ResourceBusyException, NotProvisionedException 4826 Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); 4827 throw e; 4828 } 4829 4830 } 4831 4832 int handleProvisioninig(UUID uuid, long taskId) { 4833 synchronized (this) { 4834 if (mDrmProvisioningInProgress) { 4835 Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress"); 4836 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4837 } 4838 4839 MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); 4840 if (provReq == null) { 4841 Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null."); 4842 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4843 } 4844 4845 Log.v(TAG, "handleProvisioninig provReq " 4846 + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); 4847 4848 // networking in a background thread 4849 mDrmProvisioningInProgress = true; 4850 4851 mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId)); 4852 4853 return PREPARE_DRM_STATUS_SUCCESS; 4854 } 4855 } 4856 4857 void provision(UUID uuid, long taskId) { 4858 4859 MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); 4860 String urlStr = provReq.getDefaultUrl(); 4861 urlStr += "&signedRequest=" + new String(provReq.getData()); 4862 Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr); 4863 4864 byte[] response = null; 4865 boolean provisioningSucceeded = false; 4866 int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; 4867 try { 4868 URL url = new URL(urlStr); 4869 final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 4870 try { 4871 connection.setRequestMethod("POST"); 4872 connection.setDoOutput(false); 4873 connection.setDoInput(true); 4874 connection.setConnectTimeout(PROVISION_TIMEOUT_MS); 4875 connection.setReadTimeout(PROVISION_TIMEOUT_MS); 4876 4877 connection.connect(); 4878 response = readInputStreamFully(connection.getInputStream()); 4879 4880 Log.v(TAG, "handleProvisioninig: Thread run: response " + 4881 response.length + " " + response); 4882 } catch (Exception e) { 4883 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4884 Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url); 4885 } finally { 4886 connection.disconnect(); 4887 } 4888 } catch (Exception e) { 4889 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4890 Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e); 4891 } 4892 4893 if (response != null) { 4894 try { 4895 mDrmObj.provideProvisionResponse(response); 4896 Log.v(TAG, "handleProvisioninig: Thread run: " + 4897 "provideProvisionResponse SUCCEEDED!"); 4898 4899 provisioningSucceeded = true; 4900 } catch (Exception e) { 4901 status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; 4902 Log.w(TAG, "handleProvisioninig: Thread run: " + 4903 "provideProvisionResponse " + e); 4904 } 4905 } 4906 4907 boolean succeeded = false; 4908 4909 synchronized (this) { 4910 // continuing with prepareDrm 4911 if (provisioningSucceeded) { 4912 succeeded = resumePrepare(uuid); 4913 status = (succeeded) ? 4914 PREPARE_DRM_STATUS_SUCCESS : 4915 PREPARE_DRM_STATUS_PREPARATION_ERROR; 4916 } 4917 mDrmProvisioningInProgress = false; 4918 mPrepareDrmInProgress = false; 4919 if (!succeeded) { 4920 cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock 4921 } 4922 } // synchronized 4923 4924 // calling the callback outside the lock 4925 finishPrepare(status); 4926 4927 synchronized (mTaskLock) { 4928 if (mCurrentTask != null 4929 && mCurrentTask.mTaskId == taskId 4930 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM 4931 && mCurrentTask.mNeedToWaitForEventToComplete) { 4932 mCurrentTask = null; 4933 processPendingTask_l(); 4934 } 4935 } 4936 } 4937 4938 Runnable newProvisioningTask(UUID uuid, long taskId) { 4939 return new Runnable() { 4940 @Override 4941 public void run() { 4942 provision(uuid, taskId); 4943 } 4944 }; 4945 } 4946 4947 boolean resumePrepare(UUID uuid) { 4948 Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); 4949 4950 // mDrmLock is guaranteed to be held 4951 boolean success = false; 4952 try { 4953 // resuming 4954 prepareDrm_openSessionStep(uuid); 4955 4956 this.mActiveDrmUUID = uuid; 4957 4958 success = true; 4959 } catch (Exception e) { 4960 Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e); 4961 // mDrmObj clean up is done by the caller 4962 } 4963 4964 return success; 4965 } 4966 4967 synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) { 4968 if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) { 4969 return false; 4970 } 4971 mPrepareInfo = prepareInfo; 4972 return true; 4973 } 4974 4975 void finishPrepare(int status) { 4976 if (status != PREPARE_DRM_STATUS_SUCCESS) { 4977 notifyPrepared(status, null); 4978 return; 4979 } 4980 4981 if (mPrepareInfo == null) { 4982 // Deprecated: this can only happen when using MediaPlayer Version 1 APIs 4983 notifyPrepared(status, null); 4984 return; 4985 } 4986 4987 final byte[] keySetId = mPrepareInfo.mKeySetId; 4988 if (keySetId != null) { 4989 try { 4990 mDrmObj.restoreKeys(mDrmSessionId, keySetId); 4991 notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId); 4992 } catch (Exception e) { 4993 notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId); 4994 } 4995 return; 4996 } 4997 4998 sDrmThreadPool.submit(newKeyExchangeTask()); 4999 } 5000 5001 Runnable newKeyExchangeTask() { 5002 return new Runnable() { 5003 @Override 5004 public void run() { 5005 final byte[] initData = mPrepareInfo.mInitData; 5006 final String mimeType = mPrepareInfo.mMimeType; 5007 final int keyType = mPrepareInfo.mKeyType; 5008 final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters; 5009 byte[] keySetId = null; 5010 try { 5011 KeyRequest req; 5012 req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams); 5013 byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() { 5014 @Override 5015 public byte[] notifyWait(DrmEventCallback callback) { 5016 final MediaPlayer2 mp = MediaPlayer2.this; 5017 return callback.onDrmKeyRequest(mp, mDSD, req); 5018 } 5019 }); 5020 keySetId = provideDrmKeyResponse(null, response); 5021 } catch (Exception e) { 5022 } 5023 if (keySetId == null) { 5024 notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null); 5025 } else { 5026 notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId); 5027 } 5028 } 5029 }; 5030 } 5031 5032 void notifyPrepared(final int status, byte[] keySetId) { 5033 5034 Message msg; 5035 if (status == PREPARE_DRM_STATUS_SUCCESS) { 5036 msg = mTaskHandler.obtainMessage( 5037 MEDIA_DRM_PREPARED, 0, 0, null); 5038 } else { 5039 msg = mTaskHandler.obtainMessage( 5040 MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null); 5041 } 5042 mTaskHandler.post(new Runnable() { 5043 @Override 5044 public void run() { 5045 mTaskHandler.handleMessage(msg, mSrcId); 5046 } 5047 }); 5048 5049 sendDrmEvent(new DrmEventNotifier() { 5050 @Override 5051 public void notify(DrmEventCallback callback) { 5052 callback.onDrmPrepared(MediaPlayer2.this, mDSD, status, 5053 keySetId); 5054 } 5055 }); 5056 5057 } 5058 5059 void cleanDrmObj() { 5060 // the caller holds mDrmLock 5061 Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); 5062 5063 if (mDrmSessionId != null) { 5064 mDrmObj.closeSession(mDrmSessionId); 5065 mDrmSessionId = null; 5066 } 5067 } 5068 5069 void release() throws NoDrmSchemeException { 5070 synchronized (this) { 5071 Log.v(TAG, "releaseDrm:"); 5072 5073 if (mActiveDrmUUID == null) { 5074 Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); 5075 throw new NoDrmSchemeException( 5076 "releaseDrm: No active DRM scheme to release."); 5077 } 5078 5079 try { 5080 // we don't have the player's state in this layer. The below call raises 5081 // exception if we're in a non-stopped/prepared state. 5082 5083 // for cleaning native/mediaserver crypto object 5084 native_releaseDrm(mSrcId); 5085 5086 // for cleaning client-side MediaDrm object; only called if above has succeeded 5087 cleanDrmObj(); 5088 5089 this.mActiveDrmUUID = null; 5090 } catch (IllegalStateException e) { 5091 Log.w(TAG, "releaseDrm: Exception ", e); 5092 throw new IllegalStateException( 5093 "releaseDrm: The player is not in a valid state."); 5094 } catch (Exception e) { 5095 Log.e(TAG, "releaseDrm: Exception ", e); 5096 } 5097 } // synchronized 5098 } 5099 5100 void cleanup() { 5101 synchronized (this) { 5102 Log.v(TAG, "cleanupDrm: " + 5103 " mProvisioningTask=" + mProvisionResult + 5104 " mPrepareDrmInProgress=" + mPrepareDrmInProgress + 5105 " mActiveDrmScheme=" + mActiveDrmUUID); 5106 5107 if (mProvisionResult != null) { 5108 // timeout; relying on HttpUrlConnection 5109 try { 5110 mProvisionResult.get(); 5111 } 5112 catch (InterruptedException | ExecutionException e) { 5113 Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); 5114 } 5115 } 5116 5117 // set to false to avoid duplicate release calls 5118 this.mActiveDrmUUID = null; 5119 5120 native_releaseDrm(mSrcId); 5121 cleanDrmObj(); 5122 } // synchronized 5123 } 5124 5125 Runnable newCleanupTask() { 5126 return new Runnable() { 5127 @Override 5128 public void run() { 5129 cleanup(); 5130 } 5131 }; 5132 } 5133 5134 MediaDrm.KeyRequest getDrmKeyRequest( 5135 byte[] keySetId, byte[] initData, 5136 String mimeType, int keyType, 5137 Map<String, String> optionalParameters) 5138 throws NoDrmSchemeException { 5139 synchronized (this) { 5140 if (mActiveDrmUUID == null) { 5141 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); 5142 throw new NoDrmSchemeException( 5143 "getDrmKeyRequest: Has to set a DRM scheme first."); 5144 } 5145 5146 try { 5147 byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? 5148 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 5149 keySetId; // keySetId for KEY_TYPE_RELEASE 5150 5151 HashMap<String, String> hmapOptionalParameters = 5152 (optionalParameters != null) 5153 ? new HashMap<String, String>(optionalParameters) 5154 : null; 5155 5156 MediaDrm.KeyRequest request = mDrmObj.getKeyRequest( 5157 scope, initData, mimeType, keyType, hmapOptionalParameters); 5158 Log.v(TAG, "getDrmKeyRequest: --> request: " + request); 5159 5160 return request; 5161 5162 } catch (NotProvisionedException e) { 5163 Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " + 5164 "Unexpected. Shouldn't have reached here."); 5165 throw new IllegalStateException("getDrmKeyRequest: provisioning error."); 5166 } catch (Exception e) { 5167 Log.w(TAG, "getDrmKeyRequest Exception " + e); 5168 throw e; 5169 } 5170 5171 } 5172 } 5173 5174 byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response) 5175 throws NoDrmSchemeException, DeniedByServerException { 5176 synchronized (this) { 5177 5178 if (mActiveDrmUUID == null) { 5179 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); 5180 throw new NoDrmSchemeException( 5181 "getDrmKeyRequest: Has to set a DRM scheme first."); 5182 } 5183 5184 try { 5185 byte[] scope = (keySetId == null) ? 5186 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 5187 keySetId; // keySetId for KEY_TYPE_RELEASE 5188 5189 byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); 5190 5191 Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId 5192 + " response: " + response + " --> " + keySetResult); 5193 5194 5195 return keySetResult; 5196 5197 } catch (NotProvisionedException e) { 5198 Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " + 5199 "Unexpected. Shouldn't have reached here."); 5200 throw new IllegalStateException("provideDrmKeyResponse: " + 5201 "Unexpected provisioning error."); 5202 } catch (Exception e) { 5203 Log.w(TAG, "provideDrmKeyResponse Exception " + e); 5204 throw e; 5205 } 5206 } 5207 } 5208 5209 void restoreDrmKeys(byte[] keySetId) 5210 throws NoDrmSchemeException { 5211 synchronized (this) { 5212 if (mActiveDrmUUID == null) { 5213 Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); 5214 throw new NoDrmSchemeException( 5215 "restoreDrmKeys: Has to set a DRM scheme first."); 5216 } 5217 5218 try { 5219 mDrmObj.restoreKeys(mDrmSessionId, keySetId); 5220 } catch (Exception e) { 5221 Log.w(TAG, "restoreKeys Exception " + e); 5222 throw e; 5223 } 5224 } 5225 } 5226 5227 String getDrmPropertyString(String propertyName) 5228 throws NoDrmSchemeException { 5229 String v; 5230 synchronized (this) { 5231 5232 if (mActiveDrmUUID == null && !mDrmConfigAllowed) { 5233 Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); 5234 throw new NoDrmSchemeException( 5235 "getDrmPropertyString: Has to prepareDrm() first."); 5236 } 5237 5238 try { 5239 v = mDrmObj.getPropertyString(propertyName); 5240 } catch (Exception e) { 5241 Log.w(TAG, "getDrmPropertyString Exception " + e); 5242 throw e; 5243 } 5244 } // synchronized 5245 5246 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v); 5247 5248 return v; 5249 } 5250 5251 void setDrmPropertyString(String propertyName, String value) 5252 throws NoDrmSchemeException { 5253 synchronized (this) { 5254 5255 if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) { 5256 Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); 5257 throw new NoDrmSchemeException( 5258 "setDrmPropertyString: Has to prepareDrm() first."); 5259 } 5260 5261 try { 5262 mDrmObj.setPropertyString(propertyName, value); 5263 } catch ( Exception e ) { 5264 Log.w(TAG, "setDrmPropertyString Exception " + e); 5265 throw e; 5266 } 5267 } 5268 } 5269 5270 } 5271 5272 final class SourceInfo { 5273 final DataSourceDesc mDSD; 5274 final long mId = mSrcIdGenerator.getAndIncrement(); 5275 AtomicInteger mBufferedPercentage = new AtomicInteger(0); 5276 boolean mClosed = false; 5277 int mPrepareBarrier = 1; 5278 5279 // m*AsNextSource (below) only applies to pending data sources in the playlist; 5280 // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource} 5281 // are undefined. 5282 int mStateAsNextSource = NEXT_SOURCE_STATE_INIT; 5283 boolean mPlayPendingAsNextSource = false; 5284 5285 // Modular DRM 5286 final DrmHandle mDrmHandle; 5287 DrmInfo mDrmInfo; 5288 boolean mDrmInfoResolved; 5289 5290 SourceInfo(DataSourceDesc dsd) { 5291 this.mDSD = dsd; 5292 mDrmHandle = new DrmHandle(dsd, mId); 5293 } 5294 5295 void close() { 5296 synchronized (this) { 5297 if (!mClosed) { 5298 if (mDSD != null) { 5299 mDSD.close(); 5300 } 5301 mClosed = true; 5302 } 5303 } 5304 } 5305 5306 @Override 5307 public String toString() { 5308 return String.format("%s(%d)", SourceInfo.class.getName(), mId); 5309 } 5310 5311 } 5312 5313 private SourceInfo getSourceInfo(long srcId) { 5314 synchronized (mSrcLock) { 5315 if (isCurrentSource(srcId)) { 5316 return mCurrentSourceInfo; 5317 } 5318 if (isNextSource(srcId)) { 5319 return mNextSourceInfos.peek(); 5320 } 5321 } 5322 return null; 5323 } 5324 5325 private SourceInfo getSourceInfo(DataSourceDesc dsd) { 5326 synchronized (mSrcLock) { 5327 if (isCurrentSource(dsd)) { 5328 return mCurrentSourceInfo; 5329 } 5330 if (isNextSource(dsd)) { 5331 return mNextSourceInfos.peek(); 5332 } 5333 } 5334 return null; 5335 } 5336 5337 private boolean isCurrentSource(long srcId) { 5338 synchronized (mSrcLock) { 5339 return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId; 5340 } 5341 } 5342 5343 private boolean isCurrentSource(DataSourceDesc dsd) { 5344 synchronized (mSrcLock) { 5345 return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd; 5346 } 5347 } 5348 5349 private boolean isNextSource(long srcId) { 5350 SourceInfo nextSourceInfo = mNextSourceInfos.peek(); 5351 return nextSourceInfo != null && nextSourceInfo.mId == srcId; 5352 } 5353 5354 private boolean isNextSource(DataSourceDesc dsd) { 5355 SourceInfo nextSourceInfo = mNextSourceInfos.peek(); 5356 return nextSourceInfo != null && nextSourceInfo.mDSD == dsd; 5357 } 5358 5359 @GuardedBy("mSrcLock") 5360 private void setCurrentSourceInfo_l(SourceInfo sourceInfo) { 5361 cleanupSourceInfo(mCurrentSourceInfo); 5362 mCurrentSourceInfo = sourceInfo; 5363 } 5364 5365 @GuardedBy("mSrcLock") 5366 private void clearNextSourceInfos_l() { 5367 while (!mNextSourceInfos.isEmpty()) { 5368 cleanupSourceInfo(mNextSourceInfos.poll()); 5369 } 5370 } 5371 5372 private void cleanupSourceInfo(SourceInfo sourceInfo) { 5373 if (sourceInfo != null) { 5374 sourceInfo.close(); 5375 Runnable task = sourceInfo.mDrmHandle.newCleanupTask(); 5376 sDrmThreadPool.submit(task); 5377 } 5378 } 5379 5380 private void clearSourceInfos() { 5381 synchronized (mSrcLock) { 5382 setCurrentSourceInfo_l(null); 5383 clearNextSourceInfos_l(); 5384 } 5385 } 5386 5387 public static final class MetricsConstants { 5388 private MetricsConstants() {} 5389 5390 /** 5391 * Key to extract the MIME type of the video track 5392 * from the {@link MediaPlayer2#getMetrics} return value. 5393 * The value is a String. 5394 */ 5395 public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; 5396 5397 /** 5398 * Key to extract the codec being used to decode the video track 5399 * from the {@link MediaPlayer2#getMetrics} return value. 5400 * The value is a String. 5401 */ 5402 public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; 5403 5404 /** 5405 * Key to extract the width (in pixels) of the video track 5406 * from the {@link MediaPlayer2#getMetrics} return value. 5407 * The value is an integer. 5408 */ 5409 public static final String WIDTH = "android.media.mediaplayer.width"; 5410 5411 /** 5412 * Key to extract the height (in pixels) of the video track 5413 * from the {@link MediaPlayer2#getMetrics} return value. 5414 * The value is an integer. 5415 */ 5416 public static final String HEIGHT = "android.media.mediaplayer.height"; 5417 5418 /** 5419 * Key to extract the count of video frames played 5420 * from the {@link MediaPlayer2#getMetrics} return value. 5421 * The value is an integer. 5422 */ 5423 public static final String FRAMES = "android.media.mediaplayer.frames"; 5424 5425 /** 5426 * Key to extract the count of video frames dropped 5427 * from the {@link MediaPlayer2#getMetrics} return value. 5428 * The value is an integer. 5429 */ 5430 public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; 5431 5432 /** 5433 * Key to extract the MIME type of the audio track 5434 * from the {@link MediaPlayer2#getMetrics} return value. 5435 * The value is a String. 5436 */ 5437 public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; 5438 5439 /** 5440 * Key to extract the codec being used to decode the audio track 5441 * from the {@link MediaPlayer2#getMetrics} return value. 5442 * The value is a String. 5443 */ 5444 public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; 5445 5446 /** 5447 * Key to extract the duration (in milliseconds) of the 5448 * media being played 5449 * from the {@link MediaPlayer2#getMetrics} return value. 5450 * The value is a long. 5451 */ 5452 public static final String DURATION = "android.media.mediaplayer.durationMs"; 5453 5454 /** 5455 * Key to extract the playing time (in milliseconds) of the 5456 * media being played 5457 * from the {@link MediaPlayer2#getMetrics} return value. 5458 * The value is a long. 5459 */ 5460 public static final String PLAYING = "android.media.mediaplayer.playingMs"; 5461 5462 /** 5463 * Key to extract the count of errors encountered while 5464 * playing the media 5465 * from the {@link MediaPlayer2#getMetrics} return value. 5466 * The value is an integer. 5467 */ 5468 public static final String ERRORS = "android.media.mediaplayer.err"; 5469 5470 /** 5471 * Key to extract an (optional) error code detected while 5472 * playing the media 5473 * from the {@link MediaPlayer2#getMetrics} return value. 5474 * The value is an integer. 5475 */ 5476 public static final String ERROR_CODE = "android.media.mediaplayer.errcode"; 5477 5478 } 5479 5480 private void keepAudioSessionIdAlive(int sessionId) { 5481 synchronized (mSessionIdLock) { 5482 if (mDummyAudioTrack != null) { 5483 if (mDummyAudioTrack.getAudioSessionId() == sessionId) { 5484 return; 5485 } 5486 mDummyAudioTrack.release(); 5487 } 5488 // TODO: parameters can be optimized 5489 mDummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 5490 AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2, 5491 AudioTrack.MODE_STATIC, sessionId); 5492 } 5493 } 5494 5495 private void keepAudioSessionIdAlive(AudioTrack at) { 5496 synchronized (mSessionIdLock) { 5497 if (mDummyAudioTrack != null) { 5498 if (mDummyAudioTrack.getAudioSessionId() == at.getAudioSessionId()) { 5499 at.release(); 5500 return; 5501 } 5502 mDummyAudioTrack.release(); 5503 } 5504 mDummyAudioTrack = at; 5505 } 5506 } 5507 } 5508