From 57701b9dc6587df362c9b358cf8d46f757505ab2 Mon Sep 17 00:00:00 2001 From: Joe Forbes Date: Wed, 24 Oct 2018 07:37:17 -0700 Subject: [PATCH] feat: added API to set media keys directly (#61) --- README.md | 23 +++++++++++++++ src/plugin.js | 47 ++++++++++++++++++++++++++++-- test/plugin.test.js | 69 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2610a9..705e9f4 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Maintenance Status: Stable - [Source Options](#source-options) - [Plugin Options](#plugin-options) - [emeOptions](#emeoptions) + - [initializeMediaKeys](#initializemediakeys) - [Passing methods seems complicated](#passing-methods-seems-complicated) - [Special Events](#special-events) - [Getting Started](#getting-started) @@ -328,6 +329,28 @@ player.src({ }); ``` +### initializeMediaKeys +Type: `function` + +`player.eme.initializeMediaKeys()` sets up MediaKeys immediately on demand. This is useful for setting up the video element for DRM before loading any content. Otherwise the video element is set up for DRM on `encrypted` events. This is not supported in Safari. + +```javascript +// additional plugin options +var emeOptions = { + keySystems: { + 'org.w3.clearkey': {...} + } +}; + +player.eme.initializeMediaKeys(emeOptions, function(error) { + if (error) { + // do something with error + } + + // do something else +}); +``` + ### Passing methods seems complicated While simple URLs are supported for many EME implementations, we wanted to provide as much diff --git a/src/plugin.js b/src/plugin.js index f0d5727..5b474c0 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -235,9 +235,50 @@ const onPlayerReady = (player) => { * An object of options left to the plugin author to define. */ const eme = function(options = {}) { - this.eme.options = options; - - this.ready(() => onPlayerReady(this)); + const player = this; + + player.ready(() => onPlayerReady(player)); + + // Plugin API + player.eme = { + /** + * Sets up MediaKeys on demand + * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 + * + * @function initializeMediaKeys + * @param {Object} [emeOptions={}] + * An object of eme plugin options. + * @param {Function} [callback=function(){}] + */ + initializeMediaKeys(emeOptions = {}, callback = function() {}) { + // TODO: this should be refactored and renamed to be less tied + // to encrypted events + const mergedEmeOptions = videojs.mergeOptions( + player.currentSource(), + options, + emeOptions + ); + + // fake an encrypted event for handleEncryptedEvent + const mockEncryptedEvent = { + initDataType: 'cenc', + initData: null, + target: player.tech_.el_ + }; + + setupSessions(player); + + if (player.tech_.el_.setMediaKeys) { + handleEncryptedEvent(mockEncryptedEvent, mergedEmeOptions, player.eme.sessions, player.tech_) + .then(() => callback()) + .catch((error) => callback(error)); + } else if (player.tech_.el_.msSetMediaKeys) { + handleMsNeedKeyEvent(mockEncryptedEvent, mergedEmeOptions, player.eme.sessions, player.tech_); + callback(); + } + }, + options + }; }; // Register the plugin with video.js. diff --git a/test/plugin.test.js b/test/plugin.test.js index cda62b5..5a3bc73 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -37,9 +37,23 @@ QUnit.module('videojs-contrib-eme', { this.video = document.createElement('video'); this.fixture.appendChild(this.video); this.player = videojs(this.video); + + this.origRequestMediaKeySystemAccess = window.navigator.requestMediaKeySystemAccess; + + window.navigator.requestMediaKeySystemAccess = (keySystem, options) => { + return Promise.resolve({ + keySystem: 'org.w3.clearkey', + createMediaKeys: () => { + return { + createSession: () => new videojs.EventTarget() + }; + } + }); + }; }, afterEach() { + window.navigator.requestMediaKeySystemAccess = this.origRequestMediaKeySystemAccess; this.player.dispose(); this.clock.restore(); } @@ -82,6 +96,61 @@ QUnit.test('exposes options', function(assert) { 'exposes publisherId'); }); +// skip test for Safari +if (!window.WebKitMediaKeys) { + QUnit.test('initializeMediaKeys standard', function(assert) { + const done = assert.async(); + const initData = new Uint8Array([1, 2, 3]).buffer; + + this.player.eme(); + + this.player.eme.initializeMediaKeys({ + keySystems: { + 'org.w3.clearkey': { + pssh: initData + } + } + }, () => { + const sessions = this.player.eme.sessions; + + assert.equal(sessions.length, 1, 'created a session when keySystems in options'); + assert.deepEqual(sessions[0].initData, initData, 'captured initData in the session'); + done(); + }); + }); +} + +QUnit.test('initializeMediaKeys ms-prefix', function(assert) { + const done = assert.async(); + // stub setMediaKeys + const setMediaKeys = this.player.tech_.el_.setMediaKeys; + + this.player.tech_.el_.setMediaKeys = null; + this.player.tech_.el_.msSetMediaKeys = () => {}; + + const initData = new Uint8Array([1, 2, 3]).buffer; + + this.player.eme(); + + this.player.eme.initializeMediaKeys({ + keySystems: { + 'com.microsoft.playready': { + pssh: initData + } + } + }, () => { + const sessions = this.player.eme.sessions; + + assert.equal(sessions.length, 1, 'created a session when keySystems in options'); + assert.deepEqual(sessions[0].initData, initData, 'captured initData in the session'); + + done(); + }); + + this.player.tech_.el_.msSetMediaKeys = null; + this.player.tech_.el_.setMediaKeys = setMediaKeys; +}); + QUnit.module('plugin guard functions', { beforeEach() { this.options = {