Support EXT-X-INDEPENDENT-SEGMENTS in the master playlist
From the spec: If the EXT-X-INDEPENDENT-SEGMENTS tag appears in a Master Playlist, it applies to every Media Segment in every Media Playlist in the Master Playlist. ---- This requires propagation of attributes from the master playlist to the media playlists. This CL only includes independent segments, but other inheritable attributes will be supported in following changes. Other inheritable attributes include variable substitution definitions and session keys. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=202628422
This commit is contained in:
parent
a916ad6a50
commit
efa714ab4f
@ -18,9 +18,12 @@
|
||||
* Error handling:
|
||||
* Allow configuration of the Loader retry delay
|
||||
([#3370](https://github.com/google/ExoPlayer/issues/3370)).
|
||||
* HLS: Pass HTTP response headers to `HlsExtractorFactory.createExtractor`.
|
||||
* DRM: Allow DrmInitData to carry a license server URL
|
||||
([#3393](https://github.com/google/ExoPlayer/issues/3393)).
|
||||
* HLS:
|
||||
* Pass HTTP response headers to `HlsExtractorFactory.createExtractor`.
|
||||
* Add support for EXT-X-INDEPENDENT-SEGMENTS in the master playlist.
|
||||
* DRM:
|
||||
* Allow DrmInitData to carry a license server URL
|
||||
([#3393](https://github.com/google/ExoPlayer/issues/3393)).
|
||||
* Add method to `BandwidthMeter` to return the `TransferListener` used to gather
|
||||
bandwidth information.
|
||||
* Add callback to `VideoListener` to notify of surface size changes.
|
||||
|
@ -248,7 +248,7 @@ import java.util.List;
|
||||
return;
|
||||
}
|
||||
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
|
||||
independentSegments = mediaPlaylist.hasIndependentSegmentsTag;
|
||||
independentSegments = mediaPlaylist.hasIndependentSegments;
|
||||
|
||||
updateLiveEdgeTimeUs(mediaPlaylist);
|
||||
|
||||
|
@ -544,6 +544,9 @@ public final class DefaultHlsPlaylistTracker
|
||||
}
|
||||
|
||||
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
|
||||
// Update the loaded playlist with any inheritable information from the master playlist.
|
||||
loadedPlaylist = loadedPlaylist.copyWithMasterPlaylistInfo(masterPlaylist);
|
||||
|
||||
HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
||||
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||
lastSnapshotLoadMs = currentTimeMs;
|
||||
|
@ -99,11 +99,18 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||
* @param subtitles See {@link #subtitles}.
|
||||
* @param muxedAudioFormat See {@link #muxedAudioFormat}.
|
||||
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
|
||||
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
|
||||
*/
|
||||
public HlsMasterPlaylist(String baseUri, List<String> tags, List<HlsUrl> variants,
|
||||
List<HlsUrl> audios, List<HlsUrl> subtitles, Format muxedAudioFormat,
|
||||
List<Format> muxedCaptionFormats) {
|
||||
super(baseUri, tags);
|
||||
public HlsMasterPlaylist(
|
||||
String baseUri,
|
||||
List<String> tags,
|
||||
List<HlsUrl> variants,
|
||||
List<HlsUrl> audios,
|
||||
List<HlsUrl> subtitles,
|
||||
Format muxedAudioFormat,
|
||||
List<Format> muxedCaptionFormats,
|
||||
boolean hasIndependentSegments) {
|
||||
super(baseUri, tags, hasIndependentSegments);
|
||||
this.variants = Collections.unmodifiableList(variants);
|
||||
this.audios = Collections.unmodifiableList(audios);
|
||||
this.subtitles = Collections.unmodifiableList(subtitles);
|
||||
@ -121,7 +128,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||
copyRenditionsList(audios, GROUP_INDEX_AUDIO, streamKeys),
|
||||
copyRenditionsList(subtitles, GROUP_INDEX_SUBTITLE, streamKeys),
|
||||
muxedAudioFormat,
|
||||
muxedCaptionFormats);
|
||||
muxedCaptionFormats,
|
||||
hasIndependentSegments);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,8 +141,15 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||
public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) {
|
||||
List<HlsUrl> variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUrl));
|
||||
List<HlsUrl> emptyList = Collections.emptyList();
|
||||
return new HlsMasterPlaylist(null, Collections.<String>emptyList(), variant, emptyList,
|
||||
emptyList, null, null);
|
||||
return new HlsMasterPlaylist(
|
||||
null,
|
||||
Collections.<String>emptyList(),
|
||||
variant,
|
||||
emptyList,
|
||||
emptyList,
|
||||
/* muxedAudioFormat= */ null,
|
||||
/* muxedCaptionFormats= */ null,
|
||||
/* hasIndependentSegments= */ false);
|
||||
}
|
||||
|
||||
private static List<HlsUrl> copyRenditionsList(
|
||||
|
@ -174,10 +174,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
* The target duration in microseconds, as defined by #EXT-X-TARGETDURATION.
|
||||
*/
|
||||
public final long targetDurationUs;
|
||||
/**
|
||||
* Whether the playlist contains the #EXT-X-INDEPENDENT-SEGMENTS tag.
|
||||
*/
|
||||
public final boolean hasIndependentSegmentsTag;
|
||||
/**
|
||||
* Whether the playlist contains the #EXT-X-ENDLIST tag.
|
||||
*/
|
||||
@ -211,7 +207,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
* @param mediaSequence See {@link #mediaSequence}.
|
||||
* @param version See {@link #version}.
|
||||
* @param targetDurationUs See {@link #targetDurationUs}.
|
||||
* @param hasIndependentSegmentsTag See {@link #hasIndependentSegmentsTag}.
|
||||
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
|
||||
* @param hasEndTag See {@link #hasEndTag}.
|
||||
* @param hasProgramDateTime See {@link #hasProgramDateTime}.
|
||||
* @param drmInitData See {@link #drmInitData}.
|
||||
@ -228,12 +224,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
long mediaSequence,
|
||||
int version,
|
||||
long targetDurationUs,
|
||||
boolean hasIndependentSegmentsTag,
|
||||
boolean hasIndependentSegments,
|
||||
boolean hasEndTag,
|
||||
boolean hasProgramDateTime,
|
||||
DrmInitData drmInitData,
|
||||
List<Segment> segments) {
|
||||
super(baseUri, tags);
|
||||
super(baseUri, tags, hasIndependentSegments);
|
||||
this.playlistType = playlistType;
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.hasDiscontinuitySequence = hasDiscontinuitySequence;
|
||||
@ -241,7 +237,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
this.mediaSequence = mediaSequence;
|
||||
this.version = version;
|
||||
this.targetDurationUs = targetDurationUs;
|
||||
this.hasIndependentSegmentsTag = hasIndependentSegmentsTag;
|
||||
this.hasEndTag = hasEndTag;
|
||||
this.hasProgramDateTime = hasProgramDateTime;
|
||||
this.drmInitData = drmInitData;
|
||||
@ -295,7 +290,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
*
|
||||
* @param startTimeUs The start time for the returned playlist.
|
||||
* @param discontinuitySequence The discontinuity sequence for the returned playlist.
|
||||
* @return The playlist.
|
||||
* @return An identical playlist including the provided discontinuity and timing information.
|
||||
*/
|
||||
public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
|
||||
return new HlsMediaPlaylist(
|
||||
@ -309,7 +304,41 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
hasIndependentSegmentsTag,
|
||||
hasIndependentSegments,
|
||||
hasEndTag,
|
||||
hasProgramDateTime,
|
||||
drmInitData,
|
||||
segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a playlist identical to this one, except for adding any inheritable attributes from the
|
||||
* provided {@link HlsMasterPlaylist}.
|
||||
*
|
||||
* <p>The inheritable attributes are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #hasIndependentSegments}.
|
||||
* </ul>
|
||||
*
|
||||
* @return An identical playlist including the inheritable attributes from {@code masterPlaylist}.
|
||||
*/
|
||||
public HlsMediaPlaylist copyWithMasterPlaylistInfo(HlsMasterPlaylist masterPlaylist) {
|
||||
if (hasIndependentSegments || !masterPlaylist.hasIndependentSegments) {
|
||||
return this;
|
||||
}
|
||||
return new HlsMediaPlaylist(
|
||||
playlistType,
|
||||
baseUri,
|
||||
tags,
|
||||
startOffsetUs,
|
||||
startTimeUs,
|
||||
hasDiscontinuitySequence,
|
||||
discontinuitySequence,
|
||||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
hasIndependentSegments || masterPlaylist.hasIndependentSegments,
|
||||
hasEndTag,
|
||||
hasProgramDateTime,
|
||||
drmInitData,
|
||||
@ -319,8 +348,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
/**
|
||||
* Returns a playlist identical to this one except that an end tag is added. If an end tag is
|
||||
* already present then the playlist will return itself.
|
||||
*
|
||||
* @return The playlist.
|
||||
*/
|
||||
public HlsMediaPlaylist copyWithEndTag() {
|
||||
if (this.hasEndTag) {
|
||||
@ -337,7 +364,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
hasIndependentSegmentsTag,
|
||||
hasIndependentSegments,
|
||||
/* hasEndTag= */ true,
|
||||
hasProgramDateTime,
|
||||
drmInitData,
|
||||
|
@ -30,14 +30,21 @@ public abstract class HlsPlaylist implements FilterableManifest<HlsPlaylist> {
|
||||
* The list of tags in the playlist.
|
||||
*/
|
||||
public final List<String> tags;
|
||||
/**
|
||||
* Whether the media is formed of independent segments, as defined by the
|
||||
* #EXT-X-INDEPENDENT-SEGMENTS tag.
|
||||
*/
|
||||
public final boolean hasIndependentSegments;
|
||||
|
||||
/**
|
||||
* @param baseUri See {@link #baseUri}.
|
||||
* @param tags See {@link #tags}.
|
||||
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
|
||||
*/
|
||||
protected HlsPlaylist(String baseUri, List<String> tags) {
|
||||
protected HlsPlaylist(String baseUri, List<String> tags, boolean hasIndependentSegments) {
|
||||
this.baseUri = baseUri;
|
||||
this.tags = Collections.unmodifiableList(tags);
|
||||
this.hasIndependentSegments = hasIndependentSegments;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -217,6 +217,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
Format muxedAudioFormat = null;
|
||||
List<Format> muxedCaptionFormats = null;
|
||||
boolean noClosedCaptions = false;
|
||||
boolean hasIndependentSegmentsTag = false;
|
||||
|
||||
String line;
|
||||
while (iterator.hasNext()) {
|
||||
@ -227,7 +228,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
tags.add(line);
|
||||
}
|
||||
|
||||
if (line.startsWith(TAG_MEDIA)) {
|
||||
if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
|
||||
hasIndependentSegmentsTag = true;
|
||||
} else if (line.startsWith(TAG_MEDIA)) {
|
||||
// Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF
|
||||
// tags.
|
||||
mediaTags.add(line);
|
||||
@ -326,8 +329,15 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
if (noClosedCaptions) {
|
||||
muxedCaptionFormats = Collections.emptyList();
|
||||
}
|
||||
return new HlsMasterPlaylist(baseUri, tags, variants, audios, subtitles, muxedAudioFormat,
|
||||
muxedCaptionFormats);
|
||||
return new HlsMasterPlaylist(
|
||||
baseUri,
|
||||
tags,
|
||||
variants,
|
||||
audios,
|
||||
subtitles,
|
||||
muxedAudioFormat,
|
||||
muxedCaptionFormats,
|
||||
hasIndependentSegmentsTag);
|
||||
}
|
||||
|
||||
@C.SelectionFlags
|
||||
|
@ -105,6 +105,18 @@ public class HlsMasterPlaylistParserTest {
|
||||
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\","
|
||||
+ "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n";
|
||||
|
||||
private static final String PLAYLIST_WITH_INDEPENDENT_SEGMENTS =
|
||||
" #EXTM3U\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-INDEPENDENT-SEGMENTS\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
|
||||
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
||||
+ "http://example.com/low.m3u8\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
|
||||
+ "http://example.com/spaces_in_codecs.m3u8\n";
|
||||
|
||||
@Test
|
||||
public void testParseMasterPlaylist() throws IOException {
|
||||
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
|
||||
@ -195,6 +207,17 @@ public class HlsMasterPlaylistParserTest {
|
||||
assertThat(secondAudioFormat.sampleMimeType).isEqualTo(MimeTypes.AUDIO_AC3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndependentSegments() throws IOException {
|
||||
HlsMasterPlaylist playlistWithIndependentSegments =
|
||||
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INDEPENDENT_SEGMENTS);
|
||||
assertThat(playlistWithIndependentSegments.hasIndependentSegments).isTrue();
|
||||
|
||||
HlsMasterPlaylist playlistWithoutIndependentSegments =
|
||||
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
|
||||
assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse();
|
||||
}
|
||||
|
||||
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse(uri);
|
||||
|
@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -278,4 +279,42 @@ public class HlsMediaPlaylistParserTest {
|
||||
assertThat(segments.get(1).initializationSegment.url).isEqualTo("init1.ts");
|
||||
assertThat(segments.get(3).initializationSegment.url).isEqualTo("init2.ts");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterPlaylistAttributeInheritance() throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-VERSION:3\n"
|
||||
+ "#EXT-X-TARGETDURATION:5\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:10\n"
|
||||
+ "#EXTINF:5.005,\n"
|
||||
+ "02/00/27.ts\n"
|
||||
+ "#EXT-X-MAP:URI=\"init1.ts\""
|
||||
+ "#EXTINF:5.005,\n"
|
||||
+ "02/00/32.ts\n"
|
||||
+ "#EXTINF:5.005,\n"
|
||||
+ "02/00/42.ts\n"
|
||||
+ "#EXT-X-MAP:URI=\"init2.ts\""
|
||||
+ "#EXTINF:5.005,\n"
|
||||
+ "02/00/47.ts\n";
|
||||
InputStream inputStream =
|
||||
new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
assertThat(playlist.hasIndependentSegments).isFalse();
|
||||
|
||||
HlsMasterPlaylist masterPlaylist =
|
||||
new HlsMasterPlaylist(
|
||||
/* baseUri= */ "https://example.com/",
|
||||
/* tags= */ Collections.emptyList(),
|
||||
/* variants= */ Collections.emptyList(),
|
||||
/* audios= */ Collections.emptyList(),
|
||||
/* subtitles= */ Collections.emptyList(),
|
||||
/* muxedAudioFormat= */ null,
|
||||
/* muxedCaptionFormats= */ null,
|
||||
/* hasIndependentSegments= */ true);
|
||||
|
||||
assertThat(playlist.copyWithMasterPlaylistInfo(masterPlaylist).hasIndependentSegments).isTrue();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user