@tiny-codes/react-easy 1.4.8 → 1.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/es/hooks/index.d.ts +3 -0
  3. package/es/hooks/index.js +3 -0
  4. package/es/hooks/index.js.map +1 -1
  5. package/es/hooks/useAudioPlayer.d.ts +7 -0
  6. package/es/hooks/useAudioPlayer.js +13 -0
  7. package/es/hooks/useAudioPlayer.js.map +1 -0
  8. package/es/hooks/useSSE.d.ts +26 -0
  9. package/es/hooks/useSSE.js +147 -0
  10. package/es/hooks/useSSE.js.map +1 -0
  11. package/es/hooks/useUserMedia.d.ts +11 -4
  12. package/es/hooks/useUserMedia.js +10 -8
  13. package/es/hooks/useUserMedia.js.map +1 -1
  14. package/es/utils/AudioPlayer.d.ts +166 -0
  15. package/es/utils/AudioPlayer.js +457 -0
  16. package/es/utils/AudioPlayer.js.map +1 -0
  17. package/es/utils/base64.d.ts +48 -0
  18. package/es/utils/base64.js +139 -0
  19. package/es/utils/base64.js.map +1 -0
  20. package/es/utils/crypto.d.ts +0 -30
  21. package/es/utils/crypto.js +8 -93
  22. package/es/utils/crypto.js.map +1 -1
  23. package/es/utils/index.d.ts +5 -1
  24. package/es/utils/index.js +5 -1
  25. package/es/utils/index.js.map +1 -1
  26. package/es/utils/stream.d.ts +17 -10
  27. package/es/utils/stream.js +13 -5
  28. package/es/utils/stream.js.map +1 -1
  29. package/lib/hooks/index.d.ts +3 -0
  30. package/lib/hooks/index.js +8 -0
  31. package/lib/hooks/index.js.map +2 -2
  32. package/lib/hooks/useAudioPlayer.d.ts +7 -0
  33. package/lib/hooks/useAudioPlayer.js +42 -0
  34. package/lib/hooks/useAudioPlayer.js.map +7 -0
  35. package/lib/hooks/useSSE.d.ts +26 -0
  36. package/lib/hooks/useSSE.js +128 -0
  37. package/lib/hooks/useSSE.js.map +7 -0
  38. package/lib/hooks/useUserMedia.d.ts +11 -4
  39. package/lib/hooks/useUserMedia.js +9 -7
  40. package/lib/hooks/useUserMedia.js.map +2 -2
  41. package/lib/utils/AudioPlayer.d.ts +166 -0
  42. package/lib/utils/AudioPlayer.js +305 -0
  43. package/lib/utils/AudioPlayer.js.map +7 -0
  44. package/lib/utils/base64.d.ts +48 -0
  45. package/lib/utils/base64.js +120 -0
  46. package/lib/utils/base64.js.map +7 -0
  47. package/lib/utils/crypto.d.ts +0 -30
  48. package/lib/utils/crypto.js +7 -66
  49. package/lib/utils/crypto.js.map +2 -2
  50. package/lib/utils/index.d.ts +5 -1
  51. package/lib/utils/index.js +28 -3
  52. package/lib/utils/index.js.map +2 -2
  53. package/lib/utils/stream.d.ts +17 -10
  54. package/lib/utils/stream.js +9 -4
  55. package/lib/utils/stream.js.map +2 -2
  56. package/package.json +2 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks/useUserMedia.tsx"],
4
- "sourcesContent": ["import { useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { App, Checkbox, Flex, Form, Modal, notification, Select, Typography } from 'antd';\nimport EasyConfigProvider from '../components/ConfigProvider';\nimport ReactEasyContext from '../components/ConfigProvider/context';\nimport { StreamTimeSlicerClass } from '../utils/stream';\nimport useRefFunction from './useRefFunction';\nimport useRefValue from './useRefValue';\nimport useT from './useT';\n\nexport interface UseUserMediaProps {\n /**\n * - **EN:** The media constraints for the audio and video stream.\n * - **CN:** 媒体流的媒体约束。\n */\n media: Pick<MediaStreamConstraints, 'audio' | 'video'>;\n /**\n * - **EN:** Whether to enable PCM output, only effective when recording audio. Please use\n * `onPcmStreamChunk` callback to get PCM data. Default is `false`.\n * - **CN:** 是否启用 PCM 输出,仅在录制音频时有效。请使用 `onPcmStreamChunk` 回调获取 PCM 数据。默认`false`\n */\n pcmAudioOptions?: {\n /**\n * - **EN:** The audio context options for the PCM output.\n * - **CN:** PCM 输出的音频上下文选项。\n */\n audioContext?: AudioContextOptions;\n /**\n * - **EN:** The worklet options for the PCM output.\n * - **CN:** PCM 输出的工作线程选项。\n */\n workletOptions?: AudioWorkletNodeOptions;\n };\n /**\n * - **EN:** Callback function that is triggered when the recording starts, providing the media\n * stream.\n * - **CN:** 开始录制时触发的回调函数,提供媒体流。\n *\n * @param {MediaStream} stream - The media stream.\n */\n onStartRecording?: (stream: MediaStream) => void;\n /**\n * - **EN:** Callback function that is triggered when the recording stops.\n * - **CN:** 停止录制时触发的回调函数。\n */\n onStopRecording?: () => void;\n /**\n * - **EN:** Callback function that is triggered when a new chunk of media data is available.\n * - **CN:** 当录制媒体流时,每个时间分片会触发一次 `onStreamChunk` 回调,提供媒体数据块。\n *\n * > 注意音频流编码格式为:audio/webm;codecs=opus。如果希望获取 PCM 数据,请使用 `onPcmData` 回调。\n *\n * @param {Blob} chunk - The media data chunk (MIME: audio/webm;codecs=opus) | 媒体数据块 (MIME:\n * audio/webm;codecs=opus)\n */\n onStreamChunk?: (chunk: Blob) => void;\n /**\n * - **EN:** Callback for raw PCM float data (per render quantum)\n * - **CN:** 获取原始 PCM 浮点数据的回调(每个渲染量子)\n *\n * @param data Monophonic or polyphonic spliced data | 单声道或多声道拼接数据\n * @param sampleRate Sample rate | 采样率\n */\n onPcmStreamChunk?: (channels: Float32Array[], sampleRate: number) => void;\n /**\n * - **EN:** Whether to disable this hook.\n * - **CN:** 是否禁用此工具\n */\n disabled?: boolean;\n /**\n * - **EN:** The slicing time period (milliseconds) for each fragment of the audio and video stream,\n * each time slice will trigger the `onStreamChunk` callback. Default is `500`.\n * - **CN:** 媒体流每个分片的切片时间段(毫秒),每个时间分片会触发一次 `onStreamChunk` 回调,默认值为 `500`。\n */\n streamSliceMs?: number;\n /**\n * - **EN:** The silence detection threshold (0-1) for the audio stream, below which the audio is\n * considered silent. Default is `0`.\n * - **CN:** 音频流的静音检测阈值(0-1),低于该值音频被视为静音。默认值为 `0`。\n */\n soundDetectionThreshold?: number;\n /**\n * - **EN:** The timeout duration (milliseconds) for detecting sound input. If no sound is detected\n * within this period, the user will be prompted to re-select the audio device. Default is\n * `3000`.\n * - **CN:** 检测是否有声音输入的超时时间(毫秒),如果在该时间段内没有检测到声音,则会提示用户重新选择音频设备。默认值为 `3000`。\n */\n soundDetectionTimeout?: number;\n}\n\nconst useUserMedia = (props: UseUserMediaProps): UseUserMediaResult => {\n const {\n media,\n pcmAudioOptions,\n disabled,\n streamSliceMs = 500,\n soundDetectionThreshold = 0,\n soundDetectionTimeout = 3000,\n onStartRecording,\n onStopRecording,\n onStreamChunk,\n onPcmStreamChunk,\n } = props;\n const context = useContext(ReactEasyContext);\n const { lang } = context;\n const contextRef = useRefValue(context);\n const t = useT();\n const app = App.useApp();\n // @ts-expect-error: because app may return a stub object when App is not used\n const modal = app.modal?.confirm ? app.modal : Modal;\n const modalRef = useRefValue(modal);\n const [requestMicrophoneEnUrl, setRequestMicrophoneEnUrl] = useState<string>();\n const [requestMicrophoneZhUrl, setRequestMicrophoneZhUrl] = useState<string>();\n const [resetMicrophoneEnUrl, setResetMicrophoneEnUrl] = useState<string>();\n const [resetMicrophoneZhUrl, setResetMicrophoneZhUrl] = useState<string>();\n const [requestCameraEnUrl, setRequestCameraEnUrl] = useState<string>();\n const [requestCameraZhUrl, setRequestCameraZhUrl] = useState<string>();\n const [resetCameraEnUrl, setResetCameraEnUrl] = useState<string>();\n const [resetCameraZhUrl, setResetCameraZhUrl] = useState<string>();\n const [isRecording, setIsRecording] = useState(false);\n const [mediaStream, setMediaStream] = useState<MediaStream>();\n const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);\n const stopSoundListeningRef = useRef<(() => void) | undefined>(undefined);\n const closePcmRef = useRef<(() => void) | undefined>(undefined);\n const includeAudio = !!media.audio;\n const exactAudioDeviceIdRef = useRef<string | undefined>(undefined);\n const rafRef = useRef<number>(0);\n const isSpeakingRef = useRef(false);\n const silenceVolumeThresholdRef = useRefValue(soundDetectionThreshold);\n const silenceDetectDurationRef = useRefValue(soundDetectionTimeout);\n const soundDetectStart = useRef<number>(0);\n const pcmSampleRateRef = useRef<number>(0);\n const onPcmStreamChunkRef = useRefValue(onPcmStreamChunk);\n const pcmStreamSlicerRef = useRef(\n new StreamTimeSlicerClass({\n timeSlice: streamSliceMs,\n onSlice: (channels) => {\n onPcmStreamChunkRef.current?.(channels, pcmSampleRateRef.current);\n },\n })\n );\n const deviceType = useMemo(\n () => (media.video ? t('hooks.useUserMedia.camera') : t('hooks.useUserMedia.microphone')),\n [media, t]\n );\n const featureName = useMemo(\n () => (media.video ? t('hooks.featureName.camera') : t('hooks.featureName.microphone')),\n [media, t]\n );\n\n const showDeniedPopup = () => {\n const resetMicrophoneUrl = lang === 'zh-CN' ? resetMicrophoneZhUrl : resetMicrophoneEnUrl;\n const resetCameraUrl = lang === 'zh-CN' ? resetCameraZhUrl : resetCameraEnUrl;\n modal.error({\n title: t('hooks.useUserMedia.devicePermission', { deviceType }),\n width: 500,\n content: (\n <div>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text strong type=\"danger\">\n {t('hooks.useUserMedia.deniedPermission', { deviceType, featureName })}\n </Typography.Text>\n </Typography.Paragraph>\n <Typography.Paragraph>{t('hooks.useUserMedia.reopenPermissionGuide', { deviceType })}</Typography.Paragraph>\n <img\n src={media.video ? resetCameraUrl : resetMicrophoneUrl}\n alt=\"microphone-permission\"\n style={{ width: 380 }}\n />\n </div>\n ),\n });\n };\n const recordStream = async () => {\n let stream: MediaStream;\n try {\n const options = media;\n if (media.audio) {\n if (exactAudioDeviceIdRef.current) {\n if (media.audio === true) {\n options.audio = { deviceId: { exact: exactAudioDeviceIdRef.current } };\n } else {\n options.audio = { deviceId: { exact: exactAudioDeviceIdRef.current }, ...media.audio };\n }\n }\n }\n stream = await navigator.mediaDevices.getUserMedia(options);\n setMediaStream(stream);\n onStartRecording?.(stream);\n\n const recorder = new MediaRecorder(stream);\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n onStreamChunk?.(event.data);\n }\n };\n if (streamSliceMs) {\n recorder.start(streamSliceMs);\n } else {\n recorder.start();\n }\n setMediaRecorder(recorder);\n setIsRecording(true);\n\n // Capture PCM data if enabled\n if (options.audio && onPcmStreamChunkRef.current) {\n try {\n const ctx = new AudioContext(pcmAudioOptions?.audioContext);\n pcmSampleRateRef.current = ctx.sampleRate;\n // Safari might need resume\n if (ctx.state === 'suspended') {\n await ctx.resume();\n }\n const sourceNode = ctx.createMediaStreamSource(stream);\n let node: AudioWorkletNode | undefined;\n\n closePcmRef.current = () => {\n node?.port.close();\n node?.disconnect();\n sourceNode.disconnect();\n ctx.close().catch(() => {\n // Ignore errors\n });\n };\n\n const setupWorklet = async () => {\n try {\n // Load the worklet module\n await ctx.audioWorklet.addModule(generatePcmCaptureProcessorModule());\n node = new AudioWorkletNode(ctx, 'pcm-capture', pcmAudioOptions?.workletOptions);\n node.port.onmessage = (e: MessageEvent) => {\n if (e.data?.type === 'pcm') {\n const channels = e.data.channels as Float32Array[];\n pcmStreamSlicerRef.current.push(channels);\n }\n };\n sourceNode.connect(node);\n } catch (err) {\n fallbackScriptProcessor({ ctx, sourceNode, streamSlicer: pcmStreamSlicerRef.current });\n }\n };\n\n if ('audioWorklet' in ctx) {\n setupWorklet();\n } else {\n fallbackScriptProcessor({ ctx, sourceNode, streamSlicer: pcmStreamSlicerRef.current });\n }\n } catch (e) {\n console.error('setup pcm worklet failed', e);\n }\n }\n return recorder;\n } catch (error) {\n console.error(error);\n if (error instanceof Error && error.name === 'NotAllowedError') {\n showDeniedPopup();\n notification.error({ message: t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }) });\n throw new Error(t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }));\n }\n notification.error({ message: t('hooks.useUserMedia.notSupport') });\n throw new Error(t('hooks.useUserMedia.notSupport'));\n }\n };\n\n const startRecording = useRefFunction(async () => {\n if (disabled) {\n throw new Error(t('hooks.useUserMedia.disabledWarning'));\n }\n if (isRecording) {\n throw new Error(t('hooks.useUserMedia.isRecordingNow'));\n }\n if (!navigator.mediaDevices?.getUserMedia || !navigator.permissions?.query) {\n notification.error({\n message: t('hooks.useUserMedia.notSupport'),\n });\n throw new Error(t('hooks.useUserMedia.notSupport'));\n }\n try {\n const result = await window.navigator.permissions.query({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: media.video ? ('camera' as any) : ('microphone' as any),\n });\n if (result.state === 'denied') {\n showDeniedPopup();\n notification.error({ message: t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }) });\n throw new Error(t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }));\n } else {\n if (result.state === 'prompt') {\n const requestMicrophoneUrl = lang === 'zh-CN' ? requestMicrophoneZhUrl : requestMicrophoneEnUrl;\n const requestCameraUrl = lang === 'zh-CN' ? requestCameraZhUrl : requestCameraEnUrl;\n return new Promise<MediaRecorder>((resolve, reject) => {\n modal.warning({\n title: t('hooks.useUserMedia.devicePermission', { deviceType }),\n content: (\n <div>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text strong>{t('hooks.useUserMedia.requestTip1', { deviceType })}</Typography.Text>\n </Typography.Paragraph>\n <Typography.Paragraph>{t('hooks.useUserMedia.requestTip2', { featureName })}</Typography.Paragraph>\n <div>\n <img\n src={media.video ? requestCameraUrl : requestMicrophoneUrl}\n alt=\"microphone-permission\"\n style={{ width: 380 }}\n />\n </div>\n </div>\n ),\n onOk: () => {\n try {\n recordStream()\n .then((recorder) => {\n resolve(recorder);\n })\n .catch((error) => {\n reject(error);\n });\n } catch (error) {\n console.error(error);\n reject(error);\n }\n },\n width: 500,\n });\n });\n } else {\n return await recordStream();\n }\n }\n } catch (error) {\n console.error(error);\n throw error;\n }\n });\n\n const stopRecording = useRefFunction(() => {\n mediaRecorder?.stop();\n mediaStream?.getTracks().forEach((t) => t.stop());\n setMediaStream(undefined);\n setIsRecording(false);\n isSpeakingRef.current = false;\n cancelAnimationFrame(rafRef.current);\n stopSoundListeningRef.current?.();\n closePcmRef.current?.();\n onStopRecording?.();\n });\n\n // Wait for sound. If keep silent for a while, show a modal to let user reselect audio device\n const waitForSound = useRefFunction((mediaStream: MediaStream) => {\n const ctx = new AudioContext();\n const source = ctx.createMediaStreamSource(mediaStream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 2048;\n source.connect(analyser);\n soundDetectStart.current = Date.now();\n const data = new Uint8Array(analyser.fftSize);\n const cancelDetect = () => {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n stopSoundListeningRef.current?.();\n };\n const loop = () => {\n analyser.getByteTimeDomainData(data);\n // Calculate RMS\n let sum = 0;\n for (const value of data) {\n const v = (value - 128) / 128;\n sum += v * v;\n }\n const rms = Math.sqrt(sum / data.length); // 0~1\n if (rms > silenceVolumeThresholdRef.current) {\n if (!isSpeakingRef.current) {\n isSpeakingRef.current = true;\n cancelDetect();\n return;\n }\n } else {\n if (Date.now() > soundDetectStart.current + silenceDetectDurationRef.current) {\n navigator.mediaDevices.enumerateDevices().then((devices) => {\n const audioInputs = devices.filter((d) => d.kind === 'audioinput');\n modalRef.current.confirm({\n title: t('hooks.useUserMedia.soundDetectTitle'),\n content: (\n <EasyConfigProvider {...contextRef.current}>\n <SaveAudioDeviceForm\n devices={audioInputs}\n mediaStream={mediaStream}\n onDeviceChange={(deviceId) => (exactAudioDeviceIdRef.current = deviceId)}\n />\n </EasyConfigProvider>\n ),\n width: 500,\n onOk: () => {\n if (exactAudioDeviceIdRef.current) {\n stopRecording();\n setTimeout(() => {\n startRecording();\n });\n }\n },\n onCancel: () => {\n cancelDetect();\n },\n });\n });\n\n cancelDetect();\n return;\n }\n }\n rafRef.current = requestAnimationFrame(loop);\n };\n loop();\n\n stopSoundListeningRef.current = () => {\n analyser.disconnect();\n source.disconnect();\n ctx.close().catch(() => {\n // Ignore errors\n });\n stopSoundListeningRef.current = undefined;\n };\n });\n\n // Component destroy\n useEffect(() => {\n return stopRecording;\n }, []);\n\n // Stop recording when disabled has been changed\n useEffect(() => {\n if (disabled && isRecording) {\n stopRecording();\n }\n }, [disabled, isRecording]);\n\n // Update PCM stream slicer time slice when input sample rate changes\n useEffect(() => {\n if (streamSliceMs && pcmStreamSlicerRef.current.timeSlice !== streamSliceMs) {\n pcmStreamSlicerRef.current.timeSlice = streamSliceMs;\n }\n }, [streamSliceMs]);\n\n // Detect sound activity (only for audio or media with audio)\n useEffect(() => {\n if (includeAudio && mediaStream && !isSpeakingRef.current) {\n try {\n waitForSound(mediaStream);\n } catch (e) {\n console.warn('Audio volume detecting failed:', e);\n }\n }\n return () => {\n stopSoundListeningRef.current?.();\n };\n }, [includeAudio, mediaStream, t]);\n\n useEffect(() => {\n import('../assets/request-microphone-en.js').then((module) => {\n setRequestMicrophoneEnUrl(module.default);\n });\n import('../assets/request-microphone-zh.js').then((module) => {\n setRequestMicrophoneZhUrl(module.default);\n });\n import('../assets/reset-microphone-en.js').then((module) => {\n setResetMicrophoneEnUrl(module.default);\n });\n import('../assets/reset-microphone-zh.js').then((module) => {\n setResetMicrophoneZhUrl(module.default);\n });\n import('../assets/request-camera-en.js').then((module) => {\n setRequestCameraEnUrl(module.default);\n });\n import('../assets/request-camera-zh.js').then((module) => {\n setRequestCameraZhUrl(module.default);\n });\n import('../assets/reset-camera-en.js').then((module) => {\n setResetCameraEnUrl(module.default);\n });\n import('../assets/reset-camera-zh.js').then((module) => {\n setResetCameraZhUrl(module.default);\n });\n }, []);\n\n return {\n isRecording,\n startRecording,\n stopRecording,\n };\n};\n\nexport interface UseUserMediaResult {\n /**\n * - **EN** Whether the media stream is currently being recorded\n * - **CN** 是否正在录制媒体流\n */\n isRecording: boolean;\n /**\n * - **EN** Start recording the media stream\n * - **CN** 开始录制媒体流\n */\n startRecording: () => Promise<MediaRecorder>;\n /**\n * - **EN** Stop recording the media stream\n * - **CN** 停止录制媒体流\n */\n stopRecording: () => void;\n /**\n * - **EN** Get the media stream being recorded, returns the stream if recording, otherwise returns\n * `undefined`\n * - **CN** 获取正在录制的媒体流,如果正在录制则返回该流,否则返回 `undefined`\n */\n mediaStream?: MediaStream;\n}\n\nfunction SaveAudioDeviceForm(props: {\n devices: MediaDeviceInfo[];\n mediaStream: MediaStream;\n onDeviceChange: (deviceId: string) => void;\n}) {\n const { devices, mediaStream, onDeviceChange } = props;\n const [form] = Form.useForm();\n const t = useT();\n const { lang } = useContext(ReactEasyContext);\n const [saveAudioDeviceEnUrl1, setSaveAudioDeviceEnUrl1] = useState<string>();\n const [saveAudioDeviceEnUrl2, setSaveAudioDeviceEnUrl2] = useState<string>();\n const [saveAudioDeviceZhUrl1, setSaveAudioDeviceZhUrl1] = useState<string>();\n const [saveAudioDeviceZhUrl2, setSaveAudioDeviceZhUrl2] = useState<string>();\n const [saveDefaultAudioDevicePermanently, setSaveDefaultAudioDevicePermanently] = useState(false);\n const audioInputs = useMemo(() => devices.filter((d) => d.kind === 'audioinput'), [devices]);\n const [selectedDeviceId, setSelectedDeviceId] = useState<string | undefined>(\n () => mediaStream.getAudioTracks()[0]?.getSettings()?.deviceId ?? audioInputs[0]?.deviceId\n );\n\n const openDataImageInNewTab = useRefFunction((dataUrl: string | undefined) => {\n if (!dataUrl) return;\n const [meta, b64] = dataUrl.split(',');\n const mime = meta.match(/data:(.*);base64/)?.[1] || 'image/png';\n const binary = atob(b64);\n const len = binary.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);\n const blob = new Blob([bytes], { type: mime });\n const url = URL.createObjectURL(blob);\n window.open(url);\n });\n\n useEffect(() => {\n import('../assets/save-default-audio1-en.js').then((module) => {\n setSaveAudioDeviceEnUrl1(module.default);\n });\n import('../assets/save-default-audio2-en.js').then((module) => {\n setSaveAudioDeviceEnUrl2(module.default);\n });\n import('../assets/save-default-audio1-zh.js').then((module) => {\n setSaveAudioDeviceZhUrl1(module.default);\n });\n import('../assets/save-default-audio2-zh.js').then((module) => {\n setSaveAudioDeviceZhUrl2(module.default);\n });\n }, []);\n\n return (\n <Form layout=\"vertical\" form={form}>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text>{t('hooks.useUserMedia.soundDetectDescription')}</Typography.Text>\n </Typography.Paragraph>\n <Form.Item label={t('hooks.useUserMedia.chooseMicrophoneDevice')}>\n <Select\n options={audioInputs.map((input) => ({\n label: input.label,\n value: input.deviceId,\n }))}\n defaultValue={selectedDeviceId}\n onChange={(id) => {\n setSelectedDeviceId(id);\n onDeviceChange(id);\n }}\n />\n </Form.Item>\n <Form.Item style={{ marginBottom: 0 }}>\n <Checkbox onChange={(e) => setSaveDefaultAudioDevicePermanently(e.target.checked)}>\n {t('hooks.useUserMedia.rememberDefaultAudioDevice')}\n </Checkbox>\n </Form.Item>\n {saveDefaultAudioDevicePermanently && (\n <div>\n <Typography.Paragraph>\n <Typography.Text>{t('hooks.useUserMedia.rememberDefaultAudioDeviceTip')}</Typography.Text>\n </Typography.Paragraph>\n <Flex gap={8} align=\"flex-start\">\n <div style={{ flex: 1, minWidth: 0 }}>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n openDataImageInNewTab(lang === 'zh-CN' ? saveAudioDeviceZhUrl1 : saveAudioDeviceEnUrl1);\n }}\n >\n <img\n src={lang === 'zh-CN' ? saveAudioDeviceZhUrl1 : saveAudioDeviceEnUrl1}\n alt=\"the first step to save default audio device\"\n style={{ width: '100%', height: 'auto' }}\n />\n </a>\n </div>\n <div style={{ flex: 1, minWidth: 0 }}>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n openDataImageInNewTab(lang === 'zh-CN' ? saveAudioDeviceZhUrl2 : saveAudioDeviceEnUrl2);\n }}\n >\n <img\n src={lang === 'zh-CN' ? saveAudioDeviceZhUrl2 : saveAudioDeviceEnUrl2}\n alt=\"the second step to save default audio device\"\n style={{ width: '100%', height: 'auto' }}\n />\n </a>\n </div>\n </Flex>\n </div>\n )}\n </Form>\n );\n}\n\nfunction generatePcmCaptureProcessorModule() {\n const workletCode = `\nclass PcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs, outputs, parameters) {\n const channelsIn = inputs[0];\n if (channelsIn && channelsIn[0]) {\n const channels = channelsIn.map((ch) => {\n const copy = new Float32Array(ch.length);\n copy.set(ch);\n return copy;\n });\n this.port.postMessage({ type: 'pcm', channels }, channels.map(ch => ch.buffer));\n }\n return true;\n }\n}\nregisterProcessor('pcm-capture', PcmCaptureProcessor);\n`;\n\n // Create a Blob from the worklet code and return its URL\n const blob = new Blob([workletCode], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n return blobUrl;\n}\n\nfunction fallbackScriptProcessor(options: {\n ctx: AudioContext;\n sourceNode: MediaStreamAudioSourceNode;\n streamSlicer: StreamTimeSlicerClass;\n}) {\n const { ctx, sourceNode, streamSlicer } = options;\n const bufferSize = 128;\n const processor = ctx.createScriptProcessor(bufferSize, 1, 1);\n processor.onaudioprocess = (ev) => {\n const channels = [];\n for (let i = 0; i < ev.inputBuffer.numberOfChannels; i++) {\n const input = ev.inputBuffer.getChannelData(i);\n const copy = new Float32Array(input.length);\n copy.set(input);\n channels.push(copy);\n }\n streamSlicer.push(channels);\n };\n sourceNode.connect(processor);\n // 可不输出:processor.connect(ctx!.destination);不连接 destination 在部分浏览器会被自动 GC,可连到 destination 或 gain(0)\n const gain = ctx.createGain();\n gain.gain.value = 0;\n processor.connect(gain).connect(ctx.destination);\n}\n\nexport default useUserMedia;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { App, Checkbox, Flex, Form, Modal, notification, Select, Typography } from 'antd';\nimport EasyConfigProvider from '../components/ConfigProvider';\nimport ReactEasyContext from '../components/ConfigProvider/context';\nimport { StreamTimeSlicerClass, type StreamTimeSlicerOptions } from '../utils/stream';\nimport useRefFunction from './useRefFunction';\nimport useRefValue from './useRefValue';\nimport useT from './useT';\n\nexport interface UseUserMediaProps {\n /**\n * - **EN:** The media constraints for the audio and video stream.\n * - **CN:** 媒体流的媒体约束。\n */\n media: Pick<MediaStreamConstraints, 'audio' | 'video'>;\n /**\n * - **EN:** Whether to enable PCM output, only effective when recording audio. Please use\n * `onPcmStreamChunk` callback to get PCM data. Default is `false`.\n * - **CN:** 是否启用 PCM 输出,仅在录制音频时有效。请使用 `onPcmStreamChunk` 回调获取 PCM 数据。默认`false`\n */\n pcmAudioOptions?: {\n /**\n * - **EN:** The audio context options for the PCM output.\n * - **CN:** PCM 输出的音频上下文选项。\n */\n audioContext?: AudioContextOptions;\n /**\n * - **EN:** The worklet options for the PCM output.\n * - **CN:** PCM 输出的工作线程选项。\n */\n workletOptions?: AudioWorkletNodeOptions;\n };\n /**\n * - **EN:** Callback function that is triggered when the recording starts, providing the media\n * stream.\n * - **CN:** 开始录制时触发的回调函数,提供媒体流。\n *\n * @param {MediaStream} stream - The media stream.\n */\n onStartRecording?: (stream: MediaStream) => void;\n /**\n * - **EN:** Callback function that is triggered when the recording stops.\n * - **CN:** 停止录制时触发的回调函数。\n */\n onStopRecording?: () => void;\n /**\n * - **EN:** Callback function that is triggered when a new chunk of media data is available.\n * - **CN:** 当录制媒体流时,每个时间分片会触发一次 `onStreamChunk` 回调,提供媒体数据块。\n *\n * > 注意音频流编码格式为:audio/webm;codecs=opus。如果希望获取 PCM 数据,请使用 `onPcmData` 回调。\n *\n * @param {Blob} chunk - The media data chunk (MIME: audio/webm;codecs=opus) | 媒体数据块 (MIME:\n * audio/webm;codecs=opus)\n */\n onStreamChunk?: (chunk: Blob) => void;\n /**\n * - **EN:** Callback for raw PCM float data (per render quantum)\n * - **CN:** 获取原始 PCM 浮点数据的回调(每个渲染量子)\n *\n * @param data Monophonic or polyphonic spliced data | 单声道或多声道拼接数据\n * @param sampleRate Sample rate | 采样率\n */\n onPcmStreamChunk?: (channels: Float32Array[], sampleRate: number) => void;\n /**\n * - **EN:** Whether to disable this hook.\n * - **CN:** 是否禁用此工具\n */\n disabled?: boolean;\n /**\n * - **EN:** The slicing mode for the audio and video stream.\n * - **CN:** 媒体流的切片模式。\n */\n streamSliceMode?: StreamTimeSlicerOptions['sliceMode'];\n /**\n * - **EN:** The slicing value (milliseconds or bytes) for the audio and video stream, when\n * `streamSliceMode` is `time`, it represents milliseconds, and when it is `size`, it represents\n * bytes.\n * - **CN:** 媒体流切片的切片值(毫秒或字节),当 `streamSliceMode` 为 `time` 时表示毫秒,为 `size` 时表示字节。\n */\n streamSliceValue?: StreamTimeSlicerOptions['value'];\n /**\n * - **EN:** The silence detection threshold (0-1) for the audio stream, below which the audio is\n * considered silent. Default is `0`.\n * - **CN:** 音频流的静音检测阈值(0-1),低于该值音频被视为静音。默认值为 `0`。\n */\n soundDetectionThreshold?: number;\n /**\n * - **EN:** The timeout duration (milliseconds) for detecting sound input. If no sound is detected\n * within this period, the user will be prompted to re-select the audio device. Default is\n * `3000`.\n * - **CN:** 检测是否有声音输入的超时时间(毫秒),如果在该时间段内没有检测到声音,则会提示用户重新选择音频设备。默认值为 `3000`。\n */\n soundDetectionTimeout?: number;\n}\n\nconst useUserMedia = (props: UseUserMediaProps): UseUserMediaResult => {\n const {\n media,\n pcmAudioOptions,\n disabled,\n streamSliceMode = 'time',\n streamSliceValue,\n soundDetectionThreshold = 0,\n soundDetectionTimeout = 3000,\n onStartRecording,\n onStopRecording,\n onStreamChunk,\n onPcmStreamChunk,\n } = props;\n const context = useContext(ReactEasyContext);\n const { lang } = context;\n const contextRef = useRefValue(context);\n const t = useT();\n const app = App.useApp();\n // @ts-expect-error: because app may return a stub object when App is not used\n const modal = app.modal?.confirm ? app.modal : Modal;\n const modalRef = useRefValue(modal);\n const [requestMicrophoneEnUrl, setRequestMicrophoneEnUrl] = useState<string>();\n const [requestMicrophoneZhUrl, setRequestMicrophoneZhUrl] = useState<string>();\n const [resetMicrophoneEnUrl, setResetMicrophoneEnUrl] = useState<string>();\n const [resetMicrophoneZhUrl, setResetMicrophoneZhUrl] = useState<string>();\n const [requestCameraEnUrl, setRequestCameraEnUrl] = useState<string>();\n const [requestCameraZhUrl, setRequestCameraZhUrl] = useState<string>();\n const [resetCameraEnUrl, setResetCameraEnUrl] = useState<string>();\n const [resetCameraZhUrl, setResetCameraZhUrl] = useState<string>();\n const [isRecording, setIsRecording] = useState(false);\n const [mediaStream, setMediaStream] = useState<MediaStream>();\n const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);\n const stopSoundListeningRef = useRef<(() => void) | undefined>(undefined);\n const closePcmRef = useRef<(() => void) | undefined>(undefined);\n const includeAudio = !!media.audio;\n const exactAudioDeviceIdRef = useRef<string | undefined>(undefined);\n const rafRef = useRef<number>(0);\n const isSpeakingRef = useRef(false);\n const silenceVolumeThresholdRef = useRefValue(soundDetectionThreshold);\n const silenceDetectDurationRef = useRefValue(soundDetectionTimeout);\n const soundDetectStart = useRef<number>(0);\n const pcmSampleRateRef = useRef<number>(0);\n const onPcmStreamChunkRef = useRefValue(onPcmStreamChunk);\n const pcmStreamSlicerRef = useRef(\n new StreamTimeSlicerClass({\n sliceMode: streamSliceMode,\n value: streamSliceValue || 0,\n onSlice: (channels) => {\n onPcmStreamChunkRef.current?.(channels, pcmSampleRateRef.current);\n },\n })\n );\n const deviceType = useMemo(\n () => (media.video ? t('hooks.useUserMedia.camera') : t('hooks.useUserMedia.microphone')),\n [media, t]\n );\n const featureName = useMemo(\n () => (media.video ? t('hooks.featureName.camera') : t('hooks.featureName.microphone')),\n [media, t]\n );\n\n const showDeniedPopup = () => {\n const resetMicrophoneUrl = lang === 'zh-CN' ? resetMicrophoneZhUrl : resetMicrophoneEnUrl;\n const resetCameraUrl = lang === 'zh-CN' ? resetCameraZhUrl : resetCameraEnUrl;\n modal.error({\n title: t('hooks.useUserMedia.devicePermission', { deviceType }),\n width: 500,\n content: (\n <div>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text strong type=\"danger\">\n {t('hooks.useUserMedia.deniedPermission', { deviceType, featureName })}\n </Typography.Text>\n </Typography.Paragraph>\n <Typography.Paragraph>{t('hooks.useUserMedia.reopenPermissionGuide', { deviceType })}</Typography.Paragraph>\n <img\n src={media.video ? resetCameraUrl : resetMicrophoneUrl}\n alt=\"microphone-permission\"\n style={{ width: 380 }}\n />\n </div>\n ),\n });\n };\n const recordStream = async () => {\n let stream: MediaStream;\n try {\n const options = media;\n if (media.audio) {\n if (exactAudioDeviceIdRef.current) {\n if (media.audio === true) {\n options.audio = { deviceId: { exact: exactAudioDeviceIdRef.current } };\n } else {\n options.audio = { deviceId: { exact: exactAudioDeviceIdRef.current }, ...media.audio };\n }\n }\n }\n stream = await navigator.mediaDevices.getUserMedia(options);\n setMediaStream(stream);\n onStartRecording?.(stream);\n\n const recorder = new MediaRecorder(stream);\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n onStreamChunk?.(event.data);\n }\n };\n if (streamSliceMode === 'time' && streamSliceValue) {\n recorder.start(streamSliceValue);\n } else {\n recorder.start();\n }\n setMediaRecorder(recorder);\n setIsRecording(true);\n\n // Capture PCM data if enabled\n if (options.audio && onPcmStreamChunkRef.current) {\n try {\n const ctx = new AudioContext(pcmAudioOptions?.audioContext);\n pcmSampleRateRef.current = ctx.sampleRate;\n // Safari might need resume\n if (ctx.state === 'suspended') {\n await ctx.resume();\n }\n const sourceNode = ctx.createMediaStreamSource(stream);\n let node: AudioWorkletNode | undefined;\n\n closePcmRef.current = () => {\n node?.port.close();\n node?.disconnect();\n sourceNode.disconnect();\n ctx.close().catch(() => {\n // Ignore errors\n });\n };\n\n const setupWorklet = async () => {\n try {\n // Load the worklet module\n await ctx.audioWorklet.addModule(generatePcmCaptureProcessorModule());\n node = new AudioWorkletNode(ctx, 'pcm-capture', pcmAudioOptions?.workletOptions);\n node.port.onmessage = (e: MessageEvent) => {\n if (e.data?.type === 'pcm') {\n const channels = e.data.channels as Float32Array[];\n pcmStreamSlicerRef.current.push(channels);\n }\n };\n sourceNode.connect(node);\n } catch (err) {\n fallbackScriptProcessor({ ctx, sourceNode, streamSlicer: pcmStreamSlicerRef.current });\n }\n };\n\n if ('audioWorklet' in ctx) {\n setupWorklet();\n } else {\n fallbackScriptProcessor({ ctx, sourceNode, streamSlicer: pcmStreamSlicerRef.current });\n }\n } catch (e) {\n console.error('setup pcm worklet failed', e);\n }\n }\n return recorder;\n } catch (error) {\n console.error(error);\n if (error instanceof Error && error.name === 'NotAllowedError') {\n showDeniedPopup();\n notification.error({ message: t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }) });\n throw new Error(t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }));\n }\n notification.error({ message: t('hooks.useUserMedia.notSupport') });\n throw new Error(t('hooks.useUserMedia.notSupport'));\n }\n };\n\n const startRecording = useRefFunction(async () => {\n if (disabled) {\n throw new Error(t('hooks.useUserMedia.disabledWarning'));\n }\n if (isRecording) {\n throw new Error(t('hooks.useUserMedia.isRecordingNow'));\n }\n if (!navigator.mediaDevices?.getUserMedia || !navigator.permissions?.query) {\n notification.error({\n message: t('hooks.useUserMedia.notSupport'),\n });\n throw new Error(t('hooks.useUserMedia.notSupport'));\n }\n try {\n const result = await window.navigator.permissions.query({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: media.video ? ('camera' as any) : ('microphone' as any),\n });\n if (result.state === 'denied') {\n showDeniedPopup();\n notification.error({ message: t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }) });\n throw new Error(t('hooks.useUserMedia.deniedPermission', { deviceType, featureName }));\n } else {\n if (result.state === 'prompt') {\n const requestMicrophoneUrl = lang === 'zh-CN' ? requestMicrophoneZhUrl : requestMicrophoneEnUrl;\n const requestCameraUrl = lang === 'zh-CN' ? requestCameraZhUrl : requestCameraEnUrl;\n return new Promise<MediaRecorder>((resolve, reject) => {\n modal.warning({\n title: t('hooks.useUserMedia.devicePermission', { deviceType }),\n content: (\n <div>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text strong>{t('hooks.useUserMedia.requestTip1', { deviceType })}</Typography.Text>\n </Typography.Paragraph>\n <Typography.Paragraph>{t('hooks.useUserMedia.requestTip2', { featureName })}</Typography.Paragraph>\n <div>\n <img\n src={media.video ? requestCameraUrl : requestMicrophoneUrl}\n alt=\"microphone-permission\"\n style={{ width: 380 }}\n />\n </div>\n </div>\n ),\n onOk: () => {\n try {\n recordStream()\n .then((recorder) => {\n resolve(recorder);\n })\n .catch((error) => {\n reject(error);\n });\n } catch (error) {\n console.error(error);\n reject(error);\n }\n },\n width: 500,\n });\n });\n } else {\n return await recordStream();\n }\n }\n } catch (error) {\n console.error(error);\n throw error;\n }\n });\n\n const stopRecording = useRefFunction(() => {\n mediaRecorder?.stop();\n mediaStream?.getTracks().forEach((t) => t.stop());\n setMediaStream(undefined);\n setIsRecording(false);\n isSpeakingRef.current = false;\n cancelAnimationFrame(rafRef.current);\n stopSoundListeningRef.current?.();\n closePcmRef.current?.();\n onStopRecording?.();\n });\n\n // Wait for sound. If keep silent for a while, show a modal to let user reselect audio device\n const waitForSound = useRefFunction((mediaStream: MediaStream) => {\n const ctx = new AudioContext();\n const source = ctx.createMediaStreamSource(mediaStream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 2048;\n source.connect(analyser);\n soundDetectStart.current = Date.now();\n const data = new Uint8Array(analyser.fftSize);\n const cancelDetect = () => {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n stopSoundListeningRef.current?.();\n };\n const loop = () => {\n analyser.getByteTimeDomainData(data);\n // Calculate RMS\n let sum = 0;\n for (const value of data) {\n const v = (value - 128) / 128;\n sum += v * v;\n }\n const rms = Math.sqrt(sum / data.length); // 0~1\n if (rms > silenceVolumeThresholdRef.current) {\n if (!isSpeakingRef.current) {\n isSpeakingRef.current = true;\n cancelDetect();\n return;\n }\n } else {\n if (Date.now() > soundDetectStart.current + silenceDetectDurationRef.current) {\n navigator.mediaDevices.enumerateDevices().then((devices) => {\n const audioInputs = devices.filter((d) => d.kind === 'audioinput');\n modalRef.current.confirm({\n title: t('hooks.useUserMedia.soundDetectTitle'),\n content: (\n <EasyConfigProvider {...contextRef.current}>\n <SaveAudioDeviceForm\n devices={audioInputs}\n mediaStream={mediaStream}\n onDeviceChange={(deviceId) => (exactAudioDeviceIdRef.current = deviceId)}\n />\n </EasyConfigProvider>\n ),\n width: 500,\n onOk: () => {\n if (exactAudioDeviceIdRef.current) {\n stopRecording();\n setTimeout(() => {\n startRecording();\n });\n }\n },\n onCancel: () => {\n cancelDetect();\n },\n });\n });\n\n cancelDetect();\n return;\n }\n }\n rafRef.current = requestAnimationFrame(loop);\n };\n loop();\n\n stopSoundListeningRef.current = () => {\n analyser.disconnect();\n source.disconnect();\n ctx.close().catch(() => {\n // Ignore errors\n });\n stopSoundListeningRef.current = undefined;\n };\n });\n\n // Component destroy\n useEffect(() => {\n return stopRecording;\n }, []);\n\n // Stop recording when disabled has been changed\n useEffect(() => {\n if (disabled && isRecording) {\n stopRecording();\n }\n }, [disabled, isRecording]);\n\n // Update PCM stream slicer time slice when input sample rate changes\n useEffect(() => {\n if (streamSliceValue && pcmStreamSlicerRef.current.value !== streamSliceValue) {\n pcmStreamSlicerRef.current.value = streamSliceValue;\n }\n }, [streamSliceValue]);\n\n // Detect sound activity (only for audio or media with audio)\n useEffect(() => {\n if (includeAudio && mediaStream && !isSpeakingRef.current) {\n try {\n waitForSound(mediaStream);\n } catch (e) {\n console.warn('Audio volume detecting failed:', e);\n }\n }\n return () => {\n stopSoundListeningRef.current?.();\n };\n }, [includeAudio, mediaStream, t]);\n\n useEffect(() => {\n import('../assets/request-microphone-en.js').then((module) => {\n setRequestMicrophoneEnUrl(module.default);\n });\n import('../assets/request-microphone-zh.js').then((module) => {\n setRequestMicrophoneZhUrl(module.default);\n });\n import('../assets/reset-microphone-en.js').then((module) => {\n setResetMicrophoneEnUrl(module.default);\n });\n import('../assets/reset-microphone-zh.js').then((module) => {\n setResetMicrophoneZhUrl(module.default);\n });\n import('../assets/request-camera-en.js').then((module) => {\n setRequestCameraEnUrl(module.default);\n });\n import('../assets/request-camera-zh.js').then((module) => {\n setRequestCameraZhUrl(module.default);\n });\n import('../assets/reset-camera-en.js').then((module) => {\n setResetCameraEnUrl(module.default);\n });\n import('../assets/reset-camera-zh.js').then((module) => {\n setResetCameraZhUrl(module.default);\n });\n }, []);\n\n return {\n isRecording,\n startRecording,\n stopRecording,\n };\n};\n\nexport interface UseUserMediaResult {\n /**\n * - **EN** Whether the media stream is currently being recorded\n * - **CN** 是否正在录制媒体流\n */\n isRecording: boolean;\n /**\n * - **EN** Start recording the media stream\n * - **CN** 开始录制媒体流\n */\n startRecording: () => Promise<MediaRecorder>;\n /**\n * - **EN** Stop recording the media stream\n * - **CN** 停止录制媒体流\n */\n stopRecording: () => void;\n /**\n * - **EN** Get the media stream being recorded, returns the stream if recording, otherwise returns\n * `undefined`\n * - **CN** 获取正在录制的媒体流,如果正在录制则返回该流,否则返回 `undefined`\n */\n mediaStream?: MediaStream;\n}\n\nfunction SaveAudioDeviceForm(props: {\n devices: MediaDeviceInfo[];\n mediaStream: MediaStream;\n onDeviceChange: (deviceId: string) => void;\n}) {\n const { devices, mediaStream, onDeviceChange } = props;\n const [form] = Form.useForm();\n const t = useT();\n const { lang } = useContext(ReactEasyContext);\n const [saveAudioDeviceEnUrl1, setSaveAudioDeviceEnUrl1] = useState<string>();\n const [saveAudioDeviceEnUrl2, setSaveAudioDeviceEnUrl2] = useState<string>();\n const [saveAudioDeviceZhUrl1, setSaveAudioDeviceZhUrl1] = useState<string>();\n const [saveAudioDeviceZhUrl2, setSaveAudioDeviceZhUrl2] = useState<string>();\n const [saveDefaultAudioDevicePermanently, setSaveDefaultAudioDevicePermanently] = useState(false);\n const audioInputs = useMemo(() => devices.filter((d) => d.kind === 'audioinput'), [devices]);\n const [selectedDeviceId, setSelectedDeviceId] = useState<string | undefined>(\n () => mediaStream.getAudioTracks()[0]?.getSettings()?.deviceId ?? audioInputs[0]?.deviceId\n );\n\n const openDataImageInNewTab = useRefFunction((dataUrl: string | undefined) => {\n if (!dataUrl) return;\n const [meta, b64] = dataUrl.split(',');\n const mime = meta.match(/data:(.*);base64/)?.[1] || 'image/png';\n const binary = atob(b64);\n const len = binary.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);\n const blob = new Blob([bytes], { type: mime });\n const url = URL.createObjectURL(blob);\n window.open(url);\n });\n\n useEffect(() => {\n import('../assets/save-default-audio1-en.js').then((module) => {\n setSaveAudioDeviceEnUrl1(module.default);\n });\n import('../assets/save-default-audio2-en.js').then((module) => {\n setSaveAudioDeviceEnUrl2(module.default);\n });\n import('../assets/save-default-audio1-zh.js').then((module) => {\n setSaveAudioDeviceZhUrl1(module.default);\n });\n import('../assets/save-default-audio2-zh.js').then((module) => {\n setSaveAudioDeviceZhUrl2(module.default);\n });\n }, []);\n\n return (\n <Form layout=\"vertical\" form={form}>\n <Typography.Paragraph></Typography.Paragraph>\n <Typography.Paragraph>\n <Typography.Text>{t('hooks.useUserMedia.soundDetectDescription')}</Typography.Text>\n </Typography.Paragraph>\n <Form.Item label={t('hooks.useUserMedia.chooseMicrophoneDevice')}>\n <Select\n options={audioInputs.map((input) => ({\n label: input.label,\n value: input.deviceId,\n }))}\n defaultValue={selectedDeviceId}\n onChange={(id) => {\n setSelectedDeviceId(id);\n onDeviceChange(id);\n }}\n />\n </Form.Item>\n <Form.Item style={{ marginBottom: 0 }}>\n <Checkbox onChange={(e) => setSaveDefaultAudioDevicePermanently(e.target.checked)}>\n {t('hooks.useUserMedia.rememberDefaultAudioDevice')}\n </Checkbox>\n </Form.Item>\n {saveDefaultAudioDevicePermanently && (\n <div>\n <Typography.Paragraph>\n <Typography.Text>{t('hooks.useUserMedia.rememberDefaultAudioDeviceTip')}</Typography.Text>\n </Typography.Paragraph>\n <Flex gap={8} align=\"flex-start\">\n <div style={{ flex: 1, minWidth: 0 }}>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n openDataImageInNewTab(lang === 'zh-CN' ? saveAudioDeviceZhUrl1 : saveAudioDeviceEnUrl1);\n }}\n >\n <img\n src={lang === 'zh-CN' ? saveAudioDeviceZhUrl1 : saveAudioDeviceEnUrl1}\n alt=\"the first step to save default audio device\"\n style={{ width: '100%', height: 'auto' }}\n />\n </a>\n </div>\n <div style={{ flex: 1, minWidth: 0 }}>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n openDataImageInNewTab(lang === 'zh-CN' ? saveAudioDeviceZhUrl2 : saveAudioDeviceEnUrl2);\n }}\n >\n <img\n src={lang === 'zh-CN' ? saveAudioDeviceZhUrl2 : saveAudioDeviceEnUrl2}\n alt=\"the second step to save default audio device\"\n style={{ width: '100%', height: 'auto' }}\n />\n </a>\n </div>\n </Flex>\n </div>\n )}\n </Form>\n );\n}\n\nfunction generatePcmCaptureProcessorModule() {\n const workletCode = `\nclass PcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs, outputs, parameters) {\n const channelsIn = inputs[0];\n if (channelsIn && channelsIn[0]) {\n const channels = channelsIn.map((ch) => {\n const copy = new Float32Array(ch.length);\n copy.set(ch);\n return copy;\n });\n this.port.postMessage({ type: 'pcm', channels }, channels.map(ch => ch.buffer));\n }\n return true;\n }\n}\nregisterProcessor('pcm-capture', PcmCaptureProcessor);\n`;\n\n // Create a Blob from the worklet code and return its URL\n const blob = new Blob([workletCode], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n return blobUrl;\n}\n\nfunction fallbackScriptProcessor(options: {\n ctx: AudioContext;\n sourceNode: MediaStreamAudioSourceNode;\n streamSlicer: StreamTimeSlicerClass;\n}) {\n const { ctx, sourceNode, streamSlicer } = options;\n const bufferSize = 128;\n const processor = ctx.createScriptProcessor(bufferSize, 1, 1);\n processor.onaudioprocess = (ev) => {\n const channels = [];\n for (let i = 0; i < ev.inputBuffer.numberOfChannels; i++) {\n const input = ev.inputBuffer.getChannelData(i);\n const copy = new Float32Array(input.length);\n copy.set(input);\n channels.push(copy);\n }\n streamSlicer.push(channels);\n };\n sourceNode.connect(processor);\n // 可不输出:processor.connect(ctx!.destination);不连接 destination 在部分浏览器会被自动 GC,可连到 destination 或 gain(0)\n const gain = ctx.createGain();\n gain.gain.value = 0;\n processor.connect(gain).connect(ctx.destination);\n}\n\nexport default useUserMedia;\n"],
5
+ "mappings": "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",
6
6
  "names": ["ReactEasyContext", "useRefValue", "useT", "_a", "useRefFunction", "t", "mediaStream", "EasyConfigProvider", "module"]
7
7
  }
@@ -0,0 +1,166 @@
1
+ export type AudioSource = string | ReadableStreamDefaultReader<Uint8Array> | ArrayBuffer | Uint8Array | Blob;
2
+ export interface AudioPlayerInit {
3
+ /**
4
+ * - **EN:** Audio source (URL or streaming data)
5
+ * - **CN:** 音频源(URL或流数据)
6
+ */
7
+ source?: AudioSource | (() => AudioSource | Promise<AudioSource>);
8
+ /**
9
+ * - **EN:** Initial volume level (0-1). Default is `0.5`
10
+ * - **CN:** 初始音量级别(0-1)。默认值为`0.5`
11
+ */
12
+ volume?: number;
13
+ /**
14
+ * - **EN:** Callback when audio starts playing
15
+ * - **CN:** 音频开始播放时的回调
16
+ */
17
+ onPlay?: () => void;
18
+ /**
19
+ * - **EN:** Callback when audio is paused
20
+ * - **CN:** 音频暂停时的回调
21
+ */
22
+ onPause?: () => void;
23
+ /**
24
+ * - **EN:** Callback when audio is stopped
25
+ * - **CN:** 音频停止时的回调
26
+ */
27
+ onStop?: () => void;
28
+ /**
29
+ * - **EN:** Callback when audio playback ends
30
+ * - **CN:** 音频播放结束时的回调
31
+ */
32
+ onPlayEnd?: () => void;
33
+ /**
34
+ * - **EN:** Callback when an error occurs
35
+ * - **CN:** 发生错误时的回调
36
+ */
37
+ onError?: (error: any) => void;
38
+ }
39
+ /**
40
+ * - **EN:** An audio player class that supports URL or streaming data input
41
+ * - **CN:** 一个音频播放器类,支持URL或流数据输入
42
+ */
43
+ declare class AudioPlayer {
44
+ private audio;
45
+ private _volume;
46
+ private audioContext;
47
+ private gainNode;
48
+ private sourceNode;
49
+ private options;
50
+ private onPlayEnd;
51
+ /**
52
+ * - **EN:** Creates an audio player instance
53
+ * - **CN:** 创建音频播放器实例
54
+ *
55
+ * @param source - can be a URL string or ReadableStreamDefaultReader |
56
+ * 可以是URL字符串或ReadableStreamDefaultReader
57
+ */
58
+ constructor(options?: AudioPlayerInit);
59
+ /**
60
+ * - **EN:** Check if audio is currently playing
61
+ * - **CN:** 检查音频是否正在播放
62
+ */
63
+ get isPlaying(): boolean;
64
+ /**
65
+ * - **EN:** Get current playback time (seconds)
66
+ * - **CN:** 获取当前播放时间(秒)
67
+ */
68
+ get currentTime(): number;
69
+ /**
70
+ * - **EN:** Get total audio duration (seconds)
71
+ * - **CN:** 获取音频总时长(秒)
72
+ */
73
+ get duration(): number;
74
+ /**
75
+ * - **EN:** Get current volume value (0-1)
76
+ * - **CN:** 获取当前音量值(0-1)
77
+ */
78
+ get volume(): number;
79
+ /**
80
+ * - **EN:** Play audio. If previously paused, will resume from the pause position
81
+ * - **CN:** 播放音频 如果之前暂停过,将从暂停位置继续播放
82
+ */
83
+ play(): Promise<void>;
84
+ /**
85
+ * - **EN:** Seek forward by a certain number of seconds
86
+ * - **CN:** 向前跳转一定秒数
87
+ *
88
+ * @param seconds - number of seconds to seek forward | 要向前跳转的秒数
89
+ */
90
+ seekForward(seconds: number): void;
91
+ /**
92
+ * - **EN:** Seek backward by a certain number of seconds
93
+ * - **CN:** 向后跳转一定秒数
94
+ *
95
+ * @param seconds - number of seconds to seek backward | 要向后跳转的秒数
96
+ */
97
+ seekBackward(seconds: number): void;
98
+ /**
99
+ * - **EN:** Set current playback time (in seconds)
100
+ * - **CN:** 设置当前播放时间(以秒为单位)
101
+ *
102
+ * @param time - time in seconds | 时间(秒)
103
+ */
104
+ seek(time: number): void;
105
+ /**
106
+ * - **EN:** Pause audio playback. When played again, will continue from current position
107
+ * - **CN:** 暂停音频播放 再次播放时将从当前位置继续
108
+ */
109
+ pause(): void;
110
+ /**
111
+ * - **EN:** Stop audio playback. Progress will reset to the beginning
112
+ * - **CN:** 停止音频播放 进度会重置到开始位置
113
+ */
114
+ stop(): void;
115
+ /**
116
+ * - **EN:** Update audio source
117
+ * - **CN:** 更新音频源
118
+ *
119
+ * @param source - can be a URL `string` or `ReadableStreamDefaultReader` |
120
+ * 可以是URL字符串或`ReadableStreamDefaultReader`
121
+ */
122
+ setAudioSource(source?: AudioSource): Promise<void>;
123
+ /**
124
+ * - **EN:** Increase volume (by 10% each time)
125
+ * - **CN:** 增加音量(每次增加10%)
126
+ *
127
+ * @param percent - increase percentage (default 10%) | 增加百分比(默认10%)
128
+ */
129
+ volumeUp(percent?: number): void;
130
+ /**
131
+ * - **EN:** Decrease volume (by 10% each time)
132
+ * - **CN:** 降低音量(每次降低10%)
133
+ *
134
+ * @param percent - decrease percentage (default 10%) | 降低百分比(默认10%)
135
+ */
136
+ volumeDown(percent?: number): void;
137
+ /**
138
+ * - **EN:** Set volume to a specific value (0-1)
139
+ * - **CN:** 将音量设置为特定值(0-1)
140
+ *
141
+ * @param value - new volume value (0-1) | 新的音量值(0-1)
142
+ */
143
+ setVolume(value: number): void;
144
+ /**
145
+ * - **EN:** Add audio event listener
146
+ * - **CN:** 添加音频事件监听器
147
+ */
148
+ addEventListener: HTMLAudioElement['addEventListener'];
149
+ /**
150
+ * - **EN:** Remove audio event listener
151
+ * - **CN:** 移除音频事件监听器
152
+ */
153
+ removeEventListener: HTMLAudioElement['removeEventListener'];
154
+ /**
155
+ * - **EN:** Release resources
156
+ * - **CN:** 释放资源
157
+ */
158
+ dispose(): void;
159
+ /** Process streaming data source */
160
+ private handleStreamSource;
161
+ /** Initialize Web Audio API for better volume control */
162
+ private initAudioContext;
163
+ /** Update audio playback volume */
164
+ private updateVolume;
165
+ }
166
+ export default AudioPlayer;
@@ -0,0 +1,305 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/utils/AudioPlayer.ts
20
+ var AudioPlayer_exports = {};
21
+ __export(AudioPlayer_exports, {
22
+ default: () => AudioPlayer_default
23
+ });
24
+ module.exports = __toCommonJS(AudioPlayer_exports);
25
+ var AudioPlayer = class {
26
+ /**
27
+ * - **EN:** Creates an audio player instance
28
+ * - **CN:** 创建音频播放器实例
29
+ *
30
+ * @param source - can be a URL string or ReadableStreamDefaultReader |
31
+ * 可以是URL字符串或ReadableStreamDefaultReader
32
+ */
33
+ constructor(options) {
34
+ this.audioContext = null;
35
+ this.gainNode = null;
36
+ this.sourceNode = null;
37
+ /**
38
+ * - **EN:** Add audio event listener
39
+ * - **CN:** 添加音频事件监听器
40
+ */
41
+ this.addEventListener = (event, listener) => {
42
+ this.audio.addEventListener(event, listener);
43
+ };
44
+ /**
45
+ * - **EN:** Remove audio event listener
46
+ * - **CN:** 移除音频事件监听器
47
+ */
48
+ this.removeEventListener = (event, listener) => {
49
+ this.audio.removeEventListener(event, listener);
50
+ };
51
+ const { source, volume } = options || {};
52
+ this.options = options;
53
+ this.audio = new Audio();
54
+ this._volume = volume != null ? Math.min(1, Math.max(0, volume)) : 0.5;
55
+ this.audio.volume = this._volume;
56
+ if (typeof source === "function") {
57
+ const result = source();
58
+ if (typeof result === "object" && "then" in result && typeof result.then === "function") {
59
+ result.then((data) => this.setAudioSource(data));
60
+ } else {
61
+ this.setAudioSource(result);
62
+ }
63
+ } else {
64
+ this.setAudioSource(source);
65
+ }
66
+ this.onPlayEnd = () => {
67
+ var _a, _b;
68
+ (_b = (_a = this.options) == null ? void 0 : _a.onPlayEnd) == null ? void 0 : _b.call(_a);
69
+ };
70
+ this.addEventListener("ended", this.onPlayEnd);
71
+ }
72
+ /**
73
+ * - **EN:** Check if audio is currently playing
74
+ * - **CN:** 检查音频是否正在播放
75
+ */
76
+ get isPlaying() {
77
+ var _a;
78
+ return ((_a = this.audioContext) == null ? void 0 : _a.state) === "running";
79
+ }
80
+ /**
81
+ * - **EN:** Get current playback time (seconds)
82
+ * - **CN:** 获取当前播放时间(秒)
83
+ */
84
+ get currentTime() {
85
+ return this.audio.currentTime;
86
+ }
87
+ /**
88
+ * - **EN:** Get total audio duration (seconds)
89
+ * - **CN:** 获取音频总时长(秒)
90
+ */
91
+ get duration() {
92
+ return this.audio.duration;
93
+ }
94
+ /**
95
+ * - **EN:** Get current volume value (0-1)
96
+ * - **CN:** 获取当前音量值(0-1)
97
+ */
98
+ get volume() {
99
+ return this._volume;
100
+ }
101
+ /**
102
+ * - **EN:** Play audio. If previously paused, will resume from the pause position
103
+ * - **CN:** 播放音频 如果之前暂停过,将从暂停位置继续播放
104
+ */
105
+ async play() {
106
+ var _a, _b, _c, _d, _e;
107
+ if (!this.audioContext) {
108
+ this.initAudioContext();
109
+ }
110
+ if (((_a = this.audioContext) == null ? void 0 : _a.state) === "suspended") {
111
+ await this.audioContext.resume();
112
+ }
113
+ try {
114
+ await this.audio.play();
115
+ (_c = (_b = this.options) == null ? void 0 : _b.onPlay) == null ? void 0 : _c.call(_b);
116
+ } catch (error) {
117
+ console.error("Error playing audio:", error);
118
+ (_e = (_d = this.options) == null ? void 0 : _d.onError) == null ? void 0 : _e.call(_d, error);
119
+ }
120
+ }
121
+ /**
122
+ * - **EN:** Seek forward by a certain number of seconds
123
+ * - **CN:** 向前跳转一定秒数
124
+ *
125
+ * @param seconds - number of seconds to seek forward | 要向前跳转的秒数
126
+ */
127
+ seekForward(seconds) {
128
+ if (seconds < 0) {
129
+ return;
130
+ }
131
+ if (!isNaN(this.audio.duration)) {
132
+ this.audio.currentTime = Math.min(this.audio.currentTime + seconds, this.audio.duration);
133
+ } else {
134
+ this.audio.currentTime += seconds;
135
+ }
136
+ }
137
+ /**
138
+ * - **EN:** Seek backward by a certain number of seconds
139
+ * - **CN:** 向后跳转一定秒数
140
+ *
141
+ * @param seconds - number of seconds to seek backward | 要向后跳转的秒数
142
+ */
143
+ seekBackward(seconds) {
144
+ if (seconds < 0) {
145
+ return;
146
+ }
147
+ this.audio.currentTime = Math.max(this.audio.currentTime - seconds, 0);
148
+ }
149
+ /**
150
+ * - **EN:** Set current playback time (in seconds)
151
+ * - **CN:** 设置当前播放时间(以秒为单位)
152
+ *
153
+ * @param time - time in seconds | 时间(秒)
154
+ */
155
+ seek(time) {
156
+ const newTime = Math.max(0, time);
157
+ if (!isNaN(this.audio.duration)) {
158
+ this.audio.currentTime = Math.min(newTime, this.audio.duration);
159
+ } else {
160
+ this.audio.currentTime = newTime;
161
+ }
162
+ }
163
+ /**
164
+ * - **EN:** Pause audio playback. When played again, will continue from current position
165
+ * - **CN:** 暂停音频播放 再次播放时将从当前位置继续
166
+ */
167
+ pause() {
168
+ var _a, _b;
169
+ this.audio.pause();
170
+ (_b = (_a = this.options) == null ? void 0 : _a.onPause) == null ? void 0 : _b.call(_a);
171
+ }
172
+ /**
173
+ * - **EN:** Stop audio playback. Progress will reset to the beginning
174
+ * - **CN:** 停止音频播放 进度会重置到开始位置
175
+ */
176
+ stop() {
177
+ var _a, _b;
178
+ this.audio.pause();
179
+ this.audio.currentTime = 0;
180
+ (_b = (_a = this.options) == null ? void 0 : _a.onStop) == null ? void 0 : _b.call(_a);
181
+ }
182
+ /**
183
+ * - **EN:** Update audio source
184
+ * - **CN:** 更新音频源
185
+ *
186
+ * @param source - can be a URL `string` or `ReadableStreamDefaultReader` |
187
+ * 可以是URL字符串或`ReadableStreamDefaultReader`
188
+ */
189
+ async setAudioSource(source) {
190
+ this.audio.pause();
191
+ this.audio.src = "";
192
+ if (typeof source === "string") {
193
+ this.audio.src = source;
194
+ } else {
195
+ await this.handleStreamSource(source);
196
+ }
197
+ }
198
+ /**
199
+ * - **EN:** Increase volume (by 10% each time)
200
+ * - **CN:** 增加音量(每次增加10%)
201
+ *
202
+ * @param percent - increase percentage (default 10%) | 增加百分比(默认10%)
203
+ */
204
+ volumeUp(percent = 0.1) {
205
+ this._volume = Math.min(1, this._volume + percent);
206
+ this.updateVolume();
207
+ }
208
+ /**
209
+ * - **EN:** Decrease volume (by 10% each time)
210
+ * - **CN:** 降低音量(每次降低10%)
211
+ *
212
+ * @param percent - decrease percentage (default 10%) | 降低百分比(默认10%)
213
+ */
214
+ volumeDown(percent = 0.1) {
215
+ this._volume = Math.max(0, this._volume - percent);
216
+ this.updateVolume();
217
+ }
218
+ /**
219
+ * - **EN:** Set volume to a specific value (0-1)
220
+ * - **CN:** 将音量设置为特定值(0-1)
221
+ *
222
+ * @param value - new volume value (0-1) | 新的音量值(0-1)
223
+ */
224
+ setVolume(value) {
225
+ this._volume = Math.min(1, Math.max(0, value));
226
+ this.updateVolume();
227
+ }
228
+ /**
229
+ * - **EN:** Release resources
230
+ * - **CN:** 释放资源
231
+ */
232
+ dispose() {
233
+ this.audio.pause();
234
+ this.audio.src = "";
235
+ this.removeEventListener("ended", this.onPlayEnd);
236
+ if (this.audioContext) {
237
+ try {
238
+ this.audioContext.close();
239
+ } catch (error) {
240
+ console.error("Error closing AudioContext:", error);
241
+ }
242
+ this.audioContext = null;
243
+ }
244
+ this.sourceNode = null;
245
+ this.gainNode = null;
246
+ }
247
+ /** Process streaming data source */
248
+ async handleStreamSource(source) {
249
+ if (!source)
250
+ return;
251
+ try {
252
+ let blob;
253
+ if (source instanceof Blob) {
254
+ blob = source;
255
+ } else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
256
+ blob = new Blob([source]);
257
+ } else {
258
+ const stream = new ReadableStream({
259
+ async pull(controller) {
260
+ try {
261
+ const { done, value } = await source.read();
262
+ if (done) {
263
+ controller.close();
264
+ } else {
265
+ controller.enqueue(value);
266
+ }
267
+ } catch (err) {
268
+ controller.error(err);
269
+ }
270
+ }
271
+ });
272
+ const response = new Response(stream);
273
+ blob = await response.blob();
274
+ }
275
+ const url = URL.createObjectURL(blob);
276
+ this.audio.src = url;
277
+ this.audio.onload = () => {
278
+ URL.revokeObjectURL(url);
279
+ };
280
+ } catch (error) {
281
+ console.error("Error processing audio stream:", error);
282
+ }
283
+ }
284
+ /** Initialize Web Audio API for better volume control */
285
+ initAudioContext() {
286
+ if (this.audioContext)
287
+ return;
288
+ this.audioContext = new AudioContext();
289
+ this.sourceNode = this.audioContext.createMediaElementSource(this.audio);
290
+ this.gainNode = this.audioContext.createGain();
291
+ this.sourceNode.connect(this.gainNode);
292
+ this.gainNode.connect(this.audioContext.destination);
293
+ this.gainNode.gain.value = this._volume;
294
+ }
295
+ /** Update audio playback volume */
296
+ updateVolume() {
297
+ if (this.gainNode) {
298
+ this.gainNode.gain.value = this._volume;
299
+ } else {
300
+ this.audio.volume = this._volume;
301
+ }
302
+ }
303
+ };
304
+ var AudioPlayer_default = AudioPlayer;
305
+ //# sourceMappingURL=AudioPlayer.js.map