Commit 3187bd08 by olly Committed by Oliver Woodman

Align DASH/SS/HLS chunk replacement mechanisms.

DASH + SS previously had a ridiculously complicated chunk
replacement mechanism in resumeFromBackOff. It also didn't
allow replacement of the first media chunk in the queue,
even though it's possible to remove it in the case that no
corresponding samples have been consumed.

This CL moves DASH + SS to the simpler model used in the
HLS implementation, where the chunk source has a single
opportunity to cancel (and hence later replace) the chunk
when the load error occurs. With this change comes the
ability to replace the first media chunk in the queue in
all cases where it's possible to do so.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=118573418
parent d8e6b096
...@@ -226,7 +226,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -226,7 +226,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
public void continueBuffering(long positionUs) { public void continueBuffering(long positionUs) {
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
chunkSource.continueBuffering(positionUs); chunkSource.continueBuffering(positionUs);
updateLoadControl(); maybeStartLoading();
} }
@Override @Override
...@@ -360,7 +360,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -360,7 +360,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs); currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
} }
clearCurrentLoadable(); clearCurrentLoadable();
updateLoadControl(); maybeStartLoading();
} }
@Override @Override
...@@ -380,12 +380,29 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -380,12 +380,29 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
@Override @Override
public void onLoadError(Loadable loadable, IOException e) { public void onLoadError(Loadable loadable, IOException e) {
currentLoadableException = e; Chunk currentLoadable = currentLoadableHolder.chunk;
currentLoadableExceptionCount++; long bytesLoaded = currentLoadable.bytesLoaded();
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); boolean isMediaChunk = isMediaChunk(currentLoadable);
notifyLoadError(e); boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
chunkSource.onChunkLoadError(currentLoadableHolder.chunk, e); if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
updateLoadControl(); if (isMediaChunk) {
BaseMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == currentLoadable);
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs;
}
}
clearCurrentLoadable();
notifyLoadError(e);
notifyLoadCanceled(bytesLoaded);
} else {
currentLoadableException = e;
currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
notifyLoadError(e);
}
maybeStartLoading();
} }
/** /**
...@@ -408,7 +425,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -408,7 +425,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
sampleQueue.clear(); sampleQueue.clear();
mediaChunks.clear(); mediaChunks.clear();
clearCurrentLoadable(); clearCurrentLoadable();
updateLoadControl(); maybeStartLoading();
} }
} }
...@@ -422,7 +439,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -422,7 +439,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
currentLoadableExceptionCount = 0; currentLoadableExceptionCount = 0;
} }
private void updateLoadControl() { private void maybeStartLoading() {
long now = SystemClock.elapsedRealtime(); long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs(); long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null; boolean isBackedOff = currentLoadableException != null;
...@@ -433,9 +450,14 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -433,9 +450,14 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1) if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|| (now - lastPerformedBufferOperation > 2000))) { || (now - lastPerformedBufferOperation > 2000))) {
// Perform the evaluation. // Perform the evaluation.
lastPerformedBufferOperation = now; currentLoadableHolder.endOfStream = false;
doChunkOperation(); currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
long playbackPositionUs = pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs
: downstreamPositionUs;
chunkSource.getChunkOperation(readOnlyMediaChunks, playbackPositionUs, currentLoadableHolder);
loadingFinished = currentLoadableHolder.endOfStream;
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize); boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
lastPerformedBufferOperation = now;
// Update the next load position as appropriate. // Update the next load position as appropriate.
if (currentLoadableHolder.chunk == null) { if (currentLoadableHolder.chunk == null) {
// Set loadPosition to -1 to indicate that we don't have anything to load. // Set loadPosition to -1 to indicate that we don't have anything to load.
...@@ -453,13 +475,33 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -453,13 +475,33 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
if (isBackedOff) { if (isBackedOff) {
long elapsedMillis = now - currentLoadableExceptionTimestamp; long elapsedMillis = now - currentLoadableExceptionTimestamp;
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
resumeFromBackOff(); currentLoadableException = null;
loader.startLoading(currentLoadableHolder.chunk, this);
} }
return; return;
} }
if (!loader.isLoading() && nextLoader) { if (!loader.isLoading() && nextLoader) {
maybeStartLoading(); Chunk currentLoadable = currentLoadableHolder.chunk;
if (currentLoadable == null) {
// Nothing to load.
return;
}
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
if (isMediaChunk(currentLoadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
if (isPendingReset()) {
pendingResetPositionUs = NO_RESET_PENDING;
}
notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
} }
} }
...@@ -476,97 +518,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -476,97 +518,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
} }
/** /**
* Resumes loading.
* <p>
* If the {@link ChunkSource} returns a chunk equivalent to the backed off chunk B, then the
* loading of B will be resumed. In all other cases B will be discarded and the new chunk will
* be loaded.
*/
private void resumeFromBackOff() {
currentLoadableException = null;
Chunk backedOffChunk = currentLoadableHolder.chunk;
if (!isMediaChunk(backedOffChunk)) {
doChunkOperation();
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading.
loader.startLoading(backedOffChunk, this);
} else {
// Chunk was changed. Notify that the existing load was canceled.
notifyLoadCanceled(backedOffChunk.bytesLoaded());
// Start loading the replacement.
maybeStartLoading();
}
return;
}
if (backedOffChunk == mediaChunks.getFirst()) {
// We're not able to clear the first media chunk, so we have no choice but to continue
// loading it.
loader.startLoading(backedOffChunk, this);
return;
}
// The current loadable is the last media chunk. Remove it before we invoke the chunk source,
// and add it back again afterwards.
BaseMediaChunk removedChunk = mediaChunks.removeLast();
Assertions.checkState(backedOffChunk == removedChunk);
doChunkOperation();
mediaChunks.add(removedChunk);
if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading.
loader.startLoading(backedOffChunk, this);
} else {
// Chunk was changed. Notify that the existing load was canceled.
notifyLoadCanceled(backedOffChunk.bytesLoaded());
// This call will remove and release at least one chunk from the end of mediaChunks. Since
// the current loadable is the last media chunk, it is guaranteed to be removed.
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
clearCurrentLoadableException();
maybeStartLoading();
}
}
private void maybeStartLoading() {
Chunk currentLoadable = currentLoadableHolder.chunk;
if (currentLoadable == null) {
// Nothing to load.
return;
}
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
if (isMediaChunk(currentLoadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
if (isPendingReset()) {
pendingResetPositionUs = NO_RESET_PENDING;
}
notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
}
/**
* Sets up the {@link #currentLoadableHolder}, passes it to the chunk source to cause it to be
* updated with the next operation, and updates {@link #loadingFinished} if the end of the stream
* is reached.
*/
private void doChunkOperation() {
currentLoadableHolder.endOfStream = false;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks,
pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs,
currentLoadableHolder);
loadingFinished = currentLoadableHolder.endOfStream;
}
/**
* Discard upstream media chunks until the queue length is equal to the length specified. * Discard upstream media chunks until the queue length is equal to the length specified.
* *
* @param queueLength The desired length of the queue. * @param queueLength The desired length of the queue.
......
...@@ -127,9 +127,11 @@ public interface ChunkSource { ...@@ -127,9 +127,11 @@ public interface ChunkSource {
* This method should only be called when the source is enabled. * This method should only be called when the source is enabled.
* *
* @param chunk The chunk whose load encountered the error. * @param chunk The chunk whose load encountered the error.
* @param cancelable Whether the load can be canceled.
* @param e The error. * @param e The error.
* @return True if the load should be canceled. False otherwise.
*/ */
void onChunkLoadError(Chunk chunk, Exception e); boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e);
/** /**
* Disables the source. * Disables the source.
......
...@@ -480,8 +480,9 @@ public class DashChunkSource implements ChunkSource { ...@@ -480,8 +480,9 @@ public class DashChunkSource implements ChunkSource {
} }
@Override @Override
public void onChunkLoadError(Chunk chunk, Exception e) { public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
// Do nothing. // TODO: Consider implementing representation blacklisting.
return false;
} }
@Override @Override
......
...@@ -511,14 +511,12 @@ public class HlsChunkSource { ...@@ -511,14 +511,12 @@ public class HlsChunkSource {
* this source. * this source.
* *
* @param chunk The chunk whose load encountered the error. * @param chunk The chunk whose load encountered the error.
* @param cancelable Whether the load can be canceled.
* @param e The error. * @param e The error.
* @return True if the error was handled by the source. False otherwise. * @return True if the load should be canceled. False otherwise.
*/ */
public boolean onChunkLoadError(Chunk chunk, IOException e) { public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException e) {
if (chunk.bytesLoaded() == 0 if (cancelable && e instanceof InvalidResponseCodeException) {
&& (chunk instanceof TsChunk || chunk instanceof MediaPlaylistChunk
|| chunk instanceof EncryptionKeyChunk)
&& (e instanceof InvalidResponseCodeException)) {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode; int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) { if (responseCode == 404 || responseCode == 410) {
......
...@@ -417,18 +417,21 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -417,18 +417,21 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
@Override @Override
public void onLoadError(Loadable loadable, IOException e) { public void onLoadError(Loadable loadable, IOException e) {
if (chunkSource.onChunkLoadError(currentLoadable, e)) { long bytesLoaded = currentLoadable.bytesLoaded();
// Error handled by source. boolean cancelable = !isTsChunk(currentLoadable) || bytesLoaded == 0;
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
if (previousTsLoadable == null && !isPendingReset()) { if (previousTsLoadable == null && !isPendingReset()) {
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} }
clearCurrentLoadable(); clearCurrentLoadable();
notifyLoadError(e);
notifyLoadCanceled(bytesLoaded);
} else { } else {
currentLoadableException = e; currentLoadableException = e;
currentLoadableExceptionCount++; currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
notifyLoadError(e);
} }
notifyLoadError(e);
maybeStartLoading(); maybeStartLoading();
} }
......
...@@ -310,8 +310,9 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -310,8 +310,9 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
@Override @Override
public void onChunkLoadError(Chunk chunk, Exception e) { public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
// Do nothing. // TODO: Consider implementing stream element blacklisting.
return false;
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment