@siteed/expo-audio-stream 0.6.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 +2 -0
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +147 -136
- package/build/useAudioRecording.js.map +1 -1
- package/ios/AudioStreamManager.swift +15 -10
- package/ios/ExpoAudioStreamModule.swift +12 -6
- package/package.json +1 -1
- package/src/useAudioRecording.ts +182 -163
|
@@ -2,6 +2,7 @@ import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./Ex
|
|
|
2
2
|
export interface AudioDataEvent {
|
|
3
3
|
data: string | Blob;
|
|
4
4
|
position: number;
|
|
5
|
+
fileUri: string;
|
|
5
6
|
eventDataSize: number;
|
|
6
7
|
totalSize: number;
|
|
7
8
|
}
|
|
@@ -9,6 +10,7 @@ export interface UseAudioRecorderState {
|
|
|
9
10
|
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
10
11
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
11
12
|
pauseRecording: () => void;
|
|
13
|
+
resumeRecording: () => void;
|
|
12
14
|
isRecording: boolean;
|
|
13
15
|
isPaused: boolean;
|
|
14
16
|
duration: number;
|
|
@@ -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,169 +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
|
-
eventDataSize: deltaSize,
|
|
77
|
-
totalSize,
|
|
78
|
-
});
|
|
79
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
80
|
-
// Fetch the audio data from the fileUri
|
|
81
|
-
// const options = {
|
|
82
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
83
|
-
// position: lastEmittedSize,
|
|
84
|
-
// length: deltaSize,
|
|
85
|
-
// };
|
|
86
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
87
|
-
// const binaryData = atob(base64Content);
|
|
88
|
-
// const content = new Uint8Array(binaryData.length);
|
|
89
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
90
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
91
|
-
// }
|
|
92
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
93
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
console.error("[useAudioRecorder] Error reading audio file:", error);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (buffer) {
|
|
100
|
-
// Coming from web
|
|
101
|
-
onAudioStream?.({
|
|
102
|
-
data: buffer,
|
|
103
|
-
position,
|
|
104
|
-
eventDataSize: deltaSize,
|
|
105
|
-
totalSize,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
console.error("[useAudioRecorder] Error processing audio event:", error);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
if (debug) {
|
|
115
|
-
console.log(`[useAudioRecorder] Subscribed to audio event listener`, subscribe);
|
|
116
|
-
}
|
|
135
|
+
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
136
|
+
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
137
|
+
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
117
138
|
return () => {
|
|
118
|
-
|
|
119
|
-
console.log(`[useAudioRecorder] Removing audio event listener`);
|
|
120
|
-
}
|
|
139
|
+
logDebug(`${TAG} Removing audio event listener`);
|
|
121
140
|
subscribe.remove();
|
|
122
141
|
};
|
|
123
|
-
}, []);
|
|
142
|
+
}, [handleAudioEvent, logDebug]);
|
|
124
143
|
const startRecording = useCallback(async (recordingOptions) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
setSize(0);
|
|
128
|
-
setDuration(0);
|
|
129
|
-
try {
|
|
130
|
-
if (debug) {
|
|
131
|
-
console.log(`[useAudioRecorder] start recoding`, recordingOptions);
|
|
132
|
-
}
|
|
133
|
-
const fileUrl = await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
134
|
-
return fileUrl;
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
console.error("[useAudioRecorder] Error starting recording:", error);
|
|
138
|
-
setIsRecording(false);
|
|
144
|
+
if (debug) {
|
|
145
|
+
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
139
146
|
}
|
|
140
|
-
|
|
147
|
+
const startResult = await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
148
|
+
dispatch({ type: "START" });
|
|
149
|
+
return startResult;
|
|
150
|
+
}, [logDebug]);
|
|
141
151
|
const stopRecording = useCallback(async () => {
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return result;
|
|
148
|
-
}, []);
|
|
152
|
+
logDebug(`${TAG} stop recording`);
|
|
153
|
+
const stopResult = await ExpoAudioStreamModule.stopRecording();
|
|
154
|
+
dispatch({ type: "STOP" });
|
|
155
|
+
return stopResult;
|
|
156
|
+
}, [logDebug]);
|
|
149
157
|
const pauseRecording = useCallback(async () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
logDebug(`${TAG} pause recording`);
|
|
159
|
+
const pauseResult = await ExpoAudioStreamModule.pauseRecording();
|
|
160
|
+
dispatch({ type: "PAUSE" });
|
|
161
|
+
return pauseResult;
|
|
162
|
+
}, [logDebug]);
|
|
163
|
+
const resumeRecording = useCallback(async () => {
|
|
164
|
+
logDebug(`${TAG} resume recording`);
|
|
165
|
+
const resumeResult = await ExpoAudioStreamModule.resumeRecording();
|
|
166
|
+
dispatch({ type: "RESUME" });
|
|
167
|
+
return resumeResult;
|
|
168
|
+
}, [logDebug]);
|
|
159
169
|
return {
|
|
160
170
|
startRecording,
|
|
161
171
|
stopRecording,
|
|
162
172
|
pauseRecording,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
resumeRecording,
|
|
174
|
+
isPaused: state.isPaused,
|
|
175
|
+
isRecording: state.isRecording,
|
|
176
|
+
duration: state.duration,
|
|
177
|
+
size: state.size,
|
|
167
178
|
};
|
|
168
179
|
}
|
|
169
180
|
//# sourceMappingURL=useAudioRecording.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,GAAG,CAAC;AAO1C,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAkB5D,MAAM,UAAU,gBAAgB,CAAC,EAC/B,aAAa,EACb,KAAK,GAAG,KAAK,GAId;IACC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpC,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAsB,qBAAqB,CAAC,MAAM,EAAE,CAAC;YACjE,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,MAAM,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,qCAAqC;gBACrC,OAAO;YACT,CAAC;YACD,wCAAwC;YACxC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;QACnE,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CACT,qDAAqD,EACrD,aAAa,CACd,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,qBAAqB,CACrC,KAAK,EAAE,EACL,OAAO,EACP,SAAS,EACT,SAAS,EACT,eAAe,EACf,QAAQ,EACR,UAAU,EACV,OAAO,EACP,QAAQ,EACR,MAAM,GACP,EAAE,EAAE;YACH,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,CAAC,GAAG,CAAC,0CAA0C,EAAE;wBACtD,OAAO;wBACP,SAAS;wBACT,SAAS;wBACT,QAAQ;wBACR,QAAQ;wBACR,eAAe;wBACf,UAAU;wBACV,aAAa,EAAE,OAAO,EAAE,MAAM;qBAC/B,CAAC,CAAC;gBACL,CAAC;gBACD,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;oBAClB,+DAA+D;oBAC/D,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;wBAC1B,wDAAwD;wBACxD,IAAI,CAAC;4BACH,IAAI,CAAC,OAAO,EAAE,CAAC;gCACb,OAAO,CAAC,KAAK,CACX,kDAAkD,CACnD,CAAC;gCACF,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;4BACnD,CAAC;4BACD,oCAAoC;4BACpC,mDAAmD;4BACnD,gDAAgD;4BAChD,kEAAkE;4BAClE,IAAI;4BACJ,oCAAoC;4BAEpC,eAAe;4BACf,iBAAiB;4BACjB,8HAA8H;4BAC9H,OAAO;4BACP,IAAI;4BAEJ,aAAa,EAAE,CAAC;gCACd,IAAI,EAAE,OAAO;gCACb,QAAQ;gCACR,aAAa,EAAE,SAAS;gCACxB,SAAS;6BACV,CAAC,CAAC;4BAEH,+EAA+E;4BAC/E,wCAAwC;4BACxC,oBAAoB;4BACpB,gDAAgD;4BAChD,iCAAiC;4BACjC,yBAAyB;4BACzB,KAAK;4BACL,8EAA8E;4BAC9E,0CAA0C;4BAC1C,qDAAqD;4BACrD,gDAAgD;4BAChD,yCAAyC;4BACzC,IAAI;4BACJ,oHAAoH;4BACpH,4EAA4E;wBAC9E,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,OAAO,CAAC,KAAK,CACX,8CAA8C,EAC9C,KAAK,CACN,CAAC;wBACJ,CAAC;oBACH,CAAC;yBAAM,IAAI,MAAM,EAAE,CAAC;wBAClB,kBAAkB;wBAClB,aAAa,EAAE,CAAC;4BACd,IAAI,EAAE,MAAM;4BACZ,QAAQ;4BACR,aAAa,EAAE,SAAS;4BACxB,SAAS;yBACV,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CACX,kDAAkD,EAClD,KAAK,CACN,CAAC;YACJ,CAAC;QACH,CAAC,CACF,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CACT,uDAAuD,EACvD,SAAS,CACV,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,EAAE;YACV,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;YAClE,CAAC;YACD,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAChC,KAAK,EAAE,gBAAiC,EAAE,EAAE;QAC1C,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,CAAC,CAAC,CAAC,CAAC;QACX,WAAW,CAAC,CAAC,CAAC,CAAC;QACf,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,GAAG,CAAC,mCAAmC,EAAE,gBAAgB,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,OAAO,GACX,MAAM,qBAAqB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;YAE/D,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;YACrE,cAAc,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,EACD,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5B,MAAM,MAAM,GACV,MAAM,qBAAqB,CAAC,aAAa,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,MAAM,CAAC;IAChB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC5C,IAAI,CAAC;YACH,MAAM,qBAAqB,CAAC,aAAa,EAAE,CAAC;YAC5C,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,cAAc,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO;QACL,cAAc;QACd,aAAa;QACb,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,IAAI;KACL,CAAC;AACJ,CAAC","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 eventDataSize: number;\n totalSize: number;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => 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 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 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 try {\n if (debug) {\n console.log(`[useAudioRecorder] start recoding`, recordingOptions);\n }\n\n const fileUrl =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n\n return fileUrl;\n } catch (error) {\n console.error(\"[useAudioRecorder] Error starting recording:\", error);\n setIsRecording(false);\n }\n },\n [debug],\n );\n\n const stopRecording = useCallback(async () => {\n console.log(`STOOOOOP NOW`);\n const result: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n console.log(`STOOOOOP NOW 2`);\n setIsRecording(false);\n setIsPaused(false);\n return result;\n }, []);\n\n const pauseRecording = useCallback(async () => {\n try {\n await ExpoAudioStreamModule.stopRecording();\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 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"]}
|
|
@@ -76,9 +76,9 @@ class AudioStreamManager: NSObject {
|
|
|
76
76
|
internal var recordingSettings: RecordingSettings?
|
|
77
77
|
internal var recordingUUID: UUID?
|
|
78
78
|
internal var mimeType: String = "audio/wav"
|
|
79
|
-
private var lastBuffer: AVAudioPCMBuffer?
|
|
80
79
|
private var lastBufferTime: AVAudioTime?
|
|
81
|
-
|
|
80
|
+
private var accumulatedData = Data()
|
|
81
|
+
|
|
82
82
|
weak var delegate: AudioStreamManagerDelegate? // Define the delegate here
|
|
83
83
|
|
|
84
84
|
override init() {
|
|
@@ -208,6 +208,8 @@ class AudioStreamManager: NSObject {
|
|
|
208
208
|
|
|
209
209
|
emissionInterval = max(100.0, Double(intervalMilliseconds)) / 1000.0
|
|
210
210
|
lastEmissionTime = Date()
|
|
211
|
+
accumulatedData.removeAll()
|
|
212
|
+
totalDataSize = 0
|
|
211
213
|
|
|
212
214
|
let session = AVAudioSession.sharedInstance()
|
|
213
215
|
do {
|
|
@@ -240,9 +242,6 @@ class AudioStreamManager: NSObject {
|
|
|
240
242
|
|
|
241
243
|
// Processing the current buffer
|
|
242
244
|
self.processAudioBuffer(buffer, fileURL: self.recordingFileURL!)
|
|
243
|
-
|
|
244
|
-
// Store the buffer and time as the last buffer and time
|
|
245
|
-
self.lastBuffer = buffer
|
|
246
245
|
self.lastBufferTime = time
|
|
247
246
|
}
|
|
248
247
|
|
|
@@ -302,9 +301,12 @@ class AudioStreamManager: NSObject {
|
|
|
302
301
|
return nil
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
//
|
|
306
|
-
if
|
|
307
|
-
|
|
304
|
+
// Emit any remaining accumulated data
|
|
305
|
+
if !accumulatedData.isEmpty {
|
|
306
|
+
let currentTime = Date()
|
|
307
|
+
let recordingTime = currentTime.timeIntervalSince(startTime)
|
|
308
|
+
delegate?.audioStreamManager(self, didReceiveAudioData: accumulatedData, recordingTime: recordingTime, totalDataSize: totalDataSize)
|
|
309
|
+
accumulatedData.removeAll()
|
|
308
310
|
}
|
|
309
311
|
|
|
310
312
|
let endTime = Date()
|
|
@@ -329,7 +331,6 @@ class AudioStreamManager: NSObject {
|
|
|
329
331
|
sampleRate: settings.sampleRate
|
|
330
332
|
)
|
|
331
333
|
recordingFileURL = nil // Reset for next recording
|
|
332
|
-
lastBuffer = nil // Reset last buffer
|
|
333
334
|
lastBufferTime = nil // Reset last buffer time
|
|
334
335
|
|
|
335
336
|
return result
|
|
@@ -383,6 +384,9 @@ class AudioStreamManager: NSObject {
|
|
|
383
384
|
data.insert(contentsOf: header, at: 0)
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
// Accumulate new data
|
|
388
|
+
accumulatedData.append(data)
|
|
389
|
+
|
|
386
390
|
// print("Writing data size: \(data.count) bytes") // Debug: Check the size of data being written
|
|
387
391
|
fileHandle.seekToEndOfFile()
|
|
388
392
|
fileHandle.write(data)
|
|
@@ -396,9 +400,10 @@ class AudioStreamManager: NSObject {
|
|
|
396
400
|
if let startTime = startTime {
|
|
397
401
|
let recordingTime = currentTime.timeIntervalSince(startTime)
|
|
398
402
|
// print("Emitting data: Recording time \(recordingTime) seconds, Data size \(totalDataSize) bytes")
|
|
399
|
-
self.delegate?.audioStreamManager(self, didReceiveAudioData:
|
|
403
|
+
self.delegate?.audioStreamManager(self, didReceiveAudioData: accumulatedData, recordingTime: recordingTime, totalDataSize: totalDataSize)
|
|
400
404
|
self.lastEmissionTime = currentTime // Update last emission time
|
|
401
405
|
self.lastEmittedSize = totalDataSize
|
|
406
|
+
accumulatedData.removeAll() // Reset accumulated data after emission
|
|
402
407
|
}
|
|
403
408
|
}
|
|
404
409
|
}
|
|
@@ -31,9 +31,18 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
31
31
|
let interval = options["interval"] as? Int ?? 1000
|
|
32
32
|
|
|
33
33
|
let settings = RecordingSettings(sampleRate: sampleRate, numberOfChannels: numberOfChannels, bitDepth: bitDepth)
|
|
34
|
-
let result = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if let result = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval) {
|
|
35
|
+
let resultDict: [String: Any] = [
|
|
36
|
+
"fileUri": result.fileUri,
|
|
37
|
+
"channels": result.channels,
|
|
38
|
+
"bitDepth": result.bitDepth,
|
|
39
|
+
"sampleRate": result.sampleRate,
|
|
40
|
+
"mimeType": result.mimeType,
|
|
41
|
+
]
|
|
42
|
+
promise.resolve(resultDict)
|
|
43
|
+
} else {
|
|
44
|
+
promise.reject("ERROR", "Failed to start recording.")
|
|
45
|
+
}
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
|
|
@@ -97,9 +106,6 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
97
106
|
"streamUuid": manager.recordingUUID?.uuidString ?? UUID().uuidString
|
|
98
107
|
]
|
|
99
108
|
|
|
100
|
-
// Update the last emitted size for the next calculation
|
|
101
|
-
manager.lastEmittedSize += Int64(deltaSize)
|
|
102
|
-
|
|
103
109
|
// Emit the event to JavaScript
|
|
104
110
|
sendEvent(audioDataEvent, eventBody)
|
|
105
111
|
}
|
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,
|
|
@@ -13,6 +14,7 @@ import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
|
13
14
|
export interface AudioDataEvent {
|
|
14
15
|
data: string | Blob;
|
|
15
16
|
position: number;
|
|
17
|
+
fileUri: string;
|
|
16
18
|
eventDataSize: number;
|
|
17
19
|
totalSize: number;
|
|
18
20
|
}
|
|
@@ -20,12 +22,54 @@ export interface UseAudioRecorderState {
|
|
|
20
22
|
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
21
23
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
22
24
|
pauseRecording: () => void;
|
|
25
|
+
resumeRecording: () => void;
|
|
23
26
|
isRecording: boolean;
|
|
24
27
|
isPaused: boolean;
|
|
25
28
|
duration: number; // Duration of the recording
|
|
26
29
|
size: number; // Size in bytes of the recorded audio
|
|
27
30
|
}
|
|
28
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
|
+
|
|
29
73
|
export function useAudioRecorder({
|
|
30
74
|
onAudioStream,
|
|
31
75
|
debug = false,
|
|
@@ -33,205 +77,180 @@ export function useAudioRecorder({
|
|
|
33
77
|
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
34
78
|
debug?: boolean;
|
|
35
79
|
}): UseAudioRecorderState {
|
|
36
|
-
const [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
);
|
|
40
169
|
|
|
41
170
|
const checkStatus = useCallback(async () => {
|
|
42
171
|
try {
|
|
43
|
-
if (!isRecording) {
|
|
172
|
+
if (!state.isRecording) {
|
|
44
173
|
return;
|
|
45
174
|
}
|
|
46
175
|
|
|
47
176
|
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
48
177
|
if (debug) {
|
|
49
|
-
|
|
178
|
+
logDebug("[useAudioRecorder] Status:", status);
|
|
50
179
|
}
|
|
51
180
|
|
|
52
181
|
if (!status.isRecording) {
|
|
53
|
-
|
|
54
|
-
|
|
182
|
+
dispatch({ type: "STOP" });
|
|
183
|
+
} else {
|
|
184
|
+
dispatch({
|
|
185
|
+
type: "UPDATE_STATUS",
|
|
186
|
+
payload: { duration: status.duration, size: status.size },
|
|
187
|
+
});
|
|
55
188
|
}
|
|
56
|
-
// Extract matching file from filesystem
|
|
57
|
-
setDuration(status.duration);
|
|
58
|
-
setSize(status.size);
|
|
59
189
|
} catch (error) {
|
|
60
190
|
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
61
191
|
}
|
|
62
|
-
}, [isRecording]);
|
|
192
|
+
}, [state.isRecording, logDebug]);
|
|
63
193
|
|
|
64
194
|
useEffect(() => {
|
|
65
|
-
const interval = setInterval(checkStatus, 1000);
|
|
66
|
-
return () => clearInterval(interval);
|
|
67
|
-
}, [checkStatus]);
|
|
195
|
+
const interval = state.isRecording ? setInterval(checkStatus, 1000) : null;
|
|
196
|
+
return () => (interval ? clearInterval(interval) : undefined);
|
|
197
|
+
}, [checkStatus, state.isRecording]);
|
|
68
198
|
|
|
69
199
|
useEffect(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const subscribe = addAudioEventListener(
|
|
77
|
-
async ({
|
|
78
|
-
fileUri,
|
|
79
|
-
deltaSize,
|
|
80
|
-
totalSize,
|
|
81
|
-
lastEmittedSize,
|
|
82
|
-
position,
|
|
83
|
-
streamUuid,
|
|
84
|
-
encoded,
|
|
85
|
-
mimeType,
|
|
86
|
-
buffer,
|
|
87
|
-
}) => {
|
|
88
|
-
try {
|
|
89
|
-
if (debug) {
|
|
90
|
-
console.log(`[useAudioRecorder] Received audio event:`, {
|
|
91
|
-
fileUri,
|
|
92
|
-
deltaSize,
|
|
93
|
-
totalSize,
|
|
94
|
-
position,
|
|
95
|
-
mimeType,
|
|
96
|
-
lastEmittedSize,
|
|
97
|
-
streamUuid,
|
|
98
|
-
encodedLength: encoded?.length,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
if (deltaSize > 0) {
|
|
102
|
-
// Coming from native ( ios / android ) otherwise buffer is set
|
|
103
|
-
if (Platform.OS !== "web") {
|
|
104
|
-
// Read the audio file as a base64 string for comparison
|
|
105
|
-
try {
|
|
106
|
-
if (!encoded) {
|
|
107
|
-
console.error(
|
|
108
|
-
"[useAudioRecorder] Encoded audio data is missing",
|
|
109
|
-
);
|
|
110
|
-
throw new Error("Encoded audio data is missing");
|
|
111
|
-
}
|
|
112
|
-
// const binaryData = atob(encoded);
|
|
113
|
-
// const bytes = new Uint8Array(binaryData.length);
|
|
114
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
115
|
-
// bytes[i] = binaryData.charCodeAt(i) & 0xff; // Mask to 8 bits
|
|
116
|
-
// }
|
|
117
|
-
// const arrayBuffer = bytes.buffer;
|
|
118
|
-
|
|
119
|
-
// if (debug) {
|
|
120
|
-
// console.log(
|
|
121
|
-
// `[useAudioRecorder] Read audio file position=${position} deltaSize: ${deltaSize} vs encoded.length: ${encoded.length}`,
|
|
122
|
-
// );
|
|
123
|
-
// }
|
|
124
|
-
|
|
125
|
-
onAudioStream?.({
|
|
126
|
-
data: encoded,
|
|
127
|
-
position,
|
|
128
|
-
eventDataSize: deltaSize,
|
|
129
|
-
totalSize,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
133
|
-
// Fetch the audio data from the fileUri
|
|
134
|
-
// const options = {
|
|
135
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
136
|
-
// position: lastEmittedSize,
|
|
137
|
-
// length: deltaSize,
|
|
138
|
-
// };
|
|
139
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
140
|
-
// const binaryData = atob(base64Content);
|
|
141
|
-
// const content = new Uint8Array(binaryData.length);
|
|
142
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
143
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
144
|
-
// }
|
|
145
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
146
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error(
|
|
149
|
-
"[useAudioRecorder] Error reading audio file:",
|
|
150
|
-
error,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
} else if (buffer) {
|
|
154
|
-
// Coming from web
|
|
155
|
-
onAudioStream?.({
|
|
156
|
-
data: buffer,
|
|
157
|
-
position,
|
|
158
|
-
eventDataSize: deltaSize,
|
|
159
|
-
totalSize,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error(
|
|
165
|
-
"[useAudioRecorder] Error processing audio event:",
|
|
166
|
-
error,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
if (debug) {
|
|
172
|
-
console.log(
|
|
173
|
-
`[useAudioRecorder] Subscribed to audio event listener`,
|
|
174
|
-
subscribe,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
200
|
+
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
201
|
+
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
202
|
+
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
203
|
+
|
|
177
204
|
return () => {
|
|
178
|
-
|
|
179
|
-
console.log(`[useAudioRecorder] Removing audio event listener`);
|
|
180
|
-
}
|
|
205
|
+
logDebug(`${TAG} Removing audio event listener`);
|
|
181
206
|
subscribe.remove();
|
|
182
207
|
};
|
|
183
|
-
}, []);
|
|
208
|
+
}, [handleAudioEvent, logDebug]);
|
|
184
209
|
|
|
185
210
|
const startRecording = useCallback(
|
|
186
211
|
async (recordingOptions: RecordingConfig) => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
setSize(0);
|
|
190
|
-
setDuration(0);
|
|
191
|
-
try {
|
|
192
|
-
if (debug) {
|
|
193
|
-
console.log(`[useAudioRecorder] start recoding`, recordingOptions);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const fileUrl =
|
|
197
|
-
await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
198
|
-
|
|
199
|
-
return fileUrl;
|
|
200
|
-
} catch (error) {
|
|
201
|
-
console.error("[useAudioRecorder] Error starting recording:", error);
|
|
202
|
-
setIsRecording(false);
|
|
212
|
+
if (debug) {
|
|
213
|
+
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
203
214
|
}
|
|
215
|
+
const startResult: StartAudioStreamResult =
|
|
216
|
+
await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
217
|
+
dispatch({ type: "START" });
|
|
218
|
+
|
|
219
|
+
return startResult;
|
|
204
220
|
},
|
|
205
|
-
[
|
|
221
|
+
[logDebug],
|
|
206
222
|
);
|
|
207
223
|
|
|
208
224
|
const stopRecording = useCallback(async () => {
|
|
209
|
-
|
|
210
|
-
const
|
|
225
|
+
logDebug(`${TAG} stop recording`);
|
|
226
|
+
const stopResult: AudioStreamResult =
|
|
211
227
|
await ExpoAudioStreamModule.stopRecording();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return result;
|
|
216
|
-
}, []);
|
|
228
|
+
dispatch({ type: "STOP" });
|
|
229
|
+
return stopResult;
|
|
230
|
+
}, [logDebug]);
|
|
217
231
|
|
|
218
232
|
const pauseRecording = useCallback(async () => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
logDebug(`${TAG} pause recording`);
|
|
234
|
+
const pauseResult = await ExpoAudioStreamModule.pauseRecording();
|
|
235
|
+
dispatch({ type: "PAUSE" });
|
|
236
|
+
return pauseResult;
|
|
237
|
+
}, [logDebug]);
|
|
238
|
+
|
|
239
|
+
const resumeRecording = useCallback(async () => {
|
|
240
|
+
logDebug(`${TAG} resume recording`);
|
|
241
|
+
const resumeResult = await ExpoAudioStreamModule.resumeRecording();
|
|
242
|
+
dispatch({ type: "RESUME" });
|
|
243
|
+
return resumeResult;
|
|
244
|
+
}, [logDebug]);
|
|
227
245
|
|
|
228
246
|
return {
|
|
229
247
|
startRecording,
|
|
230
248
|
stopRecording,
|
|
231
249
|
pauseRecording,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
250
|
+
resumeRecording,
|
|
251
|
+
isPaused: state.isPaused,
|
|
252
|
+
isRecording: state.isRecording,
|
|
253
|
+
duration: state.duration,
|
|
254
|
+
size: state.size,
|
|
236
255
|
};
|
|
237
256
|
}
|