From ddaa9092ec6cda22145f0f562e4463f01f312175 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 25 Nov 2015 17:00:18 +0000 Subject: [PATCH] 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). --- .../exoplayer/hls/HlsMediaPlaylistParserTest.java | 11 ++++++----- .../google/android/exoplayer/hls/HlsChunkSource.java | 9 ++++++--- .../android/exoplayer/hls/HlsMediaPlaylist.java | 12 ++++++------ .../android/exoplayer/hls/HlsPlaylistParser.java | 11 +++++++---- .../com/google/android/exoplayer/hls/TsChunk.java | 11 +++++++++-- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java index beec89e4a6..e4337e9650 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 6f91fb1ce2..63f1f4c3df 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -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); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java index ec4c538155..44c760bccf 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java @@ -29,9 +29,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist { */ public static final class Segment implements Comparable { - 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; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java index 87d3fb4eff..b4665d0bdf 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java @@ -40,6 +40,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser 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 || 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 List 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 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 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; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java index 520046be36..8354e312d0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java @@ -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;