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
This commit is contained in:
parent
691f9c50cf
commit
40d9838315
@ -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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -629,7 +629,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.
|
||||||
|
@ -42,20 +42,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,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) {
|
||||||
@ -101,7 +102,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
|
||||||
|
@ -81,8 +81,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;
|
|||||||
* {@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;
|
|||||||
|
|
||||||
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]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,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.
|
||||||
|
@ -18,22 +18,43 @@ package androidx.media3.exoplayer.hls;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
||||||
|
import androidx.media3.exoplayer.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. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
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;
|
|||||||
* @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 androidx.media3.exoplayer.analytics.PlayerId;
|
|||||||
import androidx.media3.exoplayer.drm.DrmSession;
|
import androidx.media3.exoplayer.drm.DrmSession;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Rendition;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Variant;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistTracker;
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistTracker;
|
||||||
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
@ -180,17 +180,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;
|
||||||
@ -218,7 +219,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.
|
||||||
@ -232,8 +234,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 =
|
||||||
@ -249,16 +251,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;
|
||||||
}
|
}
|
||||||
@ -491,15 +495,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<>();
|
||||||
@ -507,7 +512,7 @@ public final class HlsMediaPeriod
|
|||||||
|
|
||||||
if (hasVariants) {
|
if (hasVariants) {
|
||||||
buildAndPrepareMainSampleStreamWrapper(
|
buildAndPrepareMainSampleStreamWrapper(
|
||||||
masterPlaylist,
|
multivariantPlaylist,
|
||||||
positionUs,
|
positionUs,
|
||||||
sampleStreamWrappers,
|
sampleStreamWrappers,
|
||||||
manifestUrlIndicesPerWrapper,
|
manifestUrlIndicesPerWrapper,
|
||||||
@ -525,7 +530,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;
|
||||||
@ -541,7 +547,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);
|
||||||
}
|
}
|
||||||
@ -562,8 +568,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
|
||||||
@ -572,13 +578,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.
|
||||||
@ -587,16 +593,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;
|
||||||
@ -613,8 +619,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) {
|
||||||
@ -627,10 +633,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;
|
||||||
@ -655,8 +661,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);
|
||||||
@ -671,16 +677,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;
|
||||||
@ -694,7 +701,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));
|
||||||
@ -709,7 +716,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));
|
||||||
@ -770,7 +777,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);
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,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.
|
||||||
@ -275,10 +276,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.
|
||||||
@ -526,9 +527,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;
|
|||||||
* 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;
|
|||||||
* 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;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
|||||||
* @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;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
|||||||
* 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;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -23,8 +23,8 @@ import androidx.media3.common.util.UriUtil;
|
|||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DataSpec;
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.datasource.cache.CacheDataSource;
|
import androidx.media3.datasource.cache.CacheDataSource;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
|
||||||
import androidx.media3.exoplayer.offline.SegmentDownloader;
|
import androidx.media3.exoplayer.offline.SegmentDownloader;
|
||||||
@ -46,14 +46,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.
|
||||||
@ -115,9 +115,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)));
|
||||||
|
@ -30,7 +30,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.HttpDataSource;
|
import androidx.media3.datasource.HttpDataSource;
|
||||||
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Variant;
|
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.RenditionReport;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.RenditionReport;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||||
import androidx.media3.exoplayer.source.LoadEventInfo;
|
import androidx.media3.exoplayer.source.LoadEventInfo;
|
||||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||||
@ -74,7 +74,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;
|
||||||
@ -133,30 +133,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;
|
||||||
@ -181,8 +184,8 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public HlsMasterPlaylist getMasterPlaylist() {
|
public HlsMultivariantPlaylist getMultivariantPlaylist() {
|
||||||
return masterPlaylist;
|
return multivariantPlaylist;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -245,18 +248,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,
|
||||||
@ -329,7 +333,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++) {
|
||||||
@ -384,9 +388,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;
|
||||||
@ -689,7 +696,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,
|
||||||
@ -825,7 +832,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);
|
||||||
@ -837,7 +844,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 =
|
||||||
|
@ -51,9 +51,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,183 +15,24 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.hls.playlist;
|
package androidx.media3.exoplayer.hls.playlist;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.DrmInitData;
|
import androidx.media3.common.DrmInitData;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.common.StreamKey;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
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. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
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}.
|
* Creates an HLS multivariant playlist.
|
||||||
* @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.
|
* @deprecated Use {@link HlsMultivariantPlaylist#HlsMultivariantPlaylist} instead.
|
||||||
* @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}.
|
|
||||||
* @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}.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public HlsMasterPlaylist(
|
public HlsMasterPlaylist(
|
||||||
String baseUri,
|
String baseUri,
|
||||||
List<String> tags,
|
List<String> tags,
|
||||||
@ -205,117 +46,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,322 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.media3.exoplayer.hls.playlist;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.DrmInitData;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.StreamKey;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
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.
|
||||||
|
@UnstableApi
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -37,11 +37,11 @@ import androidx.media3.common.util.UriUtil;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry;
|
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry;
|
||||||
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo;
|
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Rendition;
|
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Variant;
|
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Part;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.RenditionReport;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.RenditionReport;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition;
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||||
import androidx.media3.exoplayer.upstream.ParsingLoadable;
|
import androidx.media3.exoplayer.upstream.ParsingLoadable;
|
||||||
import androidx.media3.extractor.mp4.PsshAtomUtil;
|
import androidx.media3.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@ -225,28 +225,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +268,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)
|
||||||
@ -277,7 +279,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());
|
||||||
@ -321,8 +323,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<>();
|
||||||
@ -580,7 +582,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,
|
||||||
@ -629,7 +631,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)
|
||||||
@ -640,7 +642,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<>();
|
||||||
@ -750,11 +752,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(
|
||||||
|
@ -31,14 +31,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);
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,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.
|
||||||
@ -122,8 +122,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.
|
||||||
*/
|
*/
|
||||||
@ -154,15 +154,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
|
||||||
@ -194,8 +194,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.
|
||||||
*/
|
*/
|
||||||
|
@ -27,9 +27,9 @@ import androidx.media3.datasource.TransferListener;
|
|||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Rendition;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Variant;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistTracker;
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistTracker;
|
||||||
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
||||||
@ -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 {
|
|||||||
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 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|||||||
/** 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;
|
|||||||
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 androidx.media3.exoplayer.hls.offline;
|
|||||||
|
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_DATA;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_DATA;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_URI;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.ENC_MEDIA_PLAYLIST_URI;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MASTER_MEDIA_PLAYLIST_1_INDEX;
|
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MASTER_MEDIA_PLAYLIST_2_INDEX;
|
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MASTER_PLAYLIST_DATA;
|
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MASTER_PLAYLIST_URI;
|
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_DIR;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_DIR;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_URI;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_0_URI;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_1_DIR;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_1_DIR;
|
||||||
@ -30,6 +26,10 @@ import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PL
|
|||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_DIR;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_DIR;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_URI;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_3_URI;
|
||||||
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_DATA;
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MEDIA_PLAYLIST_DATA;
|
||||||
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MULTIVARIANT_MEDIA_PLAYLIST_1_INDEX;
|
||||||
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MULTIVARIANT_MEDIA_PLAYLIST_2_INDEX;
|
||||||
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MULTIVARIANT_PLAYLIST_DATA;
|
||||||
|
import static androidx.media3.exoplayer.hls.offline.HlsDownloadTestData.MULTIVARIANT_PLAYLIST_URI;
|
||||||
import static androidx.media3.test.utils.CacheAsserts.assertCacheEmpty;
|
import static androidx.media3.test.utils.CacheAsserts.assertCacheEmpty;
|
||||||
import static androidx.media3.test.utils.CacheAsserts.assertCachedData;
|
import static androidx.media3.test.utils.CacheAsserts.assertCachedData;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
@ -44,7 +44,7 @@ import androidx.media3.datasource.cache.Cache;
|
|||||||
import androidx.media3.datasource.cache.CacheDataSource;
|
import androidx.media3.datasource.cache.CacheDataSource;
|
||||||
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
||||||
import androidx.media3.datasource.cache.SimpleCache;
|
import androidx.media3.datasource.cache.SimpleCache;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist;
|
||||||
import androidx.media3.exoplayer.offline.DefaultDownloaderFactory;
|
import androidx.media3.exoplayer.offline.DefaultDownloaderFactory;
|
||||||
import androidx.media3.exoplayer.offline.DownloadRequest;
|
import androidx.media3.exoplayer.offline.DownloadRequest;
|
||||||
import androidx.media3.exoplayer.offline.Downloader;
|
import androidx.media3.exoplayer.offline.Downloader;
|
||||||
@ -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 {
|
|||||||
@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 {
|
|||||||
@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 {
|
|||||||
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 {
|
|||||||
.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 {
|
|||||||
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 {
|
|||||||
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;
|
|||||||
@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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
List<HttpUrl> httpUrls =
|
List<HttpUrl> httpUrls =
|
||||||
enqueueWebServerResponses(
|
enqueueWebServerResponses(
|
||||||
new String[] {
|
new String[] {
|
||||||
"/master.m3u8",
|
"/multivariant.m3u8",
|
||||||
"/media0/playlist.m3u8?param1=1¶m2=2",
|
"/media0/playlist.m3u8?param1=1¶m2=2",
|
||||||
"/media0/playlist.m3u8?param1=1¶m2=2&_HLS_skip=YES"
|
"/media0/playlist.m3u8?param1=1¶m2=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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
/* 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 {
|
|||||||
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 {
|
|||||||
/* 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 androidx.media3.common.Metadata;
|
|||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.ParserException;
|
import androidx.media3.common.ParserException;
|
||||||
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry;
|
import androidx.media3.exoplayer.hls.HlsTrackMetadataEntry;
|
||||||
import androidx.media3.exoplayer.hls.playlist.HlsMasterPlaylist.Variant;
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
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;
|
|||||||
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 {
|
|||||||
+ "#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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
/* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user