Allow ClippingMediaPeriod to be used on its own.

ClippingMediaPeriod may be a useful component for other MediaSources too so
remove its dependency on ClippingMediaSource.

Also allow the clipping end point to be TIME_END_OF_SOURCE, in which case
the clipping window extends to the end of the wrapped period.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=144056285
This commit is contained in:
andrewlewis 2017-01-10 01:08:26 -08:00 committed by Oliver Woodman
parent 59ab0fa9f1
commit bf65df1b35
3 changed files with 57 additions and 34 deletions

View File

@ -26,10 +26,12 @@ import java.io.IOException;
* Wraps a {@link MediaPeriod} and clips its {@link SampleStream}s to provide a subsequence of their * Wraps a {@link MediaPeriod} and clips its {@link SampleStream}s to provide a subsequence of their
* samples. * samples.
*/ */
/* package */ final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callback { public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
/**
* The {@link MediaPeriod} wrapped by this clipping media period.
*/
public final MediaPeriod mediaPeriod; public final MediaPeriod mediaPeriod;
private final ClippingMediaSource mediaSource;
private MediaPeriod.Callback callback; private MediaPeriod.Callback callback;
private long startUs; private long startUs;
@ -40,18 +42,31 @@ import java.io.IOException;
/** /**
* Creates a new clipping media period that provides a clipped view of the specified * Creates a new clipping media period that provides a clipped view of the specified
* {@link MediaPeriod}'s sample streams. * {@link MediaPeriod}'s sample streams.
* <p>
* The clipping start/end positions must be specified by calling {@link #setClipping(long, long)}
* on the playback thread before preparation completes.
* *
* @param mediaPeriod The media period to clip. * @param mediaPeriod The media period to clip.
* @param mediaSource The {@link ClippingMediaSource} to which this period belongs.
*/ */
public ClippingMediaPeriod(MediaPeriod mediaPeriod, ClippingMediaSource mediaSource) { public ClippingMediaPeriod(MediaPeriod mediaPeriod) {
this.mediaPeriod = mediaPeriod; this.mediaPeriod = mediaPeriod;
this.mediaSource = mediaSource;
startUs = C.TIME_UNSET; startUs = C.TIME_UNSET;
endUs = C.TIME_UNSET; endUs = C.TIME_UNSET;
sampleStreams = new ClippingSampleStream[0]; sampleStreams = new ClippingSampleStream[0];
} }
/**
* Sets 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) {
this.startUs = startUs;
this.endUs = endUs;
}
@Override @Override
public void prepare(MediaPeriod.Callback callback) { public void prepare(MediaPeriod.Callback callback) {
this.callback = callback; this.callback = callback;
@ -80,7 +95,8 @@ import java.io.IOException;
long enablePositionUs = mediaPeriod.selectTracks(selections, mayRetainStreamFlags, long enablePositionUs = mediaPeriod.selectTracks(selections, mayRetainStreamFlags,
internalStreams, streamResetFlags, positionUs + startUs); internalStreams, streamResetFlags, positionUs + startUs);
Assertions.checkState(enablePositionUs == positionUs + startUs Assertions.checkState(enablePositionUs == positionUs + startUs
|| (enablePositionUs >= startUs && enablePositionUs <= endUs)); || (enablePositionUs >= startUs
&& (endUs == C.TIME_END_OF_SOURCE || enablePositionUs <= endUs)));
for (int i = 0; i < streams.length; i++) { for (int i = 0; i < streams.length; i++) {
if (internalStreams[i] == null) { if (internalStreams[i] == null) {
sampleStreams[i] = null; sampleStreams[i] = null;
@ -110,14 +126,16 @@ import java.io.IOException;
if (discontinuityUs == C.TIME_UNSET) { if (discontinuityUs == C.TIME_UNSET) {
return C.TIME_UNSET; return C.TIME_UNSET;
} }
Assertions.checkState(discontinuityUs >= startUs && discontinuityUs <= endUs); Assertions.checkState(discontinuityUs >= startUs);
Assertions.checkState(endUs == C.TIME_END_OF_SOURCE || discontinuityUs <= endUs);
return discontinuityUs - startUs; return discontinuityUs - startUs;
} }
@Override @Override
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
long bufferedPositionUs = mediaPeriod.getBufferedPositionUs(); long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
if (bufferedPositionUs == C.TIME_END_OF_SOURCE || bufferedPositionUs >= endUs) { if (bufferedPositionUs == C.TIME_END_OF_SOURCE
|| (endUs != C.TIME_END_OF_SOURCE && bufferedPositionUs >= endUs)) {
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} }
return Math.max(0, bufferedPositionUs - startUs); return Math.max(0, bufferedPositionUs - startUs);
@ -131,14 +149,16 @@ import java.io.IOException;
} }
} }
long seekUs = mediaPeriod.seekToUs(positionUs + startUs); long seekUs = mediaPeriod.seekToUs(positionUs + startUs);
Assertions.checkState(seekUs == positionUs + startUs || (seekUs >= startUs && seekUs <= endUs)); Assertions.checkState(seekUs == positionUs + startUs
|| (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs)));
return seekUs - startUs; return seekUs - startUs;
} }
@Override @Override
public long getNextLoadPositionUs() { public long getNextLoadPositionUs() {
long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs(); long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE || nextLoadPositionUs >= endUs) { if (nextLoadPositionUs == C.TIME_END_OF_SOURCE
|| (endUs != C.TIME_END_OF_SOURCE && nextLoadPositionUs >= endUs)) {
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} }
return nextLoadPositionUs - startUs; return nextLoadPositionUs - startUs;
@ -153,8 +173,6 @@ import java.io.IOException;
@Override @Override
public void onPrepared(MediaPeriod mediaPeriod) { public void onPrepared(MediaPeriod mediaPeriod) {
startUs = mediaSource.getStartUs();
endUs = mediaSource.getEndUs();
Assertions.checkState(startUs != C.TIME_UNSET && endUs != C.TIME_UNSET); Assertions.checkState(startUs != C.TIME_UNSET && endUs != C.TIME_UNSET);
// If the clipping start position is non-zero, the clipping sample streams will adjust // If the clipping start position is non-zero, the clipping sample streams will adjust
// timestamps on buffers they read from the unclipped sample streams. These adjusted buffer // timestamps on buffers they read from the unclipped sample streams. These adjusted buffer
@ -223,15 +241,15 @@ import java.io.IOException;
} }
int result = stream.readData(formatHolder, buffer); int result = stream.readData(formatHolder, buffer);
// TODO: Clear gapless playback metadata if a format was read (if applicable). // TODO: Clear gapless playback metadata if a format was read (if applicable).
if ((result == C.RESULT_BUFFER_READ && buffer.timeUs >= endUs) if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ
|| (result == C.RESULT_NOTHING_READ && buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ
&& mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE)) { && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE))) {
buffer.clear(); buffer.clear();
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
sentEos = true; sentEos = true;
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
} }
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) {
buffer.timeUs -= startUs; buffer.timeUs -= startUs;
} }
return result; return result;

View File

@ -21,17 +21,19 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
/** /**
* {@link MediaSource} that wraps a source and clips its timeline based on specified start/end * {@link MediaSource} that wraps a source and clips its timeline based on specified start/end
* positions. The wrapped source may only have a single period/window and it must not be dynamic * positions. The wrapped source may only have a single period/window and it must not be dynamic
* (live). The specified start position must correspond to a synchronization sample in the period. * (live).
*/ */
public final class ClippingMediaSource implements MediaSource, MediaSource.Listener { public final class ClippingMediaSource implements MediaSource, MediaSource.Listener {
private final MediaSource mediaSource; private final MediaSource mediaSource;
private final long startUs; private final long startUs;
private final long endUs; private final long endUs;
private final ArrayList<ClippingMediaPeriod> mediaPeriods;
private MediaSource.Listener sourceListener; private MediaSource.Listener sourceListener;
private ClippingTimeline clippingTimeline; private ClippingTimeline clippingTimeline;
@ -51,20 +53,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
this.mediaSource = Assertions.checkNotNull(mediaSource); this.mediaSource = Assertions.checkNotNull(mediaSource);
startUs = startPositionUs; startUs = startPositionUs;
endUs = endPositionUs; endUs = endPositionUs;
} mediaPeriods = new ArrayList<>();
/**
* Returns the start position of the clipping source's timeline in microseconds.
*/
/* package */ long getStartUs() {
return clippingTimeline.startUs;
}
/**
* Returns the end position of the clipping source's timeline in microseconds.
*/
/* package */ long getEndUs() {
return clippingTimeline.endUs;
} }
@Override @Override
@ -80,12 +69,16 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
@Override @Override
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
return new ClippingMediaPeriod( ClippingMediaPeriod mediaPeriod = new ClippingMediaPeriod(
mediaSource.createPeriod(index, allocator, startUs + positionUs), this); mediaSource.createPeriod(index, allocator, startUs + positionUs));
mediaPeriods.add(mediaPeriod);
mediaPeriod.setClipping(clippingTimeline.startUs, clippingTimeline.endUs);
return mediaPeriod;
} }
@Override @Override
public void releasePeriod(MediaPeriod mediaPeriod) { public void releasePeriod(MediaPeriod mediaPeriod) {
Assertions.checkState(mediaPeriods.remove(mediaPeriod));
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
} }
@ -100,6 +93,13 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
clippingTimeline = new ClippingTimeline(timeline, startUs, endUs); clippingTimeline = new ClippingTimeline(timeline, startUs, endUs);
sourceListener.onSourceInfoRefreshed(clippingTimeline, manifest); sourceListener.onSourceInfoRefreshed(clippingTimeline, manifest);
long startUs = clippingTimeline.startUs;
long endUs = clippingTimeline.endUs == C.TIME_UNSET ? C.TIME_END_OF_SOURCE
: clippingTimeline.endUs;
int count = mediaPeriods.size();
for (int i = 0; i < count; i++) {
mediaPeriods.get(i).setClipping(startUs, endUs);
}
} }
/** /**
@ -112,7 +112,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
private final long endUs; private final long endUs;
/** /**
* Creates a new timeline that wraps the specified timeline. * Creates a new clipping timeline that wraps the specified timeline.
* *
* @param timeline The timeline to clip. * @param timeline The timeline to clip.
* @param startUs The number of microseconds to clip from the start of {@code timeline}. * @param startUs The number of microseconds to clip from the start of {@code timeline}.

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.io.IOException; import java.io.IOException;
@ -47,6 +48,10 @@ public interface MediaPeriod extends SequenceableLoader {
* <p> * <p>
* {@code callback.onPrepared} is called when preparation completes. If preparation fails, * {@code callback.onPrepared} is called when preparation completes. If preparation fails,
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}. * {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
* <p>
* If preparation succeeds and results in a source timeline change (e.g. the period duration
* becoming known), {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} will be
* called before {@code callback.onPrepared}.
* *
* @param callback Callback to receive updates from this period, including being notified when * @param callback Callback to receive updates from this period, including being notified when
* preparation completes. * preparation completes.