Files
musicland/src/components/player/ProgressSlider.js
T
2025-12-08 15:35:16 +01:00

93 lines
2.8 KiB
JavaScript

import React, { useCallback, useMemo, useRef, useState } from "react";
import Slider from "../Slider";
const clamp01 = (value) => Math.min(1, Math.max(0, value || 0));
const formatTime = (ms) => {
const totalSeconds = Math.max(0, Math.floor((Number(ms) || 0) / 1000));
const minutes = Math.floor(totalSeconds / 60)
.toString()
.padStart(1, "0");
const seconds = (totalSeconds % 60).toString().padStart(2, "0");
return `${minutes}:${seconds}`;
};
const ProgressSlider = ({
positionMs = 0,
durationMs = 0,
isPlaying = false,
onSeek, // (targetMs) => void | Promise<void>
onPause, // () => void | Promise<void>
onPlay, // () => void | Promise<void>
onSeekStart, // () => void | Promise<void>
onSeekEnd, // () => void | Promise<void>
disabled = false,
}) => {
const wasPlayingRef = useRef(false);
const [isSeeking, setIsSeeking] = useState(false);
const [previewRatio, setPreviewRatio] = useState(null);
const { safePosition, safeDuration, progress } = useMemo(() => {
const dur = Math.max(0, Number(durationMs) || 0);
const pos = Math.max(0, Math.min(dur, Number(positionMs) || 0));
const ratio = dur > 0 ? clamp01(pos / dur) : 0;
return { safePosition: pos, safeDuration: dur, progress: ratio };
}, [durationMs, positionMs]);
const handleSeekStart = useCallback(() => {
if (disabled || !safeDuration) return;
wasPlayingRef.current = !!isPlaying;
setIsSeeking(true);
setPreviewRatio(progress);
if (typeof onSeekStart === "function") {
onSeekStart();
}
if (isPlaying && typeof onPause === "function") {
onPause();
}
}, [disabled, safeDuration, isPlaying, onPause, onSeekStart, progress]);
const handleSeek = useCallback(
(ratio) => {
if (disabled || !safeDuration || typeof onSeek !== "function") return;
const bounded = clamp01(ratio);
setPreviewRatio(bounded);
const targetMs = safeDuration * bounded;
onSeek(targetMs);
},
[disabled, onSeek, safeDuration]
);
const handleSeekEnd = useCallback(() => {
if (disabled || !safeDuration) return;
setIsSeeking(false);
setPreviewRatio(null);
if (typeof onSeekEnd === "function") {
onSeekEnd();
}
if (wasPlayingRef.current && typeof onPlay === "function") {
onPlay();
}
wasPlayingRef.current = false;
}, [disabled, onPlay, onSeekEnd, safeDuration]);
return (
<Slider
value={formatTime(
isSeeking && previewRatio != null
? safeDuration * previewRatio
: safePosition
)}
maxValue={formatTime(safeDuration)}
progress={progress}
seekEnabled={!disabled && safeDuration > 0}
onSeekStart={handleSeekStart}
onSeek={handleSeek}
onSeekEnd={handleSeekEnd}
/>
);
};
export default ProgressSlider;