mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Waveform playerbar improvements (#1781)
* Defer waveform loading & show default seek bar as fallback * Add configurable waveform loading delay * Add 2s default value for waveform loading delay * disable transcoding config on waveform url --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
@@ -1082,6 +1082,8 @@
|
|||||||
"volumeWheelStep": "volume wheel step",
|
"volumeWheelStep": "volume wheel step",
|
||||||
"volumeWidth_description": "the width of the volume slider",
|
"volumeWidth_description": "the width of the volume slider",
|
||||||
"volumeWidth": "volume slider width",
|
"volumeWidth": "volume slider width",
|
||||||
|
"waveformLoadingDelay": "waveform loading delay",
|
||||||
|
"waveformLoadingDelay_description": "delay in seconds before loading waveform. increase this value if you are experiencing stutters when using the web player.",
|
||||||
"webAudio_description": "use web audio. this enables advanced features like replaygain. disable if you experience otherwise",
|
"webAudio_description": "use web audio. this enables advanced features like replaygain. disable if you experience otherwise",
|
||||||
"webAudio": "use web audio",
|
"webAudio": "use web audio",
|
||||||
"windowBarStyle_description": "select the style of the window bar",
|
"windowBarStyle_description": "select the style of the window bar",
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { CustomPlayerbarSlider } from './playerbar-slider';
|
|||||||
import styles from './playerbar-waveform.module.css';
|
import styles from './playerbar-waveform.module.css';
|
||||||
|
|
||||||
import { useSongUrl } from '/@/renderer/features/player/audio-player/hooks/use-stream-url';
|
import { useSongUrl } from '/@/renderer/features/player/audio-player/hooks/use-stream-url';
|
||||||
|
import { PlayerbarSeekSlider } from '/@/renderer/features/player/components/playerbar-seek-slider';
|
||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
import { BarAlign, usePlayerbarSlider, usePlayerSong, usePlayerTimestamp } from '/@/renderer/store';
|
import { BarAlign, usePlayerbarSlider, usePlayerSong, usePlayerTimestamp } from '/@/renderer/store';
|
||||||
import { useAppThemeColors, useColorScheme } from '/@/renderer/themes/use-app-theme';
|
import { useAppThemeColors, useColorScheme } from '/@/renderer/themes/use-app-theme';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
|
||||||
export const PlayerbarWaveform = () => {
|
export const PlayerbarWaveform = () => {
|
||||||
@@ -18,6 +18,7 @@ export const PlayerbarWaveform = () => {
|
|||||||
const playerbarSlider = usePlayerbarSlider();
|
const playerbarSlider = usePlayerbarSlider();
|
||||||
const currentTime = usePlayerTimestamp();
|
const currentTime = usePlayerTimestamp();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const audioElementRef = useRef<HTMLAudioElement>(document.createElement('audio'));
|
||||||
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);
|
||||||
@@ -29,7 +30,7 @@ export const PlayerbarWaveform = () => {
|
|||||||
|
|
||||||
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
|
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
|
||||||
|
|
||||||
const streamUrl = useSongUrl(currentSong, true, { bitrate: 64, enabled: true, format: 'mp3' });
|
const streamUrl = useSongUrl(currentSong, true, { bitrate: 64, enabled: false, format: 'mp3' });
|
||||||
|
|
||||||
const { color } = useAppThemeColors();
|
const { color } = useAppThemeColors();
|
||||||
const primaryColor = (color['--theme-colors-primary'] as string) || 'rgb(53, 116, 252)';
|
const primaryColor = (color['--theme-colors-primary'] as string) || 'rgb(53, 116, 252)';
|
||||||
@@ -56,28 +57,20 @@ export const PlayerbarWaveform = () => {
|
|||||||
fillParent: true,
|
fillParent: true,
|
||||||
height: 18,
|
height: 18,
|
||||||
interact: false,
|
interact: false,
|
||||||
|
media: audioElementRef.current,
|
||||||
normalize: false,
|
normalize: false,
|
||||||
progressColor: primaryColor,
|
progressColor: primaryColor,
|
||||||
url: streamUrl || undefined,
|
|
||||||
waveColor,
|
waveColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset loading state when stream URL changes and ensure media is muted
|
// Reset loading state when stream URL changes and ensure media is muted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (wavesurfer) {
|
}, [streamUrl]);
|
||||||
wavesurfer.setVolume(0);
|
|
||||||
const mediaElement = wavesurfer.getMediaElement();
|
|
||||||
if (mediaElement) {
|
|
||||||
mediaElement.muted = true;
|
|
||||||
mediaElement.volume = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [streamUrl, wavesurfer]);
|
|
||||||
|
|
||||||
// Handle waveform ready state
|
// Handle waveform ready state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wavesurfer) return;
|
if (!wavesurfer || !streamUrl) return;
|
||||||
|
|
||||||
const handleReady = () => {
|
const handleReady = () => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -90,20 +83,18 @@ export const PlayerbarWaveform = () => {
|
|||||||
|
|
||||||
wavesurfer.on('ready', handleReady);
|
wavesurfer.on('ready', handleReady);
|
||||||
|
|
||||||
// Check if already loaded
|
const waveformTimeout = setTimeout(
|
||||||
if (wavesurfer.getDuration() > 0) {
|
() => {
|
||||||
setIsLoading(false);
|
wavesurfer.load(streamUrl);
|
||||||
const mediaElement = wavesurfer.getMediaElement();
|
},
|
||||||
if (mediaElement) {
|
playerbarSlider?.loadingDelay ? playerbarSlider.loadingDelay * 1000 : 2000,
|
||||||
mediaElement.muted = true;
|
);
|
||||||
mediaElement.volume = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
wavesurfer.un('ready', handleReady);
|
wavesurfer.un('ready', handleReady);
|
||||||
|
clearTimeout(waveformTimeout);
|
||||||
};
|
};
|
||||||
}, [wavesurfer]);
|
}, [wavesurfer, streamUrl, playerbarSlider.loadingDelay]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wavesurfer) return;
|
if (!wavesurfer) return;
|
||||||
@@ -363,12 +354,12 @@ export const PlayerbarWaveform = () => {
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
left: 0,
|
left: 0,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 3,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
>
|
>
|
||||||
<Spinner container />
|
<PlayerbarSeekSlider max={songDuration} min={0} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
@@ -477,6 +477,36 @@ export const ControlSettings = memo(() => {
|
|||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
defaultValue={playerbarSlider?.loadingDelay ?? 2}
|
||||||
|
max={30}
|
||||||
|
min={0}
|
||||||
|
onBlur={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
playerbarSlider: {
|
||||||
|
...playerbarSlider,
|
||||||
|
loadingDelay: e.currentTarget.value
|
||||||
|
? Number(e.currentTarget.value)
|
||||||
|
: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
rightSection={<Text size="sm">s</Text>}
|
||||||
|
width={75}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.waveformLoadingDelay', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.waveformLoadingDelay', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ const PlayerbarSliderSchema = z.object({
|
|||||||
barGap: z.number(),
|
barGap: z.number(),
|
||||||
barRadius: z.number(),
|
barRadius: z.number(),
|
||||||
barWidth: z.number(),
|
barWidth: z.number(),
|
||||||
|
loadingDelay: z.number(),
|
||||||
type: PlayerbarSliderTypeSchema,
|
type: PlayerbarSliderTypeSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1148,6 +1149,7 @@ const initialState: SettingsState = {
|
|||||||
barGap: 1,
|
barGap: 1,
|
||||||
barRadius: 4,
|
barRadius: 4,
|
||||||
barWidth: 2,
|
barWidth: 2,
|
||||||
|
loadingDelay: 2,
|
||||||
type: PlayerbarSliderType.SLIDER,
|
type: PlayerbarSliderType.SLIDER,
|
||||||
},
|
},
|
||||||
playerItems,
|
playerItems,
|
||||||
|
|||||||
Reference in New Issue
Block a user