@siteed/expo-audio-stream 0.7.0 → 0.7.1
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/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +144 -138
- package/build/useAudioRecording.js.map +1 -1
- package/package.json +1 -1
- package/src/useAudioRecording.ts +175 -163
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,
|
|
1
|
+
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA2CD,wBAAgB,gBAAgB,CAAC,EAC/B,aAAa,EACb,KAAa,GACd,EAAE;IACD,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,qBAAqB,CAiLxB"}
|
|
@@ -1,174 +1,180 @@
|
|
|
1
1
|
import { Platform } from "expo-modules-core";
|
|
2
|
-
import { useCallback, useEffect,
|
|
2
|
+
import { useCallback, useEffect, useReducer } from "react";
|
|
3
3
|
import { addAudioEventListener } from ".";
|
|
4
4
|
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
5
|
+
function recorderReducer(state, action) {
|
|
6
|
+
switch (action.type) {
|
|
7
|
+
case "START":
|
|
8
|
+
return {
|
|
9
|
+
...state,
|
|
10
|
+
isRecording: true,
|
|
11
|
+
isPaused: false,
|
|
12
|
+
duration: 0,
|
|
13
|
+
size: 0,
|
|
14
|
+
};
|
|
15
|
+
case "STOP":
|
|
16
|
+
return { ...state, isRecording: false, isPaused: false };
|
|
17
|
+
case "PAUSE":
|
|
18
|
+
return { ...state, isPaused: true, isRecording: false };
|
|
19
|
+
case "RESUME":
|
|
20
|
+
return { ...state, isPaused: false, isRecording: true };
|
|
21
|
+
case "UPDATE_STATUS":
|
|
22
|
+
return {
|
|
23
|
+
...state,
|
|
24
|
+
duration: action.payload.duration,
|
|
25
|
+
size: action.payload.size,
|
|
26
|
+
};
|
|
27
|
+
default:
|
|
28
|
+
return state;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
5
31
|
export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
6
|
-
const [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
32
|
+
const [state, dispatch] = useReducer(recorderReducer, {
|
|
33
|
+
isRecording: false,
|
|
34
|
+
isPaused: false,
|
|
35
|
+
duration: 0,
|
|
36
|
+
size: 0,
|
|
37
|
+
});
|
|
38
|
+
const logDebug = (message, data) => {
|
|
39
|
+
if (debug) {
|
|
40
|
+
console.log(`[useAudioRecorder] ${message}`, data);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const TAG = "[ useAudioRecorder ] ";
|
|
44
|
+
const handleAudioEvent = useCallback(async (eventData) => {
|
|
45
|
+
const { fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, } = eventData;
|
|
46
|
+
logDebug(`useAudioRecorder] Received audio event:`, {
|
|
47
|
+
fileUri,
|
|
48
|
+
deltaSize,
|
|
49
|
+
totalSize,
|
|
50
|
+
position,
|
|
51
|
+
mimeType,
|
|
52
|
+
lastEmittedSize,
|
|
53
|
+
streamUuid,
|
|
54
|
+
encodedLength: encoded?.length,
|
|
55
|
+
});
|
|
56
|
+
if (deltaSize === 0) {
|
|
57
|
+
// Ignore packet with no data
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Add more detailed handling here
|
|
61
|
+
try {
|
|
62
|
+
// Coming from native ( ios / android ) otherwise buffer is set
|
|
63
|
+
if (Platform.OS !== "web") {
|
|
64
|
+
// Read the audio file as a base64 string for comparison
|
|
65
|
+
if (!encoded) {
|
|
66
|
+
console.error("[useAudioRecorder] Encoded audio data is missing");
|
|
67
|
+
throw new Error("Encoded audio data is missing");
|
|
68
|
+
}
|
|
69
|
+
await onAudioStream?.({
|
|
70
|
+
data: encoded,
|
|
71
|
+
position,
|
|
72
|
+
fileUri,
|
|
73
|
+
eventDataSize: deltaSize,
|
|
74
|
+
totalSize,
|
|
75
|
+
});
|
|
76
|
+
// Below code is optional, used to compare encoded data to audio on file system
|
|
77
|
+
// Fetch the audio data from the fileUri
|
|
78
|
+
// const options = {
|
|
79
|
+
// encoding: FileSystem.EncodingType.Base64,
|
|
80
|
+
// position: lastEmittedSize,
|
|
81
|
+
// length: deltaSize,
|
|
82
|
+
// };
|
|
83
|
+
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
84
|
+
// const binaryData = atob(base64Content);
|
|
85
|
+
// const content = new Uint8Array(binaryData.length);
|
|
86
|
+
// for (let i = 0; i < binaryData.length; i++) {
|
|
87
|
+
// content[i] = binaryData.charCodeAt(i);
|
|
88
|
+
// }
|
|
89
|
+
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
90
|
+
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
91
|
+
}
|
|
92
|
+
else if (buffer) {
|
|
93
|
+
// Coming from web
|
|
94
|
+
await onAudioStream?.({
|
|
95
|
+
data: buffer,
|
|
96
|
+
position,
|
|
97
|
+
fileUri,
|
|
98
|
+
eventDataSize: deltaSize,
|
|
99
|
+
totalSize,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`${TAG} Error processing audio event:`, error);
|
|
105
|
+
}
|
|
106
|
+
}, [logDebug, onAudioStream]);
|
|
10
107
|
const checkStatus = useCallback(async () => {
|
|
11
108
|
try {
|
|
12
|
-
if (!isRecording) {
|
|
109
|
+
if (!state.isRecording) {
|
|
13
110
|
return;
|
|
14
111
|
}
|
|
15
112
|
const status = ExpoAudioStreamModule.status();
|
|
16
113
|
if (debug) {
|
|
17
|
-
|
|
114
|
+
logDebug("[useAudioRecorder] Status:", status);
|
|
18
115
|
}
|
|
19
116
|
if (!status.isRecording) {
|
|
20
|
-
|
|
21
|
-
|
|
117
|
+
dispatch({ type: "STOP" });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
dispatch({
|
|
121
|
+
type: "UPDATE_STATUS",
|
|
122
|
+
payload: { duration: status.duration, size: status.size },
|
|
123
|
+
});
|
|
22
124
|
}
|
|
23
|
-
// Extract matching file from filesystem
|
|
24
|
-
setDuration(status.duration);
|
|
25
|
-
setSize(status.size);
|
|
26
125
|
}
|
|
27
126
|
catch (error) {
|
|
28
127
|
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
29
128
|
}
|
|
30
|
-
}, [isRecording]);
|
|
129
|
+
}, [state.isRecording, logDebug]);
|
|
31
130
|
useEffect(() => {
|
|
32
|
-
const interval = setInterval(checkStatus, 1000);
|
|
33
|
-
return () => clearInterval(interval);
|
|
34
|
-
}, [checkStatus]);
|
|
131
|
+
const interval = state.isRecording ? setInterval(checkStatus, 1000) : null;
|
|
132
|
+
return () => (interval ? clearInterval(interval) : undefined);
|
|
133
|
+
}, [checkStatus, state.isRecording]);
|
|
35
134
|
useEffect(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, }) => {
|
|
40
|
-
try {
|
|
41
|
-
if (debug) {
|
|
42
|
-
console.log(`[useAudioRecorder] Received audio event:`, {
|
|
43
|
-
fileUri,
|
|
44
|
-
deltaSize,
|
|
45
|
-
totalSize,
|
|
46
|
-
position,
|
|
47
|
-
mimeType,
|
|
48
|
-
lastEmittedSize,
|
|
49
|
-
streamUuid,
|
|
50
|
-
encodedLength: encoded?.length,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (deltaSize > 0) {
|
|
54
|
-
// Coming from native ( ios / android ) otherwise buffer is set
|
|
55
|
-
if (Platform.OS !== "web") {
|
|
56
|
-
// Read the audio file as a base64 string for comparison
|
|
57
|
-
try {
|
|
58
|
-
if (!encoded) {
|
|
59
|
-
console.error("[useAudioRecorder] Encoded audio data is missing");
|
|
60
|
-
throw new Error("Encoded audio data is missing");
|
|
61
|
-
}
|
|
62
|
-
// const binaryData = atob(encoded);
|
|
63
|
-
// const bytes = new Uint8Array(binaryData.length);
|
|
64
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
65
|
-
// bytes[i] = binaryData.charCodeAt(i) & 0xff; // Mask to 8 bits
|
|
66
|
-
// }
|
|
67
|
-
// const arrayBuffer = bytes.buffer;
|
|
68
|
-
// if (debug) {
|
|
69
|
-
// console.log(
|
|
70
|
-
// `[useAudioRecorder] Read audio file position=${position} deltaSize: ${deltaSize} vs encoded.length: ${encoded.length}`,
|
|
71
|
-
// );
|
|
72
|
-
// }
|
|
73
|
-
onAudioStream?.({
|
|
74
|
-
data: encoded,
|
|
75
|
-
position,
|
|
76
|
-
fileUri,
|
|
77
|
-
eventDataSize: deltaSize,
|
|
78
|
-
totalSize,
|
|
79
|
-
});
|
|
80
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
81
|
-
// Fetch the audio data from the fileUri
|
|
82
|
-
// const options = {
|
|
83
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
84
|
-
// position: lastEmittedSize,
|
|
85
|
-
// length: deltaSize,
|
|
86
|
-
// };
|
|
87
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
88
|
-
// const binaryData = atob(base64Content);
|
|
89
|
-
// const content = new Uint8Array(binaryData.length);
|
|
90
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
91
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
92
|
-
// }
|
|
93
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
94
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
console.error("[useAudioRecorder] Error reading audio file:", error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (buffer) {
|
|
101
|
-
// Coming from web
|
|
102
|
-
onAudioStream?.({
|
|
103
|
-
data: buffer,
|
|
104
|
-
position,
|
|
105
|
-
fileUri,
|
|
106
|
-
eventDataSize: deltaSize,
|
|
107
|
-
totalSize,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
console.error("[useAudioRecorder] Error processing audio event:", error);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
if (debug) {
|
|
117
|
-
console.log(`[useAudioRecorder] Subscribed to audio event listener`, subscribe);
|
|
118
|
-
}
|
|
135
|
+
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
136
|
+
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
137
|
+
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
119
138
|
return () => {
|
|
120
|
-
|
|
121
|
-
console.log(`[useAudioRecorder] Removing audio event listener`);
|
|
122
|
-
}
|
|
139
|
+
logDebug(`${TAG} Removing audio event listener`);
|
|
123
140
|
subscribe.remove();
|
|
124
141
|
};
|
|
125
|
-
}, []);
|
|
142
|
+
}, [handleAudioEvent, logDebug]);
|
|
126
143
|
const startRecording = useCallback(async (recordingOptions) => {
|
|
127
|
-
setIsRecording(true);
|
|
128
|
-
setIsPaused(false);
|
|
129
|
-
setSize(0);
|
|
130
|
-
setDuration(0);
|
|
131
144
|
if (debug) {
|
|
132
|
-
|
|
145
|
+
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
133
146
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
const startResult = await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
148
|
+
dispatch({ type: "START" });
|
|
149
|
+
return startResult;
|
|
150
|
+
}, [logDebug]);
|
|
137
151
|
const stopRecording = useCallback(async () => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
}, []);
|
|
152
|
+
logDebug(`${TAG} stop recording`);
|
|
153
|
+
const stopResult = await ExpoAudioStreamModule.stopRecording();
|
|
154
|
+
dispatch({ type: "STOP" });
|
|
155
|
+
return stopResult;
|
|
156
|
+
}, [logDebug]);
|
|
143
157
|
const pauseRecording = useCallback(async () => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
catch (error) {
|
|
150
|
-
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
151
|
-
}
|
|
152
|
-
}, [debug]);
|
|
158
|
+
logDebug(`${TAG} pause recording`);
|
|
159
|
+
const pauseResult = await ExpoAudioStreamModule.pauseRecording();
|
|
160
|
+
dispatch({ type: "PAUSE" });
|
|
161
|
+
return pauseResult;
|
|
162
|
+
}, [logDebug]);
|
|
153
163
|
const resumeRecording = useCallback(async () => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
catch (error) {
|
|
160
|
-
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
161
|
-
}
|
|
162
|
-
}, [debug]);
|
|
164
|
+
logDebug(`${TAG} resume recording`);
|
|
165
|
+
const resumeResult = await ExpoAudioStreamModule.resumeRecording();
|
|
166
|
+
dispatch({ type: "RESUME" });
|
|
167
|
+
return resumeResult;
|
|
168
|
+
}, [logDebug]);
|
|
163
169
|
return {
|
|
164
170
|
startRecording,
|
|
165
171
|
stopRecording,
|
|
166
172
|
pauseRecording,
|
|
167
173
|
resumeRecording,
|
|
168
|
-
isPaused,
|
|
169
|
-
isRecording,
|
|
170
|
-
duration,
|
|
171
|
-
size,
|
|
174
|
+
isPaused: state.isPaused,
|
|
175
|
+
isRecording: state.isRecording,
|
|
176
|
+
duration: state.duration,
|
|
177
|
+
size: state.size,
|
|
172
178
|
};
|
|
173
179
|
}
|
|
174
180
|
//# sourceMappingURL=useAudioRecording.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioStreamResult,\n AudioStreamStatus,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | Blob;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => void;\n resumeRecording: () => void;\n isRecording: boolean;\n isPaused: boolean;\n duration: number; // Duration of the recording\n size: number; // Size in bytes of the recorded audio\n}\n\nexport function useAudioRecorder({\n onAudioStream,\n debug = false,\n}: {\n onAudioStream?: (_: AudioDataEvent) => Promise<void>;\n debug?: boolean;\n}): UseAudioRecorderState {\n const [isRecording, setIsRecording] = useState(false);\n const [isPaused, setIsPaused] = useState(false);\n const [duration, setDuration] = useState(0);\n const [size, setSize] = useState(0);\n\n const checkStatus = useCallback(async () => {\n try {\n if (!isRecording) {\n return;\n }\n\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n console.log(`[useAudioRecorder] Status:`, status);\n }\n\n if (!status.isRecording) {\n // Don't update if recording stopped.\n return;\n }\n // Extract matching file from filesystem\n setDuration(status.duration);\n setSize(status.size);\n } catch (error) {\n console.error(`[useAudioRecorder] Error getting status:`, error);\n }\n }, [isRecording]);\n\n useEffect(() => {\n const interval = setInterval(checkStatus, 1000);\n return () => clearInterval(interval);\n }, [checkStatus]);\n\n useEffect(() => {\n if (debug) {\n console.log(\n `[useAudioRecorder] Registering audio event listener`,\n onAudioStream,\n );\n }\n const subscribe = addAudioEventListener(\n async ({\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n }) => {\n try {\n if (debug) {\n console.log(`[useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n });\n }\n if (deltaSize > 0) {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== \"web\") {\n // Read the audio file as a base64 string for comparison\n try {\n if (!encoded) {\n console.error(\n \"[useAudioRecorder] Encoded audio data is missing\",\n );\n throw new Error(\"Encoded audio data is missing\");\n }\n // const binaryData = atob(encoded);\n // const bytes = new Uint8Array(binaryData.length);\n // for (let i = 0; i < binaryData.length; i++) {\n // bytes[i] = binaryData.charCodeAt(i) & 0xff; // Mask to 8 bits\n // }\n // const arrayBuffer = bytes.buffer;\n\n // if (debug) {\n // console.log(\n // `[useAudioRecorder] Read audio file position=${position} deltaSize: ${deltaSize} vs encoded.length: ${encoded.length}`,\n // );\n // }\n\n onAudioStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n\n // Below code is optional, used to compare encoded data to audio on file system\n // Fetch the audio data from the fileUri\n // const options = {\n // encoding: FileSystem.EncodingType.Base64,\n // position: lastEmittedSize,\n // length: deltaSize,\n // };\n // const base64Content = await FileSystem.readAsStringAsync(fileUri, options);\n // const binaryData = atob(base64Content);\n // const content = new Uint8Array(binaryData.length);\n // for (let i = 0; i < binaryData.length; i++) {\n // content[i] = binaryData.charCodeAt(i);\n // }\n // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array\n // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)\n } catch (error) {\n console.error(\n \"[useAudioRecorder] Error reading audio file:\",\n error,\n );\n }\n } else if (buffer) {\n // Coming from web\n onAudioStream?.({\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n }\n }\n } catch (error) {\n console.error(\n \"[useAudioRecorder] Error processing audio event:\",\n error,\n );\n }\n },\n );\n if (debug) {\n console.log(\n `[useAudioRecorder] Subscribed to audio event listener`,\n subscribe,\n );\n }\n return () => {\n if (debug) {\n console.log(`[useAudioRecorder] Removing audio event listener`);\n }\n subscribe.remove();\n };\n }, []);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n setIsRecording(true);\n setIsPaused(false);\n setSize(0);\n setDuration(0);\n if (debug) {\n console.log(`[useAudioRecorder] start recoding`, recordingOptions);\n }\n\n const result: StartAudioStreamResult =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n return result;\n },\n [debug],\n );\n\n const stopRecording = useCallback(async () => {\n const result: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n setIsRecording(false);\n setIsPaused(false);\n return result;\n }, []);\n\n const pauseRecording = useCallback(async () => {\n try {\n await ExpoAudioStreamModule.pauseRecording();\n setIsPaused(true);\n setIsRecording(false);\n } catch (error) {\n console.error(\"[useAudioRecorder] Error pausing recording:\", error);\n }\n }, [debug]);\n\n const resumeRecording = useCallback(async () => {\n try {\n await ExpoAudioStreamModule.resumeRecording();\n setIsPaused(true);\n setIsRecording(false);\n } catch (error) {\n console.error(\"[useAudioRecorder] Error pausing recording:\", error);\n }\n }, [debug]);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused,\n isRecording,\n duration,\n size,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useReducer, useState } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioEventPayload,\n AudioStreamResult,\n AudioStreamStatus,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | Blob;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => void;\n resumeRecording: () => void;\n isRecording: boolean;\n isPaused: boolean;\n duration: number; // Duration of the recording\n size: number; // Size in bytes of the recorded audio\n}\n\ninterface RecorderState {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n}\n\ntype RecorderAction =\n | { type: \"START\" | \"STOP\" | \"PAUSE\" | \"RESUME\" }\n | { type: \"UPDATE_STATUS\"; payload: { duration: number; size: number } };\n\nfunction recorderReducer(\n state: RecorderState,\n action: RecorderAction,\n): RecorderState {\n switch (action.type) {\n case \"START\":\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n duration: 0,\n size: 0,\n };\n case \"STOP\":\n return { ...state, isRecording: false, isPaused: false };\n case \"PAUSE\":\n return { ...state, isPaused: true, isRecording: false };\n case \"RESUME\":\n return { ...state, isPaused: false, isRecording: true };\n case \"UPDATE_STATUS\":\n return {\n ...state,\n duration: action.payload.duration,\n size: action.payload.size,\n };\n default:\n return state;\n }\n}\n\nexport function useAudioRecorder({\n onAudioStream,\n debug = false,\n}: {\n onAudioStream?: (_: AudioDataEvent) => Promise<void>;\n debug?: boolean;\n}): UseAudioRecorderState {\n const [state, dispatch] = useReducer(recorderReducer, {\n isRecording: false,\n isPaused: false,\n duration: 0,\n size: 0,\n });\n\n const logDebug = (message: string, data?: any) => {\n if (debug) {\n console.log(`[useAudioRecorder] ${message}`, data);\n }\n };\n\n const TAG = \"[ useAudioRecorder ] \";\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n } = eventData;\n logDebug(`useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n });\n if (deltaSize === 0) {\n // Ignore packet with no data\n return;\n }\n // Add more detailed handling here\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== \"web\") {\n // Read the audio file as a base64 string for comparison\n if (!encoded) {\n console.error(\"[useAudioRecorder] Encoded audio data is missing\");\n throw new Error(\"Encoded audio data is missing\");\n }\n await onAudioStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n\n // Below code is optional, used to compare encoded data to audio on file system\n // Fetch the audio data from the fileUri\n // const options = {\n // encoding: FileSystem.EncodingType.Base64,\n // position: lastEmittedSize,\n // length: deltaSize,\n // };\n // const base64Content = await FileSystem.readAsStringAsync(fileUri, options);\n // const binaryData = atob(base64Content);\n // const content = new Uint8Array(binaryData.length);\n // for (let i = 0; i < binaryData.length; i++) {\n // content[i] = binaryData.charCodeAt(i);\n // }\n // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array\n // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)\n } else if (buffer) {\n // Coming from web\n await onAudioStream?.({\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n }\n } catch (error) {\n console.error(`${TAG} Error processing audio event:`, error);\n }\n },\n [logDebug, onAudioStream],\n );\n\n const checkStatus = useCallback(async () => {\n try {\n if (!state.isRecording) {\n return;\n }\n\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n logDebug(\"[useAudioRecorder] Status:\", status);\n }\n\n if (!status.isRecording) {\n dispatch({ type: \"STOP\" });\n } else {\n dispatch({\n type: \"UPDATE_STATUS\",\n payload: { duration: status.duration, size: status.size },\n });\n }\n } catch (error) {\n console.error(`[useAudioRecorder] Error getting status:`, error);\n }\n }, [state.isRecording, logDebug]);\n\n useEffect(() => {\n const interval = state.isRecording ? setInterval(checkStatus, 1000) : null;\n return () => (interval ? clearInterval(interval) : undefined);\n }, [checkStatus, state.isRecording]);\n\n useEffect(() => {\n logDebug(`${TAG} Registering audio event listener`, onAudioStream);\n const subscribe = addAudioEventListener(handleAudioEvent);\n logDebug(`${TAG} Subscribed to audio event listener`, subscribe);\n\n return () => {\n logDebug(`${TAG} Removing audio event listener`);\n subscribe.remove();\n };\n }, [handleAudioEvent, logDebug]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n if (debug) {\n logDebug(`${TAG} start recoding`, recordingOptions);\n }\n const startResult: StartAudioStreamResult =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n dispatch({ type: \"START\" });\n\n return startResult;\n },\n [logDebug],\n );\n\n const stopRecording = useCallback(async () => {\n logDebug(`${TAG} stop recording`);\n const stopResult: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n dispatch({ type: \"STOP\" });\n return stopResult;\n }, [logDebug]);\n\n const pauseRecording = useCallback(async () => {\n logDebug(`${TAG} pause recording`);\n const pauseResult = await ExpoAudioStreamModule.pauseRecording();\n dispatch({ type: \"PAUSE\" });\n return pauseResult;\n }, [logDebug]);\n\n const resumeRecording = useCallback(async () => {\n logDebug(`${TAG} resume recording`);\n const resumeResult = await ExpoAudioStreamModule.resumeRecording();\n dispatch({ type: \"RESUME\" });\n return resumeResult;\n }, [logDebug]);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n duration: state.duration,\n size: state.size,\n };\n}\n"]}
|
package/package.json
CHANGED
package/src/useAudioRecording.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Platform } from "expo-modules-core";
|
|
2
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useReducer, useState } from "react";
|
|
3
3
|
|
|
4
4
|
import { addAudioEventListener } from ".";
|
|
5
5
|
import {
|
|
6
|
+
AudioEventPayload,
|
|
6
7
|
AudioStreamResult,
|
|
7
8
|
AudioStreamStatus,
|
|
8
9
|
RecordingConfig,
|
|
@@ -28,6 +29,47 @@ export interface UseAudioRecorderState {
|
|
|
28
29
|
size: number; // Size in bytes of the recorded audio
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
interface RecorderState {
|
|
33
|
+
isRecording: boolean;
|
|
34
|
+
isPaused: boolean;
|
|
35
|
+
duration: number;
|
|
36
|
+
size: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type RecorderAction =
|
|
40
|
+
| { type: "START" | "STOP" | "PAUSE" | "RESUME" }
|
|
41
|
+
| { type: "UPDATE_STATUS"; payload: { duration: number; size: number } };
|
|
42
|
+
|
|
43
|
+
function recorderReducer(
|
|
44
|
+
state: RecorderState,
|
|
45
|
+
action: RecorderAction,
|
|
46
|
+
): RecorderState {
|
|
47
|
+
switch (action.type) {
|
|
48
|
+
case "START":
|
|
49
|
+
return {
|
|
50
|
+
...state,
|
|
51
|
+
isRecording: true,
|
|
52
|
+
isPaused: false,
|
|
53
|
+
duration: 0,
|
|
54
|
+
size: 0,
|
|
55
|
+
};
|
|
56
|
+
case "STOP":
|
|
57
|
+
return { ...state, isRecording: false, isPaused: false };
|
|
58
|
+
case "PAUSE":
|
|
59
|
+
return { ...state, isPaused: true, isRecording: false };
|
|
60
|
+
case "RESUME":
|
|
61
|
+
return { ...state, isPaused: false, isRecording: true };
|
|
62
|
+
case "UPDATE_STATUS":
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
duration: action.payload.duration,
|
|
66
|
+
size: action.payload.size,
|
|
67
|
+
};
|
|
68
|
+
default:
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
export function useAudioRecorder({
|
|
32
74
|
onAudioStream,
|
|
33
75
|
debug = false,
|
|
@@ -35,210 +77,180 @@ export function useAudioRecorder({
|
|
|
35
77
|
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
36
78
|
debug?: boolean;
|
|
37
79
|
}): UseAudioRecorderState {
|
|
38
|
-
const [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
80
|
+
const [state, dispatch] = useReducer(recorderReducer, {
|
|
81
|
+
isRecording: false,
|
|
82
|
+
isPaused: false,
|
|
83
|
+
duration: 0,
|
|
84
|
+
size: 0,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const logDebug = (message: string, data?: any) => {
|
|
88
|
+
if (debug) {
|
|
89
|
+
console.log(`[useAudioRecorder] ${message}`, data);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const TAG = "[ useAudioRecorder ] ";
|
|
94
|
+
const handleAudioEvent = useCallback(
|
|
95
|
+
async (eventData: AudioEventPayload) => {
|
|
96
|
+
const {
|
|
97
|
+
fileUri,
|
|
98
|
+
deltaSize,
|
|
99
|
+
totalSize,
|
|
100
|
+
lastEmittedSize,
|
|
101
|
+
position,
|
|
102
|
+
streamUuid,
|
|
103
|
+
encoded,
|
|
104
|
+
mimeType,
|
|
105
|
+
buffer,
|
|
106
|
+
} = eventData;
|
|
107
|
+
logDebug(`useAudioRecorder] Received audio event:`, {
|
|
108
|
+
fileUri,
|
|
109
|
+
deltaSize,
|
|
110
|
+
totalSize,
|
|
111
|
+
position,
|
|
112
|
+
mimeType,
|
|
113
|
+
lastEmittedSize,
|
|
114
|
+
streamUuid,
|
|
115
|
+
encodedLength: encoded?.length,
|
|
116
|
+
});
|
|
117
|
+
if (deltaSize === 0) {
|
|
118
|
+
// Ignore packet with no data
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Add more detailed handling here
|
|
122
|
+
try {
|
|
123
|
+
// Coming from native ( ios / android ) otherwise buffer is set
|
|
124
|
+
if (Platform.OS !== "web") {
|
|
125
|
+
// Read the audio file as a base64 string for comparison
|
|
126
|
+
if (!encoded) {
|
|
127
|
+
console.error("[useAudioRecorder] Encoded audio data is missing");
|
|
128
|
+
throw new Error("Encoded audio data is missing");
|
|
129
|
+
}
|
|
130
|
+
await onAudioStream?.({
|
|
131
|
+
data: encoded,
|
|
132
|
+
position,
|
|
133
|
+
fileUri,
|
|
134
|
+
eventDataSize: deltaSize,
|
|
135
|
+
totalSize,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Below code is optional, used to compare encoded data to audio on file system
|
|
139
|
+
// Fetch the audio data from the fileUri
|
|
140
|
+
// const options = {
|
|
141
|
+
// encoding: FileSystem.EncodingType.Base64,
|
|
142
|
+
// position: lastEmittedSize,
|
|
143
|
+
// length: deltaSize,
|
|
144
|
+
// };
|
|
145
|
+
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
146
|
+
// const binaryData = atob(base64Content);
|
|
147
|
+
// const content = new Uint8Array(binaryData.length);
|
|
148
|
+
// for (let i = 0; i < binaryData.length; i++) {
|
|
149
|
+
// content[i] = binaryData.charCodeAt(i);
|
|
150
|
+
// }
|
|
151
|
+
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
152
|
+
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
153
|
+
} else if (buffer) {
|
|
154
|
+
// Coming from web
|
|
155
|
+
await onAudioStream?.({
|
|
156
|
+
data: buffer,
|
|
157
|
+
position,
|
|
158
|
+
fileUri,
|
|
159
|
+
eventDataSize: deltaSize,
|
|
160
|
+
totalSize,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`${TAG} Error processing audio event:`, error);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[logDebug, onAudioStream],
|
|
168
|
+
);
|
|
42
169
|
|
|
43
170
|
const checkStatus = useCallback(async () => {
|
|
44
171
|
try {
|
|
45
|
-
if (!isRecording) {
|
|
172
|
+
if (!state.isRecording) {
|
|
46
173
|
return;
|
|
47
174
|
}
|
|
48
175
|
|
|
49
176
|
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
50
177
|
if (debug) {
|
|
51
|
-
|
|
178
|
+
logDebug("[useAudioRecorder] Status:", status);
|
|
52
179
|
}
|
|
53
180
|
|
|
54
181
|
if (!status.isRecording) {
|
|
55
|
-
|
|
56
|
-
|
|
182
|
+
dispatch({ type: "STOP" });
|
|
183
|
+
} else {
|
|
184
|
+
dispatch({
|
|
185
|
+
type: "UPDATE_STATUS",
|
|
186
|
+
payload: { duration: status.duration, size: status.size },
|
|
187
|
+
});
|
|
57
188
|
}
|
|
58
|
-
// Extract matching file from filesystem
|
|
59
|
-
setDuration(status.duration);
|
|
60
|
-
setSize(status.size);
|
|
61
189
|
} catch (error) {
|
|
62
190
|
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
63
191
|
}
|
|
64
|
-
}, [isRecording]);
|
|
192
|
+
}, [state.isRecording, logDebug]);
|
|
65
193
|
|
|
66
194
|
useEffect(() => {
|
|
67
|
-
const interval = setInterval(checkStatus, 1000);
|
|
68
|
-
return () => clearInterval(interval);
|
|
69
|
-
}, [checkStatus]);
|
|
195
|
+
const interval = state.isRecording ? setInterval(checkStatus, 1000) : null;
|
|
196
|
+
return () => (interval ? clearInterval(interval) : undefined);
|
|
197
|
+
}, [checkStatus, state.isRecording]);
|
|
70
198
|
|
|
71
199
|
useEffect(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
const subscribe = addAudioEventListener(
|
|
79
|
-
async ({
|
|
80
|
-
fileUri,
|
|
81
|
-
deltaSize,
|
|
82
|
-
totalSize,
|
|
83
|
-
lastEmittedSize,
|
|
84
|
-
position,
|
|
85
|
-
streamUuid,
|
|
86
|
-
encoded,
|
|
87
|
-
mimeType,
|
|
88
|
-
buffer,
|
|
89
|
-
}) => {
|
|
90
|
-
try {
|
|
91
|
-
if (debug) {
|
|
92
|
-
console.log(`[useAudioRecorder] Received audio event:`, {
|
|
93
|
-
fileUri,
|
|
94
|
-
deltaSize,
|
|
95
|
-
totalSize,
|
|
96
|
-
position,
|
|
97
|
-
mimeType,
|
|
98
|
-
lastEmittedSize,
|
|
99
|
-
streamUuid,
|
|
100
|
-
encodedLength: encoded?.length,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (deltaSize > 0) {
|
|
104
|
-
// Coming from native ( ios / android ) otherwise buffer is set
|
|
105
|
-
if (Platform.OS !== "web") {
|
|
106
|
-
// Read the audio file as a base64 string for comparison
|
|
107
|
-
try {
|
|
108
|
-
if (!encoded) {
|
|
109
|
-
console.error(
|
|
110
|
-
"[useAudioRecorder] Encoded audio data is missing",
|
|
111
|
-
);
|
|
112
|
-
throw new Error("Encoded audio data is missing");
|
|
113
|
-
}
|
|
114
|
-
// const binaryData = atob(encoded);
|
|
115
|
-
// const bytes = new Uint8Array(binaryData.length);
|
|
116
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
117
|
-
// bytes[i] = binaryData.charCodeAt(i) & 0xff; // Mask to 8 bits
|
|
118
|
-
// }
|
|
119
|
-
// const arrayBuffer = bytes.buffer;
|
|
120
|
-
|
|
121
|
-
// if (debug) {
|
|
122
|
-
// console.log(
|
|
123
|
-
// `[useAudioRecorder] Read audio file position=${position} deltaSize: ${deltaSize} vs encoded.length: ${encoded.length}`,
|
|
124
|
-
// );
|
|
125
|
-
// }
|
|
126
|
-
|
|
127
|
-
onAudioStream?.({
|
|
128
|
-
data: encoded,
|
|
129
|
-
position,
|
|
130
|
-
fileUri,
|
|
131
|
-
eventDataSize: deltaSize,
|
|
132
|
-
totalSize,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
136
|
-
// Fetch the audio data from the fileUri
|
|
137
|
-
// const options = {
|
|
138
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
139
|
-
// position: lastEmittedSize,
|
|
140
|
-
// length: deltaSize,
|
|
141
|
-
// };
|
|
142
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
143
|
-
// const binaryData = atob(base64Content);
|
|
144
|
-
// const content = new Uint8Array(binaryData.length);
|
|
145
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
146
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
147
|
-
// }
|
|
148
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
149
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.error(
|
|
152
|
-
"[useAudioRecorder] Error reading audio file:",
|
|
153
|
-
error,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
} else if (buffer) {
|
|
157
|
-
// Coming from web
|
|
158
|
-
onAudioStream?.({
|
|
159
|
-
data: buffer,
|
|
160
|
-
position,
|
|
161
|
-
fileUri,
|
|
162
|
-
eventDataSize: deltaSize,
|
|
163
|
-
totalSize,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
console.error(
|
|
169
|
-
"[useAudioRecorder] Error processing audio event:",
|
|
170
|
-
error,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
if (debug) {
|
|
176
|
-
console.log(
|
|
177
|
-
`[useAudioRecorder] Subscribed to audio event listener`,
|
|
178
|
-
subscribe,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
200
|
+
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
201
|
+
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
202
|
+
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
203
|
+
|
|
181
204
|
return () => {
|
|
182
|
-
|
|
183
|
-
console.log(`[useAudioRecorder] Removing audio event listener`);
|
|
184
|
-
}
|
|
205
|
+
logDebug(`${TAG} Removing audio event listener`);
|
|
185
206
|
subscribe.remove();
|
|
186
207
|
};
|
|
187
|
-
}, []);
|
|
208
|
+
}, [handleAudioEvent, logDebug]);
|
|
188
209
|
|
|
189
210
|
const startRecording = useCallback(
|
|
190
211
|
async (recordingOptions: RecordingConfig) => {
|
|
191
|
-
setIsRecording(true);
|
|
192
|
-
setIsPaused(false);
|
|
193
|
-
setSize(0);
|
|
194
|
-
setDuration(0);
|
|
195
212
|
if (debug) {
|
|
196
|
-
|
|
213
|
+
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
197
214
|
}
|
|
198
|
-
|
|
199
|
-
const result: StartAudioStreamResult =
|
|
215
|
+
const startResult: StartAudioStreamResult =
|
|
200
216
|
await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
201
|
-
|
|
217
|
+
dispatch({ type: "START" });
|
|
218
|
+
|
|
219
|
+
return startResult;
|
|
202
220
|
},
|
|
203
|
-
[
|
|
221
|
+
[logDebug],
|
|
204
222
|
);
|
|
205
223
|
|
|
206
224
|
const stopRecording = useCallback(async () => {
|
|
207
|
-
|
|
225
|
+
logDebug(`${TAG} stop recording`);
|
|
226
|
+
const stopResult: AudioStreamResult =
|
|
208
227
|
await ExpoAudioStreamModule.stopRecording();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}, []);
|
|
228
|
+
dispatch({ type: "STOP" });
|
|
229
|
+
return stopResult;
|
|
230
|
+
}, [logDebug]);
|
|
213
231
|
|
|
214
232
|
const pauseRecording = useCallback(async () => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
221
|
-
}
|
|
222
|
-
}, [debug]);
|
|
233
|
+
logDebug(`${TAG} pause recording`);
|
|
234
|
+
const pauseResult = await ExpoAudioStreamModule.pauseRecording();
|
|
235
|
+
dispatch({ type: "PAUSE" });
|
|
236
|
+
return pauseResult;
|
|
237
|
+
}, [logDebug]);
|
|
223
238
|
|
|
224
239
|
const resumeRecording = useCallback(async () => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
231
|
-
}
|
|
232
|
-
}, [debug]);
|
|
240
|
+
logDebug(`${TAG} resume recording`);
|
|
241
|
+
const resumeResult = await ExpoAudioStreamModule.resumeRecording();
|
|
242
|
+
dispatch({ type: "RESUME" });
|
|
243
|
+
return resumeResult;
|
|
244
|
+
}, [logDebug]);
|
|
233
245
|
|
|
234
246
|
return {
|
|
235
247
|
startRecording,
|
|
236
248
|
stopRecording,
|
|
237
249
|
pauseRecording,
|
|
238
250
|
resumeRecording,
|
|
239
|
-
isPaused,
|
|
240
|
-
isRecording,
|
|
241
|
-
duration,
|
|
242
|
-
size,
|
|
251
|
+
isPaused: state.isPaused,
|
|
252
|
+
isRecording: state.isRecording,
|
|
253
|
+
duration: state.duration,
|
|
254
|
+
size: state.size,
|
|
243
255
|
};
|
|
244
256
|
}
|