diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 33dd090c99..26a86b6492 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -1729,9 +1729,9 @@ shaka.hls.HlsParser = class { let codecs = ''; /** @type {string} */ const type = this.getType_(tag); - // Text does not require a codec. - if (type != shaka.util.ManifestParserUtils.ContentType.TEXT && groupId && - this.groupIdToCodecsMap_.has(groupId)) { + if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) { + codecs = tag.getAttributeValue('CODECS') || ''; + } else if (groupId && this.groupIdToCodecsMap_.has(groupId)) { codecs = this.groupIdToCodecsMap_.get(groupId); } @@ -2007,6 +2007,13 @@ shaka.hls.HlsParser = class { } } + if (type == ContentType.TEXT) { + const firstSegment = realStream.segmentIndex.get(0); + if (firstSegment && firstSegment.initSegmentReference) { + stream.mimeType = 'application/mp4'; + } + } + // Add finishing touches to the stream that can only be done once we have // more full context on the media as a whole. if (this.hasEnoughInfoToFinalizeStreams_()) { diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index a8bf25a448..832ab9c3b2 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -378,7 +378,7 @@ shaka.media.StreamingEngine = class { /** * @param {shaka.extern.Stream} textStream */ - switchTextStream(textStream) { + async switchTextStream(textStream) { this.currentTextStream_ = textStream; if (!this.startupComplete_) { @@ -390,6 +390,13 @@ shaka.media.StreamingEngine = class { goog.asserts.assert(textStream && textStream.type == ContentType.TEXT, 'Wrong stream type passed to switchTextStream!'); + // In HLS it is possible that the mimetype changes when the media + // playlist is downloaded, so it is necessary to have the updated data + // here. + if (!textStream.segmentIndex) { + await textStream.createSegmentIndex(); + } + this.switchInternal_( textStream, /* clearBuffer= */ true, /* safeMargin= */ 0, /* force= */ false); diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js index f8a0974181..3eab6a30d8 100644 --- a/test/hls/hls_parser_unit.js +++ b/test/hls/hls_parser_unit.js @@ -2263,6 +2263,51 @@ describe('HlsParser', () => { expect(actual).toEqual(manifest); }); + it('parses manifest with MP4+WEBVTT streams', async () => { + const master = [ + '#EXTM3U\n', + '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",', + 'CODECS="wvtt",URI="text"\n', + '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",', + 'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n', + 'video\n', + ].join(''); + + const media = [ + '#EXTM3U\n', + '#EXT-X-PLAYLIST-TYPE:VOD\n', + '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', + '#EXTINF:5,\n', + '#EXT-X-BYTERANGE:121090@616\n', + 'main.mp4', + ].join(''); + + const manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.anyTimeline(); + manifest.addPartialVariant((variant) => { + variant.addPartialStream(ContentType.VIDEO); + }); + manifest.addPartialTextStream((stream) => { + stream.language = 'en'; + stream.mime('application/mp4', 'wvtt'); + }); + manifest.sequenceMode = sequenceMode; + manifest.type = shaka.media.ManifestParser.HLS; + }); + + fakeNetEngine + .setResponseText('test:/master', master) + .setResponseText('test:/audio', media) + .setResponseText('test:/video', media) + .setResponseText('test:/text', media) + .setResponseValue('test:/init.mp4', initSegmentData) + .setResponseValue('test:/main.mp4', segmentData); + + const actual = await parser.start('test:/master', playerInterface); + await loadAllStreamsFor(actual); + expect(actual).toEqual(manifest); + }); + it('detects VTT streams by codec', async () => { const master = [ '#EXTM3U\n',