Skip to content

Commit f9e23c0

Browse files
Fix PlayReady key endianness (#7510)
1 parent 665ea29 commit f9e23c0

File tree

1 file changed

+63
-6
lines changed

1 file changed

+63
-6
lines changed

src/controller/eme-controller.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
removeEventListener,
1313
} from '../utils/event-listener-helper';
1414
import { arrayToHex } from '../utils/hex';
15+
import { changeEndianness } from '../utils/keysystem-util';
1516
import { Logger } from '../utils/logger';
1617
import {
1718
getKeySystemsForConfig,
@@ -881,6 +882,10 @@ class EMEController extends Logger implements ComponentAPI {
881882
const sessionLevelKeyId = arrayToHex(
882883
new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []),
883884
);
885+
886+
let hasMatchedKey = false;
887+
const keyStatuses: { status: MediaKeyStatus; keyId: string }[] = [];
888+
884889
mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach(
885890
(status: MediaKeyStatus, keyId: BufferSource) => {
886891
// keyStatuses.forEach is not standard API so the callback value looks weird on xboxone
@@ -890,27 +895,79 @@ class EMEController extends Logger implements ComponentAPI {
890895
keyId = status;
891896
status = temp;
892897
}
893-
const keyIdWithStatusChange = arrayToHex(
898+
899+
const keyIdArray: Uint8Array =
894900
'buffer' in keyId
895901
? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength)
896-
: new Uint8Array(keyId),
902+
: new Uint8Array(keyId);
903+
904+
// Handle PlayReady little-endian key ID conversion for status comparison only
905+
// Don't modify the original key ID from playlist parsing
906+
if (
907+
mediaKeySessionContext.keySystem === KeySystems.PLAYREADY &&
908+
keyIdArray.length === 16
909+
) {
910+
changeEndianness(keyIdArray);
911+
}
912+
913+
const keyIdWithStatusChange = arrayToHex(
914+
keyIdArray as Uint8Array<ArrayBuffer>,
897915
);
898916

917+
// Store all key statuses for processing
918+
keyStatuses.push({ status, keyId: keyIdWithStatusChange });
919+
899920
// Error immediately when encountering a key ID with this status again
900921
if (status === 'internal-error') {
901922
this.bannedKeyIds[keyIdWithStatusChange] = status;
902923
}
903924

904-
// Only acknowledge status changes for level-key ID
925+
// Check if this key matches the session-level key ID
905926
const matched = keyIdWithStatusChange === sessionLevelKeyId;
906-
this.log(
907-
`${matched ? '' : 'un'}matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
908-
);
909927
if (matched) {
928+
hasMatchedKey = true;
910929
mediaKeySessionContext.keyStatus = status;
930+
this.log(
931+
`matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
932+
);
933+
} else {
934+
this.log(
935+
`unmatched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
936+
);
911937
}
912938
},
913939
);
940+
941+
// Handle case where no keys matched but all have the same status
942+
// This can happen with PlayReady when key IDs don't align properly
943+
if (!hasMatchedKey && keyStatuses.length > 0) {
944+
const firstStatus = keyStatuses[0].status;
945+
const allSameStatus = !keyStatuses.some(
946+
({ status }) => status !== firstStatus,
947+
);
948+
949+
if (
950+
allSameStatus &&
951+
(firstStatus === 'usable' || firstStatus.startsWith('usable'))
952+
) {
953+
this.log(
954+
`No key matched session keyId ${sessionLevelKeyId}, but all keys have usable status "${firstStatus}". Treating as usable.`,
955+
);
956+
mediaKeySessionContext.keyStatus = firstStatus;
957+
} else if (
958+
allSameStatus &&
959+
(firstStatus === 'internal-error' || firstStatus === 'expired')
960+
) {
961+
this.log(
962+
`No key matched session keyId ${sessionLevelKeyId}, but all keys have error status "${firstStatus}". Applying to session.`,
963+
);
964+
mediaKeySessionContext.keyStatus = firstStatus;
965+
} else {
966+
this.log(
967+
`No key matched session keyId ${sessionLevelKeyId}. Key statuses: ${keyStatuses.map(({ keyId, status }) => `${keyId}:${status}`).join(', ')}`,
968+
);
969+
}
970+
}
914971
}
915972

916973
private fetchServerCertificate(

0 commit comments

Comments
 (0)