diff --git a/.eslintrc.js b/.eslintrc.js index 473df4d2eb..907cfcb3e5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -71,6 +71,18 @@ module.exports = { message: "Avoid relying on `SourceBufferList` directly unless it is API-facing. Prefer our more restricted `ISourceBufferList` type", }, + MediaKeySystemAccess: { + message: + "Avoid relying on `MediaKeySystemAccess` directly unless it is API-facing. Prefer our more restricted `IMediaKeySystemAccess` type", + }, + MediaKeys: { + message: + "Avoid relying on `MediaKeys` directly unless it is API-facing. Prefer our more restricted `IMediaKeys` type", + }, + MediaKeySession: { + message: + "Avoid relying on `MediaKeySession` directly unless it is API-facing. Prefer our more restricted `IMediaKeySession` type", + }, }, }, ], diff --git a/src/compat/browser_compatibility_types.ts b/src/compat/browser_compatibility_types.ts index d552e015b5..2196488dfe 100644 --- a/src/compat/browser_compatibility_types.ts +++ b/src/compat/browser_compatibility_types.ts @@ -17,12 +17,6 @@ import type { IListener } from "../utils/event_emitter"; import globalScope from "../utils/global_scope"; -/** Regular MediaKeys type + optional functions present in IE11. */ -interface ICompatMediaKeysConstructor { - isTypeSupported?: (type: string) => boolean; // IE11 only - new (keyType?: string): MediaKeys; // argument for IE11 only -} - /** * Browser implementation of a VTTCue constructor. * TODO open TypeScript issue about it? @@ -191,18 +185,22 @@ export interface ISourceBuffer extends IEventTarget { onupdatestart: ((evt: Event) => void) | null; } +export interface IMediaEncryptedEvent extends MediaEncryptedEvent { + forceSessionRecreation?: boolean; +} + /** Events potentially dispatched by an `IMediaElement` */ export interface IMediaElementEventMap { canplay: Event; canplaythrough: Event; - encrypted: MediaEncryptedEvent; + encrypted: IMediaEncryptedEvent; ended: Event; enterpictureinpicture: Event; error: Event; leavepictureinpicture: Event; loadeddata: Event; loadedmetadata: Event; - needkey: MediaEncryptedEvent; + needkey: IMediaEncryptedEvent; pause: Event; play: Event; playing: Event; @@ -214,7 +212,7 @@ export interface IMediaElementEventMap { visibilitychange: Event; volumechange: Event; waiting: Event; - webkitneedkey: MediaEncryptedEvent; + webkitneedkey: IMediaEncryptedEvent; } /** @@ -241,7 +239,7 @@ export interface IMediaElement extends IEventTarget { duration: number; ended: boolean; error: MediaError | null; - mediaKeys: null | MediaKeys; + mediaKeys: null | IMediaKeys; muted: boolean; nodeName: string; paused: boolean; @@ -264,9 +262,9 @@ export interface IMediaElement extends IEventTarget { play(): Promise; removeAttribute(attr: string): void; removeChild(x: unknown): void; - setMediaKeys(x: MediaKeys | null): Promise; + setMediaKeys(x: IMediaKeys | null): Promise; - onencrypted: ((evt: MediaEncryptedEvent) => void) | null; + onencrypted: ((evt: IMediaEncryptedEvent) => void) | null; oncanplay: ((evt: Event) => void) | null; oncanplaythrough: ((evt: Event) => void) | null; onended: ((evt: Event) => void) | null; @@ -308,12 +306,36 @@ export interface IMediaElement extends IEventTarget { msSetMediaKeys?: (mediaKeys: unknown) => void; webkitSetMediaKeys?: (mediaKeys: unknown) => void; webkitKeys?: { - createSession?: (mimeType: string, initData: BufferSource) => MediaKeySession; + createSession?: (mimeType: string, initData: BufferSource) => IMediaKeySession; }; audioTracks?: ICompatAudioTrackList; videoTracks?: ICompatVideoTrackList; } +export interface IMediaKeySystemAccess { + readonly keySystem: string; + getConfiguration(): MediaKeySystemConfiguration; + createMediaKeys(): Promise; +} + +export interface IMediaKeys { + isTypeSupported?: (type: string) => boolean; // IE11 only + createSession(sessionType?: MediaKeySessionType): IMediaKeySession; + setServerCertificate(serverCertificate: BufferSource): Promise; +} + +export interface IMediaKeySession extends IEventTarget { + readonly closed: Promise; + readonly expiration: number; + readonly keyStatuses: MediaKeyStatusMap; + readonly sessionId: string; + close(): Promise; + generateRequest(_initDataType: string, _initData: BufferSource): Promise; + load(sessionId: string): Promise; + remove(): Promise; + update(response: BufferSource): Promise; +} + // @ts-expect-error unused function, just used for compile-time typechecking // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-restricted-types function testMediaElement(x: HTMLMediaElement) { @@ -346,6 +368,30 @@ function testSourceBufferList(x: SourceBufferList) { function assertCompatibleISourceBufferList(_x: ISourceBufferList) { // Noop } +// @ts-expect-error unused function, just used for compile-time typechecking +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-restricted-types +function testMediaKeySystemAccess(x: MediaKeySystemAccess) { + assertCompatibleIMediaKeySystemAccess(x); +} +function assertCompatibleIMediaKeySystemAccess(_x: IMediaKeySystemAccess) { + // Noop +} +// @ts-expect-error unused function, just used for compile-time typechecking +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-restricted-types +function testMediaKeys(x: MediaKeys) { + assertCompatibleIMediaKeys(x); +} +function assertCompatibleIMediaKeys(_x: IMediaKeys) { + // Noop +} +// @ts-expect-error unused function, just used for compile-time typechecking +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-restricted-types +function testMediaKeySession(x: MediaKeySession) { + assertCompatibleIMediaKeySession(x); +} +function assertCompatibleIMediaKeySession(_x: IMediaKeySession) { + // Noop +} /** * AudioTrackList implementation (that TS forgot). @@ -459,7 +505,6 @@ export type { ICompatVideoTrackList, ICompatAudioTrack, ICompatVideoTrack, - ICompatMediaKeysConstructor, ICompatTextTrack, ICompatVTTCue, ICompatVTTCueConstructor, diff --git a/src/compat/eme/close_session.ts b/src/compat/eme/close_session.ts index 92a42d6f81..d0babfa7f3 100644 --- a/src/compat/eme/close_session.ts +++ b/src/compat/eme/close_session.ts @@ -17,7 +17,7 @@ import log from "../../log"; import cancellableSleep from "../../utils/cancellable_sleep"; import TaskCanceller, { CancellationError } from "../../utils/task_canceller"; -import type { ICustomMediaKeySession } from "./custom_media_keys"; +import type { IMediaKeySession } from "../browser_compatibility_types"; /** * Close the given `MediaKeySession` and returns a Promise resolving when the @@ -32,9 +32,7 @@ import type { ICustomMediaKeySession } from "./custom_media_keys"; * @param {MediaKeySession|Object} session * @returns {Promise.} */ -export default function closeSession( - session: MediaKeySession | ICustomMediaKeySession, -): Promise { +export default function closeSession(session: IMediaKeySession): Promise { const timeoutCanceller = new TaskCanceller(); return Promise.race([ diff --git a/src/compat/eme/custom_key_system_access.ts b/src/compat/eme/custom_key_system_access.ts index af888b79f1..da27d29e4a 100644 --- a/src/compat/eme/custom_key_system_access.ts +++ b/src/compat/eme/custom_key_system_access.ts @@ -13,14 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { ICustomMediaKeys } from "./custom_media_keys"; - -// MediaKeySystemAccess implementation -export interface ICustomMediaKeySystemAccess { - readonly keySystem: string; - getConfiguration(): MediaKeySystemConfiguration; - createMediaKeys(): Promise; -} +import type { IMediaKeySystemAccess, IMediaKeys } from "../browser_compatibility_types"; /** * Simple implementation of the MediaKeySystemAccess EME API. @@ -28,7 +21,7 @@ export interface ICustomMediaKeySystemAccess { * All needed arguments are given to the constructor * @class CustomMediaKeySystemAccess */ -export default class CustomMediaKeySystemAccess implements ICustomMediaKeySystemAccess { +export default class CustomMediaKeySystemAccess implements IMediaKeySystemAccess { /** * @param {string} _keyType - type of key system (e.g. "widevine" or * "com.widevine.alpha"). @@ -38,7 +31,7 @@ export default class CustomMediaKeySystemAccess implements ICustomMediaKeySystem */ constructor( private readonly _keyType: string, - private readonly _mediaKeys: ICustomMediaKeys | MediaKeys, + private readonly _mediaKeys: IMediaKeys, private readonly _configuration: MediaKeySystemConfiguration, ) {} @@ -54,7 +47,7 @@ export default class CustomMediaKeySystemAccess implements ICustomMediaKeySystem * @returns {Promise.} - Promise wrapping the MediaKeys for this * MediaKeySystemAccess. Never rejects. */ - public createMediaKeys(): Promise { + public createMediaKeys(): Promise { return new Promise((res) => res(this._mediaKeys)); } diff --git a/src/compat/eme/custom_media_keys/ie11_media_keys.ts b/src/compat/eme/custom_media_keys/ie11_media_keys.ts index 9cd20c8aca..e10108b611 100644 --- a/src/compat/eme/custom_media_keys/ie11_media_keys.ts +++ b/src/compat/eme/custom_media_keys/ie11_media_keys.ts @@ -18,25 +18,23 @@ import EventEmitter from "../../../utils/event_emitter"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import TaskCanceller from "../../../utils/task_canceller"; import wrapInPromise from "../../../utils/wrapInPromise"; -import type { IMediaElement } from "../../browser_compatibility_types"; +import type { + IMediaElement, + IMediaKeySession, + IMediaKeys, +} from "../../browser_compatibility_types"; import * as events from "../../event_listeners"; import type { MSMediaKeys, MSMediaKeySession } from "./ms_media_keys_constructor"; import { MSMediaKeysConstructor } from "./ms_media_keys_constructor"; -import type { - ICustomMediaKeys, - ICustomMediaKeySession, - ICustomMediaKeyStatusMap, - IMediaKeySessionEvents, -} from "./types"; class IE11MediaKeySession - extends EventEmitter - implements ICustomMediaKeySession + extends EventEmitter + implements IMediaKeySession { public readonly update: (license: Uint8Array) => Promise; - public readonly closed: Promise; + public readonly closed: Promise; public expiration: number; - public keyStatuses: ICustomMediaKeyStatusMap; + public keyStatuses: MediaKeyStatusMap; private readonly _mk: MSMediaKeys; private readonly _sessionClosingCanceller: TaskCanceller; private _ss: MSMediaKeySession | undefined; @@ -47,7 +45,9 @@ class IE11MediaKeySession this._mk = mk; this._sessionClosingCanceller = new TaskCanceller(); this.closed = new Promise((resolve) => { - this._sessionClosingCanceller.signal.register(() => resolve()); + this._sessionClosingCanceller.signal.register(() => + resolve("closed-by-application"), + ); }); this.update = (license: Uint8Array) => { return new Promise((resolve, reject) => { @@ -81,21 +81,30 @@ class IE11MediaKeySession events.onKeyMessage( this._ss, (evt) => { - this.trigger((evt as Event).type ?? "message", evt as Event); + this.trigger( + ((evt as Event).type ?? "message") as keyof MediaKeySessionEventMap, + evt as Event, + ); }, this._sessionClosingCanceller.signal, ); events.onKeyAdded( this._ss, (evt) => { - this.trigger((evt as Event).type ?? "keyadded", evt as Event); + this.trigger( + ((evt as Event).type ?? "keyadded") as keyof MediaKeySessionEventMap, + evt as Event, + ); }, this._sessionClosingCanceller.signal, ); events.onKeyError( this._ss, (evt) => { - this.trigger((evt as Event).type ?? "keyerror", evt as Event); + this.trigger( + ((evt as Event).type ?? "keyerror") as keyof MediaKeySessionEventMap, + evt as Event, + ); }, this._sessionClosingCanceller.signal, ); @@ -123,7 +132,7 @@ class IE11MediaKeySession } } -class IE11CustomMediaKeys implements ICustomMediaKeys { +class IE11CustomMediaKeys implements IMediaKeys { private _videoElement?: IMediaElement; private _mediaKeys?: MSMediaKeys; @@ -143,14 +152,14 @@ class IE11CustomMediaKeys implements ICustomMediaKeys { }); } - createSession(/* sessionType */): ICustomMediaKeySession { + createSession(/* sessionType */): IMediaKeySession { if (this._videoElement === undefined || this._mediaKeys === undefined) { throw new Error("Video not attached to the MediaKeys"); } return new IE11MediaKeySession(this._mediaKeys); } - setServerCertificate(): Promise { + setServerCertificate(): Promise { throw new Error("Server certificate is not implemented in your browser"); } } @@ -158,10 +167,7 @@ class IE11CustomMediaKeys implements ICustomMediaKeys { export default function getIE11MediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => IE11CustomMediaKeys; - setMediaKeys: ( - elt: IMediaElement, - mediaKeys: MediaKeys | ICustomMediaKeys | null, - ) => Promise; + setMediaKeys: (elt: IMediaElement, mediaKeys: IMediaKeys | null) => Promise; } { const isTypeSupported = (keySystem: string, type?: string | null) => { if (MSMediaKeysConstructor === undefined) { @@ -175,7 +181,7 @@ export default function getIE11MediaKeysCallbacks(): { const createCustomMediaKeys = (keyType: string) => new IE11CustomMediaKeys(keyType); const setMediaKeys = ( elt: IMediaElement, - mediaKeys: MediaKeys | ICustomMediaKeys | null, + mediaKeys: IMediaKeys | null, ): Promise => { if (mediaKeys === null) { // msSetMediaKeys only accepts native MSMediaKeys as argument. diff --git a/src/compat/eme/custom_media_keys/index.ts b/src/compat/eme/custom_media_keys/index.ts index 19ae83bb8e..21eff0060c 100644 --- a/src/compat/eme/custom_media_keys/index.ts +++ b/src/compat/eme/custom_media_keys/index.ts @@ -5,11 +5,9 @@ import getMozMediaKeysCallbacks, { import getOldKitWebKitMediaKeyCallbacks, { isOldWebkitMediaElement, } from "./old_webkit_media_keys"; -import type { ICustomMediaKeys, ICustomMediaKeySession } from "./types"; import getWebKitMediaKeysCallbacks from "./webkit_media_keys"; import { WebKitMediaKeysConstructor } from "./webkit_media_keys_constructor"; -export type { ICustomMediaKeys, ICustomMediaKeySession }; export { getIE11MediaKeysCallbacks, MSMediaKeysConstructor, diff --git a/src/compat/eme/custom_media_keys/moz_media_keys_constructor.ts b/src/compat/eme/custom_media_keys/moz_media_keys_constructor.ts index e615f17279..203af1c121 100644 --- a/src/compat/eme/custom_media_keys/moz_media_keys_constructor.ts +++ b/src/compat/eme/custom_media_keys/moz_media_keys_constructor.ts @@ -16,11 +16,10 @@ import globalScope from "../../../utils/global_scope"; import wrapInPromise from "../../../utils/wrapInPromise"; -import type { IMediaElement } from "../../browser_compatibility_types"; -import type { ICustomMediaKeys } from "./types"; +import type { IMediaElement, IMediaKeys } from "../../browser_compatibility_types"; interface IMozMediaKeysConstructor { - new (keySystem: string): ICustomMediaKeys; + new (keySystem: string): IMediaKeys; isTypeSupported(keySystem: string, type?: string | null): boolean; } @@ -41,11 +40,8 @@ export { MozMediaKeysConstructor }; export default function getMozMediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; - createCustomMediaKeys: (keyType: string) => ICustomMediaKeys; - setMediaKeys: ( - elt: IMediaElement, - mediaKeys: MediaKeys | ICustomMediaKeys | null, - ) => Promise; + createCustomMediaKeys: (keyType: string) => IMediaKeys; + setMediaKeys: (elt: IMediaElement, mediaKeys: IMediaKeys | null) => Promise; } { const isTypeSupported = (keySystem: string, type?: string | null) => { if (MozMediaKeysConstructor === undefined) { @@ -64,7 +60,7 @@ export default function getMozMediaKeysCallbacks(): { }; const setMediaKeys = ( elt: IMediaElement, - mediaKeys: MediaKeys | ICustomMediaKeys | null, + mediaKeys: IMediaKeys | null, ): Promise => { return wrapInPromise(() => { if ( diff --git a/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts b/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts index 92dce51f13..134d93d9a3 100644 --- a/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts +++ b/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts @@ -20,13 +20,11 @@ import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import noop from "../../../utils/noop"; import { utf8ToStr } from "../../../utils/string_parsing"; import wrapInPromise from "../../../utils/wrapInPromise"; -import type { IMediaElement } from "../../browser_compatibility_types"; import type { - ICustomMediaKeys, - ICustomMediaKeySession, - ICustomMediaKeyStatusMap, - IMediaKeySessionEvents, -} from "./types"; + IMediaElement, + IMediaKeySession, + IMediaKeys, +} from "../../browser_compatibility_types"; export interface IOldWebkitHTMLMediaElement extends HTMLVideoElement { webkitGenerateKeyRequest: (keyType: string, initData: ArrayBuffer) => void; @@ -60,12 +58,12 @@ export function isOldWebkitMediaElement( * @class OldWebkitMediaKeySession */ class OldWebkitMediaKeySession - extends EventEmitter - implements ICustomMediaKeySession + extends EventEmitter + implements IMediaKeySession { - public readonly closed: Promise; + public readonly closed: Promise; public expiration: number; - public keyStatuses: ICustomMediaKeyStatusMap; + public keyStatuses: MediaKeyStatusMap; public sessionId: string; private readonly _vid: IOldWebkitHTMLMediaElement; @@ -84,7 +82,7 @@ class OldWebkitMediaKeySession this.expiration = NaN; const onSessionRelatedEvent = (evt: Event) => { - this.trigger(evt.type, evt); + this.trigger(evt.type as keyof MediaKeySessionEventMap, evt); }; this.closed = new Promise((resolve) => { this._closeSession = () => { @@ -94,7 +92,7 @@ class OldWebkitMediaKeySession mediaElement.removeEventListener(`webkit${evt}`, onSessionRelatedEvent); }, ); - resolve(); + resolve("closed-by-application"); }; }); @@ -155,7 +153,7 @@ class OldWebkitMediaKeySession } } -class OldWebKitCustomMediaKeys implements ICustomMediaKeys { +class OldWebKitCustomMediaKeys implements IMediaKeys { private readonly _keySystem: string; private _videoElement?: IOldWebkitHTMLMediaElement; @@ -172,14 +170,14 @@ class OldWebKitCustomMediaKeys implements ICustomMediaKeys { }); } - createSession(/* sessionType */): ICustomMediaKeySession { + createSession(/* sessionType */): IMediaKeySession { if (isNullOrUndefined(this._videoElement)) { throw new Error("Video not attached to the MediaKeys"); } return new OldWebkitMediaKeySession(this._videoElement, this._keySystem); } - setServerCertificate(): Promise { + setServerCertificate(): Promise { throw new Error("Server certificate is not implemented in your browser"); } } @@ -187,10 +185,7 @@ class OldWebKitCustomMediaKeys implements ICustomMediaKeys { export default function getOldWebKitMediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => OldWebKitCustomMediaKeys; - setMediaKeys: ( - elt: IMediaElement, - mediaKeys: MediaKeys | ICustomMediaKeys | null, - ) => Promise; + setMediaKeys: (elt: IMediaElement, mediaKeys: IMediaKeys | null) => Promise; } { const isTypeSupported = function (keyType: string): boolean { // get any