MediaPlayer2Impl.java revision b3f34a470ab85b4a8ef1cf6d4d174eb406139a70
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 addTask(new Task(CALL_COMPLETED_RELEASE_DRM, false) { 1371 @Override 1372 void process() throws NoDrmSchemeException { 1373 try { 1374 mPlayer.releaseDrm(); 1375 } catch (MediaPlayer.NoDrmSchemeException e) { 1376 throw new NoDrmSchemeException(e.getMessage()); 1377 } 1378 } 1379 }); 1380 } 1381 1382 1383 /** 1384 * A key request/response exchange occurs between the app and a license server 1385 * to obtain or release keys used to decrypt encrypted content. 1386 * <p> 1387 * getDrmKeyRequest() is used to obtain an opaque key request byte array that is 1388 * delivered to the license server. The opaque key request byte array is returned 1389 * in KeyRequest.data. The recommended URL to deliver the key request to is 1390 * returned in KeyRequest.defaultUrl. 1391 * <p> 1392 * After the app has received the key request response from the server, 1393 * it should deliver to the response to the DRM engine plugin using the method 1394 * {@link #provideDrmKeyResponse}. 1395 * 1396 * @param keySetId is the key-set identifier of the offline keys being released when keyType is 1397 * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when 1398 * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 1399 * 1400 * @param initData is the container-specific initialization data when the keyType is 1401 * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is 1402 * interpreted based on the mime type provided in the mimeType parameter. It could 1403 * contain, for example, the content ID, key ID or other data obtained from the content 1404 * metadata that is required in generating the key request. 1405 * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. 1406 * 1407 * @param mimeType identifies the mime type of the content 1408 * 1409 * @param keyType specifies the type of the request. The request may be to acquire 1410 * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content 1411 * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired 1412 * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. 1413 * 1414 * @param optionalParameters are included in the key request message to 1415 * allow a client application to provide additional message parameters to the server. 1416 * This may be {@code null} if no additional parameters are to be sent. 1417 * 1418 * @throws NoDrmSchemeException if there is no active DRM session 1419 */ 1420 @Override 1421 @NonNull 1422 public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, 1423 @Nullable byte[] initData, @Nullable String mimeType, int keyType, 1424 @Nullable Map<String, String> optionalParameters) 1425 throws NoDrmSchemeException { 1426 try { 1427 return mPlayer.getKeyRequest(keySetId, initData, mimeType, keyType, optionalParameters); 1428 } catch (MediaPlayer.NoDrmSchemeException e) { 1429 throw new NoDrmSchemeException(e.getMessage()); 1430 } 1431 } 1432 1433 1434 /** 1435 * A key response is received from the license server by the app, then it is 1436 * provided to the DRM engine plugin using provideDrmKeyResponse. When the 1437 * response is for an offline key request, a key-set identifier is returned that 1438 * can be used to later restore the keys to a new session with the method 1439 * {@ link # restoreDrmKeys}. 1440 * When the response is for a streaming or release request, null is returned. 1441 * 1442 * @param keySetId When the response is for a release request, keySetId identifies 1443 * the saved key associated with the release request (i.e., the same keySetId 1444 * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the 1445 * response is for either streaming or offline key requests. 1446 * 1447 * @param response the byte array response from the server 1448 * 1449 * @throws NoDrmSchemeException if there is no active DRM session 1450 * @throws DeniedByServerException if the response indicates that the 1451 * server rejected the request 1452 */ 1453 @Override 1454 public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) 1455 throws NoDrmSchemeException, DeniedByServerException { 1456 try { 1457 return mPlayer.provideKeyResponse(keySetId, response); 1458 } catch (MediaPlayer.NoDrmSchemeException e) { 1459 throw new NoDrmSchemeException(e.getMessage()); 1460 } 1461 } 1462 1463 1464 /** 1465 * Restore persisted offline keys into a new session. keySetId identifies the 1466 * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}. 1467 * 1468 * @param keySetId identifies the saved key set to restore 1469 */ 1470 @Override 1471 public void restoreDrmKeys(@NonNull final byte[] keySetId) 1472 throws NoDrmSchemeException { 1473 addTask(new Task(CALL_COMPLETED_RESTORE_DRM_KEYS, false) { 1474 @Override 1475 void process() throws NoDrmSchemeException { 1476 try { 1477 mPlayer.restoreKeys(keySetId); 1478 } catch (MediaPlayer.NoDrmSchemeException e) { 1479 throw new NoDrmSchemeException(e.getMessage()); 1480 } 1481 } 1482 }); 1483 } 1484 1485 1486 /** 1487 * Read a DRM engine plugin String property value, given the property name string. 1488 * <p> 1489 * 1490 1491 * @param propertyName the property name 1492 * 1493 * Standard fields names are: 1494 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 1495 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 1496 */ 1497 @Override 1498 @NonNull 1499 public String getDrmPropertyString(@NonNull String propertyName) 1500 throws NoDrmSchemeException { 1501 try { 1502 return mPlayer.getDrmPropertyString(propertyName); 1503 } catch (MediaPlayer.NoDrmSchemeException e) { 1504 throw new NoDrmSchemeException(e.getMessage()); 1505 } 1506 } 1507 1508 1509 /** 1510 * Set a DRM engine plugin String property value. 1511 * <p> 1512 * 1513 * @param propertyName the property name 1514 * @param value the property value 1515 * 1516 * Standard fields names are: 1517 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 1518 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 1519 */ 1520 @Override 1521 public void setDrmPropertyString(@NonNull String propertyName, 1522 @NonNull String value) 1523 throws NoDrmSchemeException { 1524 try { 1525 mPlayer.setDrmPropertyString(propertyName, value); 1526 } catch (MediaPlayer.NoDrmSchemeException e) { 1527 throw new NoDrmSchemeException(e.getMessage()); 1528 } 1529 } 1530 1531 private void setPlaybackParamsInternal(final PlaybackParams params) { 1532 PlaybackParams current = mPlayer.getPlaybackParams(); 1533 mPlayer.setPlaybackParams(params); 1534 if (current.getSpeed() != params.getSpeed()) { 1535 notifyPlayerEvent(new PlayerEventNotifier() { 1536 @Override 1537 public void notify(PlayerEventCallback cb) { 1538 cb.onPlaybackSpeedChanged(MediaPlayer2Impl.this, params.getSpeed()); 1539 } 1540 }); 1541 } 1542 } 1543 1544 private void setPlayerState(@PlayerState final int state) { 1545 synchronized (mLock) { 1546 if (mPlayerState == state) { 1547 return; 1548 } 1549 mPlayerState = state; 1550 } 1551 notifyPlayerEvent(new PlayerEventNotifier() { 1552 @Override 1553 public void notify(PlayerEventCallback cb) { 1554 cb.onPlayerStateChanged(MediaPlayer2Impl.this, state); 1555 } 1556 }); 1557 } 1558 1559 private void setBufferingState(@BuffState final int state) { 1560 synchronized (mLock) { 1561 if (mBufferingState == state) { 1562 return; 1563 } 1564 mBufferingState = state; 1565 } 1566 notifyPlayerEvent(new PlayerEventNotifier() { 1567 @Override 1568 public void notify(PlayerEventCallback cb) { 1569 cb.onBufferingStateChanged(MediaPlayer2Impl.this, mCurrentDSD, state); 1570 } 1571 }); 1572 } 1573 1574 private void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) { 1575 List<Pair<Executor, MediaPlayer2EventCallback>> records; 1576 synchronized (mLock) { 1577 records = new ArrayList<>(mMp2EventCallbackRecords); 1578 } 1579 for (final Pair<Executor, MediaPlayer2EventCallback> record : records) { 1580 record.first.execute(new Runnable() { 1581 @Override 1582 public void run() { 1583 notifier.notify(record.second); 1584 } 1585 }); 1586 } 1587 } 1588 1589 private void notifyPlayerEvent(final PlayerEventNotifier notifier) { 1590 ArrayMap<PlayerEventCallback, Executor> map; 1591 synchronized (mLock) { 1592 map = new ArrayMap<>(mPlayerEventCallbackMap); 1593 } 1594 final int callbackCount = map.size(); 1595 for (int i = 0; i < callbackCount; i++) { 1596 final Executor executor = map.valueAt(i); 1597 final PlayerEventCallback cb = map.keyAt(i); 1598 executor.execute(new Runnable() { 1599 @Override 1600 public void run() { 1601 notifier.notify(cb); 1602 } 1603 }); 1604 } 1605 } 1606 1607 private void notifyDrmEvent(final DrmEventNotifier notifier) { 1608 List<Pair<Executor, DrmEventCallback>> records; 1609 synchronized (mLock) { 1610 records = new ArrayList<>(mDrmEventCallbackRecords); 1611 } 1612 for (final Pair<Executor, DrmEventCallback> record : records) { 1613 record.first.execute(new Runnable() { 1614 @Override 1615 public void run() { 1616 notifier.notify(record.second); 1617 } 1618 }); 1619 } 1620 } 1621 1622 private interface Mp2EventNotifier { 1623 void notify(MediaPlayer2EventCallback callback); 1624 } 1625 1626 private interface PlayerEventNotifier { 1627 void notify(PlayerEventCallback callback); 1628 } 1629 1630 private interface DrmEventNotifier { 1631 void notify(DrmEventCallback callback); 1632 } 1633 1634 private void setUpListeners() { 1635 mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 1636 @Override 1637 public void onPrepared(MediaPlayer mp) { 1638 setPlayerState(PLAYER_STATE_PAUSED); 1639 setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE); 1640 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1641 @Override 1642 public void notify(MediaPlayer2EventCallback callback) { 1643 callback.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PREPARED, 0); 1644 } 1645 }); 1646 notifyPlayerEvent(new PlayerEventNotifier() { 1647 @Override 1648 public void notify(PlayerEventCallback cb) { 1649 cb.onMediaPrepared(MediaPlayer2Impl.this, mCurrentDSD); 1650 } 1651 }); 1652 synchronized (mTaskLock) { 1653 if (mCurrentTask != null 1654 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE 1655 && mCurrentTask.mDSD == mCurrentDSD 1656 && mCurrentTask.mNeedToWaitForEventToComplete) { 1657 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 1658 mCurrentTask = null; 1659 processPendingTask_l(); 1660 } 1661 } 1662 } 1663 }); 1664 mPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { 1665 @Override 1666 public void onVideoSizeChanged(MediaPlayer mp, final int width, final int height) { 1667 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1668 @Override 1669 public void notify(MediaPlayer2EventCallback cb) { 1670 cb.onVideoSizeChanged(MediaPlayer2Impl.this, mCurrentDSD, width, height); 1671 } 1672 }); 1673 } 1674 }); 1675 mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { 1676 @Override 1677 public boolean onInfo(MediaPlayer mp, int what, int extra) { 1678 switch (what) { 1679 case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: 1680 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1681 @Override 1682 public void notify(MediaPlayer2EventCallback cb) { 1683 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, 1684 MEDIA_INFO_VIDEO_RENDERING_START, 0); 1685 } 1686 }); 1687 break; 1688 case MediaPlayer.MEDIA_INFO_BUFFERING_START: 1689 setBufferingState(BUFFERING_STATE_BUFFERING_AND_STARVED); 1690 break; 1691 case MediaPlayer.MEDIA_INFO_BUFFERING_END: 1692 setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE); 1693 break; 1694 } 1695 return false; 1696 } 1697 }); 1698 mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 1699 @Override 1700 public void onCompletion(MediaPlayer mp) { 1701 setPlayerState(PLAYER_STATE_PAUSED); 1702 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1703 @Override 1704 public void notify(MediaPlayer2EventCallback cb) { 1705 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE, 1706 0); 1707 } 1708 }); 1709 } 1710 }); 1711 mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 1712 @Override 1713 public boolean onError(MediaPlayer mp, final int what, final int extra) { 1714 setPlayerState(PLAYER_STATE_ERROR); 1715 setBufferingState(BUFFERING_STATE_UNKNOWN); 1716 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1717 @Override 1718 public void notify(MediaPlayer2EventCallback cb) { 1719 int w = sErrorEventMap.getOrDefault(what, MEDIA_ERROR_UNKNOWN); 1720 cb.onError(MediaPlayer2Impl.this, mCurrentDSD, w, extra); 1721 } 1722 }); 1723 return true; 1724 } 1725 }); 1726 mPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() { 1727 @Override 1728 public void onSeekComplete(MediaPlayer mp) { 1729 synchronized (mTaskLock) { 1730 if (mCurrentTask != null 1731 && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO 1732 && mCurrentTask.mNeedToWaitForEventToComplete) { 1733 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 1734 mCurrentTask = null; 1735 processPendingTask_l(); 1736 } 1737 } 1738 final long seekPos = getCurrentPosition(); 1739 notifyPlayerEvent(new PlayerEventNotifier() { 1740 @Override 1741 public void notify(PlayerEventCallback cb) { 1742 // TODO: The actual seeked position might be different from the 1743 // requested position. Clarify which one is expected here. 1744 cb.onSeekCompleted(MediaPlayer2Impl.this, seekPos); 1745 } 1746 }); 1747 } 1748 }); 1749 mPlayer.setOnTimedMetaDataAvailableListener( 1750 new MediaPlayer.OnTimedMetaDataAvailableListener() { 1751 @Override 1752 public void onTimedMetaDataAvailable(MediaPlayer mp, final TimedMetaData data) { 1753 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1754 @Override 1755 public void notify(MediaPlayer2EventCallback cb) { 1756 cb.onTimedMetaDataAvailable( 1757 MediaPlayer2Impl.this, mCurrentDSD, data); 1758 } 1759 }); 1760 } 1761 }); 1762 mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { 1763 @Override 1764 public boolean onInfo(MediaPlayer mp, final int what, final int extra) { 1765 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1766 @Override 1767 public void notify(MediaPlayer2EventCallback cb) { 1768 int w = sInfoEventMap.getOrDefault(what, MEDIA_INFO_UNKNOWN); 1769 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, w, extra); 1770 } 1771 }); 1772 return true; 1773 } 1774 }); 1775 mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { 1776 @Override 1777 public void onBufferingUpdate(MediaPlayer mp, final int percent) { 1778 if (percent >= 100) { 1779 setBufferingState(BUFFERING_STATE_BUFFERING_COMPLETE); 1780 } 1781 mBufferedPercentageCurrent.set(percent); 1782 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1783 @Override 1784 public void notify(MediaPlayer2EventCallback cb) { 1785 cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, 1786 MEDIA_INFO_BUFFERING_UPDATE, percent); 1787 } 1788 }); 1789 } 1790 }); 1791 mPlayer.setOnMediaTimeDiscontinuityListener( 1792 new MediaPlayer.OnMediaTimeDiscontinuityListener() { 1793 @Override 1794 public void onMediaTimeDiscontinuity( 1795 MediaPlayer mp, final MediaTimestamp timestamp) { 1796 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1797 @Override 1798 public void notify(MediaPlayer2EventCallback cb) { 1799 cb.onMediaTimeDiscontinuity( 1800 MediaPlayer2Impl.this, mCurrentDSD, timestamp); 1801 } 1802 }); 1803 } 1804 }); 1805 mPlayer.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() { 1806 @Override 1807 public void onSubtitleData(MediaPlayer mp, final SubtitleData data) { 1808 notifyMediaPlayer2Event(new Mp2EventNotifier() { 1809 @Override 1810 public void notify(MediaPlayer2EventCallback cb) { 1811 cb.onSubtitleData(MediaPlayer2Impl.this, mCurrentDSD, data); 1812 } 1813 }); 1814 } 1815 }); 1816 mPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() { 1817 @Override 1818 public void onDrmInfo(MediaPlayer mp, final MediaPlayer.DrmInfo drmInfo) { 1819 notifyDrmEvent(new DrmEventNotifier() { 1820 @Override 1821 public void notify(DrmEventCallback cb) { 1822 cb.onDrmInfo(MediaPlayer2Impl.this, mCurrentDSD, 1823 new DrmInfoImpl(drmInfo.getPssh(), drmInfo.getSupportedSchemes())); 1824 } 1825 }); 1826 } 1827 }); 1828 mPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() { 1829 @Override 1830 public void onDrmPrepared(MediaPlayer mp, final int status) { 1831 notifyDrmEvent(new DrmEventNotifier() { 1832 @Override 1833 public void notify(DrmEventCallback cb) { 1834 int s = sPrepareDrmStatusMap.getOrDefault( 1835 status, PREPARE_DRM_STATUS_PREPARATION_ERROR); 1836 cb.onDrmPrepared(MediaPlayer2Impl.this, mCurrentDSD, s); 1837 } 1838 }); 1839 } 1840 }); 1841 } 1842 1843 /** 1844 * Encapsulates the DRM properties of the source. 1845 */ 1846 public static final class DrmInfoImpl extends DrmInfo { 1847 private Map<UUID, byte[]> mMapPssh; 1848 private UUID[] mSupportedSchemes; 1849 1850 /** 1851 * Returns the PSSH info of the data source for each supported DRM scheme. 1852 */ 1853 @Override 1854 public Map<UUID, byte[]> getPssh() { 1855 return mMapPssh; 1856 } 1857 1858 /** 1859 * Returns the intersection of the data source and the device DRM schemes. 1860 * It effectively identifies the subset of the source's DRM schemes which 1861 * are supported by the device too. 1862 */ 1863 @Override 1864 public List<UUID> getSupportedSchemes() { 1865 return Arrays.asList(mSupportedSchemes); 1866 } 1867 1868 private DrmInfoImpl(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) { 1869 mMapPssh = pssh; 1870 mSupportedSchemes = supportedSchemes; 1871 } 1872 1873 private DrmInfoImpl(Parcel parcel) { 1874 Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize()); 1875 1876 int psshsize = parcel.readInt(); 1877 byte[] pssh = new byte[psshsize]; 1878 parcel.readByteArray(pssh); 1879 1880 Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh)); 1881 mMapPssh = parsePSSH(pssh, psshsize); 1882 Log.v(TAG, "DrmInfoImpl() PSSH: " + mMapPssh); 1883 1884 int supportedDRMsCount = parcel.readInt(); 1885 mSupportedSchemes = new UUID[supportedDRMsCount]; 1886 for (int i = 0; i < supportedDRMsCount; i++) { 1887 byte[] uuid = new byte[16]; 1888 parcel.readByteArray(uuid); 1889 1890 mSupportedSchemes[i] = bytesToUUID(uuid); 1891 1892 Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " 1893 + mSupportedSchemes[i]); 1894 } 1895 1896 Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize 1897 + " supportedDRMsCount: " + supportedDRMsCount); 1898 } 1899 1900 private DrmInfoImpl makeCopy() { 1901 return new DrmInfoImpl(this.mMapPssh, this.mSupportedSchemes); 1902 } 1903 1904 private String arrToHex(byte[] bytes) { 1905 String out = "0x"; 1906 for (int i = 0; i < bytes.length; i++) { 1907 out += String.format("%02x", bytes[i]); 1908 } 1909 1910 return out; 1911 } 1912 1913 private UUID bytesToUUID(byte[] uuid) { 1914 long msb = 0, lsb = 0; 1915 for (int i = 0; i < 8; i++) { 1916 msb |= (((long) uuid[i] & 0xff) << (8 * (7 - i))); 1917 lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i))); 1918 } 1919 1920 return new UUID(msb, lsb); 1921 } 1922 1923 private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { 1924 Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); 1925 1926 final int uuidSize = 16; 1927 final int dataLenSize = 4; 1928 1929 int len = psshsize; 1930 int numentries = 0; 1931 int i = 0; 1932 1933 while (len > 0) { 1934 if (len < uuidSize) { 1935 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1936 + "UUID: (%d < 16) pssh: %d", len, psshsize)); 1937 return null; 1938 } 1939 1940 byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize); 1941 UUID uuid = bytesToUUID(subset); 1942 i += uuidSize; 1943 len -= uuidSize; 1944 1945 // get data length 1946 if (len < 4) { 1947 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1948 + "datalen: (%d < 4) pssh: %d", len, psshsize)); 1949 return null; 1950 } 1951 1952 subset = Arrays.copyOfRange(pssh, i, i + dataLenSize); 1953 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) 1954 ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) 1955 | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) 1956 : ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) 1957 | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff); 1958 i += dataLenSize; 1959 len -= dataLenSize; 1960 1961 if (len < datalen) { 1962 Log.w(TAG, String.format("parsePSSH: len is too short to parse " 1963 + "data: (%d < %d) pssh: %d", len, datalen, psshsize)); 1964 return null; 1965 } 1966 1967 byte[] data = Arrays.copyOfRange(pssh, i, i + datalen); 1968 1969 // skip the data 1970 i += datalen; 1971 len -= datalen; 1972 1973 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", 1974 numentries, uuid, arrToHex(data), psshsize)); 1975 numentries++; 1976 result.put(uuid, data); 1977 } 1978 1979 return result; 1980 } 1981 1982 }; // DrmInfoImpl 1983 1984 /** 1985 * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). 1986 * Extends MediaDrm.MediaDrmException 1987 */ 1988 public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException { 1989 public NoDrmSchemeExceptionImpl(String detailMessage) { 1990 super(detailMessage); 1991 } 1992 } 1993 1994 /** 1995 * Thrown when the device requires DRM provisioning but the provisioning attempt has 1996 * failed due to a network error (Internet reachability, timeout, etc.). 1997 * Extends MediaDrm.MediaDrmException 1998 */ 1999 public static final class ProvisioningNetworkErrorExceptionImpl 2000 extends ProvisioningNetworkErrorException { 2001 public ProvisioningNetworkErrorExceptionImpl(String detailMessage) { 2002 super(detailMessage); 2003 } 2004 } 2005 2006 /** 2007 * Thrown when the device requires DRM provisioning but the provisioning attempt has 2008 * failed due to the provisioning server denying the request. 2009 * Extends MediaDrm.MediaDrmException 2010 */ 2011 public static final class ProvisioningServerErrorExceptionImpl 2012 extends ProvisioningServerErrorException { 2013 public ProvisioningServerErrorExceptionImpl(String detailMessage) { 2014 super(detailMessage); 2015 } 2016 } 2017 2018 private abstract class Task implements Runnable { 2019 private final int mMediaCallType; 2020 private final boolean mNeedToWaitForEventToComplete; 2021 private DataSourceDesc mDSD; 2022 2023 Task(int mediaCallType, boolean needToWaitForEventToComplete) { 2024 mMediaCallType = mediaCallType; 2025 mNeedToWaitForEventToComplete = needToWaitForEventToComplete; 2026 } 2027 2028 abstract void process() throws IOException, NoDrmSchemeException; 2029 2030 @Override 2031 public void run() { 2032 int status = CALL_STATUS_NO_ERROR; 2033 try { 2034 process(); 2035 } catch (IllegalStateException e) { 2036 status = CALL_STATUS_INVALID_OPERATION; 2037 } catch (IllegalArgumentException e) { 2038 status = CALL_STATUS_BAD_VALUE; 2039 } catch (SecurityException e) { 2040 status = CALL_STATUS_PERMISSION_DENIED; 2041 } catch (IOException e) { 2042 status = CALL_STATUS_ERROR_IO; 2043 } catch (NoDrmSchemeException e) { 2044 status = CALL_STATUS_NO_DRM_SCHEME; 2045 } catch (Exception e) { 2046 status = CALL_STATUS_ERROR_UNKNOWN; 2047 } 2048 synchronized (mSrcLock) { 2049 mDSD = mCurrentDSD; 2050 } 2051 2052 if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) { 2053 2054 sendCompleteNotification(status); 2055 2056 synchronized (mTaskLock) { 2057 mCurrentTask = null; 2058 processPendingTask_l(); 2059 } 2060 } 2061 } 2062 2063 private void sendCompleteNotification(final int status) { 2064 // In {@link #notifyWhenCommandLabelReached} case, a separate callback 2065 // {#link #onCommandLabelReached} is already called in {@code process()}. 2066 if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) { 2067 return; 2068 } 2069 notifyMediaPlayer2Event(new Mp2EventNotifier() { 2070 @Override 2071 public void notify(MediaPlayer2EventCallback cb) { 2072 cb.onCallCompleted( 2073 MediaPlayer2Impl.this, mDSD, mMediaCallType, status); 2074 } 2075 }); 2076 } 2077 }; 2078} 2079