@siteed/expo-audio-stream 0.5.1 → 0.6.0
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/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +9 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +62 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +4 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +445 -0
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +12 -0
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +7 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +43 -392
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +16 -0
- package/build/ExpoAudioStream.types.d.ts +12 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStreamModule.web.d.ts +2 -2
- package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.web.js +5 -1
- package/build/ExpoAudioStreamModule.web.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/useAudioRecording.d.ts +5 -4
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +38 -27
- package/build/useAudioRecording.js.map +1 -1
- package/ios/AudioStreamManager.swift +46 -16
- package/ios/ExpoAudioStreamModule.swift +2 -2
- package/package.json +1 -1
- package/src/ExpoAudioStream.types.ts +14 -5
- package/src/ExpoAudioStreamModule.web.ts +8 -3
- package/src/index.ts +4 -0
- package/src/useAudioRecording.ts +48 -34
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { AudioStreamResult,
|
|
1
|
+
import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
|
|
2
2
|
export interface AudioDataEvent {
|
|
3
|
-
data: string |
|
|
3
|
+
data: string | Blob;
|
|
4
4
|
position: number;
|
|
5
|
-
|
|
5
|
+
eventDataSize: number;
|
|
6
|
+
totalSize: number;
|
|
6
7
|
}
|
|
7
8
|
export interface UseAudioRecorderState {
|
|
8
|
-
startRecording: (_:
|
|
9
|
+
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
9
10
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
10
11
|
pauseRecording: () => void;
|
|
11
12
|
isRecording: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,iBAAiB,EAEjB,
|
|
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"}
|
|
@@ -7,31 +7,31 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
7
7
|
const [isPaused, setIsPaused] = useState(false);
|
|
8
8
|
const [duration, setDuration] = useState(0);
|
|
9
9
|
const [size, setSize] = useState(0);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const checkStatus = useCallback(async () => {
|
|
11
|
+
try {
|
|
12
|
+
if (!isRecording) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const status = ExpoAudioStreamModule.status();
|
|
16
|
+
if (debug) {
|
|
17
|
+
console.log(`[useAudioRecorder] Status:`, status);
|
|
18
|
+
}
|
|
19
|
+
if (!status.isRecording) {
|
|
20
|
+
// Don't update if recording stopped.
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Extract matching file from filesystem
|
|
24
|
+
setDuration(status.duration);
|
|
25
|
+
setSize(status.size);
|
|
13
26
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
if (debug)
|
|
18
|
-
console.log(`[useAudioRecorder] Getting status`);
|
|
19
|
-
const status = ExpoAudioStreamModule.status();
|
|
20
|
-
if (debug) {
|
|
21
|
-
console.log(`[useAudioRecorder] Status:`, status);
|
|
22
|
-
}
|
|
23
|
-
// Extract matching file from filesystem
|
|
24
|
-
setDuration(status.duration);
|
|
25
|
-
setSize(status.size);
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
29
|
-
}
|
|
30
|
-
}, 1000);
|
|
31
|
-
return () => clearInterval(interval);
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
}, [isRecording]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const interval = setInterval(checkStatus, 1000);
|
|
33
|
+
return () => clearInterval(interval);
|
|
34
|
+
}, [checkStatus]);
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
if (debug) {
|
|
37
37
|
console.log(`[useAudioRecorder] Registering audio event listener`, onAudioStream);
|
|
@@ -70,7 +70,12 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
70
70
|
// `[useAudioRecorder] Read audio file position=${position} deltaSize: ${deltaSize} vs encoded.length: ${encoded.length}`,
|
|
71
71
|
// );
|
|
72
72
|
// }
|
|
73
|
-
onAudioStream?.({
|
|
73
|
+
onAudioStream?.({
|
|
74
|
+
data: encoded,
|
|
75
|
+
position,
|
|
76
|
+
eventDataSize: deltaSize,
|
|
77
|
+
totalSize,
|
|
78
|
+
});
|
|
74
79
|
// Below code is optional, used to compare encoded data to audio on file system
|
|
75
80
|
// Fetch the audio data from the fileUri
|
|
76
81
|
// const options = {
|
|
@@ -92,9 +97,13 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
else if (buffer) {
|
|
95
|
-
const data = await buffer.arrayBuffer();
|
|
96
100
|
// Coming from web
|
|
97
|
-
onAudioStream?.({
|
|
101
|
+
onAudioStream?.({
|
|
102
|
+
data: buffer,
|
|
103
|
+
position,
|
|
104
|
+
eventDataSize: deltaSize,
|
|
105
|
+
totalSize,
|
|
106
|
+
});
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
}
|
|
@@ -130,9 +139,11 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
130
139
|
}
|
|
131
140
|
}, [debug]);
|
|
132
141
|
const stopRecording = useCallback(async () => {
|
|
142
|
+
console.log(`STOOOOOP NOW`);
|
|
143
|
+
const result = await ExpoAudioStreamModule.stopRecording();
|
|
144
|
+
console.log(`STOOOOOP NOW 2`);
|
|
133
145
|
setIsRecording(false);
|
|
134
146
|
setIsPaused(false);
|
|
135
|
-
const result = await ExpoAudioStreamModule.stopRecording();
|
|
136
147
|
return result;
|
|
137
148
|
}, []);
|
|
138
149
|
const pauseRecording = useCallback(async () => {
|
|
@@ -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 RecordingOptions,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | ArrayBuffer;\n position: number;\n size: number;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingOptions) => Promise<string | null>;\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 useEffect(() => {\n if (debug) {\n console.log(\n `[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`,\n );\n }\n if (isRecording || isPaused) {\n const interval = setInterval(() => {\n try {\n if (debug) console.log(`[useAudioRecorder] Getting status`);\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n console.log(`[useAudioRecorder] Status:`, status);\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 }, 1000);\n return () => clearInterval(interval);\n }\n\n return () => null;\n }, [isRecording, isPaused, debug]);\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?.({ data: encoded, position, size: deltaSize });\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 const data = await buffer.arrayBuffer();\n // Coming from web\n onAudioStream?.({ data, position, size: deltaSize });\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: RecordingOptions) => {\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 setIsRecording(false);\n setIsPaused(false);\n const result: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\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, 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"]}
|
|
@@ -40,6 +40,14 @@ struct RecordingResult {
|
|
|
40
40
|
var sampleRate: Double
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
struct StartRecordingResult {
|
|
44
|
+
var fileUri: String
|
|
45
|
+
var mimeType: String
|
|
46
|
+
var channels: Int
|
|
47
|
+
var bitDepth: Int
|
|
48
|
+
var sampleRate: Double
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
protocol AudioStreamManagerDelegate: AnyObject {
|
|
44
52
|
func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64)
|
|
45
53
|
}
|
|
@@ -68,6 +76,9 @@ class AudioStreamManager: NSObject {
|
|
|
68
76
|
internal var recordingSettings: RecordingSettings?
|
|
69
77
|
internal var recordingUUID: UUID?
|
|
70
78
|
internal var mimeType: String = "audio/wav"
|
|
79
|
+
private var lastBuffer: AVAudioPCMBuffer?
|
|
80
|
+
private var lastBufferTime: AVAudioTime?
|
|
81
|
+
|
|
71
82
|
weak var delegate: AudioStreamManagerDelegate? // Define the delegate here
|
|
72
83
|
|
|
73
84
|
override init() {
|
|
@@ -102,16 +113,9 @@ class AudioStreamManager: NSObject {
|
|
|
102
113
|
let fileName = "\(recordingUUID!.uuidString).wav"
|
|
103
114
|
let fileURL = documentsDirectory.appendingPathComponent(fileName)
|
|
104
115
|
|
|
105
|
-
if fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let wavHeader = createWavHeader(dataSize: 0) // Initially set data size to 0
|
|
109
|
-
fileHandle.write(wavHeader)
|
|
110
|
-
fileHandle.closeFile()
|
|
111
|
-
} catch {
|
|
112
|
-
Logger.debug("Failed to write WAV header: \(error.localizedDescription)")
|
|
113
|
-
return nil
|
|
114
|
-
}
|
|
116
|
+
if !fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) {
|
|
117
|
+
Logger.debug("Failed to create file at: \(fileURL.path)")
|
|
118
|
+
return nil
|
|
115
119
|
}
|
|
116
120
|
return fileURL
|
|
117
121
|
}
|
|
@@ -123,8 +127,8 @@ class AudioStreamManager: NSObject {
|
|
|
123
127
|
let channels = UInt32(recordingSettings!.numberOfChannels)
|
|
124
128
|
let bitDepth = UInt32(recordingSettings!.bitDepth)
|
|
125
129
|
|
|
126
|
-
let byteRate = sampleRate * channels * (bitDepth / 8)
|
|
127
130
|
let blockAlign = channels * (bitDepth / 8)
|
|
131
|
+
let byteRate = sampleRate * blockAlign
|
|
128
132
|
|
|
129
133
|
// "RIFF" chunk descriptor
|
|
130
134
|
header.append(contentsOf: "RIFF".utf8)
|
|
@@ -138,7 +142,7 @@ class AudioStreamManager: NSObject {
|
|
|
138
142
|
header.append(contentsOf: UInt16(channels).littleEndianBytes)
|
|
139
143
|
header.append(contentsOf: sampleRate.littleEndianBytes)
|
|
140
144
|
header.append(contentsOf: byteRate.littleEndianBytes) // byteRate
|
|
141
|
-
header.append(contentsOf: UInt16(
|
|
145
|
+
header.append(contentsOf: UInt16(blockAlign).littleEndianBytes) // blockAlign
|
|
142
146
|
header.append(contentsOf: UInt16(bitDepth).littleEndianBytes) // bits per sample
|
|
143
147
|
|
|
144
148
|
// "data" sub-chunk
|
|
@@ -176,7 +180,7 @@ class AudioStreamManager: NSObject {
|
|
|
176
180
|
|
|
177
181
|
}
|
|
178
182
|
|
|
179
|
-
func startRecording(settings: RecordingSettings, intervalMilliseconds: Int)
|
|
183
|
+
func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> StartRecordingResult? {
|
|
180
184
|
guard !isRecording else {
|
|
181
185
|
Logger.debug("Debug: Recording is already in progress.")
|
|
182
186
|
return nil
|
|
@@ -234,7 +238,12 @@ class AudioStreamManager: NSObject {
|
|
|
234
238
|
let formatDescription = describeAudioFormat(buffer.format)
|
|
235
239
|
Logger.debug("Debug: Buffer format - \(formatDescription)")
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
// Processing the current buffer
|
|
242
|
+
self.processAudioBuffer(buffer, fileURL: self.recordingFileURL!)
|
|
243
|
+
|
|
244
|
+
// Store the buffer and time as the last buffer and time
|
|
245
|
+
self.lastBuffer = buffer
|
|
246
|
+
self.lastBufferTime = time
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
recordingFileURL = createRecordingFile()
|
|
@@ -248,7 +257,13 @@ class AudioStreamManager: NSObject {
|
|
|
248
257
|
try audioEngine.start()
|
|
249
258
|
isRecording = true
|
|
250
259
|
Logger.debug("Debug: Recording started successfully.")
|
|
251
|
-
return
|
|
260
|
+
return StartRecordingResult(
|
|
261
|
+
fileUri: recordingFileURL!.path,
|
|
262
|
+
mimeType: mimeType,
|
|
263
|
+
channels: settings.numberOfChannels,
|
|
264
|
+
bitDepth: settings.bitDepth,
|
|
265
|
+
sampleRate: settings.sampleRate
|
|
266
|
+
)
|
|
252
267
|
} catch {
|
|
253
268
|
Logger.debug("Error: Could not start the audio engine: \(error.localizedDescription)")
|
|
254
269
|
isRecording = false
|
|
@@ -287,6 +302,11 @@ class AudioStreamManager: NSObject {
|
|
|
287
302
|
return nil
|
|
288
303
|
}
|
|
289
304
|
|
|
305
|
+
// Ensure all remaining audio data is sent
|
|
306
|
+
if let lastBuffer = lastBuffer, let lastBufferTime = lastBufferTime {
|
|
307
|
+
self.processAudioBuffer(lastBuffer, fileURL: fileURL)
|
|
308
|
+
}
|
|
309
|
+
|
|
290
310
|
let endTime = Date()
|
|
291
311
|
let duration = Int64(endTime.timeIntervalSince(startTime) * 1000) - Int64(pausedDuration * 1000)
|
|
292
312
|
|
|
@@ -309,6 +329,9 @@ class AudioStreamManager: NSObject {
|
|
|
309
329
|
sampleRate: settings.sampleRate
|
|
310
330
|
)
|
|
311
331
|
recordingFileURL = nil // Reset for next recording
|
|
332
|
+
lastBuffer = nil // Reset last buffer
|
|
333
|
+
lastBufferTime = nil // Reset last buffer time
|
|
334
|
+
|
|
312
335
|
return result
|
|
313
336
|
} catch {
|
|
314
337
|
print("Failed to fetch file attributes: \(error)")
|
|
@@ -351,7 +374,14 @@ class AudioStreamManager: NSObject {
|
|
|
351
374
|
print("Buffer data is nil.")
|
|
352
375
|
return
|
|
353
376
|
}
|
|
354
|
-
|
|
377
|
+
var data = Data(bytes: bufferData, count: Int(audioData.mDataByteSize))
|
|
378
|
+
|
|
379
|
+
// Check if this is the first buffer to process and totalDataSize is 0
|
|
380
|
+
if totalDataSize == 0 {
|
|
381
|
+
// Since it's the first buffer, prepend the WAV header
|
|
382
|
+
let header = createWavHeader(dataSize: 0) // Set initial dataSize to 0, update later
|
|
383
|
+
data.insert(contentsOf: header, at: 0)
|
|
384
|
+
}
|
|
355
385
|
|
|
356
386
|
// print("Writing data size: \(data.count) bytes") // Debug: Check the size of data being written
|
|
357
387
|
fileHandle.seekToEndOfFile()
|
|
@@ -31,9 +31,9 @@ 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
|
|
34
|
+
let result = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
|
|
35
35
|
|
|
36
|
-
promise.resolve(
|
|
36
|
+
promise.resolve(result)
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -20,6 +20,14 @@ export interface AudioStreamResult {
|
|
|
20
20
|
sampleRate?: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export interface StartAudioStreamResult {
|
|
24
|
+
fileUri: string;
|
|
25
|
+
mimeType: string;
|
|
26
|
+
channels?: number;
|
|
27
|
+
bitDepth?: number;
|
|
28
|
+
sampleRate?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
export interface AudioStreamStatus {
|
|
24
32
|
isRecording: boolean;
|
|
25
33
|
isPaused: boolean;
|
|
@@ -29,10 +37,11 @@ export interface AudioStreamStatus {
|
|
|
29
37
|
mimeType: string;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
export type EncodingType = "pcm_16bit" | "pcm_8bit";
|
|
41
|
+
|
|
42
|
+
export interface RecordingConfig {
|
|
43
|
+
sampleRate?: 16000 | 44100 | 48000;
|
|
44
|
+
channels?: 1 | 2; // 1 or 2 MONO or STEREO
|
|
45
|
+
encoding?: EncodingType;
|
|
37
46
|
interval?: number;
|
|
38
47
|
}
|
|
@@ -4,7 +4,8 @@ import { EventEmitter } from "expo-modules-core";
|
|
|
4
4
|
import {
|
|
5
5
|
AudioEventPayload,
|
|
6
6
|
AudioStreamResult,
|
|
7
|
-
|
|
7
|
+
RecordingConfig,
|
|
8
|
+
StartAudioStreamResult,
|
|
8
9
|
} from "./ExpoAudioStream.types";
|
|
9
10
|
|
|
10
11
|
const log = debug("expo-audio-stream:useAudioRecording");
|
|
@@ -58,7 +59,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Start recording with options
|
|
61
|
-
async startRecording(options:
|
|
62
|
+
async startRecording(options: RecordingConfig = {}) {
|
|
62
63
|
if (this.isRecording) {
|
|
63
64
|
throw new Error("Recording is already in progress");
|
|
64
65
|
}
|
|
@@ -74,7 +75,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
74
75
|
this.lastEmittedTime = 0;
|
|
75
76
|
this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
|
|
76
77
|
const fileUri = `${this.streamUuid}.webm`;
|
|
77
|
-
|
|
78
|
+
const streamConfig: StartAudioStreamResult = {
|
|
79
|
+
fileUri,
|
|
80
|
+
mimeType: "audio/webm",
|
|
81
|
+
};
|
|
82
|
+
return streamConfig;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// Setup listeners for the MediaRecorder
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,10 @@ export function clearAudioFiles(): Promise<void> {
|
|
|
26
26
|
return ExpoAudioStreamModule.clearAudioFiles();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function test(): void {
|
|
30
|
+
return ExpoAudioStreamModule.test();
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
export function addAudioEventListener(
|
|
30
34
|
listener: (event: AudioEventPayload) => Promise<void>,
|
|
31
35
|
): Subscription {
|
package/src/useAudioRecording.ts
CHANGED
|
@@ -5,17 +5,19 @@ import { addAudioEventListener } from ".";
|
|
|
5
5
|
import {
|
|
6
6
|
AudioStreamResult,
|
|
7
7
|
AudioStreamStatus,
|
|
8
|
-
|
|
8
|
+
RecordingConfig,
|
|
9
|
+
StartAudioStreamResult,
|
|
9
10
|
} from "./ExpoAudioStream.types";
|
|
10
11
|
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
11
12
|
|
|
12
13
|
export interface AudioDataEvent {
|
|
13
|
-
data: string |
|
|
14
|
+
data: string | Blob;
|
|
14
15
|
position: number;
|
|
15
|
-
|
|
16
|
+
eventDataSize: number;
|
|
17
|
+
totalSize: number;
|
|
16
18
|
}
|
|
17
19
|
export interface UseAudioRecorderState {
|
|
18
|
-
startRecording: (_:
|
|
20
|
+
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
19
21
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
20
22
|
pauseRecording: () => void;
|
|
21
23
|
isRecording: boolean;
|
|
@@ -36,32 +38,33 @@ export function useAudioRecorder({
|
|
|
36
38
|
const [duration, setDuration] = useState(0);
|
|
37
39
|
const [size, setSize] = useState(0);
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}, 1000);
|
|
60
|
-
return () => clearInterval(interval);
|
|
41
|
+
const checkStatus = useCallback(async () => {
|
|
42
|
+
try {
|
|
43
|
+
if (!isRecording) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
48
|
+
if (debug) {
|
|
49
|
+
console.log(`[useAudioRecorder] Status:`, status);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!status.isRecording) {
|
|
53
|
+
// Don't update if recording stopped.
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Extract matching file from filesystem
|
|
57
|
+
setDuration(status.duration);
|
|
58
|
+
setSize(status.size);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`[useAudioRecorder] Error getting status:`, error);
|
|
61
61
|
}
|
|
62
|
+
}, [isRecording]);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const interval = setInterval(checkStatus, 1000);
|
|
66
|
+
return () => clearInterval(interval);
|
|
67
|
+
}, [checkStatus]);
|
|
65
68
|
|
|
66
69
|
useEffect(() => {
|
|
67
70
|
if (debug) {
|
|
@@ -119,7 +122,12 @@ export function useAudioRecorder({
|
|
|
119
122
|
// );
|
|
120
123
|
// }
|
|
121
124
|
|
|
122
|
-
onAudioStream?.({
|
|
125
|
+
onAudioStream?.({
|
|
126
|
+
data: encoded,
|
|
127
|
+
position,
|
|
128
|
+
eventDataSize: deltaSize,
|
|
129
|
+
totalSize,
|
|
130
|
+
});
|
|
123
131
|
|
|
124
132
|
// Below code is optional, used to compare encoded data to audio on file system
|
|
125
133
|
// Fetch the audio data from the fileUri
|
|
@@ -143,9 +151,13 @@ export function useAudioRecorder({
|
|
|
143
151
|
);
|
|
144
152
|
}
|
|
145
153
|
} else if (buffer) {
|
|
146
|
-
const data = await buffer.arrayBuffer();
|
|
147
154
|
// Coming from web
|
|
148
|
-
onAudioStream?.({
|
|
155
|
+
onAudioStream?.({
|
|
156
|
+
data: buffer,
|
|
157
|
+
position,
|
|
158
|
+
eventDataSize: deltaSize,
|
|
159
|
+
totalSize,
|
|
160
|
+
});
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
163
|
} catch (error) {
|
|
@@ -171,7 +183,7 @@ export function useAudioRecorder({
|
|
|
171
183
|
}, []);
|
|
172
184
|
|
|
173
185
|
const startRecording = useCallback(
|
|
174
|
-
async (recordingOptions:
|
|
186
|
+
async (recordingOptions: RecordingConfig) => {
|
|
175
187
|
setIsRecording(true);
|
|
176
188
|
setIsPaused(false);
|
|
177
189
|
setSize(0);
|
|
@@ -194,10 +206,12 @@ export function useAudioRecorder({
|
|
|
194
206
|
);
|
|
195
207
|
|
|
196
208
|
const stopRecording = useCallback(async () => {
|
|
197
|
-
|
|
198
|
-
setIsPaused(false);
|
|
209
|
+
console.log(`STOOOOOP NOW`);
|
|
199
210
|
const result: AudioStreamResult =
|
|
200
211
|
await ExpoAudioStreamModule.stopRecording();
|
|
212
|
+
console.log(`STOOOOOP NOW 2`);
|
|
213
|
+
setIsRecording(false);
|
|
214
|
+
setIsPaused(false);
|
|
201
215
|
return result;
|
|
202
216
|
}, []);
|
|
203
217
|
|