mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
optimize playerbar slider component
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
import formatDuration from 'format-duration';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './playerbar-slider.module.css';
|
||||||
|
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { usePlayerTimestamp } from '/@/renderer/store';
|
||||||
|
import { CustomPlayerbarSlider } from './playerbar-slider';
|
||||||
|
|
||||||
|
interface PlayerbarSeekSliderProps {
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlayerbarSeekSlider = ({ max, min }: PlayerbarSeekSliderProps) => {
|
||||||
|
const [isSeeking, setIsSeeking] = useState(false);
|
||||||
|
const [seekValue, setSeekValue] = useState(0);
|
||||||
|
const currentTime = usePlayerTimestamp();
|
||||||
|
const seekTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const lastSeekValueRef = useRef<null | number>(null);
|
||||||
|
|
||||||
|
const { mediaSeekToTimestamp } = usePlayer();
|
||||||
|
|
||||||
|
const handleSeekToTimestamp = (timestamp: number) => {
|
||||||
|
mediaSeekToTimestamp(timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sync isSeeking state when currentTime catches up to seek value
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSeeking && lastSeekValueRef.current !== null) {
|
||||||
|
const timeDiff = Math.abs(currentTime - lastSeekValueRef.current);
|
||||||
|
if (timeDiff < 0.5) {
|
||||||
|
setIsSeeking(false);
|
||||||
|
lastSeekValueRef.current = null;
|
||||||
|
if (seekTimeoutRef.current) {
|
||||||
|
clearTimeout(seekTimeoutRef.current);
|
||||||
|
seekTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentTime, isSeeking]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (seekTimeoutRef.current) {
|
||||||
|
clearTimeout(seekTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomPlayerbarSlider
|
||||||
|
label={(value) => formatDuration(value * 1000)}
|
||||||
|
max={max}
|
||||||
|
min={min}
|
||||||
|
onChange={(e) => {
|
||||||
|
// Cancel any pending timeout if user starts seeking again
|
||||||
|
if (seekTimeoutRef.current) {
|
||||||
|
clearTimeout(seekTimeoutRef.current);
|
||||||
|
seekTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
setIsSeeking(true);
|
||||||
|
setSeekValue(e);
|
||||||
|
}}
|
||||||
|
onChangeEnd={(e) => {
|
||||||
|
setSeekValue(e);
|
||||||
|
lastSeekValueRef.current = e;
|
||||||
|
handleSeekToTimestamp(e);
|
||||||
|
|
||||||
|
if (seekTimeoutRef.current) {
|
||||||
|
clearTimeout(seekTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep isSeeking true to prevent slider from snapping back.
|
||||||
|
// The useEffect will detect when currentTime catches up and clear isSeeking.
|
||||||
|
// Also set a fallback timeout to clear isSeeking after a max delay
|
||||||
|
// in case the seek doesn't complete (e.g., network issues).
|
||||||
|
seekTimeoutRef.current = setTimeout(() => {
|
||||||
|
setIsSeeking(false);
|
||||||
|
lastSeekValueRef.current = null;
|
||||||
|
seekTimeoutRef.current = null;
|
||||||
|
}, 1000);
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
}}
|
||||||
|
size={6}
|
||||||
|
value={
|
||||||
|
isSeeking
|
||||||
|
? seekValue
|
||||||
|
: lastSeekValueRef.current !== null &&
|
||||||
|
Math.abs(currentTime - lastSeekValueRef.current) > 0.5
|
||||||
|
? lastSeekValueRef.current
|
||||||
|
: currentTime
|
||||||
|
}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import formatDuration from 'format-duration';
|
import formatDuration from 'format-duration';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
|
import { PlayerbarSeekSlider } from './playerbar-seek-slider';
|
||||||
import styles from './playerbar-slider.module.css';
|
import styles from './playerbar-slider.module.css';
|
||||||
import { PlayerbarWaveform } from './playerbar-waveform';
|
import { PlayerbarWaveform } from './playerbar-waveform';
|
||||||
|
|
||||||
import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player';
|
import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player';
|
||||||
import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player';
|
import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player';
|
||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
|
||||||
import { useRemote } from '/@/renderer/features/remote/hooks/use-remote';
|
import { useRemote } from '/@/renderer/features/remote/hooks/use-remote';
|
||||||
import {
|
import {
|
||||||
useAppStore,
|
useAppStore,
|
||||||
@@ -27,10 +26,7 @@ export const PlayerbarSlider = () => {
|
|||||||
const playerbarSlider = usePlayerbarSlider();
|
const playerbarSlider = usePlayerbarSlider();
|
||||||
|
|
||||||
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
|
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
|
||||||
const [isSeeking, setIsSeeking] = useState(false);
|
|
||||||
const [seekValue, setSeekValue] = useState(0);
|
|
||||||
const currentTime = usePlayerTimestamp();
|
const currentTime = usePlayerTimestamp();
|
||||||
const seekTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
const formattedDuration = formatDuration(songDuration * 1000 || 0);
|
const formattedDuration = formatDuration(songDuration * 1000 || 0);
|
||||||
const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0);
|
const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0);
|
||||||
@@ -39,20 +35,6 @@ export const PlayerbarSlider = () => {
|
|||||||
const { showTimeRemaining } = useAppStore();
|
const { showTimeRemaining } = useAppStore();
|
||||||
const { setShowTimeRemaining } = useAppStoreActions();
|
const { setShowTimeRemaining } = useAppStoreActions();
|
||||||
|
|
||||||
const { mediaSeekToTimestamp } = usePlayer();
|
|
||||||
|
|
||||||
const handleSeekToTimestamp = (timestamp: number) => {
|
|
||||||
mediaSeekToTimestamp(timestamp);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (seekTimeoutRef.current) {
|
|
||||||
clearTimeout(seekTimeoutRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useRemote();
|
useRemote();
|
||||||
|
|
||||||
const isWaveform = playerbarSlider?.type === PlayerbarSliderType.WAVEFORM;
|
const isWaveform = playerbarSlider?.type === PlayerbarSliderType.WAVEFORM;
|
||||||
@@ -76,36 +58,7 @@ export const PlayerbarSlider = () => {
|
|||||||
{isWaveform ? (
|
{isWaveform ? (
|
||||||
<PlayerbarWaveform />
|
<PlayerbarWaveform />
|
||||||
) : (
|
) : (
|
||||||
<CustomPlayerbarSlider
|
<PlayerbarSeekSlider max={songDuration} min={0} />
|
||||||
label={(value) => formatDuration(value * 1000)}
|
|
||||||
max={songDuration}
|
|
||||||
min={0}
|
|
||||||
onChange={(e) => {
|
|
||||||
// Cancel any pending timeout if user starts seeking again
|
|
||||||
if (seekTimeoutRef.current) {
|
|
||||||
clearTimeout(seekTimeoutRef.current);
|
|
||||||
seekTimeoutRef.current = null;
|
|
||||||
}
|
|
||||||
setIsSeeking(true);
|
|
||||||
setSeekValue(e);
|
|
||||||
}}
|
|
||||||
onChangeEnd={(e) => {
|
|
||||||
setSeekValue(e);
|
|
||||||
handleSeekToTimestamp(e);
|
|
||||||
|
|
||||||
// Delay resetting isSeeking to allow currentTime to catch up
|
|
||||||
seekTimeoutRef.current = setTimeout(() => {
|
|
||||||
setIsSeeking(false);
|
|
||||||
seekTimeoutRef.current = null;
|
|
||||||
}, 300);
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
e?.stopPropagation();
|
|
||||||
}}
|
|
||||||
size={6}
|
|
||||||
value={!isSeeking ? currentTime : seekValue}
|
|
||||||
w="100%"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.sliderValueWrapper}>
|
<div className={styles.sliderValueWrapper}>
|
||||||
|
|||||||
Reference in New Issue
Block a user