Rework event handler to use add/remove styyle

This commit is contained in:
2025-09-27 18:36:25 +02:00
parent 1ec3610aae
commit 62b965f9e0
5 changed files with 60 additions and 212 deletions

View File

@@ -87,17 +87,17 @@ import { VideoPlayer } from 'react-native-video';
const player = new VideoPlayer('https://example.com/video.mp4');
player.onLoad = (data) => {
player.addEventListener('onLoad', (data) => {
console.log('Video loaded! Duration:', data.duration);
};
});
player.onProgress = (data) => {
player.addEventListener('onProgress', (data) => {
console.log('Current time:', data.currentTime);
};
});
player.onError = (error) => {
player.addEventListener('onError', (error) => {
console.error('Player Error:', error.code, error.message);
};
});
player.play();
```

View File

@@ -20,8 +20,6 @@ import { VideoPlayerEvents } from './VideoPlayerEvents';
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
protected player: VideoPlayerImpl;
public onError?: (error: VideoRuntimeError) => void = undefined;
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
const hybridSource = createSource(source);
const player = createPlayer(hybridSource);
@@ -57,8 +55,10 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
private throwError(error: unknown) {
const parsedError = tryParseNativeVideoError(error);
if (parsedError instanceof VideoRuntimeError && this.onError) {
this.onError(parsedError);
if (
parsedError instanceof VideoRuntimeError
&& this.triggerEvent('onError', parsedError)
) {
// We don't throw errors if onError is provided
return;
}

View File

@@ -1,10 +1,11 @@
import type { VideoPlayerEventEmitter } from '../spec/nitro/VideoPlayerEventEmitter.nitro';
import type { VideoPlayerEvents as VideoPlayerEventsInterface } from './types/Events';
import type { AllPlayerEvents as PlayerEvents } from './types/Events';
export class VideoPlayerEvents implements VideoPlayerEventsInterface {
export class VideoPlayerEvents {
protected eventEmitter: VideoPlayerEventEmitter;
protected eventListeners: Partial<Record<keyof PlayerEvents, Set<(...params: any[]) => void>>> = {};
protected readonly supportedEvents: (keyof VideoPlayerEventsInterface)[] = [
protected readonly supportedEvents: (keyof PlayerEvents)[] = [
'onAudioBecomingNoisy',
'onAudioFocusChange',
'onBandwidthUpdate',
@@ -28,6 +29,37 @@ export class VideoPlayerEvents implements VideoPlayerEventsInterface {
constructor(eventEmitter: VideoPlayerEventEmitter) {
this.eventEmitter = eventEmitter;
for (let event of this.supportedEvents){
// @ts-expect-error we narrow the type of the event
this.eventEmitter[event] = this.triggerEvent.bind(this, event);
}
}
protected triggerEvent<Event extends keyof PlayerEvents>(
event: Event,
...params: Parameters<PlayerEvents[Event]>
): boolean {
if (!this.eventListeners[event]?.size)
return false;
for (let fn of this.eventListeners[event]) {
fn(...params);
}
return true;
}
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
) {
this.eventListeners[event] ??= new Set<PlayerEvents[Event]>();
this.eventListeners[event].add(callback);
}
removeEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
) {
this.eventListeners[event]!.delete(callback);
}
/**
@@ -43,177 +75,7 @@ export class VideoPlayerEvents implements VideoPlayerEventsInterface {
* Clears a specific event from the event emitter.
* @param event - The name of the event to clear.
*/
clearEvent(event: keyof VideoPlayerEventsInterface) {
this.eventEmitter[event] = VideoPlayerEvents.NOOP;
}
static NOOP = () => {};
set onAudioBecomingNoisy(
value: VideoPlayerEventsInterface['onAudioBecomingNoisy']
) {
this.eventEmitter.onAudioBecomingNoisy = value;
}
get onAudioBecomingNoisy(): VideoPlayerEventsInterface['onAudioBecomingNoisy'] {
return this.eventEmitter.onAudioBecomingNoisy;
}
set onAudioFocusChange(
value: VideoPlayerEventsInterface['onAudioFocusChange']
) {
this.eventEmitter.onAudioFocusChange = value;
}
get onAudioFocusChange(): VideoPlayerEventsInterface['onAudioFocusChange'] {
return this.eventEmitter.onAudioFocusChange;
}
set onBandwidthUpdate(
value: VideoPlayerEventsInterface['onBandwidthUpdate']
) {
this.eventEmitter.onBandwidthUpdate = value;
}
get onBandwidthUpdate(): VideoPlayerEventsInterface['onBandwidthUpdate'] {
return this.eventEmitter.onBandwidthUpdate;
}
set onBuffer(value: VideoPlayerEventsInterface['onBuffer']) {
this.eventEmitter.onBuffer = value;
}
get onBuffer(): VideoPlayerEventsInterface['onBuffer'] {
return this.eventEmitter.onBuffer;
}
set onControlsVisibleChange(
value: VideoPlayerEventsInterface['onControlsVisibleChange']
) {
this.eventEmitter.onControlsVisibleChange = value;
}
get onControlsVisibleChange(): VideoPlayerEventsInterface['onControlsVisibleChange'] {
return this.eventEmitter.onControlsVisibleChange;
}
set onEnd(value: VideoPlayerEventsInterface['onEnd']) {
this.eventEmitter.onEnd = value;
}
get onEnd(): VideoPlayerEventsInterface['onEnd'] {
return this.eventEmitter.onEnd;
}
set onExternalPlaybackChange(
value: VideoPlayerEventsInterface['onExternalPlaybackChange']
) {
this.eventEmitter.onExternalPlaybackChange = value;
}
get onExternalPlaybackChange(): VideoPlayerEventsInterface['onExternalPlaybackChange'] {
return this.eventEmitter.onExternalPlaybackChange;
}
set onLoad(value: VideoPlayerEventsInterface['onLoad']) {
this.eventEmitter.onLoad = value;
}
get onLoad(): VideoPlayerEventsInterface['onLoad'] {
return this.eventEmitter.onLoad;
}
set onLoadStart(value: VideoPlayerEventsInterface['onLoadStart']) {
this.eventEmitter.onLoadStart = value;
}
get onLoadStart(): VideoPlayerEventsInterface['onLoadStart'] {
return this.eventEmitter.onLoadStart;
}
set onPlaybackStateChange(
value: VideoPlayerEventsInterface['onPlaybackStateChange']
) {
this.eventEmitter.onPlaybackStateChange = value;
}
get onPlaybackStateChange(): VideoPlayerEventsInterface['onPlaybackStateChange'] {
return this.eventEmitter.onPlaybackStateChange;
}
set onPlaybackRateChange(
value: VideoPlayerEventsInterface['onPlaybackRateChange']
) {
this.eventEmitter.onPlaybackRateChange = value;
}
get onPlaybackRateChange(): VideoPlayerEventsInterface['onPlaybackRateChange'] {
return this.eventEmitter.onPlaybackRateChange;
}
set onProgress(value: VideoPlayerEventsInterface['onProgress']) {
this.eventEmitter.onProgress = value;
}
get onProgress(): VideoPlayerEventsInterface['onProgress'] {
return this.eventEmitter.onProgress;
}
set onReadyToDisplay(value: VideoPlayerEventsInterface['onReadyToDisplay']) {
this.eventEmitter.onReadyToDisplay = value;
}
get onReadyToDisplay(): VideoPlayerEventsInterface['onReadyToDisplay'] {
return this.eventEmitter.onReadyToDisplay;
}
set onSeek(value: VideoPlayerEventsInterface['onSeek']) {
this.eventEmitter.onSeek = value;
}
get onSeek(): VideoPlayerEventsInterface['onSeek'] {
return this.eventEmitter.onSeek;
}
set onStatusChange(value: VideoPlayerEventsInterface['onStatusChange']) {
this.eventEmitter.onStatusChange = value;
}
get onStatusChange(): VideoPlayerEventsInterface['onStatusChange'] {
return this.eventEmitter.onStatusChange;
}
set onTimedMetadata(value: VideoPlayerEventsInterface['onTimedMetadata']) {
this.eventEmitter.onTimedMetadata = value;
}
get onTimedMetadata(): VideoPlayerEventsInterface['onTimedMetadata'] {
return this.eventEmitter.onTimedMetadata;
}
set onTextTrackDataChanged(
value: VideoPlayerEventsInterface['onTextTrackDataChanged']
) {
this.eventEmitter.onTextTrackDataChanged = value;
}
get onTextTrackDataChanged(): VideoPlayerEventsInterface['onTextTrackDataChanged'] {
return this.eventEmitter.onTextTrackDataChanged;
}
set onTrackChange(value: VideoPlayerEventsInterface['onTrackChange']) {
this.eventEmitter.onTrackChange = value;
}
get onTrackChange(): VideoPlayerEventsInterface['onTrackChange'] {
return this.eventEmitter.onTrackChange;
}
set onVolumeChange(value: VideoPlayerEventsInterface['onVolumeChange']) {
this.eventEmitter.onVolumeChange = value;
}
get onVolumeChange(): VideoPlayerEventsInterface['onVolumeChange'] {
return this.eventEmitter.onVolumeChange;
clearEvent(event: keyof PlayerEvents) {
this.eventListeners[event]?.clear();
}
}

View File

@@ -1,19 +1,6 @@
import { useEffect } from 'react';
import { VideoPlayer } from '../VideoPlayer';
import { type VideoPlayerEvents } from '../types/Events';
// Omit undefined from events
type NonUndefined<T> = T extends undefined ? never : T;
// Valid events names
type Events = keyof VideoPlayerEvents | 'onError';
// Valid events params
type EventsParams<T extends Events> = T extends keyof VideoPlayerEvents
? // (Native) Events from VideoPlayerEvents
Parameters<VideoPlayerEvents[T]>
: // (JS) Events from Video Player
Parameters<NonUndefined<VideoPlayer[T]>>;
import { type AllPlayerEvents } from '../types/Events';
/**
* Attaches an event listener to a `VideoPlayer` instance for a specified event.
@@ -22,22 +9,15 @@ type EventsParams<T extends Events> = T extends keyof VideoPlayerEvents
* @param event - The name of the event to attach the callback to
* @param callback - The callback for the event
*/
export const useEvent = <T extends Events>(
export const useEvent = <T extends keyof AllPlayerEvents>(
player: VideoPlayer,
event: T,
callback: (...args: EventsParams<T>) => void
callback: AllPlayerEvents[T]
) => {
useEffect(() => {
// @ts-expect-error we narrow the type of the event
player[event] = callback;
player.addEventListener(event, callback);
return () => {
if (event === 'onError') {
// onError is not native event, so we can set it to undefined
player.onError = undefined;
} else {
player.clearEvent(event);
}
};
return () => player.removeEventListener(event, callback);
;
}, [player, event, callback]);
};

View File

@@ -1,5 +1,6 @@
import type { VideoPlayerSource } from '../../spec/nitro/VideoPlayerSource.nitro';
import type { TextTrack } from './TextTrack';
import type { VideoRuntimeError } from './VideoError';
import type { VideoOrientation } from './VideoOrientation';
import type { VideoPlayerStatus } from './VideoPlayerStatus';
@@ -92,6 +93,11 @@ export interface VideoPlayerEvents {
onStatusChange: (status: VideoPlayerStatus) => void;
}
export interface AllPlayerEvents extends VideoPlayerEvents {
onError: (error: VideoRuntimeError) => void;
}
export interface VideoViewEvents {
/**
* Called when the video view's picture in picture state changes.