@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.
@@ -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,EACL,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,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,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,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,CA0MxB"}
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, useState } from "react";
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 [isRecording, setIsRecording] = useState(false);
7
- const [isPaused, setIsPaused] = useState(false);
8
- const [duration, setDuration] = useState(0);
9
- const [size, setSize] = useState(0);
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
- console.log(`[useAudioRecorder] Status:`, status);
114
+ logDebug("[useAudioRecorder] Status:", status);
18
115
  }
19
116
  if (!status.isRecording) {
20
- // Don't update if recording stopped.
21
- return;
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
- if (debug) {
37
- console.log(`[useAudioRecorder] Registering audio event listener`, onAudioStream);
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
- if (debug) {
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
- setIsRecording(true);
126
- setIsPaused(false);
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
- }, [debug]);
147
+ const startResult = await ExpoAudioStreamModule.startRecording(recordingOptions);
148
+ dispatch({ type: "START" });
149
+ return startResult;
150
+ }, [logDebug]);
141
151
  const stopRecording = useCallback(async () => {
142
- console.log(`STOOOOOP NOW`);
143
- const result = await ExpoAudioStreamModule.stopRecording();
144
- console.log(`STOOOOOP NOW 2`);
145
- setIsRecording(false);
146
- setIsPaused(false);
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
- try {
151
- await ExpoAudioStreamModule.stopRecording();
152
- setIsPaused(true);
153
- setIsRecording(false);
154
- }
155
- catch (error) {
156
- console.error("[useAudioRecorder] Error pausing recording:", error);
157
- }
158
- }, [debug]);
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
- isPaused,
164
- isRecording,
165
- duration,
166
- size,
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":"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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 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
- // Ensure all remaining audio data is sent
306
- if let lastBuffer = lastBuffer, let lastBufferTime = lastBufferTime {
307
- self.processAudioBuffer(lastBuffer, fileURL: fileURL)
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: data, recordingTime: recordingTime, totalDataSize: totalDataSize)
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
- promise.resolve(result)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -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 [isRecording, setIsRecording] = useState(false);
37
- const [isPaused, setIsPaused] = useState(false);
38
- const [duration, setDuration] = useState(0);
39
- const [size, setSize] = useState(0);
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
- console.log(`[useAudioRecorder] Status:`, status);
178
+ logDebug("[useAudioRecorder] Status:", status);
50
179
  }
51
180
 
52
181
  if (!status.isRecording) {
53
- // Don't update if recording stopped.
54
- return;
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
- if (debug) {
71
- console.log(
72
- `[useAudioRecorder] Registering audio event listener`,
73
- onAudioStream,
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
- if (debug) {
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
- setIsRecording(true);
188
- setIsPaused(false);
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
- [debug],
221
+ [logDebug],
206
222
  );
207
223
 
208
224
  const stopRecording = useCallback(async () => {
209
- console.log(`STOOOOOP NOW`);
210
- const result: AudioStreamResult =
225
+ logDebug(`${TAG} stop recording`);
226
+ const stopResult: AudioStreamResult =
211
227
  await ExpoAudioStreamModule.stopRecording();
212
- console.log(`STOOOOOP NOW 2`);
213
- setIsRecording(false);
214
- setIsPaused(false);
215
- return result;
216
- }, []);
228
+ dispatch({ type: "STOP" });
229
+ return stopResult;
230
+ }, [logDebug]);
217
231
 
218
232
  const pauseRecording = useCallback(async () => {
219
- try {
220
- await ExpoAudioStreamModule.stopRecording();
221
- setIsPaused(true);
222
- setIsRecording(false);
223
- } catch (error) {
224
- console.error("[useAudioRecorder] Error pausing recording:", error);
225
- }
226
- }, [debug]);
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
- isPaused,
233
- isRecording,
234
- duration,
235
- size,
250
+ resumeRecording,
251
+ isPaused: state.isPaused,
252
+ isRecording: state.isRecording,
253
+ duration: state.duration,
254
+ size: state.size,
236
255
  };
237
256
  }