From d05a6b71e9122845c428efca014f4e1a8397f46d Mon Sep 17 00:00:00 2001 From: Theodore Abshire Date: Fri, 23 Jun 2017 11:19:26 -0700 Subject: [PATCH] Fixes parsing Microsoft-packaged HLS. Microsoft HLS packaging tools generate audio-only variants in an odd way; specifically, the variants have an AUDIO tag despite being audio-only, and thus double-link to the stream. Previously, the way we detected stream type lead to us assigning one version of the audio stream to audio and one to video, thus erroneously making those variants appear to be video+audio variants. This makes it so that, if the AUDIO tag has the same content URI as the base stream, it only uses the version in the AUDIO tag. Change-Id: Ie940970587e95a9020ed67589042008d0568e153 --- lib/hls/hls_parser.js | 44 ++++++++++++++++++++++++++++--------- test/hls/hls_parser_unit.js | 34 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 991cd3371e..abb1a82735 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -78,7 +78,8 @@ shaka.hls.HlsParser = function() { * @typedef {{ * stream: !shakaExtern.Stream, * segmentIndex: !shaka.media.SegmentIndex, - * drmInfos: !Array. + * drmInfos: !Array., + * relativeUri: !string * }} * * @description @@ -90,6 +91,8 @@ shaka.hls.HlsParser = function() { * SegmentIndex of the stream. * @property {!Array.} drmInfos * DrmInfos of the stream. There may be multiple for multi-DRM content. + * @property {!string} relativeUri + * The uri associated with the stream, relative to the manifest. */ shaka.hls.HlsParser.StreamInfo; @@ -299,6 +302,7 @@ shaka.hls.HlsParser.prototype.createVariantsForTag_ = function(tag, playlist) { if (!audioStreamInfos.length && !videoStreamInfos.length) { // There are no associated streams. This is either an audio-only stream, // a video-only stream, or a multiplexed stream. + var ignoreStream = false; if (codecs.length == 1) { // There is only one codec, so it shouldn't be multiplexed. @@ -322,9 +326,22 @@ shaka.hls.HlsParser.prototype.createVariantsForTag_ = function(tag, playlist) { codecs = [codecs.join(',')]; } } else if (audioStreamInfos.length) { - // There are associated audio streams. Assume this is video. - shaka.log.debug('Guessing video.'); - type = ContentType.VIDEO; + var streamURI = HlsParser.getRequiredAttributeValue_(tag, 'URI'); + var firstAudioStreamURI = audioStreamInfos[0].relativeUri; + if (streamURI == firstAudioStreamURI) { + // The Microsoft HLS manifest generators will make audio-only variants + // that link to their URI both directly and through an audio tag. + // In that case, ignore the local URI and use the version in the + // AUDIO tag, so you inherit its language. + // As an example, see the manifest linked in issue #860. + shaka.log.debug('Guessing audio-only.'); + type = ContentType.AUDIO; + ignoreStream = true; + } else { + // There are associated audio streams. Assume this is video. + shaka.log.debug('Guessing video.'); + type = ContentType.VIDEO; + } } else { // There are associated video streams. Assume this is audio. goog.asserts.assert(videoStreamInfos.length, @@ -334,14 +351,19 @@ shaka.hls.HlsParser.prototype.createVariantsForTag_ = function(tag, playlist) { } goog.asserts.assert(type, 'Type should have been set by now!'); + if (ignoreStream) + return Promise.resolve(); return this.createStreamInfoFromVariantTag_(tag, codecs, type, timeOffset); }.bind(this)).then(function(streamInfo) { - goog.asserts.assert(streamInfo, 'We should have created a stream!'); - if (streamInfo.stream.type == ContentType.AUDIO) { - audioStreamInfos = [streamInfo]; - } else { - videoStreamInfos = [streamInfo]; + if (streamInfo) { + if (streamInfo.stream.type == ContentType.AUDIO) { + audioStreamInfos = [streamInfo]; + } else { + videoStreamInfos = [streamInfo]; + } } + goog.asserts.assert(videoStreamInfos || audioStreamInfos, + 'We should have created a stream!'); return this.createVariants_( audioStreamInfos, @@ -558,6 +580,7 @@ shaka.hls.HlsParser.prototype.createStreamInfo_ = var Utils = shaka.hls.Utils; var ContentType = shaka.util.ManifestParserUtils.ContentType; var HlsParser = shaka.hls.HlsParser; + var relativeUri = uri; uri = Utils.constructAbsoluteUri(this.manifestUri_, uri); return this.requestManifest_(uri).then(function(response) { @@ -684,7 +707,8 @@ shaka.hls.HlsParser.prototype.createStreamInfo_ = return { stream: stream, segmentIndex: segmentIndex, - drmInfos: drmInfos + drmInfos: drmInfos, + relativeUri: relativeUri }; }.bind(this)); }.bind(this)); diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js index 4865c48051..2cc205e69f 100644 --- a/test/hls/hls_parser_unit.js +++ b/test/hls/hls_parser_unit.js @@ -170,6 +170,40 @@ describe('HlsParser', function() { testHlsParser(master, media, manifest, done); }); + it('handles audio tags on audio streams', function(done) { + var master = [ + '#EXTM3U\n', + '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a",AUDIO="aud1"\n', + 'test://audio\n', + '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",', + 'URI="test://audio"\n' + ].join(''); + + var media = [ + '#EXTM3U\n', + '#EXT-X-MAP:URI="test://main.mp4",BYTERANGE="616@0"\n', + '#EXTINF:5,\n', + '#EXT-X-BYTERANGE:121090@616\n', + 'test://main.mp4' + ].join(''); + + var manifest = new shaka.test.ManifestGenerator() + .anyTimeline() + .addPeriod(jasmine.any(Number)) + .addVariant(jasmine.any(Number)) + .language('en') + .bandwidth(200) + .addAudio(jasmine.any(Number)) + .language('en') + .anySegmentFunctions() + .anyInitSegment() + .presentationTimeOffset(0) + .mime('audio/mp4', 'mp4a') + .build(); + + testHlsParser(master, media, manifest, done); + }); + it('parses multiplexed variant', function(done) { var master = [ '#EXTM3U\n',