@exode-team/react-recorder 1.1.12 → 1.1.15

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 (92) hide show
  1. package/README.md +2 -2
  2. package/dist/components/AudioRecorder.js +50 -0
  3. package/dist/components/AudioRecorder.js.map +1 -0
  4. package/dist/components/AudioRecorder.messages.js +47 -0
  5. package/dist/components/AudioRecorder.messages.js.map +1 -0
  6. package/dist/components/AudioRecorder.styled.js +16 -0
  7. package/dist/components/AudioRecorder.styled.js.map +1 -0
  8. package/dist/components/IdleControl/IdleControl.js +12 -0
  9. package/dist/components/IdleControl/IdleControl.js.map +1 -0
  10. package/dist/components/IdleControl/IdleControl.styled.js +68 -0
  11. package/dist/components/IdleControl/IdleControl.styled.js.map +1 -0
  12. package/dist/components/RecordingShell/RecordingShell.js +80 -0
  13. package/dist/components/RecordingShell/RecordingShell.js.map +1 -0
  14. package/dist/components/RecordingShell/RecordingShell.styled.js +159 -0
  15. package/dist/components/RecordingShell/RecordingShell.styled.js.map +1 -0
  16. package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js +151 -0
  17. package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js.map +1 -0
  18. package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js +92 -0
  19. package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js.map +1 -0
  20. package/dist/components/VoiceRecorderInput/PreviewBars.js +52 -0
  21. package/dist/components/VoiceRecorderInput/PreviewBars.js.map +1 -0
  22. package/dist/components/VoiceRecorderInput/TimelineBars.js +31 -0
  23. package/dist/components/VoiceRecorderInput/TimelineBars.js.map +1 -0
  24. package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js +51 -0
  25. package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js.map +1 -0
  26. package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js +175 -0
  27. package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js.map +1 -0
  28. package/dist/components/WaveformBars/WaveformBars.js +29 -0
  29. package/dist/components/WaveformBars/WaveformBars.js.map +1 -0
  30. package/dist/components/WaveformBars/WaveformBars.styled.js +20 -0
  31. package/dist/components/WaveformBars/WaveformBars.styled.js.map +1 -0
  32. package/dist/constants.js +17 -0
  33. package/dist/constants.js.map +1 -0
  34. package/dist/hooks/useAudioBlobLoader.d.ts +1 -5
  35. package/dist/hooks/useAudioBlobLoader.d.ts.map +1 -1
  36. package/dist/hooks/useAudioBlobLoader.js +175 -0
  37. package/dist/hooks/useAudioBlobLoader.js.map +1 -0
  38. package/dist/hooks/useAudioPlayback.js +119 -0
  39. package/dist/hooks/useAudioPlayback.js.map +1 -0
  40. package/dist/hooks/useAudioRecorderHandlers.js +137 -0
  41. package/dist/hooks/useAudioRecorderHandlers.js.map +1 -0
  42. package/dist/hooks/useMediaRecorder.js +127 -0
  43. package/dist/hooks/useMediaRecorder.js.map +1 -0
  44. package/dist/hooks/useRecorderVisualization.js +94 -0
  45. package/dist/hooks/useRecorderVisualization.js.map +1 -0
  46. package/dist/hooks/useRecordingSender.js +53 -0
  47. package/dist/hooks/useRecordingSender.js.map +1 -0
  48. package/dist/hooks/useRecordingTiming.js +93 -0
  49. package/dist/hooks/useRecordingTiming.js.map +1 -0
  50. package/dist/hooks/useVoiceMessagePlayer.js +177 -0
  51. package/dist/hooks/useVoiceMessagePlayer.js.map +1 -0
  52. package/dist/hooks/useVoiceRecorder.js +153 -0
  53. package/dist/hooks/useVoiceRecorder.js.map +1 -0
  54. package/dist/hooks/useVoiceRecorderInput.js +163 -0
  55. package/dist/hooks/useVoiceRecorderInput.js.map +1 -0
  56. package/dist/index.cjs +108 -61
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.js +13 -2612
  59. package/dist/index.js.map +1 -1
  60. package/dist/platform/animation-frame-adapter.js +14 -0
  61. package/dist/platform/animation-frame-adapter.js.map +1 -0
  62. package/dist/platform/audio-analyzer-adapter.d.ts.map +1 -1
  63. package/dist/platform/audio-analyzer-adapter.js +74 -0
  64. package/dist/platform/audio-analyzer-adapter.js.map +1 -0
  65. package/dist/platform/audio-blob-fetcher.js +19 -0
  66. package/dist/platform/audio-blob-fetcher.js.map +1 -0
  67. package/dist/platform/audio-playback-adapter.d.ts.map +1 -1
  68. package/dist/platform/audio-playback-adapter.js +114 -0
  69. package/dist/platform/audio-playback-adapter.js.map +1 -0
  70. package/dist/platform/audio-playback-manager.js +26 -0
  71. package/dist/platform/audio-playback-manager.js.map +1 -0
  72. package/dist/platform/media-recorder-adapter.js +23 -0
  73. package/dist/platform/media-recorder-adapter.js.map +1 -0
  74. package/dist/platform/microphone.js +24 -0
  75. package/dist/platform/microphone.js.map +1 -0
  76. package/dist/platform/mpeg-frame.js +60 -0
  77. package/dist/platform/mpeg-frame.js.map +1 -0
  78. package/dist/utils/format.js +13 -0
  79. package/dist/utils/format.js.map +1 -0
  80. package/dist/utils/index.d.ts +1 -0
  81. package/dist/utils/index.d.ts.map +1 -1
  82. package/dist/utils/math.js +16 -0
  83. package/dist/utils/math.js.map +1 -0
  84. package/dist/utils/pseudo-waveform.d.ts +12 -0
  85. package/dist/utils/pseudo-waveform.d.ts.map +1 -0
  86. package/dist/utils/pseudo-waveform.js +40 -0
  87. package/dist/utils/pseudo-waveform.js.map +1 -0
  88. package/dist/utils/voice-blob-cache.js +35 -0
  89. package/dist/utils/voice-blob-cache.js.map +1 -0
  90. package/dist/utils/voice-playback-chain.js +42 -0
  91. package/dist/utils/voice-playback-chain.js.map +1 -0
  92. package/package.json +2 -1
@@ -0,0 +1,127 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+ import { requestMicrophoneAccess, classifyMicError } from '../platform/microphone.js';
3
+ import { stopStream, createRecorderSession } from '../platform/media-recorder-adapter.js';
4
+
5
+ /**
6
+ * useMediaRecorder
7
+ *
8
+ * @author: exode <hello@exode.ru>
9
+ */
10
+ const useMediaRecorder = (callbacks) => {
11
+ const [isRecording, setIsRecording] = useState(false);
12
+ const [isPaused, setIsPaused] = useState(false);
13
+ const mediaRecorderRef = useRef(null);
14
+ const mediaStreamRef = useRef(null);
15
+ const audioChunksRef = useRef([]);
16
+ const discardRef = useRef(false);
17
+ const needsPreviewRef = useRef(false);
18
+ const cleanupStream = useCallback(() => {
19
+ if (mediaStreamRef.current) {
20
+ stopStream(mediaStreamRef.current);
21
+ mediaStreamRef.current = null;
22
+ }
23
+ }, []);
24
+ const startRecording = useCallback(async (mimeType) => {
25
+ try {
26
+ const stream = await requestMicrophoneAccess();
27
+ mediaStreamRef.current = stream;
28
+ callbacks.onStreamAcquired(stream);
29
+ audioChunksRef.current = [];
30
+ discardRef.current = false;
31
+ const { recorder } = createRecorderSession(stream, {
32
+ onDataAvailable: (event) => {
33
+ if (event.data.size > 0) {
34
+ audioChunksRef.current.push(event.data);
35
+ callbacks.onDataChunk(event.data);
36
+ }
37
+ if (needsPreviewRef.current) {
38
+ callbacks.onNeedsPreview(audioChunksRef.current);
39
+ needsPreviewRef.current = false;
40
+ }
41
+ },
42
+ onStop: () => {
43
+ const discarded = discardRef.current;
44
+ discardRef.current = false;
45
+ mediaRecorderRef.current = null;
46
+ cleanupStream();
47
+ setIsRecording(false);
48
+ setIsPaused(false);
49
+ callbacks.onStopped(audioChunksRef.current, discarded);
50
+ },
51
+ onPause: () => {
52
+ setIsPaused(true);
53
+ callbacks.onPaused();
54
+ },
55
+ onResume: () => {
56
+ setIsPaused(false);
57
+ needsPreviewRef.current = false;
58
+ callbacks.onResumed();
59
+ },
60
+ }, { mimeType });
61
+ mediaRecorderRef.current = recorder;
62
+ recorder.start();
63
+ setIsRecording(true);
64
+ setIsPaused(false);
65
+ }
66
+ catch (error) {
67
+ console.error('[audio-recorder] Error accessing microphone:', error);
68
+ const kind = classifyMicError(error);
69
+ if (kind === 'permission') {
70
+ callbacks.onPermissionDenied(error);
71
+ }
72
+ else {
73
+ callbacks.onError(error);
74
+ }
75
+ }
76
+ }, [callbacks, cleanupStream]);
77
+ const stopRecording = useCallback(() => {
78
+ if (!mediaRecorderRef.current || !isRecording) {
79
+ return;
80
+ }
81
+ if (mediaRecorderRef.current.state === 'paused') {
82
+ mediaRecorderRef.current.resume();
83
+ }
84
+ mediaRecorderRef.current.stop();
85
+ }, [isRecording]);
86
+ const cancelRecording = useCallback(() => {
87
+ if (!mediaRecorderRef.current) {
88
+ return;
89
+ }
90
+ discardRef.current = true;
91
+ needsPreviewRef.current = false;
92
+ if (mediaRecorderRef.current.state !== 'inactive') {
93
+ mediaRecorderRef.current.stop();
94
+ }
95
+ }, []);
96
+ const togglePause = useCallback(() => {
97
+ if (!mediaRecorderRef.current || !isRecording) {
98
+ return;
99
+ }
100
+ if (mediaRecorderRef.current.state === 'recording') {
101
+ needsPreviewRef.current = true;
102
+ try {
103
+ mediaRecorderRef.current.requestData();
104
+ }
105
+ catch (error) {
106
+ console.warn('[audio-recorder] requestData failed', error);
107
+ }
108
+ mediaRecorderRef.current.pause();
109
+ }
110
+ else if (mediaRecorderRef.current.state === 'paused') {
111
+ needsPreviewRef.current = false;
112
+ mediaRecorderRef.current.resume();
113
+ }
114
+ }, [isRecording]);
115
+ return {
116
+ isPaused,
117
+ isRecording,
118
+ togglePause,
119
+ cleanupStream,
120
+ stopRecording,
121
+ startRecording,
122
+ cancelRecording,
123
+ };
124
+ };
125
+
126
+ export { useMediaRecorder };
127
+ //# sourceMappingURL=useMediaRecorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMediaRecorder.js","sources":["../../src/hooks/useMediaRecorder.ts"],"sourcesContent":["/**\n * useMediaRecorder\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useRef, useState } from 'react';\n\nimport { classifyMicError, createRecorderSession, requestMicrophoneAccess, stopStream } from '../platform';\n\nimport type { MediaRecorderCallbacks } from '../types';\n\n\ninterface UseMediaRecorderResult {\n isRecording: boolean;\n isPaused: boolean;\n startRecording: (mimeType: string) => Promise<void>;\n stopRecording: () => void;\n cancelRecording: () => void;\n togglePause: () => void;\n cleanupStream: () => void;\n}\n\nconst useMediaRecorder = (callbacks: MediaRecorderCallbacks): UseMediaRecorderResult => {\n\n const [ isRecording, setIsRecording ] = useState(false);\n const [ isPaused, setIsPaused ] = useState(false);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const mediaStreamRef = useRef<MediaStream | null>(null);\n const audioChunksRef = useRef<Blob[]>([]);\n const discardRef = useRef(false);\n const needsPreviewRef = useRef(false);\n\n const cleanupStream = useCallback(() => {\n if (mediaStreamRef.current) {\n stopStream(mediaStreamRef.current);\n mediaStreamRef.current = null;\n }\n }, []);\n\n const startRecording = useCallback(async (mimeType: string) => {\n try {\n const stream = await requestMicrophoneAccess();\n\n mediaStreamRef.current = stream;\n callbacks.onStreamAcquired(stream);\n\n audioChunksRef.current = [];\n discardRef.current = false;\n\n const { recorder } = createRecorderSession(stream, {\n onDataAvailable: (event) => {\n if (event.data.size > 0) {\n audioChunksRef.current.push(event.data);\n callbacks.onDataChunk(event.data);\n }\n\n if (needsPreviewRef.current) {\n callbacks.onNeedsPreview(audioChunksRef.current);\n needsPreviewRef.current = false;\n }\n },\n onStop: () => {\n const discarded = discardRef.current;\n\n discardRef.current = false;\n mediaRecorderRef.current = null;\n cleanupStream();\n setIsRecording(false);\n setIsPaused(false);\n\n callbacks.onStopped(audioChunksRef.current, discarded);\n },\n onPause: () => {\n setIsPaused(true);\n callbacks.onPaused();\n },\n onResume: () => {\n setIsPaused(false);\n needsPreviewRef.current = false;\n callbacks.onResumed();\n },\n }, { mimeType });\n\n mediaRecorderRef.current = recorder;\n recorder.start();\n setIsRecording(true);\n setIsPaused(false);\n } catch (error) {\n console.error('[audio-recorder] Error accessing microphone:', error);\n\n const kind = classifyMicError(error);\n\n if (kind === 'permission') {\n callbacks.onPermissionDenied(error);\n } else {\n callbacks.onError(error);\n }\n }\n }, [ callbacks, cleanupStream ]);\n\n const stopRecording = useCallback(() => {\n if (!mediaRecorderRef.current || !isRecording) {\n return;\n }\n\n if (mediaRecorderRef.current.state === 'paused') {\n mediaRecorderRef.current.resume();\n }\n\n mediaRecorderRef.current.stop();\n }, [ isRecording ]);\n\n const cancelRecording = useCallback(() => {\n if (!mediaRecorderRef.current) {\n return;\n }\n\n discardRef.current = true;\n needsPreviewRef.current = false;\n\n if (mediaRecorderRef.current.state !== 'inactive') {\n mediaRecorderRef.current.stop();\n }\n }, []);\n\n const togglePause = useCallback(() => {\n if (!mediaRecorderRef.current || !isRecording) {\n return;\n }\n\n if (mediaRecorderRef.current.state === 'recording') {\n needsPreviewRef.current = true;\n\n try {\n mediaRecorderRef.current.requestData();\n } catch (error) {\n console.warn('[audio-recorder] requestData failed', error);\n }\n\n mediaRecorderRef.current.pause();\n } else if (mediaRecorderRef.current.state === 'paused') {\n needsPreviewRef.current = false;\n mediaRecorderRef.current.resume();\n }\n }, [ isRecording ]);\n\n return {\n isPaused,\n isRecording,\n togglePause,\n cleanupStream,\n stopRecording,\n startRecording,\n cancelRecording,\n };\n};\n\n\nexport { useMediaRecorder };\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+ import { createInitialLevels } from '../constants.js';
3
+ import { createStreamAnalyzer, decodeAudioLevels, readFrequencyLevels } from '../platform/audio-analyzer-adapter.js';
4
+ import { cancelFrame, scheduleFrame } from '../platform/animation-frame-adapter.js';
5
+
6
+ /**
7
+ * useRecorderVisualization
8
+ *
9
+ * @author: exode <hello@exode.ru>
10
+ */
11
+ const useRecorderVisualization = () => {
12
+ const [audioLevels, setAudioLevels] = useState(createInitialLevels);
13
+ const [previewLevels, setPreviewLevels] = useState(createInitialLevels);
14
+ const analyzerRef = useRef(null);
15
+ const animationFrameRef = useRef(null);
16
+ const resetAudioLevels = useCallback(() => {
17
+ setAudioLevels(createInitialLevels);
18
+ }, []);
19
+ const resetPreviewLevels = useCallback(() => {
20
+ setPreviewLevels(createInitialLevels);
21
+ }, []);
22
+ const stopVisualization = useCallback((options) => {
23
+ var _a, _b;
24
+ if (animationFrameRef.current) {
25
+ cancelFrame(animationFrameRef.current);
26
+ animationFrameRef.current = null;
27
+ }
28
+ if (((_a = options === null || options === void 0 ? void 0 : options.closeContext) !== null && _a !== void 0 ? _a : true) && analyzerRef.current) {
29
+ void analyzerRef.current.context.close();
30
+ analyzerRef.current = null;
31
+ }
32
+ if ((_b = options === null || options === void 0 ? void 0 : options.resetLevels) !== null && _b !== void 0 ? _b : true) {
33
+ resetAudioLevels();
34
+ }
35
+ }, [resetAudioLevels]);
36
+ const visualize = useCallback(() => {
37
+ if (!analyzerRef.current) {
38
+ return;
39
+ }
40
+ const { analyser } = analyzerRef.current;
41
+ const draw = () => {
42
+ animationFrameRef.current = scheduleFrame(draw);
43
+ const levels = readFrequencyLevels(analyser);
44
+ setAudioLevels(levels);
45
+ };
46
+ draw();
47
+ }, []);
48
+ const resumeVisualization = useCallback(() => {
49
+ if (analyzerRef.current && !animationFrameRef.current) {
50
+ visualize();
51
+ }
52
+ }, [visualize]);
53
+ const initializeVisualization = useCallback((stream) => {
54
+ analyzerRef.current = createStreamAnalyzer(stream);
55
+ visualize();
56
+ }, [visualize]);
57
+ const generatePreviewLevels = useCallback(async (blob) => {
58
+ try {
59
+ const levels = await decodeAudioLevels(blob);
60
+ setAudioLevels(levels);
61
+ setPreviewLevels(levels);
62
+ }
63
+ catch (error) {
64
+ console.warn('[audio-recorder] Failed to build waveform preview', error);
65
+ setPreviewLevels(createInitialLevels());
66
+ }
67
+ }, []);
68
+ const cleanupVisualization = useCallback(() => {
69
+ if (animationFrameRef.current) {
70
+ cancelFrame(animationFrameRef.current);
71
+ }
72
+ animationFrameRef.current = null;
73
+ if (analyzerRef.current) {
74
+ void analyzerRef.current.context.close();
75
+ analyzerRef.current = null;
76
+ }
77
+ resetAudioLevels();
78
+ resetPreviewLevels();
79
+ }, [resetAudioLevels, resetPreviewLevels]);
80
+ return {
81
+ audioLevels,
82
+ previewLevels,
83
+ resetAudioLevels,
84
+ stopVisualization,
85
+ resetPreviewLevels,
86
+ resumeVisualization,
87
+ cleanupVisualization,
88
+ generatePreviewLevels,
89
+ initializeVisualization,
90
+ };
91
+ };
92
+
93
+ export { useRecorderVisualization };
94
+ //# sourceMappingURL=useRecorderVisualization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRecorderVisualization.js","sources":["../../src/hooks/useRecorderVisualization.ts"],"sourcesContent":["/**\n * useRecorderVisualization\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useRef, useState } from 'react';\n\nimport { createInitialLevels } from '../constants';\n\nimport type { StreamAnalyzer } from '../platform';\nimport { cancelFrame, createStreamAnalyzer, decodeAudioLevels, readFrequencyLevels, scheduleFrame } from '../platform';\n\n\ninterface UseRecorderVisualizationResult {\n audioLevels: number[];\n previewLevels: number[];\n resetAudioLevels: () => void;\n resetPreviewLevels: () => void;\n resumeVisualization: () => void;\n cleanupVisualization: () => void;\n generatePreviewLevels: (blob: Blob) => Promise<void>;\n initializeVisualization: (stream: MediaStream) => void;\n stopVisualization: (options?: { resetLevels?: boolean; closeContext?: boolean }) => void;\n}\n\n\nconst useRecorderVisualization = (): UseRecorderVisualizationResult => {\n\n const [ audioLevels, setAudioLevels ] = useState<number[]>(createInitialLevels);\n const [ previewLevels, setPreviewLevels ] = useState<number[]>(createInitialLevels);\n\n const analyzerRef = useRef<StreamAnalyzer | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const resetAudioLevels = useCallback(() => {\n setAudioLevels(createInitialLevels);\n }, []);\n\n const resetPreviewLevels = useCallback(() => {\n setPreviewLevels(createInitialLevels);\n }, []);\n\n const stopVisualization = useCallback(\n (options?: { resetLevels?: boolean; closeContext?: boolean }) => {\n if (animationFrameRef.current) {\n cancelFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n if ((options?.closeContext ?? true) && analyzerRef.current) {\n void analyzerRef.current.context.close();\n analyzerRef.current = null;\n }\n\n if (options?.resetLevels ?? true) {\n resetAudioLevels();\n }\n },\n [ resetAudioLevels ],\n );\n\n const visualize = useCallback(() => {\n if (!analyzerRef.current) {\n return;\n }\n\n const { analyser } = analyzerRef.current;\n\n const draw = () => {\n animationFrameRef.current = scheduleFrame(draw);\n\n const levels = readFrequencyLevels(analyser);\n setAudioLevels(levels);\n };\n\n draw();\n }, []);\n\n const resumeVisualization = useCallback(() => {\n if (analyzerRef.current && !animationFrameRef.current) {\n visualize();\n }\n }, [ visualize ]);\n\n const initializeVisualization = useCallback(\n (stream: MediaStream) => {\n analyzerRef.current = createStreamAnalyzer(stream);\n visualize();\n },\n [ visualize ],\n );\n\n const generatePreviewLevels = useCallback(async (blob: Blob) => {\n try {\n const levels = await decodeAudioLevels(blob);\n\n setAudioLevels(levels);\n setPreviewLevels(levels);\n } catch (error) {\n console.warn('[audio-recorder] Failed to build waveform preview', error);\n setPreviewLevels(createInitialLevels());\n }\n }, []);\n\n const cleanupVisualization = useCallback(() => {\n if (animationFrameRef.current) {\n cancelFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = null;\n\n if (analyzerRef.current) {\n void analyzerRef.current.context.close();\n analyzerRef.current = null;\n }\n\n resetAudioLevels();\n resetPreviewLevels();\n }, [ resetAudioLevels, resetPreviewLevels ]);\n\n return {\n audioLevels,\n previewLevels,\n resetAudioLevels,\n stopVisualization,\n resetPreviewLevels,\n resumeVisualization,\n cleanupVisualization,\n generatePreviewLevels,\n initializeVisualization,\n };\n};\n\n\nexport { useRecorderVisualization };\n"],"names":[],"mappings":";;;;;AAAA;;;;AAIG;AAuBH,MAAM,wBAAwB,GAAG,MAAqC;IAElE,MAAM,CAAE,WAAW,EAAE,cAAc,CAAE,GAAG,QAAQ,CAAW,mBAAmB,CAAC;IAC/E,MAAM,CAAE,aAAa,EAAE,gBAAgB,CAAE,GAAG,QAAQ,CAAW,mBAAmB,CAAC;AAEnF,IAAA,MAAM,WAAW,GAAG,MAAM,CAAwB,IAAI,CAAC;AACvD,IAAA,MAAM,iBAAiB,GAAG,MAAM,CAAgB,IAAI,CAAC;AAErD,IAAA,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAK;QACtC,cAAc,CAAC,mBAAmB,CAAC;IACvC,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,kBAAkB,GAAG,WAAW,CAAC,MAAK;QACxC,gBAAgB,CAAC,mBAAmB,CAAC;IACzC,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,iBAAiB,GAAG,WAAW,CACjC,CAAC,OAA2D,KAAI;;AAC5D,QAAA,IAAI,iBAAiB,CAAC,OAAO,EAAE;AAC3B,YAAA,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC;AACtC,YAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;QACpC;AAEA,QAAA,IAAI,CAAC,CAAA,EAAA,GAAA,OAAO,KAAA,IAAA,IAAP,OAAO,uBAAP,OAAO,CAAE,YAAY,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI,KAAK,WAAW,CAAC,OAAO,EAAE;YACxD,KAAK,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE;AACxC,YAAA,WAAW,CAAC,OAAO,GAAG,IAAI;QAC9B;QAEA,IAAI,CAAA,EAAA,GAAA,OAAO,KAAA,IAAA,IAAP,OAAO,KAAA,MAAA,GAAA,MAAA,GAAP,OAAO,CAAE,WAAW,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI,EAAE;AAC9B,YAAA,gBAAgB,EAAE;QACtB;AACJ,IAAA,CAAC,EACD,CAAE,gBAAgB,CAAE,CACvB;AAED,IAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAK;AAC/B,QAAA,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;YACtB;QACJ;AAEA,QAAA,MAAM,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAC,OAAO;QAExC,MAAM,IAAI,GAAG,MAAK;AACd,YAAA,iBAAiB,CAAC,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC;AAE/C,YAAA,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC;YAC5C,cAAc,CAAC,MAAM,CAAC;AAC1B,QAAA,CAAC;AAED,QAAA,IAAI,EAAE;IACV,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,mBAAmB,GAAG,WAAW,CAAC,MAAK;QACzC,IAAI,WAAW,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE;AACnD,YAAA,SAAS,EAAE;QACf;AACJ,IAAA,CAAC,EAAE,CAAE,SAAS,CAAE,CAAC;AAEjB,IAAA,MAAM,uBAAuB,GAAG,WAAW,CACvC,CAAC,MAAmB,KAAI;AACpB,QAAA,WAAW,CAAC,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC;AAClD,QAAA,SAAS,EAAE;AACf,IAAA,CAAC,EACD,CAAE,SAAS,CAAE,CAChB;IAED,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,IAAU,KAAI;AAC3D,QAAA,IAAI;AACA,YAAA,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC;YAE5C,cAAc,CAAC,MAAM,CAAC;YACtB,gBAAgB,CAAC,MAAM,CAAC;QAC5B;QAAE,OAAO,KAAK,EAAE;AACZ,YAAA,OAAO,CAAC,IAAI,CAAC,mDAAmD,EAAE,KAAK,CAAC;AACxE,YAAA,gBAAgB,CAAC,mBAAmB,EAAE,CAAC;QAC3C;IACJ,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,oBAAoB,GAAG,WAAW,CAAC,MAAK;AAC1C,QAAA,IAAI,iBAAiB,CAAC,OAAO,EAAE;AAC3B,YAAA,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC;QAC1C;AAEA,QAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;AAEhC,QAAA,IAAI,WAAW,CAAC,OAAO,EAAE;YACrB,KAAK,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE;AACxC,YAAA,WAAW,CAAC,OAAO,GAAG,IAAI;QAC9B;AAEA,QAAA,gBAAgB,EAAE;AAClB,QAAA,kBAAkB,EAAE;AACxB,IAAA,CAAC,EAAE,CAAE,gBAAgB,EAAE,kBAAkB,CAAE,CAAC;IAE5C,OAAO;QACH,WAAW;QACX,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,kBAAkB;QAClB,mBAAmB;QACnB,oBAAoB;QACpB,qBAAqB;QACrB,uBAAuB;KAC1B;AACL;;;;"}
@@ -0,0 +1,53 @@
1
+ import { useCallback } from 'react';
2
+
3
+ /**
4
+ * useRecordingSender — handles sending a recorded voice message via an upload strategy.
5
+ *
6
+ * @author: exode <hello@exode.ru>
7
+ */
8
+ const useRecordingSender = (options) => {
9
+ const { uploader, fileName, mimeType, uploadId, audioBlob, onUploaded, getDurationMs, onRecordingError, deleteRecording, autoResetAfterUpload, } = options;
10
+ const sendRecording = useCallback(async () => {
11
+ if (!audioBlob) {
12
+ const error = new Error('Voice recording is not available');
13
+ onRecordingError === null || onRecordingError === void 0 ? void 0 : onRecordingError(error);
14
+ return;
15
+ }
16
+ if (!uploader) {
17
+ const error = new Error('Uploader strategy is not defined');
18
+ onRecordingError === null || onRecordingError === void 0 ? void 0 : onRecordingError(error);
19
+ throw error;
20
+ }
21
+ try {
22
+ const durationMs = getDurationMs();
23
+ const durationSec = durationMs / 1000;
24
+ const file = new File([audioBlob], fileName, {
25
+ type: audioBlob.type || mimeType,
26
+ });
27
+ const uploaded = await uploader(file, { uploadId });
28
+ await onUploaded(uploaded, durationSec);
29
+ if (autoResetAfterUpload) {
30
+ deleteRecording();
31
+ }
32
+ }
33
+ catch (error) {
34
+ onRecordingError === null || onRecordingError === void 0 ? void 0 : onRecordingError(error);
35
+ throw error;
36
+ }
37
+ }, [
38
+ uploadId,
39
+ uploader,
40
+ fileName,
41
+ audioBlob,
42
+ mimeType,
43
+ onUploaded,
44
+ getDurationMs,
45
+ deleteRecording,
46
+ onRecordingError,
47
+ autoResetAfterUpload,
48
+ ]);
49
+ return { sendRecording };
50
+ };
51
+
52
+ export { useRecordingSender };
53
+ //# sourceMappingURL=useRecordingSender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRecordingSender.js","sources":["../../src/hooks/useRecordingSender.ts"],"sourcesContent":["/**\n * useRecordingSender — handles sending a recorded voice message via an upload strategy.\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback } from 'react';\n\nimport type { UploadStrategy } from '../types';\n\n\ninterface UseRecordingSenderOptions<TFile> {\n fileName: string;\n mimeType: string;\n audioBlob: Blob | null;\n uploadId: string;\n getDurationMs: () => number;\n uploader?: UploadStrategy<TFile>;\n onUploaded: (file: TFile, duration: number) => void | Promise<void>;\n deleteRecording: () => void;\n autoResetAfterUpload: boolean;\n onRecordingError?: (error: unknown) => void;\n}\n\n\nconst useRecordingSender = <TFile = unknown>(options: UseRecordingSenderOptions<TFile>) => {\n\n const {\n uploader,\n fileName,\n mimeType,\n uploadId,\n audioBlob,\n onUploaded,\n getDurationMs,\n onRecordingError,\n deleteRecording,\n autoResetAfterUpload,\n } = options;\n\n const sendRecording = useCallback(async () => {\n if (!audioBlob) {\n const error = new Error('Voice recording is not available');\n\n onRecordingError?.(error);\n\n return;\n }\n\n if (!uploader) {\n const error = new Error('Uploader strategy is not defined');\n\n onRecordingError?.(error);\n throw error;\n }\n\n try {\n const durationMs = getDurationMs();\n const durationSec = durationMs / 1000;\n\n const file = new File([ audioBlob ], fileName, {\n type: audioBlob.type || mimeType,\n });\n\n const uploaded = await uploader(file, { uploadId });\n await onUploaded(uploaded as TFile, durationSec);\n\n if (autoResetAfterUpload) {\n deleteRecording();\n }\n } catch (error) {\n onRecordingError?.(error);\n throw error;\n }\n }, [\n uploadId,\n uploader,\n fileName,\n audioBlob,\n mimeType,\n onUploaded,\n getDurationMs,\n deleteRecording,\n onRecordingError,\n autoResetAfterUpload,\n ]);\n\n return { sendRecording };\n};\n\n\nexport { useRecordingSender };\n"],"names":[],"mappings":";;AAAA;;;;AAIG;AAqBH,MAAM,kBAAkB,GAAG,CAAkB,OAAyC,KAAI;IAEtF,MAAM,EACF,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACvB,GAAG,OAAO;AAEX,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,YAAW;QACzC,IAAI,CAAC,SAAS,EAAE;AACZ,YAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,kCAAkC,CAAC;AAE3D,YAAA,gBAAgB,aAAhB,gBAAgB,KAAA,MAAA,GAAA,MAAA,GAAhB,gBAAgB,CAAG,KAAK,CAAC;YAEzB;QACJ;QAEA,IAAI,CAAC,QAAQ,EAAE;AACX,YAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,kCAAkC,CAAC;AAE3D,YAAA,gBAAgB,aAAhB,gBAAgB,KAAA,MAAA,GAAA,MAAA,GAAhB,gBAAgB,CAAG,KAAK,CAAC;AACzB,YAAA,MAAM,KAAK;QACf;AAEA,QAAA,IAAI;AACA,YAAA,MAAM,UAAU,GAAG,aAAa,EAAE;AAClC,YAAA,MAAM,WAAW,GAAG,UAAU,GAAG,IAAI;YAErC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAE,SAAS,CAAE,EAAE,QAAQ,EAAE;AAC3C,gBAAA,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,QAAQ;AACnC,aAAA,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC;AACnD,YAAA,MAAM,UAAU,CAAC,QAAiB,EAAE,WAAW,CAAC;YAEhD,IAAI,oBAAoB,EAAE;AACtB,gBAAA,eAAe,EAAE;YACrB;QACJ;QAAE,OAAO,KAAK,EAAE;AACZ,YAAA,gBAAgB,aAAhB,gBAAgB,KAAA,MAAA,GAAA,MAAA,GAAhB,gBAAgB,CAAG,KAAK,CAAC;AACzB,YAAA,MAAM,KAAK;QACf;AACJ,IAAA,CAAC,EAAE;QACC,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,QAAQ;QACR,UAAU;QACV,aAAa;QACb,eAAe;QACf,gBAAgB;QAChB,oBAAoB;AACvB,KAAA,CAAC;IAEF,OAAO,EAAE,aAAa,EAAE;AAC5B;;;;"}
@@ -0,0 +1,93 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { roundTo } from '../utils/math.js';
3
+
4
+ /**
5
+ * useRecordingTiming
6
+ *
7
+ * @author: exode <hello@exode.ru>
8
+ */
9
+ const useRecordingTiming = () => {
10
+ const [lastDuration, setLastDuration] = useState(0);
11
+ const [recordingTime, setRecordingTime] = useState(0);
12
+ const timerRef = useRef(null);
13
+ const recordingStartRef = useRef(null);
14
+ const recordingAccumulatedRef = useRef(0);
15
+ const stopTicker = useCallback(() => {
16
+ if (timerRef.current) {
17
+ clearInterval(timerRef.current);
18
+ timerRef.current = null;
19
+ }
20
+ }, []);
21
+ const startTicker = useCallback(() => {
22
+ if (timerRef.current) {
23
+ clearInterval(timerRef.current);
24
+ }
25
+ timerRef.current = setInterval(() => {
26
+ if (recordingStartRef.current === null) {
27
+ setRecordingTime(roundTo(recordingAccumulatedRef.current));
28
+ return;
29
+ }
30
+ const elapsed = (performance.now() - recordingStartRef.current) / 1000 +
31
+ recordingAccumulatedRef.current;
32
+ setRecordingTime(roundTo(elapsed));
33
+ }, 100);
34
+ }, []);
35
+ const startNewSession = useCallback(() => {
36
+ recordingAccumulatedRef.current = 0;
37
+ setRecordingTime(0);
38
+ setLastDuration(0);
39
+ recordingStartRef.current = performance.now();
40
+ startTicker();
41
+ }, [startTicker]);
42
+ const resumeSession = useCallback(() => {
43
+ recordingStartRef.current = performance.now();
44
+ startTicker();
45
+ }, [startTicker]);
46
+ const pauseSession = useCallback(() => {
47
+ if (recordingStartRef.current === null) {
48
+ return;
49
+ }
50
+ const elapsed = (performance.now() - recordingStartRef.current) / 1000;
51
+ recordingAccumulatedRef.current += elapsed;
52
+ recordingStartRef.current = null;
53
+ setRecordingTime(roundTo(recordingAccumulatedRef.current));
54
+ stopTicker();
55
+ }, [stopTicker]);
56
+ const getElapsedMs = useCallback(() => {
57
+ const accumulated = recordingAccumulatedRef.current;
58
+ const elapsed = recordingStartRef.current !== null
59
+ ? (performance.now() - recordingStartRef.current) / 1000 + accumulated
60
+ : accumulated;
61
+ return Math.round(elapsed * 1000);
62
+ }, []);
63
+ const resetSession = useCallback(() => {
64
+ const accumulated = recordingAccumulatedRef.current;
65
+ const elapsed = recordingStartRef.current !== null
66
+ ? (performance.now() - recordingStartRef.current) / 1000 + accumulated
67
+ : accumulated;
68
+ if (elapsed > 0) {
69
+ setLastDuration(roundTo(elapsed));
70
+ }
71
+ stopTicker();
72
+ recordingStartRef.current = null;
73
+ recordingAccumulatedRef.current = 0;
74
+ setRecordingTime(0);
75
+ }, [stopTicker]);
76
+ useEffect(() => {
77
+ return () => {
78
+ stopTicker();
79
+ };
80
+ }, [stopTicker]);
81
+ return {
82
+ lastDuration,
83
+ getElapsedMs,
84
+ pauseSession,
85
+ resetSession,
86
+ recordingTime,
87
+ resumeSession,
88
+ startNewSession,
89
+ };
90
+ };
91
+
92
+ export { useRecordingTiming };
93
+ //# sourceMappingURL=useRecordingTiming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRecordingTiming.js","sources":["../../src/hooks/useRecordingTiming.ts"],"sourcesContent":["/**\n * useRecordingTiming\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { roundTo } from '../utils';\n\n\ninterface UseRecordingTimingResult {\n recordingTime: number;\n lastDuration: number;\n getElapsedMs: () => number;\n pauseSession: () => void;\n resetSession: () => void;\n resumeSession: () => void;\n startNewSession: () => void;\n}\n\nconst useRecordingTiming = (): UseRecordingTimingResult => {\n\n const [ lastDuration, setLastDuration ] = useState(0);\n const [ recordingTime, setRecordingTime ] = useState(0);\n\n const timerRef = useRef<NodeJS.Timeout | null>(null);\n const recordingStartRef = useRef<number | null>(null);\n const recordingAccumulatedRef = useRef(0);\n\n const stopTicker = useCallback(() => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const startTicker = useCallback(() => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n }\n\n timerRef.current = setInterval(() => {\n if (recordingStartRef.current === null) {\n setRecordingTime(roundTo(recordingAccumulatedRef.current));\n\n return;\n }\n\n const elapsed =\n (performance.now() - recordingStartRef.current) / 1000 +\n recordingAccumulatedRef.current;\n setRecordingTime(roundTo(elapsed));\n }, 100);\n }, []);\n\n const startNewSession = useCallback(() => {\n recordingAccumulatedRef.current = 0;\n setRecordingTime(0);\n setLastDuration(0);\n\n recordingStartRef.current = performance.now();\n startTicker();\n }, [ startTicker ]);\n\n const resumeSession = useCallback(() => {\n recordingStartRef.current = performance.now();\n startTicker();\n }, [ startTicker ]);\n\n const pauseSession = useCallback(() => {\n if (recordingStartRef.current === null) {\n return;\n }\n\n const elapsed = (performance.now() - recordingStartRef.current) / 1000;\n recordingAccumulatedRef.current += elapsed;\n recordingStartRef.current = null;\n\n setRecordingTime(roundTo(recordingAccumulatedRef.current));\n stopTicker();\n }, [ stopTicker ]);\n\n const getElapsedMs = useCallback(() => {\n const accumulated = recordingAccumulatedRef.current;\n\n const elapsed = recordingStartRef.current !== null\n ? (performance.now() - recordingStartRef.current) / 1000 + accumulated\n : accumulated;\n\n return Math.round(elapsed * 1000);\n }, []);\n\n const resetSession = useCallback(() => {\n const accumulated = recordingAccumulatedRef.current;\n\n const elapsed = recordingStartRef.current !== null\n ? (performance.now() - recordingStartRef.current) / 1000 + accumulated\n : accumulated;\n\n if (elapsed > 0) {\n setLastDuration(roundTo(elapsed));\n }\n\n stopTicker();\n recordingStartRef.current = null;\n recordingAccumulatedRef.current = 0;\n setRecordingTime(0);\n }, [ stopTicker ]);\n\n useEffect(() => {\n return () => {\n stopTicker();\n };\n }, [ stopTicker ]);\n\n return {\n lastDuration,\n getElapsedMs,\n pauseSession,\n resetSession,\n recordingTime,\n resumeSession,\n startNewSession,\n };\n};\n\n\nexport { useRecordingTiming };\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ import { useRef, useState, useCallback, useEffect } from 'react';
2
+ import { formatTime } from '../utils/format.js';
3
+ import { audioPlaybackManager } from '../platform/audio-playback-manager.js';
4
+ import { useAudioBlobLoader } from './useAudioBlobLoader.js';
5
+
6
+ /**
7
+ * useVoiceMessagePlayer
8
+ *
9
+ * @author: exode <hello@exode.ru>
10
+ */
11
+ const useVoiceMessagePlayer = (options) => {
12
+ const { src, onEnded, onPreviousTrack, onNextTrack, waveformData, preloadedBlob, duration: externalDuration, } = options;
13
+ const onEndedRef = useRef(onEnded);
14
+ const onPreviousTrackRef = useRef(onPreviousTrack);
15
+ const onNextTrackRef = useRef(onNextTrack);
16
+ onEndedRef.current = onEnded;
17
+ onPreviousTrackRef.current = onPreviousTrack;
18
+ onNextTrackRef.current = onNextTrack;
19
+ const [isPlaying, setIsPlaying] = useState(false);
20
+ const [currentTime, setCurrentTime] = useState(0);
21
+ const [duration, setDuration] = useState(externalDuration || 0);
22
+ const progress = duration > 0 ? currentTime / duration : 0;
23
+ /** Stop this player (called by manager when another player starts) */
24
+ const stopThis = useCallback(() => {
25
+ const audio = audioRef.current;
26
+ if (audio && !audio.paused) {
27
+ audio.pause();
28
+ }
29
+ setIsPlaying(false);
30
+ }, []);
31
+ /** Subscribe to global manager on mount */
32
+ useEffect(() => {
33
+ return audioPlaybackManager.subscribe(stopThis);
34
+ }, [stopThis]);
35
+ const { audioRef, isLoading, isLoaded, waveformLevels, ensureAudioLoaded, } = useAudioBlobLoader({
36
+ src,
37
+ waveformData,
38
+ preloadedBlob,
39
+ playbackCallbacks: {
40
+ onLoadedMetadata: (dur) => {
41
+ if (!externalDuration && dur && isFinite(dur) && dur > 0.5) {
42
+ setDuration(dur);
43
+ }
44
+ },
45
+ onTimeUpdate: (time) => setCurrentTime(time),
46
+ onEnded: () => {
47
+ var _a;
48
+ setIsPlaying(false);
49
+ setCurrentTime(0);
50
+ (_a = onEndedRef.current) === null || _a === void 0 ? void 0 : _a.call(onEndedRef);
51
+ },
52
+ onPlay: () => setIsPlaying(true),
53
+ onPause: () => setIsPlaying(false),
54
+ onStop: () => {
55
+ setIsPlaying(false);
56
+ setCurrentTime(0);
57
+ },
58
+ onPreviousTrack: () => { var _a; return (_a = onPreviousTrackRef.current) === null || _a === void 0 ? void 0 : _a.call(onPreviousTrackRef); },
59
+ onNextTrack: () => { var _a; return (_a = onNextTrackRef.current) === null || _a === void 0 ? void 0 : _a.call(onNextTrackRef); },
60
+ },
61
+ });
62
+ /** Pre-fetch audio without playing (for viewport preloading) */
63
+ const prefetch = useCallback(() => {
64
+ if (isLoading || audioRef.current) {
65
+ return;
66
+ }
67
+ void ensureAudioLoaded();
68
+ }, [isLoading, ensureAudioLoaded]);
69
+ /** Load audio and auto-play after loaded */
70
+ const preload = useCallback(() => {
71
+ if (isLoading || audioRef.current) {
72
+ return;
73
+ }
74
+ audioPlaybackManager.stopOthers(stopThis);
75
+ void ensureAudioLoaded().then(() => {
76
+ const audio = audioRef.current;
77
+ if (audio) {
78
+ audio.play()
79
+ .then(() => setIsPlaying(true))
80
+ .catch(() => setIsPlaying(false));
81
+ }
82
+ });
83
+ }, [isLoading, ensureAudioLoaded, stopThis, audioRef]);
84
+ /** Programmatically start playback (used for chained playback) */
85
+ const play = useCallback(() => {
86
+ const audio = audioRef.current;
87
+ if (audio) {
88
+ audioPlaybackManager.stopOthers(stopThis);
89
+ audio.play()
90
+ .then(() => setIsPlaying(true))
91
+ .catch(() => setIsPlaying(false));
92
+ }
93
+ else {
94
+ preload();
95
+ }
96
+ }, [stopThis, preload, audioRef]);
97
+ /** Toggle play/pause (only works after audio is loaded) */
98
+ const togglePlayback = useCallback(() => {
99
+ const audio = audioRef.current;
100
+ if (!audio || isLoading) {
101
+ return;
102
+ }
103
+ if (audio.paused) {
104
+ audioPlaybackManager.stopOthers(stopThis);
105
+ audio.play()
106
+ .then(() => setIsPlaying(true))
107
+ .catch(() => setIsPlaying(false));
108
+ }
109
+ else {
110
+ audio.pause();
111
+ setIsPlaying(false);
112
+ }
113
+ }, [isLoading, stopThis, audioRef]);
114
+ const seekTo = useCallback((seekPercent) => {
115
+ const audio = audioRef.current;
116
+ if (!audio || !isFinite(audio.duration)) {
117
+ return;
118
+ }
119
+ const time = seekPercent * audio.duration;
120
+ audio.currentTime = time;
121
+ setCurrentTime(time);
122
+ }, [audioRef]);
123
+ /** Seek to a specific time in seconds and start playback */
124
+ const seekToTime = useCallback((seconds) => {
125
+ const audio = audioRef.current;
126
+ if (audio && isFinite(audio.duration)) {
127
+ audioPlaybackManager.stopOthers(stopThis);
128
+ audio.currentTime = Math.max(0, Math.min(seconds, audio.duration));
129
+ setCurrentTime(audio.currentTime);
130
+ audio.play()
131
+ .then(() => setIsPlaying(true))
132
+ .catch(() => setIsPlaying(false));
133
+ }
134
+ else {
135
+ void ensureAudioLoaded().then(() => {
136
+ const loadedAudio = audioRef.current;
137
+ if (loadedAudio) {
138
+ audioPlaybackManager.stopOthers(stopThis);
139
+ loadedAudio.currentTime = Math.max(0, Math.min(seconds, loadedAudio.duration || seconds));
140
+ setCurrentTime(loadedAudio.currentTime);
141
+ loadedAudio.play()
142
+ .then(() => setIsPlaying(true))
143
+ .catch(() => setIsPlaying(false));
144
+ }
145
+ });
146
+ }
147
+ }, [
148
+ audioRef,
149
+ stopThis,
150
+ ensureAudioLoaded,
151
+ ]);
152
+ /** Sync external duration */
153
+ useEffect(() => {
154
+ if (externalDuration) {
155
+ setDuration(externalDuration);
156
+ }
157
+ }, [externalDuration]);
158
+ return {
159
+ play,
160
+ progress,
161
+ duration,
162
+ isLoaded,
163
+ isPlaying,
164
+ isLoading,
165
+ currentTime,
166
+ waveformLevels,
167
+ seekTo,
168
+ seekToTime,
169
+ prefetch,
170
+ preload,
171
+ formatTime,
172
+ togglePlayback,
173
+ };
174
+ };
175
+
176
+ export { useVoiceMessagePlayer };
177
+ //# sourceMappingURL=useVoiceMessagePlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVoiceMessagePlayer.js","sources":["../../src/hooks/useVoiceMessagePlayer.ts"],"sourcesContent":["/**\n * useVoiceMessagePlayer\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { formatTime } from '../utils';\nimport { audioPlaybackManager } from '../platform/audio-playback-manager';\n\nimport { useAudioBlobLoader } from './useAudioBlobLoader';\n\nimport type { UseVoiceMessagePlayerOptions, UseVoiceMessagePlayerResult } from '../types';\n\n\nconst useVoiceMessagePlayer = (options: UseVoiceMessagePlayerOptions): UseVoiceMessagePlayerResult => {\n\n const {\n src,\n onEnded,\n onPreviousTrack,\n onNextTrack,\n waveformData,\n preloadedBlob,\n duration: externalDuration,\n } = options;\n\n const onEndedRef = useRef(onEnded);\n const onPreviousTrackRef = useRef(onPreviousTrack);\n const onNextTrackRef = useRef(onNextTrack);\n\n onEndedRef.current = onEnded;\n onPreviousTrackRef.current = onPreviousTrack;\n onNextTrackRef.current = onNextTrack;\n\n const [ isPlaying, setIsPlaying ] = useState(false);\n const [ currentTime, setCurrentTime ] = useState(0);\n const [ duration, setDuration ] = useState(externalDuration || 0);\n\n const progress = duration > 0 ? currentTime / duration : 0;\n\n /** Stop this player (called by manager when another player starts) */\n const stopThis = useCallback(() => {\n const audio = audioRef.current;\n\n if (audio && !audio.paused) {\n audio.pause();\n }\n\n setIsPlaying(false);\n }, []);\n\n /** Subscribe to global manager on mount */\n useEffect(() => {\n return audioPlaybackManager.subscribe(stopThis);\n }, [ stopThis ]);\n\n const {\n audioRef,\n isLoading,\n isLoaded,\n waveformLevels,\n ensureAudioLoaded,\n } = useAudioBlobLoader({\n src,\n waveformData,\n preloadedBlob,\n playbackCallbacks: {\n onLoadedMetadata: (dur) => {\n if (!externalDuration && dur && isFinite(dur) && dur > 0.5) {\n setDuration(dur);\n }\n },\n onTimeUpdate: (time) => setCurrentTime(time),\n onEnded: () => {\n setIsPlaying(false);\n setCurrentTime(0);\n onEndedRef.current?.();\n },\n onPlay: () => setIsPlaying(true),\n onPause: () => setIsPlaying(false),\n onStop: () => {\n setIsPlaying(false);\n setCurrentTime(0);\n },\n onPreviousTrack: () => onPreviousTrackRef.current?.(),\n onNextTrack: () => onNextTrackRef.current?.(),\n },\n });\n\n /** Pre-fetch audio without playing (for viewport preloading) */\n const prefetch = useCallback(() => {\n if (isLoading || audioRef.current) {\n return;\n }\n\n void ensureAudioLoaded();\n }, [ isLoading, ensureAudioLoaded ]);\n\n /** Load audio and auto-play after loaded */\n const preload = useCallback(() => {\n if (isLoading || audioRef.current) {\n return;\n }\n\n audioPlaybackManager.stopOthers(stopThis);\n\n void ensureAudioLoaded().then(() => {\n const audio = audioRef.current;\n\n if (audio) {\n audio.play()\n .then(() => setIsPlaying(true))\n .catch(() => setIsPlaying(false));\n }\n });\n }, [ isLoading, ensureAudioLoaded, stopThis, audioRef ]);\n\n /** Programmatically start playback (used for chained playback) */\n const play = useCallback(() => {\n const audio = audioRef.current;\n\n if (audio) {\n audioPlaybackManager.stopOthers(stopThis);\n audio.play()\n .then(() => setIsPlaying(true))\n .catch(() => setIsPlaying(false));\n } else {\n preload();\n }\n }, [ stopThis, preload, audioRef ]);\n\n /** Toggle play/pause (only works after audio is loaded) */\n const togglePlayback = useCallback(() => {\n const audio = audioRef.current;\n\n if (!audio || isLoading) {\n return;\n }\n\n if (audio.paused) {\n audioPlaybackManager.stopOthers(stopThis);\n audio.play()\n .then(() => setIsPlaying(true))\n .catch(() => setIsPlaying(false));\n } else {\n audio.pause();\n setIsPlaying(false);\n }\n }, [ isLoading, stopThis, audioRef ]);\n\n const seekTo = useCallback((seekPercent: number) => {\n const audio = audioRef.current;\n\n if (!audio || !isFinite(audio.duration)) {\n return;\n }\n\n const time = seekPercent * audio.duration;\n\n audio.currentTime = time;\n setCurrentTime(time);\n }, [ audioRef ]);\n\n /** Seek to a specific time in seconds and start playback */\n const seekToTime = useCallback((seconds: number) => {\n const audio = audioRef.current;\n\n if (audio && isFinite(audio.duration)) {\n audioPlaybackManager.stopOthers(stopThis);\n\n audio.currentTime = Math.max(0, Math.min(seconds, audio.duration));\n setCurrentTime(audio.currentTime);\n\n audio.play()\n .then(() => setIsPlaying(true))\n .catch(() => setIsPlaying(false));\n } else {\n void ensureAudioLoaded().then(() => {\n const loadedAudio = audioRef.current;\n\n if (loadedAudio) {\n audioPlaybackManager.stopOthers(stopThis);\n\n loadedAudio.currentTime = Math.max(0, Math.min(\n seconds,\n loadedAudio.duration || seconds,\n ));\n\n setCurrentTime(loadedAudio.currentTime);\n\n loadedAudio.play()\n .then(() => setIsPlaying(true))\n .catch(() => setIsPlaying(false));\n }\n });\n }\n }, [\n audioRef,\n stopThis,\n ensureAudioLoaded,\n ]);\n\n /** Sync external duration */\n useEffect(() => {\n if (externalDuration) {\n setDuration(externalDuration);\n }\n }, [ externalDuration ]);\n\n return {\n play,\n progress,\n duration,\n isLoaded,\n isPlaying,\n isLoading,\n currentTime,\n waveformLevels,\n seekTo,\n seekToTime,\n prefetch,\n preload,\n formatTime,\n togglePlayback,\n };\n};\n\n\nexport { useVoiceMessagePlayer };\n"],"names":[],"mappings":"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"}