From 8ee23408192ba2bbad7fed2da5f10eadb0437db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= Date: Wed, 25 Mar 2026 13:23:02 +0100 Subject: [PATCH] refactor(web): split events by platform and isolate DOM types --- .gitignore | 2 +- config/tsconfig.json | 2 +- .../src/core/VideoPlayer.web.ts | 23 --- .../src/core/VideoPlayerEvents.native.ts | 44 ++++++ .../src/core/VideoPlayerEvents.ts | 149 +----------------- .../src/core/VideoPlayerEvents.web.ts | 29 ++++ .../src/core/VideoPlayerEventsBase.ts | 108 +++++++++++++ 7 files changed, 186 insertions(+), 171 deletions(-) create mode 100644 packages/react-native-video/src/core/VideoPlayerEvents.native.ts create mode 100644 packages/react-native-video/src/core/VideoPlayerEvents.web.ts create mode 100644 packages/react-native-video/src/core/VideoPlayerEventsBase.ts diff --git a/.gitignore b/.gitignore index aeb971ae..be0757ae 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,4 @@ android/keystores/debug.keystore lib/ # TypeScript -tsconfig.tsbuildinfo \ No newline at end of file +*.tsbuildinfo \ No newline at end of file diff --git a/config/tsconfig.json b/config/tsconfig.json index 460d6874..17e329de 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -16,7 +16,7 @@ "allowUnusedLabels": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "lib": ["ESNext", "dom"], + "lib": ["ESNext"], "module": "ESNext", "moduleResolution": "bundler", "noEmit": true, diff --git a/packages/react-native-video/src/core/VideoPlayer.web.ts b/packages/react-native-video/src/core/VideoPlayer.web.ts index fb817434..9d63220d 100644 --- a/packages/react-native-video/src/core/VideoPlayer.web.ts +++ b/packages/react-native-video/src/core/VideoPlayer.web.ts @@ -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: 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 }; diff --git a/packages/react-native-video/src/core/VideoPlayerEvents.native.ts b/packages/react-native-video/src/core/VideoPlayerEvents.native.ts new file mode 100644 index 00000000..95f4d8ab --- /dev/null +++ b/packages/react-native-video/src/core/VideoPlayerEvents.native.ts @@ -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: 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); + } + } +} diff --git a/packages/react-native-video/src/core/VideoPlayerEvents.ts b/packages/react-native-video/src/core/VideoPlayerEvents.ts index 62409a12..488356d8 100644 --- a/packages/react-native-video/src/core/VideoPlayerEvents.ts +++ b/packages/react-native-video/src/core/VideoPlayerEvents.ts @@ -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 void>> - > = {}; - - protected readonly supportedEvents: (keyof PlayerEvents)[] = - ALL_PLAYER_EVENTS; - - constructor(eventEmitter: VideoPlayerEventEmitterBase) { - this.eventEmitter = eventEmitter; - } - - protected triggerJSEvent( - event: Event, - ...params: Parameters - ): 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: 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"; diff --git a/packages/react-native-video/src/core/VideoPlayerEvents.web.ts b/packages/react-native-video/src/core/VideoPlayerEvents.web.ts new file mode 100644 index 00000000..fee8c22d --- /dev/null +++ b/packages/react-native-video/src/core/VideoPlayerEvents.web.ts @@ -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: 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); + } + } +} diff --git a/packages/react-native-video/src/core/VideoPlayerEventsBase.ts b/packages/react-native-video/src/core/VideoPlayerEventsBase.ts new file mode 100644 index 00000000..8856d22d --- /dev/null +++ b/packages/react-native-video/src/core/VideoPlayerEventsBase.ts @@ -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 void>> + > = {}; + + protected readonly supportedEvents: (keyof PlayerEvents)[] = + ALL_PLAYER_EVENTS; + + constructor(eventEmitter: VideoPlayerEventEmitterBase) { + this.eventEmitter = eventEmitter; + } + + protected triggerJSEvent( + event: Event, + ...params: Parameters + ): boolean { + if (!this.jsEventListeners[event]) return false; + this.jsEventListeners[event]?.forEach((fn) => fn(...params)); + return true; + } + + addEventListener( + 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(); + } +}