Skip to content

Commit

Permalink
feat: eme error interface (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrums86 committed Mar 22, 2024
1 parent 462d539 commit 5820da7
Show file tree
Hide file tree
Showing 10 changed files with 807 additions and 276 deletions.
343 changes: 232 additions & 111 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@
"test/"
],
"dependencies": {
"global": "^4.3.2",
"video.js": "^6 || ^7 || ^8"
"global": "^4.3.2"
},
"peerDependencies": {
"video.js": "^8.11.8"
},
"devDependencies": {
"conventional-changelog-cli": "^2.0.12",
Expand Down
223 changes: 149 additions & 74 deletions src/eme.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,84 +101,129 @@ export const makeNewRequest = (player, requestOptions) => {
getLicense,
removeSession,
eventBus,
contentId
contentId,
emeError,
keySystem
} = requestOptions;

const keySession = mediaKeys.createSession();

eventBus.trigger('keysessioncreated');
try {
const keySession = mediaKeys.createSession();

player.on('dispose', () => {
keySession.close();
});
eventBus.trigger('keysessioncreated');

return new Promise((resolve, reject) => {
keySession.addEventListener('message', (event) => {
// all other types will be handled by keystatuseschange
if (event.messageType !== 'license-request' && event.messageType !== 'license-renewal') {
return;
}

getLicense(options, event.message, contentId)
.then((license) => {
resolve(keySession.update(license));
})
.catch((err) => {
reject(err);
});
}, false);
player.on('dispose', () => {
keySession.close().then(() => {
eventBus.trigger('keysessionclosed');
}).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToCloseSession,
keySystem
};

keySession.addEventListener('keystatuseschange', (event) => {
let expired = false;
emeError(error, metadata);
});
});

// based on https://www.w3.org/TR/encrypted-media/#example-using-all-events
keySession.keyStatuses.forEach((status, keyId) => {
// Trigger an event so that outside listeners can take action if appropriate.
// For instance, the `output-restricted` status should result in an
// error being thrown.
return new Promise((resolve, reject) => {
keySession.addEventListener('message', (event) => {
eventBus.trigger({
keyId,
status,
target: keySession,
type: 'keystatuschange'
type: 'keymessage',
event
});
switch (status) {
case 'expired':
// If one key is expired in a session, all keys are expired. From
// https://www.w3.org/TR/encrypted-media/#dom-mediakeystatus-expired, "All other
// keys in the session must have this status."
expired = true;
break;
case 'internal-error':
const message =
'Key status reported as "internal-error." Leaving the session open since we ' +
'don\'t have enough details to know if this error is fatal.';

// "This value is not actionable by the application."
// https://www.w3.org/TR/encrypted-media/#dom-mediakeystatus-internal-error
videojs.log.warn(message, event);
break;
// all other types will be handled by keystatuseschange
if (event.messageType !== 'license-request' && event.messageType !== 'license-renewal') {
return;
}
});

if (expired) {
// Close session and remove it from the session list to ensure that a new
// session can be created.
//
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log.debug('Session expired, closing the session.');
keySession.close().then(() => {
removeSession(initData);
makeNewRequest(player, requestOptions);
getLicense(options, event.message, contentId)
.then((license) => {
resolve(keySession.update(license).then(() => {
eventBus.trigger('keysessionupdated');
}).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToUpdateSessionWithReceivedLicenseKeys,
keySystem
};

emeError(error, metadata);
}));
})
.catch((err) => {
reject(err);
});
}, false);

keySession.addEventListener('keystatuseschange', (event) => {
let expired = false;

// based on https://www.w3.org/TR/encrypted-media/#example-using-all-events
keySession.keyStatuses.forEach((status, keyId) => {
// Trigger an event so that outside listeners can take action if appropriate.
// For instance, the `output-restricted` status should result in an
// error being thrown.
eventBus.trigger({
keyId,
status,
target: keySession,
type: 'keystatuschange'
});
switch (status) {
case 'expired':
// If one key is expired in a session, all keys are expired. From
// https://www.w3.org/TR/encrypted-media/#dom-mediakeystatus-expired, "All other
// keys in the session must have this status."
expired = true;
break;
case 'internal-error':
const message =
'Key status reported as "internal-error." Leaving the session open since we ' +
'don\'t have enough details to know if this error is fatal.';

// "This value is not actionable by the application."
// https://www.w3.org/TR/encrypted-media/#dom-mediakeystatus-internal-error
videojs.log.warn(message, event);
break;
}
});
}
}, false);

keySession.generateRequest(initDataType, initData).catch(() => {
reject('Unable to create or initialize key session');
if (expired) {
// Close session and remove it from the session list to ensure that a new
// session can be created.
videojs.log.debug('Session expired, closing the session.');
keySession.close().then(() => {
eventBus.trigger('keysessionclosed');
removeSession(initData);
makeNewRequest(player, requestOptions);
}).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToCloseSession,
keySystem
};

emeError(error, metadata);
});
}
}, false);

keySession.generateRequest(initDataType, initData).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToGenerateLicenseRequest,
keySystem
};

emeError(error, metadata);
reject('Unable to create or initialize key session');
});
});
});

} catch (error) {
const metadata = {
errorType: videojs.Error.EMEFailedToCreateMediaKeySession,
keySystem
};

emeError(error, metadata);
}
};

/*
Expand Down Expand Up @@ -217,7 +262,8 @@ export const addSession = ({
getLicense,
contentId,
removeSession,
eventBus
eventBus,
emeError
}) => {
const sessionData = {
initDataType,
Expand All @@ -226,7 +272,9 @@ export const addSession = ({
getLicense,
removeSession,
eventBus,
contentId
contentId,
emeError,
keySystem: video.keySystem
};

if (video.mediaKeysObject) {
Expand Down Expand Up @@ -262,15 +310,23 @@ export const addPendingSessions = ({
player,
video,
certificate,
createdMediaKeys
createdMediaKeys,
emeError
}) => {
// save media keys on the video element to act as a reference for other functions so
// that they don't recreate the keys
video.mediaKeysObject = createdMediaKeys;
const promises = [];

if (certificate) {
promises.push(createdMediaKeys.setServerCertificate(certificate));
promises.push(createdMediaKeys.setServerCertificate(certificate).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToSetServerCertificate,
keySystem: video.keySystem
};

emeError(error, metadata);
}));
}

for (let i = 0; i < video.pendingSessionData.length; i++) {
Expand All @@ -284,13 +340,22 @@ export const addPendingSessions = ({
getLicense: data.getLicense,
removeSession: data.removeSession,
eventBus: data.eventBus,
contentId: data.contentId
contentId: data.contentId,
emeError: data.emeError,
keySystem: video.keySystem
}));
}

video.pendingSessionData = [];

promises.push(video.setMediaKeys(createdMediaKeys));
promises.push(video.setMediaKeys(createdMediaKeys).catch((error) => {
const metadata = {
errorType: videojs.Error.EMEFailedToAttachMediaKeysToVideoElement,
keySystem: video.keySystem
};

emeError(error, metadata);
}));

return Promise.all(promises);
};
Expand Down Expand Up @@ -388,7 +453,8 @@ export const standard5July2016 = ({
keySystemAccess,
options,
removeSession,
eventBus
eventBus,
emeError
}) => {
let keySystemPromise = Promise.resolve();
const keySystem = keySystemAccess.keySystem;
Expand Down Expand Up @@ -438,13 +504,21 @@ export const standard5July2016 = ({
}).then(() => {
return keySystemAccess.createMediaKeys();
}).then((createdMediaKeys) => {
eventBus.trigger('keysystemaccesscomplete');
return addPendingSessions({
player,
video,
certificate,
createdMediaKeys
createdMediaKeys,
emeError
});
}).catch((err) => {
const metadata = {
errorType: videojs.Error.EMEFailedToCreateMediaKeys,
keySystem
};

emeError(err, metadata);
// if we have a specific error message, use it, otherwise show a more
// generic one
if (err) {
Expand All @@ -468,7 +542,8 @@ export const standard5July2016 = ({
getLicense,
contentId,
removeSession,
eventBus
eventBus,
emeError
});
});
};
Loading

0 comments on commit 5820da7

Please sign in to comment.