diff --git a/player/videojs.coffee b/player/videojs.coffee index 1b50a086..b904d324 100644 --- a/player/videojs.coffee +++ b/player/videojs.coffee @@ -46,6 +46,10 @@ hasAnyTextTracks = (data) -> ntracks = data?.meta?.textTracks?.length ? 0 return ntracks > 0 +hasAnyAudioTracks = (data) -> + ntracks = data?.meta?.audioTracks?.length ? 0 + return ntracks > 0 + window.VideoJSPlayer = class VideoJSPlayer extends Player constructor: (data) -> if not (this instanceof VideoJSPlayer) @@ -108,13 +112,21 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player $('').attr(attrs).appendTo(video) ) + pluginData = + videoJsResolutionSwitcher: + default: @sources[0].res + + if hasAnyAudioTracks(data) + pluginData.audioSwitch = + audioTracks: data.meta.audioTracks, + volume: VOLUME + @player = videojs(video[0], # https://github.com/Dash-Industry-Forum/dash.js/issues/2184 autoplay: @sources[0].type != 'application/dash+xml', controls: true, - plugins: - videoJsResolutionSwitcher: - default: @sources[0].res + plugins: pluginData + ) @player.ready(=> # Have to use updateSrc instead of tags diff --git a/src/custom-media.js b/src/custom-media.js index 53c87572..8de50841 100644 --- a/src/custom-media.js +++ b/src/custom-media.js @@ -22,8 +22,9 @@ const SOURCE_CONTENT_TYPES = new Set([ 'application/dash+xml', 'application/x-mpegURL', 'audio/aac', - 'audio/ogg', + 'audio/mp4', 'audio/mpeg', + 'audio/ogg', 'audio/opus', 'video/mp4', 'video/ogg', @@ -36,8 +37,9 @@ const LIVE_ONLY_CONTENT_TYPES = new Set([ const AUDIO_ONLY_CONTENT_TYPES = new Set([ 'audio/aac', - 'audio/ogg', + 'audio/mp4', 'audio/mpeg', + 'audio/ogg', 'audio/opus' ]); @@ -142,6 +144,7 @@ export function convert(id, data) { const meta = { direct: sources, + audioTracks: data.audioTracks, textTracks: data.textTracks, thumbnail: data.thumbnail, // Currently ignored by Media live: !!data.live // Currently ignored by Media @@ -170,11 +173,12 @@ export function validate(data) { validateURL(data.thumbnail); } - validateSources(data.sources); + validateSources(data.sources, data); + validateAudioTracks(data.audioTracks); validateTextTracks(data.textTracks); } -function validateSources(sources) { +function validateSources(sources, data) { if (!Array.isArray(sources)) throw new ValidationError('sources must be a list'); if (sources.length === 0) @@ -210,6 +214,45 @@ function validateSources(sources) { } } +function validateAudioTracks(audioTracks) { + if (typeof audioTracks === 'undefined') { + return; + } + + if (!Array.isArray(audioTracks)){ + throw new ValidationError('audioTracks must be a list'); + } + + for (let track of audioTracks) { + if (typeof track.url !== 'string'){ + throw new ValidationError('audio track URL must be a string'); + } + validateURL(track.url); + + if (!AUDIO_ONLY_CONTENT_TYPES.has(track.contentType)){ + throw new ValidationError( + `unacceptable audio track contentType "${track.contentType}"` + ); + } + if (typeof track.label !== 'string'){ + throw new ValidationError('audio track label must be a string'); + } + if (!track.label){ + throw new ValidationError('audio track label must be nonempty'); + } + + if (typeof track.language !== 'string'){ + throw new ValidationError('audio track language must be a string'); + } + if (!track.language){ + throw new ValidationError('audio track language must be nonempty'); + } + if (!/^[a-z]{2,3}$/.test(track.language)){ + throw new ValidationError('audio track language must be a two or three letter IETF BCP 47 subtag'); + } + } +} + function validateTextTracks(textTracks) { if (typeof textTracks === 'undefined') { return; diff --git a/src/media.js b/src/media.js index f486a853..70df36ae 100644 --- a/src/media.js +++ b/src/media.js @@ -38,7 +38,8 @@ Media.prototype = { scuri: this.meta.scuri, embed: this.meta.embed, gdrive_subtitles: this.meta.gdrive_subtitles, - textTracks: this.meta.textTracks + textTracks: this.meta.textTracks, + audioTracks: this.meta.audioTracks } };