diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
index ae47ba36c2..e669fa536a 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
@@ -28,9 +28,7 @@ import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
import com.google.android.exoplayer.extractor.ts.TsExtractor;
-import com.google.android.exoplayer.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
-import com.google.android.exoplayer.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer.hls.playlist.Variant;
import com.google.android.exoplayer.upstream.DataSource;
@@ -48,10 +46,8 @@ import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.List;
import java.util.Locale;
/**
@@ -70,54 +66,53 @@ public class HlsChunkSource {
private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
- private final int type;
+ private final String baseUri;
private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator;
private final Evaluation evaluation;
private final HlsPlaylistParser playlistParser;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
+ private final Variant[] variants;
+ private final HlsMediaPlaylist[] variantPlaylists;
+ private final long[] variantLastPlaylistLoadTimesMs;
private byte[] scratchSpace;
private boolean live;
private long durationUs;
private IOException fatalError;
- private String baseUri;
- private Format muxedAudioFormat;
- private Format muxedCaptionFormat;
private Uri encryptionKeyUri;
private byte[] encryptionKey;
private String encryptionIvString;
private byte[] encryptionIv;
- // Properties of exposed tracks.
- private Variant[] variants;
- private HlsMediaPlaylist[] variantPlaylists;
- private long[] variantLastPlaylistLoadTimesMs;
-
// Properties of enabled variants.
private Variant[] enabledVariants;
private long[] enabledVariantBlacklistTimes;
private boolean[] enabledVariantBlacklistFlags;
/**
- * @param type The type of chunk provided by the source. One of {@link C#TRACK_TYPE_DEFAULT},
- * {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}.
+ * @param baseUri The playlist's base uri.
+ * @param variants The available variants.
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @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 adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
*/
- public HlsChunkSource(int type, DataSource dataSource,
+ public HlsChunkSource(String baseUri, Variant[] variants, DataSource dataSource,
PtsTimestampAdjusterProvider timestampAdjusterProvider,
FormatEvaluator adaptiveFormatEvaluator) {
- this.type = type;
+ this.baseUri = baseUri;
+ this.variants = variants;
this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.timestampAdjusterProvider = timestampAdjusterProvider;
playlistParser = new HlsPlaylistParser();
evaluation = new Evaluation();
+ variantPlaylists = new HlsMediaPlaylist[variants.length];
+ variantLastPlaylistLoadTimesMs = new long[variants.length];
+ selectTracks(new int[] {0});
}
/**
@@ -141,19 +136,6 @@ public class HlsChunkSource {
return adaptiveFormatEvaluator != null;
}
- /**
- * Prepares the source.
- *
- * @param playlist A {@link HlsPlaylist}.
- */
- public void prepare(HlsPlaylist playlist) {
- processPlaylist(playlist);
- if (variants.length > 0) {
- // Select the first variant listed in the master playlist.
- selectTracks(new int[] {0});
- }
- }
-
/**
* Returns whether this is a live playback.
*
@@ -199,28 +181,6 @@ public class HlsChunkSource {
return variants[index].format;
}
- /**
- * Returns the format of the audio muxed into variants, or null if unknown.
- *
- * This method should only be called after the source has been prepared.
- *
- * @return The format of the audio muxed into variants, or null if unknown.
- */
- public Format getMuxedAudioFormat() {
- return muxedAudioFormat;
- }
-
- /**
- * Returns the format of the captions muxed into variants, or null if unknown.
- *
- * This method should only be called after the source has been prepared.
- *
- * @return The format of the captions muxed into variants, or null if unknown.
- */
- public Format getMuxedCaptionFormat() {
- return muxedCaptionFormat;
- }
-
/**
* Selects tracks for use.
*
@@ -269,17 +229,6 @@ public class HlsChunkSource {
return false;
}
- /**
- * Notifies the source that a seek has occurred.
- *
- * This method should only be called after the source has been prepared.
- */
- public void seek() {
- if (type == C.TRACK_TYPE_DEFAULT) {
- timestampAdjusterProvider.reset();
- }
- }
-
/**
* Resets the source.
*
@@ -493,90 +442,6 @@ public class HlsChunkSource {
// Private methods.
- private void processPlaylist(HlsPlaylist playlist) {
- baseUri = playlist.baseUri;
-
- if (playlist instanceof HlsMediaPlaylist) {
- if (type == C.TRACK_TYPE_TEXT || type == C.TRACK_TYPE_AUDIO) {
- variants = new Variant[0];
- variantPlaylists = new HlsMediaPlaylist[variants.length];
- variantLastPlaylistLoadTimesMs = new long[variants.length];
- return;
- }
-
- // type == C.TRACK_TYPE_DEFAULT
- Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null,
- Format.NO_VALUE);
- variants = new Variant[] {new Variant(baseUri, format, null)};
- variantPlaylists = new HlsMediaPlaylist[variants.length];
- variantLastPlaylistLoadTimesMs = new long[variants.length];
- setMediaPlaylist(0, (HlsMediaPlaylist) playlist);
- return;
- }
-
- HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
- muxedAudioFormat = masterPlaylist.muxedAudioFormat;
- muxedCaptionFormat = masterPlaylist.muxedCaptionFormat;
- if (type == C.TRACK_TYPE_TEXT || type == C.TRACK_TYPE_AUDIO) {
- List variantList = type == C.TRACK_TYPE_AUDIO ? masterPlaylist.audios
- : masterPlaylist.subtitles;
- if (variantList != null && !variantList.isEmpty()) {
- variants = new Variant[variantList.size()];
- variantList.toArray(variants);
- } else {
- variants = new Variant[0];
- }
- variantPlaylists = new HlsMediaPlaylist[variants.length];
- variantLastPlaylistLoadTimesMs = new long[variants.length];
- return;
- }
-
- // type == C.TRACK_TYPE_DEFAULT
- List enabledVariantList = new ArrayList<>(masterPlaylist.variants);
- ArrayList definiteVideoVariants = new ArrayList<>();
- ArrayList 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.
- }
-
- variants = new Variant[enabledVariantList.size()];
- enabledVariantList.toArray(variants);
- variantPlaylists = new HlsMediaPlaylist[variants.length];
- variantLastPlaylistLoadTimesMs = new long[variants.length];
- }
-
- private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
- String codecs = variant.codecs;
- if (TextUtils.isEmpty(codecs)) {
- return false;
- }
- String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
- for (String codec : codecArray) {
- if (codec.startsWith(prefix)) {
- return true;
- }
- }
- return false;
- }
-
private int getNextVariantIndex(HlsMediaChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants();
if (enabledVariants.length > 1) {
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
index 2c6d7bd127..b195296fc3 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
+import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup;
@@ -25,17 +26,21 @@ import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.chunk.FormatEvaluator;
+import com.google.android.exoplayer.hls.playlist.HlsMasterPlaylist;
+import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser;
+import com.google.android.exoplayer.hls.playlist.Variant;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
-import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestFetcher;
+import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.Pair;
import java.io.IOException;
@@ -54,75 +59,59 @@ public final class HlsSampleSource implements SampleSource {
// TODO: Use this for playlist loads as well.
private static final int MIN_LOADABLE_RETRY_COUNT = 3;
- private final ManifestFetcher manifestFetcher;
- private final HlsChunkSource[] chunkSources;
- private final HlsTrackStreamWrapper[] trackStreamWrappers;
+ private final DataSourceFactory dataSourceFactory;
+ private final BandwidthMeter bandwidthMeter;
+ private final Handler eventHandler;
+ private final ChunkTrackStreamEventListener eventListener;
+ private final LoadControl loadControl;
private final IdentityHashMap trackStreamSources;
- private final int[] selectedTrackCounts;
+ private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
+ private final ManifestFetcher manifestFetcher;
- private boolean prepared;
private boolean seenFirstTrackSelection;
private long durationUs;
- private HlsPlaylist playlist;
+ private boolean isLive;
private TrackGroupArray trackGroups;
+ private int[] selectedTrackCounts;
+ private HlsTrackStreamWrapper[] trackStreamWrappers;
private HlsTrackStreamWrapper[] enabledTrackStreamWrappers;
+ private boolean pendingReset;
+ private long lastSeekPositionUs;
public HlsSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkTrackStreamEventListener eventListener) {
+ this.dataSourceFactory = dataSourceFactory;
+ this.bandwidthMeter = bandwidthMeter;
+ this.eventHandler = eventHandler;
+ this.eventListener = eventListener;
+
+ loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
+ timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
+ trackStreamSources = new IdentityHashMap<>();
+
HlsPlaylistParser parser = new HlsPlaylistParser();
DataSource manifestDataSource = dataSourceFactory.createDataSource();
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser);
-
- LoadControl loadControl = new DefaultLoadControl(
- new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
- PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
-
- DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
- HlsChunkSource defaultChunkSource = new HlsChunkSource(C.TRACK_TYPE_DEFAULT,
- defaultDataSource, timestampAdjusterProvider,
- new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter));
- HlsTrackStreamWrapper defaultTrackStreamWrapper = new HlsTrackStreamWrapper(defaultChunkSource,
- loadControl, C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO,
- MIN_LOADABLE_RETRY_COUNT);
-
- DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
- HlsChunkSource audioChunkSource = new HlsChunkSource(C.TRACK_TYPE_AUDIO,
- audioDataSource, timestampAdjusterProvider, null);
- HlsTrackStreamWrapper audioTrackStreamWrapper = new HlsTrackStreamWrapper(audioChunkSource,
- loadControl, C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO,
- MIN_LOADABLE_RETRY_COUNT);
-
- DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
- HlsChunkSource textChunkSource = new HlsChunkSource(C.TRACK_TYPE_TEXT, textDataSource,
- timestampAdjusterProvider, null);
- HlsTrackStreamWrapper textTrackStreamWrapper = new HlsTrackStreamWrapper(
- textChunkSource, loadControl, C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener,
- C.TRACK_TYPE_TEXT, MIN_LOADABLE_RETRY_COUNT);
-
- chunkSources = new HlsChunkSource[] {defaultChunkSource, audioChunkSource, textChunkSource};
- trackStreamWrappers = new HlsTrackStreamWrapper[] {defaultTrackStreamWrapper,
- audioTrackStreamWrapper, textTrackStreamWrapper};
- selectedTrackCounts = new int[trackStreamWrappers.length];
- trackStreamSources = new IdentityHashMap<>();
}
@Override
public boolean prepare(long positionUs) throws IOException {
- if (prepared) {
+ if (trackGroups != null) {
return true;
}
- if (playlist == null) {
- playlist = manifestFetcher.getManifest();
+ if (trackStreamWrappers == null) {
+ HlsPlaylist playlist = manifestFetcher.getManifest();
if (playlist == null) {
manifestFetcher.maybeThrowError();
manifestFetcher.requestRefresh();
return false;
}
- for (HlsChunkSource chunkSource : chunkSources) {
- chunkSource.prepare(playlist);
- }
+ List trackStreamWrapperList = buildTrackStreamWrappers(playlist);
+ trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
+ trackStreamWrapperList.toArray(trackStreamWrappers);
+ selectedTrackCounts = new int[trackStreamWrappers.length];
}
boolean trackStreamWrappersPrepared = true;
@@ -133,15 +122,13 @@ public final class HlsSampleSource implements SampleSource {
return false;
}
- durationUs = 0;
+ // The wrapper at index 0 is the one of type TRACK_TYPE_DEFAULT.
+ durationUs = trackStreamWrappers[0].getDurationUs();
+ isLive = trackStreamWrappers[0].isLive();
+
int totalTrackGroupCount = 0;
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
totalTrackGroupCount += trackStreamWrapper.getTrackGroups().length;
- if (durationUs != C.UNSET_TIME_US) {
- long wrapperDurationUs = trackStreamWrapper.getDurationUs();
- durationUs = wrapperDurationUs == C.UNSET_TIME_US
- ? C.UNSET_TIME_US : Math.max(durationUs, wrapperDurationUs);
- }
}
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0;
@@ -152,7 +139,6 @@ public final class HlsSampleSource implements SampleSource {
}
}
trackGroups = new TrackGroupArray(trackGroupArray);
- prepared = true;
return true;
}
@@ -169,7 +155,6 @@ public final class HlsSampleSource implements SampleSource {
@Override
public TrackStream[] selectTracks(List oldStreams,
List newSelections, long positionUs) {
- Assertions.checkState(prepared);
TrackStream[] newStreams = new TrackStream[newSelections.size()];
// Select tracks for each wrapper.
int enabledTrackStreamWrapperCount = 0;
@@ -201,37 +186,37 @@ public final class HlsSampleSource implements SampleSource {
@Override
public long readReset() {
- long resetPositionUs = C.UNSET_TIME_US;
- for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
- long childResetPositionUs = trackStreamWrapper.readReset();
- if (resetPositionUs == C.UNSET_TIME_US) {
- resetPositionUs = childResetPositionUs;
- } else if (childResetPositionUs != C.UNSET_TIME_US) {
- resetPositionUs = Math.min(resetPositionUs, childResetPositionUs);
+ if (pendingReset) {
+ pendingReset = false;
+ for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
+ trackStreamWrapper.setReadingEnabled(true);
}
+ return lastSeekPositionUs;
}
- return resetPositionUs;
+ return C.UNSET_TIME_US;
}
@Override
public long getBufferedPositionUs() {
- long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE;
+ long bufferedPositionUs = Long.MAX_VALUE;
for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
long rendererBufferedPositionUs = trackStreamWrapper.getBufferedPositionUs();
- if (rendererBufferedPositionUs == C.UNSET_TIME_US) {
- return C.UNSET_TIME_US;
- } else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) {
- // This wrapper is fully buffered.
- } else {
+ if (rendererBufferedPositionUs != C.END_OF_SOURCE_US) {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
- return bufferedPositionUs == Long.MAX_VALUE ? C.UNSET_TIME_US : bufferedPositionUs;
+ return bufferedPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : bufferedPositionUs;
}
@Override
public void seekToUs(long positionUs) {
+ // Treat all seeks into non-seekable media as being to t=0.
+ positionUs = isLive ? 0 : positionUs;
+ lastSeekPositionUs = positionUs;
+ pendingReset = true;
+ timestampAdjusterProvider.reset();
for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
+ trackStreamWrapper.setReadingEnabled(false);
trackStreamWrapper.seekToUs(positionUs);
}
}
@@ -239,13 +224,92 @@ public final class HlsSampleSource implements SampleSource {
@Override
public void release() {
manifestFetcher.release();
- for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
- trackStreamWrapper.release();
+ if (trackStreamWrappers != null) {
+ for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
+ trackStreamWrapper.release();
+ }
}
}
// Internal methods.
+ private List buildTrackStreamWrappers(HlsPlaylist playlist) {
+ ArrayList trackStreamWrappers = new ArrayList<>();
+ String baseUri = playlist.baseUri;
+
+ if (playlist instanceof HlsMediaPlaylist) {
+ Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null,
+ Format.NO_VALUE);
+ Variant[] variants = new Variant[] {new Variant(playlist.baseUri, format, null)};
+ trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
+ new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), C.DEFAULT_MUXED_BUFFER_SIZE,
+ null, null));
+ return trackStreamWrappers;
+ }
+
+ HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
+
+ // Build the default stream wrapper.
+ List selectedVariants = new ArrayList<>(masterPlaylist.variants);
+ ArrayList definiteVideoVariants = new ArrayList<>();
+ ArrayList definiteAudioOnlyVariants = new ArrayList<>();
+ for (int i = 0; i < selectedVariants.size(); i++) {
+ Variant variant = selectedVariants.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.
+ selectedVariants = definiteVideoVariants;
+ } else if (definiteAudioOnlyVariants.size() < selectedVariants.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.
+ selectedVariants.removeAll(definiteAudioOnlyVariants);
+ } else {
+ // Leave the enabled variants unchanged. They're likely either all video or all audio.
+ }
+ Variant[] variants = new Variant[selectedVariants.size()];
+ selectedVariants.toArray(variants);
+ trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
+ new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), C.DEFAULT_MUXED_BUFFER_SIZE,
+ masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat));
+
+ // Build the audio stream wrapper if applicable.
+ List audioVariants = masterPlaylist.audios;
+ if (!audioVariants.isEmpty()) {
+ variants = new Variant[audioVariants.size()];
+ audioVariants.toArray(variants);
+ trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
+ C.DEFAULT_AUDIO_BUFFER_SIZE, null, null));
+ }
+
+ // Build the text stream wrapper if applicable.
+ List subtitleVariants = masterPlaylist.subtitles;
+ if (!subtitleVariants.isEmpty()) {
+ variants = new Variant[subtitleVariants.size()];
+ subtitleVariants.toArray(variants);
+ trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
+ C.DEFAULT_TEXT_BUFFER_SIZE, null, null));
+ }
+
+ return trackStreamWrappers;
+ }
+
+ private HlsTrackStreamWrapper buildTrackStreamWrapper(int trackType, String baseUri,
+ Variant[] variants, FormatEvaluator formatEvaluator, int bufferSize, Format muxedAudioFormat,
+ Format muxedCaptionFormat) {
+ DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
+ HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
+ timestampAdjusterProvider, formatEvaluator);
+ return new HlsTrackStreamWrapper(defaultChunkSource, loadControl, bufferSize, muxedAudioFormat,
+ muxedCaptionFormat, eventHandler, eventListener, trackType, MIN_LOADABLE_RETRY_COUNT);
+ }
+
private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,
List allOldStreams, List allNewSelections, long positionUs,
TrackStream[] allNewStreams) {
@@ -295,4 +359,18 @@ public final class HlsSampleSource implements SampleSource {
throw new IndexOutOfBoundsException();
}
+ private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
+ String codecs = variant.codecs;
+ if (TextUtils.isEmpty(codecs)) {
+ return false;
+ }
+ String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
+ for (String codec : codecArray) {
+ if (codec.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
index 3cd48c76c3..93b20b0bc1 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
@@ -62,12 +62,14 @@ import java.util.List;
private final ChunkHolder nextChunkHolder;
private final EventDispatcher eventDispatcher;
private final LoadControl loadControl;
+ private final Format muxedAudioFormat;
+ private final Format muxedCaptionFormat;
private volatile boolean sampleQueuesBuilt;
private boolean prepared;
private boolean seenFirstTrackSelection;
- private boolean notifyReset;
+ private boolean readingEnabled;
private int enabledTrackCount;
private Format downstreamFormat;
@@ -88,6 +90,10 @@ import java.util.List;
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
* @param loadControl Controls when the source is permitted to load data.
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
+ * @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio,
+ * this is the audio {@link Format} as defined by the playlist.
+ * @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed
+ * captions, this is the audio {@link Format} as defined by the playlist.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
@@ -96,16 +102,20 @@ import java.util.List;
* before propagating an error.
*/
public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl,
- int bufferSizeContribution, Handler eventHandler,
- ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
+ int bufferSizeContribution, Format muxedAudioFormat, Format muxedCaptionFormat,
+ Handler eventHandler, ChunkTrackStreamEventListener eventListener, int eventSourceId,
+ int minLoadableRetryCount) {
this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution;
+ this.muxedAudioFormat = muxedAudioFormat;
+ this.muxedCaptionFormat = muxedCaptionFormat;
loader = new Loader("Loader:HlsTrackStreamWrapper", minLoadableRetryCount);
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder();
sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>();
+ readingEnabled = true;
pendingResetPositionUs = C.UNSET_TIME_US;
}
@@ -150,6 +160,10 @@ import java.util.List;
return chunkSource.getDurationUs();
}
+ public boolean isLive() {
+ return chunkSource.isLive();
+ }
+
public TrackGroupArray getTrackGroups() {
return trackGroups;
}
@@ -211,12 +225,8 @@ import java.util.List;
}
}
- public long readReset() {
- if (notifyReset) {
- notifyReset = false;
- return lastSeekPositionUs;
- }
- return C.UNSET_TIME_US;
+ public void setReadingEnabled(boolean readingEnabled) {
+ this.readingEnabled = readingEnabled;
}
public long getBufferedPositionUs() {
@@ -265,7 +275,7 @@ import java.util.List;
}
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) {
- if (notifyReset || isPendingReset()) {
+ if (!readingEnabled || isPendingReset()) {
return TrackStream.NOTHING_READ;
}
@@ -449,9 +459,9 @@ import java.util.List;
Format trackFormat = null;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
- trackFormat = chunkSource.getMuxedAudioFormat();
+ trackFormat = muxedAudioFormat;
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
- trackFormat = chunkSource.getMuxedCaptionFormat();
+ trackFormat = muxedCaptionFormat;
}
}
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
@@ -494,32 +504,9 @@ import java.util.List;
* @param positionUs The position to seek to.
*/
private void seekToInternal(long positionUs) {
- // Treat all seeks into non-seekable media as being to t=0.
- positionUs = chunkSource.isLive() ? 0 : positionUs;
lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs;
- notifyReset = true;
- boolean seekInsideBuffer = !isPendingReset();
- // TODO[REFACTOR]: This will nearly always fail to seek inside all buffers due to sparse tracks
- // such as ID3 (probably EIA608 too). We need a way to not care if we can't seek to the keyframe
- // before for such tracks. For ID3 we probably explicitly don't want the keyframe before, even
- // if we do have it, since it might be quite a long way behind the seek position. We probably
- // only want to output ID3 buffers whose timestamps are greater than or equal to positionUs.
- int sampleQueueCount = sampleQueues.size();
- for (int i = 0; seekInsideBuffer && i < sampleQueueCount; i++) {
- if (groupEnabledStates[i]) {
- seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs);
- }
- }
- if (seekInsideBuffer) {
- while (mediaChunks.size() > 1 && mediaChunks.get(1).startTimeUs <= positionUs) {
- mediaChunks.removeFirst();
- }
- } else {
- // If we failed to seek within the sample queues, we need to restart.
- chunkSource.seek();
- restartFrom(positionUs);
- }
+ restartFrom(positionUs);
}
private void discardSamplesForDisabledTracks() {