Commit 1eff6cf2 by olly Committed by Oliver Woodman

Bring back multi-audio and VTT support for HLS.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117338865
parent 1d4305cb
......@@ -428,14 +428,14 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
@Override
public void onCues(List<Cue> cues) {
if (captionListener != null && trackInfo.getTrackSelection(TYPE_TEXT) != null) {
if (captionListener != null) {
captionListener.onCues(cues);
}
}
@Override
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null && trackInfo.getTrackSelection(TYPE_METADATA) != null) {
if (id3MetadataListener != null) {
id3MetadataListener.onId3Metadata(id3Frames);
}
}
......
......@@ -17,7 +17,9 @@ package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MultiSampleSource;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsPlaylist;
......@@ -40,7 +42,9 @@ import android.os.Handler;
public class HlsSourceBuilder implements SourceBuilder {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int MAIN_BUFFER_SEGMENTS = 256;
private static final int MAIN_BUFFER_SEGMENTS = 254;
private static final int AUDIO_BUFFER_SEGMENTS = 54;
private static final int TEXT_BUFFER_SEGMENTS = 2;
private final Context context;
private final String userAgent;
......@@ -64,11 +68,26 @@ public class HlsSourceBuilder implements SourceBuilder {
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_DEFAULT,
dataSource, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
return new HlsSampleSource(chunkSource, loadControl,
DataSource defaultDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher,
HlsChunkSource.TYPE_DEFAULT, defaultDataSource, timestampAdjusterProvider,
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter));
HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl,
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_AUDIO,
audioDataSource, timestampAdjusterProvider, null);
HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO);
DataSource subtitleDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher,
HlsChunkSource.TYPE_SUBTITLE, subtitleDataSource, timestampAdjusterProvider, null);
HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
return new MultiSampleSource(defaultSampleSource, audioSampleSource, subtitleSampleSource);
}
}
......@@ -121,10 +121,12 @@ public class TrackSelectionHelper implements View.OnClickListener, DialogInterfa
boolean haveSupportedTracks = false;
trackViews = new CheckedTextView[trackGroups.length][];
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
root.addView(inflater.inflate(R.layout.list_divider, root, false));
TrackGroup group = trackGroups.get(groupIndex);
trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
if (trackIndex == 0 || !group.adaptive) {
root.addView(inflater.inflate(R.layout.list_divider, root, false));
}
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex]
? android.R.layout.simple_list_item_single_choice
: android.R.layout.simple_list_item_multiple_choice;
......
......@@ -558,6 +558,8 @@ import java.util.concurrent.atomic.AtomicInteger;
}
}
// The new selections are being activated.
trackSelector.onSelectionActivated(result.second);
enabledRenderers = new TrackRenderer[enabledRendererCount];
enabledRendererCount = 0;
......@@ -597,9 +599,6 @@ import java.util.concurrent.atomic.AtomicInteger;
}
}
}
// The new selections have been activated.
trackSelector.onSelectionActivated(result.second);
}
private void reselectTracksInternal() throws ExoPlaybackException {
......
......@@ -295,7 +295,7 @@ public final class Format {
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
requiresSecureDecryption);
}
/**
* @return A {@link MediaFormat} representation of this format.
*/
......
......@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.Format;
import java.util.Collections;
import java.util.List;
......@@ -24,12 +26,20 @@ import java.util.List;
public final class HlsMasterPlaylist extends HlsPlaylist {
public final List<Variant> variants;
public final List<Variant> audios;
public final List<Variant> subtitles;
public HlsMasterPlaylist(String baseUri, List<Variant> variants, List<Variant> subtitles) {
public final Format muxedAudioFormat;
public final Format muxedCaptionFormat;
public HlsMasterPlaylist(String baseUri, List<Variant> variants, List<Variant> audios,
List<Variant> subtitles, Format muxedAudioFormat, Format muxedCaptionFormat) {
super(baseUri, HlsPlaylist.TYPE_MASTER);
this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios);
this.subtitles = Collections.unmodifiableList(subtitles);
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormat = muxedCaptionFormat;
}
}
......@@ -59,6 +59,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
private static final String METHOD_ATTR = "METHOD";
private static final String URI_ATTR = "URI";
private static final String IV_ATTR = "IV";
private static final String INSTREAM_ID_ATTR = "INSTREAM-ID";
private static final String AUDIO_TYPE = "AUDIO";
private static final String VIDEO_TYPE = "VIDEO";
......@@ -98,6 +99,8 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
Pattern.compile(LANGUAGE_ATTR + "=\"(.+?)\"");
private static final Pattern NAME_ATTR_REGEX =
Pattern.compile(NAME_ATTR + "=\"(.+?)\"");
private static final Pattern INSTREAM_ID_ATTR_REGEX =
Pattern.compile(INSTREAM_ID_ATTR + "=\"(.+?)\"");
// private static final Pattern AUTOSELECT_ATTR_REGEX =
// HlsParserUtil.compileBooleanAttrPattern(AUTOSELECT_ATTR);
// private static final Pattern DEFAULT_ATTR_REGEX =
......@@ -140,12 +143,15 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
throws IOException {
ArrayList<Variant> variants = new ArrayList<>();
ArrayList<Variant> audios = new ArrayList<>();
ArrayList<Variant> subtitles = new ArrayList<>();
int bitrate = 0;
String codecs = null;
int width = -1;
int height = -1;
String name = null;
Format muxedAudioFormat = null;
Format muxedCaptionFormat = null;
boolean expectingStreamInfUrl = false;
String line;
......@@ -153,16 +159,37 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
line = iterator.next();
if (line.startsWith(MEDIA_TAG)) {
String type = HlsParserUtil.parseStringAttr(line, TYPE_ATTR_REGEX, TYPE_ATTR);
if (SUBTITLES_TYPE.equals(type)) {
if (CLOSED_CAPTIONS_TYPE.equals(type)) {
String instreamId = HlsParserUtil.parseStringAttr(line, INSTREAM_ID_ATTR_REGEX,
INSTREAM_ID_ATTR);
if ("CC1".equals(instreamId)) {
// We assume all subtitles belong to the same group.
String captionName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
muxedCaptionFormat = Format.createTextContainerFormat(captionName,
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, -1, language);
}
} else if (SUBTITLES_TYPE.equals(type)) {
// We assume all subtitles belong to the same group.
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
MimeTypes.TEXT_VTT, bitrate, language);
subtitles.add(new Variant(uri, format, null));
} else {
// TODO: Support other types of media tag.
subtitles.add(new Variant(uri, format, codecs));
} else if (AUDIO_TYPE.equals(type)) {
// We assume all audios belong to the same group.
String uri = HlsParserUtil.parseOptionalStringAttr(line, URI_ATTR_REGEX);
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
String audioName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
int audioBitrate = uri != null ? bitrate : -1;
Format format = Format.createAudioContainerFormat(audioName, MimeTypes.APPLICATION_M3U8,
null, audioBitrate, -1, -1, null, language);
if (uri != null) {
audios.add(new Variant(uri, format, codecs));
} else {
muxedAudioFormat = format;
}
}
} else if (line.startsWith(STREAM_INF_TAG)) {
bitrate = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
......@@ -202,7 +229,8 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
expectingStreamInfUrl = false;
}
}
return new HlsMasterPlaylist(baseUri, variants, subtitles);
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
muxedCaptionFormat);
}
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
......
......@@ -58,8 +58,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
private static final int PRIMARY_TYPE_NONE = 0;
private static final int PRIMARY_TYPE_AUDIO = 1;
private static final int PRIMARY_TYPE_VIDEO = 2;
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 LinkedList<HlsExtractorWrapper> extractors;
......@@ -136,6 +137,10 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
return true;
} else if (!chunkSource.prepare()) {
return false;
} else if (chunkSource.getTrackCount() == 0) {
trackGroups = new TrackGroupArray();
prepared = true;
return true;
}
if (!extractors.isEmpty()) {
while (true) {
......@@ -368,15 +373,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
} else if (loadingFinished) {
return C.END_OF_SOURCE_US;
} else {
long largestParsedTimestampUs = extractors.getLast().getLargestParsedTimestampUs();
long bufferedPositionUs = extractors.getLast().getLargestParsedTimestampUs();
if (extractors.size() > 1) {
// When adapting from one format to the next, the penultimate extractor may have the largest
// parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet).
largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
bufferedPositionUs = Math.max(bufferedPositionUs,
extractors.get(extractors.size() - 2).getLargestParsedTimestampUs());
}
return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
: largestParsedTimestampUs;
if (previousTsLoadable != null) {
// Buffered position should be at least as large as the end time of the previously loaded
// chunk.
bufferedPositionUs = Math.max(previousTsLoadable.endTimeUs, bufferedPositionUs);
}
return bufferedPositionUs == Long.MIN_VALUE ? downstreamPositionUs
: bufferedPositionUs;
}
}
......@@ -489,6 +499,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
trackType = PRIMARY_TYPE_VIDEO;
} else if (MimeTypes.isAudio(sampleMimeType)) {
trackType = PRIMARY_TYPE_AUDIO;
} else if (MimeTypes.isText(sampleMimeType)) {
trackType = PRIMARY_TYPE_TEXT;
} else {
trackType = PRIMARY_TYPE_NONE;
}
......@@ -520,10 +532,18 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = getSampleFormat(chunkSource.getTrackFormat(j), sampleFormat);
}
trackGroups[i] = new TrackGroup(true, formats);
trackGroups[i] = new TrackGroup(chunkSource.isAdaptive(), formats);
primaryTrackGroupIndex = i;
} else {
trackGroups[i] = new TrackGroup(sampleFormat);
Format trackFormat = null;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
trackFormat = chunkSource.getMuxedAudioFormat();
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
trackFormat = chunkSource.getMuxedCaptionFormat();
}
}
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
}
}
this.trackGroups = new TrackGroupArray(trackGroups);
......@@ -550,6 +570,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
* @return The derived sample format.
*/
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
if (containerFormat == null) {
return sampleFormat;
}
int width = containerFormat.width == -1 ? Format.NO_VALUE : containerFormat.width;
int height = containerFormat.height == -1 ? Format.NO_VALUE : containerFormat.height;
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, width,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment