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:
aquilescanta 2018-06-29 04:28:33 -07:00 committed by Oliver Woodman
parent a916ad6a50
commit efa714ab4f
9 changed files with 155 additions and 28 deletions

View File

@ -18,8 +18,11 @@
* 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
* 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.

View File

@ -248,7 +248,7 @@ import java.util.List;
return;
}
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
independentSegments = mediaPlaylist.hasIndependentSegmentsTag;
independentSegments = mediaPlaylist.hasIndependentSegments;
updateLiveEdgeTimeUs(mediaPlaylist);

View File

@ -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;

View File

@ -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(

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);

View File

@ -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();
}
}