Enable track selection + WebVTT for HLS.

See the documentation of buildTracks for the gory details.

Issue: #151
Issue: #676
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=112149293
This commit is contained in:
olly 2016-01-14 07:24:03 -08:00 committed by Oliver Woodman
parent b6b97a8683
commit 2690f569af
4 changed files with 239 additions and 48 deletions

View File

@ -25,12 +25,14 @@ import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.hls.DefaultHlsTrackSelector; import com.google.android.exoplayer.hls.DefaultHlsTrackSelector;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylist; import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser; import com.google.android.exoplayer.hls.HlsPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource; import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider; import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
import com.google.android.exoplayer.metadata.Id3Parser; import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultAllocator;
@ -53,7 +55,8 @@ import java.util.Map;
public class HlsRendererBuilder implements RendererBuilder { public class HlsRendererBuilder implements RendererBuilder {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENTS = 256; private static final int MAIN_BUFFER_SEGMENTS = 256;
private static final int TEXT_BUFFER_SEGMENTS = 2;
private final Context context; private final Context context;
private final String userAgent; private final String userAgent;
@ -127,13 +130,15 @@ public class HlsRendererBuilder implements RendererBuilder {
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
// Build the video/audio/metadata renderers.
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(true /* isMaster */, dataSource, url, HlsChunkSource chunkSource = new HlsChunkSource(true /* isMaster */, dataSource, url,
manifest, DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter, manifest, DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter,
new PtsTimestampAdjusterProvider(), HlsChunkSource.ADAPTIVE_MODE_SPLICE); timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
5000, mainHandler, player, 50); 5000, mainHandler, player, 50);
@ -142,14 +147,30 @@ public class HlsRendererBuilder implements RendererBuilder {
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>( MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
sampleSource, new Id3Parser(), player, mainHandler.getLooper()); sampleSource, new Id3Parser(), player, mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
mainHandler.getLooper()); // Build the text renderer, preferring Webvtt where available.
boolean preferWebvtt = false;
if (manifest instanceof HlsMasterPlaylist) {
preferWebvtt = !((HlsMasterPlaylist) manifest).subtitles.isEmpty();
}
TrackRenderer textRenderer;
if (preferWebvtt) {
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource textChunkSource = new HlsChunkSource(false /* isMaster */, textDataSource,
url, manifest, DefaultHlsTrackSelector.newVttInstance(), bandwidthMeter,
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource textSampleSource = new HlsSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper());
} else {
textRenderer = new Eia608TrackRenderer(sampleSource, player, mainHandler.getLooper());
}
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_METADATA] = id3Renderer; renderers[DemoPlayer.TYPE_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer; renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
player.onRenderers(renderers, bandwidthMeter); player.onRenderers(renderers, bandwidthMeter);
} }

View File

@ -234,6 +234,13 @@ public final class MediaFormat {
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight); subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight);
} }
public MediaFormat copyWithFixedTrackInfo(String trackId, int bitrate, int width, int height,
String language) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, NO_VALUE, NO_VALUE);
}
public MediaFormat copyAsAdaptive(String trackId) { public MediaFormat copyAsAdaptive(String trackId) {
return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth,

View File

@ -133,7 +133,7 @@ public class HlsChunkSource implements HlsTrackSelector.Output {
// TODO: Expose tracks. // TODO: Expose tracks.
private final ArrayList<ExposedTrack> tracks; private final ArrayList<ExposedTrack> tracks;
private ExposedTrack enabledTrack; private int selectedTrackIndex;
// A list of variants considered during playback, ordered by decreasing bandwidth. The following // A list of variants considered during playback, ordered by decreasing bandwidth. The following
// three arrays are of the same length and are ordered in the same way (i.e. variantPlaylists[i], // three arrays are of the same length and are ordered in the same way (i.e. variantPlaylists[i],
@ -295,6 +295,18 @@ public class HlsChunkSource implements HlsTrackSelector.Output {
return variants.length == 1 ? variants[0] : null; return variants.length == 1 ? variants[0] : null;
} }
/**
* Returns the currently selected track index.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The currently selected track index.
*/
public int getSelectedTrackIndex() {
return selectedTrackIndex;
}
/** /**
* Selects a track for use. * Selects a track for use.
* <p> * <p>
@ -303,9 +315,10 @@ public class HlsChunkSource implements HlsTrackSelector.Output {
* @param index The track index. * @param index The track index.
*/ */
public void selectTrack(int index) { public void selectTrack(int index) {
enabledTrack = tracks.get(index); selectedTrackIndex = index;
selectedVariantIndex = enabledTrack.defaultVariantIndex; ExposedTrack selectedTrack = tracks.get(selectedTrackIndex);
variants = enabledTrack.variants; selectedVariantIndex = selectedTrack.defaultVariantIndex;
variants = selectedTrack.variants;
variantPlaylists = new HlsMediaPlaylist[variants.length]; variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length]; variantLastPlaylistLoadTimesMs = new long[variants.length];
variantBlacklistTimes = new long[variants.length]; variantBlacklistTimes = new long[variants.length];
@ -472,9 +485,10 @@ public class HlsChunkSource implements HlsTrackSelector.Output {
// The master source has yet to instantiate an adjuster for the discontinuity sequence. // The master source has yet to instantiate an adjuster for the discontinuity sequence.
return; return;
} }
ExposedTrack selectedTrack = tracks.get(selectedTrackIndex);
Extractor extractor = new TsExtractor(timestampAdjuster); Extractor extractor = new TsExtractor(timestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight); switchingVariantSpliced, selectedTrack.adaptiveMaxWidth, selectedTrack.adaptiveMaxHeight);
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.
extractorWrapper = previousTsChunk.extractorWrapper; extractorWrapper = previousTsChunk.extractorWrapper;

View File

@ -36,6 +36,7 @@ import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
/** /**
@ -55,6 +56,11 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
private static final long NO_RESET_PENDING = Long.MIN_VALUE; private static final long NO_RESET_PENDING = Long.MIN_VALUE;
private static final int PRIMARY_TYPE_NONE = 0;
private static final int PRIMARY_TYPE_TEXT = 1;
private static final int PRIMARY_TYPE_AUDIO = 2;
private static final int PRIMARY_TYPE_VIDEO = 3;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final LinkedList<HlsExtractorWrapper> extractors; private final LinkedList<HlsExtractorWrapper> extractors;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
@ -71,11 +77,22 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
private boolean loadControlRegistered; private boolean loadControlRegistered;
private int trackCount; private int trackCount;
private int enabledTrackCount; private int enabledTrackCount;
private Format downstreamFormat;
// Tracks are complicated in HLS. See documentation of buildTracks for details.
// Indexed by track (as exposed by this source).
private MediaFormat[] trackFormats;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
private boolean[] pendingDiscontinuities; private boolean[] pendingDiscontinuities;
private MediaFormat[] trackFormat;
private MediaFormat[] downstreamMediaFormats; private MediaFormat[] downstreamMediaFormats;
private Format downstreamFormat; // Maps track index (as exposed by this source) to the corresponding chunk source track index for
// primary tracks, or to -1 otherwise.
private int[] chunkSourceTrackIndices;
// Maps track index (as exposed by this source) to the corresponding extractor track index.
private int[] extractorTrackIndices;
// Indexed by extractor track index.
private boolean[] extractorTrackEnabledStates;
private long downstreamPositionUs; private long downstreamPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
@ -137,20 +154,9 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
// We're not prepared, but we might have loaded what we need. // We're not prepared, but we might have loaded what we need.
HlsExtractorWrapper extractor = extractors.getFirst(); HlsExtractorWrapper extractor = extractors.getFirst();
if (extractor.isPrepared()) { if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount(); buildTracks(extractor);
trackEnabledStates = new boolean[trackCount];
pendingDiscontinuities = new boolean[trackCount];
downstreamMediaFormats = new MediaFormat[trackCount];
trackFormat = new MediaFormat[trackCount];
long durationUs = chunkSource.getDurationUs();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (MimeTypes.isVideo(format.mimeType)) {
format = format.copyAsAdaptive(null);
}
trackFormat[i] = format;
}
prepared = true; prepared = true;
maybeStartLoading(); // Update the load control.
return true; return true;
} else if (extractors.size() > 1) { } else if (extractors.size() > 1) {
extractors.removeFirst().clear(); extractors.removeFirst().clear();
@ -185,15 +191,13 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
@Override @Override
public MediaFormat getFormat(int track) { public MediaFormat getFormat(int track) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
return trackFormat[track]; return trackFormats[track];
} }
@Override @Override
public void enable(int track, long positionUs) { public void enable(int track, long positionUs) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Assertions.checkState(!trackEnabledStates[track]); setTrackEnabledState(track, true);
enabledTrackCount++;
trackEnabledStates[track] = true;
downstreamMediaFormats[track] = null; downstreamMediaFormats[track] = null;
pendingDiscontinuities[track] = false; pendingDiscontinuities[track] = false;
downstreamFormat = null; downstreamFormat = null;
@ -202,6 +206,16 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
loadControl.register(this, bufferSizeContribution); loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true; loadControlRegistered = true;
} }
int chunkSourceTrack = chunkSourceTrackIndices[track];
if (chunkSourceTrack != -1 && chunkSourceTrack != chunkSource.getSelectedTrackIndex()) {
// This is a primary track whose corresponding chunk source track is different to the one
// currently selected. We need to change the selection and restart. Since other exposed tracks
// may be enabled too, we need to implement the restart as a seek so that all downstream
// renderers receive a discontinuity event.
chunkSource.selectTrack(chunkSourceTrack);
seekToInternal(positionUs);
return;
}
if (enabledTrackCount == 1) { if (enabledTrackCount == 1) {
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
if (wasLoadControlRegistered && downstreamPositionUs == positionUs) { if (wasLoadControlRegistered && downstreamPositionUs == positionUs) {
@ -220,9 +234,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
@Override @Override
public void disable(int track) { public void disable(int track) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Assertions.checkState(trackEnabledStates[track]); setTrackEnabledState(track, false);
enabledTrackCount--;
trackEnabledStates[track] = false;
if (enabledTrackCount == 0) { if (enabledTrackCount == 0) {
chunkSource.reset(); chunkSource.reset();
downstreamPositionUs = Long.MIN_VALUE; downstreamPositionUs = Long.MIN_VALUE;
@ -259,7 +271,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
break; break;
} }
if (extractor.hasSamples(track)) { int extractorTrack = extractorTrackIndices[track];
if (extractor.hasSamples(extractorTrack)) {
return true; return true;
} }
} }
@ -302,8 +315,9 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
extractor.configureSpliceTo(extractors.get(1)); extractor.configureSpliceTo(extractors.get(1));
} }
int extractorTrack = extractorTrackIndices[track];
int extractorIndex = 0; int extractorIndex = 0;
while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(track)) { while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(extractorTrack)) {
// We're finished reading from the extractor for this particular track, so advance to the // We're finished reading from the extractor for this particular track, so advance to the
// next one for the current read. // next one for the current read.
extractor = extractors.get(++extractorIndex); extractor = extractors.get(++extractorIndex);
@ -312,14 +326,14 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
} }
} }
MediaFormat mediaFormat = extractor.getMediaFormat(track); MediaFormat mediaFormat = extractor.getMediaFormat(extractorTrack);
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track])) { if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track])) {
formatHolder.format = mediaFormat; formatHolder.format = mediaFormat;
downstreamMediaFormats[track] = mediaFormat; downstreamMediaFormats[track] = mediaFormat;
return FORMAT_READ; return FORMAT_READ;
} }
if (extractor.getSample(track, sampleHolder)) { if (extractor.getSample(extractorTrack, sampleHolder)) {
boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs;
sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0; sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0;
return SAMPLE_READ; return SAMPLE_READ;
@ -346,6 +360,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
Assertions.checkState(prepared); Assertions.checkState(prepared);
Assertions.checkState(enabledTrackCount > 0); Assertions.checkState(enabledTrackCount > 0);
// Ignore seeks to the current position.
long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs;
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
@ -353,13 +368,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return; return;
} }
// TODO: Optimize the seek for the case where the position is already buffered. seekToInternal(positionUs);
downstreamPositionUs = positionUs;
for (int i = 0; i < pendingDiscontinuities.length; i++) {
pendingDiscontinuities[i] = true;
}
chunkSource.seek();
restartFrom(positionUs);
} }
@Override @Override
@ -448,6 +457,146 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
// Internal stuff. // Internal stuff.
/**
* Builds tracks that are exposed by this {@link HlsSampleSource} instance, as well as internal
* data-structures required for operation.
* <p>
* 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
* and caption tracks. We wish to allow the user to select between an adaptive track that spans
* all variants, as well as each individual variant. If multiple audio tracks are present within
* each variant then we wish to allow the user to select between those also.
* <p>
* To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks,
* where N is the number of variants defined in the HLS master playlist. These consist of one
* 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}:
* <ul>
* <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.
* Else text is the primary type if present. Else there is no primary type.</li>
* <li>If there is exactly one extractor track of the primary type, it's expanded into (N+1)
* exposed tracks, all of which correspond to the primary extractor track and each of which
* corresponds to a different chunk source track. Selecting one of these tracks has the effect
* of switching the selected track on the chunk source.</li>
* <li>All other extractor tracks are exposed directly. Selecting one of these tracks has the
* effect of selecting an extractor track, leaving the selected track on the chunk source
* unchanged.</li>
* </ul>
*
* @param extractor The prepared extractor.
*/
private void buildTracks(HlsExtractorWrapper extractor) {
// Iterate through the extractor tracks to discover the "primary" track type, and the index
// of the single track of this type.
int primaryExtractorTrackType = PRIMARY_TYPE_NONE;
int primaryExtractorTrackIndex = -1;
int extractorTrackCount = extractor.getTrackCount();
for (int i = 0; i < extractorTrackCount; i++) {
String mimeType = extractor.getMediaFormat(i).mimeType;
int trackType;
if (MimeTypes.isVideo(mimeType)) {
trackType = PRIMARY_TYPE_VIDEO;
} else if (MimeTypes.isAudio(mimeType)) {
trackType = PRIMARY_TYPE_AUDIO;
} else if (MimeTypes.isText(mimeType)) {
trackType = PRIMARY_TYPE_TEXT;
} else {
trackType = PRIMARY_TYPE_NONE;
}
if (trackType > primaryExtractorTrackType) {
primaryExtractorTrackType = trackType;
primaryExtractorTrackIndex = i;
} else if (trackType == primaryExtractorTrackType && primaryExtractorTrackIndex != -1) {
// We have multiple tracks of the primary type. We only want an index if there only
// exists a single track of the primary type, so set the index back to -1.
primaryExtractorTrackIndex = -1;
}
}
// Calculate the number of tracks that will be exposed.
int chunkSourceTrackCount = chunkSource.getTrackCount();
boolean expandPrimaryExtractorTrack = primaryExtractorTrackIndex != -1;
trackCount = extractorTrackCount;
if (expandPrimaryExtractorTrack) {
trackCount += chunkSourceTrackCount - 1;
}
// Instantiate the necessary internal data-structures.
trackFormats = new MediaFormat[trackCount];
trackEnabledStates = new boolean[trackCount];
pendingDiscontinuities = new boolean[trackCount];
downstreamMediaFormats = new MediaFormat[trackCount];
chunkSourceTrackIndices = new int[trackCount];
extractorTrackIndices = new int[trackCount];
extractorTrackEnabledStates = new boolean[extractorTrackCount];
// Construct the set of exposed tracks.
long durationUs = chunkSource.getDurationUs();
int trackIndex = 0;
for (int i = 0; i < extractorTrackCount; i++) {
MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (i == primaryExtractorTrackIndex) {
for (int j = 0; j < chunkSourceTrackCount; j++) {
extractorTrackIndices[trackIndex] = i;
chunkSourceTrackIndices[trackIndex] = j;
Variant fixedTrackVariant = chunkSource.getFixedTrackVariant(j);
trackFormats[trackIndex++] = fixedTrackVariant == null ? format.copyAsAdaptive(null)
: copyWithFixedTrackInfo(format, fixedTrackVariant.format);
}
} else {
extractorTrackIndices[trackIndex] = i;
chunkSourceTrackIndices[trackIndex] = -1;
trackFormats[trackIndex++] = format;
}
}
}
/**
* Enables or disables the track at a given index.
*
* @param track The index of the track.
* @param enabledState True if the track is being enabled, or false if it's being disabled.
*/
private void setTrackEnabledState(int track, boolean enabledState) {
Assertions.checkState(trackEnabledStates[track] != enabledState);
int extractorTrack = extractorTrackIndices[track];
Assertions.checkState(extractorTrackEnabledStates[extractorTrack] != enabledState);
trackEnabledStates[track] = enabledState;
extractorTrackEnabledStates[extractorTrack] = enabledState;
enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1);
}
/**
* Copies a provided {@link MediaFormat}, incorporating information from the {@link Format} of
* a fixed (i.e. non-adaptive) track.
*
* @param format The {@link MediaFormat} to copy.
* @param fixedTrackFormat The {@link Format} to incorporate into the copy.
* @return The copied {@link MediaFormat}.
*/
private static MediaFormat copyWithFixedTrackInfo(MediaFormat format, Format fixedTrackFormat) {
int width = fixedTrackFormat.width == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.width;
int height = fixedTrackFormat.height == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.height;
return format.copyWithFixedTrackInfo(fixedTrackFormat.id, fixedTrackFormat.bitrate, width,
height, fixedTrackFormat.language);
}
/**
* Performs a seek. The operation is performed even if the seek is to the current position.
*
* @param positionUs The position to seek to.
*/
private void seekToInternal(long positionUs) {
lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs;
Arrays.fill(pendingDiscontinuities, true);
chunkSource.seek();
restartFrom(positionUs);
}
/** /**
* Gets the current extractor from which samples should be read. * Gets the current extractor from which samples should be read.
* <p> * <p>
@ -472,8 +621,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
return; return;
} }
for (int i = 0; i < trackEnabledStates.length; i++) { for (int i = 0; i < extractorTrackEnabledStates.length; i++) {
if (!trackEnabledStates[i]) { if (!extractorTrackEnabledStates[i]) {
extractor.discardUntil(i, timeUs); extractor.discardUntil(i, timeUs);
} }
} }
@ -483,8 +632,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
return false; return false;
} }
for (int i = 0; i < trackEnabledStates.length; i++) { for (int i = 0; i < extractorTrackEnabledStates.length; i++) {
if (trackEnabledStates[i] && extractor.hasSamples(i)) { if (extractorTrackEnabledStates[i] && extractor.hasSamples(i)) {
return true; return true;
} }
} }