Commit 65c44453 by tonihei Committed by Ian Baker

Rename HLS master playlist to multivariant playlist

The spec renamed this type of playlist in the latest revision
to use a more inclusive technical term (see
https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-10)

PiperOrigin-RevId: 417560274
parent e3548f26
Showing with 683 additions and 564 deletions
...@@ -237,11 +237,11 @@ ...@@ -237,11 +237,11 @@
"uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
}, },
{ {
"name": "Apple master playlist advanced (TS)", "name": "Apple multivariant playlist advanced (TS)",
"uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8"
}, },
{ {
"name": "Apple master playlist advanced (FMP4)", "name": "Apple multivariant playlist advanced (FMP4)",
"uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8"
}, },
{ {
......
...@@ -90,7 +90,7 @@ For more information, see the ...@@ -90,7 +90,7 @@ For more information, see the
A file that defines the structure and location of media in A file that defines the structure and location of media in
[adaptive streaming](#adaptive-streaming) protocols. Examples include [adaptive streaming](#adaptive-streaming) protocols. Examples include
[DASH](#dash) [MPD](#mpd) files, [HLS](#hls) master playlist files and [DASH](#dash) [MPD](#mpd) files, [HLS](#hls) multivariant playlist files and
[Smooth Streaming](#smooth-streaming) manifest files. Not to be confused with an [Smooth Streaming](#smooth-streaming) manifest files. Not to be confused with an
AndroidManifest XML file. AndroidManifest XML file.
......
...@@ -30,8 +30,8 @@ If your URI doesn't end with `.m3u8`, you can pass `MimeTypes.APPLICATION_M3U8` ...@@ -30,8 +30,8 @@ If your URI doesn't end with `.m3u8`, you can pass `MimeTypes.APPLICATION_M3U8`
to `setMimeType` of `MediaItem.Builder` to explicitly indicate the type of the to `setMimeType` of `MediaItem.Builder` to explicitly indicate the type of the
content. content.
The URI of the media item may point to either a media playlist or a master The URI of the media item may point to either a media playlist or a multivariant
playlist. If the URI points to a master playlist that declares multiple playlist. If the URI points to a multivariant playlist that declares multiple
`#EXT-X-STREAM-INF` tags then ExoPlayer will automatically adapt between `#EXT-X-STREAM-INF` tags then ExoPlayer will automatically adapt between
variants, taking into account both available bandwidth and device capabilities. variants, taking into account both available bandwidth and device capabilities.
...@@ -89,17 +89,18 @@ app's needs. See the [Customization page][] for examples. ...@@ -89,17 +89,18 @@ app's needs. See the [Customization page][] for examples.
### Disabling chunkless preparation ### ### Disabling chunkless preparation ###
By default, ExoPlayer will use chunkless preparation. This means that ExoPlayer By default, ExoPlayer will use chunkless preparation. This means that ExoPlayer
will only use the information in the master playlist to prepare the stream, will only use the information in the multivariant playlist to prepare the
which works if the `#EXT-X-STREAM-INF` tags contain the `CODECS` attribute. stream, which works if the `#EXT-X-STREAM-INF` tags contain the `CODECS`
attribute.
You may need to disable this feature if your media segments contain muxed You may need to disable this feature if your media segments contain muxed
closed-caption tracks that are not declared in the master playlist with a closed-caption tracks that are not declared in the multivariant playlist with a
`#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS` tag. Otherwise, these closed-caption tracks `#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS` tag. Otherwise, these closed-caption tracks
won't be detected and played. You can disable chunkless preparation in the won't be detected and played. You can disable chunkless preparation in the
`HlsMediaSource.Factory` as shown in the following snippet. Note that this `HlsMediaSource.Factory` as shown in the following snippet. Note that this
will increase start up time as ExoPlayer needs to download a media segment to will increase start up time as ExoPlayer needs to download a media segment to
discover these additional tracks and it is preferable to declare the discover these additional tracks and it is preferable to declare the
closed-caption tracks in the master playlist instead. closed-caption tracks in the multivariant playlist instead.
~~~ ~~~
HlsMediaSource hlsMediaSource = HlsMediaSource hlsMediaSource =
new HlsMediaSource.Factory(dataSourceFactory) new HlsMediaSource.Factory(dataSourceFactory)
...@@ -119,7 +120,7 @@ ExoPlayer][] for a full explanation. The main points are: ...@@ -119,7 +120,7 @@ ExoPlayer][] for a full explanation. The main points are:
segments. segments.
* Use the `#EXT-X-INDEPENDENT-SEGMENTS` tag. * Use the `#EXT-X-INDEPENDENT-SEGMENTS` tag.
* Prefer demuxed streams, as opposed to files that include both video and audio. * Prefer demuxed streams, as opposed to files that include both video and audio.
* Include all information you can in the Master Playlist. * Include all information you can in the Multivariant Playlist.
The following guidelines apply specifically for live streams: The following guidelines apply specifically for live streams:
......
...@@ -631,7 +631,8 @@ public final class Format implements Bundleable { ...@@ -631,7 +631,8 @@ public final class Format implements Bundleable {
* <ul> * <ul>
* <li>DASH representations: Always {@link Format#NO_VALUE}. * <li>DASH representations: Always {@link Format#NO_VALUE}.
* <li>HLS variants: The {@code AVERAGE-BANDWIDTH} attribute defined on the corresponding {@code * <li>HLS variants: The {@code AVERAGE-BANDWIDTH} attribute defined on the corresponding {@code
* EXT-X-STREAM-INF} tag in the master playlist, or {@link Format#NO_VALUE} if not present. * EXT-X-STREAM-INF} tag in the multivariant playlist, or {@link Format#NO_VALUE} if not
* present.
* <li>SmoothStreaming track elements: The {@code Bitrate} attribute defined on the * <li>SmoothStreaming track elements: The {@code Bitrate} attribute defined on the
* corresponding {@code TrackElement} in the manifest, or {@link Format#NO_VALUE} if not * corresponding {@code TrackElement} in the manifest, or {@link Format#NO_VALUE} if not
* present. * present.
......
...@@ -40,20 +40,20 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract ...@@ -40,20 +40,20 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
private static final PositionHolder POSITION_HOLDER = new PositionHolder(); private static final PositionHolder POSITION_HOLDER = new PositionHolder();
@VisibleForTesting /* package */ final Extractor extractor; @VisibleForTesting /* package */ final Extractor extractor;
private final Format masterPlaylistFormat; private final Format multivariantPlaylistFormat;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param extractor The underlying {@link Extractor}. * @param extractor The underlying {@link Extractor}.
* @param masterPlaylistFormat The {@link Format} obtained from the master playlist. * @param multivariantPlaylistFormat The {@link Format} obtained from the multivariant playlist.
* @param timestampAdjuster A {@link TimestampAdjuster} to adjust sample timestamps. * @param timestampAdjuster A {@link TimestampAdjuster} to adjust sample timestamps.
*/ */
public BundledHlsMediaChunkExtractor( public BundledHlsMediaChunkExtractor(
Extractor extractor, Format masterPlaylistFormat, TimestampAdjuster timestampAdjuster) { Extractor extractor, Format multivariantPlaylistFormat, TimestampAdjuster timestampAdjuster) {
this.extractor = extractor; this.extractor = extractor;
this.masterPlaylistFormat = masterPlaylistFormat; this.multivariantPlaylistFormat = multivariantPlaylistFormat;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
} }
...@@ -85,7 +85,8 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract ...@@ -85,7 +85,8 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
Assertions.checkState(!isReusable()); Assertions.checkState(!isReusable());
Extractor newExtractorInstance; Extractor newExtractorInstance;
if (extractor instanceof WebvttExtractor) { if (extractor instanceof WebvttExtractor) {
newExtractorInstance = new WebvttExtractor(masterPlaylistFormat.language, timestampAdjuster); newExtractorInstance =
new WebvttExtractor(multivariantPlaylistFormat.language, timestampAdjuster);
} else if (extractor instanceof AdtsExtractor) { } else if (extractor instanceof AdtsExtractor) {
newExtractorInstance = new AdtsExtractor(); newExtractorInstance = new AdtsExtractor();
} else if (extractor instanceof Ac3Extractor) { } else if (extractor instanceof Ac3Extractor) {
...@@ -99,7 +100,7 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract ...@@ -99,7 +100,7 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
"Unexpected extractor type for recreation: " + extractor.getClass().getSimpleName()); "Unexpected extractor type for recreation: " + extractor.getClass().getSimpleName());
} }
return new BundledHlsMediaChunkExtractor( return new BundledHlsMediaChunkExtractor(
newExtractorInstance, masterPlaylistFormat, timestampAdjuster); newExtractorInstance, multivariantPlaylistFormat, timestampAdjuster);
} }
@Override @Override
......
...@@ -79,8 +79,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { ...@@ -79,8 +79,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
* DefaultTsPayloadReaderFactory} instances. Other flags may be added on top of {@code * DefaultTsPayloadReaderFactory} instances. Other flags may be added on top of {@code
* payloadReaderFactoryFlags} when creating {@link DefaultTsPayloadReaderFactory}. * payloadReaderFactoryFlags} when creating {@link DefaultTsPayloadReaderFactory}.
* @param exposeCea608WhenMissingDeclarations Whether created {@link TsExtractor} instances should * @param exposeCea608WhenMissingDeclarations Whether created {@link TsExtractor} instances should
* expose a CEA-608 track should the master playlist contain no Closed Captions declarations. * expose a CEA-608 track should the multivariant playlist contain no Closed Captions
* If the master playlist contains any Closed Captions declarations, this flag is ignored. * declarations. If the multivariant playlist contains any Closed Captions declarations, this
* flag is ignored.
*/ */
public DefaultHlsExtractorFactory( public DefaultHlsExtractorFactory(
int payloadReaderFactoryFlags, boolean exposeCea608WhenMissingDeclarations) { int payloadReaderFactoryFlags, boolean exposeCea608WhenMissingDeclarations) {
......
...@@ -154,7 +154,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -154,7 +154,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* {@link HlsChunkSource}s are used for a single playback, they should all share the same * {@link HlsChunkSource}s are used for a single playback, they should all share the same
* provider. * provider.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist. * information is available in the multivariant playlist.
*/ */
public HlsChunkSource( public HlsChunkSource(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
...@@ -877,8 +877,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -877,8 +877,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public InitializationTrackSelection(TrackGroup group, int[] tracks) { public InitializationTrackSelection(TrackGroup group, int[] tracks) {
super(group, tracks); super(group, tracks);
// The initially selected index corresponds to the first EXT-X-STREAMINF tag in the master // The initially selected index corresponds to the first EXT-X-STREAMINF tag in the
// playlist. // multivariant playlist.
selectedIndex = indexOf(group.getFormat(tracks[0])); selectedIndex = indexOf(group.getFormat(tracks[0]));
} }
......
...@@ -38,7 +38,7 @@ public interface HlsExtractorFactory { ...@@ -38,7 +38,7 @@ public interface HlsExtractorFactory {
* @param uri The URI of the media chunk. * @param uri The URI of the media chunk.
* @param format A {@link Format} associated with the chunk to extract. * @param format A {@link Format} associated with the chunk to extract.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist. * information is available in the multivariant playlist.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param responseHeaders The HTTP response headers associated with the media segment or * @param responseHeaders The HTTP response headers associated with the media segment or
* initialization section to extract. * initialization section to extract.
......
...@@ -17,21 +17,42 @@ package com.google.android.exoplayer2.source.hls; ...@@ -17,21 +17,42 @@ package com.google.android.exoplayer2.source.hls;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist;
/** Holds a master playlist along with a snapshot of one of its media playlists. */ /** Holds a multivariant playlist along with a snapshot of one of its media playlists. */
public final class HlsManifest { public final class HlsManifest {
/** The master playlist of an HLS stream. */ /** @deprecated Use {@link #multivariantPlaylist} instead. */
@Deprecated
@SuppressWarnings("deprecation") // Keeping deprecated field with deprecated class.
public final HlsMasterPlaylist masterPlaylist; public final HlsMasterPlaylist masterPlaylist;
/** A snapshot of a media playlist referred to by {@link #masterPlaylist}. */ /** The multivariant playlist of an HLS stream. */
public final HlsMultivariantPlaylist multivariantPlaylist;
/** A snapshot of a media playlist referred to by {@link #multivariantPlaylist}. */
public final HlsMediaPlaylist mediaPlaylist; public final HlsMediaPlaylist mediaPlaylist;
/** /**
* @param masterPlaylist The master playlist. * @param multivariantPlaylist The multivariant playlist.
* @param mediaPlaylist The media playlist. * @param mediaPlaylist The media playlist.
*/ */
HlsManifest(HlsMasterPlaylist masterPlaylist, HlsMediaPlaylist mediaPlaylist) { @SuppressWarnings("deprecation") // Intentionally creating deprecated hlsMasterPlaylist field.
this.masterPlaylist = masterPlaylist; /* package */ HlsManifest(
HlsMultivariantPlaylist multivariantPlaylist, HlsMediaPlaylist mediaPlaylist) {
this.multivariantPlaylist = multivariantPlaylist;
this.mediaPlaylist = mediaPlaylist; this.mediaPlaylist = mediaPlaylist;
this.masterPlaylist =
new HlsMasterPlaylist(
multivariantPlaylist.baseUri,
multivariantPlaylist.tags,
multivariantPlaylist.variants,
multivariantPlaylist.videos,
multivariantPlaylist.audios,
multivariantPlaylist.subtitles,
multivariantPlaylist.closedCaptions,
multivariantPlaylist.muxedAudioFormat,
multivariantPlaylist.muxedCaptionFormats,
multivariantPlaylist.hasIndependentSegments,
multivariantPlaylist.variableDefinitions,
multivariantPlaylist.sessionKeyDrmInitData);
} }
} }
...@@ -64,7 +64,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -64,7 +64,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param segmentBaseHolder The segment holder. * @param segmentBaseHolder The segment holder.
* @param playlistUrl The url of the playlist from which this chunk was obtained. * @param playlistUrl The url of the playlist from which this chunk was obtained.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist. * information is available in the multivariant playlist.
* @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionReason See {@link #trackSelectionReason}.
* @param trackSelectionData See {@link #trackSelectionData}. * @param trackSelectionData See {@link #trackSelectionData}.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
......
...@@ -36,9 +36,9 @@ import com.google.android.exoplayer2.source.SampleStream; ...@@ -36,9 +36,9 @@ import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
...@@ -178,17 +178,18 @@ public final class HlsMediaPeriod ...@@ -178,17 +178,18 @@ public final class HlsMediaPeriod
return Assertions.checkNotNull(trackGroups); return Assertions.checkNotNull(trackGroups);
} }
// TODO: When the master playlist does not de-duplicate variants by URL and allows Renditions with // TODO: When the multivariant playlist does not de-duplicate variants by URL and allows
// null URLs, this method must be updated to calculate stream keys that are compatible with those // Renditions with null URLs, this method must be updated to calculate stream keys that are
// that may already be persisted for offline. // compatible with those that may already be persisted for offline.
@Override @Override
public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) { public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
// See HlsMasterPlaylist.copy for interpretation of StreamKeys. // See HlsMultivariantPlaylist.copy for interpretation of StreamKeys.
HlsMasterPlaylist masterPlaylist = Assertions.checkNotNull(playlistTracker.getMasterPlaylist()); HlsMultivariantPlaylist multivariantPlaylist =
boolean hasVariants = !masterPlaylist.variants.isEmpty(); Assertions.checkNotNull(playlistTracker.getMultivariantPlaylist());
boolean hasVariants = !multivariantPlaylist.variants.isEmpty();
int audioWrapperOffset = hasVariants ? 1 : 0; int audioWrapperOffset = hasVariants ? 1 : 0;
// Subtitle sample stream wrappers are held last. // Subtitle sample stream wrappers are held last.
int subtitleWrapperOffset = sampleStreamWrappers.length - masterPlaylist.subtitles.size(); int subtitleWrapperOffset = sampleStreamWrappers.length - multivariantPlaylist.subtitles.size();
TrackGroupArray mainWrapperTrackGroups; TrackGroupArray mainWrapperTrackGroups;
int mainWrapperPrimaryGroupIndex; int mainWrapperPrimaryGroupIndex;
...@@ -216,7 +217,8 @@ public final class HlsMediaPeriod ...@@ -216,7 +217,8 @@ public final class HlsMediaPeriod
hasPrimaryTrackGroupSelection = true; hasPrimaryTrackGroupSelection = true;
for (int i = 0; i < trackSelection.length(); i++) { for (int i = 0; i < trackSelection.length(); i++) {
int variantIndex = mainWrapperVariantIndices[trackSelection.getIndexInTrackGroup(i)]; int variantIndex = mainWrapperVariantIndices[trackSelection.getIndexInTrackGroup(i)];
streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, variantIndex)); streamKeys.add(
new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, variantIndex));
} }
} else { } else {
// Embedded group in main wrapper. // Embedded group in main wrapper.
...@@ -230,8 +232,8 @@ public final class HlsMediaPeriod ...@@ -230,8 +232,8 @@ public final class HlsMediaPeriod
if (selectedTrackGroupIndex != C.INDEX_UNSET) { if (selectedTrackGroupIndex != C.INDEX_UNSET) {
int groupIndexType = int groupIndexType =
i < subtitleWrapperOffset i < subtitleWrapperOffset
? HlsMasterPlaylist.GROUP_INDEX_AUDIO ? HlsMultivariantPlaylist.GROUP_INDEX_AUDIO
: HlsMasterPlaylist.GROUP_INDEX_SUBTITLE; : HlsMultivariantPlaylist.GROUP_INDEX_SUBTITLE;
int[] selectedWrapperUrlIndices = manifestUrlIndicesPerWrapper[i]; int[] selectedWrapperUrlIndices = manifestUrlIndicesPerWrapper[i];
for (int trackIndex = 0; trackIndex < trackSelection.length(); trackIndex++) { for (int trackIndex = 0; trackIndex < trackSelection.length(); trackIndex++) {
int renditionIndex = int renditionIndex =
...@@ -247,16 +249,18 @@ public final class HlsMediaPeriod ...@@ -247,16 +249,18 @@ public final class HlsMediaPeriod
// A track selection includes a variant-embedded track, but no variant is added yet. We use // A track selection includes a variant-embedded track, but no variant is added yet. We use
// the valid variant with the lowest bitrate to reduce overhead. // the valid variant with the lowest bitrate to reduce overhead.
int lowestBitrateIndex = mainWrapperVariantIndices[0]; int lowestBitrateIndex = mainWrapperVariantIndices[0];
int lowestBitrate = masterPlaylist.variants.get(mainWrapperVariantIndices[0]).format.bitrate; int lowestBitrate =
multivariantPlaylist.variants.get(mainWrapperVariantIndices[0]).format.bitrate;
for (int i = 1; i < mainWrapperVariantIndices.length; i++) { for (int i = 1; i < mainWrapperVariantIndices.length; i++) {
int variantBitrate = int variantBitrate =
masterPlaylist.variants.get(mainWrapperVariantIndices[i]).format.bitrate; multivariantPlaylist.variants.get(mainWrapperVariantIndices[i]).format.bitrate;
if (variantBitrate < lowestBitrate) { if (variantBitrate < lowestBitrate) {
lowestBitrate = variantBitrate; lowestBitrate = variantBitrate;
lowestBitrateIndex = mainWrapperVariantIndices[i]; lowestBitrateIndex = mainWrapperVariantIndices[i];
} }
} }
streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, lowestBitrateIndex)); streamKeys.add(
new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, lowestBitrateIndex));
} }
return streamKeys; return streamKeys;
} }
...@@ -489,15 +493,16 @@ public final class HlsMediaPeriod ...@@ -489,15 +493,16 @@ public final class HlsMediaPeriod
// Internal methods. // Internal methods.
private void buildAndPrepareSampleStreamWrappers(long positionUs) { private void buildAndPrepareSampleStreamWrappers(long positionUs) {
HlsMasterPlaylist masterPlaylist = Assertions.checkNotNull(playlistTracker.getMasterPlaylist()); HlsMultivariantPlaylist multivariantPlaylist =
Assertions.checkNotNull(playlistTracker.getMultivariantPlaylist());
Map<String, DrmInitData> overridingDrmInitData = Map<String, DrmInitData> overridingDrmInitData =
useSessionKeys useSessionKeys
? deriveOverridingDrmInitData(masterPlaylist.sessionKeyDrmInitData) ? deriveOverridingDrmInitData(multivariantPlaylist.sessionKeyDrmInitData)
: Collections.emptyMap(); : Collections.emptyMap();
boolean hasVariants = !masterPlaylist.variants.isEmpty(); boolean hasVariants = !multivariantPlaylist.variants.isEmpty();
List<Rendition> audioRenditions = masterPlaylist.audios; List<Rendition> audioRenditions = multivariantPlaylist.audios;
List<Rendition> subtitleRenditions = masterPlaylist.subtitles; List<Rendition> subtitleRenditions = multivariantPlaylist.subtitles;
pendingPrepareCount = 0; pendingPrepareCount = 0;
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>(); ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
...@@ -505,7 +510,7 @@ public final class HlsMediaPeriod ...@@ -505,7 +510,7 @@ public final class HlsMediaPeriod
if (hasVariants) { if (hasVariants) {
buildAndPrepareMainSampleStreamWrapper( buildAndPrepareMainSampleStreamWrapper(
masterPlaylist, multivariantPlaylist,
positionUs, positionUs,
sampleStreamWrappers, sampleStreamWrappers,
manifestUrlIndicesPerWrapper, manifestUrlIndicesPerWrapper,
...@@ -523,7 +528,8 @@ public final class HlsMediaPeriod ...@@ -523,7 +528,8 @@ public final class HlsMediaPeriod
audioVideoSampleStreamWrapperCount = sampleStreamWrappers.size(); audioVideoSampleStreamWrapperCount = sampleStreamWrappers.size();
// Subtitle stream wrappers. We can always use master playlist information to prepare these. // Subtitle stream wrappers. We can always use multivariant playlist information to prepare
// these.
for (int i = 0; i < subtitleRenditions.size(); i++) { for (int i = 0; i < subtitleRenditions.size(); i++) {
Rendition subtitleRendition = subtitleRenditions.get(i); Rendition subtitleRendition = subtitleRenditions.get(i);
String sampleStreamWrapperUid = "subtitle:" + i + ":" + subtitleRendition.name; String sampleStreamWrapperUid = "subtitle:" + i + ":" + subtitleRendition.name;
...@@ -539,7 +545,7 @@ public final class HlsMediaPeriod ...@@ -539,7 +545,7 @@ public final class HlsMediaPeriod
positionUs); positionUs);
manifestUrlIndicesPerWrapper.add(new int[] {i}); manifestUrlIndicesPerWrapper.add(new int[] {i});
sampleStreamWrappers.add(sampleStreamWrapper); sampleStreamWrappers.add(sampleStreamWrapper);
sampleStreamWrapper.prepareWithMasterPlaylistInfo( sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, subtitleRendition.format)}, new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, subtitleRendition.format)},
/* primaryTrackGroupIndex= */ 0); /* primaryTrackGroupIndex= */ 0);
} }
...@@ -560,8 +566,8 @@ public final class HlsMediaPeriod ...@@ -560,8 +566,8 @@ public final class HlsMediaPeriod
* This method creates and starts preparation of the main {@link HlsSampleStreamWrapper}. * This method creates and starts preparation of the main {@link HlsSampleStreamWrapper}.
* *
* <p>The main sample stream wrapper is the first element of {@link #sampleStreamWrappers}. It * <p>The main sample stream wrapper is the first element of {@link #sampleStreamWrappers}. It
* provides {@link SampleStream}s for the variant urls in the master playlist. It may be adaptive * provides {@link SampleStream}s for the variant urls in the multivariant playlist. It may be
* and may contain multiple muxed tracks. * adaptive and may contain multiple muxed tracks.
* *
* <p>If chunkless preparation is allowed, the media period will try preparation without segment * <p>If chunkless preparation is allowed, the media period will try preparation without segment
* downloads. This is only possible if variants contain the CODECS attribute. If not, traditional * downloads. This is only possible if variants contain the CODECS attribute. If not, traditional
...@@ -570,13 +576,13 @@ public final class HlsMediaPeriod ...@@ -570,13 +576,13 @@ public final class HlsMediaPeriod
* *
* <ul> * <ul>
* <li>A muxed audio track will be exposed if the codecs list contain an audio entry and the * <li>A muxed audio track will be exposed if the codecs list contain an audio entry and the
* master playlist either contains an EXT-X-MEDIA tag without the URI attribute or does not * multivariant playlist either contains an EXT-X-MEDIA tag without the URI attribute or
* contain any EXT-X-MEDIA tag. * does not contain any EXT-X-MEDIA tag.
* <li>Closed captions will only be exposed if they are declared by the master playlist. * <li>Closed captions will only be exposed if they are declared by the multivariant playlist.
* <li>An ID3 track is exposed preemptively, in case the segments contain an ID3 track. * <li>An ID3 track is exposed preemptively, in case the segments contain an ID3 track.
* </ul> * </ul>
* *
* @param masterPlaylist The HLS master playlist. * @param multivariantPlaylist The HLS multivariant playlist.
* @param positionUs If preparation requires any chunk downloads, the position in microseconds at * @param positionUs If preparation requires any chunk downloads, the position in microseconds at
* which downloading should start. Ignored otherwise. * which downloading should start. Ignored otherwise.
* @param sampleStreamWrappers List to which the built main sample stream wrapper should be added. * @param sampleStreamWrappers List to which the built main sample stream wrapper should be added.
...@@ -585,16 +591,16 @@ public final class HlsMediaPeriod ...@@ -585,16 +591,16 @@ public final class HlsMediaPeriod
* (i.e. {@link DrmInitData#schemeType}). * (i.e. {@link DrmInitData#schemeType}).
*/ */
private void buildAndPrepareMainSampleStreamWrapper( private void buildAndPrepareMainSampleStreamWrapper(
HlsMasterPlaylist masterPlaylist, HlsMultivariantPlaylist multivariantPlaylist,
long positionUs, long positionUs,
List<HlsSampleStreamWrapper> sampleStreamWrappers, List<HlsSampleStreamWrapper> sampleStreamWrappers,
List<int[]> manifestUrlIndicesPerWrapper, List<int[]> manifestUrlIndicesPerWrapper,
Map<String, DrmInitData> overridingDrmInitData) { Map<String, DrmInitData> overridingDrmInitData) {
int[] variantTypes = new int[masterPlaylist.variants.size()]; int[] variantTypes = new int[multivariantPlaylist.variants.size()];
int videoVariantCount = 0; int videoVariantCount = 0;
int audioVariantCount = 0; int audioVariantCount = 0;
for (int i = 0; i < masterPlaylist.variants.size(); i++) { for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
Variant variant = masterPlaylist.variants.get(i); Variant variant = multivariantPlaylist.variants.get(i);
Format format = variant.format; Format format = variant.format;
if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) { if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) {
variantTypes[i] = C.TRACK_TYPE_VIDEO; variantTypes[i] = C.TRACK_TYPE_VIDEO;
...@@ -611,8 +617,8 @@ public final class HlsMediaPeriod ...@@ -611,8 +617,8 @@ public final class HlsMediaPeriod
int selectedVariantsCount = variantTypes.length; int selectedVariantsCount = variantTypes.length;
if (videoVariantCount > 0) { if (videoVariantCount > 0) {
// We've identified some variants as definitely containing video. Assume variants within the // 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 // multivariant playlist are marked consistently, and hence that we have the full set. Filter
// any other variants, which are likely to be audio only. // out any other variants, which are likely to be audio only.
useVideoVariantsOnly = true; useVideoVariantsOnly = true;
selectedVariantsCount = videoVariantCount; selectedVariantsCount = videoVariantCount;
} else if (audioVariantCount < variantTypes.length) { } else if (audioVariantCount < variantTypes.length) {
...@@ -625,10 +631,10 @@ public final class HlsMediaPeriod ...@@ -625,10 +631,10 @@ public final class HlsMediaPeriod
Format[] selectedPlaylistFormats = new Format[selectedVariantsCount]; Format[] selectedPlaylistFormats = new Format[selectedVariantsCount];
int[] selectedVariantIndices = new int[selectedVariantsCount]; int[] selectedVariantIndices = new int[selectedVariantsCount];
int outIndex = 0; int outIndex = 0;
for (int i = 0; i < masterPlaylist.variants.size(); i++) { for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO) if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO)
&& (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) { && (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) {
Variant variant = masterPlaylist.variants.get(i); Variant variant = multivariantPlaylist.variants.get(i);
selectedPlaylistUrls[outIndex] = variant.url; selectedPlaylistUrls[outIndex] = variant.url;
selectedPlaylistFormats[outIndex] = variant.format; selectedPlaylistFormats[outIndex] = variant.format;
selectedVariantIndices[outIndex++] = i; selectedVariantIndices[outIndex++] = i;
...@@ -653,8 +659,8 @@ public final class HlsMediaPeriod ...@@ -653,8 +659,8 @@ public final class HlsMediaPeriod
trackType, trackType,
selectedPlaylistUrls, selectedPlaylistUrls,
selectedPlaylistFormats, selectedPlaylistFormats,
masterPlaylist.muxedAudioFormat, multivariantPlaylist.muxedAudioFormat,
masterPlaylist.muxedCaptionFormats, multivariantPlaylist.muxedCaptionFormats,
overridingDrmInitData, overridingDrmInitData,
positionUs); positionUs);
sampleStreamWrappers.add(sampleStreamWrapper); sampleStreamWrappers.add(sampleStreamWrapper);
...@@ -669,16 +675,17 @@ public final class HlsMediaPeriod ...@@ -669,16 +675,17 @@ public final class HlsMediaPeriod
muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, videoFormats)); muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, videoFormats));
if (numberOfAudioCodecs > 0 if (numberOfAudioCodecs > 0
&& (masterPlaylist.muxedAudioFormat != null || masterPlaylist.audios.isEmpty())) { && (multivariantPlaylist.muxedAudioFormat != null
|| multivariantPlaylist.audios.isEmpty())) {
muxedTrackGroups.add( muxedTrackGroups.add(
new TrackGroup( new TrackGroup(
/* id= */ sampleStreamWrapperUid + ":audio", /* id= */ sampleStreamWrapperUid + ":audio",
deriveAudioFormat( deriveAudioFormat(
selectedPlaylistFormats[0], selectedPlaylistFormats[0],
masterPlaylist.muxedAudioFormat, multivariantPlaylist.muxedAudioFormat,
/* isPrimaryTrackInVariant= */ false))); /* isPrimaryTrackInVariant= */ false)));
} }
List<Format> ccFormats = masterPlaylist.muxedCaptionFormats; List<Format> ccFormats = multivariantPlaylist.muxedCaptionFormats;
if (ccFormats != null) { if (ccFormats != null) {
for (int i = 0; i < ccFormats.size(); i++) { for (int i = 0; i < ccFormats.size(); i++) {
String ccId = sampleStreamWrapperUid + ":cc:" + i; String ccId = sampleStreamWrapperUid + ":cc:" + i;
...@@ -692,7 +699,7 @@ public final class HlsMediaPeriod ...@@ -692,7 +699,7 @@ public final class HlsMediaPeriod
audioFormats[i] = audioFormats[i] =
deriveAudioFormat( deriveAudioFormat(
/* variantFormat= */ selectedPlaylistFormats[i], /* variantFormat= */ selectedPlaylistFormats[i],
masterPlaylist.muxedAudioFormat, multivariantPlaylist.muxedAudioFormat,
/* isPrimaryTrackInVariant= */ true); /* isPrimaryTrackInVariant= */ true);
} }
muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, audioFormats)); muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, audioFormats));
...@@ -707,7 +714,7 @@ public final class HlsMediaPeriod ...@@ -707,7 +714,7 @@ public final class HlsMediaPeriod
.build()); .build());
muxedTrackGroups.add(id3TrackGroup); muxedTrackGroups.add(id3TrackGroup);
sampleStreamWrapper.prepareWithMasterPlaylistInfo( sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
muxedTrackGroups.toArray(new TrackGroup[0]), muxedTrackGroups.toArray(new TrackGroup[0]),
/* primaryTrackGroupIndex= */ 0, /* primaryTrackGroupIndex= */ 0,
/* optionalTrackGroupsIndices...= */ muxedTrackGroups.indexOf(id3TrackGroup)); /* optionalTrackGroupsIndices...= */ muxedTrackGroups.indexOf(id3TrackGroup));
...@@ -768,7 +775,7 @@ public final class HlsMediaPeriod ...@@ -768,7 +775,7 @@ public final class HlsMediaPeriod
if (allowChunklessPreparation && codecStringsAllowChunklessPreparation) { if (allowChunklessPreparation && codecStringsAllowChunklessPreparation) {
Format[] renditionFormats = scratchPlaylistFormats.toArray(new Format[0]); Format[] renditionFormats = scratchPlaylistFormats.toArray(new Format[0]);
sampleStreamWrapper.prepareWithMasterPlaylistInfo( sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, renditionFormats)}, new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, renditionFormats)},
/* primaryTrackGroupIndex= */ 0); /* primaryTrackGroupIndex= */ 0);
} }
......
...@@ -238,7 +238,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -238,7 +238,8 @@ public final class HlsMediaSource extends BaseMediaSource
/** /**
* Sets whether chunkless preparation is allowed. If true, preparation without chunk downloads * Sets whether chunkless preparation is allowed. If true, preparation without chunk downloads
* will be enabled for streams that provide sufficient information in their master playlist. * will be enabled for streams that provide sufficient information in their multivariant
* playlist.
* *
* @param allowChunklessPreparation Whether chunkless preparation is allowed. * @param allowChunklessPreparation Whether chunkless preparation is allowed.
* @return This factory, for convenience. * @return This factory, for convenience.
...@@ -273,10 +274,10 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -273,10 +274,10 @@ public final class HlsMediaSource extends BaseMediaSource
} }
/** /**
* Sets whether to use #EXT-X-SESSION-KEY tags provided in the master playlist. If enabled, it's * Sets whether to use #EXT-X-SESSION-KEY tags provided in the multivariant playlist. If
* assumed that any single session key declared in the master playlist can be used to obtain all * enabled, it's assumed that any single session key declared in the multivariant playlist can
* of the keys required for playback. For media where this is not true, this option should not * be used to obtain all of the keys required for playback. For media where this is not true,
* be enabled. * this option should not be enabled.
* *
* @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags. * @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags.
* @return This factory, for convenience. * @return This factory, for convenience.
...@@ -524,9 +525,9 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -524,9 +525,9 @@ public final class HlsMediaSource extends BaseMediaSource
|| mediaPlaylist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD || mediaPlaylist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD
? windowStartTimeMs ? windowStartTimeMs
: C.TIME_UNSET; : C.TIME_UNSET;
// The master playlist is non-null because the first playlist has been fetched by now. // The multivariant playlist is non-null because the first playlist has been fetched by now.
HlsManifest manifest = HlsManifest manifest =
new HlsManifest(checkNotNull(playlistTracker.getMasterPlaylist()), mediaPlaylist); new HlsManifest(checkNotNull(playlistTracker.getMultivariantPlaylist()), mediaPlaylist);
SinglePeriodTimeline timeline = SinglePeriodTimeline timeline =
playlistTracker.isLive() playlistTracker.isLive()
? createTimelineForLive( ? createTimelineForLive(
......
...@@ -103,7 +103,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -103,7 +103,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Called when the wrapper has been prepared. * Called when the wrapper has been prepared.
* *
* <p>Note: This method will be called on a later handler loop than the one on which either * <p>Note: This method will be called on a later handler loop than the one on which either
* {@link #prepareWithMasterPlaylistInfo} or {@link #continuePreparing} are invoked. * {@link #prepareWithMultivariantPlaylistInfo} or {@link #continuePreparing} are invoked.
*/ */
void onPrepared(); void onPrepared();
...@@ -197,7 +197,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -197,7 +197,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* stream's {@link DrmInitData} will be overridden. * stream's {@link DrmInitData} will be overridden.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The position from which to start loading media. * @param positionUs The position from which to start loading media.
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist. * @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the multivariant
* playlist.
* @param drmSessionManager The {@link DrmSessionManager} to acquire {@link DrmSession * @param drmSessionManager The {@link DrmSessionManager} to acquire {@link DrmSession
* DrmSessions} with. * DrmSessions} with.
* @param drmEventDispatcher A dispatcher to notify of {@link DrmSessionEventListener} events. * @param drmEventDispatcher A dispatcher to notify of {@link DrmSessionEventListener} events.
...@@ -261,7 +262,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -261,7 +262,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Prepares the sample stream wrapper with master playlist information. * Prepares the sample stream wrapper with multivariant playlist information.
* *
* @param trackGroups The {@link TrackGroup TrackGroups} to expose through {@link * @param trackGroups The {@link TrackGroup TrackGroups} to expose through {@link
* #getTrackGroups()}. * #getTrackGroups()}.
...@@ -269,7 +270,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -269,7 +270,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param optionalTrackGroupsIndices The indices of any {@code trackGroups} that should not * @param optionalTrackGroupsIndices The indices of any {@code trackGroups} that should not
* trigger a failure if not found in the media playlist's segments. * trigger a failure if not found in the media playlist's segments.
*/ */
public void prepareWithMasterPlaylistInfo( public void prepareWithMultivariantPlaylistInfo(
TrackGroup[] trackGroups, int primaryTrackGroupIndex, int... optionalTrackGroupsIndices) { TrackGroup[] trackGroups, int primaryTrackGroupIndex, int... optionalTrackGroupsIndices) {
this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups); this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups);
optionalTrackGroups = new HashSet<>(); optionalTrackGroups = new HashSet<>();
...@@ -1298,8 +1299,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1298,8 +1299,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
} }
if (trackGroups != null) { if (trackGroups != null) {
// The track groups were created with master playlist information. They only need to be mapped // The track groups were created with multivariant playlist information. They only need to be
// to a sample queue. // mapped to a sample queue.
mapSampleQueuesToMatchTrackGroups(); mapSampleQueuesToMatchTrackGroups();
} else { } else {
// Tracks are created using media segment information. // Tracks are created using media segment information.
...@@ -1334,18 +1335,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1334,18 +1335,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as * Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as
* internal data-structures required for operation. * internal data-structures required for operation.
* *
* <p>Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each * <p>Tracks in HLS are complicated. A HLS multivariant playlist contains a number of "variants".
* variant stream typically contains muxed video, audio and (possibly) additional audio, metadata * Each variant stream typically contains muxed video, audio and (possibly) additional audio,
* and caption tracks. We wish to allow the user to select between an adaptive track that spans * metadata and caption tracks. We wish to allow the user to select between an adaptive track that
* all variants, as well as each individual variant. If multiple audio tracks are present within * spans all variants, as well as each individual variant. If multiple audio tracks are present
* each variant then we wish to allow the user to select between those also. * within each variant then we wish to allow the user to select between those also.
* *
* <p>To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) * <p>To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1)
* tracks, where N is the number of variants defined in the HLS master playlist. These consist of * tracks, where N is the number of variants defined in the HLS multivariant playlist. These
* one adaptive track defined to span all variants and a track for each individual variant. The * consist of one adaptive track defined to span all variants and a track for each individual
* adaptive track is initially selected. The extractor is then prepared to discover the tracks * variant. The adaptive track is initially selected. The extractor is then prepared to discover
* inside of each variant stream. The two sets of tracks are then combined by this method to * the tracks inside of each variant stream. The two sets of tracks are then combined by this
* create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}: * method to create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}:
* *
* <ul> * <ul>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is * <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is
...@@ -1518,14 +1519,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1518,14 +1519,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Derives a track sample format from the corresponding format in the master playlist, and a * Derives a track sample format from the corresponding format in the multivariant playlist, and a
* sample format that may have been obtained from a chunk belonging to a different track in the * sample format that may have been obtained from a chunk belonging to a different track in the
* same track group. * same track group.
* *
* <p>Note: Since the sample format may have been obtained from a chunk belonging to a different * <p>Note: Since the sample format may have been obtained from a chunk belonging to a different
* track, it should not be used as a source for data that may vary between tracks. * track, it should not be used as a source for data that may vary between tracks.
* *
* @param playlistFormat The format information obtained from the master playlist. * @param playlistFormat The format information obtained from the multivariant playlist.
* @param sampleFormat The format information obtained from samples within a chunk. The chunk may * @param sampleFormat The format information obtained from samples within a chunk. The chunk may
* belong to a different track in the same track group. * belong to a different track in the same track group.
* @param propagateBitrates Whether the bitrates from the playlist format should be included in * @param propagateBitrates Whether the bitrates from the playlist format should be included in
......
...@@ -19,8 +19,8 @@ import android.net.Uri; ...@@ -19,8 +19,8 @@ import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.SegmentDownloader;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -45,14 +45,14 @@ import java.util.concurrent.Executor; ...@@ -45,14 +45,14 @@ import java.util.concurrent.Executor;
* new CacheDataSource.Factory() * new CacheDataSource.Factory()
* .setCache(cache) * .setCache(cache)
* .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory()); * .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
* // Create a downloader for the first variant in a master playlist. * // Create a downloader for the first variant in a multivariant playlist.
* HlsDownloader hlsDownloader = * HlsDownloader hlsDownloader =
* new HlsDownloader( * new HlsDownloader(
* new MediaItem.Builder() * new MediaItem.Builder()
* .setUri(playlistUri) * .setUri(playlistUri)
* .setStreamKeys( * .setStreamKeys(
* Collections.singletonList( * Collections.singletonList(
* new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, 0))) * new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0)))
* .build(), * .build(),
* Collections.singletonList(); * Collections.singletonList();
* // Perform the download. * // Perform the download.
...@@ -113,9 +113,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -113,9 +113,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
protected List<Segment> getSegments(DataSource dataSource, HlsPlaylist playlist, boolean removing) protected List<Segment> getSegments(DataSource dataSource, HlsPlaylist playlist, boolean removing)
throws IOException, InterruptedException { throws IOException, InterruptedException {
ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>(); ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
if (playlist instanceof HlsMasterPlaylist) { if (playlist instanceof HlsMultivariantPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMultivariantPlaylist multivariantPlaylist = (HlsMultivariantPlaylist) playlist;
addMediaPlaylistDataSpecs(masterPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs); addMediaPlaylistDataSpecs(multivariantPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs);
} else { } else {
mediaPlaylistDataSpecs.add( mediaPlaylistDataSpecs.add(
SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri))); SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri)));
......
...@@ -28,7 +28,8 @@ public final class DefaultHlsPlaylistParserFactory implements HlsPlaylistParserF ...@@ -28,7 +28,8 @@ public final class DefaultHlsPlaylistParserFactory implements HlsPlaylistParserF
@Override @Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser( public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { HlsMultivariantPlaylist multivariantPlaylist,
return new HlsPlaylistParser(masterPlaylist, previousMediaPlaylist); @Nullable HlsMediaPlaylist previousMediaPlaylist) {
return new HlsPlaylistParser(multivariantPlaylist, previousMediaPlaylist);
} }
} }
...@@ -29,10 +29,10 @@ import com.google.android.exoplayer2.source.LoadEventInfo; ...@@ -29,10 +29,10 @@ import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory; import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
...@@ -72,7 +72,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -72,7 +72,7 @@ public final class DefaultHlsPlaylistTracker
@Nullable private Loader initialPlaylistLoader; @Nullable private Loader initialPlaylistLoader;
@Nullable private Handler playlistRefreshHandler; @Nullable private Handler playlistRefreshHandler;
@Nullable private PrimaryPlaylistListener primaryPlaylistListener; @Nullable private PrimaryPlaylistListener primaryPlaylistListener;
@Nullable private HlsMasterPlaylist masterPlaylist; @Nullable private HlsMultivariantPlaylist multivariantPlaylist;
@Nullable private Uri primaryMediaPlaylistUrl; @Nullable private Uri primaryMediaPlaylistUrl;
@Nullable private HlsMediaPlaylist primaryMediaPlaylistSnapshot; @Nullable private HlsMediaPlaylist primaryMediaPlaylistSnapshot;
private boolean isLive; private boolean isLive;
...@@ -131,30 +131,33 @@ public final class DefaultHlsPlaylistTracker ...@@ -131,30 +131,33 @@ public final class DefaultHlsPlaylistTracker
this.playlistRefreshHandler = Util.createHandlerForCurrentLooper(); this.playlistRefreshHandler = Util.createHandlerForCurrentLooper();
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.primaryPlaylistListener = primaryPlaylistListener; this.primaryPlaylistListener = primaryPlaylistListener;
ParsingLoadable<HlsPlaylist> masterPlaylistLoadable = ParsingLoadable<HlsPlaylist> multivariantPlaylistLoadable =
new ParsingLoadable<>( new ParsingLoadable<>(
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
initialPlaylistUri, initialPlaylistUri,
C.DATA_TYPE_MANIFEST, C.DATA_TYPE_MANIFEST,
playlistParserFactory.createPlaylistParser()); playlistParserFactory.createPlaylistParser());
Assertions.checkState(initialPlaylistLoader == null); Assertions.checkState(initialPlaylistLoader == null);
initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MasterPlaylist"); initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MultivariantPlaylist");
long elapsedRealtime = long elapsedRealtime =
initialPlaylistLoader.startLoading( initialPlaylistLoader.startLoading(
masterPlaylistLoadable, multivariantPlaylistLoadable,
this, this,
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(masterPlaylistLoadable.type)); loadErrorHandlingPolicy.getMinimumLoadableRetryCount(
multivariantPlaylistLoadable.type));
eventDispatcher.loadStarted( eventDispatcher.loadStarted(
new LoadEventInfo( new LoadEventInfo(
masterPlaylistLoadable.loadTaskId, masterPlaylistLoadable.dataSpec, elapsedRealtime), multivariantPlaylistLoadable.loadTaskId,
masterPlaylistLoadable.type); multivariantPlaylistLoadable.dataSpec,
elapsedRealtime),
multivariantPlaylistLoadable.type);
} }
@Override @Override
public void stop() { public void stop() {
primaryMediaPlaylistUrl = null; primaryMediaPlaylistUrl = null;
primaryMediaPlaylistSnapshot = null; primaryMediaPlaylistSnapshot = null;
masterPlaylist = null; multivariantPlaylist = null;
initialStartTimeUs = C.TIME_UNSET; initialStartTimeUs = C.TIME_UNSET;
initialPlaylistLoader.release(); initialPlaylistLoader.release();
initialPlaylistLoader = null; initialPlaylistLoader = null;
...@@ -179,8 +182,8 @@ public final class DefaultHlsPlaylistTracker ...@@ -179,8 +182,8 @@ public final class DefaultHlsPlaylistTracker
@Override @Override
@Nullable @Nullable
public HlsMasterPlaylist getMasterPlaylist() { public HlsMultivariantPlaylist getMultivariantPlaylist() {
return masterPlaylist; return multivariantPlaylist;
} }
@Override @Override
...@@ -243,18 +246,19 @@ public final class DefaultHlsPlaylistTracker ...@@ -243,18 +246,19 @@ public final class DefaultHlsPlaylistTracker
public void onLoadCompleted( public void onLoadCompleted(
ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, long loadDurationMs) { ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, long loadDurationMs) {
HlsPlaylist result = loadable.getResult(); HlsPlaylist result = loadable.getResult();
HlsMasterPlaylist masterPlaylist; HlsMultivariantPlaylist multivariantPlaylist;
boolean isMediaPlaylist = result instanceof HlsMediaPlaylist; boolean isMediaPlaylist = result instanceof HlsMediaPlaylist;
if (isMediaPlaylist) { if (isMediaPlaylist) {
masterPlaylist = HlsMasterPlaylist.createSingleVariantMasterPlaylist(result.baseUri); multivariantPlaylist =
} else /* result instanceof HlsMasterPlaylist */ { HlsMultivariantPlaylist.createSingleVariantMultivariantPlaylist(result.baseUri);
masterPlaylist = (HlsMasterPlaylist) result; } else /* result instanceof HlsMultivariantPlaylist */ {
multivariantPlaylist = (HlsMultivariantPlaylist) result;
} }
this.masterPlaylist = masterPlaylist; this.multivariantPlaylist = multivariantPlaylist;
primaryMediaPlaylistUrl = masterPlaylist.variants.get(0).url; primaryMediaPlaylistUrl = multivariantPlaylist.variants.get(0).url;
// Add a temporary playlist listener for loading the first primary playlist. // Add a temporary playlist listener for loading the first primary playlist.
listeners.add(new FirstPrimaryMediaPlaylistListener()); listeners.add(new FirstPrimaryMediaPlaylistListener());
createBundles(masterPlaylist.mediaPlaylistUrls); createBundles(multivariantPlaylist.mediaPlaylistUrls);
LoadEventInfo loadEventInfo = LoadEventInfo loadEventInfo =
new LoadEventInfo( new LoadEventInfo(
loadable.loadTaskId, loadable.loadTaskId,
...@@ -327,7 +331,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -327,7 +331,7 @@ public final class DefaultHlsPlaylistTracker
// Internal methods. // Internal methods.
private boolean maybeSelectNewPrimaryUrl() { private boolean maybeSelectNewPrimaryUrl() {
List<Variant> variants = masterPlaylist.variants; List<Variant> variants = multivariantPlaylist.variants;
int variantsSize = variants.size(); int variantsSize = variants.size();
long currentTimeMs = SystemClock.elapsedRealtime(); long currentTimeMs = SystemClock.elapsedRealtime();
for (int i = 0; i < variantsSize; i++) { for (int i = 0; i < variantsSize; i++) {
...@@ -382,9 +386,12 @@ public final class DefaultHlsPlaylistTracker ...@@ -382,9 +386,12 @@ public final class DefaultHlsPlaylistTracker
return newPrimaryPlaylistUri; return newPrimaryPlaylistUri;
} }
/** Returns whether any of the variants in the master playlist have the specified playlist URL. */ /**
* Returns whether any of the variants in the multivariant playlist have the specified playlist
* URL.
*/
private boolean isVariantUrl(Uri playlistUrl) { private boolean isVariantUrl(Uri playlistUrl) {
List<Variant> variants = masterPlaylist.variants; List<Variant> variants = multivariantPlaylist.variants;
for (int i = 0; i < variants.size(); i++) { for (int i = 0; i < variants.size(); i++) {
if (playlistUrl.equals(variants.get(i).url)) { if (playlistUrl.equals(variants.get(i).url)) {
return true; return true;
...@@ -687,7 +694,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -687,7 +694,7 @@ public final class DefaultHlsPlaylistTracker
private void loadPlaylistImmediately(Uri playlistRequestUri) { private void loadPlaylistImmediately(Uri playlistRequestUri) {
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser = ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
playlistParserFactory.createPlaylistParser(masterPlaylist, playlistSnapshot); playlistParserFactory.createPlaylistParser(multivariantPlaylist, playlistSnapshot);
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable = ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
new ParsingLoadable<>( new ParsingLoadable<>(
mediaPlaylistDataSource, mediaPlaylistDataSource,
...@@ -823,7 +830,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -823,7 +830,7 @@ public final class DefaultHlsPlaylistTracker
if (primaryMediaPlaylistSnapshot == null) { if (primaryMediaPlaylistSnapshot == null) {
long nowMs = SystemClock.elapsedRealtime(); long nowMs = SystemClock.elapsedRealtime();
int variantExclusionCounter = 0; int variantExclusionCounter = 0;
List<Variant> variants = castNonNull(masterPlaylist).variants; List<Variant> variants = castNonNull(multivariantPlaylist).variants;
for (int i = 0; i < variants.size(); i++) { for (int i = 0; i < variants.size(); i++) {
@Nullable @Nullable
MediaPlaylistBundle mediaPlaylistBundle = playlistBundles.get(variants.get(i).url); MediaPlaylistBundle mediaPlaylistBundle = playlistBundles.get(variants.get(i).url);
...@@ -835,7 +842,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -835,7 +842,7 @@ public final class DefaultHlsPlaylistTracker
new LoadErrorHandlingPolicy.FallbackOptions( new LoadErrorHandlingPolicy.FallbackOptions(
/* numberOfLocations= */ 1, /* numberOfLocations= */ 1,
/* numberOfExcludedLocations= */ 0, /* numberOfExcludedLocations= */ 0,
/* numberOfTracks= */ masterPlaylist.variants.size(), /* numberOfTracks= */ multivariantPlaylist.variants.size(),
/* numberOfExcludedTracks= */ variantExclusionCounter); /* numberOfExcludedTracks= */ variantExclusionCounter);
@Nullable @Nullable
LoadErrorHandlingPolicy.FallbackSelection fallbackSelection = LoadErrorHandlingPolicy.FallbackSelection fallbackSelection =
......
...@@ -49,9 +49,10 @@ public final class FilteringHlsPlaylistParserFactory implements HlsPlaylistParse ...@@ -49,9 +49,10 @@ public final class FilteringHlsPlaylistParserFactory implements HlsPlaylistParse
@Override @Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser( public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { HlsMultivariantPlaylist multivariantPlaylist,
@Nullable HlsMediaPlaylist previousMediaPlaylist) {
return new FilteringManifestParser<>( return new FilteringManifestParser<>(
hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist, previousMediaPlaylist), hlsPlaylistParserFactory.createPlaylistParser(multivariantPlaylist, previousMediaPlaylist),
streamKeys); streamKeys);
} }
} }
...@@ -15,181 +15,22 @@ ...@@ -15,181 +15,22 @@
*/ */
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** Represents an HLS master playlist. */ /** @deprecated Use {@link HlsMultivariantPlaylist} instead. */
public final class HlsMasterPlaylist extends HlsPlaylist { @Deprecated
public final class HlsMasterPlaylist extends HlsMultivariantPlaylist {
/** Represents an empty master playlist, from which no attributes can be inherited. */
public static final HlsMasterPlaylist EMPTY =
new HlsMasterPlaylist(
/* baseUri= */ "",
/* tags= */ Collections.emptyList(),
/* variants= */ Collections.emptyList(),
/* videos= */ Collections.emptyList(),
/* audios= */ Collections.emptyList(),
/* subtitles= */ Collections.emptyList(),
/* closedCaptions= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
// These constants must not be changed because they are persisted in offline stream keys.
public static final int GROUP_INDEX_VARIANT = 0;
public static final int GROUP_INDEX_AUDIO = 1;
public static final int GROUP_INDEX_SUBTITLE = 2;
/** A variant (i.e. an #EXT-X-STREAM-INF tag) in a master playlist. */
public static final class Variant {
/** The variant's url. */
public final Uri url;
/** Format information associated with this variant. */
public final Format format;
/** The video rendition group referenced by this variant, or {@code null}. */
@Nullable public final String videoGroupId;
/** The audio rendition group referenced by this variant, or {@code null}. */
@Nullable public final String audioGroupId;
/** The subtitle rendition group referenced by this variant, or {@code null}. */
@Nullable public final String subtitleGroupId;
/** The caption rendition group referenced by this variant, or {@code null}. */
@Nullable public final String captionGroupId;
/**
* @param url See {@link #url}.
* @param format See {@link #format}.
* @param videoGroupId See {@link #videoGroupId}.
* @param audioGroupId See {@link #audioGroupId}.
* @param subtitleGroupId See {@link #subtitleGroupId}.
* @param captionGroupId See {@link #captionGroupId}.
*/
public Variant(
Uri url,
Format format,
@Nullable String videoGroupId,
@Nullable String audioGroupId,
@Nullable String subtitleGroupId,
@Nullable String captionGroupId) {
this.url = url;
this.format = format;
this.videoGroupId = videoGroupId;
this.audioGroupId = audioGroupId;
this.subtitleGroupId = subtitleGroupId;
this.captionGroupId = captionGroupId;
}
/**
* Creates a variant for a given media playlist url.
*
* @param url The media playlist url.
* @return The variant instance.
*/
public static Variant createMediaPlaylistVariantUrl(Uri url) {
Format format =
new Format.Builder().setId("0").setContainerMimeType(MimeTypes.APPLICATION_M3U8).build();
return new Variant(
url,
format,
/* videoGroupId= */ null,
/* audioGroupId= */ null,
/* subtitleGroupId= */ null,
/* captionGroupId= */ null);
}
/** Returns a copy of this instance with the given {@link Format}. */
public Variant copyWithFormat(Format format) {
return new Variant(url, format, videoGroupId, audioGroupId, subtitleGroupId, captionGroupId);
}
}
/** A rendition (i.e. an #EXT-X-MEDIA tag) in a master playlist. */
public static final class Rendition {
/** The rendition's url, or null if the tag does not have a URI attribute. */
@Nullable public final Uri url;
/** Format information associated with this rendition. */
public final Format format;
/** The group to which this rendition belongs. */
public final String groupId;
/** The name of the rendition. */
public final String name;
/**
* @param url See {@link #url}.
* @param format See {@link #format}.
* @param groupId See {@link #groupId}.
* @param name See {@link #name}.
*/
public Rendition(@Nullable Uri url, Format format, String groupId, String name) {
this.url = url;
this.format = format;
this.groupId = groupId;
this.name = name;
}
}
/** All of the media playlist URLs referenced by the playlist. */
public final List<Uri> mediaPlaylistUrls;
/** The variants declared by the playlist. */
public final List<Variant> variants;
/** The video renditions declared by the playlist. */
public final List<Rendition> videos;
/** The audio renditions declared by the playlist. */
public final List<Rendition> audios;
/** The subtitle renditions declared by the playlist. */
public final List<Rendition> subtitles;
/** The closed caption renditions declared by the playlist. */
public final List<Rendition> closedCaptions;
/**
* The format of the audio muxed in the variants. May be null if the playlist does not declare any
* muxed audio.
*/
@Nullable public final Format muxedAudioFormat;
/**
* The format of the closed captions declared by the playlist. May be empty if the playlist
* explicitly declares no captions are available, or null if the playlist does not declare any
* captions information.
*/
@Nullable public final List<Format> muxedCaptionFormats;
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
public final Map<String, String> variableDefinitions;
/** DRM initialization data derived from #EXT-X-SESSION-KEY tags. */
public final List<DrmInitData> sessionKeyDrmInitData;
/** /**
* @param baseUri See {@link #baseUri}. * Creates an HLS multivariant playlist.
* @param tags See {@link #tags}. *
* @param variants See {@link #variants}. * @deprecated Use {@link HlsMultivariantPlaylist#HlsMultivariantPlaylist} instead.
* @param videos See {@link #videos}.
* @param audios See {@link #audios}.
* @param subtitles See {@link #subtitles}.
* @param closedCaptions See {@link #closedCaptions}.
* @param muxedAudioFormat See {@link #muxedAudioFormat}.
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param variableDefinitions See {@link #variableDefinitions}.
* @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}.
*/ */
@Deprecated
public HlsMasterPlaylist( public HlsMasterPlaylist(
String baseUri, String baseUri,
List<String> tags, List<String> tags,
...@@ -203,117 +44,18 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -203,117 +44,18 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
boolean hasIndependentSegments, boolean hasIndependentSegments,
Map<String, String> variableDefinitions, Map<String, String> variableDefinitions,
List<DrmInitData> sessionKeyDrmInitData) { List<DrmInitData> sessionKeyDrmInitData) {
super(baseUri, tags, hasIndependentSegments); super(
this.mediaPlaylistUrls =
Collections.unmodifiableList(
getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions));
this.variants = Collections.unmodifiableList(variants);
this.videos = Collections.unmodifiableList(videos);
this.audios = Collections.unmodifiableList(audios);
this.subtitles = Collections.unmodifiableList(subtitles);
this.closedCaptions = Collections.unmodifiableList(closedCaptions);
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormats =
muxedCaptionFormats != null ? Collections.unmodifiableList(muxedCaptionFormats) : null;
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData);
}
@Override
public HlsMasterPlaylist copy(List<StreamKey> streamKeys) {
return new HlsMasterPlaylist(
baseUri, baseUri,
tags, tags,
copyStreams(variants, GROUP_INDEX_VARIANT, streamKeys), variants,
// TODO: Allow stream keys to specify video renditions to be retained. videos,
/* videos= */ Collections.emptyList(), audios,
copyStreams(audios, GROUP_INDEX_AUDIO, streamKeys), subtitles,
copyStreams(subtitles, GROUP_INDEX_SUBTITLE, streamKeys), closedCaptions,
// TODO: Update to retain all closed captions.
/* closedCaptions= */ Collections.emptyList(),
muxedAudioFormat, muxedAudioFormat,
muxedCaptionFormats, muxedCaptionFormats,
hasIndependentSegments, hasIndependentSegments,
variableDefinitions, variableDefinitions,
sessionKeyDrmInitData); sessionKeyDrmInitData);
} }
/**
* Creates a playlist with a single variant.
*
* @param variantUrl The url of the single variant.
* @return A master playlist with a single variant for the provided url.
*/
public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) {
List<Variant> variant =
Collections.singletonList(Variant.createMediaPlaylistVariantUrl(Uri.parse(variantUrl)));
return new HlsMasterPlaylist(
/* baseUri= */ "",
/* tags= */ Collections.emptyList(),
variant,
/* videos= */ Collections.emptyList(),
/* audios= */ Collections.emptyList(),
/* subtitles= */ Collections.emptyList(),
/* closedCaptions= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
private static List<Uri> getMediaPlaylistUrls(
List<Variant> variants,
List<Rendition> videos,
List<Rendition> audios,
List<Rendition> subtitles,
List<Rendition> closedCaptions) {
ArrayList<Uri> mediaPlaylistUrls = new ArrayList<>();
for (int i = 0; i < variants.size(); i++) {
Uri uri = variants.get(i).url;
if (!mediaPlaylistUrls.contains(uri)) {
mediaPlaylistUrls.add(uri);
}
}
addMediaPlaylistUrls(videos, mediaPlaylistUrls);
addMediaPlaylistUrls(audios, mediaPlaylistUrls);
addMediaPlaylistUrls(subtitles, mediaPlaylistUrls);
addMediaPlaylistUrls(closedCaptions, mediaPlaylistUrls);
return mediaPlaylistUrls;
}
private static void addMediaPlaylistUrls(List<Rendition> renditions, List<Uri> out) {
for (int i = 0; i < renditions.size(); i++) {
Uri uri = renditions.get(i).url;
if (uri != null && !out.contains(uri)) {
out.add(uri);
}
}
}
private static <T> List<T> copyStreams(
List<T> streams, int groupIndex, List<StreamKey> streamKeys) {
List<T> copiedStreams = new ArrayList<>(streamKeys.size());
// TODO:
// 1. When variants with the same URL are not de-duplicated, duplicates must not increment
// trackIndex so as to avoid breaking stream keys that have been persisted for offline. All
// duplicates should be copied if the first variant is copied, or discarded otherwise.
// 2. When renditions with null URLs are permitted, they must not increment trackIndex so as to
// avoid breaking stream keys that have been persisted for offline. All renitions with null
// URLs should be copied. They may become unreachable if all variants that reference them are
// removed, but this is OK.
// 3. Renditions with URLs matching copied variants should always themselves be copied, even if
// the corresponding stream key is omitted. Else we're throwing away information for no gain.
for (int i = 0; i < streams.size(); i++) {
T stream = streams.get(i);
for (int j = 0; j < streamKeys.size(); j++) {
StreamKey streamKey = streamKeys.get(j);
if (streamKey.groupIndex == groupIndex && streamKey.streamIndex == i) {
copiedStreams.add(stream);
break;
}
}
}
return copiedStreams;
}
} }
/*
* Copyright (C) 2016 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.exoplayer2.source.hls.playlist;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/** Represents an HLS multivariant playlist. */
// TODO(b/211458101): Make non-final once HlsMasterPlaylist is removed.
public class HlsMultivariantPlaylist extends HlsPlaylist {
/** Represents an empty multivariant playlist, from which no attributes can be inherited. */
public static final HlsMultivariantPlaylist EMPTY =
new HlsMultivariantPlaylist(
/* baseUri= */ "",
/* tags= */ Collections.emptyList(),
/* variants= */ Collections.emptyList(),
/* videos= */ Collections.emptyList(),
/* audios= */ Collections.emptyList(),
/* subtitles= */ Collections.emptyList(),
/* closedCaptions= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
// These constants must not be changed because they are persisted in offline stream keys.
public static final int GROUP_INDEX_VARIANT = 0;
public static final int GROUP_INDEX_AUDIO = 1;
public static final int GROUP_INDEX_SUBTITLE = 2;
/** A variant (i.e. an #EXT-X-STREAM-INF tag) in a multivariant playlist. */
public static final class Variant {
/** The variant's url. */
public final Uri url;
/** Format information associated with this variant. */
public final Format format;
/** The video rendition group referenced by this variant, or {@code null}. */
@Nullable public final String videoGroupId;
/** The audio rendition group referenced by this variant, or {@code null}. */
@Nullable public final String audioGroupId;
/** The subtitle rendition group referenced by this variant, or {@code null}. */
@Nullable public final String subtitleGroupId;
/** The caption rendition group referenced by this variant, or {@code null}. */
@Nullable public final String captionGroupId;
/**
* @param url See {@link #url}.
* @param format See {@link #format}.
* @param videoGroupId See {@link #videoGroupId}.
* @param audioGroupId See {@link #audioGroupId}.
* @param subtitleGroupId See {@link #subtitleGroupId}.
* @param captionGroupId See {@link #captionGroupId}.
*/
public Variant(
Uri url,
Format format,
@Nullable String videoGroupId,
@Nullable String audioGroupId,
@Nullable String subtitleGroupId,
@Nullable String captionGroupId) {
this.url = url;
this.format = format;
this.videoGroupId = videoGroupId;
this.audioGroupId = audioGroupId;
this.subtitleGroupId = subtitleGroupId;
this.captionGroupId = captionGroupId;
}
/**
* Creates a variant for a given media playlist url.
*
* @param url The media playlist url.
* @return The variant instance.
*/
public static Variant createMediaPlaylistVariantUrl(Uri url) {
Format format =
new Format.Builder().setId("0").setContainerMimeType(MimeTypes.APPLICATION_M3U8).build();
return new Variant(
url,
format,
/* videoGroupId= */ null,
/* audioGroupId= */ null,
/* subtitleGroupId= */ null,
/* captionGroupId= */ null);
}
/** Returns a copy of this instance with the given {@link Format}. */
public Variant copyWithFormat(Format format) {
return new Variant(url, format, videoGroupId, audioGroupId, subtitleGroupId, captionGroupId);
}
}
/** A rendition (i.e. an #EXT-X-MEDIA tag) in a multivariant playlist. */
public static final class Rendition {
/** The rendition's url, or null if the tag does not have a URI attribute. */
@Nullable public final Uri url;
/** Format information associated with this rendition. */
public final Format format;
/** The group to which this rendition belongs. */
public final String groupId;
/** The name of the rendition. */
public final String name;
/**
* @param url See {@link #url}.
* @param format See {@link #format}.
* @param groupId See {@link #groupId}.
* @param name See {@link #name}.
*/
public Rendition(@Nullable Uri url, Format format, String groupId, String name) {
this.url = url;
this.format = format;
this.groupId = groupId;
this.name = name;
}
}
/** All of the media playlist URLs referenced by the playlist. */
public final List<Uri> mediaPlaylistUrls;
/** The variants declared by the playlist. */
public final List<Variant> variants;
/** The video renditions declared by the playlist. */
public final List<Rendition> videos;
/** The audio renditions declared by the playlist. */
public final List<Rendition> audios;
/** The subtitle renditions declared by the playlist. */
public final List<Rendition> subtitles;
/** The closed caption renditions declared by the playlist. */
public final List<Rendition> closedCaptions;
/**
* The format of the audio muxed in the variants. May be null if the playlist does not declare any
* muxed audio.
*/
@Nullable public final Format muxedAudioFormat;
/**
* The format of the closed captions declared by the playlist. May be empty if the playlist
* explicitly declares no captions are available, or null if the playlist does not declare any
* captions information.
*/
@Nullable public final List<Format> muxedCaptionFormats;
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
public final Map<String, String> variableDefinitions;
/** DRM initialization data derived from #EXT-X-SESSION-KEY tags. */
public final List<DrmInitData> sessionKeyDrmInitData;
/**
* @param baseUri See {@link #baseUri}.
* @param tags See {@link #tags}.
* @param variants See {@link #variants}.
* @param videos See {@link #videos}.
* @param audios See {@link #audios}.
* @param subtitles See {@link #subtitles}.
* @param closedCaptions See {@link #closedCaptions}.
* @param muxedAudioFormat See {@link #muxedAudioFormat}.
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param variableDefinitions See {@link #variableDefinitions}.
* @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}.
*/
public HlsMultivariantPlaylist(
String baseUri,
List<String> tags,
List<Variant> variants,
List<Rendition> videos,
List<Rendition> audios,
List<Rendition> subtitles,
List<Rendition> closedCaptions,
@Nullable Format muxedAudioFormat,
@Nullable List<Format> muxedCaptionFormats,
boolean hasIndependentSegments,
Map<String, String> variableDefinitions,
List<DrmInitData> sessionKeyDrmInitData) {
super(baseUri, tags, hasIndependentSegments);
this.mediaPlaylistUrls =
Collections.unmodifiableList(
getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions));
this.variants = Collections.unmodifiableList(variants);
this.videos = Collections.unmodifiableList(videos);
this.audios = Collections.unmodifiableList(audios);
this.subtitles = Collections.unmodifiableList(subtitles);
this.closedCaptions = Collections.unmodifiableList(closedCaptions);
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormats =
muxedCaptionFormats != null ? Collections.unmodifiableList(muxedCaptionFormats) : null;
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData);
}
@Override
public HlsMultivariantPlaylist copy(List<StreamKey> streamKeys) {
return new HlsMultivariantPlaylist(
baseUri,
tags,
copyStreams(variants, GROUP_INDEX_VARIANT, streamKeys),
// TODO: Allow stream keys to specify video renditions to be retained.
/* videos= */ Collections.emptyList(),
copyStreams(audios, GROUP_INDEX_AUDIO, streamKeys),
copyStreams(subtitles, GROUP_INDEX_SUBTITLE, streamKeys),
// TODO: Update to retain all closed captions.
/* closedCaptions= */ Collections.emptyList(),
muxedAudioFormat,
muxedCaptionFormats,
hasIndependentSegments,
variableDefinitions,
sessionKeyDrmInitData);
}
/**
* Creates a playlist with a single variant.
*
* @param variantUrl The url of the single variant.
* @return A multivariant playlist with a single variant for the provided url.
*/
public static HlsMultivariantPlaylist createSingleVariantMultivariantPlaylist(String variantUrl) {
List<Variant> variant =
Collections.singletonList(Variant.createMediaPlaylistVariantUrl(Uri.parse(variantUrl)));
return new HlsMultivariantPlaylist(
/* baseUri= */ "",
/* tags= */ Collections.emptyList(),
variant,
/* videos= */ Collections.emptyList(),
/* audios= */ Collections.emptyList(),
/* subtitles= */ Collections.emptyList(),
/* closedCaptions= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
private static List<Uri> getMediaPlaylistUrls(
List<Variant> variants,
List<Rendition> videos,
List<Rendition> audios,
List<Rendition> subtitles,
List<Rendition> closedCaptions) {
ArrayList<Uri> mediaPlaylistUrls = new ArrayList<>();
for (int i = 0; i < variants.size(); i++) {
Uri uri = variants.get(i).url;
if (!mediaPlaylistUrls.contains(uri)) {
mediaPlaylistUrls.add(uri);
}
}
addMediaPlaylistUrls(videos, mediaPlaylistUrls);
addMediaPlaylistUrls(audios, mediaPlaylistUrls);
addMediaPlaylistUrls(subtitles, mediaPlaylistUrls);
addMediaPlaylistUrls(closedCaptions, mediaPlaylistUrls);
return mediaPlaylistUrls;
}
private static void addMediaPlaylistUrls(List<Rendition> renditions, List<Uri> out) {
for (int i = 0; i < renditions.size(); i++) {
Uri uri = renditions.get(i).url;
if (uri != null && !out.contains(uri)) {
out.add(uri);
}
}
}
private static <T> List<T> copyStreams(
List<T> streams, int groupIndex, List<StreamKey> streamKeys) {
List<T> copiedStreams = new ArrayList<>(streamKeys.size());
// TODO:
// 1. When variants with the same URL are not de-duplicated, duplicates must not increment
// trackIndex so as to avoid breaking stream keys that have been persisted for offline. All
// duplicates should be copied if the first variant is copied, or discarded otherwise.
// 2. When renditions with null URLs are permitted, they must not increment trackIndex so as to
// avoid breaking stream keys that have been persisted for offline. All renitions with null
// URLs should be copied. They may become unreachable if all variants that reference them are
// removed, but this is OK.
// 3. Renditions with URLs matching copied variants should always themselves be copied, even if
// the corresponding stream key is omitted. Else we're throwing away information for no gain.
for (int i = 0; i < streams.size(); i++) {
T stream = streams.get(i);
for (int j = 0; j < streamKeys.size(); j++) {
StreamKey streamKey = streamKeys.get(j);
if (streamKey.groupIndex == groupIndex && streamKey.streamIndex == i) {
copiedStreams.add(stream);
break;
}
}
}
return copiedStreams;
}
}
...@@ -32,11 +32,11 @@ import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; ...@@ -32,11 +32,11 @@ import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry; import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo; import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
...@@ -223,28 +223,30 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -223,28 +223,30 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final Pattern REGEX_VARIABLE_REFERENCE = private static final Pattern REGEX_VARIABLE_REFERENCE =
Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}"); Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}");
private final HlsMasterPlaylist masterPlaylist; private final HlsMultivariantPlaylist multivariantPlaylist;
@Nullable private final HlsMediaPlaylist previousMediaPlaylist; @Nullable private final HlsMediaPlaylist previousMediaPlaylist;
/** /**
* Creates an instance where media playlists are parsed without inheriting attributes from a * Creates an instance where media playlists are parsed without inheriting attributes from a
* master playlist. * multivariant playlist.
*/ */
public HlsPlaylistParser() { public HlsPlaylistParser() {
this(HlsMasterPlaylist.EMPTY, /* previousMediaPlaylist= */ null); this(HlsMultivariantPlaylist.EMPTY, /* previousMediaPlaylist= */ null);
} }
/** /**
* Creates an instance where parsed media playlists inherit attributes from the given master * Creates an instance where parsed media playlists inherit attributes from the given master
* playlist. * playlist.
* *
* @param masterPlaylist The master playlist from which media playlists will inherit attributes. * @param multivariantPlaylist The multivariant playlist from which media playlists will inherit
* attributes.
* @param previousMediaPlaylist The previous media playlist from which the new media playlist may * @param previousMediaPlaylist The previous media playlist from which the new media playlist may
* inherit skipped segments. * inherit skipped segments.
*/ */
public HlsPlaylistParser( public HlsPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { HlsMultivariantPlaylist multivariantPlaylist,
this.masterPlaylist = masterPlaylist; @Nullable HlsMediaPlaylist previousMediaPlaylist) {
this.multivariantPlaylist = multivariantPlaylist;
this.previousMediaPlaylist = previousMediaPlaylist; this.previousMediaPlaylist = previousMediaPlaylist;
} }
...@@ -264,7 +266,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -264,7 +266,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
// Do nothing. // Do nothing.
} else if (line.startsWith(TAG_STREAM_INF)) { } else if (line.startsWith(TAG_STREAM_INF)) {
extraLines.add(line); extraLines.add(line);
return parseMasterPlaylist(new LineIterator(extraLines, reader), uri.toString()); return parseMultivariantPlaylist(new LineIterator(extraLines, reader), uri.toString());
} else if (line.startsWith(TAG_TARGET_DURATION) } else if (line.startsWith(TAG_TARGET_DURATION)
|| line.startsWith(TAG_MEDIA_SEQUENCE) || line.startsWith(TAG_MEDIA_SEQUENCE)
|| line.startsWith(TAG_MEDIA_DURATION) || line.startsWith(TAG_MEDIA_DURATION)
...@@ -275,7 +277,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -275,7 +277,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|| line.equals(TAG_ENDLIST)) { || line.equals(TAG_ENDLIST)) {
extraLines.add(line); extraLines.add(line);
return parseMediaPlaylist( return parseMediaPlaylist(
masterPlaylist, multivariantPlaylist,
previousMediaPlaylist, previousMediaPlaylist,
new LineIterator(extraLines, reader), new LineIterator(extraLines, reader),
uri.toString()); uri.toString());
...@@ -319,8 +321,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -319,8 +321,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return c; return c;
} }
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri) private static HlsMultivariantPlaylist parseMultivariantPlaylist(
throws IOException { LineIterator iterator, String baseUri) throws IOException {
HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>(); HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>();
HashMap<String, String> variableDefinitions = new HashMap<>(); HashMap<String, String> variableDefinitions = new HashMap<>();
ArrayList<Variant> variants = new ArrayList<>(); ArrayList<Variant> variants = new ArrayList<>();
...@@ -578,7 +580,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -578,7 +580,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
muxedCaptionFormats = Collections.emptyList(); muxedCaptionFormats = Collections.emptyList();
} }
return new HlsMasterPlaylist( return new HlsMultivariantPlaylist(
baseUri, baseUri,
tags, tags,
deduplicatedVariants, deduplicatedVariants,
...@@ -627,7 +629,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -627,7 +629,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} }
private static HlsMediaPlaylist parseMediaPlaylist( private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, HlsMultivariantPlaylist multivariantPlaylist,
@Nullable HlsMediaPlaylist previousMediaPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist,
LineIterator iterator, LineIterator iterator,
String baseUri) String baseUri)
...@@ -638,7 +640,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -638,7 +640,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int version = 1; // Default version == 1. int version = 1; // Default version == 1.
long targetDurationUs = C.TIME_UNSET; long targetDurationUs = C.TIME_UNSET;
long partTargetDurationUs = C.TIME_UNSET; long partTargetDurationUs = C.TIME_UNSET;
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments; boolean hasIndependentSegmentsTag = multivariantPlaylist.hasIndependentSegments;
boolean hasEndTag = false; boolean hasEndTag = false;
@Nullable Segment initializationSegment = null; @Nullable Segment initializationSegment = null;
HashMap<String, String> variableDefinitions = new HashMap<>(); HashMap<String, String> variableDefinitions = new HashMap<>();
...@@ -748,11 +750,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -748,11 +750,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} else if (line.startsWith(TAG_DEFINE)) { } else if (line.startsWith(TAG_DEFINE)) {
String importName = parseOptionalStringAttr(line, REGEX_IMPORT, variableDefinitions); String importName = parseOptionalStringAttr(line, REGEX_IMPORT, variableDefinitions);
if (importName != null) { if (importName != null) {
String value = masterPlaylist.variableDefinitions.get(importName); String value = multivariantPlaylist.variableDefinitions.get(importName);
if (value != null) { if (value != null) {
variableDefinitions.put(importName, value); variableDefinitions.put(importName, value);
} else { } else {
// The master playlist does not declare the imported variable. Ignore. // The multivariant playlist does not declare the imported variable. Ignore.
} }
} else { } else {
variableDefinitions.put( variableDefinitions.put(
......
...@@ -29,14 +29,16 @@ public interface HlsPlaylistParserFactory { ...@@ -29,14 +29,16 @@ public interface HlsPlaylistParserFactory {
/** /**
* Returns a playlist parser for playlists that were referenced by the given {@link * Returns a playlist parser for playlists that were referenced by the given {@link
* HlsMasterPlaylist}. Returned {@link HlsMediaPlaylist} instances may inherit attributes from * HlsMultivariantPlaylist}. Returned {@link HlsMediaPlaylist} instances may inherit attributes
* {@code masterPlaylist}. * from {@code multivariantPlaylist}.
* *
* @param masterPlaylist The master playlist that referenced any parsed media playlists. * @param multivariantPlaylist The multivariant playlist that referenced any parsed media
* playlists.
* @param previousMediaPlaylist The previous media playlist or null if there is no previous media * @param previousMediaPlaylist The previous media playlist or null if there is no previous media
* playlist. * playlist.
* @return A parser for HLS playlists. * @return A parser for HLS playlists.
*/ */
ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser( ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist); HlsMultivariantPlaylist multivariantPlaylist,
@Nullable HlsMediaPlaylist previousMediaPlaylist);
} }
...@@ -29,8 +29,8 @@ import java.io.IOException; ...@@ -29,8 +29,8 @@ import java.io.IOException;
* <p>The playlist tracker is responsible for exposing the seeking window, which is defined by the * <p>The playlist tracker is responsible for exposing the seeking window, which is defined by the
* segments that one of the playlists exposes. This playlist is called primary and needs to be * segments that one of the playlists exposes. This playlist is called primary and needs to be
* periodically refreshed in the case of live streams. Note that the primary playlist is one of the * periodically refreshed in the case of live streams. Note that the primary playlist is one of the
* media playlists while the master playlist is an optional kind of playlist defined by the HLS * media playlists while the multivariant playlist is an optional kind of playlist defined by the
* specification (RFC 8216). * HLS specification (RFC 8216).
* *
* <p>Playlist loads might encounter errors. The tracker may choose to exclude them to ensure a * <p>Playlist loads might encounter errors. The tracker may choose to exclude them to ensure a
* primary playlist is always available. * primary playlist is always available.
...@@ -120,8 +120,8 @@ public interface HlsPlaylistTracker { ...@@ -120,8 +120,8 @@ public interface HlsPlaylistTracker {
* <p>Must be called from the playback thread. A tracker may be restarted after a {@link #stop()} * <p>Must be called from the playback thread. A tracker may be restarted after a {@link #stop()}
* call. * call.
* *
* @param initialPlaylistUri Uri of the HLS stream. Can point to a media playlist or a master * @param initialPlaylistUri Uri of the HLS stream. Can point to a media playlist or a
* playlist. * multivariant playlist.
* @param eventDispatcher A dispatcher to notify of events. * @param eventDispatcher A dispatcher to notify of events.
* @param primaryPlaylistListener A callback for the primary playlist change events. * @param primaryPlaylistListener A callback for the primary playlist change events.
*/ */
...@@ -152,15 +152,15 @@ public interface HlsPlaylistTracker { ...@@ -152,15 +152,15 @@ public interface HlsPlaylistTracker {
void removeListener(PlaylistEventListener listener); void removeListener(PlaylistEventListener listener);
/** /**
* Returns the master playlist. * Returns the multivariant playlist.
* *
* <p>If the uri passed to {@link #start} points to a media playlist, an {@link HlsMasterPlaylist} * <p>If the uri passed to {@link #start} points to a media playlist, an {@link
* with a single variant for said media playlist is returned. * HlsMultivariantPlaylist} with a single variant for said media playlist is returned.
* *
* @return The master playlist. Null if the initial playlist has yet to be loaded. * @return The multivariant playlist. Null if the initial playlist has yet to be loaded.
*/ */
@Nullable @Nullable
HlsMasterPlaylist getMasterPlaylist(); HlsMultivariantPlaylist getMultivariantPlaylist();
/** /**
* Returns the most recent snapshot available of the playlist referenced by the provided {@link * Returns the most recent snapshot available of the playlist referenced by the provided {@link
...@@ -192,8 +192,8 @@ public interface HlsPlaylistTracker { ...@@ -192,8 +192,8 @@ public interface HlsPlaylistTracker {
boolean isSnapshotValid(Uri url); boolean isSnapshotValid(Uri url);
/** /**
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this * If the tracker is having trouble refreshing the multivariant playlist or the primary playlist,
* method throws the underlying error. Otherwise, does nothing. * this method throws the underlying error. Otherwise, does nothing.
* *
* @throws IOException The underlying error. * @throws IOException The underlying error.
*/ */
......
...@@ -28,9 +28,9 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; ...@@ -28,9 +28,9 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.testutil.MediaPeriodAsserts; import com.google.android.exoplayer2.testutil.MediaPeriodAsserts;
...@@ -51,9 +51,9 @@ import org.junit.runner.RunWith; ...@@ -51,9 +51,9 @@ import org.junit.runner.RunWith;
public final class HlsMediaPeriodTest { public final class HlsMediaPeriodTest {
@Test @Test
public void getSteamKeys_isCompatibleWithHlsMasterPlaylistFilter() { public void getSteamKeys_isCompatibleWithHlsMultivariantPlaylistFilter() {
HlsMasterPlaylist testMasterPlaylist = HlsMultivariantPlaylist testMultivariantPlaylist =
createMasterPlaylist( createMultivariantPlaylist(
/* variants= */ Arrays.asList( /* variants= */ Arrays.asList(
createAudioOnlyVariant(/* peakBitrate= */ 10000), createAudioOnlyVariant(/* peakBitrate= */ 10000),
createMuxedVideoAudioVariant(/* peakBitrate= */ 200000), createMuxedVideoAudioVariant(/* peakBitrate= */ 200000),
...@@ -76,7 +76,8 @@ public final class HlsMediaPeriodTest { ...@@ -76,7 +76,8 @@ public final class HlsMediaPeriodTest {
HlsDataSourceFactory mockDataSourceFactory = mock(HlsDataSourceFactory.class); HlsDataSourceFactory mockDataSourceFactory = mock(HlsDataSourceFactory.class);
when(mockDataSourceFactory.createDataSource(anyInt())).thenReturn(mock(DataSource.class)); when(mockDataSourceFactory.createDataSource(anyInt())).thenReturn(mock(DataSource.class));
HlsPlaylistTracker mockPlaylistTracker = mock(HlsPlaylistTracker.class); HlsPlaylistTracker mockPlaylistTracker = mock(HlsPlaylistTracker.class);
when(mockPlaylistTracker.getMasterPlaylist()).thenReturn((HlsMasterPlaylist) playlist); when(mockPlaylistTracker.getMultivariantPlaylist())
.thenReturn((HlsMultivariantPlaylist) playlist);
MediaPeriodId mediaPeriodId = new MediaPeriodId(/* periodUid= */ new Object()); MediaPeriodId mediaPeriodId = new MediaPeriodId(/* periodUid= */ new Object());
return new HlsMediaPeriod( return new HlsMediaPeriod(
mock(HlsExtractorFactory.class), mock(HlsExtractorFactory.class),
...@@ -98,16 +99,16 @@ public final class HlsMediaPeriodTest { ...@@ -98,16 +99,16 @@ public final class HlsMediaPeriodTest {
}; };
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration( MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
mediaPeriodFactory, testMasterPlaylist); mediaPeriodFactory, testMultivariantPlaylist);
} }
private static HlsMasterPlaylist createMasterPlaylist( private static HlsMultivariantPlaylist createMultivariantPlaylist(
List<Variant> variants, List<Variant> variants,
List<Rendition> audios, List<Rendition> audios,
List<Rendition> subtitles, List<Rendition> subtitles,
Format muxedAudioFormat, Format muxedAudioFormat,
List<Format> muxedCaptionFormats) { List<Format> muxedCaptionFormats) {
return new HlsMasterPlaylist( return new HlsMultivariantPlaylist(
"http://baseUri", "http://baseUri",
/* tags= */ Collections.emptyList(), /* tags= */ Collections.emptyList(),
variants, variants,
......
...@@ -20,11 +20,11 @@ import com.google.common.base.Charsets; ...@@ -20,11 +20,11 @@ import com.google.common.base.Charsets;
/** Data for HLS downloading tests. */ /** Data for HLS downloading tests. */
/* package */ interface HlsDownloadTestData { /* package */ interface HlsDownloadTestData {
String MASTER_PLAYLIST_URI = "test.m3u8"; String MULTIVARIANT_PLAYLIST_URI = "test.m3u8";
int MASTER_MEDIA_PLAYLIST_1_INDEX = 0; int MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX = 0;
int MASTER_MEDIA_PLAYLIST_2_INDEX = 1; int MULTIVARIANT_MEDIA_PLAYLIST_2_INDEX = 1;
int MASTER_MEDIA_PLAYLIST_3_INDEX = 2; int MULTIVARIANT_MEDIA_PLAYLIST_3_INDEX = 2;
int MASTER_MEDIA_PLAYLIST_0_INDEX = 3; int MULTIVARIANT_MEDIA_PLAYLIST_0_INDEX = 3;
String MEDIA_PLAYLIST_0_DIR = "gear0/"; String MEDIA_PLAYLIST_0_DIR = "gear0/";
String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8"; String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8";
...@@ -35,7 +35,7 @@ import com.google.common.base.Charsets; ...@@ -35,7 +35,7 @@ import com.google.common.base.Charsets;
String MEDIA_PLAYLIST_3_DIR = "gear3/"; String MEDIA_PLAYLIST_3_DIR = "gear3/";
String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8"; String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8";
byte[] MASTER_PLAYLIST_DATA = byte[] MULTIVARIANT_PLAYLIST_DATA =
("#EXTM3U\n" ("#EXTM3U\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n"
+ MEDIA_PLAYLIST_1_URI + MEDIA_PLAYLIST_1_URI
......
...@@ -17,10 +17,6 @@ package com.google.android.exoplayer2.source.hls.offline; ...@@ -17,10 +17,6 @@ package com.google.android.exoplayer2.source.hls.offline;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_DATA; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_DATA;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_URI; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_URI;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MASTER_MEDIA_PLAYLIST_1_INDEX;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MASTER_MEDIA_PLAYLIST_2_INDEX;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MASTER_PLAYLIST_DATA;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MASTER_PLAYLIST_URI;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_DIR; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_DIR;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_URI; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_URI;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_1_DIR; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_1_DIR;
...@@ -30,6 +26,10 @@ import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestDa ...@@ -30,6 +26,10 @@ import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestDa
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_DIR; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_DIR;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_URI; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_URI;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_DATA; import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_DATA;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MULTIVARIANT_MEDIA_PLAYLIST_2_INDEX;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MULTIVARIANT_PLAYLIST_DATA;
import static com.google.android.exoplayer2.source.hls.offline.HlsDownloadTestData.MULTIVARIANT_PLAYLIST_URI;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
...@@ -43,7 +43,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest; ...@@ -43,7 +43,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.offline.Downloader; import com.google.android.exoplayer2.offline.Downloader;
import com.google.android.exoplayer2.offline.DownloaderFactory; import com.google.android.exoplayer2.offline.DownloaderFactory;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist;
import com.google.android.exoplayer2.testutil.CacheAsserts; import com.google.android.exoplayer2.testutil.CacheAsserts;
import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
...@@ -83,7 +83,7 @@ public class HlsDownloaderTest { ...@@ -83,7 +83,7 @@ public class HlsDownloaderTest {
progressListener = new ProgressListener(); progressListener = new ProgressListener();
fakeDataSet = fakeDataSet =
new FakeDataSet() new FakeDataSet()
.setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA) .setData(MULTIVARIANT_PLAYLIST_URI, MULTIVARIANT_PLAYLIST_DATA)
.setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA) .setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10) .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11) .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11)
...@@ -122,7 +122,7 @@ public class HlsDownloaderTest { ...@@ -122,7 +122,7 @@ public class HlsDownloaderTest {
@Test @Test
public void counterMethods() throws Exception { public void counterMethods() throws Exception {
HlsDownloader downloader = HlsDownloader downloader =
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX)); getHlsDownloader(MULTIVARIANT_PLAYLIST_URI, getKeys(MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX));
downloader.download(progressListener); downloader.download(progressListener);
progressListener.assertBytesDownloaded(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); progressListener.assertBytesDownloaded(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
...@@ -131,14 +131,14 @@ public class HlsDownloaderTest { ...@@ -131,14 +131,14 @@ public class HlsDownloaderTest {
@Test @Test
public void downloadRepresentation() throws Exception { public void downloadRepresentation() throws Exception {
HlsDownloader downloader = HlsDownloader downloader =
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX)); getHlsDownloader(MULTIVARIANT_PLAYLIST_URI, getKeys(MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX));
downloader.download(progressListener); downloader.download(progressListener);
assertCachedData( assertCachedData(
cache, cache,
new CacheAsserts.RequestSet(fakeDataSet) new CacheAsserts.RequestSet(fakeDataSet)
.subset( .subset(
MASTER_PLAYLIST_URI, MULTIVARIANT_PLAYLIST_URI,
MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_1_URI,
MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts",
MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts",
...@@ -149,8 +149,8 @@ public class HlsDownloaderTest { ...@@ -149,8 +149,8 @@ public class HlsDownloaderTest {
public void downloadMultipleRepresentations() throws Exception { public void downloadMultipleRepresentations() throws Exception {
HlsDownloader downloader = HlsDownloader downloader =
getHlsDownloader( getHlsDownloader(
MASTER_PLAYLIST_URI, MULTIVARIANT_PLAYLIST_URI,
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX)); getKeys(MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX, MULTIVARIANT_MEDIA_PLAYLIST_2_INDEX));
downloader.download(progressListener); downloader.download(progressListener);
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
...@@ -169,7 +169,7 @@ public class HlsDownloaderTest { ...@@ -169,7 +169,7 @@ public class HlsDownloaderTest {
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence1.ts", 14) .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence1.ts", 14)
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15); .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15);
HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys()); HlsDownloader downloader = getHlsDownloader(MULTIVARIANT_PLAYLIST_URI, getKeys());
downloader.download(progressListener); downloader.download(progressListener);
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
...@@ -179,8 +179,8 @@ public class HlsDownloaderTest { ...@@ -179,8 +179,8 @@ public class HlsDownloaderTest {
public void remove() throws Exception { public void remove() throws Exception {
HlsDownloader downloader = HlsDownloader downloader =
getHlsDownloader( getHlsDownloader(
MASTER_PLAYLIST_URI, MULTIVARIANT_PLAYLIST_URI,
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX)); getKeys(MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX, MULTIVARIANT_MEDIA_PLAYLIST_2_INDEX));
downloader.download(progressListener); downloader.download(progressListener);
downloader.remove(); downloader.remove();
...@@ -231,7 +231,7 @@ public class HlsDownloaderTest { ...@@ -231,7 +231,7 @@ public class HlsDownloaderTest {
private static ArrayList<StreamKey> getKeys(int... variantIndices) { private static ArrayList<StreamKey> getKeys(int... variantIndices) {
ArrayList<StreamKey> streamKeys = new ArrayList<>(); ArrayList<StreamKey> streamKeys = new ArrayList<>();
for (int variantIndex : variantIndices) { for (int variantIndex : variantIndices) {
streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, variantIndex)); streamKeys.add(new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, variantIndex));
} }
return streamKeys; return streamKeys;
} }
......
...@@ -44,9 +44,10 @@ import org.junit.runner.RunWith; ...@@ -44,9 +44,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DefaultHlsPlaylistTrackerTest { public class DefaultHlsPlaylistTrackerTest {
private static final String SAMPLE_M3U8_LIVE_MASTER = "media/m3u8/live_low_latency_master"; private static final String SAMPLE_M3U8_LIVE_MULTIVARIANT =
private static final String SAMPLE_M3U8_LIVE_MASTER_MEDIA_URI_WITH_PARAM = "media/m3u8/live_low_latency_multivariant";
"media/m3u8/live_low_latency_master_media_uri_with_param"; private static final String SAMPLE_M3U8_LIVE_MULTIVARIANT_MEDIA_URI_WITH_PARAM =
"media/m3u8/live_low_latency_multivariant_media_uri_with_param";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL = private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL =
"media/m3u8/live_low_latency_media_can_skip_until"; "media/m3u8/live_low_latency_media_can_skip_until";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR = private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR =
...@@ -110,15 +111,15 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -110,15 +111,15 @@ public class DefaultHlsPlaylistTrackerTest {
throws IOException, TimeoutException, InterruptedException { throws IOException, TimeoutException, InterruptedException {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] {"master.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8"}, new String[] {"multivariant.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8"},
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP_NEXT)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP_NEXT));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -141,16 +142,16 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -141,16 +142,16 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_skip=YES" "/multivariant.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_skip=YES"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -175,12 +176,12 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -175,12 +176,12 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_skip=YES", "/media0/playlist.m3u8?_HLS_skip=YES",
"/media0/playlist.m3u8" "/media0/playlist.m3u8"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR));
...@@ -188,7 +189,7 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -188,7 +189,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -206,16 +207,16 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -206,16 +207,16 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_skip=v2" "/multivariant.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_skip=v2"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -230,18 +231,18 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -230,18 +231,18 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8?param1=1&param2=2", "/media0/playlist.m3u8?param1=1&param2=2",
"/media0/playlist.m3u8?param1=1&param2=2&_HLS_skip=YES" "/media0/playlist.m3u8?param1=1&param2=2&_HLS_skip=YES"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER_MEDIA_URI_WITH_PARAM), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT_MEDIA_URI_WITH_PARAM),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -257,16 +258,16 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -257,16 +258,16 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_msn=14" "/multivariant.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_msn=14"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_NEXT)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_NEXT));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -281,18 +282,18 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -281,18 +282,18 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=1" "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=1"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_NEXT)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_NEXT));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -310,18 +311,18 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -310,18 +311,18 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0" "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT)); getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT));
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -339,11 +340,11 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -339,11 +340,11 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0" "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse( getMockResponse(
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD), SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD),
getMockResponse( getMockResponse(
...@@ -352,7 +353,7 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -352,7 +353,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSource.Factory(), new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2); /* awaitedMediaPlaylistCount= */ 2);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -370,13 +371,13 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -370,13 +371,13 @@ public class DefaultHlsPlaylistTrackerTest {
List<HttpUrl> httpUrls = List<HttpUrl> httpUrls =
enqueueWebServerResponses( enqueueWebServerResponses(
new String[] { new String[] {
"/master.m3u8", "/multivariant.m3u8",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_msn=16&_HLS_skip=YES", "/media0/playlist.m3u8?_HLS_msn=16&_HLS_skip=YES",
"/media0/playlist.m3u8", "/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_msn=17&_HLS_skip=YES" "/media0/playlist.m3u8?_HLS_msn=17&_HLS_skip=YES"
}, },
getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD),
new MockResponse().setResponseCode(400), new MockResponse().setResponseCode(400),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT),
...@@ -385,7 +386,7 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -385,7 +386,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists = List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists( runPlaylistTrackerAndCollectMediaPlaylists(
/* dataSourceFactory= */ new DefaultHttpDataSource.Factory(), /* dataSourceFactory= */ new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()), Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 3); /* awaitedMediaPlaylistCount= */ 3);
assertRequestUrlsCalled(httpUrls); assertRequestUrlsCalled(httpUrls);
...@@ -415,7 +416,9 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -415,7 +416,9 @@ public class DefaultHlsPlaylistTrackerTest {
} }
private static List<HlsMediaPlaylist> runPlaylistTrackerAndCollectMediaPlaylists( private static List<HlsMediaPlaylist> runPlaylistTrackerAndCollectMediaPlaylists(
DataSource.Factory dataSourceFactory, Uri masterPlaylistUri, int awaitedMediaPlaylistCount) DataSource.Factory dataSourceFactory,
Uri multivariantPlaylistUri,
int awaitedMediaPlaylistCount)
throws TimeoutException { throws TimeoutException {
DefaultHlsPlaylistTracker defaultHlsPlaylistTracker = DefaultHlsPlaylistTracker defaultHlsPlaylistTracker =
...@@ -427,7 +430,7 @@ public class DefaultHlsPlaylistTrackerTest { ...@@ -427,7 +430,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>(); List<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
AtomicInteger playlistCounter = new AtomicInteger(); AtomicInteger playlistCounter = new AtomicInteger();
defaultHlsPlaylistTracker.start( defaultHlsPlaylistTracker.start(
masterPlaylistUri, multivariantPlaylistUri,
new MediaSourceEventListener.EventDispatcher(), new MediaSourceEventListener.EventDispatcher(),
mediaPlaylist -> { mediaPlaylist -> {
mediaPlaylists.add(mediaPlaylist); mediaPlaylists.add(mediaPlaylist);
......
...@@ -392,7 +392,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -392,7 +392,7 @@ public class HlsMediaPlaylistParserTest {
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
(HlsMediaPlaylist) (HlsMediaPlaylist)
new HlsPlaylistParser(HlsMasterPlaylist.EMPTY, previousPlaylist) new HlsPlaylistParser(HlsMultivariantPlaylist.EMPTY, previousPlaylist)
.parse(playlistUri, inputStream); .parse(playlistUri, inputStream);
assertThat(playlist.segments).hasSize(3); assertThat(playlist.segments).hasSize(3);
...@@ -446,7 +446,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -446,7 +446,7 @@ public class HlsMediaPlaylistParserTest {
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
(HlsMediaPlaylist) (HlsMediaPlaylist)
new HlsPlaylistParser(HlsMasterPlaylist.EMPTY, previousPlaylist) new HlsPlaylistParser(HlsMultivariantPlaylist.EMPTY, previousPlaylist)
.parse(playlistUri, inputStream); .parse(playlistUri, inputStream);
assertThat(playlist.segments).hasSize(2); assertThat(playlist.segments).hasSize(2);
...@@ -1363,7 +1363,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -1363,7 +1363,7 @@ public class HlsMediaPlaylistParserTest {
} }
@Test @Test
public void masterPlaylistAttributeInheritance() throws IOException { public void multivariantPlaylistAttributeInheritance() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8"); Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString = String playlistString =
"#EXTM3U\n" "#EXTM3U\n"
...@@ -1386,8 +1386,8 @@ public class HlsMediaPlaylistParserTest { ...@@ -1386,8 +1386,8 @@ public class HlsMediaPlaylistParserTest {
assertThat(standalonePlaylist.hasIndependentSegments).isFalse(); assertThat(standalonePlaylist.hasIndependentSegments).isFalse();
inputStream.reset(); inputStream.reset();
HlsMasterPlaylist masterPlaylist = HlsMultivariantPlaylist multivariantPlaylist =
new HlsMasterPlaylist( new HlsMultivariantPlaylist(
/* baseUri= */ "https://example.com/", /* baseUri= */ "https://example.com/",
/* tags= */ Collections.emptyList(), /* tags= */ Collections.emptyList(),
/* variants= */ Collections.emptyList(), /* variants= */ Collections.emptyList(),
...@@ -1402,7 +1402,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -1402,7 +1402,7 @@ public class HlsMediaPlaylistParserTest {
/* sessionKeyDrmInitData= */ Collections.emptyList()); /* sessionKeyDrmInitData= */ Collections.emptyList());
HlsMediaPlaylist playlistWithInheritance = HlsMediaPlaylist playlistWithInheritance =
(HlsMediaPlaylist) (HlsMediaPlaylist)
new HlsPlaylistParser(masterPlaylist, /* previousMediaPlaylist= */ null) new HlsPlaylistParser(multivariantPlaylist, /* previousMediaPlaylist= */ null)
.parse(playlistUri, inputStream); .parse(playlistUri, inputStream);
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue(); assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
} }
...@@ -1450,8 +1450,8 @@ public class HlsMediaPlaylistParserTest { ...@@ -1450,8 +1450,8 @@ public class HlsMediaPlaylistParserTest {
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HashMap<String, String> variableDefinitions = new HashMap<>(); HashMap<String, String> variableDefinitions = new HashMap<>();
variableDefinitions.put("imported_base", "long_path"); variableDefinitions.put("imported_base", "long_path");
HlsMasterPlaylist masterPlaylist = HlsMultivariantPlaylist multivariantPlaylist =
new HlsMasterPlaylist( new HlsMultivariantPlaylist(
/* baseUri= */ "", /* baseUri= */ "",
/* tags= */ Collections.emptyList(), /* tags= */ Collections.emptyList(),
/* variants= */ Collections.emptyList(), /* variants= */ Collections.emptyList(),
...@@ -1466,7 +1466,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -1466,7 +1466,7 @@ public class HlsMediaPlaylistParserTest {
/* sessionKeyDrmInitData= */ Collections.emptyList()); /* sessionKeyDrmInitData= */ Collections.emptyList());
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
(HlsMediaPlaylist) (HlsMediaPlaylist)
new HlsPlaylistParser(masterPlaylist, /* previousMediaPlaylist= */ null) new HlsPlaylistParser(multivariantPlaylist, /* previousMediaPlaylist= */ null)
.parse(playlistUri, inputStream); .parse(playlistUri, inputStream);
for (int i = 1; i <= 4; i++) { for (int i = 1; i <= 4; i++) {
assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts"); assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts");
......
...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.Format; ...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry; import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
...@@ -36,9 +36,9 @@ import java.util.List; ...@@ -36,9 +36,9 @@ import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** Test for {@link HlsMasterPlaylist}. */ /** Test for {@link HlsMultivariantPlaylist}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class HlsMasterPlaylistParserTest { public class HlsMultivariantPlaylistParserTest {
private static final String PLAYLIST_URI = "https://example.com/test.m3u8"; private static final String PLAYLIST_URI = "https://example.com/test.m3u8";
...@@ -233,12 +233,13 @@ public class HlsMasterPlaylistParserTest { ...@@ -233,12 +233,13 @@ public class HlsMasterPlaylistParserTest {
+ "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=1313400,RESOLUTION=1920x1080,CODECS=\"avc1.640028\",URI=\"iframe_1313400/index.m3u8\"\n"; + "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=1313400,RESOLUTION=1920x1080,CODECS=\"avc1.640028\",URI=\"iframe_1313400/index.m3u8\"\n";
@Test @Test
public void parseMasterPlaylist_withSimple_success() throws IOException { public void parseMultivariantPlaylist_withSimple_success() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); HlsMultivariantPlaylist multivariantPlaylist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
List<HlsMasterPlaylist.Variant> variants = masterPlaylist.variants; List<HlsMultivariantPlaylist.Variant> variants = multivariantPlaylist.variants;
assertThat(variants).hasSize(5); assertThat(variants).hasSize(5);
assertThat(masterPlaylist.muxedCaptionFormats).isNull(); assertThat(multivariantPlaylist.muxedCaptionFormats).isNull();
assertThat(variants.get(0).format.bitrate).isEqualTo(1280000); assertThat(variants.get(0).format.bitrate).isEqualTo(1280000);
assertThat(variants.get(0).format.codecs).isEqualTo("mp4a.40.2,avc1.66.30"); assertThat(variants.get(0).format.codecs).isEqualTo("mp4a.40.2,avc1.66.30");
...@@ -274,20 +275,20 @@ public class HlsMasterPlaylistParserTest { ...@@ -274,20 +275,20 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withAverageBandwidth_success() throws IOException { public void parseMultivariantPlaylist_withAverageBandwidth_success() throws IOException {
HlsMasterPlaylist masterPlaylist = HlsMultivariantPlaylist multivariantPlaylist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH);
List<HlsMasterPlaylist.Variant> variants = masterPlaylist.variants; List<HlsMultivariantPlaylist.Variant> variants = multivariantPlaylist.variants;
assertThat(variants.get(0).format.bitrate).isEqualTo(1280000); assertThat(variants.get(0).format.bitrate).isEqualTo(1280000);
assertThat(variants.get(1).format.bitrate).isEqualTo(1280000); assertThat(variants.get(1).format.bitrate).isEqualTo(1280000);
} }
@Test @Test
public void parseMasterPlaylist_withInvalidHeader_throwsException() throws IOException { public void parseMultivariantPlaylist_withInvalidHeader_throwsException() throws IOException {
try { try {
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER);
fail("Expected exception not thrown."); fail("Expected exception not thrown.");
} catch (ParserException e) { } catch (ParserException e) {
// Expected due to invalid header. // Expected due to invalid header.
...@@ -295,8 +296,8 @@ public class HlsMasterPlaylistParserTest { ...@@ -295,8 +296,8 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withClosedCaption_success() throws IOException { public void parseMultivariantPlaylist_withClosedCaption_success() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC); HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
assertThat(playlist.muxedCaptionFormats).hasSize(1); assertThat(playlist.muxedCaptionFormats).hasSize(1);
Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0); Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0);
assertThat(closedCaptionFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA708); assertThat(closedCaptionFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA708);
...@@ -305,10 +306,10 @@ public class HlsMasterPlaylistParserTest { ...@@ -305,10 +306,10 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withChannelsAttribute_success() throws IOException { public void parseMultivariantPlaylist_withChannelsAttribute_success() throws IOException {
HlsMasterPlaylist playlist = HlsMultivariantPlaylist playlist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CHANNELS_ATTRIBUTE); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CHANNELS_ATTRIBUTE);
List<HlsMasterPlaylist.Rendition> audios = playlist.audios; List<HlsMultivariantPlaylist.Rendition> audios = playlist.audios;
assertThat(audios).hasSize(3); assertThat(audios).hasSize(3);
assertThat(audios.get(0).format.channelCount).isEqualTo(6); assertThat(audios.get(0).format.channelCount).isEqualTo(6);
assertThat(audios.get(1).format.channelCount).isEqualTo(2); assertThat(audios.get(1).format.channelCount).isEqualTo(2);
...@@ -316,14 +317,15 @@ public class HlsMasterPlaylistParserTest { ...@@ -316,14 +317,15 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withoutClosedCaption_success() throws IOException { public void parseMultivariantPlaylist_withoutClosedCaption_success() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC); HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);
assertThat(playlist.muxedCaptionFormats).isEmpty(); assertThat(playlist.muxedCaptionFormats).isEmpty();
} }
@Test @Test
public void parseMasterPlaylist_withAudio_codecPropagated() throws IOException { public void parseMultivariantPlaylist_withAudio_codecPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG); HlsMultivariantPlaylist playlist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
Format firstAudioFormat = playlist.audios.get(0).format; Format firstAudioFormat = playlist.audios.get(0).format;
assertThat(firstAudioFormat.codecs).isEqualTo("mp4a.40.2"); assertThat(firstAudioFormat.codecs).isEqualTo("mp4a.40.2");
...@@ -335,8 +337,9 @@ public class HlsMasterPlaylistParserTest { ...@@ -335,8 +337,9 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withAudio_audioIdPropagated() throws IOException { public void parseMultivariantPlaylist_withAudio_audioIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG); HlsMultivariantPlaylist playlist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
Format firstAudioFormat = playlist.audios.get(0).format; Format firstAudioFormat = playlist.audios.get(0).format;
assertThat(firstAudioFormat.id).isEqualTo("aud1:English"); assertThat(firstAudioFormat.id).isEqualTo("aud1:English");
...@@ -346,16 +349,17 @@ public class HlsMasterPlaylistParserTest { ...@@ -346,16 +349,17 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withCc_cCIdPropagated() throws IOException { public void parseMultivariantPlaylist_withCc_cCIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC); HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
Format firstTextFormat = playlist.muxedCaptionFormats.get(0); Format firstTextFormat = playlist.muxedCaptionFormats.get(0);
assertThat(firstTextFormat.id).isEqualTo("cc1:Eng"); assertThat(firstTextFormat.id).isEqualTo("cc1:Eng");
} }
@Test @Test
public void parseMasterPlaylist_withSubtitles_subtitlesIdPropagated() throws IOException { public void parseMultivariantPlaylist_withSubtitles_subtitlesIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES); HlsMultivariantPlaylist playlist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES);
Format firstTextFormat = playlist.subtitles.get(0).format; Format firstTextFormat = playlist.subtitles.get(0).format;
assertThat(firstTextFormat.id).isEqualTo("sub1:Eng"); assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
...@@ -363,39 +367,40 @@ public class HlsMasterPlaylistParserTest { ...@@ -363,39 +367,40 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_subtitlesWithoutUri_skipsSubtitles() throws IOException { public void parseMultivariantPlaylist_subtitlesWithoutUri_skipsSubtitles() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES_NO_URI); HlsMultivariantPlaylist playlist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES_NO_URI);
assertThat(playlist.subtitles).isEmpty(); assertThat(playlist.subtitles).isEmpty();
} }
@Test @Test
public void parseMasterPlaylist_withIndependentSegments_hasNoIndenpendentSegments() public void parseMultivariantPlaylist_withIndependentSegments_hasNoIndenpendentSegments()
throws IOException { throws IOException {
HlsMasterPlaylist playlistWithIndependentSegments = HlsMultivariantPlaylist playlistWithIndependentSegments =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INDEPENDENT_SEGMENTS); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INDEPENDENT_SEGMENTS);
assertThat(playlistWithIndependentSegments.hasIndependentSegments).isTrue(); assertThat(playlistWithIndependentSegments.hasIndependentSegments).isTrue();
HlsMasterPlaylist playlistWithoutIndependentSegments = HlsMultivariantPlaylist playlistWithoutIndependentSegments =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse(); assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse();
} }
@Test @Test
public void parseMasterPlaylist_withVariableSubstitution_success() throws IOException { public void parseMultivariantPlaylist_withVariableSubstitution_success() throws IOException {
HlsMasterPlaylist playlistWithSubstitutions = HlsMultivariantPlaylist playlistWithSubstitutions =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
HlsMasterPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0); HlsMultivariantPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0);
assertThat(variant.format.codecs).isEqualTo("mp4a.40.5"); assertThat(variant.format.codecs).isEqualTo("mp4a.40.5");
assertThat(variant.url) assertThat(variant.url)
.isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work")); .isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
} }
@Test @Test
public void parseMasterPlaylist_withTtmlSubtitle() throws IOException { public void parseMultivariantPlaylist_withTtmlSubtitle() throws IOException {
HlsMasterPlaylist playlistWithTtmlSubtitle = HlsMultivariantPlaylist playlistWithTtmlSubtitle =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE);
HlsMasterPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0); HlsMultivariantPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0);
Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format; Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format;
assertThat(firstTextFormat.id).isEqualTo("sub1:English"); assertThat(firstTextFormat.id).isEqualTo("sub1:English");
assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8); assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8);
...@@ -404,9 +409,9 @@ public class HlsMasterPlaylistParserTest { ...@@ -404,9 +409,9 @@ public class HlsMasterPlaylistParserTest {
} }
@Test @Test
public void parseMasterPlaylist_withMatchingStreamInfUrls_success() throws IOException { public void parseMultivariantPlaylist_withMatchingStreamInfUrls_success() throws IOException {
HlsMasterPlaylist playlist = HlsMultivariantPlaylist playlist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MATCHING_STREAM_INF_URLS); parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MATCHING_STREAM_INF_URLS);
assertThat(playlist.variants).hasSize(4); assertThat(playlist.variants).hasSize(4);
assertThat(playlist.variants.get(0).format.metadata) assertThat(playlist.variants.get(0).format.metadata)
.isEqualTo( .isEqualTo(
...@@ -441,7 +446,8 @@ public class HlsMasterPlaylistParserTest { ...@@ -441,7 +446,8 @@ public class HlsMasterPlaylistParserTest {
@Test @Test
public void testIFrameVariant() throws IOException { public void testIFrameVariant() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_IFRAME_VARIANTS); HlsMultivariantPlaylist playlist =
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_IFRAME_VARIANTS);
assertThat(playlist.variants).hasSize(5); assertThat(playlist.variants).hasSize(5);
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
assertThat(playlist.variants.get(i).format.roleFlags).isEqualTo(0); assertThat(playlist.variants.get(i).format.roleFlags).isEqualTo(0);
...@@ -472,11 +478,11 @@ public class HlsMasterPlaylistParserTest { ...@@ -472,11 +478,11 @@ public class HlsMasterPlaylistParserTest {
/* captionGroupId= */ "cc1"); /* captionGroupId= */ "cc1");
} }
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) private static HlsMultivariantPlaylist parseMultivariantPlaylist(
throws IOException { String uri, String playlistString) throws IOException {
Uri playlistUri = Uri.parse(uri); Uri playlistUri = Uri.parse(uri);
ByteArrayInputStream inputStream = ByteArrayInputStream inputStream =
new ByteArrayInputStream(playlistString.getBytes(Charsets.UTF_8)); new ByteArrayInputStream(playlistString.getBytes(Charsets.UTF_8));
return (HlsMasterPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); return (HlsMultivariantPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
} }
} }
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