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-VERSION:3\n"
+ "#EXT-X-TARGETDURATION:8\n" + "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n" + "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
+ "#EXT-X-ALLOW-CACHE:YES\n" + "#EXT-X-ALLOW-CACHE:YES\n"
+ "\n" + "\n"
+ "#EXTINF:7.975,\n" + "#EXTINF:7.975,\n"
@ -79,7 +80,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertNotNull(segments); assertNotNull(segments);
assertEquals(5, segments.size()); assertEquals(5, segments.size());
assertEquals(false, segments.get(0).discontinuity); assertEquals(4, segments.get(0).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(0).durationSecs); assertEquals(7.975, segments.get(0).durationSecs);
assertEquals(false, segments.get(0).isEncrypted); assertEquals(false, segments.get(0).isEncrypted);
assertEquals(null, segments.get(0).encryptionKeyUri); assertEquals(null, segments.get(0).encryptionKeyUri);
@ -88,7 +89,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(0, segments.get(0).byterangeOffset); assertEquals(0, segments.get(0).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url); 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(7.975, segments.get(1).durationSecs);
assertEquals(true, segments.get(1).isEncrypted); assertEquals(true, segments.get(1).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri); 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(51370, segments.get(1).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url); 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(7.941, segments.get(2).durationSecs);
assertEquals(false, segments.get(2).isEncrypted); assertEquals(false, segments.get(2).isEncrypted);
assertEquals(null, segments.get(2).encryptionKeyUri); assertEquals(null, segments.get(2).encryptionKeyUri);
@ -106,7 +107,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(102871, segments.get(2).byterangeOffset); assertEquals(102871, segments.get(2).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url); 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(7.975, segments.get(3).durationSecs);
assertEquals(true, segments.get(3).isEncrypted); assertEquals(true, segments.get(3).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri); 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(154372, segments.get(3).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url); 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(7.975, segments.get(4).durationSecs);
assertEquals(true, segments.get(4).isEncrypted); assertEquals(true, segments.get(4).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri); 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); Extractor extractor = new Mp3Extractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight); switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity } else if (previousTsChunk == null || liveDiscontinuity
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| !format.equals(previousTsChunk.format)) { || !format.equals(previousTsChunk.format)) {
// MPEG-2 TS segments, but we need a new extractor. // 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) { || ptsTimestampAdjuster == null) {
// TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner // TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. // identifier com.apple.streaming.transportStreamTimestamp.
@ -382,7 +384,8 @@ public class HlsChunkSource {
} }
out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, 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 static final class Segment implements Comparable<Long> {
public final boolean discontinuity;
public final double durationSecs;
public final String url; public final String url;
public final double durationSecs;
public final int discontinuitySequenceNumber;
public final long startTimeUs; public final long startTimeUs;
public final boolean isEncrypted; public final boolean isEncrypted;
public final String encryptionKeyUri; public final String encryptionKeyUri;
@ -39,12 +39,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final int byterangeOffset; public final int byterangeOffset;
public final int byterangeLength; public final int byterangeLength;
public Segment(String uri, double durationSecs, boolean discontinuity, long startTimeUs, public Segment(String uri, double durationSecs, int discontinuitySequenceNumber,
boolean isEncrypted, String encryptionKeyUri, String encryptionIV, int byterangeOffset, long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
int byterangeLength) { int byterangeOffset, int byterangeLength) {
this.url = uri; this.url = uri;
this.durationSecs = durationSecs; this.durationSecs = durationSecs;
this.discontinuity = discontinuity; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.isEncrypted = isEncrypted; this.isEncrypted = isEncrypted;
this.encryptionKeyUri = encryptionKeyUri; 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 STREAM_INF_TAG = "#EXT-X-STREAM-INF";
private static final String MEDIA_TAG = "#EXT-X-MEDIA"; private static final String MEDIA_TAG = "#EXT-X-MEDIA";
private static final String DISCONTINUITY_TAG = "#EXT-X-DISCONTINUITY"; 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_DURATION_TAG = "#EXTINF";
private static final String MEDIA_SEQUENCE_TAG = "#EXT-X-MEDIA-SEQUENCE"; private static final String MEDIA_SEQUENCE_TAG = "#EXT-X-MEDIA-SEQUENCE";
private static final String TARGET_DURATION_TAG = "#EXT-X-TARGETDURATION"; 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(KEY_TAG)
|| line.startsWith(BYTERANGE_TAG) || line.startsWith(BYTERANGE_TAG)
|| line.equals(DISCONTINUITY_TAG) || line.equals(DISCONTINUITY_TAG)
|| line.equals(DISCONTINUITY_SEQUENCE_TAG)
|| line.equals(ENDLIST_TAG)) { || line.equals(ENDLIST_TAG)) {
extraLines.add(line); extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl); return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl);
@ -208,7 +210,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
List<Segment> segments = new ArrayList<>(); List<Segment> segments = new ArrayList<>();
double segmentDurationSecs = 0.0; double segmentDurationSecs = 0.0;
boolean segmentDiscontinuity = false; int discontinuitySequenceNumber = 0;
long segmentStartTimeUs = 0; long segmentStartTimeUs = 0;
int segmentByterangeOffset = 0; int segmentByterangeOffset = 0;
int segmentByterangeLength = C.LENGTH_UNBOUNDED; int segmentByterangeLength = C.LENGTH_UNBOUNDED;
@ -249,8 +251,10 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
if (splitByteRange.length > 1) { if (splitByteRange.length > 1) {
segmentByterangeOffset = Integer.parseInt(splitByteRange[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)) { } else if (line.equals(DISCONTINUITY_TAG)) {
segmentDiscontinuity = true; discontinuitySequenceNumber++;
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
String segmentEncryptionIV; String segmentEncryptionIV;
if (!isEncrypted) { if (!isEncrypted) {
@ -264,11 +268,10 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
if (segmentByterangeLength == C.LENGTH_UNBOUNDED) { if (segmentByterangeLength == C.LENGTH_UNBOUNDED) {
segmentByterangeOffset = 0; segmentByterangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationSecs, segmentDiscontinuity, segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentByterangeOffset, segmentByterangeLength)); segmentByterangeOffset, segmentByterangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND); segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND);
segmentDiscontinuity = false;
segmentDurationSecs = 0.0; segmentDurationSecs = 0.0;
if (segmentByterangeLength != C.LENGTH_UNBOUNDED) { if (segmentByterangeLength != C.LENGTH_UNBOUNDED) {
segmentByterangeOffset += segmentByterangeLength; segmentByterangeOffset += segmentByterangeLength;

View File

@ -31,6 +31,11 @@ import java.io.IOException;
*/ */
public final class TsChunk extends MediaChunk { 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. * 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 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 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 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 chunkIndex The index of the chunk.
* @param extractorWrapper A wrapped extractor to parse samples from the data. * @param extractorWrapper A wrapped extractor to parse samples from the data.
* @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, HlsExtractorWrapper extractorWrapper, long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
byte[] encryptionKey, byte[] encryptionIv) { HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex); startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;