Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions src/controller/eme-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
removeEventListener,
} from '../utils/event-listener-helper';
import { arrayToHex } from '../utils/hex';
import { changeEndianness } from '../utils/keysystem-util';
import { Logger } from '../utils/logger';
import {
getKeySystemsForConfig,
Expand Down Expand Up @@ -881,6 +882,10 @@ class EMEController extends Logger implements ComponentAPI {
const sessionLevelKeyId = arrayToHex(
new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []),
);

let hasMatchedKey = false;
const keyStatuses: { status: MediaKeyStatus; keyId: string }[] = [];

mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach(
(status: MediaKeyStatus, keyId: BufferSource) => {
// keyStatuses.forEach is not standard API so the callback value looks weird on xboxone
Expand All @@ -890,27 +895,79 @@ class EMEController extends Logger implements ComponentAPI {
keyId = status;
status = temp;
}
const keyIdWithStatusChange = arrayToHex(

const keyIdArray: Uint8Array =
'buffer' in keyId
? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength)
: new Uint8Array(keyId),
: new Uint8Array(keyId);

// Handle PlayReady little-endian key ID conversion for status comparison only
// Don't modify the original key ID from playlist parsing
if (
mediaKeySessionContext.keySystem === KeySystems.PLAYREADY &&
keyIdArray.length === 16
) {
changeEndianness(keyIdArray);
}

const keyIdWithStatusChange = arrayToHex(
keyIdArray as Uint8Array<ArrayBuffer>,
);

// Store all key statuses for processing
keyStatuses.push({ status, keyId: keyIdWithStatusChange });

// Error immediately when encountering a key ID with this status again
if (status === 'internal-error') {
this.bannedKeyIds[keyIdWithStatusChange] = status;
}

// Only acknowledge status changes for level-key ID
// Check if this key matches the session-level key ID
const matched = keyIdWithStatusChange === sessionLevelKeyId;
this.log(
`${matched ? '' : 'un'}matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
);
if (matched) {
hasMatchedKey = true;
mediaKeySessionContext.keyStatus = status;
this.log(
`matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
);
} else {
this.log(
`unmatched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`,
);
}
},
);

// Handle case where no keys matched but all have the same status
Copy link
Contributor

@hongfeih-es hongfeih-es Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ShubhamSharma2311 how can we reproduce this? not sure this is required.
Still saw wrong key ids, seems sometimes endianness doesn't need to be changed, is this why?
Correct:

  • e13d04505bd346a088b41e3b8e9e97af
  • 76becf7d11af43698295422217009743

Incorrect:

  • 50043de1d35ba04688b41e3b8e9e97af
  • 7dcfbe76af1169438295422217009743
[log] > [eme]: unmatched key status change "status-pending" for keyStatuses keyId: e13d04505bd346a088b41e3b8e9e97af session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
eme-controller.ts:915 [log] > [eme]: unmatched key status change "status-pending" for keyStatuses keyId: 76becf7d11af43698295422217009743 session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
eme-controller.ts:915 [log] > [eme]: unmatched key status change "status-pending" for keyStatuses keyId: 50043de1d35ba04688b41e3b8e9e97af session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
eme-controller.ts:915 [log] > [eme]: unmatched key status change "status-pending" for keyStatuses keyId: 7dcfbe76af1169438295422217009743 session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
eme-controller.ts:915 [log] > [eme]: unmatched key status change "usable" for keyStatuses keyId: e13d04505bd346a088b41e3b8e9e97af session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
eme-controller.ts:915 [log] > [eme]: unmatched key status change "usable" for keyStatuses keyId: 76becf7d11af43698295422217009743 session keyId: b255031432584910a8272213421391b8 uri: data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgBBAE4AVgBzAGwAZwB5AEUARQBtAG8ASgB5AEkAVABRAGgATwBSAHUAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBVAEMAZwBNADgARQBLADkAeQBsAFEAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's changed when read from the PlayReady EXT-X-KEY, so it needs to be changes here too to match:

export function parsePlayReadyWRM(keyBytes: Uint8Array<ArrayBuffer>) {
const keyBytesUtf16 = new Uint16Array(
keyBytes.buffer,
keyBytes.byteOffset,
keyBytes.byteLength / 2,
);
const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
// Parse Playready WRMHeader XML
const xmlKeyBytes = keyByteStr.substring(
keyByteStr.indexOf('<'),
keyByteStr.length,
);
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
const keyData = xmlDoc.getElementsByTagName('KID')[0];
if (keyData) {
const keyId = keyData.childNodes[0]
? keyData.childNodes[0].nodeValue
: keyData.getAttribute('VALUE');
if (keyId) {
const keyIdArray = base64Decode(keyId).subarray(0, 16);
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
changeEndianness(keyIdArray);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hongfeih-es,

I plan to merge these changes. I have another PR in the works to improve Multi-key handling (group playlist key data with different key-is in the same session). I'll share more soon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sir, now I have to fix this thing #7510 (comment). am I right ??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sir, now I have to fix this thing #7510 (comment). am I right ??

No. These changes have been merged. New changes are in review with #7517.

Please leave review comments on #7517. This PR can be tested in the branch preview: https://bugfix-eme-multi-key.hls-js-4zn.pages.dev/demo/.

Or, file new issues against latest dev/canary at https://hlsjs-dev.video-dev.org/demo/.

// This can happen with PlayReady when key IDs don't align properly
if (!hasMatchedKey && keyStatuses.length > 0) {
const firstStatus = keyStatuses[0].status;
const allSameStatus = !keyStatuses.some(
({ status }) => status !== firstStatus,
);

if (
allSameStatus &&
(firstStatus === 'usable' || firstStatus.startsWith('usable'))
) {
this.log(
`No key matched session keyId ${sessionLevelKeyId}, but all keys have usable status "${firstStatus}". Treating as usable.`,
);
mediaKeySessionContext.keyStatus = firstStatus;
} else if (
allSameStatus &&
(firstStatus === 'internal-error' || firstStatus === 'expired')
) {
this.log(
`No key matched session keyId ${sessionLevelKeyId}, but all keys have error status "${firstStatus}". Applying to session.`,
);
mediaKeySessionContext.keyStatus = firstStatus;
} else {
this.log(
`No key matched session keyId ${sessionLevelKeyId}. Key statuses: ${keyStatuses.map(({ keyId, status }) => `${keyId}:${status}`).join(', ')}`,
);
}
}
}

private fetchServerCertificate(
Expand Down
Loading