fix: audio set progress error

This commit is contained in:
jialin
2024-12-03 16:17:54 +08:00
parent 687dabc777
commit 83294abaf0
8 changed files with 135 additions and 32 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
const t = Date.now(); const t = Date.now();
export default defineConfig({ export default defineConfig({
proxy: { proxy: {
...proxy('http://192.168.50.5') ...proxy('http://192.168.50.4')
}, },
history: { history: {
type: 'hash' type: 'hash'
+70 -3
View File
@@ -1,3 +1,4 @@
import _ from 'lodash';
import React, { import React, {
forwardRef, forwardRef,
useCallback, useCallback,
@@ -19,6 +20,8 @@ interface AudioPlayerProps {
onFinish?: () => void; onFinish?: () => void;
onAnalyse?: (analyseData: any, frequencyBinCount: any) => void; onAnalyse?: (analyseData: any, frequencyBinCount: any) => void;
onAudioprocess?: (current: number) => void; onAudioprocess?: (current: number) => void;
onPlay?: () => void;
onPause?: () => void;
} }
const AudioPlayer: React.FC< const AudioPlayer: React.FC<
@@ -49,6 +52,12 @@ const AudioPlayer: React.FC<
analyser.current.connect(audioContext.current.destination); analyser.current.connect(audioContext.current.destination);
}, []); }, []);
const debouncePause = _.debounce(() => {
if (wavesurfer.current?.isPlaying()) {
wavesurfer.current?.pause();
}
}, 100);
const listenEvents = () => { const listenEvents = () => {
wavesurfer.current?.on('ready', (duration: number) => { wavesurfer.current?.on('ready', (duration: number) => {
props.onReady?.(duration); props.onReady?.(duration);
@@ -61,16 +70,31 @@ const AudioPlayer: React.FC<
props.onFinish?.(); props.onFinish?.();
}); });
wavesurfer.current?.on('audioprocess', (current: number) => { wavesurfer.current?.on('audioprocess', (current: number) => {
console.log('audioprocess==========:', current);
props.onAudioprocess?.(current); props.onAudioprocess?.(current);
}); });
wavesurfer.current?.on('seeking', (current: number) => {
console.log('seeking', current);
});
wavesurfer.current?.on('timeupdate', (current: number) => {
console.log('timeupdate', current);
});
wavesurfer.current?.on('play', () => { wavesurfer.current?.on('play', () => {
analyser.current?.getByteFrequencyData(dataArray.current); analyser.current?.getByteFrequencyData(dataArray.current);
props.onAnalyse?.(dataArray.current, analyser); props.onAnalyse?.(dataArray.current, analyser);
props.onPlay?.();
});
wavesurfer.current?.on('pause', () => {
analyser.current?.getByteFrequencyData(dataArray.current);
props.onAnalyse?.(dataArray.current, analyser);
props.onPause?.();
}); });
}; };
const createWavesurfer = () => { const createWavesurfer = () => {
wavesurfer.current?.destroy();
wavesurfer.current = WaveSurfer.create({ wavesurfer.current = WaveSurfer.create({
container: container.current, container: container.current,
url: audioUrl, url: audioUrl,
@@ -100,15 +124,54 @@ const AudioPlayer: React.FC<
} }
}; };
const play = () => { const play = async () => {
if (wavesurfer.current) { if (wavesurfer.current) {
wavesurfer.current.play(); wavesurfer.current.play();
} }
}; };
const isPlaying = () => {
if (wavesurfer.current) {
wavesurfer.current.isPlaying();
}
};
const playPause = () => {
if (wavesurfer.current) {
wavesurfer.current.playPause();
}
};
const debounceSeekTo = _.debounce((value: number) => {
if (wavesurfer.current) {
wavesurfer.current.seekTo(value);
}
}, 50);
const debounceSetTime = _.debounce((value: number) => {
if (wavesurfer.current) {
wavesurfer.current.setTime(value);
}
}, 50);
const seekTo = (value: number) => { const seekTo = (value: number) => {
if (wavesurfer.current) {
debounceSeekTo(value);
}
};
const setTime = (value: number) => {
if (wavesurfer.current) {
debounceSetTime(value);
}
};
const seekAndPlay = (value: number) => {
if (wavesurfer.current) { if (wavesurfer.current) {
wavesurfer.current.seekTo(value); wavesurfer.current.seekTo(value);
wavesurfer.current.once('seeking', () => {
wavesurfer.current
?.play()
.catch((error) => console.error('Playback error:', error));
});
} }
}; };
@@ -130,7 +193,11 @@ const AudioPlayer: React.FC<
play, play,
pause, pause,
duration, duration,
seekTo seekTo,
setTime,
playPause,
isPlaying,
wavesurfer
}; };
}); });
+36 -21
View File
@@ -1,5 +1,4 @@
import AudioAnimation from '@/components/audio-animation'; import AudioAnimation from '@/components/audio-animation';
import useResizeObserver from '@/components/logs-viewer/use-size';
import { import {
DownloadOutlined, DownloadOutlined,
PauseCircleOutlined, PauseCircleOutlined,
@@ -9,7 +8,7 @@ import { useIntl } from '@umijs/max';
import { Button, Slider, Tooltip } from 'antd'; import { Button, Slider, Tooltip } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import _, { throttle } from 'lodash'; import _, { throttle } from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import AudioPlayer from './audio-player'; import AudioPlayer from './audio-player';
import './styles/index.less'; import './styles/index.less';
import './styles/slider-progress.less'; import './styles/slider-progress.less';
@@ -39,7 +38,7 @@ interface SpeechContentProps {
const SpeechItem: React.FC<SpeechContentProps> = (props) => { const SpeechItem: React.FC<SpeechContentProps> = (props) => {
const intl = useIntl(); const intl = useIntl();
const [isPlay, setIsPlay] = useState(props.autoplay); const [isPlay, setIsPlay] = useState(props.autoplay);
const [duration, setDuration] = useState(0); const [duration, setDuration] = useState<number>(0);
const [animationSize, setAnimationSize] = useState({ width: 900, height: 0 }); const [animationSize, setAnimationSize] = useState({ width: 900, height: 0 });
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const [audioChunks, setAudioChunks] = useState<any>({ const [audioChunks, setAudioChunks] = useState<any>({
@@ -49,17 +48,26 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
const wrapper = useRef<any>(null); const wrapper = useRef<any>(null);
const ref = useRef<any>(null); const ref = useRef<any>(null);
const size = useResizeObserver(wrapper); const handlePlay = useCallback(async () => {
try {
const handlePlay = () => { console.log(
if (isPlay) { 'isPlay:',
isPlay,
ref.current?.wavesurfer.current?.isPlaying()
);
ref.current?.pause(); ref.current?.pause();
setIsPlay(false); if (ref.current?.wavesurfer.current?.isPlaying()) {
return; ref.current?.pause();
setIsPlay(false);
return;
} else {
await ref.current?.wavesurfer.current?.play();
setIsPlay(true);
}
} catch (error) {
console.log('error:', error);
} }
ref.current?.play(); }, [ref.current]);
setIsPlay(true);
};
const handleOnAnalyse = useCallback((data: any, analyser: any) => { const handleOnAnalyse = useCallback((data: any, analyser: any) => {
setAudioChunks((pre: any) => { setAudioChunks((pre: any) => {
@@ -73,6 +81,12 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
const handleOnFinish = useCallback(() => { const handleOnFinish = useCallback(() => {
setIsPlay(false); setIsPlay(false);
}, []); }, []);
const handleOnPlay = useCallback(() => {
setIsPlay(true);
}, []);
const handleOnPause = useCallback(() => {
setIsPlay(false);
}, []);
const throttleUpdateCurrentTime = throttle((current: number) => { const throttleUpdateCurrentTime = throttle((current: number) => {
setCurrentTime(current); setCurrentTime(current);
@@ -80,8 +94,6 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
const handleOnAudioprocess = useCallback( const handleOnAudioprocess = useCallback(
(current: number) => { (current: number) => {
console.log('current:', current);
// setCurrentTime(() => current);
throttleUpdateCurrentTime(current); throttleUpdateCurrentTime(current);
}, },
[throttleUpdateCurrentTime] [throttleUpdateCurrentTime]
@@ -103,15 +115,16 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
}); });
}, []); }, []);
const handleSliderChange = (value: number) => { const debounceSeek = _.debounce((value: number) => {
ref.current?.seekTo(value); ref.current?.seekTo(value / duration);
setCurrentTime(value); setCurrentTime(value);
}, 200);
const handleSliderChange = (value: number) => {
debounceSeek(value);
}; };
useEffect(() => { const onDownload = useCallback(() => {
console.log('width:', size);
}, [size]);
const onDownload = () => {
const url = props.audioUrl || ''; const url = props.audioUrl || '';
const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${props.format}`; const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${props.format}`;
@@ -121,7 +134,7 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.remove(); link.remove();
}; }, [props.audioUrl, props.format]);
return ( return (
<div> <div>
@@ -137,6 +150,8 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
onReady={handleReay} onReady={handleReay}
onClick={handleOnClick} onClick={handleOnClick}
onFinish={handleOnFinish} onFinish={handleOnFinish}
onPlay={handleOnPlay}
onPause={handleOnPause}
onAnalyse={handleOnAnalyse} onAnalyse={handleOnAnalyse}
onAudioprocess={handleOnAudioprocess} onAudioprocess={handleOnAudioprocess}
ref={ref} ref={ref}
@@ -99,3 +99,7 @@
} }
} }
} }
.audio-container {
pointer-events: none;
}
+4 -2
View File
@@ -59,9 +59,11 @@ export default {
'models.form.backend': 'Backend', 'models.form.backend': 'Backend',
'models.form.backend_parameters': 'Backend Parameters', 'models.form.backend_parameters': 'Backend Parameters',
'models.search.gguf.tips': 'models.search.gguf.tips':
'1. GGUF models backend is llama-box(supports Linux, macOS and Windows).', '1. GGUF models use llama-box(supports Linux, macOS and Windows).',
'models.search.vllm.tips': 'models.search.vllm.tips':
'2. Non-GGUF models use vox-box for audio and vLLM for others.', '2. Non-GGUF models use vox-box for audio and vLLM(x86 Linux only) for others.',
'models.search.voxbox.tips':
'3. To deploy an audio model, uncheck the GGUF checkbox.',
'models.form.ollamalink': 'Find More in Ollama Library', 'models.form.ollamalink': 'Find More in Ollama Library',
'models.form.backend_parameters.llamabox.placeholder': 'models.form.backend_parameters.llamabox.placeholder':
'e.g., --ctx-size=8192', 'e.g., --ctx-size=8192',
+3 -2
View File
@@ -58,9 +58,10 @@ export default {
'models.form.backend': '后端', 'models.form.backend': '后端',
'models.form.backend_parameters': '后端参数', 'models.form.backend_parameters': '后端参数',
'models.search.gguf.tips': 'models.search.gguf.tips':
'1. GGUF 模型后端为 llama-box(支持 Linux, macOS 和 Windows)。', '1. GGUF 模型 llama-box(支持 Linux, macOS 和 Windows)。',
'models.search.vllm.tips': 'models.search.vllm.tips':
'2. 非 GGUF 的音频模型用 vox-box,其它非 GGUF 的模型用 vLLM。', '2. 非 GGUF 的音频模型用 vox-box,其它非 GGUF 的模型用 vLLM(仅支持 x86 Linux)。',
'models.search.voxbox.tips': '3. 若需部署音频模型取消勾选 GGUF 复选框。',
'models.form.ollamalink': '在 Ollama Library 中查找', 'models.form.ollamalink': '在 Ollama Library 中查找',
'models.form.backend_parameters.llamabox.placeholder': 'models.form.backend_parameters.llamabox.placeholder':
'例如,--ctx-size=8192', '例如,--ctx-size=8192',
@@ -256,6 +256,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
<div> <div>
{intl.formatMessage({ id: 'models.search.vllm.tips' })} {intl.formatMessage({ id: 'models.search.vllm.tips' })}
</div> </div>
<div>
{intl.formatMessage({ id: 'models.search.voxbox.tips' })}
</div>
</div> </div>
} }
> >
+14 -3
View File
@@ -52,7 +52,17 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
autoplay: boolean; autoplay: boolean;
audioUrl: string; audioUrl: string;
}[] }[]
>([]); >([
{
input: '',
voice: '',
format: '',
speed: 0,
uid: 0,
autoplay: false,
audioUrl: ''
}
]);
const locale = getLocale(); const locale = getLocale();
const intl = useIntl(); const intl = useIntl();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@@ -140,6 +150,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
setMessageId(); setMessageId();
setTokenResult(null); setTokenResult(null);
setCurrentPrompt(current?.content || ''); setCurrentPrompt(current?.content || '');
setMessageList([]);
controllerRef.current?.abort?.(); controllerRef.current?.abort?.();
controllerRef.current = new AbortController(); controllerRef.current = new AbortController();
@@ -157,7 +168,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
console.log('result:', res); console.log('result:', res);
if (res?.status_code !== 200) { if (res?.status_code && res?.status_code !== 200) {
setTokenResult({ setTokenResult({
error: true, error: true,
errorMessage: errorMessage:
@@ -212,7 +223,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
const res = await queryModelVoices({ const res = await queryModelVoices({
model: value model: value
}); });
if (res?.status_code !== 200) { if (res?.status_code && res?.status_code !== 200) {
setVoiceError({ setVoiceError({
error: true, error: true,
errorMessage: errorMessage: