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:
olly 2016-04-22 03:06:30 -07:00 committed by Oliver Woodman
parent 2b13165738
commit 90b7081824
8 changed files with 81 additions and 114 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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>

View File

@ -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.
*

View File

@ -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

View File

@ -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);

View File

@ -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);