Cleanup useEvent on web

This commit is contained in:
2026-05-24 17:09:58 +02:00
parent 9410846a0f
commit 0466e0fab4
+73 -111
View File
@@ -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<any, any>;
handler: (cb: OmniEvents[Key], value: any, prev: any) => void;
};
};
function createEventMapper<Key extends keyof OmniEvents, Result>(
key: Key,
selector: Selector<any, Result>,
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 extends keyof OmniEvents>(
event: Event,
callback: OmniEvents[Event],
) => {
const player = usePlayer();
const config = eventMapper[event];
const callbackRef = useRef(callback);
callbackRef.current = callback;
const prevRef = useRef<any>(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 = {