From f09999aa80ca1e65fc2dbc4e95f0a3636ecd1507 Mon Sep 17 00:00:00 2001 From: Michelle Zhuo Date: Tue, 20 Jun 2017 18:24:38 -0700 Subject: [PATCH] Add channel count information for HLS audio tracks Adding the count of channels, as a new field for tracks and streams. Used for HLS audio tracks. Resolves #826. Change-Id: I1448b4a8cfaf6dd798670bb2f0f3981d6c7e40c3 --- externs/shaka/manifest.js | 5 +++- externs/shaka/player.js | 7 +++-- lib/dash/dash_parser.js | 3 ++- lib/hls/hls_parser.js | 38 +++++++++++++++++++++++----- lib/offline/offline_utils.js | 3 ++- lib/player.js | 3 ++- lib/util/stream_utils.js | 6 +++-- test/hls/hls_parser_unit.js | 3 ++- test/offline/offline_utils_unit.js | 3 ++- test/offline/storage_unit.js | 6 +++-- test/player_unit.js | 21 ++++++++++----- test/test/util/manifest_generator.js | 15 ++++++++++- 12 files changed, 86 insertions(+), 27 deletions(-) diff --git a/externs/shaka/manifest.js b/externs/shaka/manifest.js index 751d5d58f..e3d177751 100644 --- a/externs/shaka/manifest.js +++ b/externs/shaka/manifest.js @@ -302,7 +302,8 @@ shakaExtern.GetSegmentReferenceFunction; * primary: boolean, * trickModeVideo: ?shakaExtern.Stream, * containsEmsgBoxes: boolean, - * roles: !Array. + * roles: !Array., + * channelsCount: ?number * }} * * @description @@ -393,6 +394,8 @@ shakaExtern.GetSegmentReferenceFunction; * @property {!Array.} roles * The roles of the stream as they appear on the manifest, * e.g. 'main', 'caption', or 'commentary'. + * @property {?number} channelsCount + * The channel count information for the audio stream. * @exportDoc */ shakaExtern.Stream; diff --git a/externs/shaka/player.js b/externs/shaka/player.js index ad2109434..ffec9ccfd 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -142,7 +142,8 @@ shakaExtern.Stats; * primary: boolean, * roles: !Array., * videoId: ?number, - * audioId: ?number + * audioId: ?number, + * channelsCount: ?number * }} * * @description @@ -182,7 +183,7 @@ shakaExtern.Stats; * The audio/video codecs string provided in the manifest, if present. * @property {?string} audioCodec * The audio codecs string provided in the manifest, if present. - * @property {?string} videoCodec + * @property {?string} videoCodec * The video codecs string provided in the manifest, if present. * @property {boolean} primary * True indicates that this in the primary language for the content. @@ -196,6 +197,8 @@ shakaExtern.Stats; * (only for variant tracks) The video stream id. * @property {?number} audioId * (only for variant tracks) The audio stream id. + * @property {?number} channelsCount + * The count of the audio track channels. * @exportDoc */ shakaExtern.Track; diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index ed78983ca..0d98a3977 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -1127,7 +1127,8 @@ shaka.dash.DashParser.prototype.parseRepresentation_ = function( primary: isPrimary, trickModeVideo: null, containsEmsgBoxes: context.representation.containsEmsgBoxes, - roles: roles + roles: roles, + channelsCount: null }; }; diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 991cd3371..9106cc5cb 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -508,17 +508,39 @@ shaka.hls.HlsParser.prototype.createStreamInfoFromMediaTag_ = // FORCED, INSTREAM-ID, CHARACTERISTICS, CHANNELS? // Attribute descriptions: // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-4.3.4.1 - + var channelsAttribute = tag.getAttributeValue('CHANNELS'); + var channelsCount = type == 'audio' ? + this.getChannelsCount_(channelsAttribute) : null; var uri = HlsParser.getRequiredAttributeValue_(tag, 'URI'); var primary = !!defaultAttr || !!autoselectAttr; - return this.createStreamInfo_(uri, allCodecs, type, timeOffset, language, - primary, label).then(function(streamInfo) { + return this.createStreamInfo_(uri, allCodecs, type, timeOffset, + language, primary, label, channelsCount).then(function(streamInfo) { this.mediaTagsToStreamInfosMap_[tag.id] = streamInfo; return streamInfo; }.bind(this)); }; +/** + * Get the channels count information for HLS audio track. + * The channels value is a string that specifies an ordered, "/" separated list + * of parameters. If the type is audio, the first parameter will be a decimal + * integer, as the number of independent, simultaneous audio channels. + * No other channels parameters are currently defined. + * + * @param {?string} channels + * + * @return {?number} channelcount + * @private + */ +shaka.hls.HlsParser.prototype.getChannelsCount_ = function(channels) { + if (!channels) return null; + var channelscountstring = channels.split('/')[0]; + var count = parseInt(channelscountstring, 10); + return count; +}; + + /** * Parse EXT-X-STREAM-INF media tag into a Stream object. * @@ -537,7 +559,7 @@ shaka.hls.HlsParser.prototype.createStreamInfoFromVariantTag_ = var uri = shaka.hls.HlsParser.getRequiredAttributeValue_(tag, 'URI'); return this.createStreamInfo_(uri, allCodecs, type, timeOffset, /* language */ 'und', /* primary */ false, - /* label */ null); + /* label */ null, /* channelcount */ null); }; @@ -549,12 +571,13 @@ shaka.hls.HlsParser.prototype.createStreamInfoFromVariantTag_ = * @param {!string} language * @param {boolean} primary * @param {?string} label + * @param {?number} channelsCount * @return {!Promise.} * @throws shaka.util.Error * @private */ -shaka.hls.HlsParser.prototype.createStreamInfo_ = - function(uri, allCodecs, type, timeOffset, language, primary, label) { +shaka.hls.HlsParser.prototype.createStreamInfo_ = function(uri, allCodecs, + type, timeOffset, language, primary, label, channelsCount) { var Utils = shaka.hls.Utils; var ContentType = shaka.util.ManifestParserUtils.ContentType; var HlsParser = shaka.hls.HlsParser; @@ -676,7 +699,8 @@ shaka.hls.HlsParser.prototype.createStreamInfo_ = width: undefined, height: undefined, bandwidth: undefined, - roles: [] + roles: [], + channelsCount: channelsCount }; this.streamsToIndexMap_[stream.id] = segmentIndex; diff --git a/lib/offline/offline_utils.js b/lib/offline/offline_utils.js index ef7454144..101e227f3 100644 --- a/lib/offline/offline_utils.js +++ b/lib/offline/offline_utils.js @@ -257,7 +257,8 @@ shaka.offline.OfflineUtils.createStream_ = function(streamDb) { trickModeVideo: null, // TODO(modmaker): Store offline? containsEmsgBoxes: false, - roles: [] + roles: [], + channelsCount: null }; }; diff --git a/lib/player.js b/lib/player.js index aa9d26939..810e819d4 100644 --- a/lib/player.js +++ b/lib/player.js @@ -1577,7 +1577,8 @@ shaka.Player.prototype.addTextTrack = function( primary: false, trickModeVideo: null, containsEmsgBoxes: false, - roles: [] + roles: [], + channelsCount: null }; // Add the stream to the loading list to ensure it isn't switched to while it diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 4d7e16dc5..b96da8dd1 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -270,7 +270,8 @@ shaka.util.StreamUtils.getVariantTracks = primary: variant.primary, roles: roles, videoId: variant.video ? variant.video.id : null, - audioId: variant.audio ? variant.audio.id : null + audioId: variant.audio ? variant.audio.id : null, + channelsCount: variant.audio ? variant.audio.channelsCount : null }; }); @@ -300,7 +301,8 @@ shaka.util.StreamUtils.getTextTracks = function(period, activeStreamId) { audioCodec: null, videoCodec: null, primary: stream.primary, - roles: stream.roles + roles: stream.roles, + channelsCount: null }; }); }; diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js index a66389976..6c74605a2 100644 --- a/test/hls/hls_parser_unit.js +++ b/test/hls/hls_parser_unit.js @@ -136,7 +136,7 @@ describe('HlsParser', function() { 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n', 'test://video\n', '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",', - 'URI="test://audio"\n' + 'CHANNELS="2",URI="test://audio"\n' ].join(''); var media = [ @@ -166,6 +166,7 @@ describe('HlsParser', function() { .anyInitSegment() .presentationTimeOffset(0) .mime('audio/mp4', 'mp4a') + .channelsCount(2) .build(); testHlsParser(master, media, manifest, done); diff --git a/test/offline/offline_utils_unit.js b/test/offline/offline_utils_unit.js index 10d16adc1..856381de0 100644 --- a/test/offline/offline_utils_unit.js +++ b/test/offline/offline_utils_unit.js @@ -262,7 +262,8 @@ describe('OfflineUtils', function() { primary: streamDb.primary, trickModeVideo: null, containsEmsgBoxes: false, - roles: [] + roles: [], + channelsCount: null }; expect(stream).toEqual(expectedStream); diff --git a/test/offline/storage_unit.js b/test/offline/storage_unit.js index 89ba753ad..577378641 100644 --- a/test/offline/storage_unit.js +++ b/test/offline/storage_unit.js @@ -107,7 +107,8 @@ describe('Storage', function() { codecs: 'vorbis', primary: true, segments: [], - roles: [] + roles: [], + channelsCount: null } ] }], @@ -147,7 +148,8 @@ describe('Storage', function() { videoCodec: 'avc1.4d401f', roles: [], videoId: 0, - audioId: 1 + audioId: 1, + channelsCount: null } ]; Promise diff --git a/test/player_unit.js b/test/player_unit.js index c29b8ce50..8ee3ec9d3 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -899,7 +899,8 @@ describe('Player', function() { primary: false, roles: [], videoId: 4, - audioId: 1 + audioId: 1, + channelsCount: null }, { id: 2, @@ -919,7 +920,8 @@ describe('Player', function() { primary: false, roles: [], videoId: 5, - audioId: 1 + audioId: 1, + channelsCount: null }, { id: 3, @@ -939,7 +941,8 @@ describe('Player', function() { primary: false, roles: [], videoId: 4, - audioId: 2 + audioId: 2, + channelsCount: null }, { id: 4, @@ -959,7 +962,8 @@ describe('Player', function() { primary: false, roles: [], videoId: 5, - audioId: 2 + audioId: 2, + channelsCount: null }, { id: 5, @@ -979,7 +983,8 @@ describe('Player', function() { primary: false, roles: [], videoId: 5, - audioId: 8 + audioId: 8, + channelsCount: null } ]; @@ -996,7 +1001,8 @@ describe('Player', function() { audioCodec: null, videoCodec: null, primary: false, - roles: [] + roles: [], + channelsCount: null }, { id: 7, @@ -1010,7 +1016,8 @@ describe('Player', function() { audioCodec: null, videoCodec: null, primary: false, - roles: [] + roles: [], + channelsCount: null } ]; }); diff --git a/test/test/util/manifest_generator.js b/test/test/util/manifest_generator.js index 338836d7d..0749e0189 100644 --- a/test/test/util/manifest_generator.js +++ b/test/test/util/manifest_generator.js @@ -473,7 +473,8 @@ shaka.test.ManifestGenerator.prototype.createStream_ = primary: false, trickModeVideo: null, containsEmsgBoxes: false, - roles: [] + roles: [], + channelsCount: null }; stream.createSegmentIndex.and.callFake( function() { return Promise.resolve(); }); @@ -805,4 +806,16 @@ shaka.test.ManifestGenerator.prototype.currentStream_ = function() { 'Must add at least one stream.'); return this.lastStreamAdded_; }; + + +/** + * Sets the count of the channels of the current stream. + * @param {number} count + * @return {!shaka.test.ManifestGenerator} + */ +shaka.test.ManifestGenerator.prototype.channelsCount = function(count) { + var stream = this.currentStream_(); + stream.channelsCount = count; + return this; +}; // }}}