Skip to content

Commit

Permalink
feat: Add support for defining custom headers for default license and…
Browse files Browse the repository at this point in the history
… certificate requests. (#76)
  • Loading branch information
alex-barstow authored and misteroneill committed Mar 20, 2019
1 parent 5238d08 commit 7197390
Show file tree
Hide file tree
Showing 10 changed files with 678 additions and 213 deletions.
463 changes: 280 additions & 183 deletions README.md

Large diffs are not rendered by default.

23 changes: 14 additions & 9 deletions src/eme.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import videojs from 'video.js';
import { requestPlayreadyLicense } from './playready';
import window from 'global/window';
import {mergeAndRemoveNull} from './utils';

export const getSupportedKeySystem = (keySystems) => {
// As this happens after the src is set on the video, we rely only on the set src (we
Expand Down Expand Up @@ -171,8 +172,8 @@ const setMediaKeys = ({
return Promise.all(promises);
};

const defaultPlayreadyGetLicense = (url) => (emeOptions, keyMessage, callback) => {
requestPlayreadyLicense(url, keyMessage, (err, response, responseBody) => {
const defaultPlayreadyGetLicense = (keySystemOptions) => (emeOptions, keyMessage, callback) => {
requestPlayreadyLicense(keySystemOptions, keyMessage, emeOptions, (err, response, responseBody) => {
if (err) {
callback(err);
return;
Expand All @@ -182,15 +183,19 @@ const defaultPlayreadyGetLicense = (url) => (emeOptions, keyMessage, callback) =
});
};

const defaultGetLicense = (url) => (emeOptions, keyMessage, callback) => {
const defaultGetLicense = (keySystemOptions) => (emeOptions, keyMessage, callback) => {
const headers = mergeAndRemoveNull(
{'Content-type': 'application/octet-stream'},
emeOptions.emeHeaders,
keySystemOptions.licenseHeaders
);

videojs.xhr({
uri: url,
uri: keySystemOptions.url,
method: 'POST',
responseType: 'arraybuffer',
body: keyMessage,
headers: {
'Content-type': 'application/octet-stream'
}
headers
}, (err, response, responseBody) => {
if (err) {
callback(err);
Expand Down Expand Up @@ -230,8 +235,8 @@ const standardizeKeySystemOptions = (keySystem, keySystemOptions) => {

if (keySystemOptions.url && !keySystemOptions.getLicense) {
keySystemOptions.getLicense = keySystem === 'com.microsoft.playready' ?
defaultPlayreadyGetLicense(keySystemOptions.url) :
defaultGetLicense(keySystemOptions.url);
defaultPlayreadyGetLicense(keySystemOptions) :
defaultGetLicense(keySystemOptions);
}

return keySystemOptions;
Expand Down
32 changes: 21 additions & 11 deletions src/fairplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import videojs from 'video.js';
import window from 'global/window';
import {stringToUint16Array, uint8ArrayToString, getHostnameFromUri} from './utils';
import {stringToUint16Array, uint8ArrayToString, getHostnameFromUri, mergeAndRemoveNull} from './utils';

export const FAIRPLAY_KEY_SYSTEM = 'com.apple.fps.1_0';

Expand Down Expand Up @@ -99,11 +99,17 @@ const addKey = ({video, contentId, initData, cert, options, getLicense, eventBus
});
};

const defaultGetCertificate = (certificateUri) => {
export const defaultGetCertificate = (fairplayOptions) => {
return (emeOptions, callback) => {
const headers = mergeAndRemoveNull(
emeOptions.emeHeaders,
fairplayOptions.certificateHeaders
);

videojs.xhr({
uri: certificateUri,
responseType: 'arraybuffer'
uri: fairplayOptions.certificateUri,
responseType: 'arraybuffer',
headers
}, (err, response, responseBody) => {
if (err) {
callback(err);
Expand All @@ -119,16 +125,20 @@ const defaultGetContentId = (emeOptions, initData) => {
return getHostnameFromUri(uint8ArrayToString(initData));
};

const defaultGetLicense = (licenseUri) => {
export const defaultGetLicense = (fairplayOptions) => {
return (emeOptions, contentId, keyMessage, callback) => {
const headers = mergeAndRemoveNull(
{'Content-type': 'application/octet-stream'},
emeOptions.emeHeaders,
fairplayOptions.licenseHeaders
);

videojs.xhr({
uri: licenseUri,
uri: fairplayOptions.licenseUri,
method: 'POST',
responseType: 'arraybuffer',
body: keyMessage,
headers: {
'Content-type': 'application/octet-stream'
}
headers
}, (err, response, responseBody) => {
if (err) {
callback(err);
Expand All @@ -143,10 +153,10 @@ const defaultGetLicense = (licenseUri) => {
const fairplay = ({video, initData, options, eventBus}) => {
const fairplayOptions = options.keySystems[FAIRPLAY_KEY_SYSTEM];
const getCertificate = fairplayOptions.getCertificate ||
defaultGetCertificate(fairplayOptions.certificateUri);
defaultGetCertificate(fairplayOptions);
const getContentId = fairplayOptions.getContentId || defaultGetContentId;
const getLicense = fairplayOptions.getLicense ||
defaultGetLicense(fairplayOptions.licenseUri);
defaultGetLicense(fairplayOptions);

return new Promise((resolve, reject) => {
getCertificate(options, (err, cert) => {
Expand Down
12 changes: 8 additions & 4 deletions src/ms-prefixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,22 @@ export const addKeyToSession = (options, session, event, eventBus) => {
}

if (typeof playreadyOptions === 'string') {
playreadyOptions = { url: playreadyOptions };
playreadyOptions = {url: playreadyOptions};
} else if (typeof playreadyOptions === 'boolean') {
playreadyOptions = {};
}

const url = playreadyOptions.url || event.destinationURL;
if (!playreadyOptions.url) {
playreadyOptions.url = event.destinationURL;
}

requestPlayreadyLicense(url, event.message.buffer, (err, response) => {
requestPlayreadyLicense(playreadyOptions, event.message.buffer, options, (err, response) => {
if (eventBus) {
eventBus.trigger('licenserequestattempted');
}
if (err) {
eventBus.trigger({
message: 'Unable to request key from url: ' + url,
message: 'Unable to request key from url: ' + playreadyOptions.url,
target: session,
type: 'mskeyerror'
});
Expand Down
14 changes: 11 additions & 3 deletions src/playready.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import videojs from 'video.js';
import window from 'global/window';
import {mergeAndRemoveNull} from './utils';

/**
* Parses the EME key message XML to extract HTTP headers and the Challenge element to use
Expand Down Expand Up @@ -40,11 +41,18 @@ export const getMessageContents = (message) => {
};
};

export const requestPlayreadyLicense = (url, messageBuffer, callback) => {
const { headers, message } = getMessageContents(messageBuffer);
export const requestPlayreadyLicense = (keySystemOptions, messageBuffer, emeOptions, callback) => {
const messageContents = getMessageContents(messageBuffer);
const message = messageContents.message;

const headers = mergeAndRemoveNull(
messageContents.headers,
emeOptions.emeHeaders,
keySystemOptions.licenseHeaders
);

videojs.xhr({
uri: url,
uri: keySystemOptions.url,
method: 'post',
headers,
body: message,
Expand Down
14 changes: 14 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import document from 'global/document';
import videojs from 'video.js';

export const stringToUint16Array = (string) => {
// 2 bytes for each char
Expand Down Expand Up @@ -52,3 +53,16 @@ export const arrayBufferFrom = (bufferOrTypedArray) => {

return bufferOrTypedArray;
};

export const mergeAndRemoveNull = (...args) => {
const result = videojs.mergeOptions(...args);

// Any header whose value is `null` will be removed.
Object.keys(result).forEach(k => {
if (result[k] === null) {
delete result[k];
}
});

return result;
};
125 changes: 125 additions & 0 deletions test/eme.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,128 @@ QUnit.test('keySession.update promise rejection', function(assert) {
});

});

QUnit.test('emeHeaders option sets headers on default license xhr request', function(assert) {
const done = assert.async();
const origXhr = videojs.xhr;
const xhrCalls = [];
const session = new videojs.EventTarget();

videojs.xhr = (options) => {
xhrCalls.push(options);
};

const keySystemAccess = {
keySystem: 'com.widevine.alpha',
createMediaKeys: () => {
return {
createSession: () => session
};
}
};

standard5July2016({
keySystemAccess,
video: {
setMediaKeys: (createdMediaKeys) => Promise.resolve(createdMediaKeys)
},
initDataType: '',
initData: '',
options: {
keySystems: {
'com.widevine.alpha': 'some-url'
},
emeHeaders: {
'Some-Header': 'some-header-value'
}
}
}).catch((e) => {});

setTimeout(() => {
session.trigger({
type: 'message',
message: 'the-message'
});

assert.equal(xhrCalls.length, 1, 'made one XHR');
assert.deepEqual(xhrCalls[0], {
uri: 'some-url',
method: 'POST',
responseType: 'arraybuffer',
body: 'the-message',
headers: {
'Content-type': 'application/octet-stream',
'Some-Header': 'some-header-value'
}
}, 'made request with proper emeHeaders option value');

videojs.xhr = origXhr;

done();
});
});

QUnit.test('licenseHeaders keySystems property overrides emeHeaders value', function(assert) {
const done = assert.async();
const origXhr = videojs.xhr;
const xhrCalls = [];
const session = new videojs.EventTarget();

videojs.xhr = (options) => {
xhrCalls.push(options);
};

const keySystemAccess = {
keySystem: 'com.widevine.alpha',
createMediaKeys: () => {
return {
createSession: () => session
};
}
};

standard5July2016({
keySystemAccess,
video: {
setMediaKeys: (createdMediaKeys) => Promise.resolve(createdMediaKeys)
},
initDataType: '',
initData: '',
options: {
keySystems: {
'com.widevine.alpha': {
url: 'some-url',
licenseHeaders: {
'Some-Header': 'priority-header-value'
}
}
},
emeHeaders: {
'Some-Header': 'lower-priority-header-value'
}
}
}).catch((e) => {});

setTimeout(() => {
session.trigger({
type: 'message',
message: 'the-message'
});

assert.equal(xhrCalls.length, 1, 'made one XHR');
assert.deepEqual(xhrCalls[0], {
uri: 'some-url',
method: 'POST',
responseType: 'arraybuffer',
body: 'the-message',
headers: {
'Content-type': 'application/octet-stream',
'Some-Header': 'priority-header-value'
}
}, 'made request with proper licenseHeaders value');

videojs.xhr = origXhr;

done();
});
});

0 comments on commit 7197390

Please sign in to comment.