mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
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:
parent
59ab0fa9f1
commit
bf65df1b35
@ -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;
|
||||||
|
@ -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}.
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user