Skip to content

Commit

Permalink
Add initial support for EXT-X-GAP
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=185676652
  • Loading branch information
AquilesCanta authored and Oliver Woodman committed Feb 16, 2018
1 parent c8e9505 commit 1d4bc7d
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 82 deletions.
1 change: 1 addition & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
([#3622](https://github.com/google/ExoPlayer/issues/3622)).
* Use long for media sequence numbers
([#3747](https://github.com/google/ExoPlayer/issues/3747))
* Add initial support for the EXT-X-GAP tag.
* New Cast extension: Simplifies toggling between local and Cast playbacks.
* Audio:
* Support TrueHD passthrough for rechunked samples in Matroska files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
*/
public class HlsMediaPlaylistParserTest extends TestCase {

public void testParseMediaPlaylist() {
public void testParseMediaPlaylist() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
Expand Down Expand Up @@ -69,76 +69,106 @@ public void testParseMediaPlaylist() {
+ "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream(
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
try {
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist).isNotNull();
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);

HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD);
assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD);
assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000);

assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679);
assertThat(mediaPlaylist.version).isEqualTo(3);
assertThat(mediaPlaylist.hasEndTag).isTrue();
List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull();
assertThat(segments).hasSize(5);
assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679);
assertThat(mediaPlaylist.version).isEqualTo(3);
assertThat(mediaPlaylist.hasEndTag).isTrue();
List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull();
assertThat(segments).hasSize(5);

Segment segment = segments.get(0);
assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence)
.isEqualTo(4);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isNull();
assertThat(segment.byterangeLength).isEqualTo(51370);
assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts");
Segment segment = segments.get(0);
assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence)
.isEqualTo(4);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isNull();
assertThat(segment.byterangeLength).isEqualTo(51370);
assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts");

segment = segments.get(1);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2680");
assertThat(segment.encryptionIV).isEqualTo("0x1566B");
assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147483648L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts");
segment = segments.get(1);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2680");
assertThat(segment.encryptionIV).isEqualTo("0x1566B");
assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147483648L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts");

segment = segments.get(2);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7941000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isEqualTo(null);
assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147535149L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts");
segment = segments.get(2);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7941000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isEqualTo(null);
assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147535149L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts");

segment = segments.get(3);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7A == 2682.
assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A");
assertThat(segment.byterangeLength).isEqualTo(51740);
assertThat(segment.byterangeOffset).isEqualTo(2147586650L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts");
segment = segments.get(3);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7A == 2682.
assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A");
assertThat(segment.byterangeLength).isEqualTo(51740);
assertThat(segment.byterangeOffset).isEqualTo(2147586650L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts");

segment = segments.get(4);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7B == 2683.
assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B");
assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET);
assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
} catch (IOException exception) {
fail(exception.getMessage());
}
segment = segments.get(4);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7B == 2683.
assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B");
assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET);
assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
}

public void testGapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test2.m3u8");
String playlistString =
"#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-TARGETDURATION:5\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PROGRAM-DATE-TIME:2016-09-22T02:00:01+00:00\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://example.com/key?value=something\"\n"
+ "#EXTINF:5.005,\n"
+ "02/00/27.ts\n"
+ "#EXTINF:5.005,\n"
+ "02/00/32.ts\n"
+ "#EXT-X-KEY:METHOD=NONE\n"
+ "#EXTINF:5.005,\n"
+ "#EXT-X-GAP \n"
+ "../dummy.ts\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://key-service.bamgrid.com/1.0/key?"
+ "hex-value=9FB8989D15EEAAF8B21B860D7ED3072A\",IV=0x410C8AC18AA42EFA18B5155484F5FC34\n"
+ "#EXTINF:5.005,\n"
+ "02/00/42.ts\n"
+ "#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.hasEndTag).isFalse();
assertThat(playlist.segments.get(1).hasGapTag).isFalse();
assertThat(playlist.segments.get(2).hasGapTag).isTrue();
assertThat(playlist.segments.get(3).hasGapTag).isFalse();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,27 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, long l
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec,
selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs,
chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous,
mediaPlaylist.drmInitData, encryptionKey, encryptionIv);
out.chunk =
new HlsMediaChunk(
extractorFactory,
mediaDataSource,
dataSpec,
initDataSpec,
selectedUrl,
muxedCaptionFormats,
trackSelection.getSelectionReason(),
trackSelection.getSelectionData(),
startTimeUs,
startTimeUs + segment.durationUs,
chunkMediaSequence,
discontinuitySequence,
segment.hasGapTag,
isTimestampMaster,
timestampAdjuster,
previous,
mediaPlaylist.drmInitData,
encryptionKey,
encryptionIv);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
private final DataSpec initDataSpec;
private final boolean isEncrypted;
private final boolean isMasterTimestampSource;
private final boolean hasGapTag;
private final TimestampAdjuster timestampAdjuster;
private final boolean shouldSpliceIn;
private final Extractor extractor;
Expand Down Expand Up @@ -97,6 +98,7 @@
* @param endTimeUs The end time of the chunk in microseconds.
* @param chunkMediaSequence The media sequence number of the chunk.
* @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param hasGapTag Whether the chunk is tagged with EXT-X-GAP.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
Expand All @@ -119,6 +121,7 @@ public HlsMediaChunk(
long endTimeUs,
long chunkMediaSequence,
int discontinuitySequenceNumber,
boolean hasGapTag,
boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster,
HlsMediaChunk previousChunk,
Expand All @@ -141,6 +144,7 @@ public HlsMediaChunk(
this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.hasGapTag = hasGapTag;
Extractor previousExtractor = null;
if (previousChunk != null) {
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
Expand Down Expand Up @@ -211,7 +215,10 @@ public boolean isLoadCanceled() {
public void load() throws IOException, InterruptedException {
maybeLoadInitData();
if (!loadCanceled) {
loadMedia();
if (!hasGapTag) {
loadMedia();
}
loadCompleted = true;
}
}

Expand Down Expand Up @@ -283,7 +290,6 @@ private void loadMedia() throws IOException, InterruptedException {
} finally {
Util.closeQuietly(dataSource);
}
loadCompleted = true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,16 @@ public static final class Segment implements Comparable<Long> {
*/
public final long byterangeLength;

/** Whether the segment is tagged with #EXT-X-GAP. */
public final boolean hasGapTag;

/**
* @param uri See {@link #url}.
* @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}.
*/
public Segment(String uri, long byterangeOffset, long byterangeLength) {
this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength);
this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength, false);
}

/**
Expand All @@ -82,10 +90,18 @@ public Segment(String uri, long byterangeOffset, long byterangeLength) {
* @param encryptionIV See {@link #encryptionIV}.
* @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}.
* @param hasGapTag See {@link #hasGapTag}.
*/
public Segment(String url, long durationUs, int relativeDiscontinuitySequence,
long relativeStartTimeUs, String fullSegmentEncryptionKeyUri,
String encryptionIV, long byterangeOffset, long byterangeLength) {
public Segment(
String url,
long durationUs,
int relativeDiscontinuitySequence,
long relativeStartTimeUs,
String fullSegmentEncryptionKeyUri,
String encryptionIV,
long byterangeOffset,
long byterangeLength,
boolean hasGapTag) {
this.url = url;
this.durationUs = durationUs;
this.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
Expand All @@ -94,6 +110,7 @@ public Segment(String url, long durationUs, int relativeDiscontinuitySequence,
this.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset;
this.byterangeLength = byterangeLength;
this.hasGapTag = hasGapTag;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
private static final String TAG_KEY = "#EXT-X-KEY";
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
private static final String TAG_GAP = "#EXT-X-GAP";

private static final String TYPE_AUDIO = "AUDIO";
private static final String TYPE_VIDEO = "VIDEO";
Expand Down Expand Up @@ -357,6 +358,7 @@ private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String
long segmentByteRangeOffset = 0;
long segmentByteRangeLength = C.LENGTH_UNSET;
long segmentMediaSequence = 0;
boolean hasGapTag = false;

String encryptionKeyUri = null;
String encryptionIV = null;
Expand Down Expand Up @@ -449,6 +451,12 @@ private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
}
} else if (line.equals(TAG_GAP)) {
hasGapTag = true;
} else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
hasIndependentSegmentsTag = true;
} else if (line.equals(TAG_ENDLIST)) {
hasEndTag = true;
} else if (!line.startsWith("#")) {
String segmentEncryptionIV;
if (encryptionKeyUri == null) {
Expand All @@ -462,19 +470,24 @@ private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String
if (segmentByteRangeLength == C.LENGTH_UNSET) {
segmentByteRangeOffset = 0;
}
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
segmentStartTimeUs, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength));
segments.add(
new Segment(
line,
segmentDurationUs,
relativeDiscontinuitySequence,
segmentStartTimeUs,
encryptionKeyUri,
segmentEncryptionIV,
segmentByteRangeOffset,
segmentByteRangeLength,
hasGapTag));
segmentStartTimeUs += segmentDurationUs;
segmentDurationUs = 0;
if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset += segmentByteRangeLength;
}
segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
hasIndependentSegmentsTag = true;
} else if (line.equals(TAG_ENDLIST)) {
hasEndTag = true;
hasGapTag = false;
}
}
return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, playlistStartTimeUs,
Expand Down

0 comments on commit 1d4bc7d

Please sign in to comment.