Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added API to set media keys directly #61

Merged
merged 15 commits into from
Oct 24, 2018
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
47 changes: 44 additions & 3 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,50 @@ const onPlayerReady = (player) => {
* An object of options left to the plugin author to define.
*/
const eme = function(options = {}) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This worked for me:

const eme = function(options = {}) {
  const player = this;

  this.ready(() => onPlayerReady(this));

  this.eme = {
    initializeMediaKeys(emeOptions = {}) {
      const mergedEmeOptions = videojs.mergeOptions(
        player.currentSource(),
        options,
        emeOptions
      );

      return initializeMediaKeys(player, mergedEmeOptions);
    },
    options
  };
};

// Register the plugin with video.js.
const registerPlugin = videojs.registerPlugin || videojs.plugin;

registerPlugin('eme', eme);

to be clear, I'm trying to avoid an anonymous function being the plugin factory here

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(),
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down
70 changes: 70 additions & 0 deletions test/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -82,6 +96,62 @@ 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();

// testing the rejection path because this isn't a real session

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can delete this now!

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 = {
Expand Down