MediaPlayer2Impl.java revision 8300cfae0b87ee2a8114c7363c8269fc50d6475c
1/* 2 * Copyright 2018 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 */ 16package androidx.media; 17 18import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20import android.annotation.TargetApi; 21import android.graphics.SurfaceTexture; 22import android.media.AudioAttributes; 23import android.media.DeniedByServerException; 24import android.media.MediaDataSource; 25import android.media.MediaDrm; 26import android.media.MediaFormat; 27import android.media.MediaPlayer; 28import android.media.MediaTimestamp; 29import android.media.PlaybackParams; 30import android.media.ResourceBusyException; 31import android.media.SubtitleData; 32import android.media.SyncParams; 33import android.media.TimedMetaData; 34import android.media.UnsupportedSchemeException; 35import android.os.Build; 36import android.os.Handler; 37import android.os.HandlerThread; 38import android.os.Looper; 39import android.os.Parcel; 40import android.os.Parcelable; 41import android.os.PersistableBundle; 42import android.util.ArrayMap; 43import android.util.Log; 44import android.util.Pair; 45import android.view.Surface; 46 47import androidx.annotation.GuardedBy; 48import androidx.annotation.NonNull; 49import androidx.annotation.Nullable; 50import androidx.annotation.RestrictTo; 51import androidx.core.util.Preconditions; 52 53import java.io.IOException; 54import java.nio.ByteOrder; 55import java.util.ArrayDeque; 56import java.util.ArrayList; 57import java.util.Arrays; 58import java.util.HashMap; 59import java.util.List; 60import java.util.Map; 61import java.util.UUID; 62import java.util.concurrent.Executor; 63import java.util.concurrent.atomic.AtomicInteger; 64 65/** 66 * @hide 67 */ 68@TargetApi(Build.VERSION_CODES.P) 69@RestrictTo(LIBRARY_GROUP) 70public final class MediaPlayer2Impl extends MediaPlayer2 { 71 72 private static final String TAG = "MediaPlayer2Impl"; 73 74 private static final int NEXT_SOURCE_STATE_ERROR = -1; 75 private static final int NEXT_SOURCE_STATE_INIT = 0; 76 private static final int NEXT_SOURCE_STATE_PREPARING = 1; 77 private static final int NEXT_SOURCE_STATE_PREPARED = 2; 78 79 private static ArrayMap<Integer, Integer> sInfoEventMap; 80 private static ArrayMap<Integer, Integer> sErrorEventMap; 81 private static ArrayMap<Integer, Integer> sPrepareDrmStatusMap; 82 83 static { 84 sInfoEventMap = new ArrayMap<>(); 85 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_UNKNOWN, MEDIA_INFO_UNKNOWN); 86 sInfoEventMap.put(2 /*MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT*/, MEDIA_INFO_STARTED_AS_NEXT); 87 sInfoEventMap.put( 88 MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, MEDIA_INFO_VIDEO_RENDERING_START); 89 sInfoEventMap.put( 90 MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, MEDIA_INFO_VIDEO_TRACK_LAGGING); 91 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_START, MEDIA_INFO_BUFFERING_START); 92 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_END, MEDIA_INFO_BUFFERING_END); 93 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, MEDIA_INFO_BAD_INTERLEAVING); 94 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, MEDIA_INFO_NOT_SEEKABLE); 95 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_METADATA_UPDATE, MEDIA_INFO_METADATA_UPDATE); 96 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, MEDIA_INFO_AUDIO_NOT_PLAYING); 97 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, MEDIA_INFO_VIDEO_NOT_PLAYING); 98 sInfoEventMap.put( 99 MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, MEDIA_INFO_UNSUPPORTED_SUBTITLE); 100 sInfoEventMap.put(MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT, MEDIA_INFO_SUBTITLE_TIMED_OUT); 101 102 sErrorEventMap = new ArrayMap<>(); 103 sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNKNOWN); 104 sErrorEventMap.put( 105 MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK, 106 MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK); 107 sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_IO, MEDIA_ERROR_IO); 108 sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_MALFORMED, MEDIA_ERROR_MALFORMED); 109 sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_UNSUPPORTED, MEDIA_ERROR_UNSUPPORTED); 110 sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_TIMED_OUT, MEDIA_ERROR_TIMED_OUT); 111 112 sPrepareDrmStatusMap.put( 113 MediaPlayer.PREPARE_DRM_STATUS_SUCCESS, PREPARE_DRM_STATUS_SUCCESS); 114 sPrepareDrmStatusMap.put( 115 MediaPlayer.PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, 116 PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR); 117 sPrepareDrmStatusMap.put( 118 MediaPlayer.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, 119 PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR); 120 sPrepareDrmStatusMap.put( 121 MediaPlayer.PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, 122 PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR); 123 } 124 125 private MediaPlayer mPlayer; // MediaPlayer is thread-safe. 126 127 private final Object mSrcLock = new Object(); 128 //--- guarded by |mSrcLock| start 129 private long mSrcIdGenerator = 0; 130 private DataSourceDesc mCurrentDSD; 131 private long mCurrentSrcId = mSrcIdGenerator++; 132 private List<DataSourceDesc> mNextDSDs; 133 private long mNextSrcId = mSrcIdGenerator++; 134 private int mNextSourceState = NEXT_SOURCE_STATE_INIT; 135 private boolean mNextSourcePlayPending = false; 136 //--- guarded by |mSrcLock| end 137 138 private AtomicInteger mBufferedPercentageCurrent = new AtomicInteger(0); 139 private AtomicInteger mBufferedPercentageNext = new AtomicInteger(0); 140 private volatile float mVolume = 1.0f; 141 142 private HandlerThread mHandlerThread; 143 private final Handler mTaskHandler; 144 private final Object mTaskLock = new Object(); 145 @GuardedBy("mTaskLock") 146 private final ArrayDeque<Task> mPendingTasks = new ArrayDeque<>(); 147 @GuardedBy("mTaskLock") 148 private Task mCurrentTask; 149 150 private final Object mLock = new Object(); 151 //--- guarded by |mLock| start 152 @PlayerState private int mPlayerState; 153 @BuffState private int mBufferingState; 154 private AudioAttributesCompat mAudioAttributes; 155 private ArrayList<Pair<Executor, MediaPlayer2EventCallback>> mMp2EventCallbackRecords = 156 new ArrayList<>(); 157 private ArrayMap<PlayerEventCallback, Executor> mPlayerEventCallbackMap = 158 new ArrayMap<>(); 159 private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords = 160 new ArrayList<>(); 161 //--- guarded by |mLock| end 162 163 /** 164 * Default constructor. 165 * <p>When done with the MediaPlayer2Impl, you should call {@link #close()}, 166 * to free the resources. If not released, too many MediaPlayer2Impl instances may 167 * result in an exception.</p> 168 */ 169 public MediaPlayer2Impl() { 170 mHandlerThread = new HandlerThread("MediaPlayer2TaskThread"); 171 mHandlerThread.start(); 172 Looper looper = mHandlerThread.getLooper(); 173 mTaskHandler = new Handler(looper); 174 175 // TODO: To make sure MediaPlayer1 listeners work, the caller thread should have a looper. 176 // Fix the framework or document this behavior. 177 mPlayer = new MediaPlayer(); 178 mPlayerState = PLAYER_STATE_IDLE; 179 mBufferingState = BUFFERING_STATE_UNKNOWN; 180 setUpListeners(); 181 } 182 183 /** 184 * Releases the resources held by this {@code MediaPlayer2} object. 185 * 186 * It is considered good practice to call this method when you're 187 * done using the MediaPlayer2. In particular, whenever an Activity 188 * of an application is paused (its onPause() method is called), 189 * or stopped (its onStop() method is called), this method should be 190 * invoked to release the MediaPlayer2 object, unless the application 191 * has a special need to keep the object around. In addition to 192 * unnecessary resources (such as memory and instances of codecs) 193 * being held, failure to call this method immediately if a 194 * MediaPlayer2 object is no longer needed may also lead to 195 * continuous battery consumption for mobile devices, and playback 196 * failure for other applications if no multiple instances of the 197 * same codec are supported on a device. Even if multiple instances 198 * of the same codec are supported, some performance degradation 199 * may be expected when unnecessary multiple instances are used 200 * at the same time. 201 * 202 * {@code close()} may be safely called after a prior {@code close()}. 203 * This class implements the Java {@code AutoCloseable} interface and 204 * may be used with try-with-resources. 205 */ 206 @Override 207 public void close() { 208 mPlayer.release(); 209 } 210 211 /** 212 * Starts or resumes playback. If playback had previously been paused, 213 * playback will continue from where it was paused. If playback had 214 * been stopped, or never started before, playback will start at the 215 * beginning. 216 * 217 * @throws IllegalStateException if it is called in an invalid state 218 */ 219 @Override 220 public void play() { 221 addTask(new Task(CALL_COMPLETED_PLAY, false) { 222 @Override 223 void process() { 224 mPlayer.start(); 225 setPlayerState(PLAYER_STATE_PLAYING); 226 } 227 }); 228 } 229 230 /** 231 * Prepares the player for playback, asynchronously. 232 * 233 * After setting the datasource and the display surface, you need to either 234 * call prepare(). For streams, you should call prepare(), 235 * which returns immediately, rather than blocking until enough data has been 236 * buffered. 237 * 238 * @throws IllegalStateException if it is called in an invalid state 239 */ 240 @Override 241 public void prepare() { 242 addTask(new Task(CALL_COMPLETED_PREPARE, true) { 243 @Override 244 void process() throws IOException { 245 mPlayer.prepareAsync(); 246 setBufferingState(BUFFERING_STATE_BUFFERING_AND_STARVED); 247 } 248 }); 249 } 250 251 /** 252 * Pauses playback. Call play() to resume. 253 * 254 * @throws IllegalStateException if the internal player engine has not been initialized. 255 */ 256 @Override 257 public void pause() { 258 addTask(new Task(CALL_COMPLETED_PAUSE, false) { 259 @Override 260 void process() { 261 mPlayer.pause(); 262 setPlayerState(PLAYER_STATE_PAUSED); 263 } 264 }); 265 } 266 267 /** 268 * Tries to play next data source if applicable. 269 * 270 * @throws IllegalStateException if it is called in an invalid state 271 */ 272 @Override 273 public void skipToNext() { 274 addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { 275 @Override 276 void process() { 277 // TODO: switch to next data source and play 278 } 279 }); 280 } 281 282 /** 283 * Gets the current playback position. 284 * 285 * @return the current position in milliseconds 286 */ 287 @Override 288 public long getCurrentPosition() { 289 return mPlayer.getCurrentPosition(); 290 } 291 292 /** 293 * Gets the duration of the file. 294 * 295 * @return the duration in milliseconds, if no duration is available 296 * (for example, if streaming live content), -1 is returned. 297 */ 298 @Override 299 public long getDuration() { 300 return mPlayer.getDuration(); 301 } 302 303 /** 304 * Gets the current buffered media source position received through progressive downloading. 305 * The received buffering percentage indicates how much of the content has been buffered 306 * or played. For example a buffering update of 80 percent when half the content 307 * has already been played indicates that the next 30 percent of the 308 * content to play has been buffered. 309 * 310 * @return the current buffered media source position in milliseconds 311 */ 312 @Override 313 public long getBufferedPosition() { 314 // Use cached buffered percent for now. 315 return getDuration() * mBufferedPercentageCurrent.get() / 100; 316 } 317 318 @Override 319 public @PlayerState int getPlayerState() { 320 synchronized (mLock) { 321 return mPlayerState; 322 } 323 } 324 325 /** 326 * Gets the current buffering state of the player. 327 * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already 328 * buffered. 329 */ 330 @Override 331 public @BuffState int getBufferingState() { 332 synchronized (mLock) { 333 return mBufferingState; 334 } 335 } 336 337 /** 338 * Sets the audio attributes for this MediaPlayer2. 339 * See {@link AudioAttributes} for how to build and configure an instance of this class. 340 * You must call this method before {@link #prepare()} in order 341 * for the audio attributes to become effective thereafter. 342 * @param attributes a non-null set of audio attributes 343 * @throws IllegalArgumentException if the attributes are null or invalid. 344 */ 345 @Override 346 public void setAudioAttributes(@NonNull final AudioAttributesCompat attributes) { 347 addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) { 348 @Override 349 void process() { 350 AudioAttributes attr; 351 synchronized (mLock) { 352 mAudioAttributes = attributes; 353 attr = (AudioAttributes) mAudioAttributes.unwrap(); 354 } 355 mPlayer.setAudioAttributes(attr); 356 } 357 }); 358 } 359 360 @Override 361 public @NonNull AudioAttributesCompat getAudioAttributes() { 362 synchronized (mLock) { 363 return mAudioAttributes; 364 } 365 } 366 367 /** 368 * Sets the data source as described by a DataSourceDesc. 369 * 370 * @param dsd the descriptor of data source you want to play 371 * @throws IllegalStateException if it is called in an invalid state 372 * @throws NullPointerException if dsd is null 373 */ 374 @Override 375 public void setDataSource(@NonNull final DataSourceDesc dsd) { 376 addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { 377 @Override 378 void process() { 379 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 380 // TODO: setDataSource could update exist data source 381 synchronized (mSrcLock) { 382 mCurrentDSD = dsd; 383 mCurrentSrcId = mSrcIdGenerator++; 384 try { 385 handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId); 386 } catch (IOException e) { 387 } 388 } 389 } 390 }); 391 } 392 393 /** 394 * Sets a single data source as described by a DataSourceDesc which will be played 395 * after current data source is finished. 396 * 397 * @param dsd the descriptor of data source you want to play after current one 398 * @throws IllegalStateException if it is called in an invalid state 399 * @throws NullPointerException if dsd is null 400 */ 401 @Override 402 public void setNextDataSource(@NonNull final DataSourceDesc dsd) { 403 addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { 404 @Override 405 void process() { 406 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 407 synchronized (mSrcLock) { 408 mNextDSDs = new ArrayList<DataSourceDesc>(1); 409 mNextDSDs.add(dsd); 410 mNextSrcId = mSrcIdGenerator++; 411 mNextSourceState = NEXT_SOURCE_STATE_INIT; 412 mNextSourcePlayPending = false; 413 } 414 /* FIXME : define and handle state. 415 int state = getMediaPlayer2State(); 416 if (state != MEDIAPLAYER2_STATE_IDLE) { 417 synchronized (mSrcLock) { 418 prepareNextDataSource_l(); 419 } 420 } 421 */ 422 } 423 }); 424 } 425 426 /** 427 * Sets a list of data sources to be played sequentially after current data source is done. 428 * 429 * @param dsds the list of data sources you want to play after current one 430 * @throws IllegalStateException if it is called in an invalid state 431 * @throws IllegalArgumentException if dsds is null or empty, or contains null DataSourceDesc 432 */ 433 @Override 434 public void setNextDataSources(@NonNull final List<DataSourceDesc> dsds) { 435 addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) { 436 @Override 437 void process() { 438 if (dsds == null || dsds.size() == 0) { 439 throw new IllegalArgumentException("data source list cannot be null or empty."); 440 } 441 for (DataSourceDesc dsd : dsds) { 442 if (dsd == null) { 443 throw new IllegalArgumentException( 444 "DataSourceDesc in the source list cannot be null."); 445 } 446 } 447 448 synchronized (mSrcLock) { 449 mNextDSDs = new ArrayList(dsds); 450 mNextSrcId = mSrcIdGenerator++; 451 mNextSourceState = NEXT_SOURCE_STATE_INIT; 452 mNextSourcePlayPending = false; 453 } 454 /* FIXME : define and handle state. 455 int state = getMediaPlayer2State(); 456 if (state != MEDIAPLAYER2_STATE_IDLE) { 457 synchronized (mSrcLock) { 458 prepareNextDataSource_l(); 459 } 460 } 461 */ 462 } 463 }); 464 } 465 466 @Override 467 public @NonNull DataSourceDesc getCurrentDataSource() { 468 synchronized (mSrcLock) { 469 return mCurrentDSD; 470 } 471 } 472 473 /** 474 * Configures the player to loop on the current data source. 475 * @param loop true if the current data source is meant to loop. 476 */ 477 @Override 478 public void loopCurrent(final boolean loop) { 479 addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { 480 @Override 481 void process() { 482 mPlayer.setLooping(loop); 483 } 484 }); 485 } 486 487 /** 488 * Sets the playback speed. 489 * A value of 1.0f is the default playback value. 490 * A negative value indicates reverse playback, check {@link #isReversePlaybackSupported()} 491 * before using negative values.<br> 492 * After changing the playback speed, it is recommended to query the actual speed supported 493 * by the player, see {@link #getPlaybackSpeed()}. 494 * @param speed the desired playback speed 495 */ 496 @Override 497 public void setPlaybackSpeed(final float speed) { 498 addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_SPEED, false) { 499 @Override 500 void process() { 501 setPlaybackParamsInternal(getPlaybackParams().setSpeed(speed)); 502 } 503 }); 504 } 505 506 /** 507 * Returns the actual playback speed to be used by the player when playing. 508 * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}. 509 * @return the actual playback speed 510 */ 511 @Override 512 public float getPlaybackSpeed() { 513 return getPlaybackParams().getSpeed(); 514 } 515 516 /** 517 * Indicates whether reverse playback is supported. 518 * Reverse playback is indicated by negative playback speeds, see 519 * {@link #setPlaybackSpeed(float)}. 520 * @return true if reverse playback is supported. 521 */ 522 @Override 523 public boolean isReversePlaybackSupported() { 524 return false; 525 } 526 527 /** 528 * Sets the volume of the audio of the media to play, expressed as a linear multiplier 529 * on the audio samples. 530 * Note that this volume is specific to the player, and is separate from stream volume 531 * used across the platform.<br> 532 * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified 533 * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player. 534 * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. 535 */ 536 @Override 537 public void setPlayerVolume(final float volume) { 538 addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { 539 @Override 540 void process() { 541 mVolume = volume; 542 mPlayer.setVolume(volume, volume); 543 } 544 }); 545 } 546 547 /** 548 * Returns the current volume of this player to this player. 549 * Note that it does not take into account the associated stream volume. 550 * @return the player volume. 551 */ 552 @Override 553 public float getPlayerVolume() { 554 return mVolume; 555 } 556 557 /** 558 * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}. 559 */ 560 @Override 561 public float getMaxPlayerVolume() { 562 return 1.0f; 563 } 564 565 /** 566 * Adds a callback to be notified of events for this player. 567 * @param e the {@link Executor} to be used for the events. 568 * @param cb the callback to receive the events. 569 */ 570 @Override 571 public void registerPlayerEventCallback(@NonNull Executor e, 572 @NonNull PlayerEventCallback cb) { 573 if (cb == null) { 574 throw new IllegalArgumentException("Illegal null PlayerEventCallback"); 575 } 576 if (e == null) { 577 throw new IllegalArgumentException( 578 "Illegal null Executor for the PlayerEventCallback"); 579 } 580 synchronized (mLock) { 581 mPlayerEventCallbackMap.put(cb, e); 582 } 583 } 584 585 /** 586 * Removes a previously registered callback for player events 587 * @param cb the callback to remove 588 */ 589 @Override 590 public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb) { 591 if (cb == null) { 592 throw new IllegalArgumentException("Illegal null PlayerEventCallback"); 593 } 594 synchronized (mLock) { 595 mPlayerEventCallbackMap.remove(cb); 596 } 597 } 598 599 @Override 600 public void notifyWhenCommandLabelReached(final Object label) { 601 addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { 602 @Override 603 void process() { 604 notifyMediaPlayer2Event(new Mp2EventNotifier() { 605 @Override 606 public void notify(MediaPlayer2EventCallback cb) { 607 cb.onCommandLabelReached(MediaPlayer2Impl.this, label); 608 } 609 }); 610 } 611 }); 612 } 613 614 /** 615 * Sets the {@link Surface} to be used as the sink for the video portion of 616 * the media. Setting a Surface will un-set any Surface or SurfaceHolder that 617 * was previously set. A null surface will result in only the audio track 618 * being played. 619 * 620 * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps 621 * returned from {@link SurfaceTexture#getTimestamp()} will have an 622 * unspecified zero point. These timestamps cannot be directly compared 623 * between different media sources, different instances of the same media 624 * source, or multiple runs of the same program. The timestamp is normally 625 * monotonically increasing and is unaffected by time-of-day adjustments, 626 * but it is reset when the position is set. 627 * 628 * @param surface The {@link Surface} to be used for the video portion of 629 * the media. 630 * @throws IllegalStateException if the internal player engine has not been 631 * initialized or has been released. 632 */ 633 @Override 634 public void setSurface(final Surface surface) { 635 addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) { 636 @Override 637 void process() { 638 mPlayer.setSurface(surface); 639 } 640 }); 641 } 642 643 /** 644 * Discards all pending commands. 645 */ 646 @Override 647 public void clearPendingCommands() { 648 // TODO: implement this. 649 } 650 651 private void addTask(Task task) { 652 synchronized (mTaskLock) { 653 mPendingTasks.add(task); 654 processPendingTask_l(); 655 } 656 } 657 658 @GuardedBy("mTaskLock") 659 private void processPendingTask_l() { 660 if (mCurrentTask != null) { 661 return; 662 } 663 if (!mPendingTasks.isEmpty()) { 664 Task task = mPendingTasks.removeFirst(); 665 mCurrentTask = task; 666 mTaskHandler.post(task); 667 } 668 } 669 670 private void handleDataSource(boolean isCurrent, @NonNull final DataSourceDesc dsd, long srcId) 671 throws IOException { 672 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 673 674 // TODO: handle the case isCurrent is false. 675 switch (dsd.getType()) { 676 case DataSourceDesc.TYPE_CALLBACK: 677 mPlayer.setDataSource(new MediaDataSource() { 678 Media2DataSource mDataSource = dsd.getMedia2DataSource(); 679 @Override 680 public int readAt(long position, byte[] buffer, int offset, int size) 681 throws IOException { 682 return mDataSource.readAt(position, buffer, offset, size); 683 } 684 685 @Override 686 public long getSize() throws IOException { 687 return mDataSource.getSize(); 688 } 689 690 @Override 691 public void close() throws IOException { 692 mDataSource.close(); 693 } 694 }); 695 break; 696 697 case DataSourceDesc.TYPE_FD: 698 mPlayer.setDataSource( 699 dsd.getFileDescriptor(), 700 dsd.getFileDescriptorOffset(), 701 dsd.getFileDescriptorLength()); 702 break; 703 704 case DataSourceDesc.TYPE_URI: 705 mPlayer.setDataSource( 706 dsd.getUriContext(), 707 dsd.getUri(), 708 dsd.getUriHeaders(), 709 dsd.getUriCookies()); 710 break; 711 712 default: 713 break; 714 } 715 } 716 717 /** 718 * Returns the width of the video. 719 * 720 * @return the width of the video, or 0 if there is no video, 721 * no display surface was set, or the width has not been determined 722 * yet. The {@code MediaPlayer2EventCallback} can be registered via 723 * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a 724 * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the width 725 * is available. 726 */ 727 @Override 728 public int getVideoWidth() { 729 return mPlayer.getVideoWidth(); 730 } 731 732 /** 733 * Returns the height of the video. 734 * 735 * @return the height of the video, or 0 if there is no video, 736 * no display surface was set, or the height has not been determined 737 * yet. The {@code MediaPlayer2EventCallback} can be registered via 738 * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a 739 * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the height 740 * is available. 741 */ 742 @Override 743 public int getVideoHeight() { 744 return mPlayer.getVideoHeight(); 745 } 746 747 @Override 748 public PersistableBundle getMetrics() { 749 return mPlayer.getMetrics(); 750 } 751 752 /** 753 * Sets playback rate using {@link PlaybackParams}. The object sets its internal 754 * PlaybackParams to the input, except that the object remembers previous speed 755 * when input speed is zero. This allows the object to resume at previous speed 756 * when play() is called. Calling it before the object is prepared does not change 757 * the object state. After the object is prepared, calling it with zero speed is 758 * equivalent to calling pause(). After the object is prepared, calling it with 759 * non-zero speed is equivalent to calling play(). 760 * 761 * @param params the playback params. 762 * @throws IllegalStateException if the internal player engine has not been 763 * initialized or has been released. 764 * @throws IllegalArgumentException if params is not supported. 765 */ 766 @Override 767 public void setPlaybackParams(@NonNull final PlaybackParams params) { 768 addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { 769 @Override 770 void process() { 771 setPlaybackParamsInternal(params); 772 } 773 }); 774 } 775 776 /** 777 * Gets the playback params, containing the current playback rate. 778 * 779 * @return the playback params. 780 * @throws IllegalStateException if the internal player engine has not been 781 * initialized. 782 */ 783 @Override 784 @NonNull 785 public PlaybackParams getPlaybackParams() { 786 return mPlayer.getPlaybackParams(); 787 } 788 789 /** 790 * Sets A/V sync mode. 791 * 792 * @param params the A/V sync params to apply 793 * @throws IllegalStateException if the internal player engine has not been 794 * initialized. 795 * @throws IllegalArgumentException if params are not supported. 796 */ 797 @Override 798 public void setSyncParams(@NonNull final SyncParams params) { 799 addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { 800 @Override 801 void process() { 802 mPlayer.setSyncParams(params); 803 } 804 }); 805 } 806 807 /** 808 * Gets the A/V sync mode. 809 * 810 * @return the A/V sync params 811 * @throws IllegalStateException if the internal player engine has not been 812 * initialized. 813 */ 814 @Override 815 @NonNull 816 public SyncParams getSyncParams() { 817 return mPlayer.getSyncParams(); 818 } 819 820 /** 821 * Moves the media to specified time position by considering the given mode. 822 * <p> 823 * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. 824 * There is at most one active seekTo processed at any time. If there is a to-be-completed 825 * seekTo, new seekTo requests will be queued in such a way that only the last request 826 * is kept. When current seekTo is completed, the queued request will be processed if 827 * that request is different from just-finished seekTo operation, i.e., the requested 828 * position or mode is different. 829 * 830 * @param msec the offset in milliseconds from the start to seek to. 831 * When seeking to the given time position, there is no guarantee that the data source 832 * has a frame located at the position. When this happens, a frame nearby will be rendered. 833 * If msec is negative, time position zero will be used. 834 * If msec is larger than duration, duration will be used. 835 * @param mode the mode indicating where exactly to seek to. 836 * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame 837 * that has a timestamp earlier than or the same as msec. Use 838 * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame 839 * that has a timestamp later than or the same as msec. Use 840 * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame 841 * that has a timestamp closest to or the same as msec. Use 842 * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may 843 * or may not be a sync frame but is closest to or the same as msec. 844 * {@link #SEEK_CLOSEST} often has larger performance overhead compared 845 * to the other options if there is no sync frame located at msec. 846 * @throws IllegalStateException if the internal player engine has not been 847 * initialized 848 * @throws IllegalArgumentException if the mode is invalid. 849 */ 850 @Override 851 public void seekTo(final long msec, @SeekMode final int mode) { 852 addTask(new Task(CALL_COMPLETED_SEEK_TO, true) { 853 @Override 854 void process() { 855 mPlayer.seekTo(msec, mode); 856 } 857 }); 858 } 859 860 /** 861 * Get current playback position as a {@link MediaTimestamp}. 862 * <p> 863 * The MediaTimestamp represents how the media time correlates to the system time in 864 * a linear fashion using an anchor and a clock rate. During regular playback, the media 865 * time moves fairly constantly (though the anchor frame may be rebased to a current 866 * system time, the linear correlation stays steady). Therefore, this method does not 867 * need to be called often. 868 * <p> 869 * To help users get current playback position, this method always anchors the timestamp 870 * to the current {@link System#nanoTime system time}, so 871 * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. 872 * 873 * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp 874 * is available, e.g. because the media player has not been initialized. 875 * @see MediaTimestamp 876 */ 877 @Override 878 @Nullable 879 public MediaTimestamp getTimestamp() { 880 return mPlayer.getTimestamp(); 881 } 882 883 /** 884 * Resets the MediaPlayer2 to its uninitialized state. After calling 885 * this method, you will have to initialize it again by setting the 886 * data source and calling prepare(). 887 */ 888 @Override 889 public void reset() { 890 mPlayer.reset(); 891 892 mBufferedPercentageCurrent.set(0); 893 mBufferedPercentageNext.set(0); 894 mVolume = 1.0f; 895 896 synchronized (mLock) { 897 mAudioAttributes = null; 898 mMp2EventCallbackRecords.clear(); 899 mPlayerEventCallbackMap.clear(); 900 mDrmEventCallbackRecords.clear(); 901 } 902 setPlayerState(PLAYER_STATE_IDLE); 903 setBufferingState(BUFFERING_STATE_UNKNOWN); 904 setUpListeners(); 905 } 906 907 /** 908 * Sets the audio session ID. 909 * 910 * @param sessionId the audio session ID. 911 * The audio session ID is a system wide unique identifier for the audio stream played by 912 * this MediaPlayer2 instance. 913 * The primary use of the audio session ID is to associate audio effects to a particular 914 * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, 915 * this effect will be applied only to the audio content of media players within the same 916 * audio session and not to the output mix. 917 * When created, a MediaPlayer2 instance automatically generates its own audio session ID. 918 * However, it is possible to force this player to be part of an already existing audio session 919 * by calling this method. 920 * This method must be called before one of the overloaded <code> setDataSource </code> methods. 921 * @throws IllegalStateException if it is called in an invalid state 922 * @throws IllegalArgumentException if the sessionId is invalid. 923 */ 924 @Override 925 public void setAudioSessionId(final int sessionId) { 926 addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) { 927 @Override 928 void process() { 929 mPlayer.setAudioSessionId(sessionId); 930 } 931 }); 932 } 933 934 @Override 935 public int getAudioSessionId() { 936 return mPlayer.getAudioSessionId(); 937 } 938 939 /** 940 * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation 941 * effect which can be applied on any sound source that directs a certain amount of its 942 * energy to this effect. This amount is defined by setAuxEffectSendLevel(). 943 * See {@link #setAuxEffectSendLevel(float)}. 944 * <p>After creating an auxiliary effect (e.g. 945 * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with 946 * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method 947 * to attach the player to the effect. 948 * <p>To detach the effect from the player, call this method with a null effect id. 949 * <p>This method must be called after one of the overloaded <code> setDataSource </code> 950 * methods. 951 * @param effectId system wide unique id of the effect to attach 952 */ 953 @Override 954 public void attachAuxEffect(final int effectId) { 955 addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) { 956 @Override 957 void process() { 958 mPlayer.attachAuxEffect(effectId); 959 } 960 }); 961 } 962 963 /** 964 * Sets the send level of the player to the attached auxiliary effect. 965 * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. 966 * <p>By default the send level is 0, so even if an effect is attached to the player 967 * this method must be called for the effect to be applied. 968 * <p>Note that the passed level value is a raw scalar. UI controls should be scaled 969 * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, 970 * so an appropriate conversion from linear UI input x to level is: 971 * x == 0 -> level = 0 972 * 0 < x <= R -> level = 10^(72*(x-R)/20/R) 973 * @param level send level scalar 974 */ 975 @Override 976 public void setAuxEffectSendLevel(final float level) { 977 addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { 978 @Override 979 void process() { 980 mPlayer.setAuxEffectSendLevel(level); 981 } 982 }); 983 } 984 985 /** 986 * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. 987 * 988 * @see MediaPlayer2#getTrackInfo 989 */ 990 public static final class TrackInfoImpl extends TrackInfo { 991 final int mTrackType; 992 final MediaFormat mFormat; 993 994 /** 995 * Gets the track type. 996 * @return TrackType which indicates if the track is video, audio, timed text. 997 */ 998 @Override 999 public int getTrackType() { 1000 return mTrackType; 1001 } 1002 1003 /** 1004 * Gets the language code of the track. 1005 * @return a language code in either way of ISO-639-1 or ISO-639-2. 1006 * When the language is unknown or could not be determined, 1007 * ISO-639-2 language code, "und", is returned. 1008 */ 1009 @Override 1010 public String getLanguage() { 1011 String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); 1012 return language == null ? "und" : language; 1013 } 1014 1015 /** 1016 * Gets the {@link MediaFormat} of the track. If the format is 1017 * unknown or could not be determined, null is returned. 1018 */ 1019 @Override 1020 public MediaFormat getFormat() { 1021 if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT 1022 || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1023 return mFormat; 1024 } 1025 return null; 1026 } 1027 1028 TrackInfoImpl(Parcel in) { 1029 mTrackType = in.readInt(); 1030 // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat 1031 // even for audio/video tracks, meaning we only set the mime and language. 1032 String mime = in.readString(); 1033 String language = in.readString(); 1034 mFormat = MediaFormat.createSubtitleFormat(mime, language); 1035 1036 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1037 mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); 1038 mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); 1039 mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); 1040 } 1041 } 1042 1043 TrackInfoImpl(int type, MediaFormat format) { 1044 mTrackType = type; 1045 mFormat = format; 1046 } 1047 1048 /** 1049 * Flatten this object in to a Parcel. 1050 * 1051 * @param dest The Parcel in which the object should be written. 1052 * @param flags Additional flags about how the object should be written. 1053 * May be 0 or {@link android.os.Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. 1054 */ 1055 /* package private */ void writeToParcel(Parcel dest, int flags) { 1056 dest.writeInt(mTrackType); 1057 dest.writeString(getLanguage()); 1058 1059 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1060 dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); 1061 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); 1062 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); 1063 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); 1064 } 1065 } 1066 1067 @Override 1068 public String toString() { 1069 StringBuilder out = new StringBuilder(128); 1070 out.append(getClass().getName()); 1071 out.append('{'); 1072 switch (mTrackType) { 1073 case MEDIA_TRACK_TYPE_VIDEO: 1074 out.append("VIDEO"); 1075 break; 1076 case MEDIA_TRACK_TYPE_AUDIO: 1077 out.append("AUDIO"); 1078 break; 1079 case MEDIA_TRACK_TYPE_TIMEDTEXT: 1080 out.append("TIMEDTEXT"); 1081 break; 1082 case MEDIA_TRACK_TYPE_SUBTITLE: 1083 out.append("SUBTITLE"); 1084 break; 1085 default: 1086 out.append("UNKNOWN"); 1087 break; 1088 } 1089 out.append(", " + mFormat.toString()); 1090 out.append("}"); 1091 return out.toString(); 1092 } 1093 1094 /** 1095 * Used to read a TrackInfoImpl from a Parcel. 1096 */ 1097 /* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR = 1098 new Parcelable.Creator<TrackInfoImpl>() { 1099 @Override 1100 public TrackInfoImpl createFromParcel(Parcel in) { 1101 return new TrackInfoImpl(in); 1102 } 1103 1104 @Override 1105 public TrackInfoImpl[] newArray(int size) { 1106 return new TrackInfoImpl[size]; 1107 } 1108 }; 1109 1110 }; 1111 1112 /** 1113 * Returns a List of track information. 1114 * 1115 * @return List of track info. The total number of tracks is the array length. 1116 * Must be called again if an external timed text source has been added after 1117 * addTimedTextSource method is called. 1118 * @throws IllegalStateException if it is called in an invalid state. 1119 */ 1120 @Override 1121 public List<TrackInfo> getTrackInfo() { 1122 MediaPlayer.TrackInfo[] list = mPlayer.getTrackInfo(); 1123 List<TrackInfo> trackList = new ArrayList<>(); 1124 for (MediaPlayer.TrackInfo info : list) { 1125 trackList.add(new TrackInfoImpl(info.getTrackType(), info.getFormat())); 1126 } 1127 return trackList; 1128 } 1129 1130 /** 1131 * Returns the index of the audio, video, or subtitle track currently selected for playback, 1132 * The return value is an index into the array returned by {@link #getTrackInfo()}, and can 1133 * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. 1134 * 1135 * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, 1136 * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or 1137 * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} 1138 * @return index of the audio, video, or subtitle track currently selected for playback; 1139 * a negative integer is returned when there is no selected track for {@code trackType} or 1140 * when {@code trackType} is not one of audio, video, or subtitle. 1141 * @throws IllegalStateException if called after {@link #close()} 1142 * 1143 * @see #getTrackInfo() 1144 * @see #selectTrack(int) 1145 * @see #deselectTrack(int) 1146 */ 1147 @Override 1148 public int getSelectedTrack(int trackType) { 1149 return mPlayer.getSelectedTrack(trackType); 1150 } 1151 1152 /** 1153 * Selects a track. 1154 * <p> 1155 * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception. 1156 * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately. 1157 * If a MediaPlayer2 is not in Started state, it just marks the track to be played. 1158 * </p> 1159 * <p> 1160 * In any valid state, if it is called multiple times on the same type of track (ie. Video, 1161 * Audio, Timed Text), the most recent one will be chosen. 1162 * </p> 1163 * <p> 1164 * The first audio and video tracks are selected by default if available, even though 1165 * this method is not called. However, no timed text track will be selected until 1166 * this function is called. 1167 * </p> 1168 * <p> 1169 * Currently, only timed text tracks or audio tracks can be selected via this method. 1170 * In addition, the support for selecting an audio track at runtime is pretty limited 1171 * in that an audio track can only be selected in the <em>Prepared</em> state. 1172 * </p> 1173 * 1174 * @param index the index of the track to be selected. The valid range of the index 1175 * is 0..total number of track - 1. The total number of tracks as well as the type of 1176 * each individual track can be found by calling {@link #getTrackInfo()} method. 1177 * @throws IllegalStateException if called in an invalid state. 1178 * @see MediaPlayer2#getTrackInfo 1179 */ 1180 @Override 1181 public void selectTrack(final int index) { 1182 addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { 1183 @Override 1184 void process() { 1185 mPlayer.selectTrack(index); 1186 } 1187 }); 1188 } 1189 1190 /** 1191 * Deselect a track. 1192 * <p> 1193 * Currently, the track must be a timed text track and no audio or video tracks can be 1194 * deselected. If the timed text track identified by index has not been 1195 * selected before, it throws an exception. 1196 * </p> 1197 * 1198 * @param index the index of the track to be deselected. The valid range of the index 1199 * is 0..total number of tracks - 1. The total number of tracks as well as the type of 1200 * each individual track can be found by calling {@link #getTrackInfo()} method. 1201 * @throws IllegalStateException if called in an invalid state. 1202 * @see MediaPlayer2#getTrackInfo 1203 */ 1204 @Override 1205 public void deselectTrack(final int index) { 1206 addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { 1207 @Override 1208 void process() { 1209 mPlayer.deselectTrack(index); 1210 } 1211 }); 1212 } 1213 1214 /** 1215 * Register a callback to be invoked when the media source is ready 1216 * for playback. 1217 * 1218 * @param eventCallback the callback that will be run 1219 * @param executor the executor through which the callback should be invoked 1220 */ 1221 @Override 1222 public void setMediaPlayer2EventCallback(@NonNull Executor executor, 1223 @NonNull MediaPlayer2EventCallback eventCallback) { 1224 if (eventCallback == null) { 1225 throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback"); 1226 } 1227 if (executor == null) { 1228 throw new IllegalArgumentException( 1229 "Illegal null Executor for the MediaPlayer2EventCallback"); 1230 } 1231 synchronized (mLock) { 1232 mMp2EventCallbackRecords.add(new Pair(executor, eventCallback)); 1233 } 1234 } 1235 1236 /** 1237 * Clears the {@link MediaPlayer2EventCallback}. 1238 */ 1239 @Override 1240 public void clearMediaPlayer2EventCallback() { 1241 synchronized (mLock) { 1242 mMp2EventCallbackRecords.clear(); 1243 } 1244 } 1245 1246 // Modular DRM begin 1247 1248 /** 1249 * Register a callback to be invoked for configuration of the DRM object before 1250 * the session is created. 1251 * The callback will be invoked synchronously during the execution 1252 * of {@link #prepareDrm(UUID uuid)}. 1253 * 1254 * @param listener the callback that will be run 1255 */ 1256 @Override 1257 public void setOnDrmConfigHelper(final OnDrmConfigHelper listener) { 1258 mPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() { 1259 @Override 1260 public void onDrmConfig(MediaPlayer mp) { 1261 listener.onDrmConfig(MediaPlayer2Impl.this, mCurrentDSD); 1262 } 1263 }); 1264 } 1265 1266 /** 1267 * Register a callback to be invoked when the media source is ready 1268 * for playback. 1269 * 1270 * @param eventCallback the callback that will be run 1271 * @param executor the executor through which the callback should be invoked 1272 */ 1273 @Override 1274 public void setDrmEventCallback(@NonNull Executor executor, 1275 @NonNull DrmEventCallback eventCallback) { 1276 if (eventCallback == null) { 1277 throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback"); 1278 } 1279 if (executor == null) { 1280 throw new IllegalArgumentException( 1281 "Illegal null Executor for the MediaPlayer2EventCallback"); 1282 } 1283 synchronized (mLock) { 1284 mDrmEventCallbackRecords.add(new Pair(executor, eventCallback)); 1285 } 1286 } 1287 1288 /** 1289 * Clears the {@link DrmEventCallback}. 1290 */ 1291 @Override 1292 public void clearDrmEventCallback() { 1293 synchronized (mLock) { 1294 mDrmEventCallbackRecords.clear(); 1295 } 1296 } 1297 1298 1299 /** 1300 * Retrieves the DRM Info associated with the current source 1301 * 1302 * @throws IllegalStateException if called before prepare() 1303 */ 1304 @Override 1305 public DrmInfo getDrmInfo() { 1306 MediaPlayer.DrmInfo info = mPlayer.getDrmInfo(); 1307 return info == null ? null : new DrmInfoImpl(info.getPssh(), info.getSupportedSchemes()); 1308 } 1309 1310 1311 /** 1312 * Prepares the DRM for the current source 1313 * <p> 1314 * If {@code OnDrmConfigHelper} is registered, it will be called during 1315 * preparation to allow configuration of the DRM properties before opening the 1316 * DRM session. Note that the callback is called synchronously in the thread that called 1317 * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} 1318 * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. 1319 * <p> 1320 * If the device has not been provisioned before, this call also provisions the device 1321 * which involves accessing the provisioning server and can take a variable time to 1322 * complete depending on the network connectivity. 1323 * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking 1324 * mode by launching the provisioning in the background and returning. The listener 1325 * will be called when provisioning and preparation has finished. If a 1326 * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning 1327 * and preparation has finished, i.e., runs in blocking mode. 1328 * <p> 1329 * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM 1330 * session being ready. The application should not make any assumption about its call 1331 * sequence (e.g., before or after prepareDrm returns), or the thread context that will 1332 * execute the listener (unless the listener is registered with a handler thread). 1333 * <p> 1334 * 1335 * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved 1336 * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. 1337 * @throws IllegalStateException if called before prepare(), or the DRM was 1338 * prepared already 1339 * @throws UnsupportedSchemeException if the crypto scheme is not supported 1340 * @throws ResourceBusyException if required DRM resources are in use 1341 * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a 1342 * network error 1343 * @throws ProvisioningServerErrorException if provisioning is required but failed due to 1344 * the request denied by the provisioning server 1345 */ 1346 @Override 1347 public void prepareDrm(@NonNull UUID uuid) 1348 throws UnsupportedSchemeException, ResourceBusyException, 1349 ProvisioningNetworkErrorException, ProvisioningServerErrorException { 1350 try { 1351 mPlayer.prepareDrm(uuid); 1352 } catch (MediaPlayer.ProvisioningNetworkErrorException e) { 1353 throw new ProvisioningNetworkErrorException(e.getMessage()); 1354 } catch (MediaPlayer.ProvisioningServerErrorException e) { 1355 throw new ProvisioningServerErrorException(e.getMessage()); 1356 } 1357 } 1358 1359 /** 1360 * Releases the DRM session 1361 * <p> 1362 * The player has to have an active DRM session and be in stopped, or prepared 1363 * state before this call is made. 1364 * A {@code reset()} call will release the DRM session implicitly. 1365 * 1366 * @throws NoDrmSchemeException if there is no active DRM session to release 1367 */ 1368 @Override 1369 public void releaseDrm() throws NoDrmSchemeException { 1370 try { 1371 mPlayer.releaseDrm(); 1372 } catch (MediaPlayer.NoDrmSchemeException e) { 1373 throw new NoDrmSchemeException(e.getMessage()); 1374 } 1375 } 1376 1377 1378 /** 1379 * A key request/response exchange occurs between the app and a license server 1380 * to obtain or release keys used to decrypt encrypted content. 1381 * <p> 1382 * getDrmKeyRequest() is used to obtain an opaque key request byte array that is 1383 * delivered to the license server. The opaque key request byte array is returned 1384 * in KeyRequest.data. The recommended URL to deliver the key request to is 1385 * returned in KeyRequest.defaultUrl. 1386 * <p> 1387 * After the app has received the key request response from the server, 1388 * it should deliver to the response to the DRM engine plugin using the method 1389 * {@link #provideDrmKeyResponse}. 1390 * 1391 * @param keySetId is the key-set identifier of the offline keys being released when keyType is 1392 * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when 1393 * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 1394 * 1395 * @param initData is the container-specific initialization data when the keyType is 1396 * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is 1397 * interpreted based on the mime type provided in the mimeType parameter. It could 1398 * contain, for example, the content ID, key ID or other data obtained from the content 1399 * metadata that is required in generating the key request. 1400 * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. 1401 * 1402 * @param mimeType identifies the mime type of the content 1403 * 1404 * @param keyType specifies the type of the request. The request may be to acquire 1405 * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content 1406 * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired 1407 * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. 1408 * 1409 * @param optionalParameters are included in the key request message to 1410 * allow a client application to provide additional message parameters to the server. 1411 * This may be {@code null} if no additional parameters are to be sent. 1412 * 1413 * @throws NoDrmSchemeException if there is no active DRM session 1414 */ 1415 @Override 1416 @NonNull 1417 public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, 1418 @Nullable byte[] initData, @Nullable String mimeType, int keyType, 1419 @Nullable Map<String, String> optionalParameters) 1420 throws NoDrmSchemeException { 1421 try { 1422 return mPlayer.getKeyRequest(keySetId, initData, mimeType, keyType, optionalParameters); 1423 } catch (MediaPlayer.NoDrmSchemeException e) { 1424 throw new NoDrmSchemeException(e.getMessage()); 1425 } 1426 } 1427 1428 1429 /** 1430 * A key response is received from the license server by the app, then it is 1431 * provided to the DRM engine plugin using provideDrmKeyResponse. When the 1432 * response is for an offline key request, a key-set identifier is returned that 1433 * can be used to later restore the keys to a new session with the method 1434 * {@ link # restoreDrmKeys}. 1435 * When the response is for a streaming or release request, null is returned. 1436 * 1437 * @param keySetId When the response is for a release request, keySetId identifies 1438 * the saved key associated with the release request (i.e., the same keySetId 1439 * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the 1440 * response is for either streaming or offline key requests. 1441 * 1442 * @param response the byte array response from the server 1443 * 1444 * @throws NoDrmSchemeException if there is no active DRM session 1445 * @throws DeniedByServerException if the response indicates that the 1446 * server rejected the request 1447 */ 1448 @Override 1449 public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) 1450 throws NoDrmSchemeException, DeniedByServerException { 1451 try { 1452 return mPlayer.provideKeyResponse(keySetId, response); 1453 } catch (MediaPlayer.NoDrmSchemeException e) { 1454 throw new NoDrmSchemeException(e.getMessage()); 1455 } 1456 } 1457 1458 1459 /** 1460 * Restore persisted offline keys into a new session. keySetId identifies the 1461 * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}. 1462 * 1463 * @param keySetId identifies the saved key set to restore 1464 */ 1465 @Override 1466 public void restoreDrmKeys(@NonNull final byte[] keySetId) 1467 throws NoDrmSchemeException { 1468 try { 1469 mPlayer.restoreKeys(keySetId); 1470 } catch (MediaPlayer.NoDrmSchemeException e) { 1471 throw new NoDrmSchemeException(e.getMessage()); 1472 } 1473 } 1474 1475 1476 /** 1477 * Read a DRM engine plugin String property value, given the property name string. 1478 * <p> 1479 * 1480 1481 * @param propertyName the property name 1482 * 1483 * Standard fields names are: 1484 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 1485 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 1486 */ 1487 @Override 1488 @NonNull 1489 public String getDrmPropertyString(@NonNull String propertyName) 1490 throws NoDrmSchemeException { 1491 try { 1492 return mPlayer.getDrmPropertyString(propertyName); 1493 } catch (MediaPlayer.NoDrmSchemeException e) { 1494 throw new NoDrmSchemeException(e.getMessage()); 1495 } 1496 } 1497 1498 1499 /** 1500 * Set a DRM engine plugin String property value. 1501 * <p> 1502 * 1503 * @param propertyName the property name 1504 * @param value the property value 1505 * 1506 * Standard fields names are: 1507 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 1508 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 1509 */ 1510 @Override 1511 public void setDrmPropertyString(@NonNull String propertyName, 1512 @NonNull String value) 1513 throws NoDrmSchemeException { 1514 try { 1515 mPlayer.setDrmPropertyString(propertyName, value); 1516 } catch (MediaPlayer.NoDrmSchemeException e) { 1517 throw new NoDrmSchemeException(e.getMessage()); 1518 } 1519 } 1520 1521 private void setPlaybackParamsInternal(final PlaybackParams params) { 1522 PlaybackParams current = mPlayer.getPlaybackParams(); 1523 mPlayer.setPlaybackParams(params); 1524 if (current.getSpeed() != params.getSpeed()) { 1525 notifyPlayerEvent(new PlayerEventNotifier() { 1526 @Override 1527 public void notify(PlayerEventCallback cb) { 1528 cb.onPlaybackSpeedChanged(MediaPlayer2Impl.this, params.getSpeed()); 1529 } 1530 }); 1531 } 1532 } 1533 1534 private void setPlayerState(@PlayerState final int state) { 1535 synchronized (mLock) { 1536 if (mPlayerState == state) { 1537 return; 1538 } 1539 mPlayerState = state; 1540 } 1541 notifyPlayerEvent(new PlayerEventNotifier() { 1542 @Override 1543 public void notify(PlayerEventCallback cb) { 1544 cb.onPlayerStateChanged(MediaPlayer2Impl.this, state); 1545 } 1546 }); 1547 } 1548 1549 private void setBufferingState(@BuffState final int state) { 1550 synchronized (mLock) { 1551 if (mBufferingState == state) { 1552 return; 1553 } 1554 mBufferingState = state; 1555 } 1556 notifyPlayerEvent(new PlayerEventNotifier() { 1557 @Override 1558 public void notify(PlayerEventCallback cb) { 1559 cb.onBufferingStateChanged(MediaPlayer2Impl.this, mCurrentDSD, state); 1560 } 1561 }); 1562 } 1563 1564 private void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) { 1565 List<Pair<Executor, MediaPlayer2EventCallback>> records; 1566 synchronized (mLock) { 1567 records = new ArrayList<>(mMp2EventCallbackRecords); 1568 } 1569 for (final Pair<Executor, MediaPlayer2EventCallback> record : records) { 1570 record.first.execute(new Runnable() { 1571 @Override 1572 public void run() { 1573 notifier.notify(record.second); 1574 } 1575 }); 1576 } 1577 } 1578 1579 private void notifyPlayerEvent(final PlayerEventNotifier notifier) { 1580 ArrayMap<PlayerEventCallback, Executor> map; 1581 synchronized (mLock) { 1582 map = new ArrayMap<>(mPlayerEventCallbackMap); 1583 } 1584 final int callbackCount = map.size(); 1585 for (int i = 0; i < callbackCount; i++) { 1586 final Executor executor = map.valueAt(i); 1587 final PlayerEventCallback cb = map.keyAt(i); 1588 executor.execute(new Runnable() { 1589 @Override 1590 public void run() { 1591 notifier.notify(cb); 1592 } 1593 }); 1594 } 1595 } 1596 1597 private void notifyDrmEvent(final DrmEventNotifier notifier) { 1598 List<Pair<Executor, DrmEventCallback>> records; 1599 synchronized (mLock) { 1600 records = new ArrayList<>(mDrmEventCallbackRecords); 1601 } 1602 for (final Pair<Executor, DrmEventCallback> record : records) { 1603 record.first.execute(new Runnable() { 1604 @Override 1605 public void run() { 1606 notifier.notify(record.second); 1607 } 1608 }); 1609 } 1610 } 1611 1612 private interface Mp2EventNotifier { 1613 void notify(MediaPlayer2EventCallback callback); 1614 } 1615 1616 private interface PlayerEventNotifier { 1617 void notify(PlayerEventCallback callback); 1618 } 1619 1620 private interface DrmEventNotifier { 1621 void notify(DrmEventCallback callback); 1622 } 1623 1624 private void setUpListeners() { 1625 mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 1626 @Override 1627 public void onPrepared(MediaPlayer mp) { 1628 setPlayerState(PLAYER_STATE_PAUSED); 1629 setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE); 1630 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1631 @Override 1632 public void notify(MediaPlayer2EventCallback callback) { 1633 callback.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PREPARED, 0); 1634 } 1635 }); 1636 notifyPlayerEvent(new PlayerEventNotifier() { 1637 @Override 1638 public void notify(PlayerEventCallback cb) { 1639 cb.onMediaPrepared(MediaPlayer2Impl.this, mCurrentDSD); 1640 } 1641 }); 1642 synchronized (mTaskLock) { 1643 if (mCurrentTask != null 1644 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE 1645 && mCurrentTask.mDSD == mCurrentDSD 1646 && mCurrentTask.mNeedToWaitForEventToComplete) { 1647 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 1648 mCurrentTask = null; 1649 processPendingTask_l(); 1650 } 1651 } 1652 } 1653 }); 1654 mPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { 1655 @Override 1656 public void onVideoSizeChanged(MediaPlayer mp, final int width, final int height) { 1657 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1658 @Override 1659 public void notify(MediaPlayer2EventCallback cb) { 1660 cb.onVideoSizeChanged(MediaPlayer2Impl.this, mCurrentDSD, width, height); 1661 } 1662 }); 1663 } 1664 }); 1665 mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { 1666 @Override 1667 public boolean onInfo(MediaPlayer mp, int what, int extra) { 1668 switch (what) { 1669 case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: 1670 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1671 @Override 1672 public void notify(MediaPlayer2EventCallback cb) { 1673 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, 1674 MEDIA_INFO_VIDEO_RENDERING_START, 0); 1675 } 1676 }); 1677 break; 1678 case MediaPlayer.MEDIA_INFO_BUFFERING_START: 1679 setBufferingState(BUFFERING_STATE_BUFFERING_AND_STARVED); 1680 break; 1681 case MediaPlayer.MEDIA_INFO_BUFFERING_END: 1682 setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE); 1683 break; 1684 } 1685 return false; 1686 } 1687 }); 1688 mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 1689 @Override 1690 public void onCompletion(MediaPlayer mp) { 1691 setPlayerState(PLAYER_STATE_PAUSED); 1692 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1693 @Override 1694 public void notify(MediaPlayer2EventCallback cb) { 1695 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE, 1696 0); 1697 } 1698 }); 1699 } 1700 }); 1701 mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 1702 @Override 1703 public boolean onError(MediaPlayer mp, final int what, final int extra) { 1704 setPlayerState(PLAYER_STATE_ERROR); 1705 setBufferingState(BUFFERING_STATE_UNKNOWN); 1706 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1707 @Override 1708 public void notify(MediaPlayer2EventCallback cb) { 1709 int w = sErrorEventMap.getOrDefault(what, MEDIA_ERROR_UNKNOWN); 1710 cb.onError(MediaPlayer2Impl.this, mCurrentDSD, w, extra); 1711 } 1712 }); 1713 return true; 1714 } 1715 }); 1716 mPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() { 1717 @Override 1718 public void onSeekComplete(MediaPlayer mp) { 1719 synchronized (mTaskLock) { 1720 if (mCurrentTask != null 1721 && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO 1722 && mCurrentTask.mNeedToWaitForEventToComplete) { 1723 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 1724 mCurrentTask = null; 1725 processPendingTask_l(); 1726 } 1727 } 1728 final long seekPos = getCurrentPosition(); 1729 notifyPlayerEvent(new PlayerEventNotifier() { 1730 @Override 1731 public void notify(PlayerEventCallback cb) { 1732 // TODO: The actual seeked position might be different from the 1733 // requested position. Clarify which one is expected here. 1734 cb.onSeekCompleted(MediaPlayer2Impl.this, seekPos); 1735 } 1736 }); 1737 } 1738 }); 1739 mPlayer.setOnTimedMetaDataAvailableListener( 1740 new MediaPlayer.OnTimedMetaDataAvailableListener() { 1741 @Override 1742 public void onTimedMetaDataAvailable(MediaPlayer mp, final TimedMetaData data) { 1743 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1744 @Override 1745 public void notify(MediaPlayer2EventCallback cb) { 1746 cb.onTimedMetaDataAvailable( 1747 MediaPlayer2Impl.this, mCurrentDSD, data); 1748 } 1749 }); 1750 } 1751 }); 1752 mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { 1753 @Override 1754 public boolean onInfo(MediaPlayer mp, final int what, final int extra) { 1755 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1756 @Override 1757 public void notify(MediaPlayer2EventCallback cb) { 1758 int w = sInfoEventMap.getOrDefault(what, MEDIA_INFO_UNKNOWN); 1759 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, w, extra); 1760 } 1761 }); 1762 return true; 1763 } 1764 }); 1765 mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { 1766 @Override 1767 public void onBufferingUpdate(MediaPlayer mp, final int percent) { 1768 if (percent >= 100) { 1769 setBufferingState(BUFFERING_STATE_BUFFERING_COMPLETE); 1770 } 1771 mBufferedPercentageCurrent.set(percent); 1772 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1773 @Override 1774 public void notify(MediaPlayer2EventCallback cb) { 1775 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, 1776 MEDIA_INFO_BUFFERING_UPDATE, percent); 1777 } 1778 }); 1779 } 1780 }); 1781 mPlayer.setOnMediaTimeDiscontinuityListener( 1782 new MediaPlayer.OnMediaTimeDiscontinuityListener() { 1783 @Override 1784 public void onMediaTimeDiscontinuity( 1785 MediaPlayer mp, final MediaTimestamp timestamp) { 1786 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1787 @Override 1788 public void notify(MediaPlayer2EventCallback cb) { 1789 cb.onMediaTimeDiscontinuity( 1790 MediaPlayer2Impl.this, mCurrentDSD, timestamp); 1791 } 1792 }); 1793 } 1794 }); 1795 mPlayer.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() { 1796 @Override 1797 public void onSubtitleData(MediaPlayer mp, final SubtitleData data) { 1798 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1799 @Override 1800 public void notify(MediaPlayer2EventCallback cb) { 1801 cb.onSubtitleData(MediaPlayer2Impl.this, mCurrentDSD, data); 1802 } 1803 }); 1804 } 1805 }); 1806 mPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() { 1807 @Override 1808 public void onDrmInfo(MediaPlayer mp, final MediaPlayer.DrmInfo drmInfo) { 1809 notifyDrmEvent(new DrmEventNotifier() { 1810 @Override 1811 public void notify(DrmEventCallback cb) { 1812 cb.onDrmInfo(MediaPlayer2Impl.this, mCurrentDSD, 1813 new DrmInfoImpl(drmInfo.getPssh(), drmInfo.getSupportedSchemes())); 1814 } 1815 }); 1816 } 1817 }); 1818 mPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() { 1819 @Override 1820 public void onDrmPrepared(MediaPlayer mp, final int status) { 1821 notifyDrmEvent(new DrmEventNotifier() { 1822 @Override 1823 public void notify(DrmEventCallback cb) { 1824 int s = sPrepareDrmStatusMap.getOrDefault( 1825 status, PREPARE_DRM_STATUS_PREPARATION_ERROR); 1826 cb.onDrmPrepared(MediaPlayer2Impl.this, mCurrentDSD, s); 1827 } 1828 }); 1829 } 1830 }); 1831 } 1832 1833 /** 1834 * Encapsulates the DRM properties of the source. 1835 */ 1836 public static final class DrmInfoImpl extends DrmInfo { 1837 private Map<UUID, byte[]> mMapPssh; 1838 private UUID[] mSupportedSchemes; 1839 1840 /** 1841 * Returns the PSSH info of the data source for each supported DRM scheme. 1842 */ 1843 @Override 1844 public Map<UUID, byte[]> getPssh() { 1845 return mMapPssh; 1846 } 1847 1848 /** 1849 * Returns the intersection of the data source and the device DRM schemes. 1850 * It effectively identifies the subset of the source's DRM schemes which 1851 * are supported by the device too. 1852 */ 1853 @Override 1854 public List<UUID> getSupportedSchemes() { 1855 return Arrays.asList(mSupportedSchemes); 1856 } 1857 1858 private DrmInfoImpl(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) { 1859 mMapPssh = pssh; 1860 mSupportedSchemes = supportedSchemes; 1861 } 1862 1863 private DrmInfoImpl(Parcel parcel) { 1864 Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize()); 1865 1866 int psshsize = parcel.readInt(); 1867 byte[] pssh = new byte[psshsize]; 1868 parcel.readByteArray(pssh); 1869 1870 Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh)); 1871 mMapPssh = parsePSSH(pssh, psshsize); 1872 Log.v(TAG, "DrmInfoImpl() PSSH: " + mMapPssh); 1873 1874 int supportedDRMsCount = parcel.readInt(); 1875 mSupportedSchemes = new UUID[supportedDRMsCount]; 1876 for (int i = 0; i < supportedDRMsCount; i++) { 1877 byte[] uuid = new byte[16]; 1878 parcel.readByteArray(uuid); 1879 1880 mSupportedSchemes[i] = bytesToUUID(uuid); 1881 1882 Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " 1883 + mSupportedSchemes[i]); 1884 } 1885 1886 Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize 1887 + " supportedDRMsCount: " + supportedDRMsCount); 1888 } 1889 1890 private DrmInfoImpl makeCopy() { 1891 return new DrmInfoImpl(this.mMapPssh, this.mSupportedSchemes); 1892 } 1893 1894 private String arrToHex(byte[] bytes) { 1895 String out = "0x"; 1896 for (int i = 0; i < bytes.length; i++) { 1897 out += String.format("%02x", bytes[i]); 1898 } 1899 1900 return out; 1901 } 1902 1903 private UUID bytesToUUID(byte[] uuid) { 1904 long msb = 0, lsb = 0; 1905 for (int i = 0; i < 8; i++) { 1906 msb |= (((long) uuid[i] & 0xff) << (8 * (7 - i))); 1907 lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i))); 1908 } 1909 1910 return new UUID(msb, lsb); 1911 } 1912 1913 private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { 1914 Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); 1915 1916 final int uuidSize = 16; 1917 final int dataLenSize = 4; 1918 1919 int len = psshsize; 1920 int numentries = 0; 1921 int i = 0; 1922 1923 while (len > 0) { 1924 if (len < uuidSize) { 1925 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1926 + "UUID: (%d < 16) pssh: %d", len, psshsize)); 1927 return null; 1928 } 1929 1930 byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize); 1931 UUID uuid = bytesToUUID(subset); 1932 i += uuidSize; 1933 len -= uuidSize; 1934 1935 // get data length 1936 if (len < 4) { 1937 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1938 + "datalen: (%d < 4) pssh: %d", len, psshsize)); 1939 return null; 1940 } 1941 1942 subset = Arrays.copyOfRange(pssh, i, i + dataLenSize); 1943 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) 1944 ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) 1945 | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) 1946 : ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) 1947 | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff); 1948 i += dataLenSize; 1949 len -= dataLenSize; 1950 1951 if (len < datalen) { 1952 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1953 + "data: (%d < %d) pssh: %d", len, datalen, psshsize)); 1954 return null; 1955 } 1956 1957 byte[] data = Arrays.copyOfRange(pssh, i, i + datalen); 1958 1959 // skip the data 1960 i += datalen; 1961 len -= datalen; 1962 1963 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", 1964 numentries, uuid, arrToHex(data), psshsize)); 1965 numentries++; 1966 result.put(uuid, data); 1967 } 1968 1969 return result; 1970 } 1971 1972 }; // DrmInfoImpl 1973 1974 /** 1975 * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). 1976 * Extends MediaDrm.MediaDrmException 1977 */ 1978 public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException { 1979 public NoDrmSchemeExceptionImpl(String detailMessage) { 1980 super(detailMessage); 1981 } 1982 } 1983 1984 /** 1985 * Thrown when the device requires DRM provisioning but the provisioning attempt has 1986 * failed due to a network error (Internet reachability, timeout, etc.). 1987 * Extends MediaDrm.MediaDrmException 1988 */ 1989 public static final class ProvisioningNetworkErrorExceptionImpl 1990 extends ProvisioningNetworkErrorException { 1991 public ProvisioningNetworkErrorExceptionImpl(String detailMessage) { 1992 super(detailMessage); 1993 } 1994 } 1995 1996 /** 1997 * Thrown when the device requires DRM provisioning but the provisioning attempt has 1998 * failed due to the provisioning server denying the request. 1999 * Extends MediaDrm.MediaDrmException 2000 */ 2001 public static final class ProvisioningServerErrorExceptionImpl 2002 extends ProvisioningServerErrorException { 2003 public ProvisioningServerErrorExceptionImpl(String detailMessage) { 2004 super(detailMessage); 2005 } 2006 } 2007 2008 private abstract class Task implements Runnable { 2009 private final int mMediaCallType; 2010 private final boolean mNeedToWaitForEventToComplete; 2011 private DataSourceDesc mDSD; 2012 2013 Task(int mediaCallType, boolean needToWaitForEventToComplete) { 2014 mMediaCallType = mediaCallType; 2015 mNeedToWaitForEventToComplete = needToWaitForEventToComplete; 2016 } 2017 2018 abstract void process() throws IOException, NoDrmSchemeException; 2019 2020 @Override 2021 public void run() { 2022 int status = CALL_STATUS_NO_ERROR; 2023 try { 2024 process(); 2025 } catch (IllegalStateException e) { 2026 status = CALL_STATUS_INVALID_OPERATION; 2027 } catch (IllegalArgumentException e) { 2028 status = CALL_STATUS_BAD_VALUE; 2029 } catch (SecurityException e) { 2030 status = CALL_STATUS_PERMISSION_DENIED; 2031 } catch (IOException e) { 2032 status = CALL_STATUS_ERROR_IO; 2033 } catch (Exception e) { 2034 status = CALL_STATUS_ERROR_UNKNOWN; 2035 } 2036 synchronized (mSrcLock) { 2037 mDSD = mCurrentDSD; 2038 } 2039 2040 if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) { 2041 2042 sendCompleteNotification(status); 2043 2044 synchronized (mTaskLock) { 2045 mCurrentTask = null; 2046 processPendingTask_l(); 2047 } 2048 } 2049 } 2050 2051 private void sendCompleteNotification(final int status) { 2052 // In {@link #notifyWhenCommandLabelReached} case, a separate callback 2053 // {#link #onCommandLabelReached} is already called in {@code process()}. 2054 if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) { 2055 return; 2056 } 2057 notifyMediaPlayer2Event(new Mp2EventNotifier() { 2058 @Override 2059 public void notify(MediaPlayer2EventCallback cb) { 2060 cb.onCallCompleted( 2061 MediaPlayer2Impl.this, mDSD, mMediaCallType, status); 2062 } 2063 }); 2064 } 2065 }; 2066} 2067