Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions src/controller/eme-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ class EMEController extends Logger implements ComponentAPI {
);
});
return keySystemAccess.then((mediaKeySystemAccess) => {
this.throwIfDestroyed();
this.log(
`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`,
);
Expand Down Expand Up @@ -1703,6 +1704,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(() => {});
}
}

Expand Down
111 changes: 111 additions & 0 deletions tests/unit/controller/eme-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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({
Expand Down
Loading