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