Skip to content

Commit

Permalink
feat: on license request errors, return response body as cause (#137)
Browse files Browse the repository at this point in the history
This adds a new cause property to the error object returned by player.error() when a license request fails with a 4xx or 5xx status code. It's a string representation, so, if it's JSON a consumer would need to call JSON.parse manually as we can't know whether it's JSON or not.
  • Loading branch information
gkatsev committed Jul 27, 2021
1 parent 5c441dc commit a9a5b82
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 55 deletions.
17 changes: 3 additions & 14 deletions src/eme.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import videojs from 'video.js';
import { requestPlayreadyLicense } from './playready';
import window from 'global/window';
import {mergeAndRemoveNull} from './utils';
import {httpResponseHandler} from './http-handler.js';

/**
* Returns an array of MediaKeySystemConfigurationObjects provided in the keySystem
Expand Down Expand Up @@ -284,20 +285,8 @@ export const defaultGetLicense = (keySystemOptions) => (emeOptions, keyMessage,
responseType: 'arraybuffer',
body: keyMessage,
headers
}, (err, response, responseBody) => {
if (err) {
callback(err);
return;
}

if (response.statusCode >= 400 && response.statusCode <= 599) {
// Pass an empty object as the error to use the default code 5 error message
callback({});
return;
}

callback(null, responseBody);
});
}, httpResponseHandler(callback, true)
);
};

const promisifyGetLicense = (getLicenseFn, eventBus) => {
Expand Down
25 changes: 8 additions & 17 deletions src/fairplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import videojs from 'video.js';
import window from 'global/window';
import {stringToUint16Array, uint8ArrayToString, getHostnameFromUri, mergeAndRemoveNull} from './utils';
import {httpResponseHandler} from './http-handler.js';

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

Expand Down Expand Up @@ -112,14 +113,17 @@ export const defaultGetCertificate = (fairplayOptions) => {
uri: fairplayOptions.certificateUri,
responseType: 'arraybuffer',
headers
}, (err, response, responseBody) => {
}, httpResponseHandler((err, license) => {
if (err) {
callback(err);
return;
}

callback(null, new Uint8Array(responseBody));
});
// in this case, license is still the raw ArrayBuffer,
// (we don't want httpResponseHandler to decode it)
// convert it into Uint8Array as expected
callback(null, new Uint8Array(license));
}));
};
};

Expand All @@ -141,20 +145,7 @@ export const defaultGetLicense = (fairplayOptions) => {
responseType: 'arraybuffer',
body: keyMessage,
headers
}, (err, response, responseBody) => {
if (err) {
callback(err);
return;
}

if (response.statusCode >= 400 && response.statusCode <= 599) {
// Pass an empty object as the error to use the default code 5 error message
callback({});
return;
}

callback(null, responseBody);
});
}, httpResponseHandler(callback, true));
};
};

Expand Down
30 changes: 30 additions & 0 deletions src/http-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import videojs from 'video.js';

let httpResponseHandler = videojs.xhr.httpHandler;

// to make sure this doesn't break with older versions of Video.js,
// do a super simple wrapper instead
if (!httpResponseHandler) {
httpResponseHandler = (callback, decodeResponseBody) => (err, response, responseBody) => {
if (err) {
callback(err);
return;
}

// if the HTTP status code is 4xx or 5xx, the request also failed
if (response.statusCode >= 400 && response.statusCode <= 599) {
let cause = responseBody;

if (decodeResponseBody) {
cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
}

callback({cause});
return;
}

// otherwise, request succeeded
callback(null, responseBody);
};
}
export { httpResponseHandler };
16 changes: 2 additions & 14 deletions src/playready.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import videojs from 'video.js';
import window from 'global/window';
import {mergeAndRemoveNull} from './utils';
import {httpResponseHandler} from './http-handler.js';

/**
* Parses the EME key message XML to extract HTTP headers and the Challenge element to use
Expand Down Expand Up @@ -57,18 +58,5 @@ export const requestPlayreadyLicense = (keySystemOptions, messageBuffer, emeOpti
headers,
body: message,
responseType: 'arraybuffer'
}, (err, response, responseBody) => {
if (err) {
callback(err);
return;
}

if (response.statusCode >= 400 && response.statusCode <= 599) {
// Pass an empty object as the error to use the default code 5 error message
callback({});
return;
}

callback(null, responseBody);
});
}, httpResponseHandler(callback, true));
};
24 changes: 18 additions & 6 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,25 @@ export const setupSessions = (player) => {
*/
export const emeErrorHandler = (player) => {
return (objOrErr) => {
const message = typeof objOrErr === 'string' ? objOrErr : (objOrErr && objOrErr.message) || null;

player.error({
const error = {
// MEDIA_ERR_ENCRYPTED is code 5
code: 5,
message
});
code: 5
};

if (typeof objOrErr === 'string') {
error.message = objOrErr;
} else if (objOrErr) {
if (objOrErr.message) {
error.message = objOrErr.message;
}
if (objOrErr.cause &&
(objOrErr.cause.length ||
objOrErr.cause.byteLength)) {
error.cause = objOrErr.cause;
}
}

player.error(error);
};
};

Expand Down
21 changes: 17 additions & 4 deletions test/eme.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -797,26 +797,39 @@ QUnit.test('getLicense calls back with error for 400 and 500 status codes', func
const getLicenseCallback = sinon.spy();
const getLicense = defaultGetLicense({});

function toArrayBuffer(obj) {
const json = JSON.stringify(obj);
const buffer = new ArrayBuffer(json.length);
const bufferView = new Uint8Array(buffer);

for (let i = 0; i < json.length; i++) {
bufferView[i] = json.charCodeAt(i);
}
return buffer;
}

videojs.xhr = (params, callback) => {
return callback(null, {statusCode: 400}, {body: 'some-body'});
return callback(null, {statusCode: 400}, toArrayBuffer({body: 'some-body'}));
};

getLicense({}, null, getLicenseCallback);

videojs.xhr = (params, callback) => {
return callback(null, {statusCode: 500}, {body: 'some-body'});
return callback(null, {statusCode: 500}, toArrayBuffer({body: 'some-body'}));
};

getLicense({}, null, getLicenseCallback);

videojs.xhr = (params, callback) => {
return callback(null, {statusCode: 599}, {body: 'some-body'});
return callback(null, {statusCode: 599}, toArrayBuffer({body: 'some-body'}));
};

getLicense({}, null, getLicenseCallback);

assert.equal(getLicenseCallback.callCount, 3, 'correct callcount');
assert.equal(getLicenseCallback.alwaysCalledWith({}), true, 'getLicense callback called with correct error');
assert.ok(getLicenseCallback.alwaysCalledWith({
cause: JSON.stringify({body: 'some-body'})
}), 'getLicense callback called with correct error');
});

QUnit.test('getLicense calls back with response body for non-400/500 status codes', function(assert) {
Expand Down

0 comments on commit a9a5b82

Please sign in to comment.