Skip to content

Commit

Permalink
Set-up a comprehensive check when trying to reuse MediaKeySystemAccess
Browse files Browse the repository at this point in the history
While working on cases where an application wants to set-up different DRM configurations depending on the content, yet still keep the same `MediaKeys` instance if possible (to keep our `MediaKeySession` cache), I tried to make sure we support at worse a case where a more constrained configuration would still be considered as compatible to a less constrained one - as long as there's no loss of features / capabilities between the two.

After attempts specific to the RxPlayer's `keySystems` API format, I ended up relying on the asked `MediaKeySystemConfiguration`s (both the new wanted one and the one that led to the current `MediaKeySystemAccess`) instead, as the corresponding logic seemed easier to me to maintain.
  • Loading branch information
peaBerberian committed Nov 21, 2024
1 parent cfb9203 commit ce6a11f
Showing 1 changed file with 97 additions and 57 deletions.
154 changes: 97 additions & 57 deletions src/main_thread/decrypt/find_key_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,52 +88,75 @@ interface IKeySystemType {
}

/**
* @param {Object} keySystem
* @param {Object} askedConfiguration
* @param {Object} newConfiguration
* @param {Object} prevConfiguration
* @param {MediaKeySystemAccess} currentKeySystemAccess
* @param {Object} currentKeySystemOptions
* @returns {null|Object}
*/
function checkCachedMediaKeySystemAccess(
keySystem: IKeySystemOption,
askedConfiguration: MediaKeySystemConfiguration,
newConfiguration: MediaKeySystemConfiguration,
prevConfiguration: MediaKeySystemConfiguration,
currentKeySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess,
currentKeySystemOptions: IKeySystemOption,
): null | {
keySystemOptions: IKeySystemOption;
askedConfiguration: MediaKeySystemConfiguration;
keySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess;
} {
): null | MediaKeySystemAccess | ICustomMediaKeySystemAccess {
const mksConfiguration = currentKeySystemAccess.getConfiguration();
if (shouldRenewMediaKeySystemAccess() || isNullOrUndefined(mksConfiguration)) {
return null;
}

// TODO Do it with MediaKeySystemAccess.prototype.keySystem instead
if (keySystem.type !== currentKeySystemOptions.type) {
if (newConfiguration.label !== prevConfiguration.label) {
return null;
}

if (
(!isNullOrUndefined(keySystem.persistentLicenseConfig) ||
keySystem.persistentState === "required") &&
mksConfiguration.persistentState !== "required"
) {
const prevDistinctiveIdentifier = prevConfiguration.distinctiveIdentifier ?? "optional";
const newDistinctiveIdentifier = newConfiguration.distinctiveIdentifier ?? "optional";
if (prevDistinctiveIdentifier !== newDistinctiveIdentifier) {
return null;
}

if (
keySystem.distinctiveIdentifier === "required" &&
mksConfiguration.distinctiveIdentifier !== "required"
) {
const prevPersistentState = prevConfiguration.persistentState ?? "optional";
const newPersistentState = newConfiguration.persistentState ?? "optional";
if (prevPersistentState !== newPersistentState) {
return null;
}

return {
keySystemOptions: keySystem,
keySystemAccess: currentKeySystemAccess,
askedConfiguration,
};
if (newConfiguration.persistentState !== prevConfiguration.persistentState) {
return null;
}

const prevInitDataTypes = prevConfiguration.initDataTypes ?? [];
const newInitDataTypes = newConfiguration.initDataTypes ?? [];
if (!isArraySubsetOf(newInitDataTypes, prevInitDataTypes)) {
return null;
}

const prevSessionTypes = prevConfiguration.sessionTypes ?? [];
const newSessionTypes = newConfiguration.sessionTypes ?? [];
if (!isArraySubsetOf(newSessionTypes, prevSessionTypes)) {
return null;
}

for (const prop of ["audioCapabilities", "videoCapabilities"] as const) {
const newCapabilities = newConfiguration[prop] ?? [];
const prevCapabilities = prevConfiguration[prop] ?? [];
const wasFound = newCapabilities.every((n) => {
for (let i = 0; i < prevCapabilities.length; i++) {
const prevCap = prevCapabilities[i];
if (
(prevCap.robustness ?? "") === (n.robustness ?? "") ||
(prevCap.encryptionScheme ?? null) === (n.encryptionScheme ?? null) ||
(prevCap.robustness ?? "") === (n.robustness ?? "")
) {
return true;
}
}
return false;
});
if (!wasFound) {
return null;
}
}

return currentKeySystemAccess;
}

/**
Expand Down Expand Up @@ -444,36 +467,6 @@ export default function getMediaKeySystemAccess(
}

const chosenType = keySystemsType[index];

const currentState = MediaKeysInfosStore.getState(mediaElement);
if (currentState !== null) {
if (eme.implementation === currentState.emeImplementation.implementation) {
// Fast way to find a compatible keySystem if the currently loaded
// one as exactly the same compatibility options.
const cachedKeySystemAccess = checkCachedMediaKeySystemAccess(
chosenType.keySystemOptions,
currentState.askedConfiguration,
currentState.mediaKeySystemAccess,
currentState.keySystemOptions,
);
if (cachedKeySystemAccess !== null) {
log.info("DRM: Found cached compatible keySystem");
return Promise.resolve({
type: "reuse-media-key-system-access" as const,
value: {
mediaKeySystemAccess: cachedKeySystemAccess.keySystemAccess,
askedConfiguration: cachedKeySystemAccess.askedConfiguration,
options: cachedKeySystemAccess.keySystemOptions,
codecSupport: extractCodecSupportListFromConfiguration(
cachedKeySystemAccess.askedConfiguration,
cachedKeySystemAccess.keySystemAccess.getConfiguration(),
),
},
});
}
}
}

const { keyType, keySystemOptions } = chosenType;

const keySystemConfigurations = buildKeySystemConfigurations(chosenType);
Expand All @@ -486,6 +479,38 @@ export default function getMediaKeySystemAccess(
let keySystemAccess;
for (let configIdx = 0; configIdx < keySystemConfigurations.length; configIdx++) {
const keySystemConfiguration = keySystemConfigurations[configIdx];
const currentState = MediaKeysInfosStore.getState(mediaElement);
if (currentState !== null) {
if (
// TODO Do it with MediaKeySystemAccess.prototype.keySystem instead
keyType === currentState.keySystemOptions.type &&
eme.implementation === currentState.emeImplementation.implementation
) {
// Fast way to find a compatible keySystem if the currently loaded
// one as exactly the same compatibility options.
const cachedKeySystemAccess = checkCachedMediaKeySystemAccess(
keySystemConfiguration,
currentState.askedConfiguration,
currentState.mediaKeySystemAccess,
);
if (cachedKeySystemAccess !== null) {
log.info("DRM: Found cached compatible keySystem");
return Promise.resolve({
type: "reuse-media-key-system-access" as const,
value: {
mediaKeySystemAccess: cachedKeySystemAccess,
askedConfiguration: currentState.askedConfiguration,
options: currentState.keySystemOptions,
codecSupport: extractCodecSupportListFromConfiguration(
currentState.askedConfiguration,
cachedKeySystemAccess.getConfiguration(),
),
},
});
}
}
}

try {
keySystemAccess = await testKeySystem(keyType, [keySystemConfiguration]);
log.info("DRM: Found compatible keysystem", keyType, index + 1);
Expand Down Expand Up @@ -544,3 +569,18 @@ export async function testKeySystem(
}
return keySystemAccess;
}

/**
* Returns `true` if `arr1`'s values are entirely contained in `arr2`.
* @param {string} arr1
* @param {string} arr2
* @return {boolean}
*/
function isArraySubsetOf(arr1: string[], arr2: string[]): boolean {
for (let i = 0; i < arr1.length; i++) {
if (!arrayIncludes(arr2, arr1[i])) {
return false;
}
}
return true;
}

0 comments on commit ce6a11f

Please sign in to comment.