mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
Propagate resets at the source rather than track level.
- Code is simpler. We only ever reset all tracks. - Allows the standalone media clock to be updated properly. This allows simpler recovery for live streams in ExtractorSampleSource. - Fixes #1285 and paves the way for a fix for #758. Issue: #1285 Issue: #758 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=120530682
This commit is contained in:
parent
2b13165738
commit
90b7081824
@ -356,14 +356,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
TraceUtil.beginSection("doSomeWork");
|
||||
|
||||
// Process resets.
|
||||
for (TrackRenderer renderer : enabledRenderers) {
|
||||
renderer.checkForReset();
|
||||
}
|
||||
|
||||
updatePositionUs();
|
||||
updateBufferedPositionUs();
|
||||
if (enabledRenderers.length > 0) {
|
||||
// Process reset if there is one, else update the position.
|
||||
if (!checkForSourceResetInternal()) {
|
||||
updatePositionUs();
|
||||
}
|
||||
updateBufferedPositionUs();
|
||||
source.continueBuffering(positionUs);
|
||||
}
|
||||
|
||||
@ -435,14 +433,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabledRenderers != null) {
|
||||
if (enabledRenderers.length > 0) {
|
||||
for (TrackRenderer renderer : enabledRenderers) {
|
||||
ensureStopped(renderer);
|
||||
}
|
||||
source.seekToUs(positionUs);
|
||||
for (TrackRenderer renderer : enabledRenderers) {
|
||||
renderer.checkForReset();
|
||||
}
|
||||
checkForSourceResetInternal();
|
||||
}
|
||||
|
||||
resumeInternal();
|
||||
@ -474,6 +470,19 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
|
||||
private boolean checkForSourceResetInternal() throws ExoPlaybackException {
|
||||
long resetPositionUs = source.readReset();
|
||||
if (resetPositionUs == C.UNSET_TIME_US) {
|
||||
return false;
|
||||
}
|
||||
positionUs = resetPositionUs;
|
||||
standaloneMediaClock.setPositionUs(resetPositionUs);
|
||||
for (TrackRenderer renderer : enabledRenderers) {
|
||||
renderer.reset(resetPositionUs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void stopInternal() {
|
||||
resetInternal();
|
||||
setState(ExoPlayer.STATE_IDLE);
|
||||
|
@ -73,11 +73,11 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
private final long fileDescriptorLength;
|
||||
|
||||
private boolean prepared;
|
||||
private boolean notifyReset;
|
||||
private long durationUs;
|
||||
private MediaExtractor extractor;
|
||||
private TrackGroupArray tracks;
|
||||
private int[] trackStates;
|
||||
private boolean[] pendingResets;
|
||||
|
||||
private int enabledTrackCount;
|
||||
private long lastSeekPositionUs;
|
||||
@ -135,7 +135,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
|
||||
}
|
||||
trackStates = new int[extractor.getTrackCount()];
|
||||
pendingResets = new boolean[trackStates.length];
|
||||
TrackGroup[] trackArray = new TrackGroup[trackStates.length];
|
||||
for (int i = 0; i < trackStates.length; i++) {
|
||||
MediaFormat format = extractor.getTrackFormat(i);
|
||||
@ -170,7 +169,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
enabledTrackCount--;
|
||||
trackStates[track] = TRACK_STATE_DISABLED;
|
||||
extractor.unselectTrack(track);
|
||||
pendingResets[track] = false;
|
||||
}
|
||||
// Select new tracks.
|
||||
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||
@ -197,6 +195,15 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
// MediaExtractor takes care of buffering. Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
if (notifyReset) {
|
||||
notifyReset = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
if (enabledTrackCount == 0) {
|
||||
@ -230,17 +237,9 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
|
||||
// TrackStream methods.
|
||||
|
||||
/* package */ long readReset(int track) {
|
||||
if (pendingResets[track]) {
|
||||
pendingResets[track] = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
/* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||
if (pendingResets[track]) {
|
||||
if (notifyReset) {
|
||||
return TrackStream.NOTHING_READ;
|
||||
}
|
||||
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
||||
@ -302,11 +301,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingSeekPositionUs = positionUs;
|
||||
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
|
||||
for (int i = 0; i < trackStates.length; ++i) {
|
||||
if (trackStates[i] != TRACK_STATE_DISABLED) {
|
||||
pendingResets[i] = true;
|
||||
}
|
||||
}
|
||||
notifyReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,11 +377,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
return FrameworkSampleSource.this.readReset(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
return FrameworkSampleSource.this.readData(track, formatHolder, buffer);
|
||||
|
@ -123,6 +123,20 @@ public final class MultiSampleSource implements SampleSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
long resetPositionUs = C.UNSET_TIME_US;
|
||||
for (SampleSource source : enabledSources) {
|
||||
long childResetPositionUs = source.readReset();
|
||||
if (resetPositionUs == C.UNSET_TIME_US) {
|
||||
resetPositionUs = childResetPositionUs;
|
||||
} else if (childResetPositionUs != C.UNSET_TIME_US) {
|
||||
resetPositionUs = Math.min(resetPositionUs, childResetPositionUs);
|
||||
}
|
||||
}
|
||||
return resetPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE;
|
||||
|
@ -84,6 +84,14 @@ public interface SampleSource {
|
||||
*/
|
||||
void continueBuffering(long positionUs);
|
||||
|
||||
/**
|
||||
* Attempts to read a pending reset.
|
||||
*
|
||||
* @return If a reset was read then the playback position in microseconds after the reset. Else
|
||||
* {@link C#UNSET_TIME_US}.
|
||||
*/
|
||||
long readReset();
|
||||
|
||||
/**
|
||||
* Returns an estimate of the position up to which data is buffered for the enabled tracks.
|
||||
* <p>
|
||||
|
@ -215,21 +215,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read and process a pending reset from the {@link TrackStream}.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void checkForReset() throws ExoPlaybackException {
|
||||
long resetPositionUs = stream.readReset();
|
||||
if (resetPositionUs != C.UNSET_TIME_US) {
|
||||
reset(resetPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the renderer.
|
||||
*
|
||||
|
@ -53,19 +53,8 @@ public interface TrackStream {
|
||||
*/
|
||||
void maybeThrowError() throws IOException;
|
||||
|
||||
/**
|
||||
* Attempts to read a pending reset.
|
||||
*
|
||||
* @return If a reset was read then the playback position in microseconds after the reset. Else
|
||||
* {@link C#UNSET_TIME_US}.
|
||||
*/
|
||||
long readReset();
|
||||
|
||||
/**
|
||||
* Attempts to read from the stream.
|
||||
* <p>
|
||||
* This method will always return {@link #NOTHING_READ} in the case that there's a pending
|
||||
* discontinuity to be read from {@link #readReset} for the specified track.
|
||||
*
|
||||
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
|
||||
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
|
||||
|
@ -207,22 +207,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
|
||||
private boolean prepared;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private boolean notifyReset;
|
||||
private int enabledTrackCount;
|
||||
private DefaultTrackOutput[] sampleQueues;
|
||||
private TrackGroupArray tracks;
|
||||
private long durationUs;
|
||||
private boolean[] pendingMediaFormat;
|
||||
private boolean[] pendingResets;
|
||||
private boolean[] trackEnabledStates;
|
||||
|
||||
private long downstreamPositionUs;
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingResetPositionUs;
|
||||
|
||||
private boolean havePendingNextSampleUs;
|
||||
private long pendingNextSampleUs;
|
||||
private long sampleTimeOffsetUs;
|
||||
|
||||
private ExtractingLoadable loadable;
|
||||
private IOException fatalException;
|
||||
private int extractedSamplesCountAtStartOfLoad;
|
||||
@ -336,7 +332,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
int trackCount = sampleQueues.length;
|
||||
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||
trackEnabledStates = new boolean[trackCount];
|
||||
pendingResets = new boolean[trackCount];
|
||||
pendingMediaFormat = new boolean[trackCount];
|
||||
durationUs = seekMap.getDurationUs();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
@ -390,7 +385,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
enabledTrackCount++;
|
||||
trackEnabledStates[track] = true;
|
||||
pendingMediaFormat[track] = true;
|
||||
pendingResets[track] = false;
|
||||
newStreams[i] = new TrackStreamImpl(track);
|
||||
}
|
||||
// Cancel or start requests as necessary.
|
||||
@ -415,6 +409,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
discardSamplesForDisabledTracks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
if (notifyReset) {
|
||||
notifyReset = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
if (loadingFinished) {
|
||||
@ -458,16 +461,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
loader.maybeThrowError();
|
||||
}
|
||||
|
||||
/* package */ long readReset(int track) {
|
||||
if (pendingResets[track]) {
|
||||
pendingResets[track] = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
/* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
if (pendingResets[track] || isPendingReset()) {
|
||||
if (notifyReset || isPendingReset()) {
|
||||
return TrackStream.NOTHING_READ;
|
||||
}
|
||||
|
||||
@ -483,12 +478,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
if (buffer.timeUs < lastSeekPositionUs) {
|
||||
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
}
|
||||
if (havePendingNextSampleUs) {
|
||||
// Set the offset to make the timestamp of this sample equal to pendingNextSampleUs.
|
||||
sampleTimeOffsetUs = pendingNextSampleUs - buffer.timeUs;
|
||||
havePendingNextSampleUs = false;
|
||||
}
|
||||
buffer.timeUs += sampleTimeOffsetUs;
|
||||
return TrackStream.BUFFER_READ;
|
||||
}
|
||||
|
||||
@ -524,10 +513,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
fatalException = e;
|
||||
return Loader.DONT_RETRY;
|
||||
}
|
||||
configureRetry();
|
||||
int extractedSamplesCount = getExtractedSamplesCount();
|
||||
boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad;
|
||||
extractedSamplesCountAtStartOfLoad = extractedSamplesCount;
|
||||
configureRetry(); // May clear the sample queues.
|
||||
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
|
||||
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
|
||||
}
|
||||
|
||||
@ -563,7 +552,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
positionUs = seekMap.isSeekable() ? positionUs : 0;
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
Arrays.fill(pendingResets, true);
|
||||
notifyReset = true;
|
||||
// If we're not pending a reset, see if we can seek within the sample queues.
|
||||
boolean seekInsideBuffer = !isPendingReset();
|
||||
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
|
||||
@ -589,8 +578,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
}
|
||||
|
||||
private void startLoading() {
|
||||
sampleTimeOffsetUs = 0;
|
||||
havePendingNextSampleUs = false;
|
||||
loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, allocator,
|
||||
requestedBufferSize);
|
||||
if (prepared) {
|
||||
@ -620,12 +607,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
// therefore that the data at the uri is a continuously shifting window of the latest
|
||||
// available media. For this case there's no way to continue loading from where a previous
|
||||
// load finished, so it's necessary to load from the start whenever commencing a new load.
|
||||
notifyReset = true;
|
||||
clearSampleQueues();
|
||||
loadable.setLoadPosition(0);
|
||||
// To avoid introducing a discontinuity, we shift the sample timestamps so that they will
|
||||
// continue from the current downstream position.
|
||||
pendingNextSampleUs = downstreamPositionUs;
|
||||
havePendingNextSampleUs = true;
|
||||
} else {
|
||||
// We're playing a seekable on-demand stream. Resume the current loadable, which will
|
||||
// request data starting from the point it left off.
|
||||
@ -706,11 +690,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
ExtractorSampleSource.this.maybeThrowError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
return ExtractorSampleSource.this.readReset(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
return ExtractorSampleSource.this.readData(track, formatHolder, buffer);
|
||||
|
@ -39,7 +39,6 @@ import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -69,6 +68,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
|
||||
private boolean prepared;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private boolean notifyReset;
|
||||
private int enabledTrackCount;
|
||||
private DefaultTrackOutput[] sampleQueues;
|
||||
private Format downstreamFormat;
|
||||
@ -79,7 +79,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
private int primaryTrackGroupIndex;
|
||||
// Indexed by group.
|
||||
private boolean[] groupEnabledStates;
|
||||
private boolean[] pendingResets;
|
||||
private Format[] downstreamSampleFormats;
|
||||
|
||||
private long downstreamPositionUs;
|
||||
@ -204,7 +203,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
int[] tracks = selection.getTracks();
|
||||
setTrackGroupEnabledState(group, true);
|
||||
downstreamSampleFormats[group] = null;
|
||||
pendingResets[group] = false;
|
||||
if (group == primaryTrackGroupIndex) {
|
||||
primaryTracksDeselected |= chunkSource.selectTracks(tracks);
|
||||
}
|
||||
@ -245,6 +243,15 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
if (notifyReset) {
|
||||
notifyReset = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
if (loadingFinished) {
|
||||
@ -293,16 +300,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
|
||||
/* package */ long readReset(int group) {
|
||||
if (pendingResets[group]) {
|
||||
pendingResets[group] = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return C.UNSET_TIME_US;
|
||||
}
|
||||
|
||||
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
if (pendingResets[group] || isPendingReset()) {
|
||||
if (notifyReset || isPendingReset()) {
|
||||
return TrackStream.NOTHING_READ;
|
||||
}
|
||||
|
||||
@ -464,7 +463,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
// Instantiate the necessary internal data-structures.
|
||||
primaryTrackGroupIndex = -1;
|
||||
groupEnabledStates = new boolean[extractorTrackCount];
|
||||
pendingResets = new boolean[extractorTrackCount];
|
||||
downstreamSampleFormats = new Format[extractorTrackCount];
|
||||
|
||||
// Construct the set of exposed track groups.
|
||||
@ -531,7 +529,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
positionUs = chunkSource.isLive() ? 0 : positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
Arrays.fill(pendingResets, true);
|
||||
notifyReset = true;
|
||||
boolean seekInsideBuffer = !isPendingReset();
|
||||
// TODO[REFACTOR]: This will nearly always fail to seek inside all buffers due to sparse tracks
|
||||
// such as ID3 (probably EIA608 too). We need a way to not care if we can't seek to the keyframe
|
||||
@ -670,11 +668,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
HlsSampleSource.this.maybeThrowError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readReset() {
|
||||
return HlsSampleSource.this.readReset(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
|
||||
return HlsSampleSource.this.readData(group, formatHolder, buffer);
|
||||
|
Loading…
x
Reference in New Issue
Block a user