From a1cad82b2775124e63a0d0c3dc19bdefd9bad029 Mon Sep 17 00:00:00 2001 From: Joe Forbes Date: Fri, 30 Mar 2018 16:31:10 -0400 Subject: [PATCH] feat: support in-manifest DRM data (#60) --- package-lock.json | 37 ++++++-- package.json | 2 +- src/videojs-http-streaming.js | 23 +++-- test/videojs-http-streaming.test.js | 130 +++++++++++++++++++--------- 4 files changed, 137 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfc353330..6ab729b68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8557,12 +8557,12 @@ } }, "videojs-contrib-eme": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/videojs-contrib-eme/-/videojs-contrib-eme-3.0.0.tgz", - "integrity": "sha512-sRK++F4+MDLteaVDyJ9jKKs0bIcmNVk2b4Jmgz2ST+RPxPK/iQ5cECu0Cng2Ue7ewqGgwRncn3Yle2g6wGPbUw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-eme/-/videojs-contrib-eme-3.2.0.tgz", + "integrity": "sha512-xNzw+iOaG1vAo8p/j54IxKRlXZ3a341mPrrgQoRhXQvuDiewccI+mzZ2660YC3dMXfBiAfkm2yaSPK9erXAUAQ==", "dev": true, "requires": { - "video.js": "5.20.4" + "video.js": "5.20.5" }, "dependencies": { "global": { @@ -8576,9 +8576,9 @@ } }, "video.js": { - "version": "5.20.4", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-5.20.4.tgz", - "integrity": "sha512-DkwZcYDN5+qNp3c1y+9N6cQ3XSoFin1dEsEstIRGmALtmh71M8JIPage0S/AC5HzCc7QdUBeeTYHLKZectB2TA==", + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-5.20.5.tgz", + "integrity": "sha1-RFza4gS85Fl4LYajGyWjKv1tjv8=", "dev": true, "requires": { "babel-runtime": "6.26.0", @@ -8588,10 +8588,31 @@ "videojs-font": "2.0.0", "videojs-ie8": "1.1.2", "videojs-swf": "5.4.1", - "videojs-vtt.js": "0.12.4", + "videojs-vtt.js": "0.12.6", "xhr": "2.2.2" } }, + "videojs-vtt.js": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.12.6.tgz", + "integrity": "sha512-XFXeGBQiljnElMhwCcZst0RDbZn2n8LU7ZScXryd3a00OaZsHAjdZu/7/RdSr7Z1jHphd45FnOvOQkGK4YrWCQ==", + "dev": true, + "requires": { + "global": "4.3.2" + }, + "dependencies": { + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "dev": true, + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + } + } + }, "xhr": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.2.2.tgz", diff --git a/package.json b/package.json index b0b126586..11d3e68fa 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "serve-static": "^1.13.2", "sinon": "^1.10.3", "uglify-es": "^3.3.9", - "videojs-contrib-eme": "^3.0.0", + "videojs-contrib-eme": "^3.2.0", "videojs-contrib-quality-levels": "^2.0.4", "videojs-standard": "^4.0.3" } diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index 055e43b13..e1c2e8b1e 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -137,7 +137,7 @@ Hls.canPlaySource = function() { 'your player\'s techOrder.'); }; -const emeOptions = (keySystemOptions, videoPlaylist, audioPlaylist) => { +const emeKeySystems = (keySystemOptions, videoPlaylist, audioPlaylist) => { if (!keySystemOptions) { return keySystemOptions; } @@ -151,6 +151,13 @@ const emeOptions = (keySystemOptions, videoPlaylist, audioPlaylist) => { videoContentType: `video/mp4; codecs="${videoPlaylist.attributes.CODECS}"` }; + if (videoPlaylist.contentProtection && + videoPlaylist.contentProtection[keySystem] && + videoPlaylist.contentProtection[keySystem].pssh) { + keySystemContentTypes[keySystem].pssh = + videoPlaylist.contentProtection[keySystem].pssh; + } + // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' // so we need to prevent overwriting the URL entirely if (typeof keySystemOptions[keySystem] === 'string') { @@ -158,9 +165,7 @@ const emeOptions = (keySystemOptions, videoPlaylist, audioPlaylist) => { } } - return { - keySystems: videojs.mergeOptions(keySystemOptions, keySystemContentTypes) - }; + return videojs.mergeOptions(keySystemOptions, keySystemContentTypes); }; const setupEmeOptions = (hlsHandler) => { @@ -170,11 +175,15 @@ const setupEmeOptions = (hlsHandler) => { const player = videojs.players[hlsHandler.tech_.options_.playerId]; if (player.eme) { - player.eme.options = videojs.mergeOptions(player.eme.options, emeOptions( + const sourceOptions = emeKeySystems( hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media() - )); + ); + + if (sourceOptions) { + player.currentSource().keySystems = sourceOptions; + } } }; @@ -705,6 +714,6 @@ export { Hls, HlsHandler, HlsSourceHandler, - emeOptions, + emeKeySystems, simpleTypeFromSourceType }; diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js index 8238c5f28..aaf42a74b 100644 --- a/test/videojs-http-streaming.test.js +++ b/test/videojs-http-streaming.test.js @@ -19,7 +19,7 @@ import { HlsSourceHandler, HlsHandler, Hls, - emeOptions, + emeKeySystems, simpleTypeFromSourceType } from '../src/videojs-http-streaming'; import window from 'global/window'; @@ -2865,6 +2865,11 @@ QUnit.test('configures eme if present on selectedinitialmedia', function(assert) return { attributes: { CODECS: 'video-codec' + }, + contentProtection: { + keySystem1: { + pssh: 'test' + } } }; }, @@ -2889,15 +2894,68 @@ QUnit.test('configures eme if present on selectedinitialmedia', function(assert) this.player.tech_.hls.masterPlaylistController_.trigger('selectedinitialmedia'); assert.deepEqual(this.player.eme.options, { - previousSetting: 1, + previousSetting: 1 + }, 'did not modify plugin options'); + + assert.deepEqual(this.player.currentSource(), { + src: 'manifest/master.mpd', + type: 'application/dash+xml', keySystems: { keySystem1: { url: 'url1', audioContentType: 'audio/mp4; codecs="audio-codec"', - videoContentType: 'video/mp4; codecs="video-codec"' + videoContentType: 'video/mp4; codecs="video-codec"', + pssh: 'test' + } + } + }, 'set source eme options'); +}); + +QUnit.test('does not set source keySystems if keySystems not provided by source', function(assert) { + this.player.src({ + src: 'manifest/master.mpd', + type: 'application/dash+xml' + }); + + this.clock.tick(1); + + this.player.tech_.hls.playlists = { + media: () => { + return { + attributes: { + CODECS: 'video-codec' + }, + contentProtection: { + keySystem1: { + pssh: 'test' + } + } + }; + }, + // mocked for renditions mixin + master: { + playlists: [] + } + }; + this.player.tech_.hls.masterPlaylistController_.mediaTypes_ = { + AUDIO: { + activePlaylistLoader: { + media: () => { + return { + attributes: { + CODECS: 'audio-codec' + } + }; + } } } - }, 'set eme options'); + }; + this.player.tech_.hls.masterPlaylistController_.trigger('selectedinitialmedia'); + + assert.deepEqual(this.player.currentSource(), { + src: 'manifest/master.mpd', + type: 'application/dash+xml' + }, 'does not set source eme options'); }); QUnit.module('HLS Integration', { @@ -3306,53 +3364,49 @@ QUnit.test('treats invalid keys as a key request failure and blacklists playlist QUnit.module('videojs-contrib-hls isolated functions'); -QUnit.test('emeOptions adds content types for all keySystems', function(assert) { +QUnit.test('emeKeySystems adds content types for all keySystems', function(assert) { assert.deepEqual( - emeOptions( + emeKeySystems( { keySystem1: {}, keySystem2: {} }, { attributes: { CODECS: 'some-video-codec' } }, { attributes: { CODECS: 'some-audio-codec' } }), { - keySystems: { - keySystem1: { - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - }, - keySystem2: { - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - } + keySystem1: { + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' + }, + keySystem2: { + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' } }, 'added content types'); }); -QUnit.test('emeOptions retains non content type properties', function(assert) { +QUnit.test('emeKeySystems retains non content type properties', function(assert) { assert.deepEqual( - emeOptions( + emeKeySystems( { keySystem1: { url: '1' }, keySystem2: { url: '2'} }, { attributes: { CODECS: 'some-video-codec' } }, { attributes: { CODECS: 'some-audio-codec' } }), { - keySystems: { - keySystem1: { - url: '1', - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - }, - keySystem2: { - url: '2', - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - } + keySystem1: { + url: '1', + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' + }, + keySystem2: { + url: '2', + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' } }, 'retained options'); }); -QUnit.test('emeOptions overwrites content types', function(assert) { +QUnit.test('emeKeySystems overwrites content types', function(assert) { assert.deepEqual( - emeOptions( + emeKeySystems( { keySystem1: { audioContentType: 'a', @@ -3366,15 +3420,13 @@ QUnit.test('emeOptions overwrites content types', function(assert) { { attributes: { CODECS: 'some-video-codec' } }, { attributes: { CODECS: 'some-audio-codec' } }), { - keySystems: { - keySystem1: { - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - }, - keySystem2: { - audioContentType: 'audio/mp4; codecs="some-audio-codec"', - videoContentType: 'video/mp4; codecs="some-video-codec"' - } + keySystem1: { + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' + }, + keySystem2: { + audioContentType: 'audio/mp4; codecs="some-audio-codec"', + videoContentType: 'video/mp4; codecs="some-video-codec"' } }, 'overwrote content types');