Skip to content

Commit b583167

Browse files
authored
Extract key id incorrectly from key statuses when using PlayReady (#115)
* fix: playReady key ID conversion in okKeyStatusChange video-dev#7509 * chore: 1.6.13
1 parent 4722c78 commit b583167

File tree

3 files changed

+66
-9
lines changed

3 files changed

+66
-9
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hls.js",
3-
"version": "1.6.12",
3+
"version": "1.6.13",
44
"license": "Apache-2.0",
55
"description": "JavaScript HLS client using MediaSourceExtension",
66
"homepage": "https://github.com/video-dev/hls.js",

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)