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:
parent
00f8deda3d
commit
ab9e3bac46
@ -19,14 +19,12 @@ import com.google.android.exoplayer.DefaultLoadControl;
|
|||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
import com.google.android.exoplayer.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
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.demo.player.DemoPlayer.RendererBuilder;
|
||||||
|
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;
|
||||||
@ -130,25 +128,10 @@ public class HlsRendererBuilder implements RendererBuilder {
|
|||||||
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();
|
||||||
|
|
||||||
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);
|
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
|
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest,
|
||||||
new PtsTimestampAdjusterProvider(), variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter,
|
||||||
|
new PtsTimestampAdjusterProvider(), 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);
|
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -39,7 +39,6 @@ import com.google.android.exoplayer.util.Util;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -47,17 +46,15 @@ import java.io.IOException;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A temporary test source of HLS chunks.
|
* 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.
|
* 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 TAG = "HlsChunkSource";
|
||||||
private static final String AAC_FILE_EXTENSION = ".aac";
|
private static final String AAC_FILE_EXTENSION = ".aac";
|
||||||
private static final String MP3_FILE_EXTENSION = ".mp3";
|
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 static final float BANDWIDTH_FRACTION = 0.8f;
|
||||||
|
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final HlsPlaylistParser playlistParser;
|
private final HlsPlaylistParser playlistParser;
|
||||||
|
private final HlsMasterPlaylist masterPlaylist;
|
||||||
|
private final HlsTrackSelector trackSelector;
|
||||||
private final BandwidthMeter bandwidthMeter;
|
private final BandwidthMeter bandwidthMeter;
|
||||||
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
private final int adaptiveMode;
|
private final int adaptiveMode;
|
||||||
private final String baseUri;
|
private final String baseUri;
|
||||||
private final int adaptiveMaxWidth;
|
|
||||||
private final int adaptiveMaxHeight;
|
|
||||||
private final long minBufferDurationToSwitchUpUs;
|
private final long minBufferDurationToSwitchUpUs;
|
||||||
private final long maxBufferDurationToSwitchDownUs;
|
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
|
// 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],
|
||||||
// variantLastPlaylistLoadTimesMs[i] and variantBlacklistTimes[i] all correspond to variants[i]).
|
// variantLastPlaylistLoadTimesMs[i] and variantBlacklistTimes[i] all correspond to variants[i]).
|
||||||
private final Variant[] variants;
|
private Variant[] variants;
|
||||||
private final HlsMediaPlaylist[] variantPlaylists;
|
private HlsMediaPlaylist[] variantPlaylists;
|
||||||
private final long[] variantLastPlaylistLoadTimesMs;
|
private long[] variantLastPlaylistLoadTimesMs;
|
||||||
private final long[] variantBlacklistTimes;
|
private long[] variantBlacklistTimes;
|
||||||
|
|
||||||
// The index in variants of the currently selected variant.
|
// The index in variants of the currently selected variant.
|
||||||
private int selectedVariantIndex;
|
private int selectedVariantIndex;
|
||||||
|
|
||||||
|
private boolean prepareCalled;
|
||||||
private byte[] scratchSpace;
|
private byte[] scratchSpace;
|
||||||
private boolean live;
|
private boolean live;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -156,22 +160,20 @@ public class HlsChunkSource {
|
|||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param playlistUrl The playlist URL.
|
* @param playlistUrl The playlist URL.
|
||||||
* @param playlist The hls playlist.
|
* @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 bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||||
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
* same provider.
|
* 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
|
* @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_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE}.
|
* {@link #ADAPTIVE_MODE_SPLICE}.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
||||||
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
|
HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter,
|
||||||
int[] variantIndices, int adaptiveMode) {
|
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) {
|
||||||
this(dataSource, playlistUrl, playlist, bandwidthMeter, timestampAdjusterProvider,
|
this(dataSource, playlistUrl, playlist, trackSelector, bandwidthMeter,
|
||||||
variantIndices, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS,
|
timestampAdjusterProvider, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS,
|
||||||
DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_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 dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param playlistUrl The playlist URL.
|
* @param playlistUrl The playlist URL.
|
||||||
* @param playlist The hls playlist.
|
* @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 bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||||
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
* same provider.
|
* 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
|
* @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_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE}.
|
* {@link #ADAPTIVE_MODE_SPLICE}.
|
||||||
@ -195,10 +195,11 @@ public class HlsChunkSource {
|
|||||||
* for a switch to a lower quality variant to be considered.
|
* for a switch to a lower quality variant to be considered.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
||||||
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
|
HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter,
|
||||||
int[] variantIndices, int adaptiveMode, long minBufferDurationToSwitchUpMs,
|
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode,
|
||||||
long maxBufferDurationToSwitchDownMs) {
|
long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
|
this.trackSelector = trackSelector;
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
this.bandwidthMeter = bandwidthMeter;
|
||||||
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
||||||
this.adaptiveMode = adaptiveMode;
|
this.adaptiveMode = adaptiveMode;
|
||||||
@ -206,48 +207,18 @@ public class HlsChunkSource {
|
|||||||
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
|
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
|
||||||
baseUri = playlist.baseUri;
|
baseUri = playlist.baseUri;
|
||||||
playlistParser = new HlsPlaylistParser();
|
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,
|
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
|
||||||
null);
|
null);
|
||||||
variants = new Variant[] {new Variant(playlistUrl, format)};
|
List<Variant> variants = new ArrayList<>();
|
||||||
variantPlaylists = new HlsMediaPlaylist[1];
|
variants.add(new Variant(playlistUrl, format));
|
||||||
variantLastPlaylistLoadTimesMs = new long[1];
|
masterPlaylist = new HlsMasterPlaylist(playlistUrl, variants,
|
||||||
variantBlacklistTimes = new long[1];
|
Collections.<Variant>emptyList());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
|
||||||
* be performed by the calling {@link HlsSampleSource}.
|
* be performed by the calling {@link HlsSampleSource}.
|
||||||
@ -374,17 +369,29 @@ public class HlsChunkSource {
|
|||||||
|
|
||||||
// Configure the extractor that will read the chunk.
|
// Configure the extractor that will read the chunk.
|
||||||
HlsExtractorWrapper extractorWrapper;
|
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
|
// 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
|
// identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
|
||||||
// case below.
|
// case below.
|
||||||
Extractor extractor = new AdtsExtractor(startTimeUs);
|
Extractor extractor = new AdtsExtractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
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);
|
Extractor extractor = new Mp3Extractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
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
|
} else if (previousTsChunk == null
|
||||||
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
||||||
|| !format.equals(previousTsChunk.format)) {
|
|| !format.equals(previousTsChunk.format)) {
|
||||||
@ -393,7 +400,7 @@ public class HlsChunkSource {
|
|||||||
segment.discontinuitySequenceNumber, startTimeUs);
|
segment.discontinuitySequenceNumber, startTimeUs);
|
||||||
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, adaptiveMaxWidth, adaptiveMaxHeight);
|
switchingVariantSpliced, enabledTrack.adaptiveMaxWidth, enabledTrack.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;
|
||||||
@ -479,6 +486,48 @@ public class HlsChunkSource {
|
|||||||
fatalError = null;
|
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) {
|
private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) {
|
||||||
clearStaleBlacklistedVariants();
|
clearStaleBlacklistedVariants();
|
||||||
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
|
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
|
||||||
@ -596,78 +645,6 @@ public class HlsChunkSource {
|
|||||||
durationUs = live ? C.UNKNOWN_TIME_US : mediaPlaylist.durationUs;
|
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() {
|
private boolean allVariantsBlacklisted() {
|
||||||
for (int i = 0; i < variantBlacklistTimes.length; i++) {
|
for (int i = 0; i < variantBlacklistTimes.length; i++) {
|
||||||
if (variantBlacklistTimes[i] == 0) {
|
if (variantBlacklistTimes[i] == 0) {
|
||||||
@ -697,7 +674,34 @@ public class HlsChunkSource {
|
|||||||
throw new IllegalStateException("Invalid format: " + format);
|
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;
|
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 String iv;
|
||||||
public final int variantIndex;
|
public final int variantIndex;
|
||||||
|
@ -129,6 +129,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
|
|||||||
public boolean prepare(long positionUs) {
|
public boolean prepare(long positionUs) {
|
||||||
if (prepared) {
|
if (prepared) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (!chunkSource.prepare()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (!extractors.isEmpty()) {
|
if (!extractors.isEmpty()) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user