Browse Source

fixed the issue of missing cleanup function in the AudioBtn component (#3133)

legao 1 year ago
parent
commit
29918c498c

+ 64 - 47
web/app/components/base/audio-btn/index.tsx

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

+ 1 - 1
web/app/components/base/audio-btn/style.module.css

@@ -7,4 +7,4 @@
   background-image: url(~@/app/components/develop/secret-key/assets/pause.svg);
   background-position: center;
   background-repeat: no-repeat;
-}
+}

+ 1 - 0
web/i18n/en-US/app-api.ts

@@ -9,6 +9,7 @@ const translation = {
   play: 'Play',
   pause: 'Pause',
   playing: 'Playing',
+  loading: 'Loading',
   merMaind: {
     rerender: 'Redo Rerender',
   },

+ 1 - 0
web/i18n/zh-Hans/app-api.ts

@@ -9,6 +9,7 @@ const translation = {
   play: '播放',
   pause: '暂停',
   playing: '播放中',
+  loading: '加载中',
   merMaind: {
     rerender: '重新渲染',
   },