mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add initial support for EXT-X-GAP
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185676652
This commit is contained in:
parent
c8e950537d
commit
1d4bc7dda7
@ -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
|
||||
|
@ -33,7 +33,7 @@ import junit.framework.TestCase;
|
||||
*/
|
||||
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"
|
||||
@ -69,76 +69,106 @@ public class HlsMediaPlaylistParserTest extends TestCase {
|
||||
+ "#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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -330,11 +330,27 @@ import java.util.List;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
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;
|
||||
@ -97,6 +98,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* @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.
|
||||
@ -119,6 +121,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
long endTimeUs,
|
||||
long chunkMediaSequence,
|
||||
int discontinuitySequenceNumber,
|
||||
boolean hasGapTag,
|
||||
boolean isMasterTimestampSource,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
HlsMediaChunk previousChunk,
|
||||
@ -141,6 +144,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
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;
|
||||
@ -211,7 +215,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public void load() throws IOException, InterruptedException {
|
||||
maybeLoadInitData();
|
||||
if (!loadCanceled) {
|
||||
loadMedia();
|
||||
if (!hasGapTag) {
|
||||
loadMedia();
|
||||
}
|
||||
loadCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,7 +290,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
} finally {
|
||||
Util.closeQuietly(dataSource);
|
||||
}
|
||||
loadCompleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,8 +69,16 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,10 +90,18 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
* @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;
|
||||
@ -94,6 +110,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
this.encryptionIV = encryptionIV;
|
||||
this.byterangeOffset = byterangeOffset;
|
||||
this.byterangeLength = byterangeLength;
|
||||
this.hasGapTag = hasGapTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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";
|
||||
@ -357,6 +358,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
long segmentByteRangeOffset = 0;
|
||||
long segmentByteRangeLength = C.LENGTH_UNSET;
|
||||
long segmentMediaSequence = 0;
|
||||
boolean hasGapTag = false;
|
||||
|
||||
String encryptionKeyUri = null;
|
||||
String encryptionIV = null;
|
||||
@ -449,6 +451,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
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) {
|
||||
@ -462,19 +470,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user