@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.
Files changed (30) hide show
  1. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +9 -0
  2. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +62 -0
  3. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +4 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +445 -0
  5. package/android/src/main/java/net/siteed/audiostream/Constants.kt +12 -0
  6. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +7 -0
  7. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +43 -392
  8. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +16 -0
  9. package/build/ExpoAudioStream.types.d.ts +12 -1
  10. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  11. package/build/ExpoAudioStream.types.js.map +1 -1
  12. package/build/ExpoAudioStreamModule.web.d.ts +2 -2
  13. package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
  14. package/build/ExpoAudioStreamModule.web.js +5 -1
  15. package/build/ExpoAudioStreamModule.web.js.map +1 -1
  16. package/build/index.d.ts +1 -0
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +3 -0
  19. package/build/index.js.map +1 -1
  20. package/build/useAudioRecording.d.ts +5 -4
  21. package/build/useAudioRecording.d.ts.map +1 -1
  22. package/build/useAudioRecording.js +38 -27
  23. package/build/useAudioRecording.js.map +1 -1
  24. package/ios/AudioStreamManager.swift +46 -16
  25. package/ios/ExpoAudioStreamModule.swift +2 -2
  26. package/package.json +1 -1
  27. package/src/ExpoAudioStream.types.ts +14 -5
  28. package/src/ExpoAudioStreamModule.web.ts +8 -3
  29. package/src/index.ts +4 -0
  30. package/src/useAudioRecording.ts +48 -34
@@ -1,11 +1,12 @@
1
- import { AudioStreamResult, RecordingOptions } from "./ExpoAudioStream.types";
1
+ import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
2
2
  export interface AudioDataEvent {
3
- data: string | ArrayBuffer;
3
+ data: string | Blob;
4
4
  position: number;
5
- size: number;
5
+ eventDataSize: number;
6
+ totalSize: number;
6
7
  }
7
8
  export interface UseAudioRecorderState {
8
- startRecording: (_: RecordingOptions) => Promise<string | null>;
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,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,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,CA8LxB"}
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
- useEffect(() => {
11
- if (debug) {
12
- console.log(`[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`);
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
- if (isRecording || isPaused) {
15
- const interval = setInterval(() => {
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
- return () => null;
34
- }, [isRecording, isPaused, debug]);
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?.({ data: encoded, position, size: deltaSize });
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?.({ data, position, size: deltaSize });
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
- do {
107
- let fileHandle = try FileHandle(forWritingTo: fileURL)
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(channels * (bitDepth / 8)).littleEndianBytes) // blockAlign
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) -> String? {
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
- self.processAudioBuffer(buffer, fileURL: fileURL)
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 recordingFileURL?.path
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
- let data = Data(bytes: bufferData, count: Int(audioData.mDataByteSize))
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 url = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
34
+ let result = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
35
35
 
36
- promise.resolve(url)
36
+ promise.resolve(result)
37
37
  }
38
38
  }
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -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 interface RecordingOptions {
33
- // TODO align Android and IOS options
34
- // sampleRate?: number;
35
- // channelConfig?: number; // numberOfChannel
36
- // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)
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
- RecordingOptions,
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: RecordingOptions = {}) {
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
- return fileUri;
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 {
@@ -5,17 +5,19 @@ import { addAudioEventListener } from ".";
5
5
  import {
6
6
  AudioStreamResult,
7
7
  AudioStreamStatus,
8
- RecordingOptions,
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 | ArrayBuffer;
14
+ data: string | Blob;
14
15
  position: number;
15
- size: number;
16
+ eventDataSize: number;
17
+ totalSize: number;
16
18
  }
17
19
  export interface UseAudioRecorderState {
18
- startRecording: (_: RecordingOptions) => Promise<string | null>;
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
- useEffect(() => {
40
- if (debug) {
41
- console.log(
42
- `[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`,
43
- );
44
- }
45
- if (isRecording || isPaused) {
46
- const interval = setInterval(() => {
47
- try {
48
- if (debug) console.log(`[useAudioRecorder] Getting status`);
49
- const status: AudioStreamStatus = ExpoAudioStreamModule.status();
50
- if (debug) {
51
- console.log(`[useAudioRecorder] Status:`, status);
52
- }
53
- // Extract matching file from filesystem
54
- setDuration(status.duration);
55
- setSize(status.size);
56
- } catch (error) {
57
- console.error(`[useAudioRecorder] Error getting status:`, error);
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
- return () => null;
64
- }, [isRecording, isPaused, debug]);
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?.({ data: encoded, position, size: deltaSize });
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?.({ data, position, size: deltaSize });
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: 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
- setIsRecording(false);
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