From 9b116cef48d7f7fa9d75eb32126ed995bd6d3bdd Mon Sep 17 00:00:00 2001 From: Garrett Singer Date: Mon, 5 Apr 2021 14:22:40 -0400 Subject: [PATCH] fix: support automatic configuration of audio and video only DRM sources (#1090) --- docs/supported-features.md | 9 ++--- src/videojs-http-streaming.js | 50 ++++++++++++++------------ test/videojs-http-streaming.test.js | 55 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/docs/supported-features.md b/docs/supported-features.md index 7a24f9f56..887c333f5 100644 --- a/docs/supported-features.md +++ b/docs/supported-features.md @@ -59,8 +59,8 @@ not meant serve as an exhaustive list. * Any browser supported resolution (e.g., 4k) * Any browser supported framerate (e.g., 60fps) * [DRM] via [videojs-contrib-eme] -* Audio only (non DRM) -* Video only (non DRM) +* Audio only (non DASH) +* Video only (non DASH) * In-manifest [WebVTT] subtitles are automatically translated into standard HTML5 subtitle tracks * [AES-128] segment encryption @@ -95,10 +95,6 @@ supported when packaged within [TS]. * [Dolby Digital] Audio (AC-3) * [Dolby Digital Plus] (E-AC-3) -### General Missing Features - -* Audio/video only DRM streams - ### HLS Missing Features Note: features for low latency HLS in the [2nd edition of HTTP Live Streaming] are on the @@ -141,6 +137,7 @@ features in the DASH specification that are not yet implemented in VHS: Note that many of the following are parsed by [mpd-parser] but are either not yet used, or simply take on their default values (in the case where they have valid defaults). +* Audio and video only streams * Audio rendition switching * Each video rendition is paired with an audio rendition for the duration of playback. * MPD diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index 96ee1c477..796f55369 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -30,7 +30,12 @@ import { comparePlaylistBandwidth, comparePlaylistResolution } from './playlist-selectors.js'; -import {isAudioCodec, isVideoCodec, browserSupportsCodec} from '@videojs/vhs-utils/es/codecs.js'; +import { + browserSupportsCodec, + getMimeForCodec, + parseCodecs +} from '@videojs/vhs-utils/es/codecs.js'; +import { unwrapCodecList } from './util/codecs.js'; import logger from './util/logger'; import {SAFE_TIME_DELTA} from './ranges'; @@ -127,35 +132,36 @@ Vhs.canPlaySource = function() { 'your player\'s techOrder.'); }; -const emeKeySystems = (keySystemOptions, videoPlaylist, audioPlaylist) => { +const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => { if (!keySystemOptions) { return keySystemOptions; } - const codecs = { - video: videoPlaylist && videoPlaylist.attributes && videoPlaylist.attributes.CODECS, - audio: audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS - }; + let codecs = {}; - if (!codecs.audio && codecs.video && codecs.video.split(',').length > 1) { - codecs.video.split(',').forEach(function(codec) { - codec = codec.trim(); + if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) { + codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS)); + } - if (isAudioCodec(codec)) { - codecs.audio = codec; - } else if (isVideoCodec(codec)) { - codecs.video = codec; - } - }); + if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) { + codecs.audio = audioPlaylist.attributes.CODECS; } - const videoContentType = codecs.video ? `video/mp4;codecs="${codecs.video}"` : null; - const audioContentType = codecs.audio ? `audio/mp4;codecs="${codecs.audio}"` : null; + + const videoContentType = getMimeForCodec(codecs.video); + const audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist const keySystemContentTypes = {}; for (const keySystem in keySystemOptions) { - keySystemContentTypes[keySystem] = {audioContentType, videoContentType}; + keySystemContentTypes[keySystem] = {}; + + if (audioContentType) { + keySystemContentTypes[keySystem].audioContentType = audioContentType; + } + if (videoContentType) { + keySystemContentTypes[keySystem].videoContentType = videoContentType; + } // Default to using the video playlist's PSSH even though they may be different, as // videojs-contrib-eme will only accept one in the options. @@ -163,11 +169,11 @@ const emeKeySystems = (keySystemOptions, videoPlaylist, audioPlaylist) => { // This shouldn't be an issue for most cases as early intialization will handle all // unique PSSH values, and if they aren't, then encrypted events should have the // specific information needed for the unique license. - if (videoPlaylist.contentProtection && - videoPlaylist.contentProtection[keySystem] && - videoPlaylist.contentProtection[keySystem].pssh) { + if (mainPlaylist.contentProtection && + mainPlaylist.contentProtection[keySystem] && + mainPlaylist.contentProtection[keySystem].pssh) { keySystemContentTypes[keySystem].pssh = - videoPlaylist.contentProtection[keySystem].pssh; + mainPlaylist.contentProtection[keySystem].pssh; } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js index e1f7ff1bd..ce497b301 100644 --- a/test/videojs-http-streaming.test.js +++ b/test/videojs-http-streaming.test.js @@ -5861,6 +5861,61 @@ QUnit.test('emeKeySystems adds content types for all keySystems', function(asser ); }); +QUnit.test('emeKeySystems supports audio only', function(assert) { + assert.deepEqual( + emeKeySystems( + { keySystem1: {}, keySystem2: {} }, + { attributes: { CODECS: 'mp4a.40.2c' } }, + ), + { + keySystem1: { + audioContentType: 'audio/mp4;codecs="mp4a.40.2c"' + }, + keySystem2: { + audioContentType: 'audio/mp4;codecs="mp4a.40.2c"' + } + }, + 'added content type' + ); +}); + +QUnit.test('emeKeySystems supports external audio only', function(assert) { + assert.deepEqual( + emeKeySystems( + { keySystem1: {}, keySystem2: {} }, + { attributes: {} }, + { attributes: { CODECS: 'mp4a.40.2c' } }, + ), + { + keySystem1: { + audioContentType: 'audio/mp4;codecs="mp4a.40.2c"' + }, + keySystem2: { + audioContentType: 'audio/mp4;codecs="mp4a.40.2c"' + } + }, + 'added content type' + ); +}); + +QUnit.test('emeKeySystems supports video only', function(assert) { + assert.deepEqual( + emeKeySystems( + { keySystem1: {}, keySystem2: {} }, + { attributes: { CODECS: 'avc1.420015' } }, + ), + { + keySystem1: { + videoContentType: 'video/mp4;codecs="avc1.420015"' + }, + keySystem2: { + videoContentType: 'video/mp4;codecs="avc1.420015"' + } + }, + 'added content type' + ); +}); + QUnit.test('emeKeySystems retains non content type properties', function(assert) { assert.deepEqual( emeKeySystems(