fix: use html audio to load stream
This commit is contained in:
@@ -189,4 +189,4 @@ const AudioAnimation: React.FC<AudioAnimationProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AudioAnimation);
|
||||
export default AudioAnimation;
|
||||
|
||||
@@ -18,8 +18,9 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
// ========================================================
|
||||
|
||||
const initAudioContext = useCallback(() => {
|
||||
audioContext.current = new (window.AudioContext ||
|
||||
window.webkitAudioContext)();
|
||||
audioContext.current = new (
|
||||
window.AudioContext || (window as any).webkitAudioContext
|
||||
)();
|
||||
|
||||
analyser.current = audioContext.current.createAnalyser();
|
||||
analyser.current.fftSize = 512;
|
||||
@@ -52,7 +53,9 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
});
|
||||
|
||||
audioRef.current.addEventListener('timeupdate', () => {
|
||||
const current = audioRef.current?.currentTime || 0;
|
||||
props.onTimeUpdate?.();
|
||||
props.onAudioProcess?.(current);
|
||||
});
|
||||
|
||||
audioRef.current.addEventListener('ended', () => {
|
||||
@@ -85,11 +88,6 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
props.onVolumeChange?.();
|
||||
});
|
||||
|
||||
audioRef.current.addEventListener('audioprocess', () => {
|
||||
const current = audioRef.current?.currentTime || 0;
|
||||
props.onAudioProcess?.(current);
|
||||
});
|
||||
|
||||
audioRef.current.addEventListener('playing', () => {
|
||||
props.onPlaying?.();
|
||||
});
|
||||
@@ -111,16 +109,44 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
play: () => {
|
||||
audioRef.current?.play();
|
||||
if (audioRef.current) {
|
||||
// If playback has ended, reset to beginning
|
||||
if (audioRef.current.currentTime >= audioRef.current.duration) {
|
||||
audioRef.current.currentTime = 0;
|
||||
}
|
||||
audioRef.current.play();
|
||||
}
|
||||
},
|
||||
pause: () => {
|
||||
audioRef.current?.pause();
|
||||
},
|
||||
seekTo: (ratio: number) => {
|
||||
if (audioRef.current && audioRef.current.duration) {
|
||||
audioRef.current.currentTime = ratio * audioRef.current.duration;
|
||||
}
|
||||
},
|
||||
// Compatibility layer for wavesurfer interface
|
||||
wavesurfer: {
|
||||
current: {
|
||||
play: () => {
|
||||
if (audioRef.current) {
|
||||
// If playback has ended, reset to beginning
|
||||
if (audioRef.current.currentTime >= audioRef.current.duration) {
|
||||
audioRef.current.currentTime = 0;
|
||||
}
|
||||
return audioRef.current.play();
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
isPlaying: () => {
|
||||
return audioRef.current ? !audioRef.current?.paused : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
console.log('audioRef.current', audioRef.current, props.url);
|
||||
initEnvents();
|
||||
}
|
||||
return () => {
|
||||
@@ -138,7 +164,6 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
audioRef.current?.removeEventListener('seeked', () => {});
|
||||
audioRef.current?.removeEventListener('seeking', () => {});
|
||||
audioRef.current?.removeEventListener('volumechange', () => {});
|
||||
audioRef.current?.removeEventListener('audioprocess', () => {});
|
||||
audioRef.current?.removeEventListener('playing', () => {});
|
||||
audioRef.current?.removeEventListener('loadedmetadata', () => {});
|
||||
audioRef.current?.removeEventListener('ended', () => {});
|
||||
@@ -146,6 +171,13 @@ const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
};
|
||||
}, [audioRef.current]);
|
||||
|
||||
// Reload audio when URL changes
|
||||
useEffect(() => {
|
||||
if (audioRef.current && props.url) {
|
||||
audioRef.current.load();
|
||||
}
|
||||
}, [props.url]);
|
||||
|
||||
return (
|
||||
<audio
|
||||
controls
|
||||
|
||||
@@ -36,8 +36,9 @@ const AudioPlayer: React.FC<
|
||||
const mediaElement = useRef<any>(null);
|
||||
|
||||
const initAudioContext = useCallback(() => {
|
||||
audioContext.current = new (window.AudioContext ||
|
||||
window.webkitAudioContext)();
|
||||
audioContext.current = new (
|
||||
window.AudioContext || window.webkitAudioContext
|
||||
)();
|
||||
|
||||
analyser.current = audioContext.current.createAnalyser();
|
||||
analyser.current.fftSize = 512;
|
||||
@@ -218,4 +219,4 @@ const AudioPlayer: React.FC<
|
||||
);
|
||||
});
|
||||
|
||||
export default React.memo(AudioPlayer);
|
||||
export default AudioPlayer;
|
||||
|
||||
@@ -4,16 +4,34 @@ import SpeechItem from './speech-item';
|
||||
interface SpeechContentProps {
|
||||
dataList: any[];
|
||||
loading?: boolean;
|
||||
onPlay?: () => void;
|
||||
onPause?: () => void;
|
||||
playerRef?: React.RefObject<any>;
|
||||
isPlaying?: boolean;
|
||||
isStream?: boolean;
|
||||
analyserData?: {
|
||||
data: Uint8Array;
|
||||
analyser: any;
|
||||
};
|
||||
}
|
||||
|
||||
const SpeechContent: React.FC<SpeechContentProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{props.dataList.map((item) => (
|
||||
<SpeechItem key={item.uid} {...item} />
|
||||
<SpeechItem
|
||||
key={item.uid}
|
||||
{...item}
|
||||
isStream={props.isStream}
|
||||
isPlaying={props.isPlaying}
|
||||
onPlay={props.onPlay}
|
||||
onPause={props.onPause}
|
||||
playerRef={props.playerRef}
|
||||
analyserData={props.analyserData}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SpeechContent);
|
||||
export default SpeechContent;
|
||||
|
||||
@@ -8,8 +8,14 @@ import { useIntl } from '@umijs/max';
|
||||
import { Button, Slider, Tooltip } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import _, { throttle } from 'lodash';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import AudioPlayer from './audio-player';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import RawAudioPlayer from '../audio-player/raw-audio-player';
|
||||
import './styles/index.less';
|
||||
import './styles/slider-progress.less';
|
||||
|
||||
@@ -34,10 +40,25 @@ interface SpeechContentProps {
|
||||
format: string;
|
||||
speed: number;
|
||||
audioUrl: string;
|
||||
onPlay?: () => void;
|
||||
onPause?: () => void;
|
||||
isStream?: boolean;
|
||||
isPlaying?: boolean;
|
||||
playerRef?: React.RefObject<any>;
|
||||
analyserData?: {
|
||||
data: Uint8Array;
|
||||
analyser: any;
|
||||
};
|
||||
}
|
||||
const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
const { isStream } = props;
|
||||
console.log(
|
||||
'Rendering SpeechItem with props:',
|
||||
props.isStream,
|
||||
props.analyserData
|
||||
);
|
||||
const intl = useIntl();
|
||||
const [isPlay, setIsPlay] = useState(props.autoplay);
|
||||
const [isPlay, setIsPlay] = useState(props.autoplay || props.isPlaying);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const [animationSize, setAnimationSize] = useState({ width: 900, height: 0 });
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
@@ -48,23 +69,47 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
const wrapper = useRef<any>(null);
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
const handlePlay = useCallback(async () => {
|
||||
useEffect(() => {
|
||||
setIsPlay(props.autoplay || props.isPlaying);
|
||||
}, [props.autoplay, props.isPlaying]);
|
||||
|
||||
const isPCMStream = useMemo(() => {
|
||||
return props.audioUrl?.startsWith('pcm-stream://');
|
||||
}, [props.audioUrl]);
|
||||
|
||||
// Sync internal ref with external playerRef if provided
|
||||
React.useEffect(() => {
|
||||
if (props.playerRef) {
|
||||
(props.playerRef as any).current = ref.current;
|
||||
}
|
||||
}, [props.playerRef, ref.current]);
|
||||
|
||||
const onPause = () => {
|
||||
ref.current?.pause();
|
||||
props.onPause?.();
|
||||
};
|
||||
|
||||
const onPlay = async () => {
|
||||
await ref.current?.wavesurfer.current?.play();
|
||||
props.onPlay?.();
|
||||
};
|
||||
|
||||
const handlePlay = async () => {
|
||||
try {
|
||||
if (ref.current?.wavesurfer.current?.isPlaying()) {
|
||||
ref.current?.pause();
|
||||
onPause();
|
||||
setIsPlay(false);
|
||||
return;
|
||||
} else {
|
||||
await ref.current?.wavesurfer.current?.play();
|
||||
await onPlay();
|
||||
setIsPlay(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error:', error);
|
||||
}
|
||||
}, [ref.current]);
|
||||
};
|
||||
|
||||
const handleOnAnalyse = useCallback((data: any, analyser: any) => {
|
||||
console.log('data+++++++++++++++++=:', data);
|
||||
setAudioChunks((pre: any) => {
|
||||
return {
|
||||
data: data,
|
||||
@@ -76,9 +121,11 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
const handleOnFinish = useCallback(() => {
|
||||
setIsPlay(false);
|
||||
}, []);
|
||||
|
||||
const handleOnPlay = useCallback(() => {
|
||||
setIsPlay(true);
|
||||
}, []);
|
||||
|
||||
const handleOnPause = useCallback(() => {
|
||||
setIsPlay(false);
|
||||
}, []);
|
||||
@@ -87,22 +134,9 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
setCurrentTime(current);
|
||||
}, 100);
|
||||
|
||||
const handleOnAudioprocess = useCallback(
|
||||
(current: number) => {
|
||||
console.log('current:', current);
|
||||
throttleUpdateCurrentTime(current);
|
||||
},
|
||||
[throttleUpdateCurrentTime]
|
||||
);
|
||||
|
||||
const handleReay = useCallback((duration: number) => {
|
||||
setIsPlay(props.autoplay);
|
||||
setDuration(duration);
|
||||
}, []);
|
||||
|
||||
const handleOnClick = useCallback((value: number) => {
|
||||
console.log('current:', value);
|
||||
}, []);
|
||||
const handleOnAudioprocess = (current: number) => {
|
||||
throttleUpdateCurrentTime(current);
|
||||
};
|
||||
|
||||
const handleAnimationResize = useCallback((size: any) => {
|
||||
setAnimationSize({
|
||||
@@ -121,7 +155,6 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
};
|
||||
|
||||
const handleReady = useCallback((duration: number) => {
|
||||
console.log('ready+++++++++++++++', duration);
|
||||
setDuration(duration);
|
||||
}, []);
|
||||
|
||||
@@ -130,9 +163,16 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
setCurrentTime(value);
|
||||
}, []);
|
||||
|
||||
const convertFormat = () => {
|
||||
if (props.format === 'pcm') {
|
||||
return 'wav';
|
||||
}
|
||||
return props.format;
|
||||
};
|
||||
|
||||
const onDownload = useCallback(() => {
|
||||
const url = props.audioUrl || '';
|
||||
const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${props.format}`;
|
||||
const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${convertFormat()}`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@@ -142,6 +182,71 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
link.remove();
|
||||
}, [props.audioUrl, props.format]);
|
||||
|
||||
console.log('isPCMStream:', isPCMStream, isPlay);
|
||||
|
||||
console.log('props.analyserData:', props.analyserData);
|
||||
|
||||
const renderPlayerActions = () => {
|
||||
if (isPCMStream) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Slider
|
||||
className="slider-progress"
|
||||
value={currentTime}
|
||||
max={duration}
|
||||
step={0.01}
|
||||
onChange={handleSliderChange}
|
||||
></Slider>
|
||||
<div className="speech-actions">
|
||||
<span className="tags">
|
||||
<span className="item">{props.format}</span>
|
||||
</span>
|
||||
<span className="duration">
|
||||
{_.round(currentTime, 2) || _.round(duration, 2)}
|
||||
</span>
|
||||
<div className="actions">
|
||||
<Tooltip
|
||||
title={
|
||||
isPlay
|
||||
? intl.formatMessage({ id: 'playground.audio.button.stop' })
|
||||
: intl.formatMessage({ id: 'playground.audio.button.play' })
|
||||
}
|
||||
>
|
||||
<Button
|
||||
disabled={!props.audioUrl || duration === 0}
|
||||
onClick={handlePlay}
|
||||
icon={
|
||||
isPlay ? (
|
||||
<PauseCircleOutlined className="font-size-16" />
|
||||
) : (
|
||||
<PlayCircleOutlined className="font-size-16" />
|
||||
)
|
||||
}
|
||||
type="text"
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.audio.button.download'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
disabled={!props.audioUrl || duration === 0}
|
||||
onClick={onDownload}
|
||||
icon={<DownloadOutlined className="font-size-16" />}
|
||||
type="text"
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="speech-item">
|
||||
@@ -150,93 +255,51 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
style={{ height: 120, width: '100%' }}
|
||||
ref={wrapper}
|
||||
>
|
||||
<AudioPlayer
|
||||
{...props}
|
||||
audioUrl={props.audioUrl}
|
||||
onReady={handleReady}
|
||||
onFinish={handleOnFinish}
|
||||
onPlay={handleOnPlay}
|
||||
onPause={handleOnPause}
|
||||
onAnalyse={handleOnAnalyse}
|
||||
onAudioprocess={handleOnAudioprocess}
|
||||
ref={ref}
|
||||
></AudioPlayer>
|
||||
{/* <RawAudioPlayer
|
||||
{...props}
|
||||
url={props.audioUrl}
|
||||
onReady={handleReady}
|
||||
onEnded={handleOnFinish}
|
||||
onPlay={handleOnPlay}
|
||||
onPause={handleOnPause}
|
||||
onAnalyse={handleOnAnalyse}
|
||||
onAudioProcess={handleOnAudioprocess}
|
||||
ref={ref}
|
||||
></RawAudioPlayer> */}
|
||||
{isPlay && (
|
||||
<AudioAnimation
|
||||
maxBarCount={100}
|
||||
amplitude={60}
|
||||
fixedHeight={true}
|
||||
height={120}
|
||||
width={800}
|
||||
analyserData={audioChunks}
|
||||
></AudioAnimation>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
className="slider-progress"
|
||||
value={currentTime}
|
||||
max={duration}
|
||||
step={0.01}
|
||||
onChange={handleSliderChange}
|
||||
></Slider>
|
||||
<div className="speech-actions">
|
||||
<span className="tags">
|
||||
<span className="item">{props.format}</span>
|
||||
</span>
|
||||
<span className="duration">
|
||||
{_.round(currentTime, 2) || _.round(duration, 2)}
|
||||
</span>
|
||||
<div className="actions">
|
||||
<Tooltip
|
||||
title={
|
||||
isPlay
|
||||
? intl.formatMessage({ id: 'playground.audio.button.stop' })
|
||||
: intl.formatMessage({ id: 'playground.audio.button.play' })
|
||||
}
|
||||
>
|
||||
<Button
|
||||
disabled={!props.audioUrl || duration === 0}
|
||||
onClick={handlePlay}
|
||||
icon={
|
||||
isPlay ? (
|
||||
<PauseCircleOutlined className="font-size-16" />
|
||||
) : (
|
||||
<PlayCircleOutlined className="font-size-16" />
|
||||
)
|
||||
}
|
||||
type="text"
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.audio.button.download'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
disabled={!props.audioUrl || duration === 0}
|
||||
onClick={onDownload}
|
||||
icon={<DownloadOutlined className="font-size-16" />}
|
||||
type="text"
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
<>
|
||||
{/* {!isPCMStream && (
|
||||
<AudioPlayer
|
||||
{...props}
|
||||
audioUrl={props.audioUrl}
|
||||
onReady={handleReady}
|
||||
onFinish={handleOnFinish}
|
||||
onPlay={handleOnPlay}
|
||||
onPause={handleOnPause}
|
||||
onAnalyse={handleOnAnalyse}
|
||||
onAudioprocess={handleOnAudioprocess}
|
||||
ref={ref}
|
||||
></AudioPlayer>
|
||||
)} */}
|
||||
{!isPCMStream && (
|
||||
<RawAudioPlayer
|
||||
{...props}
|
||||
url={props.audioUrl}
|
||||
onReady={handleReady}
|
||||
onEnded={handleOnFinish}
|
||||
onPlay={handleOnPlay}
|
||||
onPause={handleOnPause}
|
||||
onAnalyse={handleOnAnalyse}
|
||||
onAudioProcess={handleOnAudioprocess}
|
||||
ref={ref}
|
||||
></RawAudioPlayer>
|
||||
)}
|
||||
{isPlay &&
|
||||
(props.analyserData?.analyser?.current ||
|
||||
audioChunks.analyser?.current) && (
|
||||
<AudioAnimation
|
||||
maxBarCount={100}
|
||||
amplitude={60}
|
||||
fixedHeight={true}
|
||||
height={120}
|
||||
width={800}
|
||||
analyserData={props.analyserData || audioChunks}
|
||||
></AudioAnimation>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
{renderPlayerActions()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SpeechItem);
|
||||
export default SpeechItem;
|
||||
|
||||
Reference in New Issue
Block a user