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 8d12290384..ae47ba36c2 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 @@ -36,7 +36,6 @@ import com.google.android.exoplayer.hls.playlist.Variant; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; -import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.Util; @@ -51,7 +50,6 @@ import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; @@ -72,7 +70,6 @@ public class HlsChunkSource { private static final String VTT_FILE_EXTENSION = ".vtt"; private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; - private final ManifestFetcher manifestFetcher; private final int type; private final DataSource dataSource; private final FormatEvaluator adaptiveFormatEvaluator; @@ -84,8 +81,9 @@ public class HlsChunkSource { private boolean live; private long durationUs; private IOException fatalError; - private HlsMasterPlaylist masterPlaylist; private String baseUri; + private Format muxedAudioFormat; + private Format muxedCaptionFormat; private Uri encryptionKeyUri; private byte[] encryptionKey; @@ -103,7 +101,6 @@ public class HlsChunkSource { private boolean[] enabledVariantBlacklistFlags; /** - * @param manifestFetcher A fetcher for the playlist. * @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 dataSource A {@link DataSource} suitable for loading the media data. @@ -112,10 +109,9 @@ public class HlsChunkSource { * same provider. * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. */ - public HlsChunkSource(ManifestFetcher manifestFetcher, int type, - DataSource dataSource, PtsTimestampAdjusterProvider timestampAdjusterProvider, + public HlsChunkSource(int type, DataSource dataSource, + PtsTimestampAdjusterProvider timestampAdjusterProvider, FormatEvaluator adaptiveFormatEvaluator) { - this.manifestFetcher = manifestFetcher; this.type = type; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; @@ -148,38 +144,14 @@ public class HlsChunkSource { /** * Prepares the source. * - * @return True if the source was prepared, false otherwise. + * @param playlist A {@link HlsPlaylist}. */ - public boolean prepare() throws IOException { - if (masterPlaylist == null) { - HlsPlaylist playlist = manifestFetcher.getManifest(); - if (playlist == null) { - manifestFetcher.maybeThrowError(); - manifestFetcher.requestRefresh(); - return false; - } else { - baseUri = playlist.baseUri; - if (playlist.type == HlsPlaylist.TYPE_MASTER) { - masterPlaylist = (HlsMasterPlaylist) playlist; - } else { - Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, - Format.NO_VALUE); - List variants = new ArrayList<>(); - variants.add(new Variant(baseUri, format, null)); - masterPlaylist = new HlsMasterPlaylist(baseUri, variants, - Collections.emptyList(), Collections.emptyList(), null, null); - } - processMasterPlaylist(masterPlaylist); - if (variants.length > 0) { - if (playlist.type == HlsPlaylist.TYPE_MEDIA) { - setMediaPlaylist(0, (HlsMediaPlaylist) playlist); - } - // Select the first variant listed in the master playlist. - selectTracks(new int[] {0}); - } - } + public void prepare(HlsPlaylist playlist) { + processPlaylist(playlist); + if (variants.length > 0) { + // Select the first variant listed in the master playlist. + selectTracks(new int[] {0}); } - return true; } /** @@ -235,7 +207,7 @@ public class HlsChunkSource { * @return The format of the audio muxed into variants, or null if unknown. */ public Format getMuxedAudioFormat() { - return masterPlaylist.muxedAudioFormat; + return muxedAudioFormat; } /** @@ -246,7 +218,7 @@ public class HlsChunkSource { * @return The format of the captions muxed into variants, or null if unknown. */ public Format getMuxedCaptionFormat() { - return masterPlaylist.muxedCaptionFormat; + return muxedCaptionFormat; } /** @@ -521,9 +493,33 @@ public class HlsChunkSource { // Private methods. - private void processMasterPlaylist(HlsMasterPlaylist playlist) { + 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 ? playlist.audios : playlist.subtitles; + List variantList = type == C.TRACK_TYPE_AUDIO ? masterPlaylist.audios + : masterPlaylist.subtitles; if (variantList != null && !variantList.isEmpty()) { variants = new Variant[variantList.size()]; variantList.toArray(variants); @@ -535,8 +531,8 @@ public class HlsChunkSource { return; } - // Type is TYPE_DEFAULT. - List enabledVariantList = new ArrayList<>(playlist.variants); + // 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++) { 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 8faae31324..2c6d7bd127 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 @@ -48,7 +48,14 @@ import java.util.List; */ public final class HlsSampleSource implements SampleSource { + /** + * The minimum number of times to retry loading data prior to failing. + */ + // 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 IdentityHashMap trackStreamSources; private final int[] selectedTrackCounts; @@ -56,6 +63,7 @@ public final class HlsSampleSource implements SampleSource { private boolean prepared; private boolean seenFirstTrackSelection; private long durationUs; + private HlsPlaylist playlist; private TrackGroupArray trackGroups; private HlsTrackStreamWrapper[] enabledTrackStreamWrappers; @@ -71,27 +79,30 @@ public final class HlsSampleSource implements SampleSource { PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_DEFAULT, + 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); + 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(manifestFetcher, C.TRACK_TYPE_AUDIO, + 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); + loadControl, C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO, + MIN_LOADABLE_RETRY_COUNT); - DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, - subtitleDataSource, timestampAdjusterProvider, null); - HlsTrackStreamWrapper subtitleTrackStreamWrapper = new HlsTrackStreamWrapper( - subtitleChunkSource, loadControl, C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, - C.TRACK_TYPE_TEXT); + 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, subtitleTrackStreamWrapper}; + audioTrackStreamWrapper, textTrackStreamWrapper}; selectedTrackCounts = new int[trackStreamWrappers.length]; trackStreamSources = new IdentityHashMap<>(); } @@ -101,6 +112,19 @@ public final class HlsSampleSource implements SampleSource { if (prepared) { return true; } + + if (playlist == null) { + playlist = manifestFetcher.getManifest(); + if (playlist == null) { + manifestFetcher.maybeThrowError(); + manifestFetcher.requestRefresh(); + return false; + } + for (HlsChunkSource chunkSource : chunkSources) { + chunkSource.prepare(playlist); + } + } + boolean trackStreamWrappersPrepared = true; for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { trackStreamWrappersPrepared &= trackStreamWrapper.prepare(positionUs); @@ -108,6 +132,7 @@ public final class HlsSampleSource implements SampleSource { if (!trackStreamWrappersPrepared) { return false; } + durationUs = 0; int totalTrackGroupCount = 0; for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) { 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 a17c13ec34..3b9663e691 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 @@ -49,11 +49,6 @@ import java.util.List; */ /* package */ final class HlsTrackStreamWrapper implements Loader.Callback, ExtractorOutput { - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - private static final int PRIMARY_TYPE_NONE = 0; private static final int PRIMARY_TYPE_TEXT = 1; private static final int PRIMARY_TYPE_AUDIO = 2; @@ -89,32 +84,6 @@ import java.util.List; private boolean loadingFinished; - /** - * @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. - */ - public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution) { - this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); - } - - /** - * @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 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. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - */ - public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, - ChunkTrackStreamEventListener eventListener, int eventSourceId) { - this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, - eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); - } - /** * @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained. * @param loadControl Controls when the source is permitted to load data. @@ -144,9 +113,6 @@ import java.util.List; if (prepared) { return true; } - if (!chunkSource.prepare()) { - return false; - } if (chunkSource.getTrackCount() == 0) { trackGroups = new TrackGroupArray(); prepared = true;