diff --git a/.eslintrc.js b/.eslintrc.js index 1d191fc6d8..f3f79911d0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,6 +55,22 @@ module.exports = { Symbol: { message: "Avoid using the `Symbol` type. Did you mean `symbol`?", }, + HTMLMediaElement: { + message: + "Avoid relying on `HTMLMediaElement` directly unless it is API-facing. Prefer our more restricted `IMediaElement` type", + }, + MediaSource: { + message: + "Avoid relying on `MediaSource` directly unless it is API-facing. Prefer our more restricted `IMediaSource` type", + }, + SourceBuffer: { + message: + "Avoid relying on `SourceBuffer` directly unless it is API-facing. Prefer our more restricted `ISourceBuffer` type", + }, + SourceBufferList: { + message: + "Avoid relying on `SourceBufferList` directly unless it is API-facing. Prefer our more restricted `ISourceBufferList` type", + }, }, }, ], diff --git a/src/compat/__tests__/add_text_track.test.ts b/src/compat/__tests__/add_text_track.test.ts index c955c85552..399a7c3a24 100644 --- a/src/compat/__tests__/add_text_track.test.ts +++ b/src/compat/__tests__/add_text_track.test.ts @@ -1,4 +1,5 @@ import { describe, beforeEach, it, expect, vi } from "vitest"; +import type { IMediaElement } from "../../compat/browser_compatibility_types"; // Needed for calling require (which itself is needed to mock properly) because // it is not type-checked: @@ -23,7 +24,7 @@ describe("compat - addTextTrack", () => { const fakeMediaElement = { textTracks: [fakeTextTrack], addTextTrack: mockAddTextTrack, - } as unknown as HTMLMediaElement; + } as unknown as IMediaElement; vi.doMock("../browser_detection", () => ({ isIEOrEdge: true, @@ -51,7 +52,7 @@ describe("compat - addTextTrack", () => { const fakeMediaElement = { textTracks: fakeTextTracks, addTextTrack: mockAddTextTrack, - } as unknown as HTMLMediaElement; + } as unknown as IMediaElement; vi.doMock("../browser_detection", () => ({ isIEOrEdge: true, @@ -95,7 +96,7 @@ describe("compat - addTextTrack", () => { textTracks: fakeTextTracks, appendChild: mockAppendChild, childNodes: fakeChildNodes, - } as unknown as HTMLMediaElement; + } as unknown as IMediaElement; const spyOnCreateElement = vi .spyOn(document, "createElement") diff --git a/src/compat/__tests__/change_source_buffer_type.test.ts b/src/compat/__tests__/change_source_buffer_type.test.ts index eebae43b56..7316d2619a 100644 --- a/src/compat/__tests__/change_source_buffer_type.test.ts +++ b/src/compat/__tests__/change_source_buffer_type.test.ts @@ -1,11 +1,12 @@ import { describe, it, expect, vi } from "vitest"; import log from "../../log"; +import type { ISourceBuffer } from "../browser_compatibility_types"; import tryToChangeSourceBufferType from "../change_source_buffer_type"; describe("Compat - tryToChangeSourceBufferType", () => { it("should just return false if the SourceBuffer provided does not have a changeType method", () => { const spy = vi.spyOn(log, "warn"); - const fakeSourceBuffer: SourceBuffer = {} as unknown as SourceBuffer; + const fakeSourceBuffer: ISourceBuffer = {} as unknown as ISourceBuffer; expect(tryToChangeSourceBufferType(fakeSourceBuffer, "toto")).toBe(false); expect(spy).not.toHaveBeenCalled(); }); @@ -15,7 +16,7 @@ describe("Compat - tryToChangeSourceBufferType", () => { const changeTypeFn = vi.fn(); const fakeSourceBuffer = { changeType: changeTypeFn, - } as unknown as SourceBuffer; + } as unknown as ISourceBuffer; expect(tryToChangeSourceBufferType(fakeSourceBuffer, "toto")).toBe(true); expect(spy).not.toHaveBeenCalled(); }); @@ -28,7 +29,7 @@ describe("Compat - tryToChangeSourceBufferType", () => { }); const fakeSourceBuffer = { changeType: changeTypeFn, - } as unknown as SourceBuffer; + } as unknown as ISourceBuffer; expect(tryToChangeSourceBufferType(fakeSourceBuffer, "toto")).toBe(false); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith( diff --git a/src/compat/add_text_track.ts b/src/compat/add_text_track.ts index f30180e740..f473c65d4e 100644 --- a/src/compat/add_text_track.ts +++ b/src/compat/add_text_track.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ICompatTextTrack } from "./browser_compatibility_types"; +import type { ICompatTextTrack, IMediaElement } from "./browser_compatibility_types"; import { isIEOrEdge } from "./browser_detection"; /** @@ -28,7 +28,7 @@ import { isIEOrEdge } from "./browser_detection"; * @param {HTMLMediaElement} mediaElement * @returns {Object} */ -export default function addTextTrack(mediaElement: HTMLMediaElement): { +export default function addTextTrack(mediaElement: IMediaElement): { track: ICompatTextTrack; trackElement: HTMLTrackElement | undefined; } { diff --git a/src/compat/browser_compatibility_types.ts b/src/compat/browser_compatibility_types.ts index 2f991941ed..9c8de9cac9 100644 --- a/src/compat/browser_compatibility_types.ts +++ b/src/compat/browser_compatibility_types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IListener } from "../utils/event_emitter"; import globalScope from "../utils/global_scope"; import isNullOrUndefined from "../utils/is_null_or_undefined"; @@ -70,17 +71,218 @@ interface ICompatDocument extends Document { } /** - * HTMLMediaElement with added optional vendored functions used by "old" - * browsers. - * And TypeScript forgot to add assiociated AudioTrackList and VideoTrackList - * (and yes apparently a HTMLAudioElement can have an assiociated - * VideoTrackList). + * Simpler re-definition of an `EventTarget`, allowing more straightforward + * TypeScript exploitation in the RxPlayer. + */ +export interface IEventTarget { + addEventListener( + evt: TEventName, + fn: IListener, + ): void; + removeEventListener( + evt?: TEventName, + fn?: IListener, + ): void; +} + +/** Events potentially dispatched by an `ISourceBufferList` */ +export interface ISourceBufferListEventMap { + addsourcebuffer: Event; + removesourcebuffer: Event; +} + +/** + * Type-compatible with the `SourceBufferList` type (i.e. a `SourceBufferList` + * is a valid `ISourceBufferList`), the `ISourceBufferList` type allows to: + * + * - re-define some attributes or methods in cases where we detected that some + * platforms have a different implementation. + * + * - list all `SourceBufferList` attributes, methods and events that are + * relied on by the RxPlayer. + * + * - Allow an easier re-definition of that API for tests or for platforms + * which do not implement it. + */ +export interface ISourceBufferList extends IEventTarget { + readonly length: number; + onaddsourcebuffer: ((evt: Event) => void) | null; + onremovesourcebuffer: ((evt: Event) => void) | null; + [index: number]: ISourceBuffer; +} + +/** Events potentially dispatched by an `IMediaSource` */ +export interface IMediaSourceEventMap { + sourceopen: Event; + sourceended: Event; + sourceclose: Event; +} + +/** + * Type-compatible with the `MediaSource` type (i.e. a `MediaSource` is a valid + * `IMediaSource`), the `IMediaSource` type allows to: + * + * - re-define some attributes or methods in cases where we detected that some + * platforms have a different implementation. + * + * - list all `MediaSource` attributes, methods and events that are relied on + * by the RxPlayer. + * + * - Allow an easier re-definition of that API for tests or for platforms + * which do not implement it. + */ +export interface IMediaSource extends IEventTarget { + duration: number; + handle?: MediaProvider | IMediaSource | undefined; + readyState: "closed" | "open" | "ended"; + sourceBuffers: ISourceBufferList; + + addSourceBuffer(type: string): ISourceBuffer; + clearLiveSeekableRange(): void; + endOfStream(): void; + removeSourceBuffer(sb: ISourceBuffer): void; + setLiveSeekableRange(start: number, end: number): void; + + onsourceopen: ((evt: Event) => void) | null; + onsourceended: ((evt: Event) => void) | null; + onsourceclose: ((evt: Event) => void) | null; +} + +/** Events potentially dispatched by an `ISourceBuffer` */ +export interface ISourceBufferEventMap { + abort: Event; + error: Event; + update: Event; + updateend: Event; + updatestart: Event; +} + +/** + * Type-compatible with the `SourceBuffer` type (i.e. a `SourceBuffer` is a valid + * `ISourceBuffer`), the `ISourceBuffer` type allows to: + * + * - re-define some attributes or methods in cases where we detected that some + * platforms have a different implementation. + * + * - list all `SourceBuffer` attributes, methods and events that are relied on + * by the RxPlayer. + * + * - Allow an easier re-definition of that API for tests or for platforms + * which do not implement it. + */ +export interface ISourceBuffer extends IEventTarget { + appendWindowEnd: number; + appendWindowStart: number; + buffered: TimeRanges; + timestampOffset: number; + updating: boolean; + + abort(): void; + appendBuffer(data: BufferSource): void; + changeType(type: string): void; + remove(start: number, end: number): void; + + onabort: ((evt: Event) => void) | null; + onerror: ((evt: Event) => void) | null; + onupdate: ((evt: Event) => void) | null; + onupdateend: ((evt: Event) => void) | null; + onupdatestart: ((evt: Event) => void) | null; +} + +/** Events potentially dispatched by an `IMediaElement` */ +export interface IMediaElementEventMap { + canplay: Event; + canplaythrough: Event; + encrypted: MediaEncryptedEvent; + ended: Event; + enterpictureinpicture: Event; + error: Event; + leavepictureinpicture: Event; + loadeddata: Event; + loadedmetadata: Event; + needkey: MediaEncryptedEvent; + pause: Event; + play: Event; + playing: Event; + ratechange: Event; + seeked: Event; + seeking: Event; + stalled: Event; + timeupdate: Event; + visibilitychange: Event; + volumechange: Event; + waiting: Event; + webkitneedkey: MediaEncryptedEvent; +} + +/** + * Type-compatible with the `HTMLMediaElement` type (i.e. a `HTMLMediaElement` is + * a valid `IMediaElement`), the `IMediaElement` type allows to: + * + * - re-define some attributes or methods in cases where we detected that some + * platforms have a different implementation. * - * Note: I prefer to define my own `ICompatHTMLMediaElement` rather to extend - * the original definition to better detect which types have been extended and - * are not actually valid TypeScript types. + * - list all `HTMLMediaElement` attributes, methods and events that are + * relied on by the RxPlayer. + * + * - Allow a re-definition of that API for tests or for platforms which do not + * implement it. */ -interface ICompatHTMLMediaElement extends HTMLMediaElement { +export interface IMediaElement extends IEventTarget { + /* From `HTMLMediaElement`: */ + autoplay: boolean; + buffered: TimeRanges; + childNodes: NodeList | never[]; + clientHeight: number | undefined; + clientWidth: number | undefined; + currentTime: number; + duration: number; + ended: boolean; + error: MediaError | null; + mediaKeys: null | MediaKeys; + muted: boolean; + nodeName: string; + paused: boolean; + playbackRate: number; + preload: "none" | "metadata" | "auto" | ""; + readyState: number; + seekable: TimeRanges; + seeking: boolean; + src: string; + srcObject?: undefined | null | MediaProvider; + textTracks: TextTrackList | never[]; + volume: number; + + addTextTrack: (kind: TextTrackKind) => TextTrack; + appendChild(x: T): void; + hasAttribute(attr: string): boolean; + hasChildNodes(): boolean; + pause(): void; + play(): Promise; + removeAttribute(attr: string): void; + removeChild(x: unknown): void; + setMediaKeys(x: MediaKeys | null): Promise; + + onencrypted: ((evt: MediaEncryptedEvent) => void) | null; + oncanplay: ((evt: Event) => void) | null; + oncanplaythrough: ((evt: Event) => void) | null; + onended: ((evt: Event) => void) | null; + onenterpictureinpicture?: ((evt: Event) => void) | null; + onleavepictureinpicture?: ((evt: Event) => void) | null; + onerror: ((evt: Event) => void) | null; + onloadeddata: ((evt: Event) => void) | null; + onloadedmetadata: ((evt: Event) => void) | null; + onpause: ((evt: Event) => void) | null; + onplay: ((evt: Event) => void) | null; + onplaying: ((evt: Event) => void) | null; + onratechange: ((evt: Event) => void) | null; + onseeked: ((evt: Event) => void) | null; + onseeking: ((evt: Event) => void) | null; + onstalled: ((evt: Event) => void) | null; + ontimeupdate: ((evt: Event) => void) | null; + onvolumechange: ((evt: Event) => void) | null; + onwaiting: ((evt: Event) => void) | null; + mozRequestFullScreen?: () => void; msRequestFullscreen?: () => void; webkitRequestFullscreen?: () => void; @@ -93,9 +295,40 @@ interface ICompatHTMLMediaElement extends HTMLMediaElement { webkitKeys?: { createSession?: (mimeType: string, initData: BufferSource) => MediaKeySession; }; - readonly audioTracks?: ICompatAudioTrackList; - readonly videoTracks?: ICompatVideoTrackList; + audioTracks?: ICompatAudioTrackList; + videoTracks?: ICompatVideoTrackList; +} + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/ban-types */ +// @ts-expect-error unused function, just used for compile-time typechecking +function testMediaElement(x: HTMLMediaElement) { + assertCompatibleIMediaElement(x); +} +function assertCompatibleIMediaElement(_x: IMediaElement) { + // Noop +} +// @ts-expect-error unused function, just used for compile-time typechecking +function testMediaSource(x: MediaSource) { + assertCompatibleIMediaSource(x); +} +function assertCompatibleIMediaSource(_x: IMediaSource) { + // Noop +} +// @ts-expect-error unused function, just used for compile-time typechecking +function testSourceBuffer(x: SourceBuffer) { + assertCompatibleISourceBuffer(x); +} +function assertCompatibleISourceBuffer(_x: ISourceBuffer) { + // Noop +} +// @ts-expect-error unused function, just used for compile-time typechecking +function testSourceBufferList(x: SourceBufferList) { + assertCompatibleISourceBufferList(x); +} +function assertCompatibleISourceBufferList(_x: ISourceBufferList) { + // Noop } +/* eslint-enable @typescript-eslint/no-unused-vars, @typescript-eslint/ban-types */ /** * AudioTrackList implementation (that TS forgot). @@ -169,7 +402,9 @@ export interface ICompatPictureInPictureWindow extends EventTarget { /* eslint-disable */ /** MediaSource implementation, including vendored implementations. */ const gs = globalScope as any; -const MediaSource_: typeof MediaSource | undefined = +const MediaSource_: + | { new (): IMediaSource; isTypeSupported(type: string): boolean } + | undefined = gs === undefined ? undefined : !isNullOrUndefined(gs.MediaSource) @@ -202,7 +437,6 @@ export interface ICompatTextTrackList extends TextTrackList { export type { ICompatDocument, - ICompatHTMLMediaElement, ICompatAudioTrackList, ICompatVideoTrackList, ICompatAudioTrack, diff --git a/src/compat/clear_element_src.ts b/src/compat/clear_element_src.ts index 5443493117..26d2f1e29d 100644 --- a/src/compat/clear_element_src.ts +++ b/src/compat/clear_element_src.ts @@ -16,12 +16,13 @@ import log from "../log"; import isNullOrUndefined from "../utils/is_null_or_undefined"; +import type { IMediaElement } from "./browser_compatibility_types"; /** * Clear element's src attribute. * @param {HTMLMediaElement} element */ -export default function clearElementSrc(element: HTMLMediaElement): void { +export default function clearElementSrc(element: IMediaElement): void { // On some browsers, we first have to make sure the textTracks elements are // both disabled and removed from the DOM. // If we do not do that, we may be left with displayed text tracks on the 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 b9fff33862..9cd20c8aca 100644 --- a/src/compat/eme/custom_media_keys/ie11_media_keys.ts +++ b/src/compat/eme/custom_media_keys/ie11_media_keys.ts @@ -18,7 +18,7 @@ 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 { ICompatHTMLMediaElement } from "../../browser_compatibility_types"; +import type { IMediaElement } 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"; @@ -124,7 +124,7 @@ class IE11MediaKeySession } class IE11CustomMediaKeys implements ICustomMediaKeys { - private _videoElement?: ICompatHTMLMediaElement; + private _videoElement?: IMediaElement; private _mediaKeys?: MSMediaKeys; constructor(keyType: string) { @@ -134,9 +134,9 @@ class IE11CustomMediaKeys implements ICustomMediaKeys { this._mediaKeys = new MSMediaKeysConstructor(keyType); } - _setVideo(videoElement: HTMLMediaElement): Promise { + _setVideo(videoElement: IMediaElement): Promise { return wrapInPromise(() => { - this._videoElement = videoElement as ICompatHTMLMediaElement; + this._videoElement = videoElement; if (this._videoElement.msSetMediaKeys !== undefined) { this._videoElement.msSetMediaKeys(this._mediaKeys); } @@ -159,7 +159,7 @@ export default function getIE11MediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => IE11CustomMediaKeys; setMediaKeys: ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ) => Promise; } { @@ -174,7 +174,7 @@ export default function getIE11MediaKeysCallbacks(): { }; const createCustomMediaKeys = (keyType: string) => new IE11CustomMediaKeys(keyType); const setMediaKeys = ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise => { if (mediaKeys === null) { 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 8b090d8904..28d0c09e4f 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,7 +16,7 @@ import globalScope from "../../../utils/global_scope"; import wrapInPromise from "../../../utils/wrapInPromise"; -import type { ICompatHTMLMediaElement } from "../../browser_compatibility_types"; +import type { IMediaElement } from "../../browser_compatibility_types"; import type { ICustomMediaKeys } from "./types"; interface IMozMediaKeysConstructor { @@ -44,7 +44,7 @@ export default function getMozMediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => ICustomMediaKeys; setMediaKeys: ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ) => Promise; } { @@ -64,11 +64,10 @@ export default function getMozMediaKeysCallbacks(): { return new MozMediaKeysConstructor(keyType); }; const setMediaKeys = ( - mediaElement: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise => { return wrapInPromise(() => { - const elt: ICompatHTMLMediaElement = mediaElement; if ( elt.mozSetMediaKeys === undefined || typeof elt.mozSetMediaKeys !== "function" 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 2c6851fceb..f4cb0c1026 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,6 +20,7 @@ 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, @@ -165,9 +166,7 @@ class OldWebKitCustomMediaKeys implements ICustomMediaKeys { this._keySystem = keySystem; } - _setVideo( - videoElement: IOldWebkitHTMLMediaElement | HTMLMediaElement, - ): Promise { + _setVideo(videoElement: IOldWebkitHTMLMediaElement | IMediaElement): Promise { return wrapInPromise(() => { if (!isOldWebkitMediaElement(videoElement)) { throw new Error("Video not attached to the MediaKeys"); @@ -192,7 +191,7 @@ export default function getOldWebKitMediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => OldWebKitCustomMediaKeys; setMediaKeys: ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ) => Promise; } { @@ -220,7 +219,7 @@ export default function getOldWebKitMediaKeysCallbacks(): { const createCustomMediaKeys = (keyType: string) => new OldWebKitCustomMediaKeys(keyType); const setMediaKeys = ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise => { if (mediaKeys === null) { diff --git a/src/compat/eme/custom_media_keys/types.ts b/src/compat/eme/custom_media_keys/types.ts index 8bd183a1c3..143006fa8d 100644 --- a/src/compat/eme/custom_media_keys/types.ts +++ b/src/compat/eme/custom_media_keys/types.ts @@ -15,6 +15,7 @@ */ import type { IEventEmitter } from "../../../utils/event_emitter"; +import type { IMediaElement } from "../../browser_compatibility_types"; export interface ICustomMediaKeySession extends IEventEmitter { // Attributes @@ -38,7 +39,7 @@ export interface ICustomMediaKeySession extends IEventEmitter Promise; + _setVideo: (vid: IMediaElement) => Promise; createSession(sessionType?: MediaKeySessionType): ICustomMediaKeySession; setServerCertificate(setServerCertificate: BufferSource): Promise; } diff --git a/src/compat/eme/custom_media_keys/webkit_media_keys.ts b/src/compat/eme/custom_media_keys/webkit_media_keys.ts index 217304b865..26cf19e821 100644 --- a/src/compat/eme/custom_media_keys/webkit_media_keys.ts +++ b/src/compat/eme/custom_media_keys/webkit_media_keys.ts @@ -18,7 +18,7 @@ import EventEmitter from "../../../utils/event_emitter"; import noop from "../../../utils/noop"; import startsWith from "../../../utils/starts_with"; import wrapInPromise from "../../../utils/wrapInPromise"; -import type { ICompatHTMLMediaElement } from "../../browser_compatibility_types"; +import type { IMediaElement } from "../../browser_compatibility_types"; import getWebKitFairplayInitData from "../get_webkit_fairplay_initdata"; import type { ICustomMediaKeys, @@ -30,7 +30,7 @@ import type { IWebKitMediaKeys } from "./webkit_media_keys_constructor"; import { WebKitMediaKeysConstructor } from "./webkit_media_keys_constructor"; export interface ICustomWebKitMediaKeys { - _setVideo: (videoElement: HTMLMediaElement) => void; + _setVideo: (videoElement: IMediaElement) => void; createSession(mimeType: string, initData: Uint8Array): ICustomMediaKeySession; setServerCertificate(setServerCertificate: BufferSource): Promise; } @@ -47,15 +47,11 @@ function isFairplayKeyType(keyType: string): boolean { /** * Set media keys on video element using native HTMLMediaElement * setMediaKeys from WebKit. - * @param {HTMLMediaElement} videoElement + * @param {HTMLMediaElement} elt * @param {Object|null} mediaKeys * @returns {Promise} */ -function setWebKitMediaKeys( - videoElement: HTMLMediaElement, - mediaKeys: unknown, -): Promise { - const elt: ICompatHTMLMediaElement = videoElement; +function setWebKitMediaKeys(elt: IMediaElement, mediaKeys: unknown): Promise { return wrapInPromise(() => { if (elt.webkitSetMediaKeys === undefined) { throw new Error("No webKitMediaKeys API."); @@ -80,7 +76,7 @@ class WebkitMediaKeySession public expiration: number; public keyStatuses: ICustomMediaKeyStatusMap; - private readonly _videoElement: HTMLMediaElement; + private readonly _videoElement: IMediaElement; private readonly _keyType: string; private _nativeSession: undefined | MediaKeySession; private _serverCertificate: Uint8Array | undefined; @@ -94,7 +90,7 @@ class WebkitMediaKeySession * @param {Uint8Array | undefined} serverCertificate */ constructor( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keyType: string, serverCertificate?: Uint8Array, ) { @@ -144,7 +140,7 @@ class WebkitMediaKeySession public generateRequest(_initDataType: string, initData: ArrayBuffer): Promise { return new Promise((resolve) => { - const elt = this._videoElement as ICompatHTMLMediaElement; + const elt = this._videoElement; if (elt.webkitKeys?.createSession === undefined) { throw new Error("No WebKitMediaKeys API."); } @@ -233,7 +229,7 @@ class WebkitMediaKeySession } class WebKitCustomMediaKeys implements ICustomWebKitMediaKeys { - private _videoElement?: HTMLMediaElement; + private _videoElement?: IMediaElement; private _mediaKeys?: IWebKitMediaKeys; private _serverCertificate?: Uint8Array; private _keyType: string; @@ -246,7 +242,7 @@ class WebKitCustomMediaKeys implements ICustomWebKitMediaKeys { this._mediaKeys = new WebKitMediaKeysConstructor(keyType); } - _setVideo(videoElement: HTMLMediaElement): Promise { + _setVideo(videoElement: IMediaElement): Promise { this._videoElement = videoElement; if (this._videoElement === undefined) { throw new Error("Video not attached to the MediaKeys"); @@ -275,7 +271,7 @@ export default function getWebKitMediaKeysCallbacks(): { isTypeSupported: (keyType: string) => boolean; createCustomMediaKeys: (keyType: string) => WebKitCustomMediaKeys; setMediaKeys: ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ) => Promise; } { @@ -285,7 +281,7 @@ export default function getWebKitMediaKeysCallbacks(): { const isTypeSupported = WebKitMediaKeysConstructor.isTypeSupported; const createCustomMediaKeys = (keyType: string) => new WebKitCustomMediaKeys(keyType); const setMediaKeys = ( - elt: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise => { if (mediaKeys === null) { diff --git a/src/compat/eme/custom_media_keys/webkit_media_keys_constructor.ts b/src/compat/eme/custom_media_keys/webkit_media_keys_constructor.ts index a30ddf4017..677d1f9262 100644 --- a/src/compat/eme/custom_media_keys/webkit_media_keys_constructor.ts +++ b/src/compat/eme/custom_media_keys/webkit_media_keys_constructor.ts @@ -15,7 +15,7 @@ */ import globalScope from "../../../utils/global_scope"; -import type { ICompatHTMLMediaElement } from "../../browser_compatibility_types"; +import type { IMediaElement } from "../../browser_compatibility_types"; type IWebKitMediaKeys = unknown; @@ -36,8 +36,7 @@ if ( WebKitMediaKeys !== undefined && typeof WebKitMediaKeys.isTypeSupported === "function" && typeof WebKitMediaKeys.prototype.createSession === "function" && - typeof (HTMLMediaElement.prototype as ICompatHTMLMediaElement).webkitSetMediaKeys === - "function" + typeof (HTMLMediaElement.prototype as IMediaElement).webkitSetMediaKeys === "function" ) { WebKitMediaKeysConstructor = WebKitMediaKeys; } diff --git a/src/compat/eme/eme-api-implementation.ts b/src/compat/eme/eme-api-implementation.ts index 070ff3891f..96e7885deb 100644 --- a/src/compat/eme/eme-api-implementation.ts +++ b/src/compat/eme/eme-api-implementation.ts @@ -4,7 +4,7 @@ import globalScope from "../../utils/global_scope"; import isNode from "../../utils/is_node"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; import type { CancellationSignal } from "../../utils/task_canceller"; -import type { ICompatHTMLMediaElement } from "../browser_compatibility_types"; +import type { IMediaElement } from "../browser_compatibility_types"; import { isIE11 } from "../browser_detection"; import type { IEventTargetLike } from "../event_listeners"; import { createCompatibleEventListener } from "../event_listeners"; @@ -77,7 +77,7 @@ export interface IEmeApiImplementation { * scenario. */ setMediaKeys: ( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ) => Promise; @@ -278,17 +278,16 @@ function getEmeApiImplementation( /** * Set the given MediaKeys on the given HTMLMediaElement. * Emits null when done then complete. - * @param {HTMLMediaElement} mediaElement + * @param {HTMLMediaElement} elt * @param {Object} mediaKeys * @returns {Promise} */ function defaultSetMediaKeys( - mediaElement: HTMLMediaElement, + elt: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise { try { let ret: unknown; - const elt: ICompatHTMLMediaElement = mediaElement; /* eslint-disable @typescript-eslint/unbound-method */ if (typeof elt.setMediaKeys === "function") { ret = elt.setMediaKeys(mediaKeys as MediaKeys); diff --git a/src/compat/eme/set_media_keys.ts b/src/compat/eme/set_media_keys.ts index 7dbba42ae8..d8b33d970b 100644 --- a/src/compat/eme/set_media_keys.ts +++ b/src/compat/eme/set_media_keys.ts @@ -1,5 +1,6 @@ import log from "../../log"; import sleep from "../../utils/sleep"; +import type { IMediaElement } from "../browser_compatibility_types"; import shouldAwaitSetMediaKeys from "../should_await_set_media_keys"; import type { ICustomMediaKeys } from "./custom_media_keys"; import type { IEmeApiImplementation } from "./eme-api-implementation"; @@ -12,7 +13,7 @@ import type { IEmeApiImplementation } from "./eme-api-implementation"; */ export function setMediaKeys( emeImplementation: IEmeApiImplementation, - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, mediaKeys: MediaKeys | ICustomMediaKeys | null, ): Promise { const prom = emeImplementation diff --git a/src/compat/event_listeners.ts b/src/compat/event_listeners.ts index 1f823839dd..0556b5ac4e 100644 --- a/src/compat/event_listeners.ts +++ b/src/compat/event_listeners.ts @@ -26,8 +26,9 @@ import SharedReference from "../utils/reference"; import type { CancellationSignal } from "../utils/task_canceller"; import type { ICompatDocument, - ICompatHTMLMediaElement, ICompatPictureInPictureWindow, + IEventTarget, + IMediaElement, } from "./browser_compatibility_types"; const BROWSER_PREFIXES = ["", "webkit", "moz", "ms"]; @@ -228,15 +229,14 @@ export interface IPictureInPictureEvent { /** * Emit when video enters and leaves Picture-In-Picture mode. - * @param {HTMLMediaElement} elt + * @param {HTMLMediaElement} mediaElement * @param {Object} stopListening * @returns {Object} */ function getPictureOnPictureStateRef( - elt: HTMLMediaElement, + mediaElement: IMediaElement, stopListening: CancellationSignal, ): IReadOnlySharedReference { - const mediaElement = elt as ICompatHTMLMediaElement; if ( mediaElement.webkitSupportsPresentationMode === true && typeof mediaElement.webkitSetPresentationMode === "function" @@ -263,7 +263,7 @@ function getPictureOnPictureStateRef( } const isPIPEnabled = - (document as ICompatDocument).pictureInPictureElement === mediaElement; + document.pictureInPictureElement === (mediaElement as unknown as HTMLElement); const ref = new SharedReference( { isEnabled: isPIPEnabled, pipWindow: null }, stopListening, @@ -392,7 +392,7 @@ function getScreenResolutionRef( * @returns {Object} */ function getElementResolutionRef( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, pipStatusRef: IReadOnlySharedReference, stopListening: CancellationSignal, ): IReadOnlySharedReference<{ @@ -596,7 +596,7 @@ const onEnded = createCompatibleEventListener(["ended"]); * emits */ function addEventListener( - elt: IEventEmitterLike, + elt: IEventEmitterLike | IEventTarget>, evt: string, listener: (x?: unknown) => void, stopListening: CancellationSignal, diff --git a/src/compat/get_start_date.ts b/src/compat/get_start_date.ts index 07c5a072cb..1d7a64cbf2 100644 --- a/src/compat/get_start_date.ts +++ b/src/compat/get_start_date.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import type { IMediaElement } from "./browser_compatibility_types"; + /** * Calculating a live-offseted media position necessitate to obtain first an * offset, and then adding that offset to the wanted position. @@ -28,8 +30,8 @@ * @param {HTMLMediaElement} mediaElement * @returns {number|undefined} */ -export default function getStartDate(mediaElement: HTMLMediaElement): number | undefined { - const _mediaElement: HTMLMediaElement & { +export default function getStartDate(mediaElement: IMediaElement): number | undefined { + const _mediaElement: IMediaElement & { getStartDate?: () => number | Date | null | undefined; } = mediaElement; if (typeof _mediaElement.getStartDate === "function") { diff --git a/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts b/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts index b26214588e..5ad1782ff1 100644 --- a/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts +++ b/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts @@ -15,6 +15,7 @@ */ import { MediaSource_ } from "../../../compat/browser_compatibility_types"; +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import log from "../../../log"; import { resetMediaElement } from "../../../main_thread/init/utils/create_media_source"; import { SourceBufferType } from "../../../mse"; @@ -36,7 +37,7 @@ const generateMediaSourceId = idGenerator(); * @returns {Promise.} */ export default function prepareSourceBuffer( - videoElement: HTMLVideoElement, + videoElement: IMediaElement, codec: string, cleanUpSignal: CancellationSignal, ): Promise { diff --git a/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts b/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts index 7c5f33320b..1c38e24831 100644 --- a/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts +++ b/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import type { MainSourceBufferInterface } from "../../../mse/main_media_source_interface"; /** @@ -28,7 +29,7 @@ import type { MainSourceBufferInterface } from "../../../mse/main_media_source_i * @returns {Promise} */ export default function removeBufferAroundTime( - videoElement: HTMLMediaElement, + videoElement: IMediaElement, sourceBufferInterface: MainSourceBufferInterface, time: number, margin: number | undefined, diff --git a/src/features/types.ts b/src/features/types.ts index 2aeb44c6cc..c97df8c0b8 100644 --- a/src/features/types.ts +++ b/src/features/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../compat/browser_compatibility_types"; import type { SegmentSink } from "../core/segment_sinks"; import type ContentDecryptor from "../main_thread/decrypt"; import type DirectFileContentInitializer from "../main_thread/init/directfile_content_initializer"; @@ -46,7 +47,7 @@ import type { CancellationSignal } from "../utils/task_canceller"; * @returns {Object} - `SegmentSink` implementation. */ export type IHTMLTextTracksBuffer = new ( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, textTrackElement: HTMLElement, ) => SegmentSink; @@ -59,7 +60,7 @@ export type IHTMLTextTracksBuffer = new ( * tracks will be displayed will also be linked to this `HTMLMediaElement`. * @returns {Object} - `SegmentSink` implementation. */ -export type INativeTextTracksBuffer = new (mediaElement: HTMLMediaElement) => SegmentSink; +export type INativeTextTracksBuffer = new (mediaElement: IMediaElement) => SegmentSink; export type IDashNativeParser = ( dom: Document, diff --git a/src/main_thread/api/__tests__/utils.test.ts b/src/main_thread/api/__tests__/utils.test.ts index ecb563895b..e94ff3bef5 100644 --- a/src/main_thread/api/__tests__/utils.test.ts +++ b/src/main_thread/api/__tests__/utils.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "vitest"; +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import config from "../../../config"; import { getLoadedContentState } from "../utils"; @@ -10,7 +11,7 @@ describe("API - getLoadedContentState", () => { currentTime: 0, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; // we can just do every possibility here expect(getLoadedContentState(mediaElement, null)).toBe("ENDED"); @@ -34,7 +35,7 @@ describe("API - getLoadedContentState", () => { currentTime: 10, // worst case -> currentTime === duration paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, null)).toBe("PLAYING"); }); @@ -45,7 +46,7 @@ describe("API - getLoadedContentState", () => { currentTime: 10, // worst case -> currentTime === duration paused: true, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, null)).toBe("PAUSED"); }); @@ -56,7 +57,7 @@ describe("API - getLoadedContentState", () => { currentTime: 5, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "buffering")).toBe("BUFFERING"); fakeProps.paused = true; expect(getLoadedContentState(mediaElement, "buffering")).toBe("BUFFERING"); @@ -69,7 +70,7 @@ describe("API - getLoadedContentState", () => { currentTime: 5, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "freezing")).toBe("FREEZING"); fakeProps.paused = true; expect(getLoadedContentState(mediaElement, "freezing")).toBe("FREEZING"); @@ -82,7 +83,7 @@ describe("API - getLoadedContentState", () => { currentTime: 5, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "not-ready")).toBe("BUFFERING"); fakeProps.paused = true; expect(getLoadedContentState(mediaElement, "not-ready")).toBe("BUFFERING"); @@ -95,7 +96,7 @@ describe("API - getLoadedContentState", () => { currentTime: 5, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "seeking")).toBe("SEEKING"); fakeProps.paused = true; expect(getLoadedContentState(mediaElement, "seeking")).toBe("SEEKING"); @@ -108,7 +109,7 @@ describe("API - getLoadedContentState", () => { currentTime: 10, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "seeking")).toBe("ENDED"); expect(getLoadedContentState(mediaElement, "buffering")).toBe("ENDED"); expect(getLoadedContentState(mediaElement, "not-ready")).toBe("ENDED"); @@ -127,7 +128,7 @@ describe("API - getLoadedContentState", () => { currentTime: 10 - config.getCurrent().FORCED_ENDED_THRESHOLD, paused: false, }; - const mediaElement = fakeProps as HTMLMediaElement; + const mediaElement = fakeProps as IMediaElement; expect(getLoadedContentState(mediaElement, "seeking")).toBe("ENDED"); expect(getLoadedContentState(mediaElement, "buffering")).toBe("ENDED"); expect(getLoadedContentState(mediaElement, "not-ready")).toBe("ENDED"); @@ -145,7 +146,7 @@ describe("API - getLoadedContentState", () => { currentTime: 10 - config.getCurrent().FORCED_ENDED_THRESHOLD, paused: false, }; - const mediaElement1 = fakeProps1 as HTMLMediaElement; + const mediaElement1 = fakeProps1 as IMediaElement; expect(getLoadedContentState(mediaElement1, "seeking")).toBe("ENDED"); expect(getLoadedContentState(mediaElement1, "buffering")).toBe("ENDED"); expect(getLoadedContentState(mediaElement1, "not-ready")).toBe("ENDED"); @@ -162,7 +163,7 @@ describe("API - getLoadedContentState", () => { currentTime: config.getCurrent().FORCED_ENDED_THRESHOLD + 10, paused: false, }; - const mediaElement2 = fakeProps2 as HTMLMediaElement; + const mediaElement2 = fakeProps2 as IMediaElement; expect(getLoadedContentState(mediaElement2, "seeking")).toBe("ENDED"); expect(getLoadedContentState(mediaElement2, "buffering")).toBe("ENDED"); expect(getLoadedContentState(mediaElement2, "not-ready")).toBe("ENDED"); diff --git a/src/main_thread/api/option_utils.ts b/src/main_thread/api/option_utils.ts index da6c2921aa..4a933059bc 100644 --- a/src/main_thread/api/option_utils.ts +++ b/src/main_thread/api/option_utils.ts @@ -19,6 +19,7 @@ * throw if something is wrong, and return a normalized option object. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import config from "../../config"; import log from "../../log"; import type { @@ -57,7 +58,7 @@ export interface IParsedConstructorOptions { videoResolutionLimit: "videoElement" | "screen" | "none"; throttleVideoBitrateWhenHidden: boolean; - videoElement: HTMLMediaElement; + videoElement: IMediaElement; baseBandwidth: number; } @@ -133,7 +134,7 @@ function parseConstructorOptions( let wantedBufferAhead: number; let maxVideoBufferSize: number; - let videoElement: HTMLMediaElement; + let videoElement: IMediaElement; let baseBandwidth: number; const { @@ -194,7 +195,10 @@ function parseConstructorOptions( if (isNullOrUndefined(options.videoElement)) { videoElement = document.createElement("video"); - } else if (options.videoElement instanceof HTMLMediaElement) { + } else if ( + options.videoElement.nodeName.toLowerCase() === "video" || + options.videoElement.nodeName.toLowerCase() === "audio" + ) { videoElement = options.videoElement; } else { throw new Error("Invalid videoElement parameter. Should be a HTMLMediaElement."); diff --git a/src/main_thread/api/public_api.ts b/src/main_thread/api/public_api.ts index fba6acc743..ba65fd8ffc 100644 --- a/src/main_thread/api/public_api.ts +++ b/src/main_thread/api/public_api.ts @@ -19,6 +19,7 @@ * It also starts the different sub-parts of the player on various API calls. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import canRelyOnVideoVisibilityAndSize from "../../compat/can_rely_on_video_visibility_and_size"; import type { IPictureInPictureEvent } from "../../compat/event_listeners"; import { @@ -167,13 +168,13 @@ class Player extends EventEmitter { * This is used to check that a video element is not shared between multiple instances. * Use of a WeakSet ensure the object is garbage collected if it's not used anymore. */ - private static _priv_currentlyUsedVideoElements = new WeakSet(); + private static _priv_currentlyUsedVideoElements = new WeakSet(); /** * Media element attached to the RxPlayer. * Set to `null` when the RxPlayer is disposed. */ - public videoElement: HTMLMediaElement | null; // null on dispose + public videoElement: IMediaElement | null; // null on dispose /** Logger the RxPlayer uses. */ public readonly log: Logger; @@ -352,7 +353,7 @@ class Player extends EventEmitter { * @param videoElement the video element to register. * @throws Error - Throws if the element is already used by another player instance. */ - private static _priv_registerVideoElement(videoElement: HTMLMediaElement) { + private static _priv_registerVideoElement(videoElement: IMediaElement) { if (Player._priv_currentlyUsedVideoElements.has(videoElement)) { const errorMessage = "The video element is already attached to another RxPlayer instance." + @@ -374,7 +375,7 @@ class Player extends EventEmitter { * Deregister the video element of the set of elements currently in use. * @param videoElement the video element to deregister. */ - static _priv_deregisterVideoElement(videoElement: HTMLMediaElement) { + static _priv_deregisterVideoElement(videoElement: IMediaElement) { if (Player._priv_currentlyUsedVideoElements.has(videoElement)) { Player._priv_currentlyUsedVideoElements.delete(videoElement); } @@ -472,6 +473,7 @@ class Player extends EventEmitter { muted: videoElement.muted, }); }; + videoElement.addEventListener("volumechange", onVolumeChange); destroyCanceller.signal.register(() => { videoElement.removeEventListener("volumechange", onVolumeChange); @@ -1271,9 +1273,11 @@ class Player extends EventEmitter { * @returns {HTMLMediaElement|null} - The HTMLMediaElement used (`null` when * disposed) */ + /* eslint-disable @typescript-eslint/ban-types */ getVideoElement(): HTMLMediaElement | null { - return this.videoElement; + return this.videoElement as HTMLMediaElement; } + /* eslint-enable @typescript-eslint/ban-types */ /** * Returns the player's current state. diff --git a/src/main_thread/api/utils.ts b/src/main_thread/api/utils.ts index e9f5139e57..b9a18166ba 100644 --- a/src/main_thread/api/utils.ts +++ b/src/main_thread/api/utils.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import config from "../../config"; import type { IPlaybackObservation, @@ -40,7 +41,7 @@ import type { ContentInitializer, IStallingSituation } from "../init"; * remove all listeners this function has registered. */ export function emitSeekEvents( - mediaElement: HTMLMediaElement | null, + mediaElement: IMediaElement | null, playbackObserver: IReadOnlyPlaybackObserver, onSeeking: () => void, onSeeked: () => void, @@ -82,7 +83,7 @@ export function emitSeekEvents( * remove all listeners this function has registered. */ export function emitPlayPauseEvents( - mediaElement: HTMLMediaElement | null, + mediaElement: IMediaElement | null, onPlay: () => void, onPause: () => void, cancelSignal: CancellationSignal, @@ -114,7 +115,7 @@ export const enum PLAYER_STATES { export function constructPlayerStateReference( initializer: ContentInitializer, - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, playbackObserver: IReadOnlyPlaybackObserver, cancelSignal: CancellationSignal, ): IReadOnlySharedReference { @@ -212,7 +213,7 @@ export function constructPlayerStateReference( * @returns {string} */ export function getLoadedContentState( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, stalledStatus: IStallingSituation | null, ): IPlayerState { const { FORCED_ENDED_THRESHOLD } = config.getCurrent(); diff --git a/src/main_thread/decrypt/__tests__/__global__/utils.ts b/src/main_thread/decrypt/__tests__/__global__/utils.ts index 5c1e23fbf2..76e9b5478e 100644 --- a/src/main_thread/decrypt/__tests__/__global__/utils.ts +++ b/src/main_thread/decrypt/__tests__/__global__/utils.ts @@ -12,6 +12,7 @@ import { vi } from "vitest"; /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable no-restricted-properties */ +import type { IMediaElement } from "../../../../compat/browser_compatibility_types"; import type { IEmeApiImplementation, IEncryptedEventData } from "../../../../compat/eme"; import { base64ToBytes, bytesToBase64 } from "../../../../utils/base64"; import EventEmitter from "../../../../utils/event_emitter"; @@ -274,12 +275,12 @@ export function requestMediaKeySystemAccessImpl( } class MockedDecryptorEventEmitter extends EventEmitter<{ - encrypted: { elt: HTMLMediaElement; value: unknown }; + encrypted: { elt: IMediaElement; value: unknown }; keymessage: { session: MediaKeySessionImpl; value: unknown }; keyerror: { session: MediaKeySessionImpl; value: unknown }; keystatuseschange: { session: MediaKeySessionImpl; value: unknown }; }> { - public triggerEncrypted(elt: HTMLMediaElement, value: unknown) { + public triggerEncrypted(elt: IMediaElement, value: unknown) { this.trigger("encrypted", { elt, value }); } public triggerKeyError(session: MediaKeySessionImpl, value: unknown) { @@ -312,7 +313,7 @@ export function mockCompat( vi .fn() .mockImplementation( - (elt: HTMLMediaElement, fn: (x: unknown) => void, signal: CancellationSignal) => { + (elt: IMediaElement, fn: (x: unknown) => void, signal: CancellationSignal) => { elt.addEventListener("encrypted", fn); signal.register(() => { elt.removeEventListener("encrypted", fn); @@ -444,7 +445,7 @@ export function mockCompat( return { mockEvents, eventTriggers: { - triggerEncrypted(elt: HTMLMediaElement, value: unknown) { + triggerEncrypted(elt: IMediaElement, value: unknown) { ee.triggerEncrypted(elt, value); }, triggerKeyMessage(session: MediaKeySessionImpl, value: unknown) { @@ -475,7 +476,7 @@ export function mockCompat( */ export function testContentDecryptorError( ContentDecryptor: any, - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keySystemsConfigs: unknown[], ): Promise { return new Promise((res, rej) => { diff --git a/src/main_thread/decrypt/attach_media_keys.ts b/src/main_thread/decrypt/attach_media_keys.ts index 291964105a..efece246f1 100644 --- a/src/main_thread/decrypt/attach_media_keys.ts +++ b/src/main_thread/decrypt/attach_media_keys.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import type { ICustomMediaKeys, ICustomMediaKeySystemAccess, @@ -34,7 +35,7 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store"; * @param {Object} mediaElement * @returns {Promise} */ -export function disableMediaKeys(mediaElement: HTMLMediaElement): Promise { +export function disableMediaKeys(mediaElement: IMediaElement): Promise { const previousState = MediaKeysInfosStore.getState(mediaElement); MediaKeysInfosStore.setState(mediaElement, null); return setMediaKeys(previousState?.emeImplementation ?? eme, mediaElement, null); @@ -50,7 +51,7 @@ export function disableMediaKeys(mediaElement: HTMLMediaElement): Promise { +export default function clearOnStop(mediaElement: IMediaElement): Promise { log.info("DRM: Clearing-up DRM session."); if (shouldUnsetMediaKeys()) { log.info("DRM: disposing current MediaKeys."); diff --git a/src/main_thread/decrypt/content_decryptor.ts b/src/main_thread/decrypt/content_decryptor.ts index 5aed88295b..1c8e186781 100644 --- a/src/main_thread/decrypt/content_decryptor.ts +++ b/src/main_thread/decrypt/content_decryptor.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import type { ICustomMediaKeys, ICustomMediaKeySystemAccess } from "../../compat/eme"; import eme, { getInitData } from "../../compat/eme"; import config from "../../config"; @@ -147,7 +148,7 @@ export default class ContentDecryptor extends EventEmitter; /** @@ -1185,7 +1186,7 @@ type IReadyForContentStateDataUnattached = IContentDecryptorStateBase< ContentDecryptorState.ReadyForContent, true, // isInitDataQueueLocked MediaKeyAttachmentStatus.NotAttached | MediaKeyAttachmentStatus.Pending, // isMediaKeysAttached - { mediaKeysInfo: IMediaKeysInfos; mediaElement: HTMLMediaElement } // data + { mediaKeysInfo: IMediaKeysInfos; mediaElement: IMediaElement } // data >; /** diff --git a/src/main_thread/decrypt/dispose_decryption_resources.ts b/src/main_thread/decrypt/dispose_decryption_resources.ts index 20e1eb7503..e6fd8fd07c 100644 --- a/src/main_thread/decrypt/dispose_decryption_resources.ts +++ b/src/main_thread/decrypt/dispose_decryption_resources.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import { setMediaKeys } from "../../compat/eme/set_media_keys"; import log from "../../log"; import MediaKeysInfosStore from "./utils/media_keys_infos_store"; @@ -24,7 +25,7 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store"; * @returns {Promise} */ export default async function disposeDecryptionResources( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, ): Promise { const currentState = MediaKeysInfosStore.getState(mediaElement); if (currentState === null) { diff --git a/src/main_thread/decrypt/find_key_system.ts b/src/main_thread/decrypt/find_key_system.ts index 46051498de..65643176d3 100644 --- a/src/main_thread/decrypt/find_key_system.ts +++ b/src/main_thread/decrypt/find_key_system.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import { canRelyOnRequestMediaKeySystemAccess } from "../../compat/can_rely_on_request_media_key_system_access"; import type { ICustomMediaKeySystemAccess } from "../../compat/eme"; import eme from "../../compat/eme"; @@ -283,7 +284,7 @@ function buildKeySystemConfigurations( * @returns {Promise.} */ export default function getMediaKeySystemAccess( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keySystemsConfigs: IKeySystemOption[], cancelSignal: CancellationSignal, ): Promise { diff --git a/src/main_thread/decrypt/get_key_system_configuration.ts b/src/main_thread/decrypt/get_key_system_configuration.ts index b6b1be3cf3..ba22c8f2fc 100644 --- a/src/main_thread/decrypt/get_key_system_configuration.ts +++ b/src/main_thread/decrypt/get_key_system_configuration.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import MediaKeysInfosStore from "./utils/media_keys_infos_store"; /** @@ -23,7 +24,7 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store"; * @returns {Array|null} */ export default function getKeySystemConfiguration( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, ): [string, MediaKeySystemConfiguration] | null { const currentState = MediaKeysInfosStore.getState(mediaElement); if (currentState === null) { diff --git a/src/main_thread/decrypt/get_media_keys.ts b/src/main_thread/decrypt/get_media_keys.ts index 64ab94cfec..1708ab9ddd 100644 --- a/src/main_thread/decrypt/get_media_keys.ts +++ b/src/main_thread/decrypt/get_media_keys.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import canReuseMediaKeys from "../../compat/can_reuse_media_keys"; import type { ICustomMediaKeys, ICustomMediaKeySystemAccess } from "../../compat/eme"; import { EncryptedMediaError } from "../../errors"; @@ -71,7 +72,7 @@ export interface IMediaKeysInfos { * @returns {Promise.} */ export default async function getMediaKeysInfos( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keySystemsConfigs: IKeySystemOption[], cancelSignal: CancellationSignal, ): Promise { diff --git a/src/main_thread/decrypt/init_media_keys.ts b/src/main_thread/decrypt/init_media_keys.ts index c9c326f18e..46875e5fc8 100644 --- a/src/main_thread/decrypt/init_media_keys.ts +++ b/src/main_thread/decrypt/init_media_keys.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import log from "../../log"; import type { IKeySystemOption } from "../../public_types"; import type { CancellationSignal } from "../../utils/task_canceller"; @@ -29,7 +30,7 @@ import getMediaKeysInfos from "./get_media_keys"; * @returns {Promise.} */ export default async function initMediaKeys( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keySystemsConfigs: IKeySystemOption[], cancelSignal: CancellationSignal, ): Promise { diff --git a/src/main_thread/decrypt/utils/media_keys_infos_store.ts b/src/main_thread/decrypt/utils/media_keys_infos_store.ts index 39143e6dac..fb4fb5b5f8 100644 --- a/src/main_thread/decrypt/utils/media_keys_infos_store.ts +++ b/src/main_thread/decrypt/utils/media_keys_infos_store.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import type { ICustomMediaKeys, ICustomMediaKeySystemAccess, @@ -47,7 +48,7 @@ export interface IMediaElementMediaKeysInfos { // Store the MediaKeys infos attached to a media element. const currentMediaState = new WeakMap< - HTMLMediaElement, + IMediaElement, IMediaElementMediaKeysInfos | null >(); @@ -57,10 +58,7 @@ export default { * @param {HTMLMediaElement} mediaElement * @param {Object} state */ - setState( - mediaElement: HTMLMediaElement, - state: IMediaElementMediaKeysInfos | null, - ): void { + setState(mediaElement: IMediaElement, state: IMediaElementMediaKeysInfos | null): void { currentMediaState.set(mediaElement, state); }, @@ -69,7 +67,7 @@ export default { * @param {HTMLMediaElement} mediaElement * @returns {Object} */ - getState(mediaElement: HTMLMediaElement): IMediaElementMediaKeysInfos | null { + getState(mediaElement: IMediaElement): IMediaElementMediaKeysInfos | null { const currentState = currentMediaState.get(mediaElement); return currentState === undefined ? null : currentState; }, @@ -78,7 +76,7 @@ export default { * Remove MediaKeys infos currently set on a HMTLMediaElement * @param {HTMLMediaElement} mediaElement */ - clearState(mediaElement: HTMLMediaElement): void { + clearState(mediaElement: IMediaElement): void { currentMediaState.set(mediaElement, null); }, }; diff --git a/src/main_thread/init/directfile_content_initializer.ts b/src/main_thread/init/directfile_content_initializer.ts index 7975daad91..e6219f2bcb 100644 --- a/src/main_thread/init/directfile_content_initializer.ts +++ b/src/main_thread/init/directfile_content_initializer.ts @@ -19,6 +19,7 @@ * It always should be imported through the `features` object. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import clearElementSrc from "../../compat/clear_element_src"; import type { MediaError } from "../../errors"; import log from "../../log"; @@ -85,7 +86,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { * information. */ public start( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, playbackObserver: IMediaElementPlaybackObserver, ): void { const cancelSignal = this._initCanceller.signal; @@ -210,7 +211,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { * @param {Object} playbackObserver */ private _seekAndPlay( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, playbackObserver: IMediaElementPlaybackObserver, ): void { const cancelSignal = this._initCanceller.signal; @@ -260,7 +261,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { * @returns {number} */ function getDirectFileInitialTime( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, startAt?: IInitialTimeOptions, ): number { if (isNullOrUndefined(startAt)) { diff --git a/src/main_thread/init/media_source_content_initializer.ts b/src/main_thread/init/media_source_content_initializer.ts index 530f067d08..6032969705 100644 --- a/src/main_thread/init/media_source_content_initializer.ts +++ b/src/main_thread/init/media_source_content_initializer.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data"; import shouldReloadMediaSourceOnDecipherabilityUpdate from "../../compat/should_reload_media_source_on_decipherability_update"; import config from "../../config"; @@ -159,7 +160,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { * @param {Object} playbackObserver */ public start( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, playbackObserver: IMediaElementPlaybackObserver, ): void { this.prepare(); // Load Manifest if not already done @@ -217,7 +218,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { } private _initializeMediaSourceAndDecryption( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, protectionRef: IReadOnlySharedReference, ): Promise<{ mediaSource: MainMediaSourceInterface; @@ -317,7 +318,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { } private async _onInitialMediaSourceReady( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, initialMediaSource: MainMediaSourceInterface, playbackObserver: IMediaElementPlaybackObserver, drmSystemId: string | undefined, @@ -1078,7 +1079,7 @@ interface IBufferingMediaSettings { /* Manifest of the content we want to play. */ manifest: IManifest; /** Media Element on which the content will be played. */ - mediaElement: HTMLMediaElement; + mediaElement: IMediaElement; /** Emit playback conditions regularly. */ playbackObserver: IMediaElementPlaybackObserver; /** Estimate the right Representation. */ diff --git a/src/main_thread/init/multi_thread_content_initializer.ts b/src/main_thread/init/multi_thread_content_initializer.ts index 5ef53e81c9..00d6f8d403 100644 --- a/src/main_thread/init/multi_thread_content_initializer.ts +++ b/src/main_thread/init/multi_thread_content_initializer.ts @@ -1,3 +1,4 @@ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import isCodecSupported from "../../compat/is_codec_supported"; import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data"; import shouldReloadMediaSourceOnDecipherabilityUpdate from "../../compat/should_reload_media_source_on_decipherability_update"; @@ -229,7 +230,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { * @param {Object} playbackObserver */ public start( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, playbackObserver: IMediaElementPlaybackObserver, ): void { this.prepare(); // Load Manifest if not already done @@ -1134,7 +1135,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { } private _initializeContentDecryption( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, lastContentProtection: IReadOnlySharedReference, mediaSourceStatus: SharedReference, reloadMediaSource: () => void, @@ -1299,7 +1300,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { } private _reload( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, textDisplayer: ITextDisplayer | null, playbackObserver: IMediaElementPlaybackObserver, mediaSourceStatus: SharedReference, @@ -1377,7 +1378,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { parameters: { initialTime: number; autoPlay: boolean; - mediaElement: HTMLMediaElement; + mediaElement: IMediaElement; textDisplayer: ITextDisplayer | null; playbackObserver: IMediaElementPlaybackObserver; }, @@ -1553,7 +1554,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { * playback start. */ private _startPlaybackIfReady(parameters: { - mediaElement: HTMLMediaElement; + mediaElement: IMediaElement; textDisplayer: ITextDisplayer | null; playbackObserver: IMediaElementPlaybackObserver; drmInitializationStatus: IReadOnlySharedReference; @@ -1637,7 +1638,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { */ private _onCreateMediaSourceMessage( msg: ICreateMediaSourceWorkerMessage, - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, mediaSourceStatus: SharedReference, worker: Worker, ): void { diff --git a/src/main_thread/init/types.ts b/src/main_thread/init/types.ts index 6b1d1f0567..1adee1d9f0 100644 --- a/src/main_thread/init/types.ts +++ b/src/main_thread/init/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../compat/browser_compatibility_types"; import type { ISegmentSinkMetrics } from "../../core/segment_sinks/segment_buffers_store"; import type { IBufferType, IAdaptationChoice, IInbandEvent } from "../../core/types"; import type { @@ -79,7 +80,7 @@ export abstract class ContentInitializer extends EventEmitter { return createCancellablePromise(unlinkSignal, (resolve) => { diff --git a/src/main_thread/init/utils/get_loaded_reference.ts b/src/main_thread/init/utils/get_loaded_reference.ts index 474cbaddea..b423a4ae0d 100644 --- a/src/main_thread/init/utils/get_loaded_reference.ts +++ b/src/main_thread/init/utils/get_loaded_reference.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import shouldValidateMetadata from "../../../compat/should_validate_metadata"; import shouldWaitForDataBeforeLoaded from "../../../compat/should_wait_for_data_before_loaded"; import shouldWaitForHaveEnoughData from "../../../compat/should_wait_for_have_enough_data"; @@ -37,7 +38,7 @@ import TaskCanceller from "../../../utils/task_canceller"; */ export default function getLoadedReference( playbackObserver: IReadOnlyPlaybackObserver, - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, isDirectfile: boolean, cancelSignal: CancellationSignal, ): IReadOnlySharedReference { diff --git a/src/main_thread/init/utils/initial_seek_and_play.ts b/src/main_thread/init/utils/initial_seek_and_play.ts index d1f5021141..c485c20890 100644 --- a/src/main_thread/init/utils/initial_seek_and_play.ts +++ b/src/main_thread/init/utils/initial_seek_and_play.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import canSeekDirectlyAfterLoadedMetadata from "../../../compat/can_seek_directly_after_loaded_metadata"; import shouldValidateMetadata from "../../../compat/should_validate_metadata"; import { MediaError } from "../../../errors"; @@ -68,7 +69,7 @@ export default function performInitialSeekAndPlay( isDirectfile, onWarning, }: { - mediaElement: HTMLMediaElement; + mediaElement: IMediaElement; playbackObserver: IMediaElementPlaybackObserver; startTime: number | (() => number); mustAutoPlay: boolean; diff --git a/src/main_thread/init/utils/initialize_content_decryption.ts b/src/main_thread/init/utils/initialize_content_decryption.ts index 11edb49ce1..8727171f1f 100644 --- a/src/main_thread/init/utils/initialize_content_decryption.ts +++ b/src/main_thread/init/utils/initialize_content_decryption.ts @@ -1,3 +1,4 @@ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import { EncryptedMediaError } from "../../../errors"; import features from "../../../features"; import log from "../../../log"; @@ -33,7 +34,7 @@ import type { IContentProtection, IProcessedProtectionData } from "../../decrypt * initialization. */ export default function initializeContentDecryption( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, keySystems: IKeySystemOption[], protectionRef: IReadOnlySharedReference, callbacks: { diff --git a/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts b/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts index 1f0ac0df92..fcc60715a7 100644 --- a/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts +++ b/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../../compat/browser_compatibility_types"; import config from "../../../../config"; import type { IManifestMetadata } from "../../../../manifest"; import { SeekingState } from "../../../../playback_observer"; @@ -43,7 +44,7 @@ interface IStreamEventsEmitterEvent { */ export default class StreamEventsEmitter extends EventEmitter { private _manifest: IManifestMetadata; - private _mediaElement: HTMLMediaElement; + private _mediaElement: IMediaElement; private _playbackObserver: IReadOnlyPlaybackObserver; private _scheduledEventsRef: SharedReference< Array @@ -61,7 +62,7 @@ export default class StreamEventsEmitter extends EventEmitter, ) { super(); diff --git a/src/main_thread/init/utils/throw_on_media_error.ts b/src/main_thread/init/utils/throw_on_media_error.ts index c0a7703d0a..a245fd657d 100644 --- a/src/main_thread/init/utils/throw_on_media_error.ts +++ b/src/main_thread/init/utils/throw_on_media_error.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import { MediaError } from "../../../errors"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import type { CancellationSignal } from "../../../utils/task_canceller"; @@ -24,7 +25,7 @@ import type { CancellationSignal } from "../../../utils/task_canceller"; * @param {Object} cancelSignal */ export default function listenToMediaError( - mediaElement: HTMLMediaElement, + mediaElement: IMediaElement, onError: (error: MediaError) => void, cancelSignal: CancellationSignal, ): void { @@ -33,7 +34,6 @@ export default function listenToMediaError( } mediaElement.addEventListener("error", onMediaError); - cancelSignal.register(() => { mediaElement.removeEventListener("error", onMediaError); }); diff --git a/src/main_thread/text_displayer/html/html_text_displayer.ts b/src/main_thread/text_displayer/html/html_text_displayer.ts index b4e7b49ede..40c011dc78 100644 --- a/src/main_thread/text_displayer/html/html_text_displayer.ts +++ b/src/main_thread/text_displayer/html/html_text_displayer.ts @@ -1,3 +1,4 @@ +import type { IMediaElement } from "../../../compat/browser_compatibility_types"; import { onEnded, onSeeked, onSeeking } from "../../../compat/event_listeners"; import onHeightWidthChange from "../../../compat/on_height_width_change"; import config from "../../../config"; @@ -55,7 +56,7 @@ export default class HTMLTextDisplayer implements ITextDisplayer { * The video element the cues refer to. * Used to know when the user is seeking, for example. */ - private readonly _videoElement: HTMLMediaElement; + private readonly _videoElement: IMediaElement; /** Allows to cancel the interval at which subtitles are updated. */ private _subtitlesIntervalCanceller: TaskCanceller; @@ -100,7 +101,7 @@ export default class HTMLTextDisplayer implements ITextDisplayer { * @param {HTMLMediaElement} videoElement * @param {HTMLElement} textTrackElement */ - constructor(videoElement: HTMLMediaElement, textTrackElement: HTMLElement) { + constructor(videoElement: IMediaElement, textTrackElement: HTMLElement) { log.debug("HTD: Creating HTMLTextDisplayer"); this._buffered = new ManualTimeRanges(); diff --git a/src/main_thread/text_displayer/native/native_text_displayer.ts b/src/main_thread/text_displayer/native/native_text_displayer.ts index 18dfbf4866..cdb397eb05 100644 --- a/src/main_thread/text_displayer/native/native_text_displayer.ts +++ b/src/main_thread/text_displayer/native/native_text_displayer.ts @@ -1,5 +1,8 @@ import addTextTrack from "../../../compat/add_text_track"; -import type { ICompatTextTrack } from "../../../compat/browser_compatibility_types"; +import type { + ICompatTextTrack, + IMediaElement, +} from "../../../compat/browser_compatibility_types"; import removeCue from "../../../compat/remove_cue"; import log from "../../../log"; import type { ITextTrackSegmentData } from "../../../transports"; @@ -16,7 +19,7 @@ import parseTextTrackToCues from "./native_parsers"; * @class NativeTextDisplayer */ export default class NativeTextDisplayer implements ITextDisplayer { - private readonly _videoElement: HTMLMediaElement; + private readonly _videoElement: IMediaElement; private readonly _track: ICompatTextTrack; private readonly _trackElement: HTMLTrackElement | undefined; @@ -25,7 +28,7 @@ export default class NativeTextDisplayer implements ITextDisplayer { /** * @param {HTMLMediaElement} videoElement */ - constructor(videoElement: HTMLMediaElement) { + constructor(videoElement: IMediaElement) { log.debug("NTD: Creating NativeTextDisplayer"); const { track, trackElement } = addTextTrack(videoElement); this._buffered = new ManualTimeRanges(); diff --git a/src/main_thread/tracks_store/media_element_tracks_store.ts b/src/main_thread/tracks_store/media_element_tracks_store.ts index f4d7dbe0da..0eaf829fee 100644 --- a/src/main_thread/tracks_store/media_element_tracks_store.ts +++ b/src/main_thread/tracks_store/media_element_tracks_store.ts @@ -22,10 +22,10 @@ import type { ICompatAudioTrack, ICompatAudioTrackList, - ICompatHTMLMediaElement, ICompatTextTrackList, ICompatVideoTrack, ICompatVideoTrackList, + IMediaElement, } from "../../compat/browser_compatibility_types"; import enableAudioTrack from "../../compat/enable_audio_track"; import type { IRepresentation } from "../../manifest"; @@ -236,13 +236,13 @@ export default class MediaElementTracksStore extends EventEmitter { - sourceBuffer.removeEventListener("error", onError); sourceBuffer.removeEventListener("updateend", onUpdateEnd); + sourceBuffer.removeEventListener("error", onError); }); } @@ -543,7 +546,7 @@ export class MainSourceBufferInterface implements ISourceBufferInterface { } } -function resetMediaSource(mediaSource: MediaSource): void { +function resetMediaSource(mediaSource: IMediaSource): void { if (mediaSource.readyState !== "closed") { const { readyState, sourceBuffers } = mediaSource; for (let i = sourceBuffers.length - 1; i >= 0; i--) { diff --git a/src/mse/types.ts b/src/mse/types.ts index 9bda5b956b..744a42383f 100644 --- a/src/mse/types.ts +++ b/src/mse/types.ts @@ -163,6 +163,7 @@ export type IMediaSourceHandle = * Do not forget to revoke such URL (e.g. through `URL.revokeObjectURL`) when * you're done. */ + /* eslint-disable-next-line @typescript-eslint/ban-types */ value: MediaSource; }; diff --git a/src/mse/utils/end_of_stream.ts b/src/mse/utils/end_of_stream.ts index 03c75f0cf5..713a793ef3 100644 --- a/src/mse/utils/end_of_stream.ts +++ b/src/mse/utils/end_of_stream.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +import type { + IMediaSource, + ISourceBuffer, + ISourceBufferList, +} from "../../compat/browser_compatibility_types"; import { onRemoveSourceBuffers, onSourceOpen, @@ -28,8 +33,8 @@ import TaskCanceller from "../../utils/task_canceller"; * @param {SourceBufferList} sourceBuffers * @returns {Array.} */ -function getUpdatingSourceBuffers(sourceBuffers: SourceBufferList): SourceBuffer[] { - const updatingSourceBuffers: SourceBuffer[] = []; +function getUpdatingSourceBuffers(sourceBuffers: ISourceBufferList): ISourceBuffer[] { + const updatingSourceBuffers: ISourceBuffer[] = []; for (let i = 0; i < sourceBuffers.length; i++) { const SourceBuffer = sourceBuffers[i]; if (SourceBuffer.updating) { @@ -49,7 +54,7 @@ function getUpdatingSourceBuffers(sourceBuffers: SourceBufferList): SourceBuffer * @param {Object} cancelSignal */ export default function triggerEndOfStream( - mediaSource: MediaSource, + mediaSource: IMediaSource, cancelSignal: CancellationSignal, ): void { log.debug("Init: Trying to call endOfStream"); @@ -106,7 +111,7 @@ export default function triggerEndOfStream( * @param {Object} cancelSignal */ export function maintainEndOfStream( - mediaSource: MediaSource, + mediaSource: IMediaSource, cancelSignal: CancellationSignal, ): void { let endOfStreamCanceller = new TaskCanceller(); diff --git a/src/mse/utils/media_source_duration_updater.ts b/src/mse/utils/media_source_duration_updater.ts index 80e0ba44d1..0463ae33b8 100644 --- a/src/mse/utils/media_source_duration_updater.ts +++ b/src/mse/utils/media_source_duration_updater.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import type { + IMediaSource, + ISourceBufferList, +} from "../../compat/browser_compatibility_types"; import { onSourceOpen, onSourceEnded, onSourceClose } from "../../compat/event_listeners"; import hasIssuesWithHighMediaSourceDuration from "../../compat/has_issues_with_high_media_source_duration"; import log from "../../log"; @@ -34,7 +38,7 @@ export default class MediaSourceDurationUpdater { /** * `MediaSource` on which we're going to update the `duration` attribute. */ - private _mediaSource: MediaSource; + private _mediaSource: IMediaSource; /** * Abort the current duration-setting logic. @@ -47,7 +51,7 @@ export default class MediaSourceDurationUpdater { * @param {MediaSource} mediaSource - The MediaSource on which the content is * played. */ - constructor(mediaSource: MediaSource) { + constructor(mediaSource: IMediaSource) { this._mediaSource = mediaSource; this._currentMediaSourceDurationUpdateCanceller = null; } @@ -150,7 +154,7 @@ export default class MediaSourceDurationUpdater { * @returns {string} */ function setMediaSourceDuration( - mediaSource: MediaSource, + mediaSource: IMediaSource, duration: number, isRealEndKnown: boolean, ): MediaSourceDurationUpdateStatus { @@ -245,7 +249,7 @@ const enum MediaSourceDurationUpdateStatus { * @returns {Object} */ function createSourceBuffersUpdatingReference( - sourceBuffers: SourceBufferList, + sourceBuffers: ISourceBufferList, cancelSignal: CancellationSignal, ): IReadOnlySharedReference { if (sourceBuffers.length === 0) { @@ -289,7 +293,7 @@ function createSourceBuffersUpdatingReference( * @returns {Object} */ function createMediaSourceOpenReference( - mediaSource: MediaSource, + mediaSource: IMediaSource, cancelSignal: CancellationSignal, ): IReadOnlySharedReference { const isMediaSourceOpen = new SharedReference( @@ -335,7 +339,7 @@ function createMediaSourceOpenReference( * @param {Object} cancelSignal */ function recursivelyForceDurationUpdate( - mediaSource: MediaSource, + mediaSource: IMediaSource, duration: number, isRealEndKnown: boolean, cancelSignal: CancellationSignal, diff --git a/src/playback_observer/media_element_playback_observer.ts b/src/playback_observer/media_element_playback_observer.ts index 712a061994..0dca709b49 100644 --- a/src/playback_observer/media_element_playback_observer.ts +++ b/src/playback_observer/media_element_playback_observer.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IMediaElement } from "../compat/browser_compatibility_types"; import isSeekingApproximate from "../compat/is_seeking_approximate"; import config from "../config"; import log from "../log"; @@ -40,9 +41,8 @@ import ObservationPosition from "./utils/observation_position"; /** * HTMLMediaElement Events for which playback observations are calculated and * emitted. - * @type {Array.} */ -const SCANNED_MEDIA_ELEMENTS_EVENTS: IPlaybackObserverEventType[] = [ +const SCANNED_MEDIA_ELEMENTS_EVENTS = [ "canplay", "ended", "play", @@ -51,7 +51,7 @@ const SCANNED_MEDIA_ELEMENTS_EVENTS: IPlaybackObserverEventType[] = [ "seeked", "loadedmetadata", "ratechange", -]; +] as const; /** * Class allowing to "observe" current playback conditions so the RxPlayer is @@ -68,7 +68,7 @@ const SCANNED_MEDIA_ELEMENTS_EVENTS: IPlaybackObserverEventType[] = [ */ export default class PlaybackObserver { /** HTMLMediaElement which we want to observe. */ - private _mediaElement: HTMLMediaElement; + private _mediaElement: IMediaElement; /** If `true`, a `MediaSource` object is linked to `_mediaElement`. */ private _withMediaSource: boolean; @@ -141,7 +141,7 @@ export default class PlaybackObserver { * @param {HTMLMediaElement} mediaElement * @param {Object} options */ - constructor(mediaElement: HTMLMediaElement, options: IPlaybackObserverOptions) { + constructor(mediaElement: IMediaElement, options: IPlaybackObserverOptions) { this._internalSeeksIncoming = []; this._mediaElement = mediaElement; this._withMediaSource = options.withMediaSource; @@ -342,19 +342,19 @@ export default class PlaybackObserver { this._generateObservationForEvent("timeupdate"); }; let intervalId = setInterval(onInterval, interval); - const removeEventListeners = SCANNED_MEDIA_ELEMENTS_EVENTS.map((eventName) => { + SCANNED_MEDIA_ELEMENTS_EVENTS.map((eventName) => { const onMediaEvent = () => { restartInterval(); this._generateObservationForEvent(eventName); }; + this._mediaElement.addEventListener(eventName, onMediaEvent); - return () => { + this._canceller.signal.register(() => { this._mediaElement.removeEventListener(eventName, onMediaEvent); - }; + }); }); this._canceller.signal.register(() => { clearInterval(intervalId); - removeEventListeners.forEach((cb) => cb()); returnedSharedReference.finish(); }); return returnedSharedReference; @@ -610,7 +610,7 @@ function hasLoadedUntilTheEnd( * @param {HTMLMediaElement} mediaElement * @returns {Object} */ -function getMediaInfos(mediaElement: HTMLMediaElement): IMediaInfos { +function getMediaInfos(mediaElement: IMediaElement): IMediaInfos { const { buffered, currentTime, @@ -933,7 +933,7 @@ function prettyPrintBuffered(buffered: TimeRanges, currentTime: number): string * @param {HTMLMediaElement} mediaElement * @returns {Object} */ -function getInitialObservation(mediaElement: HTMLMediaElement): IPlaybackObservation { +function getInitialObservation(mediaElement: IMediaElement): IPlaybackObservation { const mediaTimings = getMediaInfos(mediaElement); return objectAssign(mediaTimings, { rebuffering: null, diff --git a/src/public_types.ts b/src/public_types.ts index d9453b19ce..4e456473bd 100644 --- a/src/public_types.ts +++ b/src/public_types.ts @@ -58,6 +58,7 @@ export interface IConstructorOptions { videoResolutionLimit?: "videoElement" | "screen" | "none"; throttleVideoBitrateWhenHidden?: boolean; + /* eslint-disable-next-line @typescript-eslint/ban-types */ videoElement?: HTMLMediaElement; baseBandwidth?: number; } diff --git a/src/tools/TextTrackRenderer/text_track_renderer.ts b/src/tools/TextTrackRenderer/text_track_renderer.ts index cc64c3055f..b744ba3927 100644 --- a/src/tools/TextTrackRenderer/text_track_renderer.ts +++ b/src/tools/TextTrackRenderer/text_track_renderer.ts @@ -60,6 +60,7 @@ export default class TextTrackRenderer { videoElement, textTrackElement, }: { + /* eslint-disable-next-line @typescript-eslint/ban-types */ videoElement: HTMLMediaElement; textTrackElement: HTMLElement; }) {