Skip to content

Commit

Permalink
Merge pull request #1597 from canalplus/fix/comprehensive-cached-check
Browse files Browse the repository at this point in the history
Set-up a comprehensive check when trying to reuse MediaKeySystemAccess
  • Loading branch information
peaBerberian authored Dec 16, 2024
2 parents 27a0a3a + 87df748 commit eb9005c
Showing 1 changed file with 106 additions and 77 deletions.
183 changes: 106 additions & 77 deletions src/main_thread/decrypt/find_key_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,59 +81,81 @@ interface IKeySystemType {
* system.
*/
keyName: string | undefined;
/** keyType: keySystem type (e.g. "com.widevine.alpha") */
/** KeySystem type (e.g. "com.widevine.alpha") */
keyType: string;
/** keySystem {Object}: the original keySystem object */
/** The original keySystem object */
keySystemOptions: IKeySystemOption;
}

/**
* @param {Object} keySystem
* @param {Object} askedConfiguration
* @param {MediaKeySystemAccess} currentKeySystemAccess
* @param {Object} currentKeySystemOptions
* @returns {null|Object}
* Takes a `newConfiguration` `MediaKeySystemConfiguration`, that is intended
* for the creation of a `MediaKeySystemAccess`, and a `prevConfiguration`
* `MediaKeySystemConfiguration`, that was the one used at creation of the
* current `MediaKeySystemAccess`.
*
* This function will then return `true` if it determined that the new
* configuration is conceptually compatible with the one used before, and
* `false` otherwise.
* @param {Object} newConfiguration - New wanted `MediaKeySystemConfiguration`
* @param {Object} prevConfiguration - The `MediaKeySystemConfiguration` that is
* relied on util now.
* @returns {boolean} - `true` if `newConfiguration` is compatible with
* `prevConfiguration`.
*/
function checkCachedMediaKeySystemAccess(
keySystem: IKeySystemOption,
askedConfiguration: MediaKeySystemConfiguration,
currentKeySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess,
currentKeySystemOptions: IKeySystemOption,
): null | {
keySystemOptions: IKeySystemOption;
askedConfiguration: MediaKeySystemConfiguration;
keySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess;
} {
const mksConfiguration = currentKeySystemAccess.getConfiguration();
if (shouldRenewMediaKeySystemAccess() || isNullOrUndefined(mksConfiguration)) {
return null;
function isNewMediaKeySystemConfigurationCompatibleWithPreviousOne(
newConfiguration: MediaKeySystemConfiguration,
prevConfiguration: MediaKeySystemConfiguration,
): boolean {
if (newConfiguration.label !== prevConfiguration.label) {
return false;
}

// TODO Do it with MediaKeySystemAccess.prototype.keySystem instead
if (keySystem.type !== currentKeySystemOptions.type) {
return null;
const prevDistinctiveIdentifier = prevConfiguration.distinctiveIdentifier ?? "optional";
const newDistinctiveIdentifier = newConfiguration.distinctiveIdentifier ?? "optional";
if (prevDistinctiveIdentifier !== newDistinctiveIdentifier) {
return false;
}

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

if (
keySystem.distinctiveIdentifier === "required" &&
mksConfiguration.distinctiveIdentifier !== "required"
) {
return null;
const prevInitDataTypes = prevConfiguration.initDataTypes ?? [];
const newInitDataTypes = newConfiguration.initDataTypes ?? [];
if (!isArraySubsetOf(newInitDataTypes, prevInitDataTypes)) {
return false;
}

return {
keySystemOptions: keySystem,
keySystemAccess: currentKeySystemAccess,
askedConfiguration,
};
const prevSessionTypes = prevConfiguration.sessionTypes ?? [];
const newSessionTypes = newConfiguration.sessionTypes ?? [];
if (!isArraySubsetOf(newSessionTypes, prevSessionTypes)) {
return false;
}

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 false;
}
}

return true;
}

/**
Expand Down Expand Up @@ -384,14 +406,7 @@ export default function getMediaKeySystemAccess(
cancelSignal: CancellationSignal,
): Promise<IFoundMediaKeySystemAccessEvent> {
log.info("DRM: Searching for compatible MediaKeySystemAccess");
/**
* Array of set keySystems for this content.
* Each item of this array is an object containing the following keys:
* - keyName {string}: keySystem canonical name (e.g. "widevine")
* - keyType {string}: keySystem type (e.g. "com.widevine.alpha")
* - keySystem {Object}: the original keySystem object
* @type {Array.<Object>}
*/
/** Array of set keySystems for this content. */
const keySystemsType: IKeySystemType[] = keySystemsConfigs.reduce(
(arr: IKeySystemType[], keySystemOptions) => {
const { EME_KEY_SYSTEMS } = config.getCurrent();
Expand Down Expand Up @@ -444,36 +459,6 @@ export default function getMediaKeySystemAccess(
}

const chosenType = keySystemsType[index];

const currentState = await MediaKeysAttacher.getAttachedMediaKeysState(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 @@ -484,8 +469,37 @@ export default function getMediaKeySystemAccess(
);

let keySystemAccess;
const currentState = await MediaKeysAttacher.getAttachedMediaKeysState(mediaElement);
for (let configIdx = 0; configIdx < keySystemConfigurations.length; configIdx++) {
const keySystemConfiguration = keySystemConfigurations[configIdx];

// Check if the current `MediaKeySystemAccess` created cannot be reused here
if (
currentState !== null &&
!shouldRenewMediaKeySystemAccess() &&
// TODO: Do it with MediaKeySystemAccess.prototype.keySystem instead?
keyType === currentState.keySystemOptions.type &&
eme.implementation === currentState.emeImplementation.implementation &&
isNewMediaKeySystemConfigurationCompatibleWithPreviousOne(
keySystemConfiguration,
currentState.askedConfiguration,
)
) {
log.info("DRM: Found cached compatible keySystem");
return Promise.resolve({
type: "reuse-media-key-system-access" as const,
value: {
mediaKeySystemAccess: currentState.mediaKeySystemAccess,
askedConfiguration: currentState.askedConfiguration,
options: currentState.keySystemOptions,
codecSupport: extractCodecSupportListFromConfiguration(
currentState.askedConfiguration,
currentState.mediaKeySystemAccess.getConfiguration(),
),
},
});
}

try {
keySystemAccess = await testKeySystem(keyType, [keySystemConfiguration]);
log.info("DRM: Found compatible keysystem", keyType, index + 1);
Expand Down Expand Up @@ -544,3 +558,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 eb9005c

Please sign in to comment.