fix(eme-controller): unhandled type error depending on promise state

This commit is contained in:
Benjamin Wallberg
2026-06-01 15:24:03 -07:00
committed by Rob Walch
parent f633b56e0e
commit 97f8d8d3c9
2 changed files with 115 additions and 0 deletions
+4
View File
@@ -308,6 +308,7 @@ class EMEController extends Logger implements ComponentAPI {
);
});
return keySystemAccess.then((mediaKeySystemAccess) => {
this.throwIfDestroyed();
this.log(
`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`,
);
@@ -1778,6 +1779,9 @@ class EMEController extends Logger implements ComponentAPI {
`Selecting key-system from session-keys ${keyFormats.join(', ')}`,
);
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
// Until onMediaEncrypted is called this promise is unhandled, add a catch to prevent unhandled rejection
// when result is not used.
this.keyFormatPromise.catch(() => {});
}
}
+111
View File
@@ -33,7 +33,12 @@ type EMEControllerTestable = Omit<EMEController, 'hls' | 'mediaKeySessions'> & {
data: MediaAttachedData,
) => void;
onMediaDetached: () => void;
onManifestLoaded: (
event: Events.MANIFEST_LOADED,
data: { sessionKeys: LevelKey[] },
) => void;
media: HTMLMediaElement | null;
keyFormatPromise: Promise<KeySystemFormats> | null;
getKeyStatuses: (mediaKeySessionContext: MediaKeySessionContext) => {
[keyId: string]: MediaKeyStatus;
};
@@ -582,6 +587,112 @@ describe('EMEController', function () {
});
});
it('should reject pending session-key key-system selection with invalid state when destroyed', function () {
let resolveKeySystemAccess!: (value: MediaKeySystemAccess) => void;
const keySystemAccessPromise = new Promise<MediaKeySystemAccess>(
(resolve) => {
resolveKeySystemAccess = resolve;
},
);
const createMediaKeysSpy = sinon.spy(() =>
Promise.resolve(new MediaKeysMock()),
);
const reqMediaKsAccessSpy = sinon.spy(() => keySystemAccessPromise);
setupEach({
emeEnabled: true,
requestMediaKeySystemAccessFunc: reqMediaKsAccessSpy,
drmSystems: {
'com.apple.fps': {},
},
});
const levelKey = getParsedLevelKey();
emeController.onManifestLoaded(Events.MANIFEST_LOADED, {
sessionKeys: [levelKey],
});
const keyFormatPromise = emeController.keyFormatPromise;
if (!keyFormatPromise) {
throw new Error('Expected pending key-system selection');
}
emeController.destroy();
const mediaKeySystemAccess: MediaKeySystemAccess = {
keySystem: KeySystems.FAIRPLAY,
createMediaKeys: createMediaKeysSpy,
getConfiguration: () => ({}),
};
resolveKeySystemAccess(mediaKeySystemAccess);
return keyFormatPromise.then(
() => {
throw new Error('Expected key-system selection to reject');
},
(error) => {
expect(error).to.be.instanceOf(Error);
expect(error.message).to.equal('invalid state');
expect(createMediaKeysSpy).not.to.have.been.called;
},
);
});
it('should handle unused session-key key-system selection rejections', function () {
let rejectKeySystemAccess!: (error: Error) => void;
const keySystemAccessPromise = new Promise<MediaKeySystemAccess>(
(resolve, reject) => {
rejectKeySystemAccess = reject;
},
);
let unhandledRejection: PromiseRejectionEvent | null = null;
const onUnhandledRejection = (event: PromiseRejectionEvent) => {
unhandledRejection = event;
event.preventDefault();
};
self.addEventListener('unhandledrejection', onUnhandledRejection);
setupEach({
emeEnabled: true,
requestMediaKeySystemAccessFunc: () => keySystemAccessPromise,
drmSystems: {
'com.apple.fps': {},
},
});
emeController.onManifestLoaded(Events.MANIFEST_LOADED, {
sessionKeys: [getParsedLevelKey()],
});
const keyFormatPromise = emeController.keyFormatPromise;
if (!keyFormatPromise) {
throw new Error('Expected key-system selection promise');
}
rejectKeySystemAccess(new Error('key-system access failed'));
return new Promise<void>((resolve) => {
self.setTimeout(resolve, 0);
})
.then(() => {
expect(unhandledRejection).to.equal(null);
expect(emeController.hls.trigger).not.to.have.been.called;
return keyFormatPromise.then(
() => {
throw new Error('Expected key-system selection to reject');
},
(error) => {
expect(error).to.be.instanceOf(Error);
expect(error.message).to.equal('key-system access failed');
},
);
})
.finally(() => {
self.removeEventListener('unhandledrejection', onUnhandledRejection);
});
});
it('should remove media property when media is detached', function () {
const reqMediaKsAccessSpy = sinon.spy(function () {
return Promise.resolve({