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 */ 16 17 package android.media; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.ActivityThread; 23 import android.content.ContentProvider; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.res.AssetFileDescriptor; 27 import android.graphics.SurfaceTexture; 28 import android.media.SubtitleController.Anchor; 29 import android.media.SubtitleTrack.RenderingWidget; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.Parcel; 36 import android.os.Parcelable; 37 import android.os.PersistableBundle; 38 import android.os.PowerManager; 39 import android.os.Process; 40 import android.os.SystemProperties; 41 import android.provider.Settings; 42 import android.system.ErrnoException; 43 import android.system.Os; 44 import android.system.OsConstants; 45 import android.util.ArrayMap; 46 import android.util.Log; 47 import android.util.Pair; 48 import android.view.Surface; 49 import android.view.SurfaceHolder; 50 import android.widget.VideoView; 51 52 import com.android.internal.annotations.GuardedBy; 53 import com.android.internal.util.Preconditions; 54 55 import dalvik.system.CloseGuard; 56 57 import libcore.io.IoBridge; 58 import libcore.io.Streams; 59 60 import java.io.ByteArrayOutputStream; 61 import java.io.File; 62 import java.io.FileDescriptor; 63 import java.io.FileInputStream; 64 import java.io.IOException; 65 import java.io.InputStream; 66 import java.lang.ref.WeakReference; 67 import java.net.HttpCookie; 68 import java.net.HttpURLConnection; 69 import java.net.URL; 70 import java.nio.ByteOrder; 71 import java.util.ArrayList; 72 import java.util.Arrays; 73 import java.util.BitSet; 74 import java.util.HashMap; 75 import java.util.LinkedList; 76 import java.util.List; 77 import java.util.Map; 78 import java.util.Scanner; 79 import java.util.Set; 80 import java.util.UUID; 81 import java.util.Vector; 82 import java.util.concurrent.Executor; 83 import java.util.concurrent.atomic.AtomicInteger; 84 85 /** 86 * @hide 87 */ 88 public final class MediaPlayer2Impl extends MediaPlayer2 { 89 static { 90 System.loadLibrary("media2_jni"); 91 native_init(); 92 } 93 94 private final static String TAG = "MediaPlayer2Impl"; 95 96 private long mNativeContext; // accessed by native methods 97 private long mNativeSurfaceTexture; // accessed by native methods 98 private int mListenerContext; // accessed by native methods 99 private SurfaceHolder mSurfaceHolder; 100 private EventHandler mEventHandler; 101 private PowerManager.WakeLock mWakeLock = null; 102 private boolean mScreenOnWhilePlaying; 103 private boolean mStayAwake; 104 private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; 105 private final CloseGuard mGuard = CloseGuard.get(); 106 107 private final Object mSrcLock = new Object(); 108 //--- guarded by |mSrcLock| start 109 private long mSrcIdGenerator = 0; 110 private DataSourceDesc mCurrentDSD; 111 private long mCurrentSrcId = mSrcIdGenerator++; 112 private List<DataSourceDesc> mNextDSDs; 113 private long mNextSrcId = mSrcIdGenerator++; 114 private int mNextSourceState = NEXT_SOURCE_STATE_INIT; 115 private boolean mNextSourcePlayPending = false; 116 //--- guarded by |mSrcLock| end 117 118 private AtomicInteger mBufferedPercentageCurrent = new AtomicInteger(0); 119 private AtomicInteger mBufferedPercentageNext = new AtomicInteger(0); 120 private volatile float mVolume = 1.0f; 121 122 // Modular DRM 123 private final Object mDrmLock = new Object(); 124 //--- guarded by |mDrmLock| start 125 private UUID mDrmUUID; 126 private DrmInfoImpl mDrmInfoImpl; 127 private MediaDrm mDrmObj; 128 private byte[] mDrmSessionId; 129 private boolean mDrmInfoResolved; 130 private boolean mActiveDrmScheme; 131 private boolean mDrmConfigAllowed; 132 private boolean mDrmProvisioningInProgress; 133 private boolean mPrepareDrmInProgress; 134 private ProvisioningThread mDrmProvisioningThread; 135 //--- guarded by |mDrmLock| end 136 137 private HandlerThread mHandlerThread; 138 private final Handler mTaskHandler; 139 private final Object mTaskLock = new Object(); 140 @GuardedBy("mTaskLock") 141 private final List<Task> mPendingTasks = new LinkedList<>(); 142 @GuardedBy("mTaskLock") 143 private Task mCurrentTask; 144 145 /** 146 * Default constructor. 147 * <p>When done with the MediaPlayer2Impl, you should call {@link #close()}, 148 * to free the resources. If not released, too many MediaPlayer2Impl instances may 149 * result in an exception.</p> 150 */ 151 public MediaPlayer2Impl() { 152 Looper looper; 153 if ((looper = Looper.myLooper()) != null) { 154 mEventHandler = new EventHandler(this, looper); 155 } else if ((looper = Looper.getMainLooper()) != null) { 156 mEventHandler = new EventHandler(this, looper); 157 } else { 158 mEventHandler = null; 159 } 160 161 mHandlerThread = new HandlerThread("MediaPlayer2TaskThread"); 162 mHandlerThread.start(); 163 looper = mHandlerThread.getLooper(); 164 mTaskHandler = new Handler(looper); 165 166 mTimeProvider = new TimeProvider(this); 167 mOpenSubtitleSources = new Vector<InputStream>(); 168 mGuard.open("close"); 169 170 /* Native setup requires a weak reference to our object. 171 * It's easier to create it here than in C++. 172 */ 173 native_setup(new WeakReference<MediaPlayer2Impl>(this)); 174 } 175 176 /** 177 * Releases the resources held by this {@code MediaPlayer2} object. 178 * 179 * It is considered good practice to call this method when you're 180 * done using the MediaPlayer2. In particular, whenever an Activity 181 * of an application is paused (its onPause() method is called), 182 * or stopped (its onStop() method is called), this method should be 183 * invoked to release the MediaPlayer2 object, unless the application 184 * has a special need to keep the object around. In addition to 185 * unnecessary resources (such as memory and instances of codecs) 186 * being held, failure to call this method immediately if a 187 * MediaPlayer2 object is no longer needed may also lead to 188 * continuous battery consumption for mobile devices, and playback 189 * failure for other applications if no multiple instances of the 190 * same codec are supported on a device. Even if multiple instances 191 * of the same codec are supported, some performance degradation 192 * may be expected when unnecessary multiple instances are used 193 * at the same time. 194 * 195 * {@code close()} may be safely called after a prior {@code close()}. 196 * This class implements the Java {@code AutoCloseable} interface and 197 * may be used with try-with-resources. 198 */ 199 @Override 200 public void close() { 201 synchronized (mGuard) { 202 release(); 203 } 204 } 205 206 /** 207 * Starts or resumes playback. If playback had previously been paused, 208 * playback will continue from where it was paused. If playback had 209 * been stopped, or never started before, playback will start at the 210 * beginning. 211 * 212 * @throws IllegalStateException if it is called in an invalid state 213 */ 214 @Override 215 public void play() { 216 addTask(new Task(CALL_COMPLETED_PLAY, false) { 217 @Override 218 void process() { 219 stayAwake(true); 220 _start(); 221 } 222 }); 223 } 224 225 private native void _start() throws IllegalStateException; 226 227 /** 228 * Prepares the player for playback, asynchronously. 229 * 230 * After setting the datasource and the display surface, you need to either 231 * call prepare(). For streams, you should call prepare(), 232 * which returns immediately, rather than blocking until enough data has been 233 * buffered. 234 * 235 * @throws IllegalStateException if it is called in an invalid state 236 */ 237 @Override 238 public void prepare() { 239 addTask(new Task(CALL_COMPLETED_PREPARE, true) { 240 @Override 241 void process() { 242 _prepare(); 243 } 244 }); 245 } 246 247 public native void _prepare(); 248 249 /** 250 * Pauses playback. Call play() to resume. 251 * 252 * @throws IllegalStateException if the internal player engine has not been 253 * initialized. 254 */ 255 @Override 256 public void pause() { 257 addTask(new Task(CALL_COMPLETED_PAUSE, false) { 258 @Override 259 void process() { 260 stayAwake(false); 261 _pause(); 262 } 263 }); 264 } 265 266 private native void _pause() throws IllegalStateException; 267 268 /** 269 * Tries to play next data source if applicable. 270 * 271 * @throws IllegalStateException if it is called in an invalid state 272 */ 273 @Override 274 public void skipToNext() { 275 addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { 276 @Override 277 void process() { 278 // TODO: switch to next data source and play 279 } 280 }); 281 } 282 283 /** 284 * Gets the current playback position. 285 * 286 * @return the current position in milliseconds 287 */ 288 @Override 289 public native long getCurrentPosition(); 290 291 /** 292 * Gets the duration of the file. 293 * 294 * @return the duration in milliseconds, if no duration is available 295 * (for example, if streaming live content), -1 is returned. 296 */ 297 @Override 298 public native long getDuration(); 299 300 /** 301 * Gets the current buffered media source position received through progressive downloading. 302 * The received buffering percentage indicates how much of the content has been buffered 303 * or played. For example a buffering update of 80 percent when half the content 304 * has already been played indicates that the next 30 percent of the 305 * content to play has been buffered. 306 * 307 * @return the current buffered media source position in milliseconds 308 */ 309 @Override 310 public long getBufferedPosition() { 311 // Use cached buffered percent for now. 312 return getDuration() * mBufferedPercentageCurrent.get() / 100; 313 } 314 315 @Override 316 public @PlayerState int getPlayerState() { 317 int mediaplayer2State = getMediaPlayer2State(); 318 int playerState; 319 switch (mediaplayer2State) { 320 case MEDIAPLAYER2_STATE_IDLE: 321 playerState = PLAYER_STATE_IDLE; 322 break; 323 case MEDIAPLAYER2_STATE_PREPARED: 324 case MEDIAPLAYER2_STATE_PAUSED: 325 playerState = PLAYER_STATE_PAUSED; 326 break; 327 case MEDIAPLAYER2_STATE_PLAYING: 328 playerState = PLAYER_STATE_PLAYING; 329 break; 330 case MEDIAPLAYER2_STATE_ERROR: 331 default: 332 playerState = PLAYER_STATE_ERROR; 333 break; 334 } 335 336 return playerState; 337 } 338 339 /** 340 * Gets the current buffering state of the player. 341 * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already 342 * buffered. 343 */ 344 @Override 345 public @BuffState int getBufferingState() { 346 // TODO: use cached state or call native function. 347 return BUFFERING_STATE_UNKNOWN; 348 } 349 350 /** 351 * Sets the audio attributes for this MediaPlayer2. 352 * See {@link AudioAttributes} for how to build and configure an instance of this class. 353 * You must call this method before {@link #prepare()} in order 354 * for the audio attributes to become effective thereafter. 355 * @param attributes a non-null set of audio attributes 356 * @throws IllegalArgumentException if the attributes are null or invalid. 357 */ 358 @Override 359 public void setAudioAttributes(@NonNull AudioAttributes attributes) { 360 addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) { 361 @Override 362 void process() { 363 if (attributes == null) { 364 final String msg = "Cannot set AudioAttributes to null"; 365 throw new IllegalArgumentException(msg); 366 } 367 Parcel pattributes = Parcel.obtain(); 368 attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); 369 setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); 370 pattributes.recycle(); 371 } 372 }); 373 } 374 375 @Override 376 public @NonNull AudioAttributes getAudioAttributes() { 377 Parcel pattributes = getParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES); 378 AudioAttributes attributes = AudioAttributes.CREATOR.createFromParcel(pattributes); 379 pattributes.recycle(); 380 return attributes; 381 } 382 383 /** 384 * Sets the data source as described by a DataSourceDesc. 385 * 386 * @param dsd the descriptor of data source you want to play 387 * @throws IllegalStateException if it is called in an invalid state 388 * @throws NullPointerException if dsd is null 389 */ 390 @Override 391 public void setDataSource(@NonNull DataSourceDesc dsd) { 392 addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { 393 @Override 394 void process() { 395 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 396 // TODO: setDataSource could update exist data source 397 synchronized (mSrcLock) { 398 mCurrentDSD = dsd; 399 mCurrentSrcId = mSrcIdGenerator++; 400 try { 401 handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId); 402 } catch (IOException e) { 403 } 404 } 405 } 406 }); 407 } 408 409 /** 410 * Sets a single data source as described by a DataSourceDesc which will be played 411 * after current data source is finished. 412 * 413 * @param dsd the descriptor of data source you want to play after current one 414 * @throws IllegalStateException if it is called in an invalid state 415 * @throws NullPointerException if dsd is null 416 */ 417 @Override 418 public void setNextDataSource(@NonNull DataSourceDesc dsd) { 419 addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { 420 @Override 421 void process() { 422 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 423 synchronized (mSrcLock) { 424 mNextDSDs = new ArrayList<DataSourceDesc>(1); 425 mNextDSDs.add(dsd); 426 mNextSrcId = mSrcIdGenerator++; 427 mNextSourceState = NEXT_SOURCE_STATE_INIT; 428 mNextSourcePlayPending = false; 429 } 430 int state = getMediaPlayer2State(); 431 if (state != MEDIAPLAYER2_STATE_IDLE) { 432 synchronized (mSrcLock) { 433 prepareNextDataSource_l(); 434 } 435 } 436 } 437 }); 438 } 439 440 /** 441 * Sets a list of data sources to be played sequentially after current data source is done. 442 * 443 * @param dsds the list of data sources you want to play after current one 444 * @throws IllegalStateException if it is called in an invalid state 445 * @throws IllegalArgumentException if dsds is null or empty, or contains null DataSourceDesc 446 */ 447 @Override 448 public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) { 449 addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) { 450 @Override 451 void process() { 452 if (dsds == null || dsds.size() == 0) { 453 throw new IllegalArgumentException("data source list cannot be null or empty."); 454 } 455 for (DataSourceDesc dsd : dsds) { 456 if (dsd == null) { 457 throw new IllegalArgumentException( 458 "DataSourceDesc in the source list cannot be null."); 459 } 460 } 461 462 synchronized (mSrcLock) { 463 mNextDSDs = new ArrayList(dsds); 464 mNextSrcId = mSrcIdGenerator++; 465 mNextSourceState = NEXT_SOURCE_STATE_INIT; 466 mNextSourcePlayPending = false; 467 } 468 int state = getMediaPlayer2State(); 469 if (state != MEDIAPLAYER2_STATE_IDLE) { 470 synchronized (mSrcLock) { 471 prepareNextDataSource_l(); 472 } 473 } 474 } 475 }); 476 } 477 478 @Override 479 public @NonNull DataSourceDesc getCurrentDataSource() { 480 synchronized (mSrcLock) { 481 return mCurrentDSD; 482 } 483 } 484 485 /** 486 * Configures the player to loop on the current data source. 487 * @param loop true if the current data source is meant to loop. 488 */ 489 @Override 490 public void loopCurrent(boolean loop) { 491 addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { 492 @Override 493 void process() { 494 // TODO: set the looping mode, send notification 495 setLooping(loop); 496 } 497 }); 498 } 499 500 private native void setLooping(boolean looping); 501 502 /** 503 * Sets the playback speed. 504 * A value of 1.0f is the default playback value. 505 * A negative value indicates reverse playback, check {@link #isReversePlaybackSupported()} 506 * before using negative values.<br> 507 * After changing the playback speed, it is recommended to query the actual speed supported 508 * by the player, see {@link #getPlaybackSpeed()}. 509 * @param speed the desired playback speed 510 */ 511 @Override 512 public void setPlaybackSpeed(float speed) { 513 addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_SPEED, false) { 514 @Override 515 void process() { 516 _setPlaybackParams(getPlaybackParams().setSpeed(speed)); 517 } 518 }); 519 } 520 521 /** 522 * Returns the actual playback speed to be used by the player when playing. 523 * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}. 524 * @return the actual playback speed 525 */ 526 @Override 527 public float getPlaybackSpeed() { 528 return getPlaybackParams().getSpeed(); 529 } 530 531 /** 532 * Indicates whether reverse playback is supported. 533 * Reverse playback is indicated by negative playback speeds, see 534 * {@link #setPlaybackSpeed(float)}. 535 * @return true if reverse playback is supported. 536 */ 537 @Override 538 public boolean isReversePlaybackSupported() { 539 return false; 540 } 541 542 /** 543 * Sets the volume of the audio of the media to play, expressed as a linear multiplier 544 * on the audio samples. 545 * Note that this volume is specific to the player, and is separate from stream volume 546 * used across the platform.<br> 547 * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified 548 * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player. 549 * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. 550 */ 551 @Override 552 public void setPlayerVolume(float volume) { 553 addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { 554 @Override 555 void process() { 556 mVolume = volume; 557 _setVolume(volume, volume); 558 } 559 }); 560 } 561 562 private native void _setVolume(float leftVolume, float rightVolume); 563 564 /** 565 * Returns the current volume of this player to this player. 566 * Note that it does not take into account the associated stream volume. 567 * @return the player volume. 568 */ 569 @Override 570 public float getPlayerVolume() { 571 return mVolume; 572 } 573 574 /** 575 * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}. 576 */ 577 @Override 578 public float getMaxPlayerVolume() { 579 return 1.0f; 580 } 581 582 /** 583 * Adds a callback to be notified of events for this player. 584 * @param e the {@link Executor} to be used for the events. 585 * @param cb the callback to receive the events. 586 */ 587 @Override 588 public void registerPlayerEventCallback(@NonNull Executor e, 589 @NonNull PlayerEventCallback cb) { 590 } 591 592 /** 593 * Removes a previously registered callback for player events 594 * @param cb the callback to remove 595 */ 596 @Override 597 public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb) { 598 } 599 600 601 private static final int NEXT_SOURCE_STATE_ERROR = -1; 602 private static final int NEXT_SOURCE_STATE_INIT = 0; 603 private static final int NEXT_SOURCE_STATE_PREPARING = 1; 604 private static final int NEXT_SOURCE_STATE_PREPARED = 2; 605 606 /* 607 * Update the MediaPlayer2Impl SurfaceTexture. 608 * Call after setting a new display surface. 609 */ 610 private native void _setVideoSurface(Surface surface); 611 612 /* Do not change these values (starting with INVOKE_ID) without updating 613 * their counterparts in include/media/mediaplayer2.h! 614 */ 615 private static final int INVOKE_ID_GET_TRACK_INFO = 1; 616 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; 617 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; 618 private static final int INVOKE_ID_SELECT_TRACK = 4; 619 private static final int INVOKE_ID_DESELECT_TRACK = 5; 620 private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6; 621 private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; 622 623 /** 624 * Create a request parcel which can be routed to the native media 625 * player using {@link #invoke(Parcel, Parcel)}. The Parcel 626 * returned has the proper InterfaceToken set. The caller should 627 * not overwrite that token, i.e it can only append data to the 628 * Parcel. 629 * 630 * @return A parcel suitable to hold a request for the native 631 * player. 632 * {@hide} 633 */ 634 @Override 635 public Parcel newRequest() { 636 Parcel parcel = Parcel.obtain(); 637 return parcel; 638 } 639 640 /** 641 * Invoke a generic method on the native player using opaque 642 * parcels for the request and reply. Both payloads' format is a 643 * convention between the java caller and the native player. 644 * Must be called after setDataSource or setPlaylist to make sure a native player 645 * exists. On failure, a RuntimeException is thrown. 646 * 647 * @param request Parcel with the data for the extension. The 648 * caller must use {@link #newRequest()} to get one. 649 * 650 * @param reply Output parcel with the data returned by the 651 * native player. 652 * {@hide} 653 */ 654 @Override 655 public void invoke(Parcel request, Parcel reply) { 656 int retcode = native_invoke(request, reply); 657 reply.setDataPosition(0); 658 if (retcode != 0) { 659 throw new RuntimeException("failure code: " + retcode); 660 } 661 } 662 663 @Override 664 public void notifyWhenCommandLabelReached(Object label) { 665 addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { 666 @Override 667 void process() { 668 synchronized (mEventCbLock) { 669 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 670 cb.first.execute(() -> cb.second.onCommandLabelReached( 671 MediaPlayer2Impl.this, label)); 672 } 673 } 674 } 675 }); 676 } 677 678 /** 679 * Sets the {@link SurfaceHolder} to use for displaying the video 680 * portion of the media. 681 * 682 * Either a surface holder or surface must be set if a display or video sink 683 * is needed. Not calling this method or {@link #setSurface(Surface)} 684 * when playing back a video will result in only the audio track being played. 685 * A null surface holder or surface will result in only the audio track being 686 * played. 687 * 688 * @param sh the SurfaceHolder to use for video display 689 * @throws IllegalStateException if the internal player engine has not been 690 * initialized or has been released. 691 * @hide 692 */ 693 @Override 694 public void setDisplay(SurfaceHolder sh) { 695 mSurfaceHolder = sh; 696 Surface surface; 697 if (sh != null) { 698 surface = sh.getSurface(); 699 } else { 700 surface = null; 701 } 702 _setVideoSurface(surface); 703 updateSurfaceScreenOn(); 704 } 705 706 /** 707 * Sets the {@link Surface} to be used as the sink for the video portion of 708 * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but 709 * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a 710 * Surface will un-set any Surface or SurfaceHolder that was previously set. 711 * A null surface will result in only the audio track being played. 712 * 713 * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps 714 * returned from {@link SurfaceTexture#getTimestamp()} will have an 715 * unspecified zero point. These timestamps cannot be directly compared 716 * between different media sources, different instances of the same media 717 * source, or multiple runs of the same program. The timestamp is normally 718 * monotonically increasing and is unaffected by time-of-day adjustments, 719 * but it is reset when the position is set. 720 * 721 * @param surface The {@link Surface} to be used for the video portion of 722 * the media. 723 * @throws IllegalStateException if the internal player engine has not been 724 * initialized or has been released. 725 */ 726 @Override 727 public void setSurface(Surface surface) { 728 addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) { 729 @Override 730 void process() { 731 if (mScreenOnWhilePlaying && surface != null) { 732 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); 733 } 734 mSurfaceHolder = null; 735 _setVideoSurface(surface); 736 updateSurfaceScreenOn(); 737 } 738 }); 739 } 740 741 /** 742 * Sets video scaling mode. To make the target video scaling mode 743 * effective during playback, this method must be called after 744 * data source is set. If not called, the default video 745 * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}. 746 * 747 * <p> The supported video scaling modes are: 748 * <ul> 749 * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} 750 * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} 751 * </ul> 752 * 753 * @param mode target video scaling mode. Must be one of the supported 754 * video scaling modes; otherwise, IllegalArgumentException will be thrown. 755 * 756 * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT 757 * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 758 * @hide 759 */ 760 @Override 761 public void setVideoScalingMode(int mode) { 762 addTask(new Task(CALL_COMPLETED_SET_VIDEO_SCALING_MODE, false) { 763 @Override 764 void process() { 765 if (!isVideoScalingModeSupported(mode)) { 766 final String msg = "Scaling mode " + mode + " is not supported"; 767 throw new IllegalArgumentException(msg); 768 } 769 Parcel request = Parcel.obtain(); 770 Parcel reply = Parcel.obtain(); 771 try { 772 request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); 773 request.writeInt(mode); 774 invoke(request, reply); 775 } finally { 776 request.recycle(); 777 reply.recycle(); 778 } 779 } 780 }); 781 } 782 783 /** 784 * Discards all pending commands. 785 */ 786 @Override 787 public void clearPendingCommands() { 788 } 789 790 private void addTask(Task task) { 791 synchronized (mTaskLock) { 792 mPendingTasks.add(task); 793 processPendingTask_l(); 794 } 795 } 796 797 @GuardedBy("mTaskLock") 798 private void processPendingTask_l() { 799 if (mCurrentTask != null) { 800 return; 801 } 802 if (!mPendingTasks.isEmpty()) { 803 Task task = mPendingTasks.remove(0); 804 mCurrentTask = task; 805 mTaskHandler.post(task); 806 } 807 } 808 809 private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId) 810 throws IOException { 811 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); 812 813 switch (dsd.getType()) { 814 case DataSourceDesc.TYPE_CALLBACK: 815 handleDataSource(isCurrent, 816 srcId, 817 dsd.getMedia2DataSource()); 818 break; 819 820 case DataSourceDesc.TYPE_FD: 821 handleDataSource(isCurrent, 822 srcId, 823 dsd.getFileDescriptor(), 824 dsd.getFileDescriptorOffset(), 825 dsd.getFileDescriptorLength()); 826 break; 827 828 case DataSourceDesc.TYPE_URI: 829 handleDataSource(isCurrent, 830 srcId, 831 dsd.getUriContext(), 832 dsd.getUri(), 833 dsd.getUriHeaders(), 834 dsd.getUriCookies()); 835 break; 836 837 default: 838 break; 839 } 840 } 841 842 /** 843 * To provide cookies for the subsequent HTTP requests, you can install your own default cookie 844 * handler and use other variants of setDataSource APIs instead. Alternatively, you can use 845 * this API to pass the cookies as a list of HttpCookie. If the app has not installed 846 * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with 847 * the provided cookies. If the app has installed its own handler already, this API requires the 848 * handler to be of CookieManager type such that the API can update the managers CookieStore. 849 * 850 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 851 * but that can be changed with key/value pairs through the headers parameter with 852 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 853 * disallow or allow cross domain redirection. 854 * 855 * @throws IllegalArgumentException if cookies are provided and the installed handler is not 856 * a CookieManager 857 * @throws IllegalStateException if it is called in an invalid state 858 * @throws NullPointerException if context or uri is null 859 * @throws IOException if uri has a file scheme and an I/O error occurs 860 */ 861 private void handleDataSource( 862 boolean isCurrent, long srcId, 863 @NonNull Context context, @NonNull Uri uri, 864 @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) 865 throws IOException { 866 // The context and URI usually belong to the calling user. Get a resolver for that user 867 // and strip out the userId from the URI if present. 868 final ContentResolver resolver = context.getContentResolver(); 869 final String scheme = uri.getScheme(); 870 final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()); 871 if (ContentResolver.SCHEME_FILE.equals(scheme)) { 872 handleDataSource(isCurrent, srcId, uri.getPath(), null, null); 873 return; 874 } 875 876 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 877 && Settings.AUTHORITY.equals(authority)) { 878 // Try cached ringtone first since the actual provider may not be 879 // encryption aware, or it may be stored on CE media storage 880 final int type = RingtoneManager.getDefaultType(uri); 881 final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId()); 882 final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); 883 if (attemptDataSource(isCurrent, srcId, resolver, cacheUri)) { 884 return; 885 } 886 if (attemptDataSource(isCurrent, srcId, resolver, actualUri)) { 887 return; 888 } 889 handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies); 890 } else { 891 // Try requested Uri locally first, or fallback to media server 892 if (attemptDataSource(isCurrent, srcId, resolver, uri)) { 893 return; 894 } 895 handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies); 896 } 897 } 898 899 private boolean attemptDataSource( 900 boolean isCurrent, long srcId, ContentResolver resolver, Uri uri) { 901 try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) { 902 if (afd.getDeclaredLength() < 0) { 903 handleDataSource(isCurrent, 904 srcId, 905 afd.getFileDescriptor(), 906 0, 907 DataSourceDesc.LONG_MAX); 908 } else { 909 handleDataSource(isCurrent, 910 srcId, 911 afd.getFileDescriptor(), 912 afd.getStartOffset(), 913 afd.getDeclaredLength()); 914 } 915 return true; 916 } catch (NullPointerException | SecurityException | IOException ex) { 917 Log.w(TAG, "Couldn't open " + uri + ": " + ex); 918 return false; 919 } 920 } 921 922 private void handleDataSource( 923 boolean isCurrent, long srcId, 924 String path, Map<String, String> headers, List<HttpCookie> cookies) 925 throws IOException { 926 String[] keys = null; 927 String[] values = null; 928 929 if (headers != null) { 930 keys = new String[headers.size()]; 931 values = new String[headers.size()]; 932 933 int i = 0; 934 for (Map.Entry<String, String> entry: headers.entrySet()) { 935 keys[i] = entry.getKey(); 936 values[i] = entry.getValue(); 937 ++i; 938 } 939 } 940 handleDataSource(isCurrent, srcId, path, keys, values, cookies); 941 } 942 943 private void handleDataSource(boolean isCurrent, long srcId, 944 String path, String[] keys, String[] values, List<HttpCookie> cookies) 945 throws IOException { 946 final Uri uri = Uri.parse(path); 947 final String scheme = uri.getScheme(); 948 if ("file".equals(scheme)) { 949 path = uri.getPath(); 950 } else if (scheme != null) { 951 // handle non-file sources 952 nativeHandleDataSourceUrl( 953 isCurrent, 954 srcId, 955 Media2HTTPService.createHTTPService(path, cookies), 956 path, 957 keys, 958 values); 959 return; 960 } 961 962 final File file = new File(path); 963 if (file.exists()) { 964 FileInputStream is = new FileInputStream(file); 965 FileDescriptor fd = is.getFD(); 966 handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX); 967 is.close(); 968 } else { 969 throw new IOException("handleDataSource failed."); 970 } 971 } 972 973 private native void nativeHandleDataSourceUrl( 974 boolean isCurrent, long srcId, 975 Media2HTTPService httpService, String path, String[] keys, String[] values) 976 throws IOException; 977 978 /** 979 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 980 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 981 * to close the file descriptor. It is safe to do so as soon as this call returns. 982 * 983 * @throws IllegalStateException if it is called in an invalid state 984 * @throws IllegalArgumentException if fd is not a valid FileDescriptor 985 * @throws IOException if fd can not be read 986 */ 987 private void handleDataSource( 988 boolean isCurrent, long srcId, 989 FileDescriptor fd, long offset, long length) throws IOException { 990 nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length); 991 } 992 993 private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId, 994 FileDescriptor fd, long offset, long length) throws IOException; 995 996 /** 997 * @throws IllegalStateException if it is called in an invalid state 998 * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource 999 */ 1000 private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource) { 1001 nativeHandleDataSourceCallback(isCurrent, srcId, dataSource); 1002 } 1003 1004 private native void nativeHandleDataSourceCallback( 1005 boolean isCurrent, long srcId, Media2DataSource dataSource); 1006 1007 // This function shall be called with |mSrcLock| acquired. 1008 private void prepareNextDataSource_l() { 1009 if (mNextDSDs == null || mNextDSDs.isEmpty() 1010 || mNextSourceState != NEXT_SOURCE_STATE_INIT) { 1011 // There is no next source or it's in preparing or prepared state. 1012 return; 1013 } 1014 1015 try { 1016 mNextSourceState = NEXT_SOURCE_STATE_PREPARING; 1017 handleDataSource(false /* isCurrent */, mNextDSDs.get(0), mNextSrcId); 1018 } catch (Exception e) { 1019 Message msg2 = mEventHandler.obtainMessage( 1020 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 1021 final long nextSrcId = mNextSrcId; 1022 mEventHandler.post(new Runnable() { 1023 @Override 1024 public void run() { 1025 mEventHandler.handleMessage(msg2, nextSrcId); 1026 } 1027 }); 1028 } 1029 } 1030 1031 // This function shall be called with |mSrcLock| acquired. 1032 private void playNextDataSource_l() { 1033 if (mNextDSDs == null || mNextDSDs.isEmpty()) { 1034 return; 1035 } 1036 1037 if (mNextSourceState == NEXT_SOURCE_STATE_PREPARED) { 1038 // Switch to next source only when it's in prepared state. 1039 mCurrentDSD = mNextDSDs.get(0); 1040 mCurrentSrcId = mNextSrcId; 1041 mBufferedPercentageCurrent.set(mBufferedPercentageNext.get()); 1042 mNextDSDs.remove(0); 1043 mNextSrcId = mSrcIdGenerator++; // make it different from mCurrentSrcId 1044 mBufferedPercentageNext.set(0); 1045 mNextSourceState = NEXT_SOURCE_STATE_INIT; 1046 mNextSourcePlayPending = false; 1047 1048 long srcId = mCurrentSrcId; 1049 try { 1050 nativePlayNextDataSource(srcId); 1051 } catch (Exception e) { 1052 Message msg2 = mEventHandler.obtainMessage( 1053 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 1054 mEventHandler.post(new Runnable() { 1055 @Override 1056 public void run() { 1057 mEventHandler.handleMessage(msg2, srcId); 1058 } 1059 }); 1060 } 1061 1062 // Wait for MEDIA2_INFO_STARTED_AS_NEXT to prepare next source. 1063 } else { 1064 if (mNextSourceState == NEXT_SOURCE_STATE_INIT) { 1065 prepareNextDataSource_l(); 1066 } 1067 mNextSourcePlayPending = true; 1068 } 1069 } 1070 1071 private native void nativePlayNextDataSource(long srcId); 1072 1073 1074 private int getAudioStreamType() { 1075 if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { 1076 mStreamType = _getAudioStreamType(); 1077 } 1078 return mStreamType; 1079 } 1080 1081 private native int _getAudioStreamType() throws IllegalStateException; 1082 1083 /** 1084 * Stops playback after playback has been started or paused. 1085 * 1086 * @throws IllegalStateException if the internal player engine has not been 1087 * initialized. 1088 * #hide 1089 */ 1090 @Override 1091 public void stop() { 1092 stayAwake(false); 1093 _stop(); 1094 } 1095 1096 private native void _stop() throws IllegalStateException; 1097 1098 //-------------------------------------------------------------------------- 1099 // Explicit Routing 1100 //-------------------- 1101 private AudioDeviceInfo mPreferredDevice = null; 1102 1103 /** 1104 * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route 1105 * the output from this MediaPlayer2. 1106 * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source. 1107 * If deviceInfo is null, default routing is restored. 1108 * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and 1109 * does not correspond to a valid audio device. 1110 */ 1111 @Override 1112 public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) { 1113 if (deviceInfo != null && !deviceInfo.isSink()) { 1114 return false; 1115 } 1116 int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; 1117 boolean status = native_setOutputDevice(preferredDeviceId); 1118 if (status == true) { 1119 synchronized (this) { 1120 mPreferredDevice = deviceInfo; 1121 } 1122 } 1123 return status; 1124 } 1125 1126 /** 1127 * Returns the selected output specified by {@link #setPreferredDevice}. Note that this 1128 * is not guaranteed to correspond to the actual device being used for playback. 1129 */ 1130 @Override 1131 public AudioDeviceInfo getPreferredDevice() { 1132 synchronized (this) { 1133 return mPreferredDevice; 1134 } 1135 } 1136 1137 /** 1138 * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2 1139 * Note: The query is only valid if the MediaPlayer2 is currently playing. 1140 * If the player is not playing, the returned device can be null or correspond to previously 1141 * selected device when the player was last active. 1142 */ 1143 @Override 1144 public AudioDeviceInfo getRoutedDevice() { 1145 int deviceId = native_getRoutedDeviceId(); 1146 if (deviceId == 0) { 1147 return null; 1148 } 1149 AudioDeviceInfo[] devices = 1150 AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); 1151 for (int i = 0; i < devices.length; i++) { 1152 if (devices[i].getId() == deviceId) { 1153 return devices[i]; 1154 } 1155 } 1156 return null; 1157 } 1158 1159 /* 1160 * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler. 1161 */ 1162 @GuardedBy("mRoutingChangeListeners") 1163 private void enableNativeRoutingCallbacksLocked(boolean enabled) { 1164 if (mRoutingChangeListeners.size() == 0) { 1165 native_enableDeviceCallback(enabled); 1166 } 1167 } 1168 1169 /** 1170 * The list of AudioRouting.OnRoutingChangedListener interfaces added (with 1171 * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)} 1172 * by an app to receive (re)routing notifications. 1173 */ 1174 @GuardedBy("mRoutingChangeListeners") 1175 private ArrayMap<AudioRouting.OnRoutingChangedListener, 1176 NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>(); 1177 1178 /** 1179 * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing 1180 * changes on this MediaPlayer2. 1181 * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive 1182 * notifications of rerouting events. 1183 * @param handler Specifies the {@link Handler} object for the thread on which to execute 1184 * the callback. If <code>null</code>, the handler on the main looper will be used. 1185 */ 1186 @Override 1187 public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, 1188 Handler handler) { 1189 synchronized (mRoutingChangeListeners) { 1190 if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { 1191 enableNativeRoutingCallbacksLocked(true); 1192 mRoutingChangeListeners.put( 1193 listener, new NativeRoutingEventHandlerDelegate(this, listener, 1194 handler != null ? handler : mEventHandler)); 1195 } 1196 } 1197 } 1198 1199 /** 1200 * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added 1201 * to receive rerouting notifications. 1202 * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface 1203 * to remove. 1204 */ 1205 @Override 1206 public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) { 1207 synchronized (mRoutingChangeListeners) { 1208 if (mRoutingChangeListeners.containsKey(listener)) { 1209 mRoutingChangeListeners.remove(listener); 1210 enableNativeRoutingCallbacksLocked(false); 1211 } 1212 } 1213 } 1214 1215 private native final boolean native_setOutputDevice(int deviceId); 1216 private native final int native_getRoutedDeviceId(); 1217 private native final void native_enableDeviceCallback(boolean enabled); 1218 1219 /** 1220 * Set the low-level power management behavior for this MediaPlayer2. This 1221 * can be used when the MediaPlayer2 is not playing through a SurfaceHolder 1222 * set with {@link #setDisplay(SurfaceHolder)} and thus can use the 1223 * high-level {@link #setScreenOnWhilePlaying(boolean)} feature. 1224 * 1225 * <p>This function has the MediaPlayer2 access the low-level power manager 1226 * service to control the device's power usage while playing is occurring. 1227 * The parameter is a combination of {@link android.os.PowerManager} wake flags. 1228 * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} 1229 * permission. 1230 * By default, no attempt is made to keep the device awake during playback. 1231 * 1232 * @param context the Context to use 1233 * @param mode the power/wake mode to set 1234 * @see android.os.PowerManager 1235 * @hide 1236 */ 1237 @Override 1238 public void setWakeMode(Context context, int mode) { 1239 boolean washeld = false; 1240 1241 /* Disable persistant wakelocks in media player based on property */ 1242 if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) { 1243 Log.w(TAG, "IGNORING setWakeMode " + mode); 1244 return; 1245 } 1246 1247 if (mWakeLock != null) { 1248 if (mWakeLock.isHeld()) { 1249 washeld = true; 1250 mWakeLock.release(); 1251 } 1252 mWakeLock = null; 1253 } 1254 1255 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 1256 mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer2Impl.class.getName()); 1257 mWakeLock.setReferenceCounted(false); 1258 if (washeld) { 1259 mWakeLock.acquire(); 1260 } 1261 } 1262 1263 /** 1264 * Control whether we should use the attached SurfaceHolder to keep the 1265 * screen on while video playback is occurring. This is the preferred 1266 * method over {@link #setWakeMode} where possible, since it doesn't 1267 * require that the application have permission for low-level wake lock 1268 * access. 1269 * 1270 * @param screenOn Supply true to keep the screen on, false to allow it 1271 * to turn off. 1272 * @hide 1273 */ 1274 @Override 1275 public void setScreenOnWhilePlaying(boolean screenOn) { 1276 if (mScreenOnWhilePlaying != screenOn) { 1277 if (screenOn && mSurfaceHolder == null) { 1278 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); 1279 } 1280 mScreenOnWhilePlaying = screenOn; 1281 updateSurfaceScreenOn(); 1282 } 1283 } 1284 1285 private void stayAwake(boolean awake) { 1286 if (mWakeLock != null) { 1287 if (awake && !mWakeLock.isHeld()) { 1288 mWakeLock.acquire(); 1289 } else if (!awake && mWakeLock.isHeld()) { 1290 mWakeLock.release(); 1291 } 1292 } 1293 mStayAwake = awake; 1294 updateSurfaceScreenOn(); 1295 } 1296 1297 private void updateSurfaceScreenOn() { 1298 if (mSurfaceHolder != null) { 1299 mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); 1300 } 1301 } 1302 1303 /** 1304 * Returns the width of the video. 1305 * 1306 * @return the width of the video, or 0 if there is no video, 1307 * no display surface was set, or the width has not been determined 1308 * yet. The {@code MediaPlayer2EventCallback} can be registered via 1309 * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a 1310 * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the width 1311 * is available. 1312 */ 1313 @Override 1314 public native int getVideoWidth(); 1315 1316 /** 1317 * Returns the height of the video. 1318 * 1319 * @return the height of the video, or 0 if there is no video, 1320 * no display surface was set, or the height has not been determined 1321 * yet. The {@code MediaPlayer2EventCallback} can be registered via 1322 * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a 1323 * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the height 1324 * is available. 1325 */ 1326 @Override 1327 public native int getVideoHeight(); 1328 1329 /** 1330 * Return Metrics data about the current player. 1331 * 1332 * @return a {@link PersistableBundle} containing the set of attributes and values 1333 * available for the media being handled by this instance of MediaPlayer2 1334 * The attributes are descibed in {@link MetricsConstants}. 1335 * 1336 * Additional vendor-specific fields may also be present in 1337 * the return value. 1338 */ 1339 @Override 1340 public PersistableBundle getMetrics() { 1341 PersistableBundle bundle = native_getMetrics(); 1342 return bundle; 1343 } 1344 1345 private native PersistableBundle native_getMetrics(); 1346 1347 /** 1348 * Checks whether the MediaPlayer2 is playing. 1349 * 1350 * @return true if currently playing, false otherwise 1351 * @throws IllegalStateException if the internal player engine has not been 1352 * initialized or has been released. 1353 * @hide 1354 */ 1355 @Override 1356 public native boolean isPlaying(); 1357 1358 @Override 1359 public @MediaPlayer2State int getMediaPlayer2State() { 1360 return native_getMediaPlayer2State(); 1361 } 1362 1363 private native int native_getMediaPlayer2State(); 1364 1365 /** 1366 * Gets the current buffering management params used by the source component. 1367 * Calling it only after {@code setDataSource} has been called. 1368 * Each type of data source might have different set of default params. 1369 * 1370 * @return the current buffering management params used by the source component. 1371 * @throws IllegalStateException if the internal player engine has not been 1372 * initialized, or {@code setDataSource} has not been called. 1373 * @hide 1374 */ 1375 @Override 1376 @NonNull 1377 public native BufferingParams getBufferingParams(); 1378 1379 /** 1380 * Sets buffering management params. 1381 * The object sets its internal BufferingParams to the input, except that the input is 1382 * invalid or not supported. 1383 * Call it only after {@code setDataSource} has been called. 1384 * The input is a hint to MediaPlayer2. 1385 * 1386 * @param params the buffering management params. 1387 * 1388 * @throws IllegalStateException if the internal player engine has not been 1389 * initialized or has been released, or {@code setDataSource} has not been called. 1390 * @throws IllegalArgumentException if params is invalid or not supported. 1391 * @hide 1392 */ 1393 @Override 1394 public void setBufferingParams(@NonNull BufferingParams params) { 1395 addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) { 1396 @Override 1397 void process() { 1398 Preconditions.checkNotNull(params, "the BufferingParams cannot be null"); 1399 _setBufferingParams(params); 1400 } 1401 }); 1402 } 1403 1404 private native void _setBufferingParams(@NonNull BufferingParams params); 1405 1406 /** 1407 * Sets playback rate and audio mode. 1408 * 1409 * @param rate the ratio between desired playback rate and normal one. 1410 * @param audioMode audio playback mode. Must be one of the supported 1411 * audio modes. 1412 * 1413 * @throws IllegalStateException if the internal player engine has not been 1414 * initialized. 1415 * @throws IllegalArgumentException if audioMode is not supported. 1416 * 1417 * @hide 1418 */ 1419 @Override 1420 @NonNull 1421 public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) { 1422 PlaybackParams params = new PlaybackParams(); 1423 params.allowDefaults(); 1424 switch (audioMode) { 1425 case PLAYBACK_RATE_AUDIO_MODE_DEFAULT: 1426 params.setSpeed(rate).setPitch(1.0f); 1427 break; 1428 case PLAYBACK_RATE_AUDIO_MODE_STRETCH: 1429 params.setSpeed(rate).setPitch(1.0f) 1430 .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL); 1431 break; 1432 case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE: 1433 params.setSpeed(rate).setPitch(rate); 1434 break; 1435 default: 1436 final String msg = "Audio playback mode " + audioMode + " is not supported"; 1437 throw new IllegalArgumentException(msg); 1438 } 1439 return params; 1440 } 1441 1442 /** 1443 * Sets playback rate using {@link PlaybackParams}. The object sets its internal 1444 * PlaybackParams to the input, except that the object remembers previous speed 1445 * when input speed is zero. This allows the object to resume at previous speed 1446 * when play() is called. Calling it before the object is prepared does not change 1447 * the object state. After the object is prepared, calling it with zero speed is 1448 * equivalent to calling pause(). After the object is prepared, calling it with 1449 * non-zero speed is equivalent to calling play(). 1450 * 1451 * @param params the playback params. 1452 * 1453 * @throws IllegalStateException if the internal player engine has not been 1454 * initialized or has been released. 1455 * @throws IllegalArgumentException if params is not supported. 1456 */ 1457 @Override 1458 public void setPlaybackParams(@NonNull PlaybackParams params) { 1459 addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { 1460 @Override 1461 void process() { 1462 Preconditions.checkNotNull(params, "the PlaybackParams cannot be null"); 1463 _setPlaybackParams(params); 1464 } 1465 }); 1466 } 1467 1468 private native void _setPlaybackParams(@NonNull PlaybackParams params); 1469 1470 /** 1471 * Gets the playback params, containing the current playback rate. 1472 * 1473 * @return the playback params. 1474 * @throws IllegalStateException if the internal player engine has not been 1475 * initialized. 1476 */ 1477 @Override 1478 @NonNull 1479 public native PlaybackParams getPlaybackParams(); 1480 1481 /** 1482 * Sets A/V sync mode. 1483 * 1484 * @param params the A/V sync params to apply 1485 * 1486 * @throws IllegalStateException if the internal player engine has not been 1487 * initialized. 1488 * @throws IllegalArgumentException if params are not supported. 1489 */ 1490 @Override 1491 public void setSyncParams(@NonNull SyncParams params) { 1492 addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { 1493 @Override 1494 void process() { 1495 Preconditions.checkNotNull(params, "the SyncParams cannot be null"); 1496 _setSyncParams(params); 1497 } 1498 }); 1499 } 1500 1501 private native void _setSyncParams(@NonNull SyncParams params); 1502 1503 /** 1504 * Gets the A/V sync mode. 1505 * 1506 * @return the A/V sync params 1507 * 1508 * @throws IllegalStateException if the internal player engine has not been 1509 * initialized. 1510 */ 1511 @Override 1512 @NonNull 1513 public native SyncParams getSyncParams(); 1514 1515 /** 1516 * Moves the media to specified time position by considering the given mode. 1517 * <p> 1518 * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. 1519 * There is at most one active seekTo processed at any time. If there is a to-be-completed 1520 * seekTo, new seekTo requests will be queued in such a way that only the last request 1521 * is kept. When current seekTo is completed, the queued request will be processed if 1522 * that request is different from just-finished seekTo operation, i.e., the requested 1523 * position or mode is different. 1524 * 1525 * @param msec the offset in milliseconds from the start to seek to. 1526 * When seeking to the given time position, there is no guarantee that the data source 1527 * has a frame located at the position. When this happens, a frame nearby will be rendered. 1528 * If msec is negative, time position zero will be used. 1529 * If msec is larger than duration, duration will be used. 1530 * @param mode the mode indicating where exactly to seek to. 1531 * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame 1532 * that has a timestamp earlier than or the same as msec. Use 1533 * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame 1534 * that has a timestamp later than or the same as msec. Use 1535 * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame 1536 * that has a timestamp closest to or the same as msec. Use 1537 * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may 1538 * or may not be a sync frame but is closest to or the same as msec. 1539 * {@link #SEEK_CLOSEST} often has larger performance overhead compared 1540 * to the other options if there is no sync frame located at msec. 1541 * @throws IllegalStateException if the internal player engine has not been 1542 * initialized 1543 * @throws IllegalArgumentException if the mode is invalid. 1544 */ 1545 @Override 1546 public void seekTo(final long msec, @SeekMode int mode) { 1547 addTask(new Task(CALL_COMPLETED_SEEK_TO, true) { 1548 @Override 1549 void process() { 1550 if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { 1551 final String msg = "Illegal seek mode: " + mode; 1552 throw new IllegalArgumentException(msg); 1553 } 1554 // TODO: pass long to native, instead of truncating here. 1555 long posMs = msec; 1556 if (posMs > Integer.MAX_VALUE) { 1557 Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to " 1558 + Integer.MAX_VALUE); 1559 posMs = Integer.MAX_VALUE; 1560 } else if (posMs < Integer.MIN_VALUE) { 1561 Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to " 1562 + Integer.MIN_VALUE); 1563 posMs = Integer.MIN_VALUE; 1564 } 1565 _seekTo(posMs, mode); 1566 } 1567 }); 1568 } 1569 1570 private native final void _seekTo(long msec, int mode); 1571 1572 /** 1573 * Get current playback position as a {@link MediaTimestamp}. 1574 * <p> 1575 * The MediaTimestamp represents how the media time correlates to the system time in 1576 * a linear fashion using an anchor and a clock rate. During regular playback, the media 1577 * time moves fairly constantly (though the anchor frame may be rebased to a current 1578 * system time, the linear correlation stays steady). Therefore, this method does not 1579 * need to be called often. 1580 * <p> 1581 * To help users get current playback position, this method always anchors the timestamp 1582 * to the current {@link System#nanoTime system time}, so 1583 * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. 1584 * 1585 * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp 1586 * is available, e.g. because the media player has not been initialized. 1587 * 1588 * @see MediaTimestamp 1589 */ 1590 @Override 1591 @Nullable 1592 public MediaTimestamp getTimestamp() 1593 { 1594 try { 1595 // TODO: get the timestamp from native side 1596 return new MediaTimestamp( 1597 getCurrentPosition() * 1000L, 1598 System.nanoTime(), 1599 isPlaying() ? getPlaybackParams().getSpeed() : 0.f); 1600 } catch (IllegalStateException e) { 1601 return null; 1602 } 1603 } 1604 1605 /** 1606 * Gets the media metadata. 1607 * 1608 * @param update_only controls whether the full set of available 1609 * metadata is returned or just the set that changed since the 1610 * last call. See {@see #METADATA_UPDATE_ONLY} and {@see 1611 * #METADATA_ALL}. 1612 * 1613 * @param apply_filter if true only metadata that matches the 1614 * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see 1615 * #BYPASS_METADATA_FILTER}. 1616 * 1617 * @return The metadata, possibly empty. null if an error occured. 1618 // FIXME: unhide. 1619 * {@hide} 1620 */ 1621 @Override 1622 public Metadata getMetadata(final boolean update_only, 1623 final boolean apply_filter) { 1624 Parcel reply = Parcel.obtain(); 1625 Metadata data = new Metadata(); 1626 1627 if (!native_getMetadata(update_only, apply_filter, reply)) { 1628 reply.recycle(); 1629 return null; 1630 } 1631 1632 // Metadata takes over the parcel, don't recycle it unless 1633 // there is an error. 1634 if (!data.parse(reply)) { 1635 reply.recycle(); 1636 return null; 1637 } 1638 return data; 1639 } 1640 1641 /** 1642 * Set a filter for the metadata update notification and update 1643 * retrieval. The caller provides 2 set of metadata keys, allowed 1644 * and blocked. The blocked set always takes precedence over the 1645 * allowed one. 1646 * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as 1647 * shorthands to allow/block all or no metadata. 1648 * 1649 * By default, there is no filter set. 1650 * 1651 * @param allow Is the set of metadata the client is interested 1652 * in receiving new notifications for. 1653 * @param block Is the set of metadata the client is not interested 1654 * in receiving new notifications for. 1655 * @return The call status code. 1656 * 1657 // FIXME: unhide. 1658 * {@hide} 1659 */ 1660 @Override 1661 public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) { 1662 // Do our serialization manually instead of calling 1663 // Parcel.writeArray since the sets are made of the same type 1664 // we avoid paying the price of calling writeValue (used by 1665 // writeArray) which burns an extra int per element to encode 1666 // the type. 1667 Parcel request = newRequest(); 1668 1669 // The parcel starts already with an interface token. There 1670 // are 2 filters. Each one starts with a 4bytes number to 1671 // store the len followed by a number of int (4 bytes as well) 1672 // representing the metadata type. 1673 int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size()); 1674 1675 if (request.dataCapacity() < capacity) { 1676 request.setDataCapacity(capacity); 1677 } 1678 1679 request.writeInt(allow.size()); 1680 for(Integer t: allow) { 1681 request.writeInt(t); 1682 } 1683 request.writeInt(block.size()); 1684 for(Integer t: block) { 1685 request.writeInt(t); 1686 } 1687 return native_setMetadataFilter(request); 1688 } 1689 1690 /** 1691 * Resets the MediaPlayer2 to its uninitialized state. After calling 1692 * this method, you will have to initialize it again by setting the 1693 * data source and calling prepare(). 1694 */ 1695 @Override 1696 public void reset() { 1697 mSelectedSubtitleTrackIndex = -1; 1698 synchronized(mOpenSubtitleSources) { 1699 for (final InputStream is: mOpenSubtitleSources) { 1700 try { 1701 is.close(); 1702 } catch (IOException e) { 1703 } 1704 } 1705 mOpenSubtitleSources.clear(); 1706 } 1707 if (mSubtitleController != null) { 1708 mSubtitleController.reset(); 1709 } 1710 if (mTimeProvider != null) { 1711 mTimeProvider.close(); 1712 mTimeProvider = null; 1713 } 1714 1715 synchronized (mEventCbLock) { 1716 mEventCallbackRecords.clear(); 1717 } 1718 synchronized (mDrmEventCbLock) { 1719 mDrmEventCallbackRecords.clear(); 1720 } 1721 1722 stayAwake(false); 1723 _reset(); 1724 // make sure none of the listeners get called anymore 1725 if (mEventHandler != null) { 1726 mEventHandler.removeCallbacksAndMessages(null); 1727 } 1728 1729 synchronized (mIndexTrackPairs) { 1730 mIndexTrackPairs.clear(); 1731 mInbandTrackIndices.clear(); 1732 }; 1733 1734 resetDrmState(); 1735 } 1736 1737 private native void _reset(); 1738 1739 /** 1740 * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be 1741 * notified when the presentation time reaches (becomes greater than or equal to) 1742 * the value specified. 1743 * 1744 * @param mediaTimeUs presentation time to get timed event callback at 1745 * @hide 1746 */ 1747 @Override 1748 public void notifyAt(long mediaTimeUs) { 1749 _notifyAt(mediaTimeUs); 1750 } 1751 1752 private native void _notifyAt(long mediaTimeUs); 1753 1754 // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h 1755 private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400; 1756 /** 1757 * Sets the parameter indicated by key. 1758 * @param key key indicates the parameter to be set. 1759 * @param value value of the parameter to be set. 1760 * @return true if the parameter is set successfully, false otherwise 1761 */ 1762 private native boolean setParameter(int key, Parcel value); 1763 1764 private native Parcel getParameter(int key); 1765 1766 1767 /** 1768 * Checks whether the MediaPlayer2 is looping or non-looping. 1769 * 1770 * @return true if the MediaPlayer2 is currently looping, false otherwise 1771 * @hide 1772 */ 1773 @Override 1774 public native boolean isLooping(); 1775 1776 /** 1777 * Sets the audio session ID. 1778 * 1779 * @param sessionId the audio session ID. 1780 * The audio session ID is a system wide unique identifier for the audio stream played by 1781 * this MediaPlayer2 instance. 1782 * The primary use of the audio session ID is to associate audio effects to a particular 1783 * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, 1784 * this effect will be applied only to the audio content of media players within the same 1785 * audio session and not to the output mix. 1786 * When created, a MediaPlayer2 instance automatically generates its own audio session ID. 1787 * However, it is possible to force this player to be part of an already existing audio session 1788 * by calling this method. 1789 * This method must be called before one of the overloaded <code> setDataSource </code> methods. 1790 * @throws IllegalStateException if it is called in an invalid state 1791 * @throws IllegalArgumentException if the sessionId is invalid. 1792 */ 1793 @Override 1794 public void setAudioSessionId(int sessionId) { 1795 addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) { 1796 @Override 1797 void process() { 1798 _setAudioSessionId(sessionId); 1799 } 1800 }); 1801 } 1802 1803 private native void _setAudioSessionId(int sessionId); 1804 1805 /** 1806 * Returns the audio session ID. 1807 * 1808 * @return the audio session ID. {@see #setAudioSessionId(int)} 1809 * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed. 1810 */ 1811 @Override 1812 public native int getAudioSessionId(); 1813 1814 /** 1815 * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation 1816 * effect which can be applied on any sound source that directs a certain amount of its 1817 * energy to this effect. This amount is defined by setAuxEffectSendLevel(). 1818 * See {@link #setAuxEffectSendLevel(float)}. 1819 * <p>After creating an auxiliary effect (e.g. 1820 * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with 1821 * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method 1822 * to attach the player to the effect. 1823 * <p>To detach the effect from the player, call this method with a null effect id. 1824 * <p>This method must be called after one of the overloaded <code> setDataSource </code> 1825 * methods. 1826 * @param effectId system wide unique id of the effect to attach 1827 */ 1828 @Override 1829 public void attachAuxEffect(int effectId) { 1830 addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) { 1831 @Override 1832 void process() { 1833 _attachAuxEffect(effectId); 1834 } 1835 }); 1836 } 1837 1838 private native void _attachAuxEffect(int effectId); 1839 1840 /** 1841 * Sets the send level of the player to the attached auxiliary effect. 1842 * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. 1843 * <p>By default the send level is 0, so even if an effect is attached to the player 1844 * this method must be called for the effect to be applied. 1845 * <p>Note that the passed level value is a raw scalar. UI controls should be scaled 1846 * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, 1847 * so an appropriate conversion from linear UI input x to level is: 1848 * x == 0 -> level = 0 1849 * 0 < x <= R -> level = 10^(72*(x-R)/20/R) 1850 * @param level send level scalar 1851 */ 1852 @Override 1853 public void setAuxEffectSendLevel(float level) { 1854 addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { 1855 @Override 1856 void process() { 1857 _setAuxEffectSendLevel(level); 1858 } 1859 }); 1860 } 1861 1862 private native void _setAuxEffectSendLevel(float level); 1863 1864 /* 1865 * @param request Parcel destinated to the media player. 1866 * @param reply[out] Parcel that will contain the reply. 1867 * @return The status code. 1868 */ 1869 private native final int native_invoke(Parcel request, Parcel reply); 1870 1871 1872 /* 1873 * @param update_only If true fetch only the set of metadata that have 1874 * changed since the last invocation of getMetadata. 1875 * The set is built using the unfiltered 1876 * notifications the native player sent to the 1877 * MediaPlayer2Manager during that period of 1878 * time. If false, all the metadatas are considered. 1879 * @param apply_filter If true, once the metadata set has been built based on 1880 * the value update_only, the current filter is applied. 1881 * @param reply[out] On return contains the serialized 1882 * metadata. Valid only if the call was successful. 1883 * @return The status code. 1884 */ 1885 private native final boolean native_getMetadata(boolean update_only, 1886 boolean apply_filter, 1887 Parcel reply); 1888 1889 /* 1890 * @param request Parcel with the 2 serialized lists of allowed 1891 * metadata types followed by the one to be 1892 * dropped. Each list starts with an integer 1893 * indicating the number of metadata type elements. 1894 * @return The status code. 1895 */ 1896 private native final int native_setMetadataFilter(Parcel request); 1897 1898 private static native final void native_init(); 1899 private native final void native_setup(Object mediaplayer2_this); 1900 private native final void native_finalize(); 1901 1902 private static native final void native_stream_event_onTearDown( 1903 long nativeCallbackPtr, long userDataPtr); 1904 private static native final void native_stream_event_onStreamPresentationEnd( 1905 long nativeCallbackPtr, long userDataPtr); 1906 private static native final void native_stream_event_onStreamDataRequest( 1907 long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr); 1908 1909 /** 1910 * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. 1911 * 1912 * @see android.media.MediaPlayer2#getTrackInfo 1913 */ 1914 public static final class TrackInfoImpl extends TrackInfo { 1915 /** 1916 * Gets the track type. 1917 * @return TrackType which indicates if the track is video, audio, timed text. 1918 */ 1919 @Override 1920 public int getTrackType() { 1921 return mTrackType; 1922 } 1923 1924 /** 1925 * Gets the language code of the track. 1926 * @return a language code in either way of ISO-639-1 or ISO-639-2. 1927 * When the language is unknown or could not be determined, 1928 * ISO-639-2 language code, "und", is returned. 1929 */ 1930 @Override 1931 public String getLanguage() { 1932 String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); 1933 return language == null ? "und" : language; 1934 } 1935 1936 /** 1937 * Gets the {@link MediaFormat} of the track. If the format is 1938 * unknown or could not be determined, null is returned. 1939 */ 1940 @Override 1941 public MediaFormat getFormat() { 1942 if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT 1943 || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1944 return mFormat; 1945 } 1946 return null; 1947 } 1948 1949 final int mTrackType; 1950 final MediaFormat mFormat; 1951 1952 TrackInfoImpl(Parcel in) { 1953 mTrackType = in.readInt(); 1954 // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat 1955 // even for audio/video tracks, meaning we only set the mime and language. 1956 String mime = in.readString(); 1957 String language = in.readString(); 1958 mFormat = MediaFormat.createSubtitleFormat(mime, language); 1959 1960 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1961 mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); 1962 mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); 1963 mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); 1964 } 1965 } 1966 1967 /** @hide */ 1968 TrackInfoImpl(int type, MediaFormat format) { 1969 mTrackType = type; 1970 mFormat = format; 1971 } 1972 1973 /** 1974 * Flatten this object in to a Parcel. 1975 * 1976 * @param dest The Parcel in which the object should be written. 1977 * @param flags Additional flags about how the object should be written. 1978 * May be 0 or {@link android.os.Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. 1979 */ 1980 /* package private */ void writeToParcel(Parcel dest, int flags) { 1981 dest.writeInt(mTrackType); 1982 dest.writeString(getLanguage()); 1983 1984 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1985 dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); 1986 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); 1987 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); 1988 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); 1989 } 1990 } 1991 1992 @Override 1993 public String toString() { 1994 StringBuilder out = new StringBuilder(128); 1995 out.append(getClass().getName()); 1996 out.append('{'); 1997 switch (mTrackType) { 1998 case MEDIA_TRACK_TYPE_VIDEO: 1999 out.append("VIDEO"); 2000 break; 2001 case MEDIA_TRACK_TYPE_AUDIO: 2002 out.append("AUDIO"); 2003 break; 2004 case MEDIA_TRACK_TYPE_TIMEDTEXT: 2005 out.append("TIMEDTEXT"); 2006 break; 2007 case MEDIA_TRACK_TYPE_SUBTITLE: 2008 out.append("SUBTITLE"); 2009 break; 2010 default: 2011 out.append("UNKNOWN"); 2012 break; 2013 } 2014 out.append(", " + mFormat.toString()); 2015 out.append("}"); 2016 return out.toString(); 2017 } 2018 2019 /** 2020 * Used to read a TrackInfoImpl from a Parcel. 2021 */ 2022 /* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR 2023 = new Parcelable.Creator<TrackInfoImpl>() { 2024 @Override 2025 public TrackInfoImpl createFromParcel(Parcel in) { 2026 return new TrackInfoImpl(in); 2027 } 2028 2029 @Override 2030 public TrackInfoImpl[] newArray(int size) { 2031 return new TrackInfoImpl[size]; 2032 } 2033 }; 2034 2035 }; 2036 2037 // We would like domain specific classes with more informative names than the `first` and `second` 2038 // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise 2039 // we document the meanings of `first` and `second` here: 2040 // 2041 // Pair.first - inband track index; non-null iff representing an inband track. 2042 // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing 2043 // an inband subtitle track or any out-of-band track (subtitle or timedtext). 2044 private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>(); 2045 private BitSet mInbandTrackIndices = new BitSet(); 2046 2047 /** 2048 * Returns a List of track information. 2049 * 2050 * @return List of track info. The total number of tracks is the array length. 2051 * Must be called again if an external timed text source has been added after 2052 * addTimedTextSource method is called. 2053 * @throws IllegalStateException if it is called in an invalid state. 2054 */ 2055 @Override 2056 public List<TrackInfo> getTrackInfo() { 2057 TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl(); 2058 // add out-of-band tracks 2059 synchronized (mIndexTrackPairs) { 2060 TrackInfoImpl allTrackInfo[] = new TrackInfoImpl[mIndexTrackPairs.size()]; 2061 for (int i = 0; i < allTrackInfo.length; i++) { 2062 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2063 if (p.first != null) { 2064 // inband track 2065 allTrackInfo[i] = trackInfo[p.first]; 2066 } else { 2067 SubtitleTrack track = p.second; 2068 allTrackInfo[i] = new TrackInfoImpl(track.getTrackType(), track.getFormat()); 2069 } 2070 } 2071 return Arrays.asList(allTrackInfo); 2072 } 2073 } 2074 2075 private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException { 2076 Parcel request = Parcel.obtain(); 2077 Parcel reply = Parcel.obtain(); 2078 try { 2079 request.writeInt(INVOKE_ID_GET_TRACK_INFO); 2080 invoke(request, reply); 2081 TrackInfoImpl trackInfo[] = reply.createTypedArray(TrackInfoImpl.CREATOR); 2082 return trackInfo; 2083 } finally { 2084 request.recycle(); 2085 reply.recycle(); 2086 } 2087 } 2088 2089 /* 2090 * A helper function to check if the mime type is supported by media framework. 2091 */ 2092 private static boolean availableMimeTypeForExternalSource(String mimeType) { 2093 if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { 2094 return true; 2095 } 2096 return false; 2097 } 2098 2099 private SubtitleController mSubtitleController; 2100 2101 /** @hide */ 2102 @Override 2103 public void setSubtitleAnchor( 2104 SubtitleController controller, 2105 SubtitleController.Anchor anchor) { 2106 // TODO: create SubtitleController in MediaPlayer2 2107 mSubtitleController = controller; 2108 mSubtitleController.setAnchor(anchor); 2109 } 2110 2111 /** 2112 * The private version of setSubtitleAnchor is used internally to set mSubtitleController if 2113 * necessary when clients don't provide their own SubtitleControllers using the public version 2114 * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one). 2115 */ 2116 private synchronized void setSubtitleAnchor() { 2117 if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) { 2118 final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread"); 2119 thread.start(); 2120 Handler handler = new Handler(thread.getLooper()); 2121 handler.post(new Runnable() { 2122 @Override 2123 public void run() { 2124 Context context = ActivityThread.currentApplication(); 2125 mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer2Impl.this); 2126 mSubtitleController.setAnchor(new Anchor() { 2127 @Override 2128 public void setSubtitleWidget(RenderingWidget subtitleWidget) { 2129 } 2130 2131 @Override 2132 public Looper getSubtitleLooper() { 2133 return Looper.getMainLooper(); 2134 } 2135 }); 2136 thread.getLooper().quitSafely(); 2137 } 2138 }); 2139 try { 2140 thread.join(); 2141 } catch (InterruptedException e) { 2142 Thread.currentThread().interrupt(); 2143 Log.w(TAG, "failed to join SetSubtitleAnchorThread"); 2144 } 2145 } 2146 } 2147 2148 private int mSelectedSubtitleTrackIndex = -1; 2149 private Vector<InputStream> mOpenSubtitleSources; 2150 2151 private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() { 2152 @Override 2153 public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) { 2154 int index = data.getTrackIndex(); 2155 synchronized (mIndexTrackPairs) { 2156 for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { 2157 if (p.first != null && p.first == index && p.second != null) { 2158 // inband subtitle track that owns data 2159 SubtitleTrack track = p.second; 2160 track.onData(data); 2161 } 2162 } 2163 } 2164 } 2165 }; 2166 2167 /** @hide */ 2168 @Override 2169 public void onSubtitleTrackSelected(SubtitleTrack track) { 2170 if (mSelectedSubtitleTrackIndex >= 0) { 2171 try { 2172 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); 2173 } catch (IllegalStateException e) { 2174 } 2175 mSelectedSubtitleTrackIndex = -1; 2176 } 2177 setOnSubtitleDataListener(null); 2178 if (track == null) { 2179 return; 2180 } 2181 2182 synchronized (mIndexTrackPairs) { 2183 for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { 2184 if (p.first != null && p.second == track) { 2185 // inband subtitle track that is selected 2186 mSelectedSubtitleTrackIndex = p.first; 2187 break; 2188 } 2189 } 2190 } 2191 2192 if (mSelectedSubtitleTrackIndex >= 0) { 2193 try { 2194 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); 2195 } catch (IllegalStateException e) { 2196 } 2197 setOnSubtitleDataListener(mSubtitleDataListener); 2198 } 2199 // no need to select out-of-band tracks 2200 } 2201 2202 /** @hide */ 2203 @Override 2204 public void addSubtitleSource(InputStream is, MediaFormat format) 2205 throws IllegalStateException 2206 { 2207 final InputStream fIs = is; 2208 final MediaFormat fFormat = format; 2209 2210 if (is != null) { 2211 // Ensure all input streams are closed. It is also a handy 2212 // way to implement timeouts in the future. 2213 synchronized(mOpenSubtitleSources) { 2214 mOpenSubtitleSources.add(is); 2215 } 2216 } else { 2217 Log.w(TAG, "addSubtitleSource called with null InputStream"); 2218 } 2219 2220 getMediaTimeProvider(); 2221 2222 // process each subtitle in its own thread 2223 final HandlerThread thread = new HandlerThread("SubtitleReadThread", 2224 Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); 2225 thread.start(); 2226 Handler handler = new Handler(thread.getLooper()); 2227 handler.post(new Runnable() { 2228 private int addTrack() { 2229 if (fIs == null || mSubtitleController == null) { 2230 return MEDIA_INFO_UNSUPPORTED_SUBTITLE; 2231 } 2232 2233 SubtitleTrack track = mSubtitleController.addTrack(fFormat); 2234 if (track == null) { 2235 return MEDIA_INFO_UNSUPPORTED_SUBTITLE; 2236 } 2237 2238 // TODO: do the conversion in the subtitle track 2239 Scanner scanner = new Scanner(fIs, "UTF-8"); 2240 String contents = scanner.useDelimiter("\\A").next(); 2241 synchronized(mOpenSubtitleSources) { 2242 mOpenSubtitleSources.remove(fIs); 2243 } 2244 scanner.close(); 2245 synchronized (mIndexTrackPairs) { 2246 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); 2247 } 2248 Handler h = mTimeProvider.mEventHandler; 2249 int what = TimeProvider.NOTIFY; 2250 int arg1 = TimeProvider.NOTIFY_TRACK_DATA; 2251 Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes()); 2252 Message m = h.obtainMessage(what, arg1, 0, trackData); 2253 h.sendMessage(m); 2254 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; 2255 } 2256 2257 public void run() { 2258 int res = addTrack(); 2259 if (mEventHandler != null) { 2260 Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); 2261 mEventHandler.sendMessage(m); 2262 } 2263 thread.getLooper().quitSafely(); 2264 } 2265 }); 2266 } 2267 2268 private void scanInternalSubtitleTracks() { 2269 setSubtitleAnchor(); 2270 2271 populateInbandTracks(); 2272 2273 if (mSubtitleController != null) { 2274 mSubtitleController.selectDefaultTrack(); 2275 } 2276 } 2277 2278 private void populateInbandTracks() { 2279 TrackInfoImpl[] tracks = getInbandTrackInfoImpl(); 2280 synchronized (mIndexTrackPairs) { 2281 for (int i = 0; i < tracks.length; i++) { 2282 if (mInbandTrackIndices.get(i)) { 2283 continue; 2284 } else { 2285 mInbandTrackIndices.set(i); 2286 } 2287 2288 // newly appeared inband track 2289 if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { 2290 SubtitleTrack track = mSubtitleController.addTrack( 2291 tracks[i].getFormat()); 2292 mIndexTrackPairs.add(Pair.create(i, track)); 2293 } else { 2294 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null)); 2295 } 2296 } 2297 } 2298 } 2299 2300 /* TODO: Limit the total number of external timed text source to a reasonable number. 2301 */ 2302 /** 2303 * Adds an external timed text source file. 2304 * 2305 * Currently supported format is SubRip with the file extension .srt, case insensitive. 2306 * Note that a single external timed text source may contain multiple tracks in it. 2307 * One can find the total number of available tracks using {@link #getTrackInfo()} to see what 2308 * additional tracks become available after this method call. 2309 * 2310 * @param path The file path of external timed text source file. 2311 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2312 * @throws IOException if the file cannot be accessed or is corrupted. 2313 * @throws IllegalArgumentException if the mimeType is not supported. 2314 * @throws IllegalStateException if called in an invalid state. 2315 * @hide 2316 */ 2317 @Override 2318 public void addTimedTextSource(String path, String mimeType) 2319 throws IOException { 2320 if (!availableMimeTypeForExternalSource(mimeType)) { 2321 final String msg = "Illegal mimeType for timed text source: " + mimeType; 2322 throw new IllegalArgumentException(msg); 2323 } 2324 2325 File file = new File(path); 2326 if (file.exists()) { 2327 FileInputStream is = new FileInputStream(file); 2328 FileDescriptor fd = is.getFD(); 2329 addTimedTextSource(fd, mimeType); 2330 is.close(); 2331 } else { 2332 // We do not support the case where the path is not a file. 2333 throw new IOException(path); 2334 } 2335 } 2336 2337 2338 /** 2339 * Adds an external timed text source file (Uri). 2340 * 2341 * Currently supported format is SubRip with the file extension .srt, case insensitive. 2342 * Note that a single external timed text source may contain multiple tracks in it. 2343 * One can find the total number of available tracks using {@link #getTrackInfo()} to see what 2344 * additional tracks become available after this method call. 2345 * 2346 * @param context the Context to use when resolving the Uri 2347 * @param uri the Content URI of the data you want to play 2348 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2349 * @throws IOException if the file cannot be accessed or is corrupted. 2350 * @throws IllegalArgumentException if the mimeType is not supported. 2351 * @throws IllegalStateException if called in an invalid state. 2352 * @hide 2353 */ 2354 @Override 2355 public void addTimedTextSource(Context context, Uri uri, String mimeType) 2356 throws IOException { 2357 String scheme = uri.getScheme(); 2358 if(scheme == null || scheme.equals("file")) { 2359 addTimedTextSource(uri.getPath(), mimeType); 2360 return; 2361 } 2362 2363 AssetFileDescriptor fd = null; 2364 try { 2365 ContentResolver resolver = context.getContentResolver(); 2366 fd = resolver.openAssetFileDescriptor(uri, "r"); 2367 if (fd == null) { 2368 return; 2369 } 2370 addTimedTextSource(fd.getFileDescriptor(), mimeType); 2371 return; 2372 } catch (SecurityException ex) { 2373 } catch (IOException ex) { 2374 } finally { 2375 if (fd != null) { 2376 fd.close(); 2377 } 2378 } 2379 } 2380 2381 /** 2382 * Adds an external timed text source file (FileDescriptor). 2383 * 2384 * It is the caller's responsibility to close the file descriptor. 2385 * It is safe to do so as soon as this call returns. 2386 * 2387 * Currently supported format is SubRip. Note that a single external timed text source may 2388 * contain multiple tracks in it. One can find the total number of available tracks 2389 * using {@link #getTrackInfo()} to see what additional tracks become available 2390 * after this method call. 2391 * 2392 * @param fd the FileDescriptor for the file you want to play 2393 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2394 * @throws IllegalArgumentException if the mimeType is not supported. 2395 * @throws IllegalStateException if called in an invalid state. 2396 * @hide 2397 */ 2398 @Override 2399 public void addTimedTextSource(FileDescriptor fd, String mimeType) { 2400 // intentionally less than LONG_MAX 2401 addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType); 2402 } 2403 2404 /** 2405 * Adds an external timed text file (FileDescriptor). 2406 * 2407 * It is the caller's responsibility to close the file descriptor. 2408 * It is safe to do so as soon as this call returns. 2409 * 2410 * Currently supported format is SubRip. Note that a single external timed text source may 2411 * contain multiple tracks in it. One can find the total number of available tracks 2412 * using {@link #getTrackInfo()} to see what additional tracks become available 2413 * after this method call. 2414 * 2415 * @param fd the FileDescriptor for the file you want to play 2416 * @param offset the offset into the file where the data to be played starts, in bytes 2417 * @param length the length in bytes of the data to be played 2418 * @param mime The mime type of the file. Must be one of the mime types listed above. 2419 * @throws IllegalArgumentException if the mimeType is not supported. 2420 * @throws IllegalStateException if called in an invalid state. 2421 * @hide 2422 */ 2423 @Override 2424 public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) { 2425 if (!availableMimeTypeForExternalSource(mime)) { 2426 throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); 2427 } 2428 2429 final FileDescriptor dupedFd; 2430 try { 2431 dupedFd = Os.dup(fd); 2432 } catch (ErrnoException ex) { 2433 Log.e(TAG, ex.getMessage(), ex); 2434 throw new RuntimeException(ex); 2435 } 2436 2437 final MediaFormat fFormat = new MediaFormat(); 2438 fFormat.setString(MediaFormat.KEY_MIME, mime); 2439 fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); 2440 2441 // A MediaPlayer2 created by a VideoView should already have its mSubtitleController set. 2442 if (mSubtitleController == null) { 2443 setSubtitleAnchor(); 2444 } 2445 2446 if (!mSubtitleController.hasRendererFor(fFormat)) { 2447 // test and add not atomic 2448 Context context = ActivityThread.currentApplication(); 2449 mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); 2450 } 2451 final SubtitleTrack track = mSubtitleController.addTrack(fFormat); 2452 synchronized (mIndexTrackPairs) { 2453 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); 2454 } 2455 2456 getMediaTimeProvider(); 2457 2458 final long offset2 = offset; 2459 final long length2 = length; 2460 final HandlerThread thread = new HandlerThread( 2461 "TimedTextReadThread", 2462 Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); 2463 thread.start(); 2464 Handler handler = new Handler(thread.getLooper()); 2465 handler.post(new Runnable() { 2466 private int addTrack() { 2467 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 2468 try { 2469 Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET); 2470 byte[] buffer = new byte[4096]; 2471 for (long total = 0; total < length2;) { 2472 int bytesToRead = (int) Math.min(buffer.length, length2 - total); 2473 int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead); 2474 if (bytes < 0) { 2475 break; 2476 } else { 2477 bos.write(buffer, 0, bytes); 2478 total += bytes; 2479 } 2480 } 2481 Handler h = mTimeProvider.mEventHandler; 2482 int what = TimeProvider.NOTIFY; 2483 int arg1 = TimeProvider.NOTIFY_TRACK_DATA; 2484 Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray()); 2485 Message m = h.obtainMessage(what, arg1, 0, trackData); 2486 h.sendMessage(m); 2487 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; 2488 } catch (Exception e) { 2489 Log.e(TAG, e.getMessage(), e); 2490 return MEDIA_INFO_TIMED_TEXT_ERROR; 2491 } finally { 2492 try { 2493 Os.close(dupedFd); 2494 } catch (ErrnoException e) { 2495 Log.e(TAG, e.getMessage(), e); 2496 } 2497 } 2498 } 2499 2500 public void run() { 2501 int res = addTrack(); 2502 if (mEventHandler != null) { 2503 Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); 2504 mEventHandler.sendMessage(m); 2505 } 2506 thread.getLooper().quitSafely(); 2507 } 2508 }); 2509 } 2510 2511 /** 2512 * Returns the index of the audio, video, or subtitle track currently selected for playback, 2513 * The return value is an index into the array returned by {@link #getTrackInfo()}, and can 2514 * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. 2515 * 2516 * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, 2517 * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or 2518 * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} 2519 * @return index of the audio, video, or subtitle track currently selected for playback; 2520 * a negative integer is returned when there is no selected track for {@code trackType} or 2521 * when {@code trackType} is not one of audio, video, or subtitle. 2522 * @throws IllegalStateException if called after {@link #close()} 2523 * 2524 * @see #getTrackInfo() 2525 * @see #selectTrack(int) 2526 * @see #deselectTrack(int) 2527 */ 2528 @Override 2529 public int getSelectedTrack(int trackType) { 2530 if (mSubtitleController != null 2531 && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE 2532 || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) { 2533 SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack(); 2534 if (subtitleTrack != null) { 2535 synchronized (mIndexTrackPairs) { 2536 for (int i = 0; i < mIndexTrackPairs.size(); i++) { 2537 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2538 if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) { 2539 return i; 2540 } 2541 } 2542 } 2543 } 2544 } 2545 2546 Parcel request = Parcel.obtain(); 2547 Parcel reply = Parcel.obtain(); 2548 try { 2549 request.writeInt(INVOKE_ID_GET_SELECTED_TRACK); 2550 request.writeInt(trackType); 2551 invoke(request, reply); 2552 int inbandTrackIndex = reply.readInt(); 2553 synchronized (mIndexTrackPairs) { 2554 for (int i = 0; i < mIndexTrackPairs.size(); i++) { 2555 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2556 if (p.first != null && p.first == inbandTrackIndex) { 2557 return i; 2558 } 2559 } 2560 } 2561 return -1; 2562 } finally { 2563 request.recycle(); 2564 reply.recycle(); 2565 } 2566 } 2567 2568 /** 2569 * Selects a track. 2570 * <p> 2571 * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception. 2572 * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately. 2573 * If a MediaPlayer2 is not in Started state, it just marks the track to be played. 2574 * </p> 2575 * <p> 2576 * In any valid state, if it is called multiple times on the same type of track (ie. Video, 2577 * Audio, Timed Text), the most recent one will be chosen. 2578 * </p> 2579 * <p> 2580 * The first audio and video tracks are selected by default if available, even though 2581 * this method is not called. However, no timed text track will be selected until 2582 * this function is called. 2583 * </p> 2584 * <p> 2585 * Currently, only timed text tracks or audio tracks can be selected via this method. 2586 * In addition, the support for selecting an audio track at runtime is pretty limited 2587 * in that an audio track can only be selected in the <em>Prepared</em> state. 2588 * </p> 2589 * @param index the index of the track to be selected. The valid range of the index 2590 * is 0..total number of track - 1. The total number of tracks as well as the type of 2591 * each individual track can be found by calling {@link #getTrackInfo()} method. 2592 * @throws IllegalStateException if called in an invalid state. 2593 * 2594 * @see android.media.MediaPlayer2#getTrackInfo 2595 */ 2596 @Override 2597 public void selectTrack(int index) { 2598 addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { 2599 @Override 2600 void process() { 2601 selectOrDeselectTrack(index, true /* select */); 2602 } 2603 }); 2604 } 2605 2606 /** 2607 * Deselect a track. 2608 * <p> 2609 * Currently, the track must be a timed text track and no audio or video tracks can be 2610 * deselected. If the timed text track identified by index has not been 2611 * selected before, it throws an exception. 2612 * </p> 2613 * @param index the index of the track to be deselected. The valid range of the index 2614 * is 0..total number of tracks - 1. The total number of tracks as well as the type of 2615 * each individual track can be found by calling {@link #getTrackInfo()} method. 2616 * @throws IllegalStateException if called in an invalid state. 2617 * 2618 * @see android.media.MediaPlayer2#getTrackInfo 2619 */ 2620 @Override 2621 public void deselectTrack(int index) { 2622 addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { 2623 @Override 2624 void process() { 2625 selectOrDeselectTrack(index, false /* select */); 2626 } 2627 }); 2628 } 2629 2630 private void selectOrDeselectTrack(int index, boolean select) 2631 throws IllegalStateException { 2632 // handle subtitle track through subtitle controller 2633 populateInbandTracks(); 2634 2635 Pair<Integer,SubtitleTrack> p = null; 2636 try { 2637 p = mIndexTrackPairs.get(index); 2638 } catch (ArrayIndexOutOfBoundsException e) { 2639 // ignore bad index 2640 return; 2641 } 2642 2643 SubtitleTrack track = p.second; 2644 if (track == null) { 2645 // inband (de)select 2646 selectOrDeselectInbandTrack(p.first, select); 2647 return; 2648 } 2649 2650 if (mSubtitleController == null) { 2651 return; 2652 } 2653 2654 if (!select) { 2655 // out-of-band deselect 2656 if (mSubtitleController.getSelectedTrack() == track) { 2657 mSubtitleController.selectTrack(null); 2658 } else { 2659 Log.w(TAG, "trying to deselect track that was not selected"); 2660 } 2661 return; 2662 } 2663 2664 // out-of-band select 2665 if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) { 2666 int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); 2667 synchronized (mIndexTrackPairs) { 2668 if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) { 2669 Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex); 2670 if (p2.first != null && p2.second == null) { 2671 // deselect inband counterpart 2672 selectOrDeselectInbandTrack(p2.first, false); 2673 } 2674 } 2675 } 2676 } 2677 mSubtitleController.selectTrack(track); 2678 } 2679 2680 private void selectOrDeselectInbandTrack(int index, boolean select) 2681 throws IllegalStateException { 2682 Parcel request = Parcel.obtain(); 2683 Parcel reply = Parcel.obtain(); 2684 try { 2685 request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK); 2686 request.writeInt(index); 2687 invoke(request, reply); 2688 } finally { 2689 request.recycle(); 2690 reply.recycle(); 2691 } 2692 } 2693 2694 // Have to declare protected for finalize() since it is protected 2695 // in the base class Object. 2696 @Override 2697 protected void finalize() throws Throwable { 2698 if (mGuard != null) { 2699 mGuard.warnIfOpen(); 2700 } 2701 2702 close(); 2703 native_finalize(); 2704 } 2705 2706 private void release() { 2707 stayAwake(false); 2708 updateSurfaceScreenOn(); 2709 synchronized (mEventCbLock) { 2710 mEventCallbackRecords.clear(); 2711 } 2712 if (mHandlerThread != null) { 2713 mHandlerThread.quitSafely(); 2714 mHandlerThread = null; 2715 } 2716 if (mTimeProvider != null) { 2717 mTimeProvider.close(); 2718 mTimeProvider = null; 2719 } 2720 mOnSubtitleDataListener = null; 2721 2722 // Modular DRM clean up 2723 mOnDrmConfigHelper = null; 2724 synchronized (mDrmEventCbLock) { 2725 mDrmEventCallbackRecords.clear(); 2726 } 2727 resetDrmState(); 2728 2729 _release(); 2730 } 2731 2732 private native void _release(); 2733 2734 /* Do not change these values without updating their counterparts 2735 * in include/media/mediaplayer2.h! 2736 */ 2737 private static final int MEDIA_NOP = 0; // interface test message 2738 private static final int MEDIA_PREPARED = 1; 2739 private static final int MEDIA_PLAYBACK_COMPLETE = 2; 2740 private static final int MEDIA_BUFFERING_UPDATE = 3; 2741 private static final int MEDIA_SEEK_COMPLETE = 4; 2742 private static final int MEDIA_SET_VIDEO_SIZE = 5; 2743 private static final int MEDIA_STARTED = 6; 2744 private static final int MEDIA_PAUSED = 7; 2745 private static final int MEDIA_STOPPED = 8; 2746 private static final int MEDIA_SKIPPED = 9; 2747 private static final int MEDIA_NOTIFY_TIME = 98; 2748 private static final int MEDIA_TIMED_TEXT = 99; 2749 private static final int MEDIA_ERROR = 100; 2750 private static final int MEDIA_INFO = 200; 2751 private static final int MEDIA_SUBTITLE_DATA = 201; 2752 private static final int MEDIA_META_DATA = 202; 2753 private static final int MEDIA_DRM_INFO = 210; 2754 private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000; 2755 2756 private TimeProvider mTimeProvider; 2757 2758 /** @hide */ 2759 @Override 2760 public MediaTimeProvider getMediaTimeProvider() { 2761 if (mTimeProvider == null) { 2762 mTimeProvider = new TimeProvider(this); 2763 } 2764 return mTimeProvider; 2765 } 2766 2767 private class EventHandler extends Handler { 2768 private MediaPlayer2Impl mMediaPlayer; 2769 2770 public EventHandler(MediaPlayer2Impl mp, Looper looper) { 2771 super(looper); 2772 mMediaPlayer = mp; 2773 } 2774 2775 @Override 2776 public void handleMessage(Message msg) { 2777 handleMessage(msg, 0); 2778 } 2779 2780 public void handleMessage(Message msg, long srcId) { 2781 if (mMediaPlayer.mNativeContext == 0) { 2782 Log.w(TAG, "mediaplayer2 went away with unhandled events"); 2783 return; 2784 } 2785 final int what = msg.arg1; 2786 final int extra = msg.arg2; 2787 2788 switch(msg.what) { 2789 case MEDIA_PREPARED: 2790 { 2791 try { 2792 scanInternalSubtitleTracks(); 2793 } catch (RuntimeException e) { 2794 // send error message instead of crashing; 2795 // send error message instead of inlining a call to onError 2796 // to avoid code duplication. 2797 Message msg2 = obtainMessage( 2798 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 2799 sendMessage(msg2); 2800 } 2801 2802 final DataSourceDesc dsd; 2803 synchronized (mSrcLock) { 2804 Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId 2805 + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId); 2806 if (srcId == mCurrentSrcId) { 2807 dsd = mCurrentDSD; 2808 prepareNextDataSource_l(); 2809 } else if (mNextDSDs != null && !mNextDSDs.isEmpty() 2810 && srcId == mNextSrcId) { 2811 dsd = mNextDSDs.get(0); 2812 mNextSourceState = NEXT_SOURCE_STATE_PREPARED; 2813 if (mNextSourcePlayPending) { 2814 playNextDataSource_l(); 2815 } 2816 } else { 2817 dsd = null; 2818 } 2819 } 2820 2821 if (dsd != null) { 2822 synchronized (mEventCbLock) { 2823 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2824 cb.first.execute(() -> cb.second.onInfo( 2825 mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0)); 2826 } 2827 } 2828 } 2829 synchronized (mTaskLock) { 2830 if (mCurrentTask != null 2831 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE 2832 && mCurrentTask.mDSD == dsd 2833 && mCurrentTask.mNeedToWaitForEventToComplete) { 2834 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 2835 mCurrentTask = null; 2836 processPendingTask_l(); 2837 } 2838 } 2839 return; 2840 } 2841 2842 case MEDIA_DRM_INFO: 2843 { 2844 if (msg.obj == null) { 2845 Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); 2846 } else if (msg.obj instanceof Parcel) { 2847 // The parcel was parsed already in postEventFromNative 2848 final DrmInfoImpl drmInfo; 2849 2850 synchronized (mDrmLock) { 2851 if (mDrmInfoImpl != null) { 2852 drmInfo = mDrmInfoImpl.makeCopy(); 2853 } else { 2854 drmInfo = null; 2855 } 2856 } 2857 2858 // notifying the client outside the lock 2859 if (drmInfo != null) { 2860 synchronized (mEventCbLock) { 2861 for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { 2862 cb.first.execute(() -> cb.second.onDrmInfo( 2863 mMediaPlayer, mCurrentDSD, drmInfo)); 2864 } 2865 } 2866 } 2867 } else { 2868 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); 2869 } 2870 return; 2871 } 2872 2873 case MEDIA_PLAYBACK_COMPLETE: 2874 { 2875 final DataSourceDesc dsd = mCurrentDSD; 2876 synchronized (mSrcLock) { 2877 if (srcId == mCurrentSrcId) { 2878 Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId 2879 + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId); 2880 playNextDataSource_l(); 2881 } 2882 } 2883 2884 synchronized (mEventCbLock) { 2885 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2886 cb.first.execute(() -> cb.second.onInfo( 2887 mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); 2888 } 2889 } 2890 stayAwake(false); 2891 return; 2892 } 2893 2894 case MEDIA_STOPPED: 2895 { 2896 TimeProvider timeProvider = mTimeProvider; 2897 if (timeProvider != null) { 2898 timeProvider.onStopped(); 2899 } 2900 break; 2901 } 2902 2903 case MEDIA_STARTED: 2904 case MEDIA_PAUSED: 2905 { 2906 TimeProvider timeProvider = mTimeProvider; 2907 if (timeProvider != null) { 2908 timeProvider.onPaused(msg.what == MEDIA_PAUSED); 2909 } 2910 break; 2911 } 2912 2913 case MEDIA_BUFFERING_UPDATE: 2914 { 2915 final int percent = msg.arg1; 2916 synchronized (mEventCbLock) { 2917 if (srcId == mCurrentSrcId) { 2918 mBufferedPercentageCurrent.set(percent); 2919 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2920 cb.first.execute(() -> cb.second.onInfo( 2921 mMediaPlayer, mCurrentDSD, MEDIA_INFO_BUFFERING_UPDATE, 2922 percent)); 2923 } 2924 } else if (srcId == mNextSrcId && !mNextDSDs.isEmpty()) { 2925 mBufferedPercentageNext.set(percent); 2926 DataSourceDesc nextDSD = mNextDSDs.get(0); 2927 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2928 cb.first.execute(() -> cb.second.onInfo( 2929 mMediaPlayer, nextDSD, MEDIA_INFO_BUFFERING_UPDATE, 2930 percent)); 2931 } 2932 } 2933 } 2934 return; 2935 } 2936 2937 case MEDIA_SEEK_COMPLETE: 2938 { 2939 synchronized (mTaskLock) { 2940 if (mCurrentTask != null 2941 && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO 2942 && mCurrentTask.mNeedToWaitForEventToComplete) { 2943 mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); 2944 mCurrentTask = null; 2945 processPendingTask_l(); 2946 } 2947 } 2948 } 2949 // fall through 2950 2951 case MEDIA_SKIPPED: 2952 { 2953 TimeProvider timeProvider = mTimeProvider; 2954 if (timeProvider != null) { 2955 timeProvider.onSeekComplete(mMediaPlayer); 2956 } 2957 return; 2958 } 2959 2960 case MEDIA_SET_VIDEO_SIZE: 2961 { 2962 final int width = msg.arg1; 2963 final int height = msg.arg2; 2964 synchronized (mEventCbLock) { 2965 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2966 cb.first.execute(() -> cb.second.onVideoSizeChanged( 2967 mMediaPlayer, mCurrentDSD, width, height)); 2968 } 2969 } 2970 return; 2971 } 2972 2973 case MEDIA_ERROR: 2974 { 2975 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); 2976 synchronized (mEventCbLock) { 2977 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 2978 cb.first.execute(() -> cb.second.onError( 2979 mMediaPlayer, mCurrentDSD, what, extra)); 2980 cb.first.execute(() -> cb.second.onInfo( 2981 mMediaPlayer, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); 2982 } 2983 } 2984 stayAwake(false); 2985 return; 2986 } 2987 2988 case MEDIA_INFO: 2989 { 2990 switch (msg.arg1) { 2991 case MEDIA_INFO_STARTED_AS_NEXT: 2992 if (srcId == mCurrentSrcId) { 2993 prepareNextDataSource_l(); 2994 } 2995 break; 2996 2997 case MEDIA_INFO_VIDEO_TRACK_LAGGING: 2998 Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); 2999 break; 3000 3001 case MEDIA_INFO_METADATA_UPDATE: 3002 try { 3003 scanInternalSubtitleTracks(); 3004 } catch (RuntimeException e) { 3005 Message msg2 = obtainMessage( 3006 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, 3007 null); 3008 sendMessage(msg2); 3009 } 3010 // fall through 3011 3012 case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: 3013 msg.arg1 = MEDIA_INFO_METADATA_UPDATE; 3014 // update default track selection 3015 if (mSubtitleController != null) { 3016 mSubtitleController.selectDefaultTrack(); 3017 } 3018 break; 3019 3020 case MEDIA_INFO_BUFFERING_START: 3021 case MEDIA_INFO_BUFFERING_END: 3022 TimeProvider timeProvider = mTimeProvider; 3023 if (timeProvider != null) { 3024 timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); 3025 } 3026 break; 3027 } 3028 3029 synchronized (mEventCbLock) { 3030 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 3031 cb.first.execute(() -> cb.second.onInfo( 3032 mMediaPlayer, mCurrentDSD, what, extra)); 3033 } 3034 } 3035 // No real default action so far. 3036 return; 3037 } 3038 3039 case MEDIA_NOTIFY_TIME: 3040 { 3041 TimeProvider timeProvider = mTimeProvider; 3042 if (timeProvider != null) { 3043 timeProvider.onNotifyTime(); 3044 } 3045 return; 3046 } 3047 3048 case MEDIA_TIMED_TEXT: 3049 { 3050 final TimedText text; 3051 if (msg.obj instanceof Parcel) { 3052 Parcel parcel = (Parcel)msg.obj; 3053 text = new TimedText(parcel); 3054 parcel.recycle(); 3055 } else { 3056 text = null; 3057 } 3058 3059 synchronized (mEventCbLock) { 3060 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 3061 cb.first.execute(() -> cb.second.onTimedText(mMediaPlayer, mCurrentDSD, text)); 3062 } 3063 } 3064 return; 3065 } 3066 3067 case MEDIA_SUBTITLE_DATA: 3068 { 3069 OnSubtitleDataListener onSubtitleDataListener = mOnSubtitleDataListener; 3070 if (onSubtitleDataListener == null) { 3071 return; 3072 } 3073 if (msg.obj instanceof Parcel) { 3074 Parcel parcel = (Parcel) msg.obj; 3075 SubtitleData data = new SubtitleData(parcel); 3076 parcel.recycle(); 3077 onSubtitleDataListener.onSubtitleData(mMediaPlayer, data); 3078 } 3079 return; 3080 } 3081 3082 case MEDIA_META_DATA: 3083 { 3084 final TimedMetaData data; 3085 if (msg.obj instanceof Parcel) { 3086 Parcel parcel = (Parcel) msg.obj; 3087 data = TimedMetaData.createTimedMetaDataFromParcel(parcel); 3088 parcel.recycle(); 3089 } else { 3090 data = null; 3091 } 3092 3093 synchronized (mEventCbLock) { 3094 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 3095 cb.first.execute(() -> cb.second.onTimedMetaDataAvailable( 3096 mMediaPlayer, mCurrentDSD, data)); 3097 } 3098 } 3099 return; 3100 } 3101 3102 case MEDIA_NOP: // interface test message - ignore 3103 { 3104 break; 3105 } 3106 3107 case MEDIA_AUDIO_ROUTING_CHANGED: 3108 { 3109 AudioManager.resetAudioPortGeneration(); 3110 synchronized (mRoutingChangeListeners) { 3111 for (NativeRoutingEventHandlerDelegate delegate 3112 : mRoutingChangeListeners.values()) { 3113 delegate.notifyClient(); 3114 } 3115 } 3116 return; 3117 } 3118 3119 default: 3120 { 3121 Log.e(TAG, "Unknown message type " + msg.what); 3122 return; 3123 } 3124 } 3125 } 3126 } 3127 3128 /* 3129 * Called from native code when an interesting event happens. This method 3130 * just uses the EventHandler system to post the event back to the main app thread. 3131 * We use a weak reference to the original MediaPlayer2 object so that the native 3132 * code is safe from the object disappearing from underneath it. (This is 3133 * the cookie passed to native_setup().) 3134 */ 3135 private static void postEventFromNative(Object mediaplayer2_ref, long srcId, 3136 int what, int arg1, int arg2, Object obj) 3137 { 3138 final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get(); 3139 if (mp == null) { 3140 return; 3141 } 3142 3143 switch (what) { 3144 case MEDIA_INFO: 3145 if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) { 3146 new Thread(new Runnable() { 3147 @Override 3148 public void run() { 3149 // this acquires the wakelock if needed, and sets the client side state 3150 mp.play(); 3151 } 3152 }).start(); 3153 Thread.yield(); 3154 } 3155 break; 3156 3157 case MEDIA_DRM_INFO: 3158 // We need to derive mDrmInfoImpl before prepare() returns so processing it here 3159 // before the notification is sent to EventHandler below. EventHandler runs in the 3160 // notification looper so its handleMessage might process the event after prepare() 3161 // has returned. 3162 Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); 3163 if (obj instanceof Parcel) { 3164 Parcel parcel = (Parcel)obj; 3165 DrmInfoImpl drmInfo = new DrmInfoImpl(parcel); 3166 synchronized (mp.mDrmLock) { 3167 mp.mDrmInfoImpl = drmInfo; 3168 } 3169 } else { 3170 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); 3171 } 3172 break; 3173 3174 case MEDIA_PREPARED: 3175 // By this time, we've learned about DrmInfo's presence or absence. This is meant 3176 // mainly for prepare() use case. For prepare(), this still can run to a race 3177 // condition b/c MediaPlayerNative releases the prepare() lock before calling notify 3178 // so we also set mDrmInfoResolved in prepare(). 3179 synchronized (mp.mDrmLock) { 3180 mp.mDrmInfoResolved = true; 3181 } 3182 break; 3183 3184 } 3185 3186 if (mp.mEventHandler != null) { 3187 Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); 3188 3189 mp.mEventHandler.post(new Runnable() { 3190 @Override 3191 public void run() { 3192 mp.mEventHandler.handleMessage(m, srcId); 3193 } 3194 }); 3195 } 3196 } 3197 3198 private final Object mEventCbLock = new Object(); 3199 private ArrayList<Pair<Executor, MediaPlayer2EventCallback> > mEventCallbackRecords 3200 = new ArrayList<Pair<Executor, MediaPlayer2EventCallback> >(); 3201 3202 /** 3203 * Register a callback to be invoked when the media source is ready 3204 * for playback. 3205 * 3206 * @param eventCallback the callback that will be run 3207 * @param executor the executor through which the callback should be invoked 3208 */ 3209 @Override 3210 public void setMediaPlayer2EventCallback(@NonNull @CallbackExecutor Executor executor, 3211 @NonNull MediaPlayer2EventCallback eventCallback) { 3212 if (eventCallback == null) { 3213 throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback"); 3214 } 3215 if (executor == null) { 3216 throw new IllegalArgumentException( 3217 "Illegal null Executor for the MediaPlayer2EventCallback"); 3218 } 3219 synchronized (mEventCbLock) { 3220 mEventCallbackRecords.add(new Pair(executor, eventCallback)); 3221 } 3222 } 3223 3224 /** 3225 * Clears the {@link MediaPlayer2EventCallback}. 3226 */ 3227 @Override 3228 public void clearMediaPlayer2EventCallback() { 3229 synchronized (mEventCbLock) { 3230 mEventCallbackRecords.clear(); 3231 } 3232 } 3233 3234 /** 3235 * Register a callback to be invoked when a track has data available. 3236 * 3237 * @param listener the callback that will be run 3238 * 3239 * @hide 3240 */ 3241 @Override 3242 public void setOnSubtitleDataListener(OnSubtitleDataListener listener) { 3243 mOnSubtitleDataListener = listener; 3244 } 3245 3246 private OnSubtitleDataListener mOnSubtitleDataListener; 3247 3248 3249 // Modular DRM begin 3250 3251 /** 3252 * Register a callback to be invoked for configuration of the DRM object before 3253 * the session is created. 3254 * The callback will be invoked synchronously during the execution 3255 * of {@link #prepareDrm(UUID uuid)}. 3256 * 3257 * @param listener the callback that will be run 3258 */ 3259 @Override 3260 public void setOnDrmConfigHelper(OnDrmConfigHelper listener) 3261 { 3262 synchronized (mDrmLock) { 3263 mOnDrmConfigHelper = listener; 3264 } // synchronized 3265 } 3266 3267 private OnDrmConfigHelper mOnDrmConfigHelper; 3268 3269 private final Object mDrmEventCbLock = new Object(); 3270 private ArrayList<Pair<Executor, DrmEventCallback> > mDrmEventCallbackRecords 3271 = new ArrayList<Pair<Executor, DrmEventCallback> >(); 3272 3273 /** 3274 * Register a callback to be invoked when the media source is ready 3275 * for playback. 3276 * 3277 * @param eventCallback the callback that will be run 3278 * @param executor the executor through which the callback should be invoked 3279 */ 3280 @Override 3281 public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor, 3282 @NonNull DrmEventCallback eventCallback) { 3283 if (eventCallback == null) { 3284 throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback"); 3285 } 3286 if (executor == null) { 3287 throw new IllegalArgumentException( 3288 "Illegal null Executor for the MediaPlayer2EventCallback"); 3289 } 3290 synchronized (mDrmEventCbLock) { 3291 mDrmEventCallbackRecords.add(new Pair(executor, eventCallback)); 3292 } 3293 } 3294 3295 /** 3296 * Clears the {@link DrmEventCallback}. 3297 */ 3298 @Override 3299 public void clearDrmEventCallback() { 3300 synchronized (mDrmEventCbLock) { 3301 mDrmEventCallbackRecords.clear(); 3302 } 3303 } 3304 3305 3306 /** 3307 * Retrieves the DRM Info associated with the current source 3308 * 3309 * @throws IllegalStateException if called before prepare() 3310 */ 3311 @Override 3312 public DrmInfo getDrmInfo() { 3313 DrmInfoImpl drmInfo = null; 3314 3315 // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; 3316 // regardless below returns drmInfo anyway instead of raising an exception 3317 synchronized (mDrmLock) { 3318 if (!mDrmInfoResolved && mDrmInfoImpl == null) { 3319 final String msg = "The Player has not been prepared yet"; 3320 Log.v(TAG, msg); 3321 throw new IllegalStateException(msg); 3322 } 3323 3324 if (mDrmInfoImpl != null) { 3325 drmInfo = mDrmInfoImpl.makeCopy(); 3326 } 3327 } // synchronized 3328 3329 return drmInfo; 3330 } 3331 3332 3333 /** 3334 * Prepares the DRM for the current source 3335 * <p> 3336 * If {@code OnDrmConfigHelper} is registered, it will be called during 3337 * preparation to allow configuration of the DRM properties before opening the 3338 * DRM session. Note that the callback is called synchronously in the thread that called 3339 * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} 3340 * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. 3341 * <p> 3342 * If the device has not been provisioned before, this call also provisions the device 3343 * which involves accessing the provisioning server and can take a variable time to 3344 * complete depending on the network connectivity. 3345 * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking 3346 * mode by launching the provisioning in the background and returning. The listener 3347 * will be called when provisioning and preparation has finished. If a 3348 * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning 3349 * and preparation has finished, i.e., runs in blocking mode. 3350 * <p> 3351 * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM 3352 * session being ready. The application should not make any assumption about its call 3353 * sequence (e.g., before or after prepareDrm returns), or the thread context that will 3354 * execute the listener (unless the listener is registered with a handler thread). 3355 * <p> 3356 * 3357 * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved 3358 * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. 3359 * 3360 * @throws IllegalStateException if called before prepare(), or the DRM was 3361 * prepared already 3362 * @throws UnsupportedSchemeException if the crypto scheme is not supported 3363 * @throws ResourceBusyException if required DRM resources are in use 3364 * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a 3365 * network error 3366 * @throws ProvisioningServerErrorException if provisioning is required but failed due to 3367 * the request denied by the provisioning server 3368 */ 3369 @Override 3370 public void prepareDrm(@NonNull UUID uuid) 3371 throws UnsupportedSchemeException, ResourceBusyException, 3372 ProvisioningNetworkErrorException, ProvisioningServerErrorException 3373 { 3374 Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); 3375 3376 boolean allDoneWithoutProvisioning = false; 3377 3378 synchronized (mDrmLock) { 3379 3380 // only allowing if tied to a protected source; might relax for releasing offline keys 3381 if (mDrmInfoImpl == null) { 3382 final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " + 3383 "DRM info be retrieved before this call."; 3384 Log.e(TAG, msg); 3385 throw new IllegalStateException(msg); 3386 } 3387 3388 if (mActiveDrmScheme) { 3389 final String msg = "prepareDrm(): Wrong usage: There is already " + 3390 "an active DRM scheme with " + mDrmUUID; 3391 Log.e(TAG, msg); 3392 throw new IllegalStateException(msg); 3393 } 3394 3395 if (mPrepareDrmInProgress) { 3396 final String msg = "prepareDrm(): Wrong usage: There is already " + 3397 "a pending prepareDrm call."; 3398 Log.e(TAG, msg); 3399 throw new IllegalStateException(msg); 3400 } 3401 3402 if (mDrmProvisioningInProgress) { 3403 final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; 3404 Log.e(TAG, msg); 3405 throw new IllegalStateException(msg); 3406 } 3407 3408 // shouldn't need this; just for safeguard 3409 cleanDrmObj(); 3410 3411 mPrepareDrmInProgress = true; 3412 3413 try { 3414 // only creating the DRM object to allow pre-openSession configuration 3415 prepareDrm_createDrmStep(uuid); 3416 } catch (Exception e) { 3417 Log.w(TAG, "prepareDrm(): Exception ", e); 3418 mPrepareDrmInProgress = false; 3419 throw e; 3420 } 3421 3422 mDrmConfigAllowed = true; 3423 } // synchronized 3424 3425 3426 // call the callback outside the lock 3427 if (mOnDrmConfigHelper != null) { 3428 mOnDrmConfigHelper.onDrmConfig(this, mCurrentDSD); 3429 } 3430 3431 synchronized (mDrmLock) { 3432 mDrmConfigAllowed = false; 3433 boolean earlyExit = false; 3434 3435 try { 3436 prepareDrm_openSessionStep(uuid); 3437 3438 mDrmUUID = uuid; 3439 mActiveDrmScheme = true; 3440 3441 allDoneWithoutProvisioning = true; 3442 } catch (IllegalStateException e) { 3443 final String msg = "prepareDrm(): Wrong usage: The player must be " + 3444 "in the prepared state to call prepareDrm()."; 3445 Log.e(TAG, msg); 3446 earlyExit = true; 3447 throw new IllegalStateException(msg); 3448 } catch (NotProvisionedException e) { 3449 Log.w(TAG, "prepareDrm: NotProvisionedException"); 3450 3451 // handle provisioning internally; it'll reset mPrepareDrmInProgress 3452 int result = HandleProvisioninig(uuid); 3453 3454 // if blocking mode, we're already done; 3455 // if non-blocking mode, we attempted to launch background provisioning 3456 if (result != PREPARE_DRM_STATUS_SUCCESS) { 3457 earlyExit = true; 3458 String msg; 3459 3460 switch (result) { 3461 case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR: 3462 msg = "prepareDrm: Provisioning was required but failed " + 3463 "due to a network error."; 3464 Log.e(TAG, msg); 3465 throw new ProvisioningNetworkErrorExceptionImpl(msg); 3466 3467 case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR: 3468 msg = "prepareDrm: Provisioning was required but the request " + 3469 "was denied by the server."; 3470 Log.e(TAG, msg); 3471 throw new ProvisioningServerErrorExceptionImpl(msg); 3472 3473 case PREPARE_DRM_STATUS_PREPARATION_ERROR: 3474 default: // default for safeguard 3475 msg = "prepareDrm: Post-provisioning preparation failed."; 3476 Log.e(TAG, msg); 3477 throw new IllegalStateException(msg); 3478 } 3479 } 3480 // nothing else to do; 3481 // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup 3482 } catch (Exception e) { 3483 Log.e(TAG, "prepareDrm: Exception " + e); 3484 earlyExit = true; 3485 throw e; 3486 } finally { 3487 if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception 3488 mPrepareDrmInProgress = false; 3489 } 3490 if (earlyExit) { // cleaning up object if didn't succeed 3491 cleanDrmObj(); 3492 } 3493 } // finally 3494 } // synchronized 3495 3496 3497 // if finished successfully without provisioning, call the callback outside the lock 3498 if (allDoneWithoutProvisioning) { 3499 synchronized (mDrmEventCbLock) { 3500 for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { 3501 cb.first.execute(() -> cb.second.onDrmPrepared( 3502 this, mCurrentDSD, PREPARE_DRM_STATUS_SUCCESS)); 3503 } 3504 } 3505 } 3506 3507 } 3508 3509 3510 private native void _releaseDrm(); 3511 3512 /** 3513 * Releases the DRM session 3514 * <p> 3515 * The player has to have an active DRM session and be in stopped, or prepared 3516 * state before this call is made. 3517 * A {@code reset()} call will release the DRM session implicitly. 3518 * 3519 * @throws NoDrmSchemeException if there is no active DRM session to release 3520 */ 3521 @Override 3522 public void releaseDrm() 3523 throws NoDrmSchemeException 3524 { 3525 addTask(new Task(CALL_COMPLETED_RELEASE_DRM, false) { 3526 @Override 3527 void process() throws NoDrmSchemeException { 3528 synchronized (mDrmLock) { 3529 Log.v(TAG, "releaseDrm:"); 3530 3531 if (!mActiveDrmScheme) { 3532 Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); 3533 throw new NoDrmSchemeExceptionImpl( 3534 "releaseDrm: No active DRM scheme to release."); 3535 } 3536 3537 try { 3538 // we don't have the player's state in this layer. The below call raises 3539 // exception if we're in a non-stopped/prepared state. 3540 3541 // for cleaning native/mediaserver crypto object 3542 _releaseDrm(); 3543 3544 // for cleaning client-side MediaDrm object; only called if above has succeeded 3545 cleanDrmObj(); 3546 3547 mActiveDrmScheme = false; 3548 } catch (IllegalStateException e) { 3549 Log.w(TAG, "releaseDrm: Exception ", e); 3550 throw new IllegalStateException( 3551 "releaseDrm: The player is not in a valid state."); 3552 } catch (Exception e) { 3553 Log.e(TAG, "releaseDrm: Exception ", e); 3554 } 3555 } // synchronized 3556 } 3557 }); 3558 } 3559 3560 3561 /** 3562 * A key request/response exchange occurs between the app and a license server 3563 * to obtain or release keys used to decrypt encrypted content. 3564 * <p> 3565 * getDrmKeyRequest() is used to obtain an opaque key request byte array that is 3566 * delivered to the license server. The opaque key request byte array is returned 3567 * in KeyRequest.data. The recommended URL to deliver the key request to is 3568 * returned in KeyRequest.defaultUrl. 3569 * <p> 3570 * After the app has received the key request response from the server, 3571 * it should deliver to the response to the DRM engine plugin using the method 3572 * {@link #provideDrmKeyResponse}. 3573 * 3574 * @param keySetId is the key-set identifier of the offline keys being released when keyType is 3575 * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when 3576 * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 3577 * 3578 * @param initData is the container-specific initialization data when the keyType is 3579 * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is 3580 * interpreted based on the mime type provided in the mimeType parameter. It could 3581 * contain, for example, the content ID, key ID or other data obtained from the content 3582 * metadata that is required in generating the key request. 3583 * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. 3584 * 3585 * @param mimeType identifies the mime type of the content 3586 * 3587 * @param keyType specifies the type of the request. The request may be to acquire 3588 * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content 3589 * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired 3590 * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. 3591 * 3592 * @param optionalParameters are included in the key request message to 3593 * allow a client application to provide additional message parameters to the server. 3594 * This may be {@code null} if no additional parameters are to be sent. 3595 * 3596 * @throws NoDrmSchemeException if there is no active DRM session 3597 */ 3598 @Override 3599 @NonNull 3600 public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData, 3601 @Nullable String mimeType, @MediaDrm.KeyType int keyType, 3602 @Nullable Map<String, String> optionalParameters) 3603 throws NoDrmSchemeException 3604 { 3605 Log.v(TAG, "getDrmKeyRequest: " + 3606 " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + 3607 " keyType: " + keyType + " optionalParameters: " + optionalParameters); 3608 3609 synchronized (mDrmLock) { 3610 if (!mActiveDrmScheme) { 3611 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); 3612 throw new NoDrmSchemeExceptionImpl( 3613 "getDrmKeyRequest: Has to set a DRM scheme first."); 3614 } 3615 3616 try { 3617 byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? 3618 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 3619 keySetId; // keySetId for KEY_TYPE_RELEASE 3620 3621 HashMap<String, String> hmapOptionalParameters = 3622 (optionalParameters != null) ? 3623 new HashMap<String, String>(optionalParameters) : 3624 null; 3625 3626 MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, 3627 keyType, hmapOptionalParameters); 3628 Log.v(TAG, "getDrmKeyRequest: --> request: " + request); 3629 3630 return request; 3631 3632 } catch (NotProvisionedException e) { 3633 Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " + 3634 "Unexpected. Shouldn't have reached here."); 3635 throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error."); 3636 } catch (Exception e) { 3637 Log.w(TAG, "getDrmKeyRequest Exception " + e); 3638 throw e; 3639 } 3640 3641 } // synchronized 3642 } 3643 3644 3645 /** 3646 * A key response is received from the license server by the app, then it is 3647 * provided to the DRM engine plugin using provideDrmKeyResponse. When the 3648 * response is for an offline key request, a key-set identifier is returned that 3649 * can be used to later restore the keys to a new session with the method 3650 * {@ link # restoreDrmKeys}. 3651 * When the response is for a streaming or release request, null is returned. 3652 * 3653 * @param keySetId When the response is for a release request, keySetId identifies 3654 * the saved key associated with the release request (i.e., the same keySetId 3655 * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the 3656 * response is for either streaming or offline key requests. 3657 * 3658 * @param response the byte array response from the server 3659 * 3660 * @throws NoDrmSchemeException if there is no active DRM session 3661 * @throws DeniedByServerException if the response indicates that the 3662 * server rejected the request 3663 */ 3664 @Override 3665 public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) 3666 throws NoDrmSchemeException, DeniedByServerException 3667 { 3668 Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response); 3669 3670 synchronized (mDrmLock) { 3671 3672 if (!mActiveDrmScheme) { 3673 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); 3674 throw new NoDrmSchemeExceptionImpl( 3675 "getDrmKeyRequest: Has to set a DRM scheme first."); 3676 } 3677 3678 try { 3679 byte[] scope = (keySetId == null) ? 3680 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 3681 keySetId; // keySetId for KEY_TYPE_RELEASE 3682 3683 byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); 3684 3685 Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response 3686 + " --> " + keySetResult); 3687 3688 3689 return keySetResult; 3690 3691 } catch (NotProvisionedException e) { 3692 Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " + 3693 "Unexpected. Shouldn't have reached here."); 3694 throw new IllegalStateException("provideDrmKeyResponse: " + 3695 "Unexpected provisioning error."); 3696 } catch (Exception e) { 3697 Log.w(TAG, "provideDrmKeyResponse Exception " + e); 3698 throw e; 3699 } 3700 } // synchronized 3701 } 3702 3703 3704 /** 3705 * Restore persisted offline keys into a new session. keySetId identifies the 3706 * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}. 3707 * 3708 * @param keySetId identifies the saved key set to restore 3709 */ 3710 @Override 3711 public void restoreDrmKeys(@NonNull byte[] keySetId) 3712 throws NoDrmSchemeException 3713 { 3714 addTask(new Task(CALL_COMPLETED_RESTORE_DRM_KEYS, false) { 3715 @Override 3716 void process() throws NoDrmSchemeException { 3717 Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); 3718 3719 synchronized (mDrmLock) { 3720 3721 if (!mActiveDrmScheme) { 3722 Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); 3723 throw new NoDrmSchemeExceptionImpl( 3724 "restoreDrmKeys: Has to set a DRM scheme first."); 3725 } 3726 3727 try { 3728 mDrmObj.restoreKeys(mDrmSessionId, keySetId); 3729 } catch (Exception e) { 3730 Log.w(TAG, "restoreKeys Exception " + e); 3731 throw e; 3732 } 3733 3734 } // synchronized 3735 } 3736 }); 3737 } 3738 3739 3740 /** 3741 * Read a DRM engine plugin String property value, given the property name string. 3742 * <p> 3743 * @param propertyName the property name 3744 * 3745 * Standard fields names are: 3746 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 3747 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 3748 */ 3749 @Override 3750 @NonNull 3751 public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) 3752 throws NoDrmSchemeException 3753 { 3754 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); 3755 3756 String value; 3757 synchronized (mDrmLock) { 3758 3759 if (!mActiveDrmScheme && !mDrmConfigAllowed) { 3760 Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); 3761 throw new NoDrmSchemeExceptionImpl( 3762 "getDrmPropertyString: Has to prepareDrm() first."); 3763 } 3764 3765 try { 3766 value = mDrmObj.getPropertyString(propertyName); 3767 } catch (Exception e) { 3768 Log.w(TAG, "getDrmPropertyString Exception " + e); 3769 throw e; 3770 } 3771 } // synchronized 3772 3773 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); 3774 3775 return value; 3776 } 3777 3778 3779 /** 3780 * Set a DRM engine plugin String property value. 3781 * <p> 3782 * @param propertyName the property name 3783 * @param value the property value 3784 * 3785 * Standard fields names are: 3786 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 3787 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 3788 */ 3789 @Override 3790 public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName, 3791 @NonNull String value) 3792 throws NoDrmSchemeException 3793 { 3794 Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); 3795 3796 synchronized (mDrmLock) { 3797 3798 if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { 3799 Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); 3800 throw new NoDrmSchemeExceptionImpl( 3801 "setDrmPropertyString: Has to prepareDrm() first."); 3802 } 3803 3804 try { 3805 mDrmObj.setPropertyString(propertyName, value); 3806 } catch ( Exception e ) { 3807 Log.w(TAG, "setDrmPropertyString Exception " + e); 3808 throw e; 3809 } 3810 } // synchronized 3811 } 3812 3813 /** 3814 * Encapsulates the DRM properties of the source. 3815 */ 3816 public static final class DrmInfoImpl extends DrmInfo { 3817 private Map<UUID, byte[]> mapPssh; 3818 private UUID[] supportedSchemes; 3819 3820 /** 3821 * Returns the PSSH info of the data source for each supported DRM scheme. 3822 */ 3823 @Override 3824 public Map<UUID, byte[]> getPssh() { 3825 return mapPssh; 3826 } 3827 3828 /** 3829 * Returns the intersection of the data source and the device DRM schemes. 3830 * It effectively identifies the subset of the source's DRM schemes which 3831 * are supported by the device too. 3832 */ 3833 @Override 3834 public List<UUID> getSupportedSchemes() { 3835 return Arrays.asList(supportedSchemes); 3836 } 3837 3838 private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) { 3839 mapPssh = Pssh; 3840 supportedSchemes = SupportedSchemes; 3841 } 3842 3843 private DrmInfoImpl(Parcel parcel) { 3844 Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize()); 3845 3846 int psshsize = parcel.readInt(); 3847 byte[] pssh = new byte[psshsize]; 3848 parcel.readByteArray(pssh); 3849 3850 Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh)); 3851 mapPssh = parsePSSH(pssh, psshsize); 3852 Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh); 3853 3854 int supportedDRMsCount = parcel.readInt(); 3855 supportedSchemes = new UUID[supportedDRMsCount]; 3856 for (int i = 0; i < supportedDRMsCount; i++) { 3857 byte[] uuid = new byte[16]; 3858 parcel.readByteArray(uuid); 3859 3860 supportedSchemes[i] = bytesToUUID(uuid); 3861 3862 Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " + 3863 supportedSchemes[i]); 3864 } 3865 3866 Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize + 3867 " supportedDRMsCount: " + supportedDRMsCount); 3868 } 3869 3870 private DrmInfoImpl makeCopy() { 3871 return new DrmInfoImpl(this.mapPssh, this.supportedSchemes); 3872 } 3873 3874 private String arrToHex(byte[] bytes) { 3875 String out = "0x"; 3876 for (int i = 0; i < bytes.length; i++) { 3877 out += String.format("%02x", bytes[i]); 3878 } 3879 3880 return out; 3881 } 3882 3883 private UUID bytesToUUID(byte[] uuid) { 3884 long msb = 0, lsb = 0; 3885 for (int i = 0; i < 8; i++) { 3886 msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) ); 3887 lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) ); 3888 } 3889 3890 return new UUID(msb, lsb); 3891 } 3892 3893 private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { 3894 Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); 3895 3896 final int UUID_SIZE = 16; 3897 final int DATALEN_SIZE = 4; 3898 3899 int len = psshsize; 3900 int numentries = 0; 3901 int i = 0; 3902 3903 while (len > 0) { 3904 if (len < UUID_SIZE) { 3905 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 3906 "UUID: (%d < 16) pssh: %d", len, psshsize)); 3907 return null; 3908 } 3909 3910 byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE); 3911 UUID uuid = bytesToUUID(subset); 3912 i += UUID_SIZE; 3913 len -= UUID_SIZE; 3914 3915 // get data length 3916 if (len < 4) { 3917 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 3918 "datalen: (%d < 4) pssh: %d", len, psshsize)); 3919 return null; 3920 } 3921 3922 subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE); 3923 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ? 3924 ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) | 3925 ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) : 3926 ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) | 3927 ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ; 3928 i += DATALEN_SIZE; 3929 len -= DATALEN_SIZE; 3930 3931 if (len < datalen) { 3932 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 3933 "data: (%d < %d) pssh: %d", len, datalen, psshsize)); 3934 return null; 3935 } 3936 3937 byte[] data = Arrays.copyOfRange(pssh, i, i+datalen); 3938 3939 // skip the data 3940 i += datalen; 3941 len -= datalen; 3942 3943 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", 3944 numentries, uuid, arrToHex(data), psshsize)); 3945 numentries++; 3946 result.put(uuid, data); 3947 } 3948 3949 return result; 3950 } 3951 3952 }; // DrmInfoImpl 3953 3954 /** 3955 * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). 3956 * Extends MediaDrm.MediaDrmException 3957 */ 3958 public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException { 3959 public NoDrmSchemeExceptionImpl(String detailMessage) { 3960 super(detailMessage); 3961 } 3962 } 3963 3964 /** 3965 * Thrown when the device requires DRM provisioning but the provisioning attempt has 3966 * failed due to a network error (Internet reachability, timeout, etc.). 3967 * Extends MediaDrm.MediaDrmException 3968 */ 3969 public static final class ProvisioningNetworkErrorExceptionImpl 3970 extends ProvisioningNetworkErrorException { 3971 public ProvisioningNetworkErrorExceptionImpl(String detailMessage) { 3972 super(detailMessage); 3973 } 3974 } 3975 3976 /** 3977 * Thrown when the device requires DRM provisioning but the provisioning attempt has 3978 * failed due to the provisioning server denying the request. 3979 * Extends MediaDrm.MediaDrmException 3980 */ 3981 public static final class ProvisioningServerErrorExceptionImpl 3982 extends ProvisioningServerErrorException { 3983 public ProvisioningServerErrorExceptionImpl(String detailMessage) { 3984 super(detailMessage); 3985 } 3986 } 3987 3988 3989 private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); 3990 3991 // Modular DRM helpers 3992 3993 private void prepareDrm_createDrmStep(@NonNull UUID uuid) 3994 throws UnsupportedSchemeException { 3995 Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); 3996 3997 try { 3998 mDrmObj = new MediaDrm(uuid); 3999 Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); 4000 } catch (Exception e) { // UnsupportedSchemeException 4001 Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); 4002 throw e; 4003 } 4004 } 4005 4006 private void prepareDrm_openSessionStep(@NonNull UUID uuid) 4007 throws NotProvisionedException, ResourceBusyException { 4008 Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); 4009 4010 // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do 4011 // it anyway so it raises provisioning error if needed. We'd rather handle provisioning 4012 // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse 4013 try { 4014 mDrmSessionId = mDrmObj.openSession(); 4015 Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); 4016 4017 // Sending it down to native/mediaserver to create the crypto object 4018 // This call could simply fail due to bad player state, e.g., after play(). 4019 _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); 4020 Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded"); 4021 4022 } catch (Exception e) { //ResourceBusyException, NotProvisionedException 4023 Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); 4024 throw e; 4025 } 4026 4027 } 4028 4029 // Called from the native side 4030 @SuppressWarnings("unused") 4031 private static boolean setAudioOutputDeviceById(AudioTrack track, int deviceId) { 4032 if (track == null) { 4033 return false; 4034 } 4035 4036 if (deviceId == 0) { 4037 // Use default routing. 4038 track.setPreferredDevice(null); 4039 return true; 4040 } 4041 4042 // TODO: Unhide AudioManager.getDevicesStatic. 4043 AudioDeviceInfo[] outputDevices = 4044 AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); 4045 4046 boolean success = false; 4047 for (AudioDeviceInfo device : outputDevices) { 4048 if (device.getId() == deviceId) { 4049 track.setPreferredDevice(device); 4050 success = true; 4051 break; 4052 } 4053 } 4054 return success; 4055 } 4056 4057 // Instantiated from the native side 4058 @SuppressWarnings("unused") 4059 private static class StreamEventCallback extends AudioTrack.StreamEventCallback { 4060 public long mJAudioTrackPtr; 4061 public long mNativeCallbackPtr; 4062 public long mUserDataPtr; 4063 4064 public StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) { 4065 super(); 4066 mJAudioTrackPtr = jAudioTrackPtr; 4067 mNativeCallbackPtr = nativeCallbackPtr; 4068 mUserDataPtr = userDataPtr; 4069 } 4070 4071 @Override 4072 public void onTearDown(AudioTrack track) { 4073 native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr); 4074 } 4075 4076 @Override 4077 public void onStreamPresentationEnd(AudioTrack track) { 4078 native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr); 4079 } 4080 4081 @Override 4082 public void onStreamDataRequest(AudioTrack track) { 4083 native_stream_event_onStreamDataRequest( 4084 mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr); 4085 } 4086 } 4087 4088 private class ProvisioningThread extends Thread { 4089 public static final int TIMEOUT_MS = 60000; 4090 4091 private UUID uuid; 4092 private String urlStr; 4093 private Object drmLock; 4094 private MediaPlayer2Impl mediaPlayer; 4095 private int status; 4096 private boolean finished; 4097 public int status() { 4098 return status; 4099 } 4100 4101 public ProvisioningThread initialize(MediaDrm.ProvisionRequest request, 4102 UUID uuid, MediaPlayer2Impl mediaPlayer) { 4103 // lock is held by the caller 4104 drmLock = mediaPlayer.mDrmLock; 4105 this.mediaPlayer = mediaPlayer; 4106 4107 urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); 4108 this.uuid = uuid; 4109 4110 status = PREPARE_DRM_STATUS_PREPARATION_ERROR; 4111 4112 Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr); 4113 return this; 4114 } 4115 4116 public void run() { 4117 4118 byte[] response = null; 4119 boolean provisioningSucceeded = false; 4120 try { 4121 URL url = new URL(urlStr); 4122 final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 4123 try { 4124 connection.setRequestMethod("POST"); 4125 connection.setDoOutput(false); 4126 connection.setDoInput(true); 4127 connection.setConnectTimeout(TIMEOUT_MS); 4128 connection.setReadTimeout(TIMEOUT_MS); 4129 4130 connection.connect(); 4131 response = Streams.readFully(connection.getInputStream()); 4132 4133 Log.v(TAG, "HandleProvisioninig: Thread run: response " + 4134 response.length + " " + response); 4135 } catch (Exception e) { 4136 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4137 Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); 4138 } finally { 4139 connection.disconnect(); 4140 } 4141 } catch (Exception e) { 4142 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4143 Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e); 4144 } 4145 4146 if (response != null) { 4147 try { 4148 mDrmObj.provideProvisionResponse(response); 4149 Log.v(TAG, "HandleProvisioninig: Thread run: " + 4150 "provideProvisionResponse SUCCEEDED!"); 4151 4152 provisioningSucceeded = true; 4153 } catch (Exception e) { 4154 status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; 4155 Log.w(TAG, "HandleProvisioninig: Thread run: " + 4156 "provideProvisionResponse " + e); 4157 } 4158 } 4159 4160 boolean succeeded = false; 4161 4162 boolean hasCallback = false; 4163 synchronized (mDrmEventCbLock) { 4164 hasCallback = !mDrmEventCallbackRecords.isEmpty(); 4165 } 4166 // non-blocking mode needs the lock 4167 if (hasCallback) { 4168 4169 synchronized (drmLock) { 4170 // continuing with prepareDrm 4171 if (provisioningSucceeded) { 4172 succeeded = mediaPlayer.resumePrepareDrm(uuid); 4173 status = (succeeded) ? 4174 PREPARE_DRM_STATUS_SUCCESS : 4175 PREPARE_DRM_STATUS_PREPARATION_ERROR; 4176 } 4177 mediaPlayer.mDrmProvisioningInProgress = false; 4178 mediaPlayer.mPrepareDrmInProgress = false; 4179 if (!succeeded) { 4180 cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock 4181 } 4182 } // synchronized 4183 4184 // calling the callback outside the lock 4185 synchronized (mDrmEventCbLock) { 4186 for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { 4187 cb.first.execute(() -> cb.second.onDrmPrepared( 4188 mediaPlayer, mCurrentDSD, status)); 4189 } 4190 } 4191 } else { // blocking mode already has the lock 4192 4193 // continuing with prepareDrm 4194 if (provisioningSucceeded) { 4195 succeeded = mediaPlayer.resumePrepareDrm(uuid); 4196 status = (succeeded) ? 4197 PREPARE_DRM_STATUS_SUCCESS : 4198 PREPARE_DRM_STATUS_PREPARATION_ERROR; 4199 } 4200 mediaPlayer.mDrmProvisioningInProgress = false; 4201 mediaPlayer.mPrepareDrmInProgress = false; 4202 if (!succeeded) { 4203 cleanDrmObj(); // cleaning up if it hasn't gone through 4204 } 4205 } 4206 4207 finished = true; 4208 } // run() 4209 4210 } // ProvisioningThread 4211 4212 private int HandleProvisioninig(UUID uuid) { 4213 // the lock is already held by the caller 4214 4215 if (mDrmProvisioningInProgress) { 4216 Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress"); 4217 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4218 } 4219 4220 MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); 4221 if (provReq == null) { 4222 Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null."); 4223 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4224 } 4225 4226 Log.v(TAG, "HandleProvisioninig provReq " + 4227 " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); 4228 4229 // networking in a background thread 4230 mDrmProvisioningInProgress = true; 4231 4232 mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this); 4233 mDrmProvisioningThread.start(); 4234 4235 int result; 4236 4237 // non-blocking: this is not the final result 4238 boolean hasCallback = false; 4239 synchronized (mDrmEventCbLock) { 4240 hasCallback = !mDrmEventCallbackRecords.isEmpty(); 4241 } 4242 if (hasCallback) { 4243 result = PREPARE_DRM_STATUS_SUCCESS; 4244 } else { 4245 // if blocking mode, wait till provisioning is done 4246 try { 4247 mDrmProvisioningThread.join(); 4248 } catch (Exception e) { 4249 Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e); 4250 } 4251 result = mDrmProvisioningThread.status(); 4252 // no longer need the thread 4253 mDrmProvisioningThread = null; 4254 } 4255 4256 return result; 4257 } 4258 4259 private boolean resumePrepareDrm(UUID uuid) { 4260 Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); 4261 4262 // mDrmLock is guaranteed to be held 4263 boolean success = false; 4264 try { 4265 // resuming 4266 prepareDrm_openSessionStep(uuid); 4267 4268 mDrmUUID = uuid; 4269 mActiveDrmScheme = true; 4270 4271 success = true; 4272 } catch (Exception e) { 4273 Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e); 4274 // mDrmObj clean up is done by the caller 4275 } 4276 4277 return success; 4278 } 4279 4280 private void resetDrmState() { 4281 synchronized (mDrmLock) { 4282 Log.v(TAG, "resetDrmState: " + 4283 " mDrmInfoImpl=" + mDrmInfoImpl + 4284 " mDrmProvisioningThread=" + mDrmProvisioningThread + 4285 " mPrepareDrmInProgress=" + mPrepareDrmInProgress + 4286 " mActiveDrmScheme=" + mActiveDrmScheme); 4287 4288 mDrmInfoResolved = false; 4289 mDrmInfoImpl = null; 4290 4291 if (mDrmProvisioningThread != null) { 4292 // timeout; relying on HttpUrlConnection 4293 try { 4294 mDrmProvisioningThread.join(); 4295 } 4296 catch (InterruptedException e) { 4297 Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); 4298 } 4299 mDrmProvisioningThread = null; 4300 } 4301 4302 mPrepareDrmInProgress = false; 4303 mActiveDrmScheme = false; 4304 4305 cleanDrmObj(); 4306 } // synchronized 4307 } 4308 4309 private void cleanDrmObj() { 4310 // the caller holds mDrmLock 4311 Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); 4312 4313 if (mDrmSessionId != null) { 4314 mDrmObj.closeSession(mDrmSessionId); 4315 mDrmSessionId = null; 4316 } 4317 if (mDrmObj != null) { 4318 mDrmObj.release(); 4319 mDrmObj = null; 4320 } 4321 } 4322 4323 private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { 4324 long msb = uuid.getMostSignificantBits(); 4325 long lsb = uuid.getLeastSignificantBits(); 4326 4327 byte[] uuidBytes = new byte[16]; 4328 for (int i = 0; i < 8; ++i) { 4329 uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); 4330 uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); 4331 } 4332 4333 return uuidBytes; 4334 } 4335 4336 // Modular DRM end 4337 4338 /* 4339 * Test whether a given video scaling mode is supported. 4340 */ 4341 private boolean isVideoScalingModeSupported(int mode) { 4342 return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT || 4343 mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); 4344 } 4345 4346 /** @hide */ 4347 static class TimeProvider implements MediaTimeProvider { 4348 private static final String TAG = "MTP"; 4349 private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; 4350 private static final long MAX_EARLY_CALLBACK_US = 1000; 4351 private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ 4352 private long mLastTimeUs = 0; 4353 private MediaPlayer2Impl mPlayer; 4354 private boolean mPaused = true; 4355 private boolean mStopped = true; 4356 private boolean mBuffering; 4357 private long mLastReportedTime; 4358 // since we are expecting only a handful listeners per stream, there is 4359 // no need for log(N) search performance 4360 private MediaTimeProvider.OnMediaTimeListener mListeners[]; 4361 private long mTimes[]; 4362 private EventHandler mEventHandler; 4363 private boolean mRefresh = false; 4364 private boolean mPausing = false; 4365 private boolean mSeeking = false; 4366 private static final int NOTIFY = 1; 4367 private static final int NOTIFY_TIME = 0; 4368 private static final int NOTIFY_STOP = 2; 4369 private static final int NOTIFY_SEEK = 3; 4370 private static final int NOTIFY_TRACK_DATA = 4; 4371 private HandlerThread mHandlerThread; 4372 4373 /** @hide */ 4374 public boolean DEBUG = false; 4375 4376 public TimeProvider(MediaPlayer2Impl mp) { 4377 mPlayer = mp; 4378 try { 4379 getCurrentTimeUs(true, false); 4380 } catch (IllegalStateException e) { 4381 // we assume starting position 4382 mRefresh = true; 4383 } 4384 4385 Looper looper; 4386 if ((looper = Looper.myLooper()) == null && 4387 (looper = Looper.getMainLooper()) == null) { 4388 // Create our own looper here in case MP was created without one 4389 mHandlerThread = new HandlerThread("MediaPlayer2MTPEventThread", 4390 Process.THREAD_PRIORITY_FOREGROUND); 4391 mHandlerThread.start(); 4392 looper = mHandlerThread.getLooper(); 4393 } 4394 mEventHandler = new EventHandler(looper); 4395 4396 mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; 4397 mTimes = new long[0]; 4398 mLastTimeUs = 0; 4399 } 4400 4401 private void scheduleNotification(int type, long delayUs) { 4402 // ignore time notifications until seek is handled 4403 if (mSeeking && type == NOTIFY_TIME) { 4404 return; 4405 } 4406 4407 if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); 4408 mEventHandler.removeMessages(NOTIFY); 4409 Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); 4410 mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); 4411 } 4412 4413 /** @hide */ 4414 public void close() { 4415 mEventHandler.removeMessages(NOTIFY); 4416 if (mHandlerThread != null) { 4417 mHandlerThread.quitSafely(); 4418 mHandlerThread = null; 4419 } 4420 } 4421 4422 /** @hide */ 4423 protected void finalize() { 4424 if (mHandlerThread != null) { 4425 mHandlerThread.quitSafely(); 4426 } 4427 } 4428 4429 /** @hide */ 4430 public void onNotifyTime() { 4431 synchronized (this) { 4432 if (DEBUG) Log.d(TAG, "onNotifyTime: "); 4433 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4434 } 4435 } 4436 4437 /** @hide */ 4438 public void onPaused(boolean paused) { 4439 synchronized(this) { 4440 if (DEBUG) Log.d(TAG, "onPaused: " + paused); 4441 if (mStopped) { // handle as seek if we were stopped 4442 mStopped = false; 4443 mSeeking = true; 4444 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 4445 } else { 4446 mPausing = paused; // special handling if player disappeared 4447 mSeeking = false; 4448 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4449 } 4450 } 4451 } 4452 4453 /** @hide */ 4454 public void onBuffering(boolean buffering) { 4455 synchronized (this) { 4456 if (DEBUG) Log.d(TAG, "onBuffering: " + buffering); 4457 mBuffering = buffering; 4458 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4459 } 4460 } 4461 4462 /** @hide */ 4463 public void onStopped() { 4464 synchronized(this) { 4465 if (DEBUG) Log.d(TAG, "onStopped"); 4466 mPaused = true; 4467 mStopped = true; 4468 mSeeking = false; 4469 mBuffering = false; 4470 scheduleNotification(NOTIFY_STOP, 0 /* delay */); 4471 } 4472 } 4473 4474 /** @hide */ 4475 public void onSeekComplete(MediaPlayer2Impl mp) { 4476 synchronized(this) { 4477 mStopped = false; 4478 mSeeking = true; 4479 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 4480 } 4481 } 4482 4483 /** @hide */ 4484 public void onNewPlayer() { 4485 if (mRefresh) { 4486 synchronized(this) { 4487 mStopped = false; 4488 mSeeking = true; 4489 mBuffering = false; 4490 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 4491 } 4492 } 4493 } 4494 4495 private synchronized void notifySeek() { 4496 mSeeking = false; 4497 try { 4498 long timeUs = getCurrentTimeUs(true, false); 4499 if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); 4500 4501 for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { 4502 if (listener == null) { 4503 break; 4504 } 4505 listener.onSeek(timeUs); 4506 } 4507 } catch (IllegalStateException e) { 4508 // we should not be there, but at least signal pause 4509 if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); 4510 mPausing = true; // special handling if player disappeared 4511 notifyTimedEvent(false /* refreshTime */); 4512 } 4513 } 4514 4515 private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) { 4516 SubtitleTrack track = trackData.first; 4517 byte[] data = trackData.second; 4518 track.onData(data, true /* eos */, ~0 /* runID: keep forever */); 4519 } 4520 4521 private synchronized void notifyStop() { 4522 for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { 4523 if (listener == null) { 4524 break; 4525 } 4526 listener.onStop(); 4527 } 4528 } 4529 4530 private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { 4531 int i = 0; 4532 for (; i < mListeners.length; i++) { 4533 if (mListeners[i] == listener || mListeners[i] == null) { 4534 break; 4535 } 4536 } 4537 4538 // new listener 4539 if (i >= mListeners.length) { 4540 MediaTimeProvider.OnMediaTimeListener[] newListeners = 4541 new MediaTimeProvider.OnMediaTimeListener[i + 1]; 4542 long[] newTimes = new long[i + 1]; 4543 System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); 4544 System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); 4545 mListeners = newListeners; 4546 mTimes = newTimes; 4547 } 4548 4549 if (mListeners[i] == null) { 4550 mListeners[i] = listener; 4551 mTimes[i] = MediaTimeProvider.NO_TIME; 4552 } 4553 return i; 4554 } 4555 4556 public void notifyAt( 4557 long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { 4558 synchronized(this) { 4559 if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); 4560 mTimes[registerListener(listener)] = timeUs; 4561 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4562 } 4563 } 4564 4565 public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { 4566 synchronized(this) { 4567 if (DEBUG) Log.d(TAG, "scheduleUpdate"); 4568 int i = registerListener(listener); 4569 4570 if (!mStopped) { 4571 mTimes[i] = 0; 4572 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4573 } 4574 } 4575 } 4576 4577 public void cancelNotifications( 4578 MediaTimeProvider.OnMediaTimeListener listener) { 4579 synchronized(this) { 4580 int i = 0; 4581 for (; i < mListeners.length; i++) { 4582 if (mListeners[i] == listener) { 4583 System.arraycopy(mListeners, i + 1, 4584 mListeners, i, mListeners.length - i - 1); 4585 System.arraycopy(mTimes, i + 1, 4586 mTimes, i, mTimes.length - i - 1); 4587 mListeners[mListeners.length - 1] = null; 4588 mTimes[mTimes.length - 1] = NO_TIME; 4589 break; 4590 } else if (mListeners[i] == null) { 4591 break; 4592 } 4593 } 4594 4595 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 4596 } 4597 } 4598 4599 private synchronized void notifyTimedEvent(boolean refreshTime) { 4600 // figure out next callback 4601 long nowUs; 4602 try { 4603 nowUs = getCurrentTimeUs(refreshTime, true); 4604 } catch (IllegalStateException e) { 4605 // assume we paused until new player arrives 4606 mRefresh = true; 4607 mPausing = true; // this ensures that call succeeds 4608 nowUs = getCurrentTimeUs(refreshTime, true); 4609 } 4610 long nextTimeUs = nowUs; 4611 4612 if (mSeeking) { 4613 // skip timed-event notifications until seek is complete 4614 return; 4615 } 4616 4617 if (DEBUG) { 4618 StringBuilder sb = new StringBuilder(); 4619 sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") 4620 .append(nowUs).append(") from {"); 4621 boolean first = true; 4622 for (long time: mTimes) { 4623 if (time == NO_TIME) { 4624 continue; 4625 } 4626 if (!first) sb.append(", "); 4627 sb.append(time); 4628 first = false; 4629 } 4630 sb.append("}"); 4631 Log.d(TAG, sb.toString()); 4632 } 4633 4634 Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners = 4635 new Vector<MediaTimeProvider.OnMediaTimeListener>(); 4636 for (int ix = 0; ix < mTimes.length; ix++) { 4637 if (mListeners[ix] == null) { 4638 break; 4639 } 4640 if (mTimes[ix] <= NO_TIME) { 4641 // ignore, unless we were stopped 4642 } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { 4643 activatedListeners.add(mListeners[ix]); 4644 if (DEBUG) Log.d(TAG, "removed"); 4645 mTimes[ix] = NO_TIME; 4646 } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { 4647 nextTimeUs = mTimes[ix]; 4648 } 4649 } 4650 4651 if (nextTimeUs > nowUs && !mPaused) { 4652 // schedule callback at nextTimeUs 4653 if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); 4654 mPlayer.notifyAt(nextTimeUs); 4655 } else { 4656 mEventHandler.removeMessages(NOTIFY); 4657 // no more callbacks 4658 } 4659 4660 for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { 4661 listener.onTimedEvent(nowUs); 4662 } 4663 } 4664 4665 public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) 4666 throws IllegalStateException { 4667 synchronized (this) { 4668 // we always refresh the time when the paused-state changes, because 4669 // we expect to have received the pause-change event delayed. 4670 if (mPaused && !refreshTime) { 4671 return mLastReportedTime; 4672 } 4673 4674 try { 4675 mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; 4676 mPaused = !mPlayer.isPlaying() || mBuffering; 4677 if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); 4678 } catch (IllegalStateException e) { 4679 if (mPausing) { 4680 // if we were pausing, get last estimated timestamp 4681 mPausing = false; 4682 if (!monotonic || mLastReportedTime < mLastTimeUs) { 4683 mLastReportedTime = mLastTimeUs; 4684 } 4685 mPaused = true; 4686 if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); 4687 return mLastReportedTime; 4688 } 4689 // TODO get time when prepared 4690 throw e; 4691 } 4692 if (monotonic && mLastTimeUs < mLastReportedTime) { 4693 /* have to adjust time */ 4694 if (mLastReportedTime - mLastTimeUs > 1000000) { 4695 // schedule seeked event if time jumped significantly 4696 // TODO: do this properly by introducing an exception 4697 mStopped = false; 4698 mSeeking = true; 4699 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 4700 } 4701 } else { 4702 mLastReportedTime = mLastTimeUs; 4703 } 4704 4705 return mLastReportedTime; 4706 } 4707 } 4708 4709 private class EventHandler extends Handler { 4710 public EventHandler(Looper looper) { 4711 super(looper); 4712 } 4713 4714 @Override 4715 public void handleMessage(Message msg) { 4716 if (msg.what == NOTIFY) { 4717 switch (msg.arg1) { 4718 case NOTIFY_TIME: 4719 notifyTimedEvent(true /* refreshTime */); 4720 break; 4721 case NOTIFY_STOP: 4722 notifyStop(); 4723 break; 4724 case NOTIFY_SEEK: 4725 notifySeek(); 4726 break; 4727 case NOTIFY_TRACK_DATA: 4728 notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj); 4729 break; 4730 } 4731 } 4732 } 4733 } 4734 } 4735 4736 private abstract class Task implements Runnable { 4737 private final int mMediaCallType; 4738 private final boolean mNeedToWaitForEventToComplete; 4739 private DataSourceDesc mDSD; 4740 4741 public Task (int mediaCallType, boolean needToWaitForEventToComplete) { 4742 mMediaCallType = mediaCallType; 4743 mNeedToWaitForEventToComplete = needToWaitForEventToComplete; 4744 } 4745 4746 abstract void process() throws IOException, NoDrmSchemeException; 4747 4748 @Override 4749 public void run() { 4750 int status = CALL_STATUS_NO_ERROR; 4751 try { 4752 process(); 4753 } catch (IllegalStateException e) { 4754 status = CALL_STATUS_INVALID_OPERATION; 4755 } catch (IllegalArgumentException e) { 4756 status = CALL_STATUS_BAD_VALUE; 4757 } catch (SecurityException e) { 4758 status = CALL_STATUS_PERMISSION_DENIED; 4759 } catch (IOException e) { 4760 status = CALL_STATUS_ERROR_IO; 4761 } catch (NoDrmSchemeException e) { 4762 status = CALL_STATUS_NO_DRM_SCHEME; 4763 } catch (Exception e) { 4764 status = CALL_STATUS_ERROR_UNKNOWN; 4765 } 4766 synchronized (mSrcLock) { 4767 mDSD = mCurrentDSD; 4768 } 4769 4770 // TODO: Make native implementations asynchronous and let them send notifications. 4771 if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) { 4772 4773 sendCompleteNotification(status); 4774 4775 synchronized (mTaskLock) { 4776 mCurrentTask = null; 4777 processPendingTask_l(); 4778 } 4779 } 4780 } 4781 4782 private void sendCompleteNotification(int status) { 4783 // In {@link #notifyWhenCommandLabelReached} case, a separate callback 4784 // {#link #onCommandLabelReached} is already called in {@code process()}. 4785 if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) { 4786 return; 4787 } 4788 synchronized (mEventCbLock) { 4789 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { 4790 cb.first.execute(() -> cb.second.onCallCompleted( 4791 MediaPlayer2Impl.this, mDSD, mMediaCallType, status)); 4792 } 4793 } 4794 } 4795 }; 4796 } 4797