first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
import {
MutableRefObject,
RefObject,
useCallback,
useContext,
useEffect,
useRef,
} from 'react';
import {createRafLoop} from '@common/utils/dom/create-ref-loop';
import {PlayerStoreContext} from '@common/player/player-context';
import {usePlayerStore} from '@common/player/hooks/use-player-store';
export interface HtmlMediaInternalStateReturn {
ref: RefObject<HTMLMediaElement>;
updateCurrentTime: () => void;
updateBuffered: () => void;
toggleTextTrackModes: (newTrackId: number, isVisible: boolean) => void;
internalState: MutableRefObject<{
currentTime: number;
playbackReady: boolean;
timeRafLoop: ReturnType<typeof createRafLoop>;
}>;
}
export function useHtmlMediaInternalState(
ref: RefObject<HTMLMediaElement>
): HtmlMediaInternalStateReturn {
const store = useContext(PlayerStoreContext);
const cuedMedia = usePlayerStore(s => s.cuedMedia);
const internalState = useRef({
currentTime: 0,
buffered: 0,
isMediaWaiting: false,
playbackReady: false,
/**
* The `timeupdate` event fires surprisingly infrequently during playback, meaning your progress
* bar (or whatever else is synced to the currentTime) moves in a choppy fashion. This helps
* resolve that by retrieving time updates in a request animation frame loop.
*/
timeRafLoop: createRafLoop(() => {
updateCurrentTime();
updateBuffered();
}),
});
const updateBuffered = useCallback(() => {
const timeRange = ref.current?.buffered;
const seconds =
!timeRange || timeRange.length === 0
? 0
: timeRange.end(timeRange.length - 1);
if (internalState.current.buffered !== seconds) {
store.getState().emit('buffered', {seconds});
internalState.current.buffered = seconds;
}
}, [ref, store]);
const updateCurrentTime = useCallback(() => {
const newTime = ref.current?.currentTime || 0;
if (
internalState.current.currentTime !== newTime &&
!store.getState().isSeeking
) {
store.getState().emit('progress', {currentTime: newTime});
internalState.current.currentTime = newTime;
}
}, [internalState, store, ref]);
const toggleTextTrackModes = useCallback(
(newTrackId: number, isVisible: boolean) => {
if (!ref.current) return;
const {textTracks} = ref.current;
if (newTrackId === -1) {
Array.from(textTracks).forEach(track => {
track.mode = 'disabled';
});
} else {
const oldTrack = textTracks[store.getState().currentTextTrack];
if (oldTrack) oldTrack.mode = 'disabled';
}
const nextTrack = textTracks[newTrackId];
if (nextTrack) {
nextTrack.mode = isVisible ? 'showing' : 'hidden';
}
store.getState().emit('currentTextTrackChange', {
trackId: !isVisible ? -1 : newTrackId,
});
store
.getState()
.emit('textTrackVisibilityChange', {isVisible: isVisible});
},
[ref, store]
);
// stop current time loop on unmount
useEffect(() => {
const timeRafLoop = internalState.current.timeRafLoop;
return () => {
timeRafLoop.stop();
};
}, []);
// reload metadata when new media is cued
useEffect(() => {
ref.current?.load();
}, [cuedMedia?.src, ref]);
return {
ref,
internalState,
updateCurrentTime,
toggleTextTrackModes,
updateBuffered,
};
}