Parse the sequence number at discontinuities.

This is required to match up segments in one playlist (e.g. VTT)
to those in another (e.g. Audio/Video).
This commit is contained in:
Oliver Woodman 2015-11-25 17:00:18 +00:00
parent 6f62b499c5
commit ddaa9092ec
5 changed files with 34 additions and 20 deletions

View File

@ -37,6 +37,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
+ "#EXT-X-ALLOW-CACHE:YES\n"
+ "\n"
+ "#EXTINF:7.975,\n"
@ -79,7 +80,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertNotNull(segments);
assertEquals(5, segments.size());
assertEquals(false, segments.get(0).discontinuity);
assertEquals(4, segments.get(0).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(0).durationSecs);
assertEquals(false, segments.get(0).isEncrypted);
assertEquals(null, segments.get(0).encryptionKeyUri);
@ -88,7 +89,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(0, segments.get(0).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url);
assertEquals(false, segments.get(1).discontinuity);
assertEquals(4, segments.get(1).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(1).durationSecs);
assertEquals(true, segments.get(1).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri);
@ -97,7 +98,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(51370, segments.get(1).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url);
assertEquals(false, segments.get(2).discontinuity);
assertEquals(4, segments.get(2).discontinuitySequenceNumber);
assertEquals(7.941, segments.get(2).durationSecs);
assertEquals(false, segments.get(2).isEncrypted);
assertEquals(null, segments.get(2).encryptionKeyUri);
@ -106,7 +107,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(102871, segments.get(2).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url);
assertEquals(true, segments.get(3).discontinuity);
assertEquals(5, segments.get(3).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(3).durationSecs);
assertEquals(true, segments.get(3).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri);
@ -117,7 +118,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(154372, segments.get(3).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url);
assertEquals(false, segments.get(4).discontinuity);
assertEquals(5, segments.get(4).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(4).durationSecs);
assertEquals(true, segments.get(4).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri);

View File

@ -364,10 +364,12 @@ public class HlsChunkSource {
Extractor extractor = new Mp3Extractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
} else if (previousTsChunk == null || liveDiscontinuity
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| !format.equals(previousTsChunk.format)) {
// MPEG-2 TS segments, but we need a new extractor.
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
if (previousTsChunk == null || liveDiscontinuity
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| ptsTimestampAdjuster == null) {
// TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp.
@ -382,7 +384,8 @@ public class HlsChunkSource {
}
out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
chunkMediaSequence, extractorWrapper, encryptionKey, encryptionIv);
chunkMediaSequence, segment.discontinuitySequenceNumber, extractorWrapper, encryptionKey,
encryptionIv);
}
/**

View File

@ -29,9 +29,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
*/
public static final class Segment implements Comparable<Long> {
public final boolean discontinuity;
public final double durationSecs;
public final String url;
public final double durationSecs;
public final int discontinuitySequenceNumber;
public final long startTimeUs;
public final boolean isEncrypted;
public final String encryptionKeyUri;
@ -39,12 +39,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final int byterangeOffset;
public final int byterangeLength;
public Segment(String uri, double durationSecs, boolean discontinuity, long startTimeUs,
boolean isEncrypted, String encryptionKeyUri, String encryptionIV, int byterangeOffset,
int byterangeLength) {
public Segment(String uri, double durationSecs, int discontinuitySequenceNumber,
long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
int byterangeOffset, int byterangeLength) {
this.url = uri;
this.durationSecs = durationSecs;
this.discontinuity = discontinuity;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.startTimeUs = startTimeUs;
this.isEncrypted = isEncrypted;
this.encryptionKeyUri = encryptionKeyUri;

View File

@ -40,6 +40,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";
private static final String MEDIA_TAG = "#EXT-X-MEDIA";
private static final String DISCONTINUITY_TAG = "#EXT-X-DISCONTINUITY";
private static final String DISCONTINUITY_SEQUENCE_TAG = "#EXT-X-DISCONTINUITY-SEQUENCE";
private static final String MEDIA_DURATION_TAG = "#EXTINF";
private static final String MEDIA_SEQUENCE_TAG = "#EXT-X-MEDIA-SEQUENCE";
private static final String TARGET_DURATION_TAG = "#EXT-X-TARGETDURATION";
@ -122,6 +123,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|| line.startsWith(KEY_TAG)
|| line.startsWith(BYTERANGE_TAG)
|| line.equals(DISCONTINUITY_TAG)
|| line.equals(DISCONTINUITY_SEQUENCE_TAG)
|| line.equals(ENDLIST_TAG)) {
extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl);
@ -208,7 +210,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
List<Segment> segments = new ArrayList<>();
double segmentDurationSecs = 0.0;
boolean segmentDiscontinuity = false;
int discontinuitySequenceNumber = 0;
long segmentStartTimeUs = 0;
int segmentByterangeOffset = 0;
int segmentByterangeLength = C.LENGTH_UNBOUNDED;
@ -249,8 +251,10 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
if (splitByteRange.length > 1) {
segmentByterangeOffset = Integer.parseInt(splitByteRange[1]);
}
} else if (line.startsWith(DISCONTINUITY_SEQUENCE_TAG)) {
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
} else if (line.equals(DISCONTINUITY_TAG)) {
segmentDiscontinuity = true;
discontinuitySequenceNumber++;
} else if (!line.startsWith("#")) {
String segmentEncryptionIV;
if (!isEncrypted) {
@ -264,11 +268,10 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
if (segmentByterangeLength == C.LENGTH_UNBOUNDED) {
segmentByterangeOffset = 0;
}
segments.add(new Segment(line, segmentDurationSecs, segmentDiscontinuity,
segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentByterangeOffset, segmentByterangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND);
segmentDiscontinuity = false;
segmentDurationSecs = 0.0;
if (segmentByterangeLength != C.LENGTH_UNBOUNDED) {
segmentByterangeOffset += segmentByterangeLength;

View File

@ -31,6 +31,11 @@ import java.io.IOException;
*/
public final class TsChunk extends MediaChunk {
/**
* The discontinuity sequence number of the chunk.
*/
public final int discontinuitySequenceNumber;
/**
* The wrapped extractor into which this chunk is being consumed.
*/
@ -48,16 +53,18 @@ public final class TsChunk extends MediaChunk {
* @param format The format of the stream to which this chunk belongs.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param chunkIndex The index of the chunk.
* @param extractorWrapper A wrapped extractor to parse samples from the data.
* @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, HlsExtractorWrapper extractorWrapper,
byte[] encryptionKey, byte[] encryptionIv) {
long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.extractorWrapper = extractorWrapper;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;