Refactor #6.HLS.1

1. Merge HlsOutput and HlsSampleSource -> HlsTrackStreamWrapper.
2. Rename HlsSource -> HlsSampleSource2. This will be renamed to
   HlsSampleSource in a subsequent CL.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=122415970
This commit is contained in:
olly 2016-05-16 06:32:22 -07:00 committed by Oliver Woodman
parent 51cf8bed29
commit 770f2c2c58
6 changed files with 161 additions and 224 deletions

View File

@ -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.demo.ui.TrackSelectionHelper;
import com.google.android.exoplayer.drm.UnsupportedDrmException; import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; 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.GeobFrame;
import com.google.android.exoplayer.metadata.id3.Id3Frame; import com.google.android.exoplayer.metadata.id3.Id3Frame;
import com.google.android.exoplayer.metadata.id3.PrivFrame; 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(), return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(),
player.getMainHandler(), player); player.getMainHandler(), player);
case Util.TYPE_HLS: case Util.TYPE_HLS:
return new HlsSource(uri, dataSourceFactory, player.getBandwidthMeter(), return new HlsSampleSource2(uri, dataSourceFactory, player.getBandwidthMeter(),
player.getMainHandler(), player); player.getMainHandler(), player);
case Util.TYPE_OTHER: case Util.TYPE_OTHER:
Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE);

View File

@ -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. * source.
* *
* @param chunk The chunk whose load has been completed. * @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 * Invoked when the {@link HlsTrackStreamWrapper} encounters an error loading a chunk obtained
* this source. * from 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 cancelable Whether the load can be canceled.

View File

@ -59,9 +59,9 @@ import java.io.IOException;
* @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param extractor The extractor to parse samples from the data. * @param extractor The extractor to parse samples from the data.
* @param extractorNeedsInit Whether the extractor needs initializing with the target * @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 * @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 encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * @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 * Initializes the chunk for loading, setting the {@link HlsTrackStreamWrapper} that will receive
* they are loaded. * samples as they are loaded.
* *
* @param output The output that will receive the loaded samples. * @param output The output that will receive the loaded samples.
*/ */
public void init(HlsOutput output) { public void init(HlsTrackStreamWrapper output) {
if (shouldSpliceIn) { if (shouldSpliceIn) {
output.splice(); output.splice();
} }

View File

@ -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<DefaultTrackOutput> 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.
}
}

View File

@ -46,21 +46,22 @@ import java.util.List;
/** /**
* A {@link SampleSource} for HLS streams. * A {@link SampleSource} for HLS streams.
*/ */
public final class HlsSource implements SampleSource { public final class HlsSampleSource2 implements SampleSource {
private final ManifestFetcher<HlsPlaylist> manifestFetcher; private final ManifestFetcher<HlsPlaylist> manifestFetcher;
private final HlsSampleSource[] sources; private final HlsTrackStreamWrapper[] trackStreamWrappers;
private final IdentityHashMap<TrackStream, HlsSampleSource> trackStreamSources; private final IdentityHashMap<TrackStream, HlsTrackStreamWrapper> trackStreamSources;
private final int[] selectedTrackCounts; private final int[] selectedTrackCounts;
private boolean prepared; private boolean prepared;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
private long durationUs; private long durationUs;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private HlsSampleSource[] enabledSources; private HlsTrackStreamWrapper[] enabledTrackStreamWrappers;
public HlsSource(Uri uri, DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter, public HlsSampleSource2(Uri uri, DataSourceFactory dataSourceFactory,
Handler eventHandler, ChunkTrackStreamEventListener eventListener) { BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkTrackStreamEventListener eventListener) {
HlsPlaylistParser parser = new HlsPlaylistParser(); HlsPlaylistParser parser = new HlsPlaylistParser();
DataSource manifestDataSource = dataSourceFactory.createDataSource(); DataSource manifestDataSource = dataSourceFactory.createDataSource();
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser); 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, HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_DEFAULT,
defaultDataSource, timestampAdjusterProvider, defaultDataSource, timestampAdjusterProvider,
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter)); new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter));
HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl, HlsTrackStreamWrapper defaultTrackStreamWrapper = new HlsTrackStreamWrapper(defaultChunkSource,
C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); loadControl, C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO, HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
audioDataSource, timestampAdjusterProvider, null); audioDataSource, timestampAdjusterProvider, null);
HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl, HlsTrackStreamWrapper audioTrackStreamWrapper = new HlsTrackStreamWrapper(audioChunkSource,
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); loadControl, C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
subtitleDataSource, timestampAdjusterProvider, null); subtitleDataSource, timestampAdjusterProvider, null);
HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl, HlsTrackStreamWrapper subtitleTrackStreamWrapper = new HlsTrackStreamWrapper(
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT); subtitleChunkSource, loadControl, C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener,
C.TRACK_TYPE_TEXT);
sources = new HlsSampleSource[] {defaultSampleSource, audioSampleSource, subtitleSampleSource}; trackStreamWrappers = new HlsTrackStreamWrapper[] {defaultTrackStreamWrapper,
selectedTrackCounts = new int[sources.length]; audioTrackStreamWrapper, subtitleTrackStreamWrapper};
selectedTrackCounts = new int[trackStreamWrappers.length];
trackStreamSources = new IdentityHashMap<>(); trackStreamSources = new IdentityHashMap<>();
} }
@ -98,29 +101,29 @@ public final class HlsSource implements SampleSource {
if (prepared) { if (prepared) {
return true; return true;
} }
boolean sourcesPrepared = true; boolean trackStreamWrappersPrepared = true;
for (HlsSampleSource source : sources) { for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
sourcesPrepared &= source.prepare(positionUs); trackStreamWrappersPrepared &= trackStreamWrapper.prepare(positionUs);
} }
if (!sourcesPrepared) { if (!trackStreamWrappersPrepared) {
return false; return false;
} }
durationUs = 0; durationUs = 0;
int totalTrackGroupCount = 0; int totalTrackGroupCount = 0;
for (HlsSampleSource source : sources) { for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
totalTrackGroupCount += source.getTrackGroups().length; totalTrackGroupCount += trackStreamWrapper.getTrackGroups().length;
if (durationUs != C.UNSET_TIME_US) { if (durationUs != C.UNSET_TIME_US) {
long sourceDurationUs = source.getDurationUs(); long wrapperDurationUs = trackStreamWrapper.getDurationUs();
durationUs = sourceDurationUs == C.UNSET_TIME_US durationUs = wrapperDurationUs == C.UNSET_TIME_US
? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs); ? C.UNSET_TIME_US : Math.max(durationUs, wrapperDurationUs);
} }
} }
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0; int trackGroupIndex = 0;
for (HlsSampleSource source : sources) { for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
int sourceTrackGroupCount = source.getTrackGroups().length; int wrapperTrackGroupCount = trackStreamWrapper.getTrackGroups().length;
for (int j = 0; j < sourceTrackGroupCount; j++) { for (int j = 0; j < wrapperTrackGroupCount; j++) {
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j); trackGroupArray[trackGroupIndex++] = trackStreamWrapper.getTrackGroups().get(j);
} }
} }
trackGroups = new TrackGroupArray(trackGroupArray); trackGroups = new TrackGroupArray(trackGroupArray);
@ -143,21 +146,21 @@ public final class HlsSource implements SampleSource {
List<TrackSelection> newSelections, long positionUs) { List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
TrackStream[] newStreams = new TrackStream[newSelections.size()]; TrackStream[] newStreams = new TrackStream[newSelections.size()];
// Select tracks for each source. // Select tracks for each wrapper.
int enabledSourceCount = 0; int enabledTrackStreamWrapperCount = 0;
for (int i = 0; i < sources.length; i++) { for (int i = 0; i < trackStreamWrappers.length; i++) {
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs, selectedTrackCounts[i] += selectTracks(trackStreamWrappers[i], oldStreams, newSelections,
newStreams); positionUs, newStreams);
if (selectedTrackCounts[i] > 0) { if (selectedTrackCounts[i] > 0) {
enabledSourceCount++; enabledTrackStreamWrapperCount++;
} }
} }
// Update the enabled sources. // Update the enabled wrappers.
enabledSources = new HlsSampleSource[enabledSourceCount]; enabledTrackStreamWrappers = new HlsTrackStreamWrapper[enabledTrackStreamWrapperCount];
enabledSourceCount = 0; enabledTrackStreamWrapperCount = 0;
for (int i = 0; i < sources.length; i++) { for (int i = 0; i < trackStreamWrappers.length; i++) {
if (selectedTrackCounts[i] > 0) { if (selectedTrackCounts[i] > 0) {
enabledSources[enabledSourceCount++] = sources[i]; enabledTrackStreamWrappers[enabledTrackStreamWrapperCount++] = trackStreamWrappers[i];
} }
} }
seenFirstTrackSelection = true; seenFirstTrackSelection = true;
@ -166,16 +169,16 @@ public final class HlsSource implements SampleSource {
@Override @Override
public void continueBuffering(long positionUs) { public void continueBuffering(long positionUs) {
for (HlsSampleSource source : enabledSources) { for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
source.continueBuffering(positionUs); trackStreamWrapper.continueBuffering(positionUs);
} }
} }
@Override @Override
public long readReset() { public long readReset() {
long resetPositionUs = C.UNSET_TIME_US; long resetPositionUs = C.UNSET_TIME_US;
for (HlsSampleSource source : enabledSources) { for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
long childResetPositionUs = source.readReset(); long childResetPositionUs = trackStreamWrapper.readReset();
if (resetPositionUs == C.UNSET_TIME_US) { if (resetPositionUs == C.UNSET_TIME_US) {
resetPositionUs = childResetPositionUs; resetPositionUs = childResetPositionUs;
} else if (childResetPositionUs != C.UNSET_TIME_US) { } else if (childResetPositionUs != C.UNSET_TIME_US) {
@ -188,12 +191,12 @@ public final class HlsSource implements SampleSource {
@Override @Override
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE; long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE;
for (HlsSampleSource source : enabledSources) { for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
long rendererBufferedPositionUs = source.getBufferedPositionUs(); long rendererBufferedPositionUs = trackStreamWrapper.getBufferedPositionUs();
if (rendererBufferedPositionUs == C.UNSET_TIME_US) { if (rendererBufferedPositionUs == C.UNSET_TIME_US) {
return C.UNSET_TIME_US; return C.UNSET_TIME_US;
} else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) { } else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) {
// This source is fully buffered. // This wrapper is fully buffered.
} else { } else {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
} }
@ -203,39 +206,40 @@ public final class HlsSource implements SampleSource {
@Override @Override
public void seekToUs(long positionUs) { public void seekToUs(long positionUs) {
for (HlsSampleSource source : enabledSources) { for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
source.seekToUs(positionUs); trackStreamWrapper.seekToUs(positionUs);
} }
} }
@Override @Override
public void release() { public void release() {
manifestFetcher.release(); manifestFetcher.release();
for (HlsSampleSource source : sources) { for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
source.release(); trackStreamWrapper.release();
} }
} }
// Internal methods. // Internal methods.
private int selectTracks(HlsSampleSource source, List<TrackStream> allOldStreams, private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams) { List<TrackStream> allOldStreams, List<TrackSelection> allNewSelections, long positionUs,
TrackStream[] allNewStreams) {
// Get the subset of the old streams for the source. // Get the subset of the old streams for the source.
ArrayList<TrackStream> oldStreams = new ArrayList<>(); ArrayList<TrackStream> oldStreams = new ArrayList<>();
for (int i = 0; i < allOldStreams.size(); i++) { for (int i = 0; i < allOldStreams.size(); i++) {
TrackStream stream = allOldStreams.get(i); TrackStream stream = allOldStreams.get(i);
if (trackStreamSources.get(stream) == source) { if (trackStreamSources.get(stream) == trackStreamWrapper) {
trackStreamSources.remove(stream); trackStreamSources.remove(stream);
oldStreams.add(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<TrackSelection> newSelections = new ArrayList<>(); ArrayList<TrackSelection> newSelections = new ArrayList<>();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) { for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i); TrackSelection selection = allNewSelections.get(i);
Pair<HlsSampleSource, Integer> sourceAndGroup = getSourceAndGroup(selection.group); Pair<HlsTrackStreamWrapper, Integer> sourceAndGroup = getSourceAndGroup(selection.group);
if (sourceAndGroup.first == source) { if (sourceAndGroup.first == trackStreamWrapper) {
newSelectionOriginalIndices[newSelections.size()] = i; newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks())); newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks()));
} }
@ -245,20 +249,21 @@ public final class HlsSource implements SampleSource {
return 0; return 0;
} }
// Perform the selection. // 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++) { for (int j = 0; j < newStreams.length; j++) {
allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j]; allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j];
trackStreamSources.put(newStreams[j], source); trackStreamSources.put(newStreams[j], trackStreamWrapper);
} }
return newSelections.size() - oldStreams.size(); return newSelections.size() - oldStreams.size();
} }
private Pair<HlsSampleSource, Integer> getSourceAndGroup(int group) { private Pair<HlsTrackStreamWrapper, Integer> getSourceAndGroup(int group) {
int totalTrackGroupCount = 0; int totalTrackGroupCount = 0;
for (HlsSampleSource source : sources) { for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
int sourceTrackGroupCount = source.getTrackGroups().length; int sourceTrackGroupCount = trackStreamWrapper.getTrackGroups().length;
if (group < totalTrackGroupCount + sourceTrackGroupCount) { if (group < totalTrackGroupCount + sourceTrackGroupCount) {
return Pair.create(source, group - totalTrackGroupCount); return Pair.create(trackStreamWrapper, group - totalTrackGroupCount);
} }
totalTrackGroupCount += sourceTrackGroupCount; totalTrackGroupCount += sourceTrackGroupCount;
} }

View File

@ -20,7 +20,6 @@ import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder; import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection; 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;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher; import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher;
import com.google.android.exoplayer.extractor.DefaultTrackOutput; 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;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions; 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.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.SparseArray;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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. * 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 Loader loader;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks; private final LinkedList<HlsMediaChunk> mediaChunks;
private final HlsOutput output;
private final int bufferSizeContribution; private final int bufferSizeContribution;
private final ChunkHolder nextChunkHolder; private final ChunkHolder nextChunkHolder;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final LoadControl loadControl; private final LoadControl loadControl;
private volatile boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
private boolean notifyReset; private boolean notifyReset;
private int enabledTrackCount; private int enabledTrackCount;
private DefaultTrackOutput[] sampleQueues;
private Format downstreamFormat; private Format downstreamFormat;
// Tracks are complicated in HLS. See documentation of buildTracks for details. // 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 loadControl Controls when the source is permitted to load data.
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. * @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) { int bufferSizeContribution) {
this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); 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 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. * @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, int bufferSizeContribution, Handler eventHandler,
ChunkTrackStreamEventListener eventListener, int eventSourceId) { ChunkTrackStreamEventListener eventListener, int eventSourceId) {
this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, 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 * @param minLoadableRetryCount The minimum number of times that the source should retry a load
* before propagating an error. * before propagating an error.
*/ */
public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, Handler eventHandler, int bufferSizeContribution, Handler eventHandler,
ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
@ -134,8 +138,8 @@ public final class HlsSampleSource implements Loader.Callback {
loader = new Loader("Loader:HLS", minLoadableRetryCount); loader = new Loader("Loader:HLS", minLoadableRetryCount);
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId); eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder(); nextChunkHolder = new ChunkHolder();
sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>(); mediaChunks = new LinkedList<>();
output = new HlsOutput(loadControl.getAllocator());
pendingResetPositionUs = C.UNSET_TIME_US; pendingResetPositionUs = C.UNSET_TIME_US;
} }
@ -151,12 +155,21 @@ public final class HlsSampleSource implements Loader.Callback {
prepared = true; prepared = true;
return true; return true;
} }
if (output.prepare()) { if (sampleQueuesBuilt) {
sampleQueues = output.getTrackOutputs(); 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(); buildTracks();
prepared = true; prepared = true;
return true; return true;
} }
}
// We're not prepared. // We're not prepared.
maybeThrowError(); maybeThrowError();
if (!loader.isLoading()) { if (!loader.isLoading()) {
@ -195,7 +208,7 @@ public final class HlsSampleSource implements Loader.Callback {
int group = selection.group; int group = selection.group;
int[] tracks = selection.getTracks(); int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true); setTrackGroupEnabledState(group, true);
sampleQueues[group].needDownstreamFormat(); sampleQueues.valueAt(group).needDownstreamFormat();
if (group == primaryTrackGroupIndex) { if (group == primaryTrackGroupIndex) {
primaryTracksDeselected |= chunkSource.selectTracks(tracks); primaryTracksDeselected |= chunkSource.selectTracks(tracks);
} }
@ -256,9 +269,10 @@ public final class HlsSampleSource implements Loader.Callback {
if (lastCompletedMediaChunk != null) { if (lastCompletedMediaChunk != null) {
bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); 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, bufferedPositionUs = Math.max(bufferedPositionUs,
sampleQueue.getLargestQueuedTimestampUs()); sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
} }
return bufferedPositionUs; return bufferedPositionUs;
} }
@ -279,7 +293,7 @@ public final class HlsSampleSource implements Loader.Callback {
// TrackStream implementation. // TrackStream implementation.
/* package */ boolean isReady(int group) { /* package */ boolean isReady(int group) {
return loadingFinished || (!isPendingReset() && !sampleQueues[group].isEmpty()); return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(group).isEmpty());
} }
/* package */ void maybeThrowError() throws IOException { /* package */ void maybeThrowError() throws IOException {
@ -303,7 +317,8 @@ public final class HlsSampleSource implements Loader.Callback {
} }
downstreamFormat = format; downstreamFormat = format;
return sampleQueues[group].readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs); return sampleQueues.valueAt(group).readData(formatHolder, buffer, loadingFinished,
lastSeekPositionUs);
} }
// Loader.Callback implementation. // 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. // Internal methods.
/** /**
* Builds tracks that are exposed by this {@link HlsSampleSource} instance, as well as internal * Builds tracks that are exposed by this {@link HlsTrackStreamWrapper} instance, as well as
* data-structures required for operation. * internal data-structures required for operation.
* <p> * <p>
* Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each * 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 * 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 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 * 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 * 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}:
* <ul> * <ul>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is * <li>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. * 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. // of the single track of this type.
int primaryExtractorTrackType = PRIMARY_TYPE_NONE; int primaryExtractorTrackType = PRIMARY_TYPE_NONE;
int primaryExtractorTrackIndex = -1; int primaryExtractorTrackIndex = -1;
int extractorTrackCount = sampleQueues.length; int extractorTrackCount = sampleQueues.size();
for (int i = 0; i < extractorTrackCount; i++) { for (int i = 0; i < extractorTrackCount; i++) {
String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType; String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType;
int trackType; int trackType;
if (MimeTypes.isVideo(sampleMimeType)) { if (MimeTypes.isVideo(sampleMimeType)) {
trackType = PRIMARY_TYPE_VIDEO; trackType = PRIMARY_TYPE_VIDEO;
@ -430,7 +478,7 @@ public final class HlsSampleSource implements Loader.Callback {
// Construct the set of exposed track groups. // Construct the set of exposed track groups.
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
for (int i = 0; i < extractorTrackCount; i++) { for (int i = 0; i < extractorTrackCount; i++) {
Format sampleFormat = sampleQueues[i].getUpstreamFormat(); Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat();
if (i == primaryExtractorTrackIndex) { if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount]; Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) { 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 // 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 // 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. // 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]) { if (groupEnabledStates[i]) {
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs); seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs);
} }
} }
if (seekInsideBuffer) { if (seekInsideBuffer) {
@ -515,12 +564,12 @@ public final class HlsSampleSource implements Loader.Callback {
} }
private void discardSamplesForDisabledTracks() { private void discardSamplesForDisabledTracks() {
if (!output.prepare()) { if (!prepared) {
return; return;
} }
for (int i = 0; i < groupEnabledStates.length; i++) { for (int i = 0; i < groupEnabledStates.length; i++) {
if (!groupEnabledStates[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() { private void clearState() {
output.clear(); for (int i = 0; i < sampleQueues.size(); i++) {
sampleQueues.valueAt(i).clear();
}
mediaChunks.clear(); mediaChunks.clear();
clearCurrentLoadable(); clearCurrentLoadable();
} }
@ -577,7 +628,7 @@ public final class HlsSampleSource implements Loader.Callback {
if (isMediaChunk(currentLoadable)) { if (isMediaChunk(currentLoadable)) {
pendingResetPositionUs = C.UNSET_TIME_US; pendingResetPositionUs = C.UNSET_TIME_US;
HlsMediaChunk mediaChunk = (HlsMediaChunk) currentLoadable; HlsMediaChunk mediaChunk = (HlsMediaChunk) currentLoadable;
mediaChunk.init(output); mediaChunk.init(this);
mediaChunks.addLast(mediaChunk); mediaChunks.addLast(mediaChunk);
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger, eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs); mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
@ -622,17 +673,17 @@ public final class HlsSampleSource implements Loader.Callback {
@Override @Override
public boolean isReady() { public boolean isReady() {
return HlsSampleSource.this.isReady(group); return HlsTrackStreamWrapper.this.isReady(group);
} }
@Override @Override
public void maybeThrowError() throws IOException { public void maybeThrowError() throws IOException {
HlsSampleSource.this.maybeThrowError(); HlsTrackStreamWrapper.this.maybeThrowError();
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
return HlsSampleSource.this.readData(group, formatHolder, buffer); return HlsTrackStreamWrapper.this.readData(group, formatHolder, buffer);
} }
} }