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() {