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: on license request errors, return response body as cause #137

Merged
merged 11 commits into from
Jul 27, 2021
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'}));
Copy link
Member Author

Choose a reason for hiding this comment

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

These were incorrect. As we specify that the response type should be an arraybuffer, we need to convert these to array buffer.

};

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