mirror of
https://github.com/samsonjs/media.git
synced 2026-04-26 14:57:47 +00:00
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
This commit is contained in:
parent
d8e6b096c4
commit
3187bd0829
6 changed files with 75 additions and 119 deletions
|
|
@ -226,7 +226,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
public void continueBuffering(long positionUs) {
|
||||
downstreamPositionUs = positionUs;
|
||||
chunkSource.continueBuffering(positionUs);
|
||||
updateLoadControl();
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -360,7 +360,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
|
||||
}
|
||||
clearCurrentLoadable();
|
||||
updateLoadControl();
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -380,12 +380,29 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
|
||||
@Override
|
||||
public void onLoadError(Loadable loadable, IOException e) {
|
||||
currentLoadableException = e;
|
||||
currentLoadableExceptionCount++;
|
||||
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
|
||||
notifyLoadError(e);
|
||||
chunkSource.onChunkLoadError(currentLoadableHolder.chunk, e);
|
||||
updateLoadControl();
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
long bytesLoaded = currentLoadable.bytesLoaded();
|
||||
boolean isMediaChunk = isMediaChunk(currentLoadable);
|
||||
boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
|
||||
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
|
||||
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
|
|||
sampleQueue.clear();
|
||||
mediaChunks.clear();
|
||||
clearCurrentLoadable();
|
||||
updateLoadControl();
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -422,7 +439,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
currentLoadableExceptionCount = 0;
|
||||
}
|
||||
|
||||
private void updateLoadControl() {
|
||||
private void maybeStartLoading() {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
long nextLoadPositionUs = getNextLoadPositionUs();
|
||||
boolean isBackedOff = currentLoadableException != null;
|
||||
|
|
@ -433,9 +450,14 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|
||||
|| (now - lastPerformedBufferOperation > 2000))) {
|
||||
// Perform the evaluation.
|
||||
lastPerformedBufferOperation = now;
|
||||
doChunkOperation();
|
||||
currentLoadableHolder.endOfStream = false;
|
||||
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);
|
||||
lastPerformedBufferOperation = now;
|
||||
// Update the next load position as appropriate.
|
||||
if (currentLoadableHolder.chunk == null) {
|
||||
// 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
|
|||
if (isBackedOff) {
|
||||
long elapsedMillis = now - currentLoadableExceptionTimestamp;
|
||||
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
|
||||
resumeFromBackOff();
|
||||
currentLoadableException = null;
|
||||
loader.startLoading(currentLoadableHolder.chunk, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -475,97 +517,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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -127,9 +127,11 @@ public interface ChunkSource {
|
|||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param chunk The chunk whose load encountered the error.
|
||||
* @param cancelable Whether the load can be canceled.
|
||||
* @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.
|
||||
|
|
|
|||
|
|
@ -480,8 +480,9 @@ public class DashChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoadError(Chunk chunk, Exception e) {
|
||||
// Do nothing.
|
||||
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
|
||||
// TODO: Consider implementing representation blacklisting.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -511,14 +511,12 @@ public class HlsChunkSource {
|
|||
* this source.
|
||||
*
|
||||
* @param chunk The chunk whose load encountered the error.
|
||||
* @param cancelable Whether the load can be canceled.
|
||||
* @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) {
|
||||
if (chunk.bytesLoaded() == 0
|
||||
&& (chunk instanceof TsChunk || chunk instanceof MediaPlaylistChunk
|
||||
|| chunk instanceof EncryptionKeyChunk)
|
||||
&& (e instanceof InvalidResponseCodeException)) {
|
||||
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException e) {
|
||||
if (cancelable && e instanceof InvalidResponseCodeException) {
|
||||
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
|
||||
int responseCode = responseCodeException.responseCode;
|
||||
if (responseCode == 404 || responseCode == 410) {
|
||||
|
|
|
|||
|
|
@ -417,18 +417,21 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
|
||||
@Override
|
||||
public void onLoadError(Loadable loadable, IOException e) {
|
||||
if (chunkSource.onChunkLoadError(currentLoadable, e)) {
|
||||
// Error handled by source.
|
||||
long bytesLoaded = currentLoadable.bytesLoaded();
|
||||
boolean cancelable = !isTsChunk(currentLoadable) || bytesLoaded == 0;
|
||||
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
|
||||
if (previousTsLoadable == null && !isPendingReset()) {
|
||||
pendingResetPositionUs = lastSeekPositionUs;
|
||||
}
|
||||
clearCurrentLoadable();
|
||||
notifyLoadError(e);
|
||||
notifyLoadCanceled(bytesLoaded);
|
||||
} else {
|
||||
currentLoadableException = e;
|
||||
currentLoadableExceptionCount++;
|
||||
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
|
||||
notifyLoadError(e);
|
||||
}
|
||||
notifyLoadError(e);
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -310,8 +310,9 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoadError(Chunk chunk, Exception e) {
|
||||
// Do nothing.
|
||||
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
|
||||
// TODO: Consider implementing stream element blacklisting.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Reference in a new issue