@exode-team/react-recorder 1.1.14 → 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.
- package/dist/components/AudioRecorder.js +50 -0
- package/dist/components/AudioRecorder.js.map +1 -0
- package/dist/components/AudioRecorder.messages.js +47 -0
- package/dist/components/AudioRecorder.messages.js.map +1 -0
- package/dist/components/AudioRecorder.styled.js +16 -0
- package/dist/components/AudioRecorder.styled.js.map +1 -0
- package/dist/components/IdleControl/IdleControl.js +12 -0
- package/dist/components/IdleControl/IdleControl.js.map +1 -0
- package/dist/components/IdleControl/IdleControl.styled.js +68 -0
- package/dist/components/IdleControl/IdleControl.styled.js.map +1 -0
- package/dist/components/RecordingShell/RecordingShell.js +80 -0
- package/dist/components/RecordingShell/RecordingShell.js.map +1 -0
- package/dist/components/RecordingShell/RecordingShell.styled.js +159 -0
- package/dist/components/RecordingShell/RecordingShell.styled.js.map +1 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js +151 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js.map +1 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js +92 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js.map +1 -0
- package/dist/components/VoiceRecorderInput/PreviewBars.js +52 -0
- package/dist/components/VoiceRecorderInput/PreviewBars.js.map +1 -0
- package/dist/components/VoiceRecorderInput/TimelineBars.js +31 -0
- package/dist/components/VoiceRecorderInput/TimelineBars.js.map +1 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js +51 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js.map +1 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js +175 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js.map +1 -0
- package/dist/components/WaveformBars/WaveformBars.js +29 -0
- package/dist/components/WaveformBars/WaveformBars.js.map +1 -0
- package/dist/components/WaveformBars/WaveformBars.styled.js +20 -0
- package/dist/components/WaveformBars/WaveformBars.styled.js.map +1 -0
- package/dist/constants.js +17 -0
- package/dist/constants.js.map +1 -0
- package/dist/hooks/useAudioBlobLoader.js +175 -0
- package/dist/hooks/useAudioBlobLoader.js.map +1 -0
- package/dist/hooks/useAudioPlayback.js +119 -0
- package/dist/hooks/useAudioPlayback.js.map +1 -0
- package/dist/hooks/useAudioRecorderHandlers.js +137 -0
- package/dist/hooks/useAudioRecorderHandlers.js.map +1 -0
- package/dist/hooks/useMediaRecorder.js +127 -0
- package/dist/hooks/useMediaRecorder.js.map +1 -0
- package/dist/hooks/useRecorderVisualization.js +94 -0
- package/dist/hooks/useRecorderVisualization.js.map +1 -0
- package/dist/hooks/useRecordingSender.js +53 -0
- package/dist/hooks/useRecordingSender.js.map +1 -0
- package/dist/hooks/useRecordingTiming.js +93 -0
- package/dist/hooks/useRecordingTiming.js.map +1 -0
- package/dist/hooks/useVoiceMessagePlayer.js +177 -0
- package/dist/hooks/useVoiceMessagePlayer.js.map +1 -0
- package/dist/hooks/useVoiceRecorder.js +153 -0
- package/dist/hooks/useVoiceRecorder.js.map +1 -0
- package/dist/hooks/useVoiceRecorderInput.js +163 -0
- package/dist/hooks/useVoiceRecorderInput.js.map +1 -0
- package/dist/index.js +13 -2659
- package/dist/index.js.map +1 -1
- package/dist/platform/animation-frame-adapter.js +14 -0
- package/dist/platform/animation-frame-adapter.js.map +1 -0
- package/dist/platform/audio-analyzer-adapter.js +74 -0
- package/dist/platform/audio-analyzer-adapter.js.map +1 -0
- package/dist/platform/audio-blob-fetcher.js +19 -0
- package/dist/platform/audio-blob-fetcher.js.map +1 -0
- package/dist/platform/audio-playback-adapter.js +114 -0
- package/dist/platform/audio-playback-adapter.js.map +1 -0
- package/dist/platform/audio-playback-manager.js +26 -0
- package/dist/platform/audio-playback-manager.js.map +1 -0
- package/dist/platform/media-recorder-adapter.js +23 -0
- package/dist/platform/media-recorder-adapter.js.map +1 -0
- package/dist/platform/microphone.js +24 -0
- package/dist/platform/microphone.js.map +1 -0
- package/dist/platform/mpeg-frame.js +60 -0
- package/dist/platform/mpeg-frame.js.map +1 -0
- package/dist/utils/format.js +13 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/math.js +16 -0
- package/dist/utils/math.js.map +1 -0
- package/dist/utils/pseudo-waveform.js +40 -0
- package/dist/utils/pseudo-waveform.js.map +1 -0
- package/dist/utils/voice-blob-cache.js +35 -0
- package/dist/utils/voice-blob-cache.js.map +1 -0
- package/dist/utils/voice-playback-chain.js +42 -0
- package/dist/utils/voice-playback-chain.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo, useEffect } from 'react';
|
|
2
|
+
import { formatTime } from '../utils/format.js';
|
|
3
|
+
import { useMediaRecorder } from './useMediaRecorder.js';
|
|
4
|
+
import { useAudioPlayback } from './useAudioPlayback.js';
|
|
5
|
+
import { useRecordingTiming } from './useRecordingTiming.js';
|
|
6
|
+
import { useRecorderVisualization } from './useRecorderVisualization.js';
|
|
7
|
+
import { useRecordingSender } from './useRecordingSender.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* useVoiceRecorder
|
|
11
|
+
*
|
|
12
|
+
* @author: exode <hello@exode.ru>
|
|
13
|
+
*/
|
|
14
|
+
function useVoiceRecorder(props) {
|
|
15
|
+
const { uploadId, uploader, onUploaded, onRecordingStop, onRecordingStart, onRecordingError, onRecordingDelete, onPermissionDenied, fileName = 'record.webm', mimeType = 'audio/webm', autoResetAfterUpload = true, } = props;
|
|
16
|
+
const [audioBlob, setAudioBlob] = useState(null);
|
|
17
|
+
const { audioURL, isPlaying, mountBlob, playbackTime, stopPlayback, resetPlayback, togglePlayback: _togglePlayback, playbackDuration, } = useAudioPlayback();
|
|
18
|
+
const { audioLevels, previewLevels, resetAudioLevels, stopVisualization, resetPreviewLevels, resumeVisualization, cleanupVisualization, generatePreviewLevels, initializeVisualization, } = useRecorderVisualization();
|
|
19
|
+
const { lastDuration, getElapsedMs, recordingTime, pauseSession, resetSession, resumeSession, startNewSession, } = useRecordingTiming();
|
|
20
|
+
const resetCurrentRecording = useCallback((opts) => {
|
|
21
|
+
var _a;
|
|
22
|
+
setAudioBlob(null);
|
|
23
|
+
resetPlayback();
|
|
24
|
+
if ((_a = opts === null || opts === void 0 ? void 0 : opts.resetPreview) !== null && _a !== void 0 ? _a : true) {
|
|
25
|
+
resetPreviewLevels();
|
|
26
|
+
}
|
|
27
|
+
if (opts === null || opts === void 0 ? void 0 : opts.resetLevels) {
|
|
28
|
+
resetAudioLevels();
|
|
29
|
+
}
|
|
30
|
+
if (opts === null || opts === void 0 ? void 0 : opts.notifyDelete) {
|
|
31
|
+
onRecordingDelete === null || onRecordingDelete === void 0 ? void 0 : onRecordingDelete();
|
|
32
|
+
}
|
|
33
|
+
}, [onRecordingDelete, resetAudioLevels, resetPlayback, resetPreviewLevels]);
|
|
34
|
+
const deleteRecording = useCallback(() => {
|
|
35
|
+
resetCurrentRecording({
|
|
36
|
+
resetLevels: true,
|
|
37
|
+
notifyDelete: true,
|
|
38
|
+
});
|
|
39
|
+
resetSession();
|
|
40
|
+
}, [resetCurrentRecording, resetSession]);
|
|
41
|
+
const { sendRecording } = useRecordingSender({
|
|
42
|
+
audioBlob,
|
|
43
|
+
fileName,
|
|
44
|
+
mimeType,
|
|
45
|
+
uploadId,
|
|
46
|
+
uploader,
|
|
47
|
+
onUploaded,
|
|
48
|
+
deleteRecording,
|
|
49
|
+
onRecordingError,
|
|
50
|
+
autoResetAfterUpload,
|
|
51
|
+
getDurationMs: getElapsedMs,
|
|
52
|
+
});
|
|
53
|
+
const mountRecordingBlob = useCallback((blob) => {
|
|
54
|
+
setAudioBlob(blob);
|
|
55
|
+
mountBlob(blob);
|
|
56
|
+
}, [mountBlob]);
|
|
57
|
+
const recorderCallbacks = useMemo(() => ({
|
|
58
|
+
onDataChunk: () => { },
|
|
59
|
+
onNeedsPreview: (chunks) => {
|
|
60
|
+
const snapshot = new Blob(chunks, { type: mimeType });
|
|
61
|
+
mountRecordingBlob(snapshot);
|
|
62
|
+
void generatePreviewLevels(snapshot);
|
|
63
|
+
},
|
|
64
|
+
onStopped: (chunks, discarded) => {
|
|
65
|
+
pauseSession();
|
|
66
|
+
stopVisualization({ resetLevels: false });
|
|
67
|
+
if (discarded) {
|
|
68
|
+
resetCurrentRecording({ notifyDelete: true });
|
|
69
|
+
resetSession();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const blob = new Blob(chunks, { type: mimeType });
|
|
73
|
+
onRecordingStop === null || onRecordingStop === void 0 ? void 0 : onRecordingStop(blob);
|
|
74
|
+
mountRecordingBlob(blob);
|
|
75
|
+
void generatePreviewLevels(blob);
|
|
76
|
+
resetSession();
|
|
77
|
+
},
|
|
78
|
+
onPaused: () => {
|
|
79
|
+
pauseSession();
|
|
80
|
+
stopVisualization({ resetLevels: false, closeContext: false });
|
|
81
|
+
},
|
|
82
|
+
onResumed: () => {
|
|
83
|
+
resumeSession();
|
|
84
|
+
resumeVisualization();
|
|
85
|
+
},
|
|
86
|
+
onStreamAcquired: (stream) => {
|
|
87
|
+
initializeVisualization(stream);
|
|
88
|
+
},
|
|
89
|
+
onPermissionDenied: (error) => {
|
|
90
|
+
onPermissionDenied === null || onPermissionDenied === void 0 ? void 0 : onPermissionDenied(error);
|
|
91
|
+
},
|
|
92
|
+
onError: (error) => {
|
|
93
|
+
onRecordingError === null || onRecordingError === void 0 ? void 0 : onRecordingError(error);
|
|
94
|
+
},
|
|
95
|
+
}), [
|
|
96
|
+
generatePreviewLevels,
|
|
97
|
+
initializeVisualization,
|
|
98
|
+
mimeType,
|
|
99
|
+
mountRecordingBlob,
|
|
100
|
+
onPermissionDenied,
|
|
101
|
+
onRecordingError,
|
|
102
|
+
onRecordingStop,
|
|
103
|
+
pauseSession,
|
|
104
|
+
resetCurrentRecording,
|
|
105
|
+
resetSession,
|
|
106
|
+
resumeSession,
|
|
107
|
+
resumeVisualization,
|
|
108
|
+
stopVisualization,
|
|
109
|
+
]);
|
|
110
|
+
const { isPaused, isRecording, togglePause, cleanupStream, stopRecording, cancelRecording, startRecording: _startRecording, } = useMediaRecorder(recorderCallbacks);
|
|
111
|
+
const startRecording = useCallback(async () => {
|
|
112
|
+
resetCurrentRecording({ resetLevels: true });
|
|
113
|
+
await _startRecording(mimeType);
|
|
114
|
+
onRecordingStart === null || onRecordingStart === void 0 ? void 0 : onRecordingStart();
|
|
115
|
+
startNewSession();
|
|
116
|
+
}, [_startRecording, mimeType, onRecordingStart, resetCurrentRecording, startNewSession]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
return () => {
|
|
119
|
+
cleanupVisualization();
|
|
120
|
+
cleanupStream();
|
|
121
|
+
resetPlayback();
|
|
122
|
+
resetSession();
|
|
123
|
+
};
|
|
124
|
+
}, [cleanupVisualization, cleanupStream, resetPlayback, resetSession]);
|
|
125
|
+
const togglePlayback = useCallback(() => {
|
|
126
|
+
_togglePlayback(onRecordingError);
|
|
127
|
+
}, [_togglePlayback, onRecordingError]);
|
|
128
|
+
return {
|
|
129
|
+
audioURL,
|
|
130
|
+
isPaused,
|
|
131
|
+
isPlaying,
|
|
132
|
+
isRecording,
|
|
133
|
+
audioLevels,
|
|
134
|
+
lastDuration,
|
|
135
|
+
playbackTime,
|
|
136
|
+
recordingTime,
|
|
137
|
+
previewLevels,
|
|
138
|
+
playbackDuration,
|
|
139
|
+
formatTime,
|
|
140
|
+
stopPlayback,
|
|
141
|
+
sendRecording,
|
|
142
|
+
stopRecording,
|
|
143
|
+
togglePlayback,
|
|
144
|
+
startRecording,
|
|
145
|
+
cancelRecording,
|
|
146
|
+
deleteRecording,
|
|
147
|
+
toggleRecordingPause: togglePause,
|
|
148
|
+
hasRecording: Boolean(audioURL),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { useVoiceRecorder };
|
|
153
|
+
//# sourceMappingURL=useVoiceRecorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVoiceRecorder.js","sources":["../../src/hooks/useVoiceRecorder.ts"],"sourcesContent":["/**\n * useVoiceRecorder\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { formatTime } from '../utils';\nimport { useMediaRecorder } from './useMediaRecorder';\nimport { useAudioPlayback } from './useAudioPlayback';\nimport { useRecordingTiming } from './useRecordingTiming';\nimport { useRecorderVisualization } from './useRecorderVisualization';\nimport { useRecordingSender } from './useRecordingSender';\n\nimport type { UseVoiceRecorderOptions, UseVoiceRecorderResult } from '../types';\n\n\nfunction useVoiceRecorder<TFile = unknown>(props: UseVoiceRecorderOptions<TFile>): UseVoiceRecorderResult {\n\n const {\n uploadId,\n uploader,\n onUploaded,\n onRecordingStop,\n onRecordingStart,\n onRecordingError,\n onRecordingDelete,\n onPermissionDenied,\n fileName = 'record.webm',\n mimeType = 'audio/webm',\n autoResetAfterUpload = true,\n } = props;\n\n const [ audioBlob, setAudioBlob ] = useState<Blob | null>(null);\n\n const {\n audioURL,\n isPlaying,\n mountBlob,\n playbackTime,\n stopPlayback,\n resetPlayback,\n togglePlayback: _togglePlayback,\n playbackDuration,\n } = useAudioPlayback();\n\n const {\n audioLevels,\n previewLevels,\n resetAudioLevels,\n stopVisualization,\n resetPreviewLevels,\n resumeVisualization,\n cleanupVisualization,\n generatePreviewLevels,\n initializeVisualization,\n } = useRecorderVisualization();\n\n const {\n lastDuration,\n getElapsedMs,\n recordingTime,\n pauseSession,\n resetSession,\n resumeSession,\n startNewSession,\n } = useRecordingTiming();\n\n const resetCurrentRecording = useCallback(\n (opts?: { resetPreview?: boolean; resetLevels?: boolean; notifyDelete?: boolean }) => {\n setAudioBlob(null);\n resetPlayback();\n if (opts?.resetPreview ?? true) {\n resetPreviewLevels();\n }\n if (opts?.resetLevels) {\n resetAudioLevels();\n }\n if (opts?.notifyDelete) {\n onRecordingDelete?.();\n }\n },\n [ onRecordingDelete, resetAudioLevels, resetPlayback, resetPreviewLevels ],\n );\n\n const deleteRecording = useCallback(() => {\n resetCurrentRecording({\n resetLevels: true,\n notifyDelete: true,\n });\n\n resetSession();\n }, [ resetCurrentRecording, resetSession ]);\n\n const { sendRecording } = useRecordingSender<TFile>({\n audioBlob,\n fileName,\n mimeType,\n uploadId,\n uploader,\n onUploaded,\n deleteRecording,\n onRecordingError,\n autoResetAfterUpload,\n getDurationMs: getElapsedMs,\n });\n\n const mountRecordingBlob = useCallback((blob: Blob) => {\n setAudioBlob(blob);\n mountBlob(blob);\n }, [ mountBlob ]);\n\n const recorderCallbacks = useMemo(() => ({\n onDataChunk: () => {},\n onNeedsPreview: (chunks: Blob[]) => {\n const snapshot = new Blob(chunks, { type: mimeType });\n mountRecordingBlob(snapshot);\n void generatePreviewLevels(snapshot);\n },\n onStopped: (chunks: Blob[], discarded: boolean) => {\n pauseSession();\n stopVisualization({ resetLevels: false });\n\n if (discarded) {\n resetCurrentRecording({ notifyDelete: true });\n resetSession();\n return;\n }\n\n const blob = new Blob(chunks, { type: mimeType });\n onRecordingStop?.(blob);\n mountRecordingBlob(blob);\n void generatePreviewLevels(blob);\n resetSession();\n },\n onPaused: () => {\n pauseSession();\n stopVisualization({ resetLevels: false, closeContext: false });\n },\n onResumed: () => {\n resumeSession();\n resumeVisualization();\n },\n onStreamAcquired: (stream: MediaStream) => {\n initializeVisualization(stream);\n },\n onPermissionDenied: (error: unknown) => {\n onPermissionDenied?.(error);\n },\n onError: (error: unknown) => {\n onRecordingError?.(error);\n },\n }), [\n generatePreviewLevels,\n initializeVisualization,\n mimeType,\n mountRecordingBlob,\n onPermissionDenied,\n onRecordingError,\n onRecordingStop,\n pauseSession,\n resetCurrentRecording,\n resetSession,\n resumeSession,\n resumeVisualization,\n stopVisualization,\n ]);\n\n const {\n isPaused,\n isRecording,\n togglePause,\n cleanupStream,\n stopRecording,\n cancelRecording,\n startRecording: _startRecording,\n } = useMediaRecorder(recorderCallbacks);\n\n const startRecording = useCallback(async () => {\n resetCurrentRecording({ resetLevels: true });\n await _startRecording(mimeType);\n onRecordingStart?.();\n startNewSession();\n }, [ _startRecording, mimeType, onRecordingStart, resetCurrentRecording, startNewSession ]);\n\n useEffect(() => {\n return () => {\n cleanupVisualization();\n cleanupStream();\n resetPlayback();\n resetSession();\n };\n }, [ cleanupVisualization, cleanupStream, resetPlayback, resetSession ]);\n\n const togglePlayback = useCallback(() => {\n _togglePlayback(onRecordingError);\n }, [ _togglePlayback, onRecordingError ]);\n\n return {\n audioURL,\n isPaused,\n isPlaying,\n isRecording,\n audioLevels,\n lastDuration,\n playbackTime,\n recordingTime,\n previewLevels,\n playbackDuration,\n formatTime,\n stopPlayback,\n sendRecording,\n stopRecording,\n togglePlayback,\n startRecording,\n cancelRecording,\n deleteRecording,\n toggleRecordingPause: togglePause,\n hasRecording: Boolean(audioURL),\n };\n}\n\n\nexport { useVoiceRecorder };\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useVoiceRecorder } from './useVoiceRecorder.js';
|
|
3
|
+
import { TIMELINE_SAMPLE_INTERVAL } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* useVoiceRecorderInput — orchestrates recording, sending and timeline for VoiceRecorderInput
|
|
7
|
+
*
|
|
8
|
+
* @author: exode <hello@exode.ru>
|
|
9
|
+
*/
|
|
10
|
+
const useVoiceRecorderInput = (options) => {
|
|
11
|
+
const { onSend, onError, onCancel, uploader, onUploaded, onPermissionDenied, autoStart = true, mimeType = 'audio/webm', fileName = 'voice-message.webm', } = options;
|
|
12
|
+
const [isSending, setIsSending] = useState(false);
|
|
13
|
+
const [timelineLevels, setTimelineLevels] = useState([]);
|
|
14
|
+
const audioBlobRef = useRef(null);
|
|
15
|
+
const audioLevelsRef = useRef([]);
|
|
16
|
+
const lastDurationRef = useRef(0);
|
|
17
|
+
const shouldSendAfterStopRef = useRef(false);
|
|
18
|
+
const doSendRef = useRef();
|
|
19
|
+
const autoStartedRef = useRef(false);
|
|
20
|
+
const [uploadId] = useState(() => typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
21
|
+
? crypto.randomUUID()
|
|
22
|
+
: `upload-${Date.now()}`);
|
|
23
|
+
const handleRecordingStop = useCallback((blob) => {
|
|
24
|
+
var _a;
|
|
25
|
+
audioBlobRef.current = blob;
|
|
26
|
+
if (shouldSendAfterStopRef.current) {
|
|
27
|
+
shouldSendAfterStopRef.current = false;
|
|
28
|
+
void ((_a = doSendRef.current) === null || _a === void 0 ? void 0 : _a.call(doSendRef));
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
const recorder = useVoiceRecorder({
|
|
32
|
+
fileName,
|
|
33
|
+
mimeType,
|
|
34
|
+
uploadId,
|
|
35
|
+
onPermissionDenied,
|
|
36
|
+
onRecordingStop: handleRecordingStop,
|
|
37
|
+
onRecordingError: onError,
|
|
38
|
+
uploader: onSend ? undefined : uploader,
|
|
39
|
+
onUploaded: onSend ? (() => { }) : (onUploaded !== null && onUploaded !== void 0 ? onUploaded : (() => { })),
|
|
40
|
+
});
|
|
41
|
+
const { audioLevels, isPaused, isRecording, recordingTime, hasRecording, stopPlayback, sendRecording, stopRecording, startRecording, cancelRecording, deleteRecording, toggleRecordingPause, } = recorder;
|
|
42
|
+
audioLevelsRef.current = audioLevels;
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (recordingTime > 0) {
|
|
45
|
+
lastDurationRef.current = recordingTime;
|
|
46
|
+
}
|
|
47
|
+
}, [recordingTime]);
|
|
48
|
+
const isPreview = isPaused || (!isRecording && hasRecording);
|
|
49
|
+
const isActiveRecording = isRecording && !isPaused;
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!isActiveRecording) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const interval = setInterval(() => {
|
|
55
|
+
const levels = audioLevelsRef.current;
|
|
56
|
+
const peak = levels.length > 0 ? Math.max(...levels) : 0;
|
|
57
|
+
setTimelineLevels((prev) => [...prev, peak]);
|
|
58
|
+
}, TIMELINE_SAMPLE_INTERVAL);
|
|
59
|
+
return () => clearInterval(interval);
|
|
60
|
+
}, [isActiveRecording]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (isRecording && !isPaused) {
|
|
63
|
+
setTimelineLevels([]);
|
|
64
|
+
}
|
|
65
|
+
}, [isRecording]);
|
|
66
|
+
const handleResume = useCallback(() => {
|
|
67
|
+
stopPlayback();
|
|
68
|
+
toggleRecordingPause();
|
|
69
|
+
}, [
|
|
70
|
+
stopPlayback,
|
|
71
|
+
toggleRecordingPause,
|
|
72
|
+
]);
|
|
73
|
+
const handlePause = toggleRecordingPause;
|
|
74
|
+
const handleCancel = useCallback(() => {
|
|
75
|
+
shouldSendAfterStopRef.current = false;
|
|
76
|
+
if (isRecording) {
|
|
77
|
+
cancelRecording();
|
|
78
|
+
}
|
|
79
|
+
else if (hasRecording) {
|
|
80
|
+
stopPlayback();
|
|
81
|
+
deleteRecording();
|
|
82
|
+
}
|
|
83
|
+
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
84
|
+
}, [
|
|
85
|
+
onCancel,
|
|
86
|
+
isRecording,
|
|
87
|
+
hasRecording,
|
|
88
|
+
stopPlayback,
|
|
89
|
+
cancelRecording,
|
|
90
|
+
deleteRecording,
|
|
91
|
+
]);
|
|
92
|
+
const doSend = useCallback(async () => {
|
|
93
|
+
setIsSending(true);
|
|
94
|
+
try {
|
|
95
|
+
stopPlayback();
|
|
96
|
+
if (onSend) {
|
|
97
|
+
if (!audioBlobRef.current) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const file = new File([audioBlobRef.current], fileName, { type: audioBlobRef.current.type || mimeType });
|
|
101
|
+
await onSend(file, recordingTime || lastDurationRef.current || 0);
|
|
102
|
+
deleteRecording();
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
await sendRecording();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
onError === null || onError === void 0 ? void 0 : onError(error);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
setIsSending(false);
|
|
113
|
+
}
|
|
114
|
+
}, [
|
|
115
|
+
onSend,
|
|
116
|
+
onError,
|
|
117
|
+
fileName,
|
|
118
|
+
mimeType,
|
|
119
|
+
recordingTime,
|
|
120
|
+
sendRecording,
|
|
121
|
+
stopPlayback,
|
|
122
|
+
deleteRecording,
|
|
123
|
+
]);
|
|
124
|
+
doSendRef.current = doSend;
|
|
125
|
+
const handleSendRequest = useCallback(async () => {
|
|
126
|
+
if (isSending) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (isRecording) {
|
|
130
|
+
shouldSendAfterStopRef.current = true;
|
|
131
|
+
stopRecording();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await doSend();
|
|
135
|
+
}, [
|
|
136
|
+
doSend,
|
|
137
|
+
isRecording,
|
|
138
|
+
isSending,
|
|
139
|
+
stopRecording,
|
|
140
|
+
]);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (autoStart && !autoStartedRef.current) {
|
|
143
|
+
autoStartedRef.current = true;
|
|
144
|
+
void startRecording().catch((error) => {
|
|
145
|
+
onPermissionDenied === null || onPermissionDenied === void 0 ? void 0 : onPermissionDenied(error);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}, [autoStart]);
|
|
149
|
+
return {
|
|
150
|
+
recorder,
|
|
151
|
+
isSending,
|
|
152
|
+
isPreview,
|
|
153
|
+
timelineLevels,
|
|
154
|
+
lastDurationRef,
|
|
155
|
+
handleResume,
|
|
156
|
+
handlePause,
|
|
157
|
+
handleCancel,
|
|
158
|
+
handleSendRequest,
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export { useVoiceRecorderInput };
|
|
163
|
+
//# sourceMappingURL=useVoiceRecorderInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVoiceRecorderInput.js","sources":["../../src/hooks/useVoiceRecorderInput.ts"],"sourcesContent":["/**\n * useVoiceRecorderInput — orchestrates recording, sending and timeline for VoiceRecorderInput\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useVoiceRecorder } from './useVoiceRecorder';\nimport { TIMELINE_SAMPLE_INTERVAL } from '../constants';\nimport type { UploadStrategy } from '../types';\n\n\ninterface UseVoiceRecorderInputOptions {\n uploader?: UploadStrategy<unknown>;\n onUploaded?: (file: unknown, duration: number) => void | Promise<void>;\n onSend?: (file: File, duration: number) => void | Promise<void>;\n onError?: (error: unknown) => void;\n onCancel?: () => void;\n onPermissionDenied?: (error: unknown) => void;\n autoStart?: boolean;\n mimeType?: string;\n fileName?: string;\n}\n\n\nconst useVoiceRecorderInput = (options: UseVoiceRecorderInputOptions) => {\n\n const {\n onSend,\n onError,\n onCancel,\n uploader,\n onUploaded,\n onPermissionDenied,\n autoStart = true,\n mimeType = 'audio/webm',\n fileName = 'voice-message.webm',\n } = options;\n\n const [ isSending, setIsSending ] = useState(false);\n const [ timelineLevels, setTimelineLevels ] = useState<number[]>([]);\n\n const audioBlobRef = useRef<Blob | null>(null);\n const audioLevelsRef = useRef<number[]>([]);\n const lastDurationRef = useRef(0);\n const shouldSendAfterStopRef = useRef(false);\n const doSendRef = useRef<() => Promise<void>>();\n const autoStartedRef = useRef(false);\n\n const [ uploadId ] = useState(\n () => typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `upload-${Date.now()}`,\n );\n\n const handleRecordingStop = useCallback((blob: Blob) => {\n audioBlobRef.current = blob;\n if (shouldSendAfterStopRef.current) {\n shouldSendAfterStopRef.current = false;\n void doSendRef.current?.();\n }\n }, []);\n\n const recorder = useVoiceRecorder({\n fileName,\n mimeType,\n uploadId,\n onPermissionDenied,\n onRecordingStop: handleRecordingStop,\n onRecordingError: onError,\n uploader: onSend ? undefined : uploader,\n onUploaded: onSend ? (() => {}) : (onUploaded ?? (() => {})),\n });\n\n const {\n audioLevels,\n isPaused,\n isRecording,\n recordingTime,\n hasRecording,\n stopPlayback,\n sendRecording,\n stopRecording,\n startRecording,\n cancelRecording,\n deleteRecording,\n toggleRecordingPause,\n } = recorder;\n\n audioLevelsRef.current = audioLevels;\n\n useEffect(() => {\n if (recordingTime > 0) {\n lastDurationRef.current = recordingTime;\n }\n }, [ recordingTime ]);\n\n const isPreview = isPaused || (!isRecording && hasRecording);\n const isActiveRecording = isRecording && !isPaused;\n\n useEffect(() => {\n if (!isActiveRecording) {\n return;\n }\n const interval = setInterval(() => {\n const levels = audioLevelsRef.current;\n const peak = levels.length > 0 ? Math.max(...levels) : 0;\n setTimelineLevels((prev) => [ ...prev, peak ]);\n }, TIMELINE_SAMPLE_INTERVAL);\n return () => clearInterval(interval);\n }, [ isActiveRecording ]);\n\n useEffect(() => {\n if (isRecording && !isPaused) {\n setTimelineLevels([]);\n }\n }, [ isRecording ]);\n\n const handleResume = useCallback(() => {\n stopPlayback();\n toggleRecordingPause();\n }, [\n stopPlayback,\n toggleRecordingPause,\n ]);\n\n const handlePause = toggleRecordingPause;\n\n const handleCancel = useCallback(() => {\n shouldSendAfterStopRef.current = false;\n\n if (isRecording) {\n cancelRecording();\n } else if (hasRecording) {\n stopPlayback();\n deleteRecording();\n }\n onCancel?.();\n }, [\n onCancel,\n isRecording,\n hasRecording,\n stopPlayback,\n cancelRecording,\n deleteRecording,\n ]);\n\n const doSend = useCallback(async () => {\n setIsSending(true);\n try {\n stopPlayback();\n if (onSend) {\n if (!audioBlobRef.current) {\n return;\n }\n\n const file = new File(\n [ audioBlobRef.current ],\n fileName,\n { type: audioBlobRef.current.type || mimeType },\n );\n\n await onSend(\n file,\n recordingTime || lastDurationRef.current || 0,\n );\n\n deleteRecording();\n } else {\n await sendRecording();\n }\n } catch (error) {\n onError?.(error);\n } finally {\n setIsSending(false);\n }\n }, [\n onSend,\n onError,\n fileName,\n mimeType,\n recordingTime,\n sendRecording,\n stopPlayback,\n deleteRecording,\n ]);\n\n doSendRef.current = doSend;\n\n const handleSendRequest = useCallback(async () => {\n if (isSending) {\n return;\n }\n\n if (isRecording) {\n shouldSendAfterStopRef.current = true;\n\n stopRecording();\n\n return;\n }\n\n await doSend();\n }, [\n doSend,\n isRecording,\n isSending,\n stopRecording,\n ]);\n\n useEffect(() => {\n if (autoStart && !autoStartedRef.current) {\n autoStartedRef.current = true;\n void startRecording().catch((error) => {\n onPermissionDenied?.(error);\n });\n }\n }, [ autoStart ]);\n\n return {\n recorder,\n isSending,\n isPreview,\n timelineLevels,\n lastDurationRef,\n handleResume,\n handlePause,\n handleCancel,\n handleSendRequest,\n };\n};\n\n\nexport { useVoiceRecorderInput };\n"],"names":[],"mappings":"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"}
|