Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Forbid usage of the MediaKeys type and other EME TS types #1477

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
},
],
Expand Down
87 changes: 45 additions & 42 deletions src/compat/browser_compatibility_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -191,18 +185,22 @@ export interface ISourceBuffer extends IEventTarget<ISourceBufferEventMap> {
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;
Expand All @@ -214,7 +212,7 @@ export interface IMediaElementEventMap {
visibilitychange: Event;
volumechange: Event;
waiting: Event;
webkitneedkey: MediaEncryptedEvent;
webkitneedkey: IMediaEncryptedEvent;
}

/**
Expand All @@ -241,7 +239,7 @@ export interface IMediaElement extends IEventTarget<IMediaElementEventMap> {
duration: number;
ended: boolean;
error: MediaError | null;
mediaKeys: null | MediaKeys;
mediaKeys: null | IMediaKeys;
muted: boolean;
nodeName: string;
paused: boolean;
Expand All @@ -264,9 +262,9 @@ export interface IMediaElement extends IEventTarget<IMediaElementEventMap> {
play(): Promise<void>;
removeAttribute(attr: string): void;
removeChild(x: unknown): void;
setMediaKeys(x: MediaKeys | null): Promise<void>;
setMediaKeys(x: IMediaKeys | null): Promise<void>;

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;
Expand Down Expand Up @@ -296,45 +294,51 @@ export interface IMediaElement extends IEventTarget<IMediaElementEventMap> {
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;
}

// @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) {
assertCompatibleIMediaElement(x);
}
function assertCompatibleIMediaElement(_x: IMediaElement) {
// 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 testMediaSource(x: MediaSource) {
assertCompatibleIMediaSource(x);
export interface IMediaKeySystemAccess {
readonly keySystem: string;
getConfiguration(): MediaKeySystemConfiguration;
createMediaKeys(): Promise<IMediaKeys>;
}
function assertCompatibleIMediaSource(_x: IMediaSource) {
// 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 testSourceBuffer(x: SourceBuffer) {
assertCompatibleISourceBuffer(x);
}
function assertCompatibleISourceBuffer(_x: ISourceBuffer) {
// Noop

export interface IMediaKeys {
isTypeSupported?: (type: string) => boolean; // IE11 only
createSession(sessionType?: MediaKeySessionType): IMediaKeySession;
setServerCertificate(serverCertificate: BufferSource): Promise<boolean>;
}
// @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 testSourceBufferList(x: SourceBufferList) {
assertCompatibleISourceBufferList(x);

export interface IMediaKeySession extends IEventTarget<MediaKeySessionEventMap> {
readonly closed: Promise<MediaKeySessionClosedReason>;
readonly expiration: number;
readonly keyStatuses: MediaKeyStatusMap;
readonly sessionId: string;
close(): Promise<void>;
generateRequest(_initDataType: string, _initData: BufferSource): Promise<void>;
load(sessionId: string): Promise<boolean>;
remove(): Promise<void>;
update(response: BufferSource): Promise<void>;
}
function assertCompatibleISourceBufferList(_x: ISourceBufferList) {
// Noop

// Trick to ensure our own types are compatible to TypeScript's
function assertTypeCompatibility<T, _U extends T>(): void {
// noop
}

/* eslint-disable @typescript-eslint/no-restricted-types */
assertTypeCompatibility<IMediaElement, HTMLMediaElement>();
assertTypeCompatibility<IMediaSource, MediaSource>();
assertTypeCompatibility<ISourceBuffer, SourceBuffer>();
assertTypeCompatibility<ISourceBufferList, SourceBufferList>();
assertTypeCompatibility<IMediaKeySystemAccess, MediaKeySystemAccess>();
assertTypeCompatibility<IMediaKeys, MediaKeys>();
assertTypeCompatibility<IMediaKeySession, MediaKeySession>();
/* eslint-enable @typescript-eslint/no-restricted-types */

/**
* AudioTrackList implementation (that TS forgot).
* Directly taken from the WHATG spec:
Expand Down Expand Up @@ -446,7 +450,6 @@ export type {
ICompatVideoTrackList,
ICompatAudioTrack,
ICompatVideoTrack,
ICompatMediaKeysConstructor,
ICompatTextTrack,
ICompatVTTCue,
ICompatVTTCueConstructor,
Expand Down
6 changes: 2 additions & 4 deletions src/compat/eme/close_session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,9 +32,7 @@ import type { ICustomMediaKeySession } from "./custom_media_keys";
* @param {MediaKeySession|Object} session
* @returns {Promise.<undefined>}
*/
export default function closeSession(
session: MediaKeySession | ICustomMediaKeySession,
): Promise<void> {
export default function closeSession(session: IMediaKeySession): Promise<void> {
const timeoutCanceller = new TaskCanceller();

return Promise.race([
Expand Down
15 changes: 4 additions & 11 deletions src/compat/eme/custom_key_system_access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,15 @@
* 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<MediaKeys | ICustomMediaKeys>;
}
import type { IMediaKeySystemAccess, IMediaKeys } from "../browser_compatibility_types";

/**
* Simple implementation of the MediaKeySystemAccess EME API.
*
* 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").
Expand All @@ -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,
) {}

Expand All @@ -54,7 +47,7 @@ export default class CustomMediaKeySystemAccess implements ICustomMediaKeySystem
* @returns {Promise.<Object>} - Promise wrapping the MediaKeys for this
* MediaKeySystemAccess. Never rejects.
*/
public createMediaKeys(): Promise<ICustomMediaKeys | MediaKeys> {
public createMediaKeys(): Promise<IMediaKeys> {
return new Promise((res) => res(this._mediaKeys));
}

Expand Down
52 changes: 29 additions & 23 deletions src/compat/eme/custom_media_keys/ie11_media_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMediaKeySessionEvents>
implements ICustomMediaKeySession
extends EventEmitter<MediaKeySessionEventMap>
Florent-Bouisset marked this conversation as resolved.
Show resolved Hide resolved
implements IMediaKeySession
{
public readonly update: (license: Uint8Array) => Promise<void>;
public readonly closed: Promise<void>;
public readonly closed: Promise<MediaKeySessionClosedReason>;
public expiration: number;
public keyStatuses: ICustomMediaKeyStatusMap;
public keyStatuses: MediaKeyStatusMap;
private readonly _mk: MSMediaKeys;
private readonly _sessionClosingCanceller: TaskCanceller;
private _ss: MSMediaKeySession | undefined;
Expand All @@ -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) => {
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -123,7 +132,7 @@ class IE11MediaKeySession
}
}

class IE11CustomMediaKeys implements ICustomMediaKeys {
class IE11CustomMediaKeys implements IMediaKeys {
private _videoElement?: IMediaElement;
private _mediaKeys?: MSMediaKeys;

Expand All @@ -143,25 +152,22 @@ 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<void> {
setServerCertificate(): Promise<boolean> {
throw new Error("Server certificate is not implemented in your browser");
}
}

export default function getIE11MediaKeysCallbacks(): {
isTypeSupported: (keyType: string) => boolean;
createCustomMediaKeys: (keyType: string) => IE11CustomMediaKeys;
setMediaKeys: (
elt: IMediaElement,
mediaKeys: MediaKeys | ICustomMediaKeys | null,
) => Promise<unknown>;
setMediaKeys: (elt: IMediaElement, mediaKeys: IMediaKeys | null) => Promise<unknown>;
} {
const isTypeSupported = (keySystem: string, type?: string | null) => {
if (MSMediaKeysConstructor === undefined) {
Expand All @@ -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<unknown> => {
if (mediaKeys === null) {
// msSetMediaKeys only accepts native MSMediaKeys as argument.
Expand Down
2 changes: 0 additions & 2 deletions src/compat/eme/custom_media_keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading