From 45c7fe9b00e0cc9068c245ba001dd67c420f12ab Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 8 Mar 2017 04:37:47 -0800 Subject: [PATCH] Drain embedded track sample queues when not enabled. I think it's likely we'll revert back to discarding media in sync with the playback position for ExtractorMediaSource and HlsMediaSource too, where the tracks are muxed with ones we're requesting anyway. Note: discardBuffer is named as it is because it'll also be used to discard for enabled tracks soon, as a result of the remaining TODO in ChunkSampleStream. For enabled tracks the discard will also be conditional on the samples having been consumed, obviously. Issue: #2362 Issue: #2176 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=149525857 --- .../android/exoplayer2/ExoPlayerTest.java | 5 + .../exoplayer2/ExoPlayerImplInternal.java | 2 + .../source/ClippingMediaPeriod.java | 5 + .../source/ExtractorMediaPeriod.java | 5 + .../exoplayer2/source/MediaPeriod.java | 7 ++ .../exoplayer2/source/MergingMediaPeriod.java | 7 ++ .../source/SingleSampleMediaPeriod.java | 5 + .../source/chunk/ChunkSampleStream.java | 104 ++++++++++-------- .../source/dash/DashMediaPeriod.java | 49 ++++++--- .../exoplayer2/source/hls/HlsMediaPeriod.java | 5 + .../source/smoothstreaming/SsMediaPeriod.java | 5 + 11 files changed, 136 insertions(+), 63 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 2ad1159c3e..93c0a7dc11 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -460,6 +460,11 @@ public final class ExoPlayerTest extends TestCase { return 0; } + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + @Override public long readDiscontinuity() { assertTrue(preparedPeriod); diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index faf86087c9..e4c109e85b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -455,6 +455,8 @@ import java.io.IOException; TraceUtil.beginSection("doSomeWork"); updatePlaybackPositions(); + playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs); + boolean allRenderersEnded = true; boolean allRenderersReadyOrEnded = true; for (Renderer renderer : enabledRenderers) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 51663a21c6..102a689742 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -109,6 +109,11 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb return enablePositionUs - startUs; } + @Override + public void discardBuffer(long positionUs) { + mediaPeriod.discardBuffer(positionUs + startUs); + } + @Override public long readDiscontinuity() { if (pendingInitialDiscontinuity) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 97e9ddd7e7..31b76a84b3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -234,6 +234,11 @@ import java.io.IOException; return positionUs; } + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + @Override public boolean continueLoading(long playbackPositionUs) { if (loadingFinished || (prepared && enabledTrackCount == 0)) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java index 31ee8df1e4..3b06542855 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java @@ -104,6 +104,13 @@ public interface MediaPeriod extends SequenceableLoader { long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs); + /** + * Discards buffered media up to the specified position. + * + * @param positionUs The position in microseconds. + */ + void discardBuffer(long positionUs); + /** * Attempts to read a discontinuity. *

diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 10c56e5576..077b5576c1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -128,6 +128,13 @@ import java.util.IdentityHashMap; return positionUs; } + @Override + public void discardBuffer(long positionUs) { + for (MediaPeriod period : enabledPeriods) { + period.discardBuffer(positionUs); + } + } + @Override public boolean continueLoading(long positionUs) { return sequenceableLoader.continueLoading(positionUs); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index fd2ebffe8e..5b717e51da 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -111,6 +111,11 @@ import java.util.Arrays; return positionUs; } + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + @Override public boolean continueLoading(long positionUs) { if (loadingFinished || loader.isLoading()) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 1ae928045d..93d86a8de1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -40,6 +40,7 @@ public class ChunkSampleStream implements SampleStream, S private final int primaryTrackType; private final int[] embeddedTrackTypes; + private final boolean[] embeddedTracksSelected; private final T chunkSource; private final SequenceableLoader.Callback> callback; private final EventDispatcher eventDispatcher; @@ -49,7 +50,7 @@ public class ChunkSampleStream implements SampleStream, S private final LinkedList mediaChunks; private final List readOnlyMediaChunks; private final DefaultTrackOutput primarySampleQueue; - private final EmbeddedSampleStream[] embeddedSampleStreams; + private final DefaultTrackOutput[] embeddedSampleQueues; private final BaseMediaChunkOutput mediaChunkOutput; private Format primaryDownstreamTrackFormat; @@ -84,7 +85,8 @@ public class ChunkSampleStream implements SampleStream, S readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length; - embeddedSampleStreams = newEmbeddedSampleStreamArray(embeddedTrackCount); + embeddedSampleQueues = new DefaultTrackOutput[embeddedTrackCount]; + embeddedTracksSelected = new boolean[embeddedTrackCount]; int[] trackTypes = new int[1 + embeddedTrackCount]; DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount]; @@ -93,9 +95,10 @@ public class ChunkSampleStream implements SampleStream, S sampleQueues[0] = primarySampleQueue; for (int i = 0; i < embeddedTrackCount; i++) { + DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); + embeddedSampleQueues[i] = trackOutput; + sampleQueues[i + 1] = trackOutput; trackTypes[i + 1] = embeddedTrackTypes[i]; - sampleQueues[i + 1] = new DefaultTrackOutput(allocator); - embeddedSampleStreams[i] = new EmbeddedSampleStream(sampleQueues[i + 1]); } mediaChunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues); @@ -104,26 +107,36 @@ public class ChunkSampleStream implements SampleStream, S } /** - * Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}. + * Discards buffered media for embedded tracks that are not currently selected, up to the + * specified position. + * + * @param positionUs The position to discard up to, in microseconds. */ - public static boolean isPrimarySampleStream(SampleStream sampleStream) { - return sampleStream instanceof ChunkSampleStream; + public void discardUnselectedEmbeddedTracksTo(long positionUs) { + for (int i = 0; i < embeddedSampleQueues.length; i++) { + if (!embeddedTracksSelected[i]) { + embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); + } + } } /** - * Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}. + * Selects the embedded track, returning a new {@link EmbeddedSampleStream} from which the track's + * samples can be consumed. {@link EmbeddedSampleStream#release()} must be called on the returned + * stream when the track is no longer required, and before calling this method again to obtain + * another stream for the same track. + * + * @param positionUs The current playback position in microseconds. + * @param trackType The type of the embedded track to enable. + * @return The {@link EmbeddedSampleStream} for the embedded track. */ - public static boolean isEmbeddedSampleStream(SampleStream sampleStream) { - return sampleStream instanceof ChunkSampleStream.EmbeddedSampleStream; - } - - /** - * Returns the {@link SampleStream} for the embedded track with the specified type. - */ - public SampleStream getEmbeddedSampleStream(int trackType) { - for (int i = 0; i < embeddedTrackTypes.length; i++) { + public EmbeddedSampleStream selectEmbeddedTrack(long positionUs, int trackType) { + for (int i = 0; i < embeddedSampleQueues.length; i++) { if (embeddedTrackTypes[i] == trackType) { - return embeddedSampleStreams[i]; + Assertions.checkState(!embeddedTracksSelected[i]); + embeddedTracksSelected[i] = true; + embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); + return new EmbeddedSampleStream(this, embeddedSampleQueues[i], i); } } // Should never happen. @@ -179,8 +192,8 @@ public class ChunkSampleStream implements SampleStream, S } // TODO: For this to work correctly, the embedded streams must not discard anything from their // sample queues beyond the current read position of the primary stream. - for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { - embeddedSampleStream.skipToKeyframeBefore(positionUs); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.skipToKeyframeBefore(positionUs); } } else { // We failed, and need to restart. @@ -191,8 +204,8 @@ public class ChunkSampleStream implements SampleStream, S loader.cancelLoading(); } else { primarySampleQueue.reset(true); - for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { - embeddedSampleStream.reset(true); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(true); } } } @@ -205,8 +218,8 @@ public class ChunkSampleStream implements SampleStream, S */ public void release() { primarySampleQueue.disable(); - for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { - embeddedSampleStream.disable(); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.disable(); } loader.release(); } @@ -232,7 +245,6 @@ public class ChunkSampleStream implements SampleStream, S if (isPendingReset()) { return C.RESULT_NOTHING_READ; } - // TODO: For embedded streams that aren't being used, we need to drain their queues here. discardDownstreamMediaChunks(primarySampleQueue.getReadIndex()); return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); @@ -264,8 +276,8 @@ public class ChunkSampleStream implements SampleStream, S loadable.bytesLoaded()); if (!released) { primarySampleQueue.reset(true); - for (EmbeddedSampleStream embeddedStream : embeddedSampleStreams) { - embeddedStream.reset(true); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(true); } callback.onContinueLoadingRequested(this); } @@ -284,8 +296,8 @@ public class ChunkSampleStream implements SampleStream, S BaseMediaChunk removed = mediaChunks.removeLast(); Assertions.checkState(removed == loadable); primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); - for (int i = 0; i < embeddedSampleStreams.length; i++) { - embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + for (int i = 0; i < embeddedSampleQueues.length; i++) { + embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); } if (mediaChunks.isEmpty()) { pendingResetPositionUs = lastSeekPositionUs; @@ -406,25 +418,28 @@ public class ChunkSampleStream implements SampleStream, S loadingFinished = false; } primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); - for (int i = 0; i < embeddedSampleStreams.length; i++) { - embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + for (int i = 0; i < embeddedSampleQueues.length; i++) { + embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); } eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs); return true; } - @SuppressWarnings("unchecked") - private static ChunkSampleStream.EmbeddedSampleStream[] - newEmbeddedSampleStreamArray(int length) { - return new ChunkSampleStream.EmbeddedSampleStream[length]; - } + /** + * A {@link SampleStream} embedded in a {@link ChunkSampleStream}. + */ + public final class EmbeddedSampleStream implements SampleStream { - private final class EmbeddedSampleStream implements SampleStream { + public final ChunkSampleStream parent; private final DefaultTrackOutput sampleQueue; + private final int index; - public EmbeddedSampleStream(DefaultTrackOutput sampleQueue) { + public EmbeddedSampleStream(ChunkSampleStream parent, DefaultTrackOutput sampleQueue, + int index) { + this.parent = parent; this.sampleQueue = sampleQueue; + this.index = index; } @Override @@ -452,16 +467,9 @@ public class ChunkSampleStream implements SampleStream, S lastSeekPositionUs); } - public void reset(boolean enable) { - sampleQueue.reset(enable); - } - - public void disable() { - sampleQueue.disable(); - } - - public void discardUpstreamSamples(int discardFromIndex) { - sampleQueue.discardUpstreamSamples(discardFromIndex); + public void release() { + Assertions.checkState(embeddedTracksSelected[index]); + embeddedTracksSelected[index] = false; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index 8905607bc1..5e0541cb31 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; +import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.Representation; @@ -125,7 +126,7 @@ import java.util.List; HashMap> primarySampleStreams = new HashMap<>(); // First pass for primary tracks. for (int i = 0; i < selections.length; i++) { - if (ChunkSampleStream.isPrimarySampleStream(streams[i])) { + if (streams[i] instanceof ChunkSampleStream) { @SuppressWarnings("unchecked") ChunkSampleStream stream = (ChunkSampleStream) streams[i]; if (selections[i] == null || !mayRetainStreamFlags[i]) { @@ -149,26 +150,31 @@ import java.util.List; } // Second pass for embedded tracks. for (int i = 0; i < selections.length; i++) { - if (ChunkSampleStream.isEmbeddedSampleStream(streams[i])) { - // Always clear even if the selection is unchanged, since the parent primary sample stream - // may have been replaced. + if ((streams[i] instanceof EmbeddedSampleStream || streams[i] instanceof EmptySampleStream) + && (selections[i] == null || !mayRetainStreamFlags[i])) { + // The stream is for an embedded track and is either no longer selected or needs replacing. + releaseIfEmbeddedSampleStream(streams[i]); streams[i] = null; } - if (streams[i] == null && selections[i] != null) { + // We need to consider replacing the stream even if it's non-null because the primary stream + // may have been replaced, selected or deselected. + if (selections[i] != null) { int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); if (trackGroupIndex >= adaptationSetCount) { - EmbeddedTrackInfo embeddedTrackInfo = - embeddedTrackInfos[trackGroupIndex - adaptationSetCount]; + int embeddedTrackIndex = trackGroupIndex - adaptationSetCount; + EmbeddedTrackInfo embeddedTrackInfo = embeddedTrackInfos[embeddedTrackIndex]; int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex; - ChunkSampleStream primarySampleStream = - primarySampleStreams.get(adaptationSetIndex); - if (primarySampleStream != null) { - streams[i] = primarySampleStream.getEmbeddedSampleStream(embeddedTrackInfo.trackType); - } else { - // The primary track in which this one is embedded is not selected. - streams[i] = new EmptySampleStream(); + ChunkSampleStream primaryStream = primarySampleStreams.get(adaptationSetIndex); + SampleStream stream = streams[i]; + boolean mayRetainStream = primaryStream == null ? stream instanceof EmptySampleStream + : (stream instanceof EmbeddedSampleStream + && ((EmbeddedSampleStream) stream).parent == primaryStream); + if (!mayRetainStream) { + releaseIfEmbeddedSampleStream(stream); + streams[i] = primaryStream == null ? new EmptySampleStream() + : primaryStream.selectEmbeddedTrack(positionUs, embeddedTrackInfo.trackType); + streamResetFlags[i] = true; } - streamResetFlags[i] = true; } } } @@ -178,6 +184,13 @@ import java.util.List; return positionUs; } + @Override + public void discardBuffer(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.discardUnselectedEmbeddedTracksTo(positionUs); + } + } + @Override public boolean continueLoading(long positionUs) { return sequenceableLoader.continueLoading(positionUs); @@ -321,6 +334,12 @@ import java.util.List; return new ChunkSampleStream[length]; } + private static void releaseIfEmbeddedSampleStream(SampleStream sampleStream) { + if (sampleStream instanceof EmbeddedSampleStream) { + ((EmbeddedSampleStream) sampleStream).release(); + } + } + private static final class EmbeddedTrackInfo { public final int adaptationSetIndex; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 0ae8becfc0..23ccccbb6b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -189,6 +189,11 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper return positionUs; } + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + @Override public boolean continueLoading(long positionUs) { return sequenceableLoader.continueLoading(positionUs); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index b9af9930dc..43cd4a9f8d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -136,6 +136,11 @@ import java.util.ArrayList; return positionUs; } + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + @Override public boolean continueLoading(long positionUs) { return sequenceableLoader.continueLoading(positionUs);