diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 98ba5839fc..da038e71da 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -29,7 +29,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer; import com.google.android.exoplayer.demo.ui.TrackSelectionHelper; import com.google.android.exoplayer.drm.UnsupportedDrmException; import com.google.android.exoplayer.extractor.ExtractorSampleSource; -import com.google.android.exoplayer.hls.HlsSource; +import com.google.android.exoplayer.hls.HlsSampleSource2; import com.google.android.exoplayer.metadata.id3.GeobFrame; import com.google.android.exoplayer.metadata.id3.Id3Frame; import com.google.android.exoplayer.metadata.id3.PrivFrame; @@ -278,7 +278,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), player.getMainHandler(), player); case Util.TYPE_HLS: - return new HlsSource(uri, dataSourceFactory, player.getBandwidthMeter(), + return new HlsSampleSource2(uri, dataSourceFactory, player.getBandwidthMeter(), player.getMainHandler(), player); case Util.TYPE_OTHER: Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 42c408d4e5..8d12290384 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -461,7 +461,7 @@ public class HlsChunkSource { } /** - * Invoked when the {@link HlsSampleSource} has finished loading a chunk obtained from this + * Invoked when the {@link HlsTrackStreamWrapper} has finished loading a chunk obtained from this * source. * * @param chunk The chunk whose load has been completed. @@ -480,8 +480,8 @@ public class HlsChunkSource { } /** - * Invoked when the {@link HlsSampleSource} encounters an error loading a chunk obtained from - * this source. + * Invoked when the {@link HlsTrackStreamWrapper} encounters an error loading a chunk obtained + * from this source. * * @param chunk The chunk whose load encountered the error. * @param cancelable Whether the load can be canceled. diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java index 8c1df7b49b..a291c068eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java @@ -59,9 +59,9 @@ import java.io.IOException; * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. * @param extractor The extractor to parse samples from the data. * @param extractorNeedsInit Whether the extractor needs initializing with the target - * {@link HlsOutput}. + * {@link HlsTrackStreamWrapper}. * @param shouldSpliceIn Whether the samples parsed from this chunk should be spliced into any - * samples already queued to the {@link HlsOutput}. + * samples already queued to the {@link HlsTrackStreamWrapper}. * @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionIv For AES encryption chunks, the encryption initialization vector. */ @@ -80,12 +80,12 @@ import java.io.IOException; } /** - * Initializes the chunk for loading, setting the {@link HlsOutput} that will receive samples as - * they are loaded. + * Initializes the chunk for loading, setting the {@link HlsTrackStreamWrapper} that will receive + * samples as they are loaded. * * @param output The output that will receive the loaded samples. */ - public void init(HlsOutput output) { + public void init(HlsTrackStreamWrapper output) { if (shouldSpliceIn) { output.splice(); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java deleted file mode 100644 index 222177326c..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.hls; - -import com.google.android.exoplayer.extractor.DefaultTrackOutput; -import com.google.android.exoplayer.extractor.ExtractorOutput; -import com.google.android.exoplayer.extractor.SeekMap; -import com.google.android.exoplayer.upstream.Allocator; - -import android.util.SparseArray; - -/** - * An {@link ExtractorOutput} for HLS playbacks. - */ -/* package */ final class HlsOutput implements ExtractorOutput { - - private final Allocator allocator; - private final SparseArray sampleQueues = new SparseArray<>(); - - private boolean prepared; - private DefaultTrackOutput[] trackOutputArray; - private volatile boolean tracksBuilt; - - public HlsOutput(Allocator allocator) { - this.allocator = allocator; - } - - // Called by the consuming thread. - - /** - * Prepares the output, or does nothing if the output is already prepared. - * - * @return True if the output is prepared, false otherwise. - */ - public boolean prepare() { - if (prepared) { - return true; - } else if (!tracksBuilt) { - return false; - } else { - if (trackOutputArray == null) { - trackOutputArray = new DefaultTrackOutput[sampleQueues.size()]; - for (int i = 0; i < trackOutputArray.length; i++) { - trackOutputArray[i] = sampleQueues.valueAt(i); - } - } - for (DefaultTrackOutput sampleQueue : trackOutputArray) { - if (sampleQueue.getUpstreamFormat() == null) { - return false; - } - } - prepared = true; - return true; - } - } - - /** - * Returns the array of track outputs, or null if the output is not yet prepared. - */ - public DefaultTrackOutput[] getTrackOutputs() { - return trackOutputArray; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Clears all track outputs. - */ - public void clear() { - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).clear(); - } - } - - /** - * Indicates to all track outputs that they should splice in subsequently queued samples. - */ - public void splice() { - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).splice(); - } - } - - // ExtractorOutput implementation. Called by the loading thread. - - @Override - public DefaultTrackOutput track(int id) { - if (sampleQueues.indexOfKey(id) >= 0) { - return sampleQueues.get(id); - } - DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); - sampleQueues.put(id, trackOutput); - return trackOutput; - } - - @Override - public void endTracks() { - tracksBuilt = true; - } - - @Override - public void seekMap(SeekMap seekMap) { - // Do nothing. - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource2.java similarity index 60% rename from library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java rename to library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource2.java index 0e4eb8ff2b..708e0a0105 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource2.java @@ -46,21 +46,22 @@ import java.util.List; /** * A {@link SampleSource} for HLS streams. */ -public final class HlsSource implements SampleSource { +public final class HlsSampleSource2 implements SampleSource { private final ManifestFetcher manifestFetcher; - private final HlsSampleSource[] sources; - private final IdentityHashMap trackStreamSources; + private final HlsTrackStreamWrapper[] trackStreamWrappers; + private final IdentityHashMap trackStreamSources; private final int[] selectedTrackCounts; private boolean prepared; private boolean seenFirstTrackSelection; private long durationUs; private TrackGroupArray trackGroups; - private HlsSampleSource[] enabledSources; + private HlsTrackStreamWrapper[] enabledTrackStreamWrappers; - public HlsSource(Uri uri, DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter, - Handler eventHandler, ChunkTrackStreamEventListener eventListener) { + public HlsSampleSource2(Uri uri, DataSourceFactory dataSourceFactory, + BandwidthMeter bandwidthMeter, Handler eventHandler, + ChunkTrackStreamEventListener eventListener) { HlsPlaylistParser parser = new HlsPlaylistParser(); DataSource manifestDataSource = dataSourceFactory.createDataSource(); manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser); @@ -73,23 +74,25 @@ public final class HlsSource implements SampleSource { HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_DEFAULT, defaultDataSource, timestampAdjusterProvider, new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter)); - HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl, - C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); + HlsTrackStreamWrapper defaultTrackStreamWrapper = new HlsTrackStreamWrapper(defaultChunkSource, + loadControl, C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO, audioDataSource, timestampAdjusterProvider, null); - HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl, - C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); + HlsTrackStreamWrapper audioTrackStreamWrapper = new HlsTrackStreamWrapper(audioChunkSource, + loadControl, C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, subtitleDataSource, timestampAdjusterProvider, null); - HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl, - C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT); + HlsTrackStreamWrapper subtitleTrackStreamWrapper = new HlsTrackStreamWrapper( + subtitleChunkSource, loadControl, C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, + C.TRACK_TYPE_TEXT); - sources = new HlsSampleSource[] {defaultSampleSource, audioSampleSource, subtitleSampleSource}; - selectedTrackCounts = new int[sources.length]; + trackStreamWrappers = new HlsTrackStreamWrapper[] {defaultTrackStreamWrapper, + audioTrackStreamWrapper, subtitleTrackStreamWrapper}; + selectedTrackCounts = new int[trackStreamWrappers.length]; trackStreamSources = new IdentityHashMap<>(); } @@ -98,29 +101,29 @@ public final class HlsSource implements SampleSource { if (prepared) { return true; } - boolean sourcesPrepared = true; - for (HlsSampleSource source : sources) { - sourcesPrepared &= source.prepare(positionUs); + boolean trackStreamWrappersPrepared = true; + for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { + trackStreamWrappersPrepared &= trackStreamWrapper.prepare(positionUs); } - if (!sourcesPrepared) { + if (!trackStreamWrappersPrepared) { return false; } durationUs = 0; int totalTrackGroupCount = 0; - for (HlsSampleSource source : sources) { - totalTrackGroupCount += source.getTrackGroups().length; + for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { + totalTrackGroupCount += trackStreamWrapper.getTrackGroups().length; if (durationUs != C.UNSET_TIME_US) { - long sourceDurationUs = source.getDurationUs(); - durationUs = sourceDurationUs == C.UNSET_TIME_US - ? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs); + long wrapperDurationUs = trackStreamWrapper.getDurationUs(); + durationUs = wrapperDurationUs == C.UNSET_TIME_US + ? C.UNSET_TIME_US : Math.max(durationUs, wrapperDurationUs); } } TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; int trackGroupIndex = 0; - for (HlsSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; - for (int j = 0; j < sourceTrackGroupCount; j++) { - trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j); + for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { + int wrapperTrackGroupCount = trackStreamWrapper.getTrackGroups().length; + for (int j = 0; j < wrapperTrackGroupCount; j++) { + trackGroupArray[trackGroupIndex++] = trackStreamWrapper.getTrackGroups().get(j); } } trackGroups = new TrackGroupArray(trackGroupArray); @@ -143,21 +146,21 @@ public final class HlsSource implements SampleSource { List newSelections, long positionUs) { Assertions.checkState(prepared); TrackStream[] newStreams = new TrackStream[newSelections.size()]; - // Select tracks for each source. - int enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { - selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs, - newStreams); + // Select tracks for each wrapper. + int enabledTrackStreamWrapperCount = 0; + for (int i = 0; i < trackStreamWrappers.length; i++) { + selectedTrackCounts[i] += selectTracks(trackStreamWrappers[i], oldStreams, newSelections, + positionUs, newStreams); if (selectedTrackCounts[i] > 0) { - enabledSourceCount++; + enabledTrackStreamWrapperCount++; } } - // Update the enabled sources. - enabledSources = new HlsSampleSource[enabledSourceCount]; - enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { + // Update the enabled wrappers. + enabledTrackStreamWrappers = new HlsTrackStreamWrapper[enabledTrackStreamWrapperCount]; + enabledTrackStreamWrapperCount = 0; + for (int i = 0; i < trackStreamWrappers.length; i++) { if (selectedTrackCounts[i] > 0) { - enabledSources[enabledSourceCount++] = sources[i]; + enabledTrackStreamWrappers[enabledTrackStreamWrapperCount++] = trackStreamWrappers[i]; } } seenFirstTrackSelection = true; @@ -166,16 +169,16 @@ public final class HlsSource implements SampleSource { @Override public void continueBuffering(long positionUs) { - for (HlsSampleSource source : enabledSources) { - source.continueBuffering(positionUs); + for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) { + trackStreamWrapper.continueBuffering(positionUs); } } @Override public long readReset() { long resetPositionUs = C.UNSET_TIME_US; - for (HlsSampleSource source : enabledSources) { - long childResetPositionUs = source.readReset(); + for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) { + long childResetPositionUs = trackStreamWrapper.readReset(); if (resetPositionUs == C.UNSET_TIME_US) { resetPositionUs = childResetPositionUs; } else if (childResetPositionUs != C.UNSET_TIME_US) { @@ -188,12 +191,12 @@ public final class HlsSource implements SampleSource { @Override public long getBufferedPositionUs() { long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE; - for (HlsSampleSource source : enabledSources) { - long rendererBufferedPositionUs = source.getBufferedPositionUs(); + for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) { + long rendererBufferedPositionUs = trackStreamWrapper.getBufferedPositionUs(); if (rendererBufferedPositionUs == C.UNSET_TIME_US) { return C.UNSET_TIME_US; } else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) { - // This source is fully buffered. + // This wrapper is fully buffered. } else { bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); } @@ -203,39 +206,40 @@ public final class HlsSource implements SampleSource { @Override public void seekToUs(long positionUs) { - for (HlsSampleSource source : enabledSources) { - source.seekToUs(positionUs); + for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) { + trackStreamWrapper.seekToUs(positionUs); } } @Override public void release() { manifestFetcher.release(); - for (HlsSampleSource source : sources) { - source.release(); + for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { + trackStreamWrapper.release(); } } // Internal methods. - private int selectTracks(HlsSampleSource source, List allOldStreams, - List allNewSelections, long positionUs, TrackStream[] allNewStreams) { + private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper, + List allOldStreams, List allNewSelections, long positionUs, + TrackStream[] allNewStreams) { // Get the subset of the old streams for the source. ArrayList oldStreams = new ArrayList<>(); for (int i = 0; i < allOldStreams.size(); i++) { TrackStream stream = allOldStreams.get(i); - if (trackStreamSources.get(stream) == source) { + if (trackStreamSources.get(stream) == trackStreamWrapper) { trackStreamSources.remove(stream); oldStreams.add(stream); } } - // Get the subset of the new selections for the source. + // Get the subset of the new selections for the wrapper. ArrayList newSelections = new ArrayList<>(); int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; for (int i = 0; i < allNewSelections.size(); i++) { TrackSelection selection = allNewSelections.get(i); - Pair sourceAndGroup = getSourceAndGroup(selection.group); - if (sourceAndGroup.first == source) { + Pair sourceAndGroup = getSourceAndGroup(selection.group); + if (sourceAndGroup.first == trackStreamWrapper) { newSelectionOriginalIndices[newSelections.size()] = i; newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks())); } @@ -245,20 +249,21 @@ public final class HlsSource implements SampleSource { return 0; } // Perform the selection. - TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs); + TrackStream[] newStreams = trackStreamWrapper.selectTracks(oldStreams, newSelections, + positionUs); for (int j = 0; j < newStreams.length; j++) { allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j]; - trackStreamSources.put(newStreams[j], source); + trackStreamSources.put(newStreams[j], trackStreamWrapper); } return newSelections.size() - oldStreams.size(); } - private Pair getSourceAndGroup(int group) { + private Pair getSourceAndGroup(int group) { int totalTrackGroupCount = 0; - for (HlsSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; + for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { + int sourceTrackGroupCount = trackStreamWrapper.getTrackGroups().length; if (group < totalTrackGroupCount + sourceTrackGroupCount) { - return Pair.create(source, group - totalTrackGroupCount); + return Pair.create(trackStreamWrapper, group - totalTrackGroupCount); } totalTrackGroupCount += sourceTrackGroupCount; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java similarity index 87% rename from library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java rename to library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java index daf84d567f..9632d30c8a 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java @@ -20,7 +20,6 @@ import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.FormatHolder; import com.google.android.exoplayer.LoadControl; -import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; @@ -30,6 +29,8 @@ import com.google.android.exoplayer.chunk.ChunkHolder; import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher; import com.google.android.exoplayer.extractor.DefaultTrackOutput; +import com.google.android.exoplayer.extractor.ExtractorOutput; +import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; @@ -37,15 +38,17 @@ import com.google.android.exoplayer.util.MimeTypes; import android.os.Handler; import android.os.SystemClock; +import android.util.SparseArray; import java.io.IOException; import java.util.LinkedList; import java.util.List; /** - * A {@link SampleSource} for HLS streams. + * Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides + * {@link TrackStream}s from which the loaded media can be consumed. */ -public final class HlsSampleSource implements Loader.Callback { +/* package */ final class HlsTrackStreamWrapper implements Loader.Callback, ExtractorOutput { /** * The default minimum number of times to retry loading data prior to failing. @@ -59,18 +62,19 @@ public final class HlsSampleSource implements Loader.Callback { private final Loader loader; private final HlsChunkSource chunkSource; + private final SparseArray sampleQueues; private final LinkedList mediaChunks; - private final HlsOutput output; private final int bufferSizeContribution; private final ChunkHolder nextChunkHolder; private final EventDispatcher eventDispatcher; private final LoadControl loadControl; + private volatile boolean sampleQueuesBuilt; + private boolean prepared; private boolean seenFirstTrackSelection; private boolean notifyReset; private int enabledTrackCount; - private DefaultTrackOutput[] sampleQueues; private Format downstreamFormat; // Tracks are complicated in HLS. See documentation of buildTracks for details. @@ -93,7 +97,7 @@ public final class HlsSampleSource implements Loader.Callback { * @param loadControl Controls when the source is permitted to load data. * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. */ - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution) { this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); } @@ -107,7 +111,7 @@ public final class HlsSampleSource implements Loader.Callback { * @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. */ - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, Handler eventHandler, ChunkTrackStreamEventListener eventListener, int eventSourceId) { this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, @@ -125,7 +129,7 @@ public final class HlsSampleSource implements Loader.Callback { * @param minLoadableRetryCount The minimum number of times that the source should retry a load * before propagating an error. */ - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, Handler eventHandler, ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; @@ -134,8 +138,8 @@ public final class HlsSampleSource implements Loader.Callback { loader = new Loader("Loader:HLS", minLoadableRetryCount); eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId); nextChunkHolder = new ChunkHolder(); + sampleQueues = new SparseArray<>(); mediaChunks = new LinkedList<>(); - output = new HlsOutput(loadControl.getAllocator()); pendingResetPositionUs = C.UNSET_TIME_US; } @@ -151,11 +155,20 @@ public final class HlsSampleSource implements Loader.Callback { prepared = true; return true; } - if (output.prepare()) { - sampleQueues = output.getTrackOutputs(); - buildTracks(); - prepared = true; - return true; + if (sampleQueuesBuilt) { + boolean canBuildTracks = true; + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + canBuildTracks = false; + break; + } + } + if (canBuildTracks) { + buildTracks(); + prepared = true; + return true; + } } // We're not prepared. maybeThrowError(); @@ -195,7 +208,7 @@ public final class HlsSampleSource implements Loader.Callback { int group = selection.group; int[] tracks = selection.getTracks(); setTrackGroupEnabledState(group, true); - sampleQueues[group].needDownstreamFormat(); + sampleQueues.valueAt(group).needDownstreamFormat(); if (group == primaryTrackGroupIndex) { primaryTracksDeselected |= chunkSource.selectTracks(tracks); } @@ -256,9 +269,10 @@ public final class HlsSampleSource implements Loader.Callback { if (lastCompletedMediaChunk != null) { bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); } - for (DefaultTrackOutput sampleQueue : sampleQueues) { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { bufferedPositionUs = Math.max(bufferedPositionUs, - sampleQueue.getLargestQueuedTimestampUs()); + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); } return bufferedPositionUs; } @@ -279,7 +293,7 @@ public final class HlsSampleSource implements Loader.Callback { // TrackStream implementation. /* package */ boolean isReady(int group) { - return loadingFinished || (!isPendingReset() && !sampleQueues[group].isEmpty()); + return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(group).isEmpty()); } /* package */ void maybeThrowError() throws IOException { @@ -303,7 +317,8 @@ public final class HlsSampleSource implements Loader.Callback { } downstreamFormat = format; - return sampleQueues[group].readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs); + return sampleQueues.valueAt(group).readData(formatHolder, buffer, loadingFinished, + lastSeekPositionUs); } // Loader.Callback implementation. @@ -361,11 +376,44 @@ public final class HlsSampleSource implements Loader.Callback { } } + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Indicates to all track outputs that they should splice in subsequently queued samples. + */ + public void splice() { + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).splice(); + } + } + + // ExtractorOutput implementation. Called by the loading thread. + + @Override + public DefaultTrackOutput track(int id) { + if (sampleQueues.indexOfKey(id) >= 0) { + return sampleQueues.get(id); + } + DefaultTrackOutput trackOutput = new DefaultTrackOutput(loadControl.getAllocator()); + sampleQueues.put(id, trackOutput); + return trackOutput; + } + + @Override + public void endTracks() { + sampleQueuesBuilt = true; + } + + @Override + public void seekMap(SeekMap seekMap) { + // Do nothing. + } + // Internal methods. /** - * Builds tracks that are exposed by this {@link HlsSampleSource} instance, as well as internal - * data-structures required for operation. + * Builds tracks that are exposed by this {@link HlsTrackStreamWrapper} instance, as well as + * internal data-structures required for operation. *

* Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each * variant stream typically contains muxed video, audio and (possibly) additional audio, metadata @@ -378,7 +426,7 @@ public final class HlsSampleSource implements Loader.Callback { * adaptive track defined to span all variants and a track for each individual variant. The * adaptive track is initially selected. The extractor is then prepared to discover the tracks * inside of each variant stream. The two sets of tracks are then combined by this method to - * create a third set, which is the set exposed by this {@link HlsSampleSource}: + * create a third set, which is the set exposed by this {@link HlsTrackStreamWrapper}: *

    *
  • The extractor tracks are inspected to infer a "primary" track type. If a video track is * present then it is always the primary type. If not, audio is the primary type if present. @@ -397,9 +445,9 @@ public final class HlsSampleSource implements Loader.Callback { // of the single track of this type. int primaryExtractorTrackType = PRIMARY_TYPE_NONE; int primaryExtractorTrackIndex = -1; - int extractorTrackCount = sampleQueues.length; + int extractorTrackCount = sampleQueues.size(); for (int i = 0; i < extractorTrackCount; i++) { - String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType; + String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType; int trackType; if (MimeTypes.isVideo(sampleMimeType)) { trackType = PRIMARY_TYPE_VIDEO; @@ -430,7 +478,7 @@ public final class HlsSampleSource implements Loader.Callback { // Construct the set of exposed track groups. TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; for (int i = 0; i < extractorTrackCount; i++) { - Format sampleFormat = sampleQueues[i].getUpstreamFormat(); + Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat(); if (i == primaryExtractorTrackIndex) { Format[] formats = new Format[chunkSourceTrackCount]; for (int j = 0; j < chunkSourceTrackCount; j++) { @@ -498,9 +546,10 @@ public final class HlsSampleSource implements Loader.Callback { // before for such tracks. For ID3 we probably explicitly don't want the keyframe before, even // if we do have it, since it might be quite a long way behind the seek position. We probably // only want to output ID3 buffers whose timestamps are greater than or equal to positionUs. - for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; seekInsideBuffer && i < sampleQueueCount; i++) { if (groupEnabledStates[i]) { - seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs); + seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); } } if (seekInsideBuffer) { @@ -515,12 +564,12 @@ public final class HlsSampleSource implements Loader.Callback { } private void discardSamplesForDisabledTracks() { - if (!output.prepare()) { + if (!prepared) { return; } for (int i = 0; i < groupEnabledStates.length; i++) { if (!groupEnabledStates[i]) { - sampleQueues[i].skipAllSamples(); + sampleQueues.valueAt(i).skipAllSamples(); } } } @@ -537,7 +586,9 @@ public final class HlsSampleSource implements Loader.Callback { } private void clearState() { - output.clear(); + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).clear(); + } mediaChunks.clear(); clearCurrentLoadable(); } @@ -577,7 +628,7 @@ public final class HlsSampleSource implements Loader.Callback { if (isMediaChunk(currentLoadable)) { pendingResetPositionUs = C.UNSET_TIME_US; HlsMediaChunk mediaChunk = (HlsMediaChunk) currentLoadable; - mediaChunk.init(output); + mediaChunk.init(this); mediaChunks.addLast(mediaChunk); eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger, mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs); @@ -622,17 +673,17 @@ public final class HlsSampleSource implements Loader.Callback { @Override public boolean isReady() { - return HlsSampleSource.this.isReady(group); + return HlsTrackStreamWrapper.this.isReady(group); } @Override public void maybeThrowError() throws IOException { - HlsSampleSource.this.maybeThrowError(); + HlsTrackStreamWrapper.this.maybeThrowError(); } @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { - return HlsSampleSource.this.readData(group, formatHolder, buffer); + return HlsTrackStreamWrapper.this.readData(group, formatHolder, buffer); } }