/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.offline.SessionDeleter');
goog.require('shaka.log');
goog.require('shaka.media.DrmEngine');
goog.require('shaka.util.ArrayUtils');
goog.requireType('shaka.net.NetworkingEngine');
/**
* Contains a utility method to delete persistent EME sessions.
*/
shaka.offline.SessionDeleter = class {
/**
* Deletes the given sessions. This never fails and instead logs the error.
*
* @param {shaka.extern.DrmConfiguration} config
* @param {!shaka.net.NetworkingEngine} netEngine
* @param {!Array.<shaka.extern.EmeSessionDB>} sessions
* @return {!Promise.<!Array.<string>>} The session IDs that were deleted.
*/
async delete(config, netEngine, sessions) {
const SessionDeleter = shaka.offline.SessionDeleter;
let deleted = [];
for (const bucket of SessionDeleter.createBuckets_(sessions)) {
// Run these sequentially to avoid creating multiple CDM instances at one
// time. Some embedded platforms may not support multiples.
const p = this.doDelete_(config, netEngine, bucket);
const cur = await p; // eslint-disable-line no-await-in-loop
deleted = deleted.concat(cur);
}
return deleted;
}
/**
* Performs the deletion of the given session IDs.
*
* @param {shaka.extern.DrmConfiguration} config
* @param {!shaka.net.NetworkingEngine} netEngine
* @param {shaka.offline.SessionDeleter.Bucket_} bucket
* @return {!Promise.<!Array.<string>>} The sessions that were deleted
* @private
*/
async doDelete_(config, netEngine, bucket) {
/** @type {!shaka.media.DrmEngine} */
const drmEngine = new shaka.media.DrmEngine({
netEngine: netEngine,
onError: () => {},
onKeyStatus: () => {},
onExpirationUpdated: () => {},
onEvent: () => {},
});
try {
drmEngine.configure(config);
await drmEngine.initForRemoval(
bucket.info.keySystem, bucket.info.licenseUri,
bucket.info.serverCertificate,
bucket.info.audioCapabilities, bucket.info.videoCapabilities);
} catch (e) {
shaka.log.warning('Error initializing EME', e);
await drmEngine.destroy();
return [];
}
try {
await drmEngine.setServerCertificate();
} catch (e) {
shaka.log.warning('Error setting server certificate', e);
await drmEngine.destroy();
return [];
}
/** @type {!Array.<string>} */
const sessionIds = [];
await Promise.all(bucket.sessionIds.map(async (sessionId) => {
// This method is in a .map(), so this starts multiple removes at once,
// so this removes the sessions in parallel.
try {
await drmEngine.removeSession(sessionId);
sessionIds.push(sessionId);
} catch (e) {
shaka.log.warning('Error deleting offline session', e);
}
}));
await drmEngine.destroy();
return sessionIds;
}
/**
* Collects the given sessions into buckets that can be done at the same time.
* Since querying with different parameters can give us back different CDMs,
* we can't just use one CDM instance to delete everything.
*
* @param {!Array.<shaka.extern.EmeSessionDB>} sessions
* @return {!Array.<shaka.offline.SessionDeleter.Bucket_>}
* @private
*/
static createBuckets_(sessions) {
const SessionDeleter = shaka.offline.SessionDeleter;
/** @type {!Array.<shaka.offline.SessionDeleter.Bucket_>} */
const ret = [];
for (const session of sessions) {
let found = false;
for (const bucket of ret) {
if (SessionDeleter.isCompatible_(bucket.info, session)) {
bucket.sessionIds.push(session.sessionId);
found = true;
break;
}
}
if (!found) {
ret.push({info: session, sessionIds: [session.sessionId]});
}
}
return ret;
}
/**
* Returns whether the given session infos are compatible with each other.
* @param {shaka.extern.EmeSessionDB} a
* @param {shaka.extern.EmeSessionDB} b
* @return {boolean}
* @private
*/
static isCompatible_(a, b) {
const ArrayUtils = shaka.util.ArrayUtils;
// TODO: Add a way to change the license server in DrmEngine to avoid
// resetting EME for different license servers.
const comp = (x, y) =>
x.robustness == y.robustness && x.contentType == y.contentType;
return a.keySystem == b.keySystem && a.licenseUri == b.licenseUri &&
ArrayUtils.hasSameElements(
a.audioCapabilities, b.audioCapabilities, comp) &&
ArrayUtils.hasSameElements(
a.videoCapabilities, b.videoCapabilities, comp);
}
};
/**
* @typedef {{
* info: shaka.extern.EmeSessionDB,
* sessionIds: !Array.<string>
* }}
*/
shaka.offline.SessionDeleter.Bucket_;