diff --git a/karma.conf.js b/karma.conf.js index 20fbd77ace..0a5d5087f5 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -272,6 +272,8 @@ module.exports = (config) => { {pattern: 'test/test/assets/hls-raw-ec3/*', included: false}, {pattern: 'test/test/assets/hls-raw-mp3/*', included: false}, {pattern: 'test/test/assets/hls-sample-aes/*', included: false}, + // eslint-disable-next-line max-len + {pattern: 'test/test/assets/hls-text-no-discontinuity/*', included: false}, {pattern: 'test/test/assets/hls-text-offset/*', included: false}, {pattern: 'test/test/assets/hls-ts-aac/*', included: false}, {pattern: 'test/test/assets/hls-ts-ac3/*', included: false}, diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 14a06556d8..50b6b3f6e6 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -4126,7 +4126,7 @@ shaka.hls.HlsParser = class { let aesKey = undefined; let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber( - playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0); + playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', -1); const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber( playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0); const skipTag = shaka.hls.Utils.getFirstTagWithName( diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index e839ab2140..775775a686 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -1139,7 +1139,8 @@ shaka.media.MediaSourceEngine = class { const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { - if (this.sequenceMode_) { + if (this.manifestType_ == shaka.media.ManifestParser.HLS && + reference.discontinuitySequence >= 0) { // This won't be known until the first video segment is appended. const offset = await this.textSequenceModeOffset_; this.textEngine_.setTimestampOffset(offset); @@ -1233,7 +1234,7 @@ shaka.media.MediaSourceEngine = class { const isBestSourceBufferForTimestamps = contentType == ContentType.VIDEO || !(ContentType.VIDEO in this.sourceBuffers_); - if (this.sequenceMode_ && isBestSourceBufferForTimestamps) { + if (isBestSourceBufferForTimestamps) { this.textSequenceModeOffset_.resolve(timestampOffset); } } @@ -1562,6 +1563,10 @@ shaka.media.MediaSourceEngine = class { this.textSequenceModeOffset_ = new shaka.util.PublicPromise(); } + if (!this.sequenceMode_) { + return; + } + // Queue an abort() to help MSE splice together overlapping segments. // We set appendWindowEnd when we change periods in DASH content, and the // period transition may result in overlap. diff --git a/lib/media/segment_reference.js b/lib/media/segment_reference.js index fa41865e4f..4f579de66c 100644 --- a/lib/media/segment_reference.js +++ b/lib/media/segment_reference.js @@ -295,7 +295,7 @@ shaka.media.SegmentReference = class { this.thumbnailSprite = null; /** @type {number} */ - this.discontinuitySequence = 0; + this.discontinuitySequence = -1; /** @type {boolean} */ this.allPartialSegments = allPartialSegments; diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 43a788b0ba..aff85ba6f0 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -2152,17 +2152,15 @@ shaka.media.StreamingEngine = class { } } - if (this.manifest_.sequenceMode) { - const lastDiscontinuitySequence = - mediaState.lastSegmentReference ? - mediaState.lastSegmentReference.discontinuitySequence : null; - // Across discontinuity bounds, we should resync timestamps for - // sequence mode playbacks. The next segment appended should - // land at its theoretical timestamp from the segment index. - if (reference.discontinuitySequence != lastDiscontinuitySequence) { - operations.push(this.playerInterface_.mediaSourceEngine.resync( - mediaState.type, reference.startTime)); - } + const lastDiscontinuitySequence = + mediaState.lastSegmentReference ? + mediaState.lastSegmentReference.discontinuitySequence : null; + // Across discontinuity bounds, we should resync timestamps. The next + // segment appended should land at its theoretical timestamp from the + // segment index. + if (reference.discontinuitySequence != lastDiscontinuitySequence) { + operations.push(this.playerInterface_.mediaSourceEngine.resync( + mediaState.type, reference.startTime)); } await Promise.all(operations); diff --git a/lib/text/vtt_text_parser.js b/lib/text/vtt_text_parser.js index efd5b7482c..97f1a6587b 100644 --- a/lib/text/vtt_text_parser.js +++ b/lib/text/vtt_text_parser.js @@ -86,13 +86,7 @@ shaka.text.VttTextParser = class { // Only use 'X-TIMESTAMP-MAP' with HLS. This overrides offset above. if (blocks[0].includes('X-TIMESTAMP-MAP') && this.manifestType_ == shaka.media.ManifestParser.HLS) { - if (this.sequenceMode_) { - // Compute a different, rollover-based offset for sequence mode. - offset = this.computeHlsSequenceModeOffset_(blocks[0], time); - } else { - // Calculate the offset from the segment startTime. - offset = time.segmentStart; - } + offset = this.computeHlsOffset_(blocks[0], time); } // Parse VTT regions. @@ -129,7 +123,7 @@ shaka.text.VttTextParser = class { * @return {number} * @private */ - computeHlsSequenceModeOffset_(headerBlock, time) { + computeHlsOffset_(headerBlock, time) { // https://bit.ly/2K92l7y // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with // the rest of the media. diff --git a/test/hls/hls_parser_integration.js b/test/hls/hls_parser_integration.js index 5d471ffcd1..b2ee481695 100644 --- a/test/hls/hls_parser_integration.js +++ b/test/hls/hls_parser_integration.js @@ -111,10 +111,6 @@ describe('HlsParser', () => { }); it('supports text discontinuity', async () => { - if (!shaka.util.Platform.supportsSequenceMode()) { - pending('Sequence mode is not supported by the platform.'); - } - player.setTextTrackVisibility(true); await player.load('/base/test/test/assets/hls-text-offset/index.m3u8'); @@ -134,4 +130,25 @@ describe('HlsParser', () => { await player.unload(); }); + + it('supports text without discontinuity', async () => { + player.setTextTrackVisibility(true); + + // eslint-disable-next-line max-len + await player.load('/base/test/test/assets/hls-text-no-discontinuity/index.m3u8'); + await video.play(); + + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 30); + + const cues = video.textTracks[0].cues; + expect(cues.length).toBe(3); + expect(cues[0].startTime).toBeCloseTo(0.6, 0); + expect(cues[0].endTime).toBeCloseTo(2.88, 0); + expect(cues[1].startTime).toBeCloseTo(2.88, 0); + expect(cues[1].endTime).toBeCloseTo(6.36, 0); + expect(cues[2].startTime).toBeCloseTo(6.36, 0); + expect(cues[2].endTime).toBeCloseTo(10.68, 0); + + await player.unload(); + }); }); diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js index 5f1f8fb62d..03a9e941e6 100644 --- a/test/hls/hls_parser_unit.js +++ b/test/hls/hls_parser_unit.js @@ -1153,6 +1153,7 @@ describe('HlsParser', () => { '#EXT-X-VERSION:3\n', '#EXT-X-TARGETDURATION:5\n', '#EXT-X-MEDIA-SEQUENCE:0\n', + '#EXT-X-DISCONTINUITY-SEQUENCE:0\n', '#EXTINF:3,\n', 'clip0-video-0.ts\n', '#EXTINF:1,\n', diff --git a/test/test/assets/hls-text-no-discontinuity/0.ts b/test/test/assets/hls-text-no-discontinuity/0.ts new file mode 100644 index 0000000000..64f8fcbc47 Binary files /dev/null and b/test/test/assets/hls-text-no-discontinuity/0.ts differ diff --git a/test/test/assets/hls-text-no-discontinuity/1.ts b/test/test/assets/hls-text-no-discontinuity/1.ts new file mode 100644 index 0000000000..64f8fcbc47 Binary files /dev/null and b/test/test/assets/hls-text-no-discontinuity/1.ts differ diff --git a/test/test/assets/hls-text-no-discontinuity/av.m3u8 b/test/test/assets/hls-text-no-discontinuity/av.m3u8 new file mode 100644 index 0000000000..b2b8e37f0b --- /dev/null +++ b/test/test/assets/hls-text-no-discontinuity/av.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:7 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-INDEPENDENT-SEGMENTS +#EXTINF:6.000000, +0.ts +#EXTINF:6.000000, +1.ts +#EXT-X-ENDLIST diff --git a/test/test/assets/hls-text-no-discontinuity/index.m3u8 b/test/test/assets/hls-text-no-discontinuity/index.m3u8 new file mode 100644 index 0000000000..f9465cf6c4 --- /dev/null +++ b/test/test/assets/hls-text-no-discontinuity/index.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="de",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="de",FORCED="NO",URI="text.m3u8" + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1240800,CODECS="avc1.640014,mp4a.40.2",RESOLUTION=256x144,SUBTITLES="subs" +av.m3u8 diff --git a/test/test/assets/hls-text-no-discontinuity/subtitle.vtt b/test/test/assets/hls-text-no-discontinuity/subtitle.vtt new file mode 100644 index 0000000000..11c29782f0 --- /dev/null +++ b/test/test/assets/hls-text-no-discontinuity/subtitle.vtt @@ -0,0 +1,15 @@ +WEBVTT + +1 +00:00:00.600 --> 00:00:02.880 +Heute haben wir einen wesentlichen Meilenstein + +2 +00:00:02.880 --> 00:00:06.360 +auf dem Weg zur Science +City Hamburg Bahrenfeld erreicht. + +3 +00:00:06.360 --> 00:00:10.680 +Die Science City ist ein 125 Hektar +großes Areal in Bahrenfeld, diff --git a/test/test/assets/hls-text-no-discontinuity/text.m3u8 b/test/test/assets/hls-text-no-discontinuity/text.m3u8 new file mode 100644 index 0000000000..7e6534b15e --- /dev/null +++ b/test/test/assets/hls-text-no-discontinuity/text.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-TARGETDURATION:12 +#EXTINF:12.000, +subtitle.vtt +#EXT-X-ENDLIST