Defer retries for progressive live audio streams

Issue: #1606

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183058160
This commit is contained in:
olly 2018-01-24 02:20:59 -08:00 committed by Oliver Woodman
parent 5dff21e5de
commit a1274591b1
2 changed files with 61 additions and 11 deletions

View File

@ -38,6 +38,8 @@
sub-streams, by allowing injection of custom `CompositeSequenceableLoader` sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`, factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`,
`SsMediaSource.Factory`, and `MergingMediaSource`. `SsMediaSource.Factory`, and `MergingMediaSource`.
* Play out existing buffer before retrying for progressive live streams
([#1606](https://github.com/google/ExoPlayer/issues/1606)).
* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are * Add `ExoPlayer.setSeekParameters` for controlling how seek operations are
performed. The `SeekParameters` class contains defaults for exact seeking and performed. The `SeekParameters` class contains defaults for exact seeking and
seeking to the closest sync points before, either side or after specified seek seeking to the closest sync points before, either side or after specified seek

View File

@ -111,6 +111,7 @@ import java.util.Arrays;
private long lastSeekPositionUs; private long lastSeekPositionUs;
private long pendingResetPositionUs; private long pendingResetPositionUs;
private boolean pendingDeferredRetry;
private int extractedSamplesCountAtStartOfLoad; private int extractedSamplesCountAtStartOfLoad;
private boolean loadingFinished; private boolean loadingFinished;
@ -259,6 +260,7 @@ import java.util.Arrays;
} }
} }
if (enabledTrackCount == 0) { if (enabledTrackCount == 0) {
pendingDeferredRetry = false;
notifyDiscontinuity = false; notifyDiscontinuity = false;
if (loader.isLoading()) { if (loader.isLoading()) {
// Discard as much as we can synchronously. // Discard as much as we can synchronously.
@ -299,7 +301,7 @@ import java.util.Arrays;
@Override @Override
public boolean continueLoading(long playbackPositionUs) { public boolean continueLoading(long playbackPositionUs) {
if (loadingFinished || (prepared && enabledTrackCount == 0)) { if (loadingFinished || pendingDeferredRetry || (prepared && enabledTrackCount == 0)) {
return false; return false;
} }
boolean continuedLoading = loadCondition.open(); boolean continuedLoading = loadCondition.open();
@ -361,6 +363,7 @@ import java.util.Arrays;
return positionUs; return positionUs;
} }
// We were unable to seek within the buffer, so need to reset. // We were unable to seek within the buffer, so need to reset.
pendingDeferredRetry = false;
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
loadingFinished = false; loadingFinished = false;
if (loader.isLoading()) { if (loader.isLoading()) {
@ -404,6 +407,8 @@ import java.util.Arrays;
formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
maybeNotifyTrackFormat(track); maybeNotifyTrackFormat(track);
} else if (result == C.RESULT_NOTHING_READ) {
maybeStartDeferredRetry(track);
} }
return result; return result;
} }
@ -424,6 +429,8 @@ import java.util.Arrays;
} }
if (skipCount > 0) { if (skipCount > 0) {
maybeNotifyTrackFormat(track); maybeNotifyTrackFormat(track);
} else {
maybeStartDeferredRetry(track);
} }
return skipCount; return skipCount;
} }
@ -441,6 +448,23 @@ import java.util.Arrays;
} }
} }
private void maybeStartDeferredRetry(int track) {
if (!pendingDeferredRetry
|| !trackIsAudioVideoFlags[track]
|| sampleQueues[track].hasNextSample()) {
return;
}
pendingResetPositionUs = 0;
pendingDeferredRetry = false;
notifyDiscontinuity = true;
lastSeekPositionUs = 0;
extractedSamplesCountAtStartOfLoad = 0;
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset();
}
callback.onContinueLoadingRequested(this);
}
private boolean suppressRead() { private boolean suppressRead() {
return notifyDiscontinuity || isPendingReset(); return notifyDiscontinuity || isPendingReset();
} }
@ -523,9 +547,9 @@ import java.util.Arrays;
} }
int extractedSamplesCount = getExtractedSamplesCount(); int extractedSamplesCount = getExtractedSamplesCount();
boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad; boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad;
configureRetry(loadable); // May reset the sample queues. return configureRetry(loadable, extractedSamplesCount)
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); ? (madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY)
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; : Loader.DONT_RETRY;
} }
// ExtractorOutput implementation. Called by the loading thread. // ExtractorOutput implementation. Called by the loading thread.
@ -636,23 +660,47 @@ import java.util.Arrays;
elapsedRealtimeMs); elapsedRealtimeMs);
} }
private void configureRetry(ExtractingLoadable loadable) { /**
* Called to configure a retry when a load error occurs.
*
* @param loadable The current loadable for which the error was encountered.
* @param currentExtractedSampleCount The current number of samples that have been extracted into
* the sample queues.
* @return Whether the loader should retry with the current loadable. False indicates a deferred
* retry.
*/
private boolean configureRetry(ExtractingLoadable loadable, int currentExtractedSampleCount) {
if (length != C.LENGTH_UNSET if (length != C.LENGTH_UNSET
|| (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) { || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) {
// We're playing an on-demand stream. Resume the current loadable, which will // We're playing an on-demand stream. Resume the current loadable, which will
// request data starting from the point it left off. // request data starting from the point it left off.
extractedSamplesCountAtStartOfLoad = currentExtractedSampleCount;
return true;
} else if (prepared && !suppressRead()) {
// We're playing a stream of unknown length and duration. Assume it's live, and 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. Deferring the retry until
// we run out of buffered data makes for a much better user experience. See:
// https://github.com/google/ExoPlayer/issues/1606.
// Note that the suppressRead() check means only a single deferred retry can occur without
// progress being made. Any subsequent failures without progress will go through the else
// block below.
pendingDeferredRetry = true;
return false;
} else { } else {
// We're playing a stream of unknown length and duration. Assume it's live, and // This is the same case as above, except in this case there's no value in deferring the retry
// therefore that the data at the uri is a continuously shifting window of the latest // because there's no buffered data to be read. This case also covers an on-demand stream with
// available media. For this case there's no way to continue loading from where a // unknown length that has yet to be prepared. This case cannot be disambiguated from the live
// previous load finished, so it's necessary to load from the start whenever commencing // stream case, so we have no option but to load from the start.
// a new load.
lastSeekPositionUs = 0;
notifyDiscontinuity = prepared; notifyDiscontinuity = prepared;
lastSeekPositionUs = 0;
extractedSamplesCountAtStartOfLoad = 0;
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset(); sampleQueue.reset();
} }
loadable.setLoadPosition(0, 0); loadable.setLoadPosition(0, 0);
return true;
} }
} }