summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java432
1 files changed, 432 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java
new file mode 100644
index 0000000000..66cb9a1fce
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/MediaPeriodHolder.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.ClippingMediaPeriod;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.EmptySampleStream;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaPeriod;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SampleStream;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroupArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelection;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelector;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelectorResult;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+
+/** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */
+/* package */ final class MediaPeriodHolder {
+
+ private static final String TAG = "MediaPeriodHolder";
+
+ /** The {@link MediaPeriod} wrapped by this class. */
+ public final MediaPeriod mediaPeriod;
+ /** The unique timeline period identifier the media period belongs to. */
+ public final Object uid;
+ /**
+ * The sample streams for each renderer associated with this period. May contain null elements.
+ */
+ public final @NullableType SampleStream[] sampleStreams;
+
+ /** Whether the media period has finished preparing. */
+ public boolean prepared;
+ /** Whether any of the tracks of this media period are enabled. */
+ public boolean hasEnabledTracks;
+ /** {@link MediaPeriodInfo} about this media period. */
+ public MediaPeriodInfo info;
+
+ private final boolean[] mayRetainStreamFlags;
+ private final RendererCapabilities[] rendererCapabilities;
+ private final TrackSelector trackSelector;
+ private final MediaSource mediaSource;
+
+ @Nullable private MediaPeriodHolder next;
+ private TrackGroupArray trackGroups;
+ private TrackSelectorResult trackSelectorResult;
+ private long rendererPositionOffsetUs;
+
+ /**
+ * Creates a new holder with information required to play it as part of a timeline.
+ *
+ * @param rendererCapabilities The renderer capabilities.
+ * @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds.
+ * @param trackSelector The track selector.
+ * @param allocator The allocator.
+ * @param mediaSource The media source that produced the media period.
+ * @param info Information used to identify this media period in its timeline period.
+ * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
+ * renderer.
+ */
+ public MediaPeriodHolder(
+ RendererCapabilities[] rendererCapabilities,
+ long rendererPositionOffsetUs,
+ TrackSelector trackSelector,
+ Allocator allocator,
+ MediaSource mediaSource,
+ MediaPeriodInfo info,
+ TrackSelectorResult emptyTrackSelectorResult) {
+ this.rendererCapabilities = rendererCapabilities;
+ this.rendererPositionOffsetUs = rendererPositionOffsetUs;
+ this.trackSelector = trackSelector;
+ this.mediaSource = mediaSource;
+ this.uid = info.id.periodUid;
+ this.info = info;
+ this.trackGroups = TrackGroupArray.EMPTY;
+ this.trackSelectorResult = emptyTrackSelectorResult;
+ sampleStreams = new SampleStream[rendererCapabilities.length];
+ mayRetainStreamFlags = new boolean[rendererCapabilities.length];
+ mediaPeriod =
+ createMediaPeriod(
+ info.id, mediaSource, allocator, info.startPositionUs, info.endPositionUs);
+ }
+
+ /**
+ * Converts time relative to the start of the period to the respective renderer time using {@link
+ * #getRendererOffset()}, in microseconds.
+ */
+ public long toRendererTime(long periodTimeUs) {
+ return periodTimeUs + getRendererOffset();
+ }
+
+ /**
+ * Converts renderer time to the respective time relative to the start of the period using {@link
+ * #getRendererOffset()}, in microseconds.
+ */
+ public long toPeriodTime(long rendererTimeUs) {
+ return rendererTimeUs - getRendererOffset();
+ }
+
+ /** Returns the renderer time of the start of the period, in microseconds. */
+ public long getRendererOffset() {
+ return rendererPositionOffsetUs;
+ }
+
+ /**
+ * Sets the renderer time of the start of the period, in microseconds.
+ *
+ * @param rendererPositionOffsetUs The new renderer position offset, in microseconds.
+ */
+ public void setRendererOffset(long rendererPositionOffsetUs) {
+ this.rendererPositionOffsetUs = rendererPositionOffsetUs;
+ }
+
+ /** Returns start position of period in renderer time. */
+ public long getStartPositionRendererTime() {
+ return info.startPositionUs + rendererPositionOffsetUs;
+ }
+
+ /** Returns whether the period is fully buffered. */
+ public boolean isFullyBuffered() {
+ return prepared
+ && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
+ }
+
+ /**
+ * Returns the buffered position in microseconds. If the period is buffered to the end, then the
+ * period duration is returned.
+ *
+ * @return The buffered position in microseconds.
+ */
+ public long getBufferedPositionUs() {
+ if (!prepared) {
+ return info.startPositionUs;
+ }
+ long bufferedPositionUs =
+ hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
+ return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs;
+ }
+
+ /**
+ * Returns the next load time relative to the start of the period, or {@link C#TIME_END_OF_SOURCE}
+ * if loading has finished.
+ */
+ public long getNextLoadPositionUs() {
+ return !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
+ }
+
+ /**
+ * Handles period preparation.
+ *
+ * @param playbackSpeed The current playback speed.
+ * @param timeline The current {@link Timeline}.
+ * @throws ExoPlaybackException If an error occurs during track selection.
+ */
+ public void handlePrepared(float playbackSpeed, Timeline timeline) throws ExoPlaybackException {
+ prepared = true;
+ trackGroups = mediaPeriod.getTrackGroups();
+ TrackSelectorResult selectorResult = selectTracks(playbackSpeed, timeline);
+ long newStartPositionUs =
+ applyTrackSelection(
+ selectorResult, info.startPositionUs, /* forceRecreateStreams= */ false);
+ rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
+ info = info.copyWithStartPositionUs(newStartPositionUs);
+ }
+
+ /**
+ * Reevaluates the buffer of the media period at the given renderer position. Should only be
+ * called if this is the loading media period.
+ *
+ * @param rendererPositionUs The playing position in renderer time, in microseconds.
+ */
+ public void reevaluateBuffer(long rendererPositionUs) {
+ Assertions.checkState(isLoadingMediaPeriod());
+ if (prepared) {
+ mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
+ }
+ }
+
+ /**
+ * Continues loading the media period at the given renderer position. Should only be called if
+ * this is the loading media period.
+ *
+ * @param rendererPositionUs The load position in renderer time, in microseconds.
+ */
+ public void continueLoading(long rendererPositionUs) {
+ Assertions.checkState(isLoadingMediaPeriod());
+ long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
+ mediaPeriod.continueLoading(loadingPeriodPositionUs);
+ }
+
+ /**
+ * Selects tracks for the period. Must only be called if {@link #prepared} is {@code true}.
+ *
+ * <p>The new track selection needs to be applied with {@link
+ * #applyTrackSelection(TrackSelectorResult, long, boolean)} before taking effect.
+ *
+ * @param playbackSpeed The current playback speed.
+ * @param timeline The current {@link Timeline}.
+ * @return The {@link TrackSelectorResult}.
+ * @throws ExoPlaybackException If an error occurs during track selection.
+ */
+ public TrackSelectorResult selectTracks(float playbackSpeed, Timeline timeline)
+ throws ExoPlaybackException {
+ TrackSelectorResult selectorResult =
+ trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
+ for (TrackSelection trackSelection : selectorResult.selections.getAll()) {
+ if (trackSelection != null) {
+ trackSelection.onPlaybackSpeed(playbackSpeed);
+ }
+ }
+ return selectorResult;
+ }
+
+ /**
+ * Applies a {@link TrackSelectorResult} to the period.
+ *
+ * @param trackSelectorResult The {@link TrackSelectorResult} to apply.
+ * @param positionUs The position relative to the start of the period at which to apply the new
+ * track selections, in microseconds.
+ * @param forceRecreateStreams Whether all streams are forced to be recreated.
+ * @return The actual position relative to the start of the period at which the new track
+ * selections are applied.
+ */
+ public long applyTrackSelection(
+ TrackSelectorResult trackSelectorResult, long positionUs, boolean forceRecreateStreams) {
+ return applyTrackSelection(
+ trackSelectorResult,
+ positionUs,
+ forceRecreateStreams,
+ new boolean[rendererCapabilities.length]);
+ }
+
+ /**
+ * Applies a {@link TrackSelectorResult} to the period.
+ *
+ * @param newTrackSelectorResult The {@link TrackSelectorResult} to apply.
+ * @param positionUs The position relative to the start of the period at which to apply the new
+ * track selections, in microseconds.
+ * @param forceRecreateStreams Whether all streams are forced to be recreated.
+ * @param streamResetFlags Will be populated to indicate which streams have been reset or were
+ * newly created.
+ * @return The actual position relative to the start of the period at which the new track
+ * selections are applied.
+ */
+ public long applyTrackSelection(
+ TrackSelectorResult newTrackSelectorResult,
+ long positionUs,
+ boolean forceRecreateStreams,
+ boolean[] streamResetFlags) {
+ for (int i = 0; i < newTrackSelectorResult.length; i++) {
+ mayRetainStreamFlags[i] =
+ !forceRecreateStreams && newTrackSelectorResult.isEquivalent(trackSelectorResult, i);
+ }
+
+ // Undo the effect of previous call to associate no-sample renderers with empty tracks
+ // so the mediaPeriod receives back whatever it sent us before.
+ disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams);
+ disableTrackSelectionsInResult();
+ trackSelectorResult = newTrackSelectorResult;
+ enableTrackSelectionsInResult();
+ // Disable streams on the period and get new streams for updated/newly-enabled tracks.
+ TrackSelectionArray trackSelections = newTrackSelectorResult.selections;
+ positionUs =
+ mediaPeriod.selectTracks(
+ trackSelections.getAll(),
+ mayRetainStreamFlags,
+ sampleStreams,
+ streamResetFlags,
+ positionUs);
+ associateNoSampleRenderersWithEmptySampleStream(sampleStreams);
+
+ // Update whether we have enabled tracks and sanity check the expected streams are non-null.
+ hasEnabledTracks = false;
+ for (int i = 0; i < sampleStreams.length; i++) {
+ if (sampleStreams[i] != null) {
+ Assertions.checkState(newTrackSelectorResult.isRendererEnabled(i));
+ // hasEnabledTracks should be true only when non-empty streams exists.
+ if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) {
+ hasEnabledTracks = true;
+ }
+ } else {
+ Assertions.checkState(trackSelections.get(i) == null);
+ }
+ }
+ return positionUs;
+ }
+
+ /** Releases the media period. No other method should be called after the release. */
+ public void release() {
+ disableTrackSelectionsInResult();
+ releaseMediaPeriod(info.endPositionUs, mediaSource, mediaPeriod);
+ }
+
+ /**
+ * Sets the next media period holder in the queue.
+ *
+ * @param nextMediaPeriodHolder The next holder, or null if this will be the new loading media
+ * period holder at the end of the queue.
+ */
+ public void setNext(@Nullable MediaPeriodHolder nextMediaPeriodHolder) {
+ if (nextMediaPeriodHolder == next) {
+ return;
+ }
+ disableTrackSelectionsInResult();
+ next = nextMediaPeriodHolder;
+ enableTrackSelectionsInResult();
+ }
+
+ /**
+ * Returns the next media period holder in the queue, or null if this is the last media period
+ * (and thus the loading media period).
+ */
+ @Nullable
+ public MediaPeriodHolder getNext() {
+ return next;
+ }
+
+ /** Returns the {@link TrackGroupArray} exposed by this media period. */
+ public TrackGroupArray getTrackGroups() {
+ return trackGroups;
+ }
+
+ /** Returns the {@link TrackSelectorResult} which is currently applied. */
+ public TrackSelectorResult getTrackSelectorResult() {
+ return trackSelectorResult;
+ }
+
+ private void enableTrackSelectionsInResult() {
+ if (!isLoadingMediaPeriod()) {
+ return;
+ }
+ for (int i = 0; i < trackSelectorResult.length; i++) {
+ boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
+ TrackSelection trackSelection = trackSelectorResult.selections.get(i);
+ if (rendererEnabled && trackSelection != null) {
+ trackSelection.enable();
+ }
+ }
+ }
+
+ private void disableTrackSelectionsInResult() {
+ if (!isLoadingMediaPeriod()) {
+ return;
+ }
+ for (int i = 0; i < trackSelectorResult.length; i++) {
+ boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
+ TrackSelection trackSelection = trackSelectorResult.selections.get(i);
+ if (rendererEnabled && trackSelection != null) {
+ trackSelection.disable();
+ }
+ }
+ }
+
+ /**
+ * For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy {@link
+ * EmptySampleStream} that was associated with it.
+ */
+ private void disassociateNoSampleRenderersWithEmptySampleStream(
+ @NullableType SampleStream[] sampleStreams) {
+ for (int i = 0; i < rendererCapabilities.length; i++) {
+ if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) {
+ sampleStreams[i] = null;
+ }
+ }
+ }
+
+ /**
+ * For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will associate it with
+ * a dummy {@link EmptySampleStream}.
+ */
+ private void associateNoSampleRenderersWithEmptySampleStream(
+ @NullableType SampleStream[] sampleStreams) {
+ for (int i = 0; i < rendererCapabilities.length; i++) {
+ if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE
+ && trackSelectorResult.isRendererEnabled(i)) {
+ sampleStreams[i] = new EmptySampleStream();
+ }
+ }
+ }
+
+ private boolean isLoadingMediaPeriod() {
+ return next == null;
+ }
+
+ /** Returns a media period corresponding to the given {@code id}. */
+ private static MediaPeriod createMediaPeriod(
+ MediaPeriodId id,
+ MediaSource mediaSource,
+ Allocator allocator,
+ long startPositionUs,
+ long endPositionUs) {
+ MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator, startPositionUs);
+ if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
+ mediaPeriod =
+ new ClippingMediaPeriod(
+ mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
+ }
+ return mediaPeriod;
+ }
+
+ /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
+ private static void releaseMediaPeriod(
+ long endPositionUs, MediaSource mediaSource, MediaPeriod mediaPeriod) {
+ try {
+ if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
+ mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
+ } else {
+ mediaSource.releasePeriod(mediaPeriod);
+ }
+ } catch (RuntimeException e) {
+ // There's nothing we can do.
+ Log.e(TAG, "Period release failed.", e);
+ }
+ }
+}