mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
attempt to optimize waveform fetch to avoid race condition for playback
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useWavesurfer } from '@wavesurfer/react';
|
import { useWavesurfer } from '@wavesurfer/react';
|
||||||
import formatDuration from 'format-duration';
|
import formatDuration from 'format-duration';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { AnimatePresence, motion } from 'motion/react';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { PlayerbarSeekSlider } from './playerbar-seek-slider';
|
||||||
import { CustomPlayerbarSlider } from './playerbar-slider';
|
import { CustomPlayerbarSlider } from './playerbar-slider';
|
||||||
import styles from './playerbar-waveform.module.css';
|
import styles from './playerbar-waveform.module.css';
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ export const PlayerbarWaveform = () => {
|
|||||||
const { mediaSeekToTimestamp } = usePlayer();
|
const { mediaSeekToTimestamp } = usePlayer();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [tooltipPosition, setTooltipPosition] = useState<null | { x: number; y: number }>(null);
|
const [tooltipPosition, setTooltipPosition] = useState<null | { x: number; y: number }>(null);
|
||||||
const [tooltipValue, setTooltipValue] = useState(0);
|
const [tooltipValue, setTooltipValue] = useState(0);
|
||||||
const seekTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const seekTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
@@ -39,6 +42,21 @@ export const PlayerbarWaveform = () => {
|
|||||||
|
|
||||||
const streamUrl = useSongUrl(currentSong, true, transcode);
|
const streamUrl = useSongUrl(currentSong, true, transcode);
|
||||||
|
|
||||||
|
// Fetch blob from stream URL
|
||||||
|
const { data: streamBlob } = useQuery({
|
||||||
|
enabled: !!streamUrl && !!currentSong,
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!streamUrl) return undefined;
|
||||||
|
|
||||||
|
const response = await fetch(streamUrl);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch stream blob');
|
||||||
|
}
|
||||||
|
return await response.blob();
|
||||||
|
},
|
||||||
|
queryKey: [currentSong?._serverId, streamUrl],
|
||||||
|
});
|
||||||
|
|
||||||
const primaryColor = usePrimaryColor();
|
const primaryColor = usePrimaryColor();
|
||||||
|
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
@@ -65,22 +83,37 @@ export const PlayerbarWaveform = () => {
|
|||||||
interact: false,
|
interact: false,
|
||||||
normalize: false,
|
normalize: false,
|
||||||
progressColor: primaryColor,
|
progressColor: primaryColor,
|
||||||
url: streamUrl || undefined,
|
url: undefined, // URL will be loaded separately via useEffect
|
||||||
waveColor,
|
waveColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset loading state when stream URL changes and ensure media is muted
|
// Update wavesurfer with blob when it becomes available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!wavesurfer || !streamBlob) return;
|
||||||
|
|
||||||
|
wavesurfer.loadBlob(streamBlob);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (wavesurfer) {
|
wavesurfer.setVolume(0);
|
||||||
wavesurfer.setVolume(0);
|
const mediaElement = wavesurfer.getMediaElement();
|
||||||
const mediaElement = wavesurfer.getMediaElement();
|
if (mediaElement) {
|
||||||
if (mediaElement) {
|
mediaElement.muted = true;
|
||||||
mediaElement.muted = true;
|
mediaElement.volume = 0;
|
||||||
mediaElement.volume = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [streamUrl, wavesurfer]);
|
}, [streamBlob, wavesurfer]);
|
||||||
|
|
||||||
|
// Reset loading state when song changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!wavesurfer) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
wavesurfer.setVolume(0);
|
||||||
|
const mediaElement = wavesurfer.getMediaElement();
|
||||||
|
if (mediaElement) {
|
||||||
|
mediaElement.muted = true;
|
||||||
|
mediaElement.volume = 0;
|
||||||
|
}
|
||||||
|
}, [wavesurfer]);
|
||||||
|
|
||||||
// Handle waveform ready state
|
// Handle waveform ready state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -351,6 +384,8 @@ export const PlayerbarWaveform = () => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative' }}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -361,7 +396,7 @@ export const PlayerbarWaveform = () => {
|
|||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isLoading && (
|
{isLoading && !isHovered && (
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
@@ -379,6 +414,19 @@ export const PlayerbarWaveform = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
{isLoading && isHovered && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
left: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayerbarSeekSlider max={songDuration} min={0} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{tooltipPosition && isDragging && (
|
{tooltipPosition && isDragging && (
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ opacity: 1, scale: 1, x: '-50%' }}
|
animate={{ opacity: 1, scale: 1, x: '-50%' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user