| 
					
				 | 
			
			
				@@ -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> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |