From 0466e0fab407d69d8b653f2adf0c474ffd823fa3 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 May 2026 17:09:51 +0200 Subject: [PATCH] Cleanup useEvent on web --- src/events.web.tsx | 184 ++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 111 deletions(-) diff --git a/src/events.web.tsx b/src/events.web.tsx index b702a3e..e603308 100644 --- a/src/events.web.tsx +++ b/src/events.web.tsx @@ -1,126 +1,88 @@ -import { useEffect, useRef, useState } from "react"; -import type { OmniEvents } from "./types/events"; -import type { OmniPlayerState, PlayerStatus } from "./types/player"; import { - selectPlayback, - selectTime, - selectBuffer, - selectPlaybackRate, - selectVolume, - selectSource, - usePlayer, type Selector, + selectBuffer, + selectError, + selectPlayback, + selectPlaybackRate, + selectSource, + selectTextTrack, + selectTime, + selectVolume, + usePlayer, } from "@videojs/react"; +import { useEffect, useRef } from "react"; +import type { OmniEvents } from "./types/events"; +import type { OmniPlayerState } from "./types/player"; + +type EventMapperConfig = { + [Key in keyof OmniEvents]?: { + selector: Selector; + handler: (cb: OmniEvents[Key], value: any, prev: any) => void; + }; +}; + +function createEventMapper( + key: Key, + selector: Selector, + handler: ( + cb: OmniEvents[Key], + value: Result, + prev: Result | undefined, + ) => void, +) { + return { [key]: { selector, handler } }; +} + +const eventMapper: EventMapperConfig = { + ...createEventMapper("end", selectPlayback, (cb, value, prev) => { + if (value?.ended && prev && !prev.ended) cb(); + }), + ...createEventMapper("error", selectError, (cb, value, prev) => { + if (value?.error && value.error !== prev?.error) { + cb( + value.error.message ?? "Unknown error", + value.error.message ?? "Unknown error", + ); + } + }), + ...createEventMapper("subtitleChange", selectTextTrack, (cb, value, prev) => { + if (value === prev) return; + const tracks = value?.textTrackList; + if (tracks?.length) { + for (let i = 0; i < tracks.length; i++) { + const track = tracks[i]; + if (track?.mode === "showing") { + cb({ + id: `text-${i}`, + label: track.label, + language: track.language, + selected: true, + }); + return; + } + } + } + cb(undefined); + }), +}; export const useEvent = ( event: Event, callback: OmniEvents[Event], ) => { - const player = usePlayer(); + const config = eventMapper[event]; const callbackRef = useRef(callback); callbackRef.current = callback; + const prevRef = useRef(undefined); + + const value = usePlayer(config?.selector ?? (() => ({}))); useEffect(() => { - const store = player.store; - const media = store.state.media as any; - - if (!media) return; - - switch (event) { - case "end": { - const handler = () => (callbackRef.current as OmniEvents["end"])(); - media.addEventListener("ended", handler); - return () => media.removeEventListener("ended", handler); - } - case "error": { - const handler = () => { - const error = store.state.error; - (callbackRef.current as OmniEvents["error"])( - error?.message ?? "Unknown error", - error?.message ?? "Unknown error", - ); - }; - media.addEventListener("error", handler); - return () => media.removeEventListener("error", handler); - } - case "prev": { - const handler = () => (callbackRef.current as OmniEvents["prev"])(); - store.state.onPrev = handler; - return () => { - if (store.state.onPrev === handler) store.state.onPrev = undefined; - }; - } - case "next": { - const handler = () => (callbackRef.current as OmniEvents["next"])(); - store.state.onNext = handler; - return () => { - if (store.state.onNext === handler) store.state.onNext = undefined; - }; - } - case "videoTrackChange": { - const handler = () => { - if (!media.videoTracks?.length) return; - for (let i = 0; i < media.videoTracks.length; i++) { - const track = media.videoTracks[i]; - if (track?.enabled) { - (callbackRef.current as OmniEvents["videoTrackChange"])({ - id: track.id || `video-${i}`, - label: track.label, - language: track.language, - selected: true, - }); - break; - } - } - }; - media.videoTracks?.addEventListener("change", handler); - return () => media.videoTracks?.removeEventListener("change", handler); - } - case "audioTrackChange": { - const handler = () => { - if (!media.audioTracks?.length) return; - for (let i = 0; i < media.audioTracks.length; i++) { - const track = media.audioTracks[i]; - if (track?.enabled) { - (callbackRef.current as OmniEvents["audioTrackChange"])({ - id: track.id || `audio-${i}`, - label: track.label, - language: track.language, - selected: true, - }); - break; - } - } - }; - media.audioTracks?.addEventListener("change", handler); - return () => media.audioTracks?.removeEventListener("change", handler); - } - case "subtitleChange": { - const handler = () => { - if (!media.textTracks?.length) return; - for (let i = 0; i < media.textTracks.length; i++) { - const track = media.textTracks[i]; - if (track?.mode === "showing") { - (callbackRef.current as OmniEvents["subtitleChange"])({ - id: track.id || `text-${i}`, - label: track.label, - language: track.language, - selected: true, - }); - return; - } - } - (callbackRef.current as OmniEvents["subtitleChange"])(undefined); - }; - media.textTracks?.addEventListener("cuechange", handler); - return () => - media.textTracks?.removeEventListener("cuechange", handler); - } - case "audioFocusChange": - case "renditionChange": - return; - } - }, [player, event]); + if (!config) return; + const prev = prevRef.current; + config.handler(callbackRef.current as any, value, prev); + prevRef.current = value; + }, [value, config]); }; type MapperConfig = {