index.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. 'use client'
  2. import { 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. type AudioBtnProps = {
  10. value: string
  11. className?: string
  12. }
  13. const AudioBtn = ({
  14. value,
  15. className,
  16. }: AudioBtnProps) => {
  17. const audioRef = useRef<HTMLAudioElement | null>(null)
  18. const [isPlaying, setIsPlaying] = useState(false)
  19. const [isPause, setPause] = useState(false)
  20. const [hasEnded, setHasEnded] = useState(false)
  21. const selector = useRef(`play-tooltip-${randomString(4)}`)
  22. const params = useParams()
  23. const pathname = usePathname()
  24. const removeCodeBlocks = (inputText: any) => {
  25. const codeBlockRegex = /```[\s\S]*?```/g
  26. return inputText.replace(codeBlockRegex, '')
  27. }
  28. const playAudio = async () => {
  29. const formData = new FormData()
  30. if (value !== '') {
  31. formData.append('text', removeCodeBlocks(value))
  32. let url = ''
  33. let isPublic = false
  34. if (params.token) {
  35. url = '/text-to-audio'
  36. isPublic = true
  37. }
  38. else if (params.appId) {
  39. if (pathname.search('explore/installed') > -1)
  40. url = `/installed-apps/${params.appId}/text-to-audio`
  41. else
  42. url = `/apps/${params.appId}/text-to-audio`
  43. }
  44. try {
  45. const audioResponse = await textToAudio(url, isPublic, formData)
  46. const blob_bytes = Buffer.from(audioResponse.data, 'latin1')
  47. const blob = new Blob([blob_bytes], { type: 'audio/wav' })
  48. const audioUrl = URL.createObjectURL(blob)
  49. const audio = new Audio(audioUrl)
  50. audioRef.current = audio
  51. audio.play().then(() => {
  52. setIsPlaying(true)
  53. }).catch(() => {
  54. setIsPlaying(false)
  55. URL.revokeObjectURL(audioUrl)
  56. })
  57. audio.onended = () => setHasEnded(true)
  58. }
  59. catch (error) {
  60. setIsPlaying(false)
  61. console.error('Error playing audio:', error)
  62. }
  63. }
  64. }
  65. const togglePlayPause = () => {
  66. if (audioRef.current) {
  67. if (isPlaying) {
  68. setPause(true)
  69. audioRef.current.pause()
  70. }
  71. else if (!hasEnded) {
  72. setPause(false)
  73. audioRef.current.play()
  74. }
  75. else if (!isPlaying) {
  76. playAudio().then()
  77. }
  78. setIsPlaying(prevIsPlaying => !prevIsPlaying)
  79. }
  80. else {
  81. playAudio().then()
  82. }
  83. }
  84. return (
  85. <div className={`${(isPlaying && !hasEnded) ? 'mr-1' : className}`}>
  86. <Tooltip
  87. selector={selector.current}
  88. content={(!isPause ? ((isPlaying && !hasEnded) ? t('appApi.playing') : t('appApi.play')) : t('appApi.pause')) as string}
  89. className='z-10'
  90. >
  91. <div
  92. className={'box-border p-0.5 flex items-center justify-center rounded-md bg-white cursor-pointer'}
  93. style={{ boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)' }}
  94. onClick={togglePlayPause}>
  95. <div className={`w-6 h-6 rounded-md hover:bg-gray-50 ${!isPause ? ((isPlaying && !hasEnded) ? s.playIcon : s.stopIcon) : s.pauseIcon}`}></div>
  96. </div>
  97. </Tooltip>
  98. </div>
  99. )
  100. }
  101. export default AudioBtn