Introduce HlsTrackSelector.

This is equivalent to DashTrackSelector and SmoothStreamingTrackSelector.
This is a step toward allowing HlsChunkSource to expose multiple tracks,
which is a requirement for supporting WebVtt.

This change also enables WebVtt extractor instantiation.

Issue: #151
Issue: #676
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=111698833
This commit is contained in:
olly 2016-01-08 08:55:12 -08:00 committed by Oliver Woodman
parent 00f8deda3d
commit ab9e3bac46
5 changed files with 340 additions and 161 deletions

View File

@ -19,14 +19,12 @@ import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecSelector;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.hls.DefaultHlsTrackSelector;
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.HlsPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
@ -130,25 +128,10 @@ public class HlsRendererBuilder implements RendererBuilder {
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
int[] variantIndices = null;
if (manifest instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) manifest;
try {
variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, masterPlaylist.variants, null, false);
} catch (DecoderQueryException e) {
player.onRenderersError(e);
return;
}
if (variantIndices.length == 0) {
player.onRenderersError(new IllegalStateException("No variants selected."));
return;
}
}
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
new PtsTimestampAdjusterProvider(), variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest,
DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter,
new PtsTimestampAdjusterProvider(), HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,

View File

@ -0,0 +1,131 @@
/*
* 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.chunk.VideoFormatSelectorUtil;
import android.content.Context;
import android.text.TextUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A default {@link HlsTrackSelector} implementation.
*/
public final class DefaultHlsTrackSelector implements HlsTrackSelector {
private static final int TYPE_DEFAULT = 0;
private static final int TYPE_VTT = 1;
private final Context context;
private final int type;
/**
* Creates a {@link DefaultHlsTrackSelector} that selects the streams defined in the playlist.
*
* @param context A context.
* @return The selector instance.
*/
public static DefaultHlsTrackSelector newDefaultInstance(Context context) {
return new DefaultHlsTrackSelector(context, TYPE_DEFAULT);
}
/**
* Creates a {@link DefaultHlsTrackSelector} that selects subtitle renditions.
*
* @return The selector instance.
*/
public static DefaultHlsTrackSelector newVttInstance() {
return new DefaultHlsTrackSelector(null, TYPE_VTT);
}
private DefaultHlsTrackSelector(Context context, int type) {
this.context = context;
this.type = type;
}
@Override
public void selectTracks(HlsMasterPlaylist playlist, Output output) throws IOException {
if (type == TYPE_VTT) {
List<Variant> subtitleVariants = playlist.subtitles;
if (subtitleVariants != null && !subtitleVariants.isEmpty()) {
for (int i = 0; i < subtitleVariants.size(); i++) {
output.fixedTrack(playlist, subtitleVariants.get(i));
}
}
return;
}
// Type is TYPE_DEFAULT.
ArrayList<Variant> enabledVariantList = new ArrayList<>();
int[] variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, playlist.variants, null, false);
for (int i = 0; i < variantIndices.length; i++) {
enabledVariantList.add(playlist.variants.get(variantIndices[i]));
}
ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
for (int i = 0; i < enabledVariantList.size(); i++) {
Variant variant = enabledVariantList.get(i);
if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) {
definiteVideoVariants.add(variant);
} else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) {
definiteAudioOnlyVariants.add(variant);
}
}
if (!definiteVideoVariants.isEmpty()) {
// We've identified some variants as definitely containing video. Assume variants within the
// master playlist are marked consistently, and hence that we have the full set. Filter out
// any other variants, which are likely to be audio only.
enabledVariantList = definiteVideoVariants;
} else if (definiteAudioOnlyVariants.size() < enabledVariantList.size()) {
// We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video.
enabledVariantList.removeAll(definiteAudioOnlyVariants);
} else {
// Leave the enabled variants unchanged. They're likely either all video or all audio.
}
if (enabledVariantList.size() > 1) {
Variant[] enabledVariants = new Variant[enabledVariantList.size()];
enabledVariantList.toArray(enabledVariants);
output.adaptiveTrack(playlist, enabledVariants);
}
for (int i = 0; i < enabledVariantList.size(); i++) {
output.fixedTrack(playlist, enabledVariantList.get(i));
}
}
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.format.codecs;
if (TextUtils.isEmpty(codecs)) {
return false;
}
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
for (int i = 0; i < codecArray.length; i++) {
if (codecArray[i].startsWith(prefix)) {
return true;
}
}
return false;
}
}

View File

@ -39,7 +39,6 @@ import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayInputStream;
@ -47,17 +46,15 @@ import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
/**
* A temporary test source of HLS chunks.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public class HlsChunkSource {
public class HlsChunkSource implements HlsTrackSelector.Output {
/**
* Interface definition for a callback to be notified of {@link HlsChunkSource} events.
@ -118,30 +115,37 @@ public class HlsChunkSource {
private static final String TAG = "HlsChunkSource";
private static final String AAC_FILE_EXTENSION = ".aac";
private static final String MP3_FILE_EXTENSION = ".mp3";
private static final String VTT_FILE_EXTENSION = ".webvtt";
private static final float BANDWIDTH_FRACTION = 0.8f;
private final DataSource dataSource;
private final HlsPlaylistParser playlistParser;
private final HlsMasterPlaylist masterPlaylist;
private final HlsTrackSelector trackSelector;
private final BandwidthMeter bandwidthMeter;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final int adaptiveMode;
private final String baseUri;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private final long minBufferDurationToSwitchUpUs;
private final long maxBufferDurationToSwitchDownUs;
// TODO: Expose tracks.
private final ArrayList<ExposedTrack> tracks;
private ExposedTrack enabledTrack;
// 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],
// variantLastPlaylistLoadTimesMs[i] and variantBlacklistTimes[i] all correspond to variants[i]).
private final Variant[] variants;
private final HlsMediaPlaylist[] variantPlaylists;
private final long[] variantLastPlaylistLoadTimesMs;
private final long[] variantBlacklistTimes;
private Variant[] variants;
private HlsMediaPlaylist[] variantPlaylists;
private long[] variantLastPlaylistLoadTimesMs;
private long[] variantBlacklistTimes;
// The index in variants of the currently selected variant.
private int selectedVariantIndex;
private boolean prepareCalled;
private byte[] scratchSpace;
private boolean live;
private long durationUs;
@ -156,22 +160,20 @@ public class HlsChunkSource {
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param playlistUrl The playlist URL.
* @param playlist The hls playlist.
* @param trackSelector Selects tracks to be exposed by this source.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
* same provider.
* @param variantIndices If {@code playlist} is a {@link HlsMasterPlaylist}, the subset of variant
* indices to consider, or null to consider all of the variants. For other playlist types
* this parameter is ignored.
* @param adaptiveMode The mode for switching from one variant to another. One of
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
* {@link #ADAPTIVE_MODE_SPLICE}.
*/
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
int[] variantIndices, int adaptiveMode) {
this(dataSource, playlistUrl, playlist, bandwidthMeter, timestampAdjusterProvider,
variantIndices, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS,
HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter,
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) {
this(dataSource, playlistUrl, playlist, trackSelector, bandwidthMeter,
timestampAdjusterProvider, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS,
DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS);
}
@ -179,13 +181,11 @@ public class HlsChunkSource {
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param playlistUrl The playlist URL.
* @param playlist The hls playlist.
* @param trackSelector Selects tracks to be exposed by this source.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
* same provider.
* @param variantIndices If {@code playlist} is a {@link HlsMasterPlaylist}, the subset of variant
* indices to consider, or null to consider all of the variants. For other playlist types
* this parameter is ignored.
* @param adaptiveMode The mode for switching from one variant to another. One of
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
* {@link #ADAPTIVE_MODE_SPLICE}.
@ -195,10 +195,11 @@ public class HlsChunkSource {
* for a switch to a lower quality variant to be considered.
*/
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
int[] variantIndices, int adaptiveMode, long minBufferDurationToSwitchUpMs,
long maxBufferDurationToSwitchDownMs) {
HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter,
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode,
long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) {
this.dataSource = dataSource;
this.trackSelector = trackSelector;
this.bandwidthMeter = bandwidthMeter;
this.timestampAdjusterProvider = timestampAdjusterProvider;
this.adaptiveMode = adaptiveMode;
@ -206,48 +207,18 @@ public class HlsChunkSource {
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
baseUri = playlist.baseUri;
playlistParser = new HlsPlaylistParser();
tracks = new ArrayList<>();
if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
masterPlaylist = (HlsMasterPlaylist) playlist;
} else {
// TODO: Infer the mime type from a chunk file extension.
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
null);
variants = new Variant[] {new Variant(playlistUrl, format)};
variantPlaylists = new HlsMediaPlaylist[1];
variantLastPlaylistLoadTimesMs = new long[1];
variantBlacklistTimes = new long[1];
setMediaPlaylist(0, (HlsMediaPlaylist) playlist);
// We won't be adapting between different variants.
adaptiveMaxWidth = MediaFormat.NO_VALUE;
adaptiveMaxHeight = MediaFormat.NO_VALUE;
} else {
List<Variant> masterPlaylistVariants = ((HlsMasterPlaylist) playlist).variants;
variants = buildOrderedVariants(masterPlaylistVariants, variantIndices);
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
variantBlacklistTimes = new long[variants.length];
int maxWidth = -1;
int maxHeight = -1;
// Select the variant that comes first in their original order in the master playlist.
int minOriginalVariantIndex = Integer.MAX_VALUE;
for (int i = 0; i < variants.length; i++) {
int originalVariantIndex = masterPlaylistVariants.indexOf(variants[i]);
if (originalVariantIndex < minOriginalVariantIndex) {
minOriginalVariantIndex = originalVariantIndex;
selectedVariantIndex = i;
}
Format variantFormat = variants[i].format;
maxWidth = Math.max(variantFormat.width, maxWidth);
maxHeight = Math.max(variantFormat.height, maxHeight);
}
if (variants.length <= 1 || adaptiveMode == ADAPTIVE_MODE_NONE) {
// We won't be adapting between different variants.
this.adaptiveMaxWidth = MediaFormat.NO_VALUE;
this.adaptiveMaxHeight = MediaFormat.NO_VALUE;
} else {
// We will be adapting between different variants.
// TODO: We should allow the default values to be passed through the constructor.
this.adaptiveMaxWidth = maxWidth > 0 ? maxWidth : 1920;
this.adaptiveMaxHeight = maxHeight > 0 ? maxHeight : 1080;
}
List<Variant> variants = new ArrayList<>();
variants.add(new Variant(playlistUrl, format));
masterPlaylist = new HlsMasterPlaylist(playlistUrl, variants,
Collections.<Variant>emptyList());
}
}
@ -267,6 +238,30 @@ public class HlsChunkSource {
}
}
/**
* Prepares the source.
*
* @return True if the source was prepared, false otherwise.
*/
public boolean prepare() {
if (!prepareCalled) {
prepareCalled = true;
try {
trackSelector.selectTracks(masterPlaylist, this);
// The following 5 lines are temporarily located here until the tracks are exposed.
enabledTrack = tracks.get(0);
selectedVariantIndex = enabledTrack.defaultVariantIndex;
variants = enabledTrack.variants;
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
variantBlacklistTimes = new long[variants.length];
} catch (IOException e) {
fatalError = e;
}
}
return fatalError == null;
}
/**
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link HlsSampleSource}.
@ -374,17 +369,29 @@ public class HlsChunkSource {
// Configure the extractor that will read the chunk.
HlsExtractorWrapper extractorWrapper;
if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) {
String lastPathSegment = chunkUri.getLastPathSegment();
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
// TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
// case below.
Extractor extractor = new AdtsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
} else if (chunkUri.getLastPathSegment().endsWith(MP3_FILE_EXTENSION)) {
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
Extractor extractor = new Mp3Extractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
} else if (lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
segment.discontinuitySequenceNumber, startTimeUs);
if (timestampAdjuster == null) {
// The master source has yet to instantiate an adjuster for the discontinuity sequence.
// TODO: Allow VTT to be the master in the case that this is the only chunks source.
return;
}
Extractor extractor = new WebvttExtractor(timestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
} else if (previousTsChunk == null
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| !format.equals(previousTsChunk.format)) {
@ -393,7 +400,7 @@ public class HlsChunkSource {
segment.discontinuitySequenceNumber, startTimeUs);
Extractor extractor = new TsExtractor(timestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
switchingVariantSpliced, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight);
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractorWrapper = previousTsChunk.extractorWrapper;
@ -479,6 +486,48 @@ public class HlsChunkSource {
fatalError = null;
}
// HlsTrackSelector.Output implementation.
@Override
public void adaptiveTrack(HlsMasterPlaylist playlist, Variant[] variants) {
Arrays.sort(variants, new Comparator<Variant>() {
private final Comparator<Format> formatComparator =
new Format.DecreasingBandwidthComparator();
@Override
public int compare(Variant first, Variant second) {
return formatComparator.compare(first.format, second.format);
}
});
int defaultVariantIndex = 0;
int maxWidth = -1;
int maxHeight = -1;
int minOriginalVariantIndex = Integer.MAX_VALUE;
for (int i = 0; i < variants.length; i++) {
int originalVariantIndex = playlist.variants.indexOf(variants[i]);
if (originalVariantIndex < minOriginalVariantIndex) {
minOriginalVariantIndex = originalVariantIndex;
defaultVariantIndex = i;
}
Format variantFormat = variants[i].format;
maxWidth = Math.max(variantFormat.width, maxWidth);
maxHeight = Math.max(variantFormat.height, maxHeight);
}
// TODO: We should allow the default values to be passed through the constructor.
// TODO: Print a warning if resolution tags are omitted.
maxWidth = maxWidth > 0 ? maxWidth : 1920;
maxHeight = maxHeight > 0 ? maxHeight : 1080;
tracks.add(new ExposedTrack(variants, defaultVariantIndex, maxWidth, maxHeight));
}
@Override
public void fixedTrack(HlsMasterPlaylist playlist, Variant variant) {
tracks.add(new ExposedTrack(variant));
}
// Private methods.
private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) {
clearStaleBlacklistedVariants();
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
@ -596,78 +645,6 @@ public class HlsChunkSource {
durationUs = live ? C.UNKNOWN_TIME_US : mediaPlaylist.durationUs;
}
/**
* Selects a list of variants to use, returning them in order of decreasing bandwidth.
*
* @param originalVariants The original list of variants.
* @param originalVariantIndices Indices of variants that in the original list that can be
* considered, or null to allow all variants to be considered.
* @return The set of enabled variants in decreasing bandwidth order.
*/
private static Variant[] buildOrderedVariants(List<Variant> originalVariants,
int[] originalVariantIndices) {
ArrayList<Variant> enabledVariantList = new ArrayList<>();
if (originalVariantIndices != null) {
for (int i = 0; i < originalVariantIndices.length; i++) {
enabledVariantList.add(originalVariants.get(originalVariantIndices[i]));
}
} else {
// If variantIndices is null then all variants are initially considered.
enabledVariantList.addAll(originalVariants);
}
ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
for (int i = 0; i < enabledVariantList.size(); i++) {
Variant variant = enabledVariantList.get(i);
if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) {
definiteVideoVariants.add(variant);
} else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) {
definiteAudioOnlyVariants.add(variant);
}
}
if (!definiteVideoVariants.isEmpty()) {
// We've identified some variants as definitely containing video. Assume variants within the
// master playlist are marked consistently, and hence that we have the full set. Filter out
// any other variants, which are likely to be audio only.
enabledVariantList = definiteVideoVariants;
} else if (definiteAudioOnlyVariants.size() < enabledVariantList.size()) {
// We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video.
enabledVariantList.removeAll(definiteAudioOnlyVariants);
} else {
// Leave the enabled variants unchanged. They're likely either all video or all audio.
}
Variant[] enabledVariants = new Variant[enabledVariantList.size()];
enabledVariantList.toArray(enabledVariants);
Arrays.sort(enabledVariants, new Comparator<Variant>() {
private final Comparator<Format> formatComparator =
new Format.DecreasingBandwidthComparator();
@Override
public int compare(Variant first, Variant second) {
return formatComparator.compare(first.format, second.format);
}
});
return enabledVariants;
}
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.format.codecs;
if (TextUtils.isEmpty(codecs)) {
return false;
}
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
for (int i = 0; i < codecArray.length; i++) {
if (codecArray[i].startsWith(prefix)) {
return true;
}
}
return false;
}
private boolean allVariantsBlacklisted() {
for (int i = 0; i < variantBlacklistTimes.length; i++) {
if (variantBlacklistTimes[i] == 0) {
@ -697,7 +674,34 @@ public class HlsChunkSource {
throw new IllegalStateException("Invalid format: " + format);
}
private static class MediaPlaylistChunk extends DataChunk {
// Private classes.
private static final class ExposedTrack {
private final Variant[] variants;
private final int defaultVariantIndex;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
public ExposedTrack(Variant fixedVariant) {
this.variants = new Variant[] {fixedVariant};
this.defaultVariantIndex = 0;
this.adaptiveMaxWidth = MediaFormat.NO_VALUE;
this.adaptiveMaxHeight = MediaFormat.NO_VALUE;
}
public ExposedTrack(Variant[] adaptiveVariants, int defaultVariantIndex, int maxWidth,
int maxHeight) {
this.variants = adaptiveVariants;
this.defaultVariantIndex = defaultVariantIndex;
this.adaptiveMaxWidth = maxWidth;
this.adaptiveMaxHeight = maxHeight;
}
}
private static final class MediaPlaylistChunk extends DataChunk {
public final int variantIndex;
@ -727,7 +731,7 @@ public class HlsChunkSource {
}
private static class EncryptionKeyChunk extends DataChunk {
private static final class EncryptionKeyChunk extends DataChunk {
public final String iv;
public final int variantIndex;

View File

@ -129,6 +129,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
public boolean prepare(long positionUs) {
if (prepared) {
return true;
} else if (!chunkSource.prepare()) {
return false;
}
if (!extractors.isEmpty()) {
while (true) {

View File

@ -0,0 +1,59 @@
/*
* 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 java.io.IOException;
/**
* Specifies a track selection from an {@link HlsMasterPlaylist}.
*/
public interface HlsTrackSelector {
/**
* Defines a selector output.
*/
interface Output {
/**
* Outputs an adaptive track, covering the specified representations in the specified
* adaptation set.
*
* @param playlist The master playlist being processed.
* @param variants The variants to use for the adaptive track.
*/
void adaptiveTrack(HlsMasterPlaylist playlist, Variant[] variants);
/**
* Outputs an fixed track corresponding to the specified representation in the specified
* adaptation set.
*
* @param playlist The master playlist being processed.
* @param variant The variant to use for the track.
*/
void fixedTrack(HlsMasterPlaylist playlist, Variant variant);
}
/**
* Outputs a track selection for a given period.
*
* @param playlist The master playlist to process.
* @param output The output to receive tracks.
* @throws IOException If an error occurs processing the period.
*/
void selectTracks(HlsMasterPlaylist playlist, Output output) throws IOException;
}