@siteed/expo-audio-stream 0.7.0 → 0.7.1

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