47
common/resources/client/player/hooks/use-current-time.ts
Executable file
47
common/resources/client/player/hooks/use-current-time.ts
Executable file
@@ -0,0 +1,47 @@
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {usePlayerActions} from '@common/player/hooks/use-player-actions';
|
||||
import {usePlayerStore} from '@common/player/hooks/use-player-store';
|
||||
|
||||
interface Props {
|
||||
precision: 'ms' | 'seconds';
|
||||
disabled?: boolean;
|
||||
}
|
||||
export function useCurrentTime(
|
||||
{precision, disabled}: Props = {precision: 'ms', disabled: false},
|
||||
) {
|
||||
const timeRef = useRef(0);
|
||||
const {subscribe, getCurrentTime} = usePlayerActions();
|
||||
const providerKey = usePlayerStore(s =>
|
||||
s.providerName && s.cuedMedia?.id
|
||||
? `${s.providerName}+${s.cuedMedia.id}`
|
||||
: null,
|
||||
);
|
||||
|
||||
const [currentTime, setCurrentTime] = useState(() => getCurrentTime());
|
||||
|
||||
useEffect(() => {
|
||||
let unsubscribe: () => void;
|
||||
if (!disabled) {
|
||||
unsubscribe = subscribe({
|
||||
progress: ({currentTime}) => {
|
||||
const time =
|
||||
precision === 'ms' ? currentTime : Math.floor(currentTime);
|
||||
if (timeRef.current !== time) {
|
||||
setCurrentTime(time);
|
||||
timeRef.current = time;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
return () => unsubscribe?.();
|
||||
}, [precision, subscribe, disabled]);
|
||||
|
||||
// update current time when media or provider changes
|
||||
useEffect(() => {
|
||||
if (providerKey) {
|
||||
setCurrentTime(getCurrentTime());
|
||||
}
|
||||
}, [providerKey, getCurrentTime]);
|
||||
|
||||
return currentTime;
|
||||
}
|
||||
5
common/resources/client/player/hooks/use-is-media-cued.ts
Executable file
5
common/resources/client/player/hooks/use-is-media-cued.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
import {usePlayerStore} from '@common/player/hooks/use-player-store';
|
||||
|
||||
export function useIsMediaCued(mediaId: string | number): boolean {
|
||||
return usePlayerStore(s => s.cuedMedia?.id === mediaId);
|
||||
}
|
||||
14
common/resources/client/player/hooks/use-is-media-playing.ts
Executable file
14
common/resources/client/player/hooks/use-is-media-playing.ts
Executable file
@@ -0,0 +1,14 @@
|
||||
import {usePlayerStore} from '@common/player/hooks/use-player-store';
|
||||
|
||||
export function useIsMediaPlaying(
|
||||
mediaId: string | number,
|
||||
groupId?: string | number
|
||||
): boolean {
|
||||
return usePlayerStore(s => {
|
||||
return (
|
||||
s.isPlaying &&
|
||||
s.cuedMedia?.id === mediaId &&
|
||||
(!groupId || groupId === s.cuedMedia.groupId)
|
||||
);
|
||||
});
|
||||
}
|
||||
55
common/resources/client/player/hooks/use-player-actions.ts
Executable file
55
common/resources/client/player/hooks/use-player-actions.ts
Executable file
@@ -0,0 +1,55 @@
|
||||
import {useContext, useMemo} from 'react';
|
||||
import {PlayerStoreContext} from '@common/player/player-context';
|
||||
import {MediaItem} from '@common/player/media-item';
|
||||
|
||||
export type PlayerActions = ReturnType<typeof usePlayerActions>;
|
||||
|
||||
export function usePlayerActions() {
|
||||
const store = useContext(PlayerStoreContext);
|
||||
|
||||
return useMemo(() => {
|
||||
const s = store.getState();
|
||||
|
||||
const overrideQueueAndPlay = async (
|
||||
mediaItems: MediaItem[],
|
||||
queuePointer?: number
|
||||
) => {
|
||||
s.stop();
|
||||
await s.overrideQueue(mediaItems, queuePointer);
|
||||
return s.play();
|
||||
};
|
||||
|
||||
return {
|
||||
play: s.play,
|
||||
playNext: s.playNext,
|
||||
playPrevious: s.playPrevious,
|
||||
pause: s.pause,
|
||||
subscribe: s.subscribe,
|
||||
emit: s.emit,
|
||||
getCurrentTime: s.getCurrentTime,
|
||||
seek: s.seek,
|
||||
toggleRepeatMode: s.toggleRepeatMode,
|
||||
toggleShuffling: s.toggleShuffling,
|
||||
getState: store.getState,
|
||||
setVolume: s.setVolume,
|
||||
setMuted: s.setMuted,
|
||||
appendToQueue: s.appendToQueue,
|
||||
removeFromQueue: s.removeFromQueue,
|
||||
enterFullscreen: s.enterFullscreen,
|
||||
exitFullscreen: s.exitFullscreen,
|
||||
toggleFullscreen: s.toggleFullscreen,
|
||||
enterPip: s.enterPip,
|
||||
exitPip: s.exitPip,
|
||||
setTextTrackVisibility: s.setTextTrackVisibility,
|
||||
setCurrentTextTrack: s.setCurrentTextTrack,
|
||||
setCurrentAudioTrack: s.setCurrentAudioTrack,
|
||||
setIsSeeking: s.setIsSeeking,
|
||||
setControlsVisible: s.setControlsVisible,
|
||||
cue: s.cue,
|
||||
overrideQueueAndPlay,
|
||||
overrideQueue: s.overrideQueue,
|
||||
setPlaybackRate: s.setPlaybackRate,
|
||||
setPlaybackQuality: s.setPlaybackQuality,
|
||||
};
|
||||
}, [store]);
|
||||
}
|
||||
29
common/resources/client/player/hooks/use-player-click-handler.ts
Executable file
29
common/resources/client/player/hooks/use-player-click-handler.ts
Executable file
@@ -0,0 +1,29 @@
|
||||
import {useCallback, useRef} from 'react';
|
||||
import {usePlayerActions} from '@common/player/hooks/use-player-actions';
|
||||
|
||||
export function usePlayerClickHandler() {
|
||||
const clickRef = useRef(0);
|
||||
const player = usePlayerActions();
|
||||
|
||||
const togglePlay = useCallback(() => {
|
||||
if (player.getState().isPlaying) {
|
||||
player.pause();
|
||||
} else {
|
||||
player.play();
|
||||
}
|
||||
}, [player]);
|
||||
|
||||
return useCallback(() => {
|
||||
if (!player.getState().providerReady) return;
|
||||
clickRef.current += 1;
|
||||
togglePlay();
|
||||
if (clickRef.current === 1) {
|
||||
setTimeout(() => {
|
||||
if (clickRef.current > 1) {
|
||||
player.toggleFullscreen();
|
||||
}
|
||||
clickRef.current = 0;
|
||||
}, 300);
|
||||
}
|
||||
}, [player, togglePlay]);
|
||||
}
|
||||
29
common/resources/client/player/hooks/use-player-store.tsx
Executable file
29
common/resources/client/player/hooks/use-player-store.tsx
Executable file
@@ -0,0 +1,29 @@
|
||||
import {StoreApi} from 'zustand';
|
||||
import {useContext} from 'react';
|
||||
import {PlayerStoreContext} from '@common/player/player-context';
|
||||
import {PlayerState} from '@common/player/state/player-state';
|
||||
import {FullscreenSlice} from '@common/player/state/fullscreen/fullscreen-slice';
|
||||
import {PipSlice} from '@common/player/state/pip/pip-slice';
|
||||
import {useStoreWithEqualityFn} from 'zustand/traditional';
|
||||
|
||||
type ExtractState<S> = S extends {
|
||||
getState: () => infer T;
|
||||
}
|
||||
? T
|
||||
: never;
|
||||
|
||||
type UsePlayerStore = {
|
||||
(): ExtractState<StoreApi<PlayerState>>;
|
||||
<U>(
|
||||
selector: (
|
||||
state: ExtractState<StoreApi<PlayerState & FullscreenSlice & PipSlice>>
|
||||
) => U,
|
||||
equalityFn?: (a: U, b: U) => boolean
|
||||
): U;
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
export const usePlayerStore: UsePlayerStore = (selector, equalityFn) => {
|
||||
const store = useContext(PlayerStoreContext);
|
||||
return useStoreWithEqualityFn(store, selector, equalityFn);
|
||||
};
|
||||
Reference in New Issue
Block a user