index.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use client'
  2. import { useEffect, useRef, useState } from 'react'
  3. import { t } from 'i18next'
  4. import { useParams, usePathname } from 'next/navigation'
  5. import s from './style.module.css'
  6. import Tooltip from '@/app/components/base/tooltip'
  7. import { randomString } from '@/utils'
  8. import { textToAudio } from '@/service/share'
  9. import Loading from '@/app/components/base/loading'
  10. type AudioBtnProps = {
  11. value: string
  12. voice?: string
  13. className?: string
  14. isAudition?: boolean
  15. }
  16. type AudioState = 'initial' | 'loading' | 'playing' | 'paused' | 'ended'
  17. const AudioBtn = ({
  18. value,
  19. voice,
  20. className,
  21. isAudition,
  22. }: AudioBtnProps) => {
  23. const audioRef = useRef<HTMLAudioElement | null>(null)
  24. const [audioState, setAudioState] = useState<AudioState>('initial')
  25. const selector = useRef(`play-tooltip-${randomString(4)}`)
  26. const params = useParams()
  27. const pathname = usePathname()
  28. const removeCodeBlocks = (inputText: any) => {
  29. const codeBlockRegex = /```[\s\S]*?```/g
  30. if (inputText)
  31. return inputText.replace(codeBlockRegex, '')
  32. return ''
  33. }
  34. const loadAudio = async () => {
  35. const formData = new FormData()
  36. if (value !== '') {
  37. setAudioState('loading')
  38. formData.append('text', removeCodeBlocks(value))
  39. formData.append('voice', removeCodeBlocks(voice))
  40. let url = ''
  41. let isPublic = false
  42. if (params.token) {
  43. url = '/text-to-audio'
  44. isPublic = true
  45. }
  46. else if (params.appId) {
  47. if (pathname.search('explore/installed') > -1)
  48. url = `/installed-apps/${params.appId}/text-to-audio`
  49. else
  50. url = `/apps/${params.appId}/text-to-audio`
  51. }
  52. try {
  53. const audioResponse = await textToAudio(url, isPublic, formData)
  54. const blob_bytes = Buffer.from(audioResponse.data, 'latin1')
  55. const blob = new Blob([blob_bytes], { type: 'audio/wav' })
  56. const audioUrl = URL.createObjectURL(blob)
  57. audioRef.current!.src = audioUrl
  58. }
  59. catch (error) {
  60. setAudioState('initial')
  61. console.error('Error playing audio:', error)
  62. }
  63. }
  64. }
  65. const handleToggle = () => {
  66. if (audioState === 'initial')
  67. loadAudio()
  68. if (audioRef.current) {
  69. if (audioState === 'playing') {
  70. audioRef.current.pause()
  71. setAudioState('paused')
  72. }
  73. else if (audioState === 'paused' || audioState === 'ended') {
  74. audioRef.current.play()
  75. setAudioState('playing')
  76. }
  77. }
  78. }
  79. useEffect(() => {
  80. const currentAudio = audioRef.current
  81. const handleLoading = () => {
  82. setAudioState('loading')
  83. }
  84. const handlePlay = () => {
  85. currentAudio?.play()
  86. setAudioState('playing')
  87. }
  88. const handleEnded = () => {
  89. setAudioState('ended')
  90. }
  91. currentAudio?.addEventListener('progress', handleLoading)
  92. currentAudio?.addEventListener('canplaythrough', handlePlay)
  93. currentAudio?.addEventListener('ended', handleEnded)
  94. return () => {
  95. if (currentAudio) {
  96. currentAudio.removeEventListener('progress', handleLoading)
  97. currentAudio.removeEventListener('canplaythrough', handlePlay)
  98. currentAudio.removeEventListener('ended', handleEnded)
  99. URL.revokeObjectURL(currentAudio.src)
  100. currentAudio.src = ''
  101. }
  102. }
  103. }, [])
  104. const tooltipContent = {
  105. initial: t('appApi.play'),
  106. ended: t('appApi.play'),
  107. paused: t('appApi.pause'),
  108. playing: t('appApi.playing'),
  109. loading: t('appApi.loading'),
  110. }[audioState]
  111. return (
  112. <div className={`${(audioState === 'loading' || audioState === 'playing') ? 'mr-1' : className}`}>
  113. <Tooltip
  114. selector={selector.current}
  115. content={tooltipContent}
  116. className='z-10'
  117. >
  118. <button
  119. disabled={audioState === 'loading'}
  120. className={`box-border p-0.5 flex items-center justify-center cursor-pointer ${isAudition || '!p-0 rounded-md bg-white'}`}
  121. onClick={handleToggle}>
  122. {audioState === 'loading' && <div className='w-6 h-6 rounded-md flex items-center justify-center p-2'><Loading /></div>}
  123. {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>}
  124. </button>
  125. </Tooltip>
  126. <audio ref={audioRef} src='' className='hidden' />
  127. </div>
  128. )
  129. }
  130. export default AudioBtn