|
|
|
@@ -1,11 +1,12 @@ |
|
|
|
'use client' |
|
|
|
import { useRef, useState } from 'react' |
|
|
|
import { useEffect, useRef, useState } from 'react' |
|
|
|
import { t } from 'i18next' |
|
|
|
import { useParams, usePathname } from 'next/navigation' |
|
|
|
import s from './style.module.css' |
|
|
|
import Tooltip from '@/app/components/base/tooltip' |
|
|
|
import { randomString } from '@/utils' |
|
|
|
import { textToAudio } from '@/service/share' |
|
|
|
import Loading from '@/app/components/base/loading' |
|
|
|
|
|
|
|
type AudioBtnProps = { |
|
|
|
value: string |
|
|
|
@@ -14,6 +15,8 @@ type AudioBtnProps = { |
|
|
|
isAudition?: boolean |
|
|
|
} |
|
|
|
|
|
|
|
type AudioState = 'initial' | 'loading' | 'playing' | 'paused' | 'ended' |
|
|
|
|
|
|
|
const AudioBtn = ({ |
|
|
|
value, |
|
|
|
voice, |
|
|
|
@@ -21,9 +24,8 @@ const AudioBtn = ({ |
|
|
|
isAudition, |
|
|
|
}: AudioBtnProps) => { |
|
|
|
const audioRef = useRef<HTMLAudioElement | null>(null) |
|
|
|
const [isPlaying, setIsPlaying] = useState(false) |
|
|
|
const [isPause, setPause] = useState(false) |
|
|
|
const [hasEnded, setHasEnded] = useState(false) |
|
|
|
const [audioState, setAudioState] = useState<AudioState>('initial') |
|
|
|
|
|
|
|
const selector = useRef(`play-tooltip-${randomString(4)}`) |
|
|
|
const params = useParams() |
|
|
|
const pathname = usePathname() |
|
|
|
@@ -34,9 +36,11 @@ const AudioBtn = ({ |
|
|
|
return '' |
|
|
|
} |
|
|
|
|
|
|
|
const playAudio = async () => { |
|
|
|
const loadAudio = async () => { |
|
|
|
const formData = new FormData() |
|
|
|
if (value !== '') { |
|
|
|
setAudioState('loading') |
|
|
|
|
|
|
|
formData.append('text', removeCodeBlocks(value)) |
|
|
|
formData.append('voice', removeCodeBlocks(voice)) |
|
|
|
|
|
|
|
@@ -59,67 +63,80 @@ const AudioBtn = ({ |
|
|
|
const blob_bytes = Buffer.from(audioResponse.data, 'latin1') |
|
|
|
const blob = new Blob([blob_bytes], { type: 'audio/wav' }) |
|
|
|
const audioUrl = URL.createObjectURL(blob) |
|
|
|
const audio = new Audio(audioUrl) |
|
|
|
audioRef.current = audio |
|
|
|
audio.play().then(() => {}).catch(() => { |
|
|
|
setIsPlaying(false) |
|
|
|
URL.revokeObjectURL(audioUrl) |
|
|
|
}) |
|
|
|
audio.onended = () => { |
|
|
|
setHasEnded(true) |
|
|
|
setIsPlaying(false) |
|
|
|
} |
|
|
|
audioRef.current!.src = audioUrl |
|
|
|
} |
|
|
|
catch (error) { |
|
|
|
setIsPlaying(false) |
|
|
|
setAudioState('initial') |
|
|
|
console.error('Error playing audio:', error) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
const togglePlayPause = () => { |
|
|
|
|
|
|
|
const handleToggle = () => { |
|
|
|
if (audioState === 'initial') |
|
|
|
loadAudio() |
|
|
|
if (audioRef.current) { |
|
|
|
if (isPlaying) { |
|
|
|
if (!hasEnded) { |
|
|
|
setPause(false) |
|
|
|
audioRef.current.play() |
|
|
|
} |
|
|
|
if (!isPause) { |
|
|
|
setPause(true) |
|
|
|
audioRef.current.pause() |
|
|
|
} |
|
|
|
if (audioState === 'playing') { |
|
|
|
audioRef.current.pause() |
|
|
|
setAudioState('paused') |
|
|
|
} |
|
|
|
else if (!isPlaying) { |
|
|
|
if (isPause) { |
|
|
|
setPause(false) |
|
|
|
audioRef.current.play() |
|
|
|
} |
|
|
|
else { |
|
|
|
setHasEnded(false) |
|
|
|
playAudio().then() |
|
|
|
} |
|
|
|
else if (audioState === 'paused' || audioState === 'ended') { |
|
|
|
audioRef.current.play() |
|
|
|
setAudioState('playing') |
|
|
|
} |
|
|
|
setIsPlaying(prevIsPlaying => !prevIsPlaying) |
|
|
|
} |
|
|
|
else { |
|
|
|
setIsPlaying(true) |
|
|
|
if (!isPlaying) |
|
|
|
playAudio().then() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
const currentAudio = audioRef.current |
|
|
|
const handleLoading = () => { |
|
|
|
setAudioState('loading') |
|
|
|
} |
|
|
|
const handlePlay = () => { |
|
|
|
currentAudio?.play() |
|
|
|
setAudioState('playing') |
|
|
|
} |
|
|
|
const handleEnded = () => { |
|
|
|
setAudioState('ended') |
|
|
|
} |
|
|
|
currentAudio?.addEventListener('progress', handleLoading) |
|
|
|
currentAudio?.addEventListener('canplaythrough', handlePlay) |
|
|
|
currentAudio?.addEventListener('ended', handleEnded) |
|
|
|
return () => { |
|
|
|
if (currentAudio) { |
|
|
|
currentAudio.removeEventListener('progress', handleLoading) |
|
|
|
currentAudio.removeEventListener('canplaythrough', handlePlay) |
|
|
|
currentAudio.removeEventListener('ended', handleEnded) |
|
|
|
URL.revokeObjectURL(currentAudio.src) |
|
|
|
currentAudio.src = '' |
|
|
|
} |
|
|
|
} |
|
|
|
}, []) |
|
|
|
|
|
|
|
const tooltipContent = { |
|
|
|
initial: t('appApi.play'), |
|
|
|
ended: t('appApi.play'), |
|
|
|
paused: t('appApi.pause'), |
|
|
|
playing: t('appApi.playing'), |
|
|
|
loading: t('appApi.loading'), |
|
|
|
}[audioState] |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className={`${(isPlaying && !hasEnded) ? 'mr-1' : className}`}> |
|
|
|
<div className={`${(audioState === 'loading' || audioState === 'playing') ? 'mr-1' : className}`}> |
|
|
|
<Tooltip |
|
|
|
selector={selector.current} |
|
|
|
content={(!isPause ? ((isPlaying && !hasEnded) ? t('appApi.playing') : t('appApi.play')) : t('appApi.pause')) as string} |
|
|
|
content={tooltipContent} |
|
|
|
className='z-10' |
|
|
|
> |
|
|
|
<div |
|
|
|
<button |
|
|
|
disabled={audioState === 'loading'} |
|
|
|
className={`box-border p-0.5 flex items-center justify-center cursor-pointer ${isAudition || '!p-0 rounded-md bg-white'}`} |
|
|
|
onClick={togglePlayPause}> |
|
|
|
<div className={`w-6 h-6 rounded-md ${!isAudition ? 'w-4 h-4 hover:bg-gray-50' : 'hover:bg-gray-50'} ${(isPlaying && !hasEnded) ? s.pauseIcon : s.playIcon}`}></div> |
|
|
|
</div> |
|
|
|
onClick={handleToggle}> |
|
|
|
{audioState === 'loading' && <div className='w-6 h-6 rounded-md flex items-center justify-center p-2'><Loading /></div>} |
|
|
|
{audioState !== 'loading' && <div className={`w-6 h-6 rounded-md ${!isAudition ? 'w-4 h-4 hover:bg-gray-50' : 'hover:bg-gray-50'} ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div>} |
|
|
|
</button> |
|
|
|
</Tooltip> |
|
|
|
<audio ref={audioRef} src='' className='hidden' /> |
|
|
|
</div> |
|
|
|
) |
|
|
|
} |