refactor(web): split events by platform and isolate DOM types

This commit is contained in:
Kamil Moskała
2026-03-25 13:23:02 +01:00
parent 1f2ee5f06f
commit 8ee2340819
7 changed files with 186 additions and 171 deletions
+1 -1
View File
@@ -84,4 +84,4 @@ android/keystores/debug.keystore
lib/
# TypeScript
tsconfig.tsbuildinfo
*.tsbuildinfo
+1 -1
View File
@@ -16,7 +16,7 @@
"allowUnusedLabels": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ESNext", "dom"],
"lib": ["ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
@@ -18,8 +18,6 @@ import { WebEventEmitter } from "./web/WebEventEmitter";
import type { VideoTrack } from "./types/VideoTrack";
import type { QualityLevel } from "./types/QualityLevel";
import type { SupportedFeatures } from "./types/SupportedFeatures";
import type { ListenerSubscription } from "./types/EventEmitter";
import type { AllPlayerEvents as PlayerEvents } from "./types/Events";
import {
mapVideoJsTracks,
type VideoJsPlayer,
@@ -409,27 +407,6 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
};
}
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event],
): ListenerSubscription {
switch (event) {
case "onAudioTrackChange":
return (this.eventEmitter as WebEventEmitter).addOnAudioTrackChangeListener(
callback as PlayerEvents["onAudioTrackChange"],
);
case "onVideoTrackChange":
return (this.eventEmitter as WebEventEmitter).addOnVideoTrackChangeListener(
callback as PlayerEvents["onVideoTrackChange"],
);
case "onQualityChange":
return (this.eventEmitter as WebEventEmitter).addOnQualityChangeListener(
callback as PlayerEvents["onQualityChange"],
);
default:
return super.addEventListener(event, callback);
}
}
}
export { VideoPlayer };
@@ -0,0 +1,44 @@
import type { AllPlayerEvents as PlayerEvents } from "./types/Events";
import type { ListenerSubscription } from "./types/EventEmitter";
import { VideoPlayerEventsBase } from "./VideoPlayerEventsBase";
export class VideoPlayerEvents extends VideoPlayerEventsBase {
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event],
): ListenerSubscription {
switch (event) {
// ----------------- Native-only Events -----------------
case 'onAudioBecomingNoisy':
return this.eventEmitter.addOnAudioBecomingNoisyListener(
callback as PlayerEvents['onAudioBecomingNoisy']
);
case 'onAudioFocusChange':
return this.eventEmitter.addOnAudioFocusChangeListener(
callback as PlayerEvents['onAudioFocusChange']
);
case 'onBandwidthUpdate':
return this.eventEmitter.addOnBandwidthUpdateListener(
callback as PlayerEvents['onBandwidthUpdate']
);
case 'onControlsVisibleChange':
return this.eventEmitter.addOnControlsVisibleChangeListener(
callback as PlayerEvents['onControlsVisibleChange']
);
case 'onExternalPlaybackChange':
return this.eventEmitter.addOnExternalPlaybackChangeListener(
callback as PlayerEvents['onExternalPlaybackChange']
);
case 'onTimedMetadata':
return this.eventEmitter.addOnTimedMetadataListener(
callback as PlayerEvents['onTimedMetadata']
);
case 'onTextTrackDataChanged':
return this.eventEmitter.addOnTextTrackDataChangedListener(
callback as PlayerEvents['onTextTrackDataChanged']
);
default:
return super.addEventListener(event, callback);
}
}
}
@@ -1,146 +1,3 @@
import {
ALL_PLAYER_EVENTS,
type JSVideoPlayerEvents,
type AllPlayerEvents as PlayerEvents,
} from "./types/Events";
import type {
ListenerSubscription,
VideoPlayerEventEmitterBase,
} from "./types/EventEmitter";
export class VideoPlayerEvents {
protected eventEmitter: VideoPlayerEventEmitterBase;
protected jsEventListeners: Partial<
Record<keyof PlayerEvents, Set<(...params: any[]) => void>>
> = {};
protected readonly supportedEvents: (keyof PlayerEvents)[] =
ALL_PLAYER_EVENTS;
constructor(eventEmitter: VideoPlayerEventEmitterBase) {
this.eventEmitter = eventEmitter;
}
protected triggerJSEvent<Event extends keyof JSVideoPlayerEvents>(
event: Event,
...params: Parameters<JSVideoPlayerEvents[Event]>
): boolean {
if (!this.jsEventListeners[event]) return false;
this.jsEventListeners[event]?.forEach((fn) => fn(...params));
return true;
}
/**
* Adds a listener for a player event.
* @throw Error if the event is not supported.
* @param event - The event to add a listener for.
* @param callback - The callback to call when the event is triggered.
* @returns A subscription object that can be used to remove the listener.
*/
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
): ListenerSubscription {
switch (event) {
// ----------------- JS Events -----------------
case 'onError':
this.jsEventListeners.onError ??= new Set();
this.jsEventListeners.onError.add(
callback as JSVideoPlayerEvents['onError']
);
return {
remove: () =>
this.jsEventListeners.onError?.delete(
callback as JSVideoPlayerEvents['onError']
),
};
// ----------------- Native Events -----------------
case 'onAudioBecomingNoisy':
return this.eventEmitter.addOnAudioBecomingNoisyListener(
callback as PlayerEvents['onAudioBecomingNoisy']
);
case 'onAudioFocusChange':
return this.eventEmitter.addOnAudioFocusChangeListener(
callback as PlayerEvents['onAudioFocusChange']
);
case 'onBandwidthUpdate':
return this.eventEmitter.addOnBandwidthUpdateListener(
callback as PlayerEvents['onBandwidthUpdate']
);
case 'onBuffer':
return this.eventEmitter.addOnBufferListener(
callback as PlayerEvents['onBuffer']
);
case 'onControlsVisibleChange':
return this.eventEmitter.addOnControlsVisibleChangeListener(
callback as PlayerEvents['onControlsVisibleChange']
);
case 'onEnd':
return this.eventEmitter.addOnEndListener(
callback as PlayerEvents['onEnd']
);
case 'onExternalPlaybackChange':
return this.eventEmitter.addOnExternalPlaybackChangeListener(
callback as PlayerEvents['onExternalPlaybackChange']
);
case 'onLoad':
return this.eventEmitter.addOnLoadListener(
callback as PlayerEvents['onLoad']
);
case 'onLoadStart':
return this.eventEmitter.addOnLoadStartListener(
callback as PlayerEvents['onLoadStart']
);
case 'onPlaybackStateChange':
return this.eventEmitter.addOnPlaybackStateChangeListener(
callback as PlayerEvents['onPlaybackStateChange']
);
case 'onPlaybackRateChange':
return this.eventEmitter.addOnPlaybackRateChangeListener(
callback as PlayerEvents['onPlaybackRateChange']
);
case 'onProgress':
return this.eventEmitter.addOnProgressListener(
callback as PlayerEvents['onProgress']
);
case 'onReadyToDisplay':
return this.eventEmitter.addOnReadyToDisplayListener(
callback as PlayerEvents['onReadyToDisplay']
);
case 'onSeek':
return this.eventEmitter.addOnSeekListener(
callback as PlayerEvents['onSeek']
);
case 'onTimedMetadata':
return this.eventEmitter.addOnTimedMetadataListener(
callback as PlayerEvents['onTimedMetadata']
);
case 'onTextTrackDataChanged':
return this.eventEmitter.addOnTextTrackDataChangedListener(
callback as PlayerEvents['onTextTrackDataChanged']
);
case 'onTrackChange':
return this.eventEmitter.addOnTrackChangeListener(
callback as PlayerEvents['onTrackChange']
);
case 'onVolumeChange':
return this.eventEmitter.addOnVolumeChangeListener(
callback as PlayerEvents['onVolumeChange']
);
case 'onStatusChange':
return this.eventEmitter.addOnStatusChangeListener(
callback as PlayerEvents['onStatusChange']
);
default:
throw new Error(`[React Native Video] Unsupported event: ${event}`);
}
}
/**
* Clears all events from the event emitter.
*/
clearAllEvents() {
this.jsEventListeners = {};
this.eventEmitter.clearAllListeners();
}
}
// Platform-specific versions: VideoPlayerEvents.native.ts, VideoPlayerEvents.web.ts
// This file is the default fallback for TypeScript resolution.
export { VideoPlayerEvents } from "./VideoPlayerEvents.native";
@@ -0,0 +1,29 @@
import type { AllPlayerEvents as PlayerEvents } from "./types/Events";
import type { ListenerSubscription } from "./types/EventEmitter";
import { VideoPlayerEventsBase } from "./VideoPlayerEventsBase";
import type { WebEventEmitter } from "./web/WebEventEmitter";
export class VideoPlayerEvents extends VideoPlayerEventsBase {
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event],
): ListenerSubscription {
switch (event) {
// ----------------- Web-only Events -----------------
case 'onAudioTrackChange':
return (this.eventEmitter as WebEventEmitter).addOnAudioTrackChangeListener(
callback as PlayerEvents['onAudioTrackChange']
);
case 'onVideoTrackChange':
return (this.eventEmitter as WebEventEmitter).addOnVideoTrackChangeListener(
callback as PlayerEvents['onVideoTrackChange']
);
case 'onQualityChange':
return (this.eventEmitter as WebEventEmitter).addOnQualityChangeListener(
callback as PlayerEvents['onQualityChange']
);
default:
return super.addEventListener(event, callback);
}
}
}
@@ -0,0 +1,108 @@
import {
ALL_PLAYER_EVENTS,
type JSVideoPlayerEvents,
type AllPlayerEvents as PlayerEvents,
} from "./types/Events";
import type {
ListenerSubscription,
VideoPlayerEventEmitterBase,
} from "./types/EventEmitter";
export class VideoPlayerEventsBase {
protected eventEmitter: VideoPlayerEventEmitterBase;
protected jsEventListeners: Partial<
Record<keyof PlayerEvents, Set<(...params: any[]) => void>>
> = {};
protected readonly supportedEvents: (keyof PlayerEvents)[] =
ALL_PLAYER_EVENTS;
constructor(eventEmitter: VideoPlayerEventEmitterBase) {
this.eventEmitter = eventEmitter;
}
protected triggerJSEvent<Event extends keyof JSVideoPlayerEvents>(
event: Event,
...params: Parameters<JSVideoPlayerEvents[Event]>
): boolean {
if (!this.jsEventListeners[event]) return false;
this.jsEventListeners[event]?.forEach((fn) => fn(...params));
return true;
}
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
): ListenerSubscription {
switch (event) {
// ----------------- JS Events -----------------
case 'onError':
this.jsEventListeners.onError ??= new Set();
this.jsEventListeners.onError.add(
callback as JSVideoPlayerEvents['onError']
);
return {
remove: () =>
this.jsEventListeners.onError?.delete(
callback as JSVideoPlayerEvents['onError']
),
};
// ----------------- Shared Events -----------------
case 'onBuffer':
return this.eventEmitter.addOnBufferListener(
callback as PlayerEvents['onBuffer']
);
case 'onEnd':
return this.eventEmitter.addOnEndListener(
callback as PlayerEvents['onEnd']
);
case 'onLoad':
return this.eventEmitter.addOnLoadListener(
callback as PlayerEvents['onLoad']
);
case 'onLoadStart':
return this.eventEmitter.addOnLoadStartListener(
callback as PlayerEvents['onLoadStart']
);
case 'onPlaybackStateChange':
return this.eventEmitter.addOnPlaybackStateChangeListener(
callback as PlayerEvents['onPlaybackStateChange']
);
case 'onPlaybackRateChange':
return this.eventEmitter.addOnPlaybackRateChangeListener(
callback as PlayerEvents['onPlaybackRateChange']
);
case 'onProgress':
return this.eventEmitter.addOnProgressListener(
callback as PlayerEvents['onProgress']
);
case 'onReadyToDisplay':
return this.eventEmitter.addOnReadyToDisplayListener(
callback as PlayerEvents['onReadyToDisplay']
);
case 'onSeek':
return this.eventEmitter.addOnSeekListener(
callback as PlayerEvents['onSeek']
);
case 'onTrackChange':
return this.eventEmitter.addOnTrackChangeListener(
callback as PlayerEvents['onTrackChange']
);
case 'onVolumeChange':
return this.eventEmitter.addOnVolumeChangeListener(
callback as PlayerEvents['onVolumeChange']
);
case 'onStatusChange':
return this.eventEmitter.addOnStatusChangeListener(
callback as PlayerEvents['onStatusChange']
);
default:
throw new Error(`[React Native Video] Unsupported event: ${event}`);
}
}
clearAllEvents() {
this.jsEventListeners = {};
this.eventEmitter.clearAllListeners();
}
}