diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 7f09c63db5..2f71d0d547 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -83,9 +83,12 @@ import com.google.android.exoplayer2.util.Assertions; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator); if (info.endPositionUs != C.TIME_END_OF_SOURCE) { - ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true); - clippingMediaPeriod.setClipping(0, info.endPositionUs); - mediaPeriod = clippingMediaPeriod; + mediaPeriod = + new ClippingMediaPeriod( + mediaPeriod, + /* enableInitialDiscontinuity= */ true, + /* startUs= */ 0, + info.endPositionUs); } this.mediaPeriod = mediaPeriod; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 98bd7daaec..c078053110 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -43,35 +44,36 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb /* package */ long endUs; /** - * Creates a new clipping media period that provides a clipped view of the specified - * {@link MediaPeriod}'s sample streams. - *
- * The clipping start/end positions must be specified by calling {@link #setClipping(long, long)} - * on the playback thread before preparation completes. - *
- * If the start point is guaranteed to be a key frame, pass {@code false} to {@code + * Creates a new clipping media period that provides a clipped view of the specified {@link + * MediaPeriod}'s sample streams. + * + *
If the start point is guaranteed to be a key frame, pass {@code false} to {@code
* enableInitialPositionDiscontinuity} to suppress an initial discontinuity when the period is
* first read from.
*
* @param mediaPeriod The media period to clip.
* @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled.
+ * @param startUs The clipping start time, in microseconds.
+ * @param endUs The clipping end time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
+ * indicate the end of the period.
*/
- public ClippingMediaPeriod(MediaPeriod mediaPeriod, boolean enableInitialDiscontinuity) {
+ public ClippingMediaPeriod(
+ MediaPeriod mediaPeriod, boolean enableInitialDiscontinuity, long startUs, long endUs) {
this.mediaPeriod = mediaPeriod;
sampleStreams = new ClippingSampleStream[0];
- pendingInitialDiscontinuityPositionUs = enableInitialDiscontinuity ? 0 : C.TIME_UNSET;
- startUs = C.TIME_UNSET;
- endUs = C.TIME_UNSET;
+ pendingInitialDiscontinuityPositionUs = enableInitialDiscontinuity ? startUs : C.TIME_UNSET;
+ this.startUs = startUs;
+ this.endUs = endUs;
}
/**
- * Sets the clipping start/end times for this period, in microseconds.
+ * Updates the clipping start/end times for this period, in microseconds.
*
* @param startUs The clipping start time, in microseconds.
* @param endUs The clipping end time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
* indicate the end of the period.
*/
- public void setClipping(long startUs, long endUs) {
+ public void updateClipping(long startUs, long endUs) {
this.startUs = startUs;
this.endUs = endUs;
}
@@ -79,7 +81,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override
public void prepare(MediaPeriod.Callback callback, long positionUs) {
this.callback = callback;
- mediaPeriod.prepare(this, startUs + positionUs);
+ mediaPeriod.prepare(this, positionUs);
}
@Override
@@ -101,13 +103,19 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
sampleStreams[i] = (ClippingSampleStream) streams[i];
childStreams[i] = sampleStreams[i] != null ? sampleStreams[i].childStream : null;
}
- long enablePositionUs = mediaPeriod.selectTracks(selections, mayRetainStreamFlags,
- childStreams, streamResetFlags, positionUs + startUs) - startUs;
- pendingInitialDiscontinuityPositionUs = isPendingInitialDiscontinuity() && positionUs == 0
- && shouldKeepInitialDiscontinuity(startUs, selections) ? enablePositionUs : C.TIME_UNSET;
- Assertions.checkState(enablePositionUs == positionUs
- || (enablePositionUs >= 0
- && (endUs == C.TIME_END_OF_SOURCE || startUs + enablePositionUs <= endUs)));
+ long enablePositionUs =
+ mediaPeriod.selectTracks(
+ selections, mayRetainStreamFlags, childStreams, streamResetFlags, positionUs);
+ pendingInitialDiscontinuityPositionUs =
+ isPendingInitialDiscontinuity()
+ && positionUs == startUs
+ && shouldKeepInitialDiscontinuity(startUs, selections)
+ ? enablePositionUs
+ : C.TIME_UNSET;
+ Assertions.checkState(
+ enablePositionUs == positionUs
+ || (enablePositionUs >= startUs
+ && (endUs == C.TIME_END_OF_SOURCE || enablePositionUs <= endUs)));
for (int i = 0; i < streams.length; i++) {
if (childStreams[i] == null) {
sampleStreams[i] = null;
@@ -121,12 +129,12 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override
public void discardBuffer(long positionUs, boolean toKeyframe) {
- mediaPeriod.discardBuffer(positionUs + startUs, toKeyframe);
+ mediaPeriod.discardBuffer(positionUs, toKeyframe);
}
@Override
public void reevaluateBuffer(long positionUs) {
- mediaPeriod.reevaluateBuffer(positionUs + startUs);
+ mediaPeriod.reevaluateBuffer(positionUs);
}
@Override
@@ -144,7 +152,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
Assertions.checkState(discontinuityUs >= startUs);
Assertions.checkState(endUs == C.TIME_END_OF_SOURCE || discontinuityUs <= endUs);
- return discontinuityUs - startUs;
+ return discontinuityUs;
}
@Override
@@ -154,7 +162,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|| (endUs != C.TIME_END_OF_SOURCE && bufferedPositionUs >= endUs)) {
return C.TIME_END_OF_SOURCE;
}
- return Math.max(0, bufferedPositionUs - startUs);
+ return bufferedPositionUs;
}
@Override
@@ -165,23 +173,21 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
sampleStream.clearSentEos();
}
}
- long offsetPositionUs = positionUs + startUs;
- long seekUs = mediaPeriod.seekToUs(offsetPositionUs);
+ long seekUs = mediaPeriod.seekToUs(positionUs);
Assertions.checkState(
- seekUs == offsetPositionUs
+ seekUs == positionUs
|| (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs)));
- return seekUs - startUs;
+ return seekUs;
}
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
- if (positionUs == 0) {
+ if (positionUs == startUs) {
// Never adjust seeks to the start of the clipped view.
- return 0;
+ return startUs;
}
- long offsetPositionUs = positionUs + startUs;
- SeekParameters clippedSeekParameters = clipSeekParameters(offsetPositionUs, seekParameters);
- return mediaPeriod.getAdjustedSeekPositionUs(offsetPositionUs, clippedSeekParameters) - startUs;
+ SeekParameters clippedSeekParameters = clipSeekParameters(positionUs, seekParameters);
+ return mediaPeriod.getAdjustedSeekPositionUs(positionUs, clippedSeekParameters);
}
@Override
@@ -191,19 +197,18 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|| (endUs != C.TIME_END_OF_SOURCE && nextLoadPositionUs >= endUs)) {
return C.TIME_END_OF_SOURCE;
}
- return nextLoadPositionUs - startUs;
+ return nextLoadPositionUs;
}
@Override
public boolean continueLoading(long positionUs) {
- return mediaPeriod.continueLoading(positionUs + startUs);
+ return mediaPeriod.continueLoading(positionUs);
}
// MediaPeriod.Callback implementation.
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
- Assertions.checkState(startUs != C.TIME_UNSET && endUs != C.TIME_UNSET);
callback.onPrepared(this);
}
@@ -216,17 +221,20 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
return pendingInitialDiscontinuityPositionUs != C.TIME_UNSET;
}
- private SeekParameters clipSeekParameters(long offsetPositionUs, SeekParameters seekParameters) {
- long toleranceBeforeMs = Math.min(offsetPositionUs - startUs, seekParameters.toleranceBeforeUs);
- long toleranceAfterMs =
- endUs == C.TIME_END_OF_SOURCE
- ? seekParameters.toleranceAfterUs
- : Math.min(endUs - offsetPositionUs, seekParameters.toleranceAfterUs);
- if (toleranceBeforeMs == seekParameters.toleranceBeforeUs
- && toleranceAfterMs == seekParameters.toleranceAfterUs) {
+ private SeekParameters clipSeekParameters(long positionUs, SeekParameters seekParameters) {
+ long toleranceBeforeUs =
+ Util.constrainValue(
+ seekParameters.toleranceBeforeUs, /* min= */ 0, /* max= */ positionUs - startUs);
+ long toleranceAfterUs =
+ Util.constrainValue(
+ seekParameters.toleranceAfterUs,
+ /* min= */ 0,
+ /* max= */ endUs == C.TIME_END_OF_SOURCE ? Long.MAX_VALUE : endUs - positionUs);
+ if (toleranceBeforeUs == seekParameters.toleranceBeforeUs
+ && toleranceAfterUs == seekParameters.toleranceAfterUs) {
return seekParameters;
} else {
- return new SeekParameters(toleranceBeforeMs, toleranceAfterMs);
+ return new SeekParameters(toleranceBeforeUs, toleranceAfterUs);
}
}
@@ -310,9 +318,6 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
sentEos = true;
return C.RESULT_BUFFER_READ;
}
- if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) {
- buffer.timeUs -= startUs;
- }
return result;
}
@@ -321,7 +326,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
if (isPendingInitialDiscontinuity()) {
return C.RESULT_NOTHING_READ;
}
- return childStream.skipData(startUs + positionUs);
+ return childStream.skipData(positionUs);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
index 6c62329820..f633dd8f15 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
@@ -76,14 +76,20 @@ public final class ClippingMediaSource extends CompositeMediaSource For live streams, if the clipping positions should move with the live window, pass {@code
+ * true} to {@code allowDynamicClippingUpdates}. Otherwise, the live stream ends when the playback
+ * reaches {@code endPositionUs} in the last reported live window at the time a media period was
+ * created.
+ *
* @param mediaSource The single-period source to wrap.
- * @param startPositionUs The start position within {@code mediaSource}'s timeline at which to
- * start providing samples, in microseconds.
- * @param endPositionUs The end position within {@code mediaSource}'s timeline at which to stop
- * providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples
- * from the specified start point up to the end of the source. Specifying a position that
- * exceeds the {@code mediaSource}'s duration will also result in the end of the source not
- * being clipped.
+ * @param startPositionUs The start position at which to start providing samples, in microseconds.
+ * If {@code relativeToDefaultPosition} is {@code false}, this position is relative to the
+ * start of the window in {@code mediaSource}'s timeline. If {@code relativeToDefaultPosition}
+ * is {@code true}, this position is relative to the default position in the window in {@code
+ * mediaSource}'s timeline.
+ * @param endPositionUs The end position at which to stop providing samples, in microseconds.
+ * Specify {@link C#TIME_END_OF_SOURCE} to provide samples from the specified start point up
+ * to the end of the source. Specifying a position that exceeds the {@code mediaSource}'s
+ * duration will also result in the end of the source not being clipped. If {@code
+ * relativeToDefaultPosition} is {@code false}, the specified position is relative to the
+ * start of the window in {@code mediaSource}'s timeline. If {@code relativeToDefaultPosition}
+ * is {@code true}, this position is relative to the default position in the window in {@code
+ * mediaSource}'s timeline.
* @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled.
+ * @param allowDynamicClippingUpdates Whether the clipping of active media periods moves with a
+ * live window. If {@code false}, playback ends when it reaches {@code endPositionUs} in the
+ * last reported live window at the time a media period was created.
+ * @param relativeToDefaultPosition Whether {@code startPositionUs} and {@code endPositionUs} are
+ * relative to the default position in the window in {@code mediaSource}'s timeline.
*/
public ClippingMediaSource(
MediaSource mediaSource,
long startPositionUs,
long endPositionUs,
- boolean enableInitialDiscontinuity) {
+ boolean enableInitialDiscontinuity,
+ boolean allowDynamicClippingUpdates,
+ boolean relativeToDefaultPosition) {
Assertions.checkArgument(startPositionUs >= 0);
this.mediaSource = Assertions.checkNotNull(mediaSource);
startUs = startPositionUs;
endUs = endPositionUs;
this.enableInitialDiscontinuity = enableInitialDiscontinuity;
+ this.allowDynamicClippingUpdates = allowDynamicClippingUpdates;
+ this.relativeToDefaultPosition = relativeToDefaultPosition;
mediaPeriods = new ArrayList<>();
+ window = new Timeline.Window();
}
@Override
@@ -146,9 +228,11 @@ public final class ClippingMediaSource extends CompositeMediaSource