@siteed/expo-audio-stream 0.3.3 → 0.4.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/README.md CHANGED
@@ -53,6 +53,7 @@ import {
53
53
 
54
54
  export default function App() {
55
55
  const { startRecording, stopRecording, duration, size, isRecording } = useAudioRecorder({
56
+ debug: true,
56
57
  onAudioStream: (audioData: Blob) => {
57
58
  console.log(`audio event`,audioData);
58
59
  }
@@ -111,15 +112,6 @@ import { addAudioEventListener } from '@siteed/expo-audio-stream';
111
112
 
112
113
  If you want to process the audio livestream directly, I recommend having another encoding step to align the audio format across platforms.
113
114
 
114
-
115
- ### Debug Configuration
116
-
117
- This library uses the npm `debug` package, to enable logging you can:
118
- ```
119
- localStorage.debug = 'expo-audio-stream:*'
120
- ```
121
- or set the DEBUG environment variable to `expo-audio-stream:*`
122
-
123
115
  ### TODO
124
116
  this package is still in development, and there are a few things that need to be done:
125
117
  - Add resume (vs currently use start) support and implement pause on iOS.
@@ -51,6 +51,8 @@ class ExpoAudioStreamModule() : Module() {
51
51
  private var lastEmittedSize = 0L
52
52
  private var mimeType = "audio/wav"
53
53
  private val mainHandler = Handler(Looper.getMainLooper())
54
+ private var bitDepth = 16
55
+ private var channels = 1
54
56
 
55
57
  @SuppressLint("MissingPermission")
56
58
  override fun definition() = ModuleDefinition {
@@ -71,11 +73,15 @@ class ExpoAudioStreamModule() : Module() {
71
73
  }
72
74
 
73
75
  Function("status") {
74
- val currentTime = System.currentTimeMillis()
75
- totalRecordedTime = (currentTime - recordingStartTime - pausedDuration) // Adjust the total recording time
76
+ // Ensure you update this to check if audioFile is null or not
77
+ val fileSize = audioFile?.length() ?: 0
78
+ val dataFileSize = fileSize - 44 // Assuming header is always 44 bytes
79
+
80
+ val byteRate = sampleRateInHz * channels * (bitDepth / 8)
81
+ val duration = if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0 // Duration in milliseconds
76
82
 
77
83
  bundleOf(
78
- "duration" to totalRecordedTime,
84
+ "duration" to duration,
79
85
  "isRecording" to isRecording.get(),
80
86
  "isPaused" to isPaused.get(),
81
87
  "mime" to mimeType,
@@ -105,12 +111,9 @@ class ExpoAudioStreamModule() : Module() {
105
111
  }
106
112
 
107
113
  // Method to write WAV file header
108
- private fun writeWavHeader(out: FileOutputStream, channelConfig: Int, sampleRate: Int, audioFormat: Int) {
109
- val channels = if (channelConfig == AudioFormat.CHANNEL_IN_MONO) 1 else 2
110
- val bitDepth = if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) 16 else 8
111
-
114
+ private fun writeWavHeader(out: FileOutputStream) {
112
115
  val header = ByteArray(44)
113
- val byteRate = sampleRate * channels * bitDepth / 8
116
+ val byteRate = sampleRateInHz * channels * bitDepth / 8
114
117
  val blockAlign = channels * bitDepth / 8
115
118
 
116
119
  // RIFF/WAVE header
@@ -121,12 +124,12 @@ class ExpoAudioStreamModule() : Module() {
121
124
 
122
125
  // 16 for PCM
123
126
  header[16] = 16
124
- header[20] = 1 // Audio format 1 for PCM
127
+ header[20] = 1 // Audio format 1 for PCM (not compressed)
125
128
  header[22] = channels.toByte()
126
- header[24] = (sampleRate and 0xff).toByte()
127
- header[25] = (sampleRate shr 8 and 0xff).toByte()
128
- header[26] = (sampleRate shr 16 and 0xff).toByte()
129
- header[27] = (sampleRate shr 24 and 0xff).toByte()
129
+ header[24] = (sampleRateInHz and 0xff).toByte()
130
+ header[25] = (sampleRateInHz shr 8 and 0xff).toByte()
131
+ header[26] = (sampleRateInHz shr 16 and 0xff).toByte()
132
+ header[27] = (sampleRateInHz shr 24 and 0xff).toByte()
130
133
  header[28] = (byteRate and 0xff).toByte()
131
134
  header[29] = (byteRate shr 8 and 0xff).toByte()
132
135
  header[30] = (byteRate shr 16 and 0xff).toByte()
@@ -143,6 +146,8 @@ class ExpoAudioStreamModule() : Module() {
143
146
  channelConfig = (params["channelConfig"] as? Int) ?: DEFAULT_CHANNEL_CONFIG
144
147
  audioFormat = (params["audioFormat"] as? Int) ?: DEFAULT_AUDIO_FORMAT
145
148
  bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
149
+ channels = if (channelConfig == AudioFormat.CHANNEL_IN_MONO) 1 else 2
150
+ bitDepth = if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) 16 else 8
146
151
  }
147
152
 
148
153
  private fun listAudioFiles(): List<String> {
@@ -201,7 +206,7 @@ class ExpoAudioStreamModule() : Module() {
201
206
 
202
207
  try {
203
208
  FileOutputStream(audioFile, true).use { fos ->
204
- writeWavHeader(fos, channelConfig, sampleRateInHz, audioFormat)
209
+ writeWavHeader(fos)
205
210
  }
206
211
  } catch (e: IOException) {
207
212
  promise.reject("FILE_CREATION_FAILED", "Failed to create audio file with WAV header", null)
@@ -230,24 +235,29 @@ class ExpoAudioStreamModule() : Module() {
230
235
  try {
231
236
  audioRecord?.stop()
232
237
  audioRecord?.release()
233
- val endTime = System.currentTimeMillis()
234
- totalRecordedTime += (endTime - recordingStartTime - pausedDuration) // Adjust the total recording time
235
- isRecording.set(false)
236
- isPaused.set(false)
237
238
 
238
- // Calculate the file size
239
239
  val fileSize = audioFile?.length() ?: 0
240
+ val dataFileSize = fileSize - 44 // Subtract header size
241
+ val byteRate = sampleRateInHz * channels * (bitDepth / 8)
242
+
243
+ // Calculate duration based on the data size and byte rate
244
+ val duration = if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0
240
245
 
241
246
  // Create result bundle
242
247
  val result = bundleOf(
243
248
  "fileUri" to audioFile?.toURI().toString(),
244
- "duration" to totalRecordedTime,
249
+ "duration" to duration,
250
+ "channels" to channels,
251
+ "bitDepth" to bitDepth,
252
+ "sampleRate" to sampleRateInHz,
245
253
  "size" to fileSize,
246
254
  "mimeType" to mimeType
247
255
  )
248
256
  promise.resolve(result)
249
257
 
250
258
  // Reset the timing variables
259
+ isRecording.set(false)
260
+ isPaused.set(false)
251
261
  totalRecordedTime = 0
252
262
  pausedDuration = 0
253
263
  } catch (e: Exception) {
@@ -320,13 +330,17 @@ class ExpoAudioStreamModule() : Module() {
320
330
  val deltaSize = fileSize - lastEmittedSize
321
331
  lastEmittedSize = fileSize // Update last emitted size
322
332
 
333
+ // Calculate position in milliseconds
334
+ val positionInMs = (from * 1000) / (sampleRateInHz * channels * (bitDepth / 8))
335
+
323
336
  mainHandler.post {
324
337
  try {
325
338
  this@ExpoAudioStreamModule.sendEvent(AUDIO_EVENT_NAME, bundleOf(
326
339
  "fileUri" to audioFile?.toURI().toString(),
327
- "from" to from,
340
+ "lastEmittedSize" to from,
328
341
  "encoded" to encodedBuffer,
329
342
  "deltaSize" to deltaSize,
343
+ "position" to positionInMs,
330
344
  "mimeType" to mimeType,
331
345
  "totalSize" to fileSize,
332
346
  "streamUuid" to streamUuid
@@ -2,7 +2,8 @@ export interface AudioEventPayload {
2
2
  encoded?: string;
3
3
  buffer?: Blob;
4
4
  fileUri: string;
5
- from: number;
5
+ lastEmittedSize: number;
6
+ position: number;
6
7
  deltaSize: number;
7
8
  totalSize: number;
8
9
  mimeType: string;
@@ -13,6 +14,9 @@ export interface AudioStreamResult {
13
14
  duration: number;
14
15
  size: number;
15
16
  mimeType: string;
17
+ channels?: number;
18
+ bitDepth?: number;
19
+ sampleRate?: number;
16
20
  }
17
21
  export interface AudioStreamStatus {
18
22
  isRecording: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAK/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAK/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface AudioEventPayload {\n encoded?: string;\n buffer?: Blob;\n fileUri: string;\n from: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n}\n\nexport interface AudioStreamResult {\n fileUri: string;\n duration: number;\n size: number;\n mimeType: string;\n}\n\nexport interface AudioStreamStatus {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n interval: number;\n mimeType: string;\n}\n\nexport interface RecordingOptions {\n // TODO align Android and IOS options\n // sampleRate?: number;\n // channelConfig?: number; // numberOfChannel\n // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)\n interval?: number;\n}\n"]}
1
+ {"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface AudioEventPayload {\n encoded?: string;\n buffer?: Blob;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n}\n\nexport interface AudioStreamResult {\n fileUri: string;\n duration: number;\n size: number;\n mimeType: string;\n channels?: number;\n bitDepth?: number;\n sampleRate?: number;\n}\n\nexport interface AudioStreamStatus {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n interval: number;\n mimeType: string;\n}\n\nexport interface RecordingOptions {\n // TODO align Android and IOS options\n // sampleRate?: number;\n // channelConfig?: number; // numberOfChannel\n // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)\n interval?: number;\n}\n"]}
@@ -7,16 +7,20 @@ declare class ExpoAudioStreamWeb extends EventEmitter {
7
7
  isPaused: boolean;
8
8
  recordingStartTime: number;
9
9
  pausedTime: number;
10
- currentDuration: number;
10
+ currentDurationMs: number;
11
11
  currentSize: number;
12
12
  currentInterval: number;
13
13
  lastEmittedSize: number;
14
+ lastEmittedTime: number;
14
15
  streamUuid: string | null;
15
16
  constructor();
16
17
  getMediaStream(): Promise<MediaStream>;
17
18
  startRecording(options?: RecordingOptions): Promise<string>;
18
19
  setupRecordingListeners(): void;
19
- emitAudioEvent(data: Blob): void;
20
+ emitAudioEvent({ data, position }: {
21
+ data: Blob;
22
+ position: number;
23
+ }): void;
20
24
  generateUUID(): string;
21
25
  stopRecording(): Promise<AudioStreamResult | null>;
22
26
  pauseRecording(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStreamModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAEL,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,cAAM,kBAAmB,SAAQ,YAAY;IAC3C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;;IA2BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,gBAAqB;IAmBnD,uBAAuB;IA0BvB,cAAc,CAAC,IAAI,EAAE,IAAI;IAgBzB,YAAY;IAUN,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;IAUN,cAAc;IAId,eAAe;CAGhB;;AAED,wBAAwC"}
1
+ {"version":3,"file":"ExpoAudioStreamModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAEL,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,cAAM,kBAAmB,SAAQ,YAAY;IAC3C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;;IA4BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,gBAAqB;IAoBnD,uBAAuB;IA4BvB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAiBnE,YAAY;IAUN,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;IAUN,cAAc;IAId,eAAe;CAGhB;;AAED,wBAAwC"}
@@ -8,10 +8,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
8
8
  isPaused;
9
9
  recordingStartTime;
10
10
  pausedTime;
11
- currentDuration;
11
+ currentDurationMs;
12
12
  currentSize;
13
13
  currentInterval;
14
14
  lastEmittedSize;
15
+ lastEmittedTime;
15
16
  streamUuid;
16
17
  constructor() {
17
18
  const mockNativeModule = {
@@ -29,10 +30,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
29
30
  this.isPaused = false;
30
31
  this.recordingStartTime = 0;
31
32
  this.pausedTime = 0;
32
- this.currentDuration = 0;
33
+ this.currentDurationMs = 0;
33
34
  this.currentSize = 0;
34
35
  this.currentInterval = 1000; // Default interval in ms
35
36
  this.lastEmittedSize = 0;
37
+ this.lastEmittedTime = 0;
36
38
  this.streamUuid = null; // Initialize UUID on first recording start
37
39
  }
38
40
  // Utility to handle user media stream
@@ -58,6 +60,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
58
60
  this.recordingStartTime = Date.now();
59
61
  this.pausedTime = 0;
60
62
  this.lastEmittedSize = 0;
63
+ this.lastEmittedTime = 0;
61
64
  this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
62
65
  const fileUri = `${this.streamUuid}.webm`;
63
66
  return fileUri;
@@ -70,7 +73,8 @@ class ExpoAudioStreamWeb extends EventEmitter {
70
73
  this.mediaRecorder.ondataavailable = (event) => {
71
74
  this.audioChunks.push(event.data);
72
75
  this.currentSize += event.data.size; // Update the size of the recording
73
- this.emitAudioEvent(event.data); // Emit the event with the correct payload
76
+ this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });
77
+ this.lastEmittedTime = event.timeStamp;
74
78
  this.lastEmittedSize = this.currentSize;
75
79
  };
76
80
  this.mediaRecorder.onstop = () => {
@@ -85,13 +89,14 @@ class ExpoAudioStreamWeb extends EventEmitter {
85
89
  this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming
86
90
  };
87
91
  }
88
- emitAudioEvent(data) {
92
+ emitAudioEvent({ data, position }) {
89
93
  const fileUri = `${this.streamUuid}.webm`;
90
94
  const audioEventPayload = {
91
95
  fileUri,
92
96
  mimeType: "audio/webm",
93
- from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
97
+ lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
94
98
  deltaSize: data.size,
99
+ position,
95
100
  totalSize: this.currentSize,
96
101
  buffer: data,
97
102
  streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
@@ -110,10 +115,10 @@ class ExpoAudioStreamWeb extends EventEmitter {
110
115
  async stopRecording() {
111
116
  this.mediaRecorder?.stop();
112
117
  this.isRecording = false;
113
- this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;
118
+ this.currentDurationMs = Date.now() - this.recordingStartTime;
114
119
  const result = {
115
120
  fileUri: `${this.streamUuid}.webm`,
116
- duration: this.currentDuration,
121
+ duration: this.currentDurationMs,
117
122
  size: this.currentSize,
118
123
  mimeType: "audio/webm",
119
124
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStreamModule.web.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import debug from \"debug\";\nimport { EventEmitter } from \"expo-modules-core\";\n\nimport {\n AudioEventPayload,\n AudioStreamResult,\n RecordingOptions,\n} from \"./ExpoAudioStream.types\";\n\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\nclass ExpoAudioStreamWeb extends EventEmitter {\n mediaRecorder: MediaRecorder | null;\n audioChunks: Blob[];\n isRecording: boolean;\n isPaused: boolean;\n recordingStartTime: number;\n pausedTime: number;\n currentDuration: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: number;\n streamUuid: string | null;\n\n constructor() {\n const mockNativeModule = {\n addListener: (eventName: string) => {\n // Not used on web\n },\n removeListeners: (count: number) => {\n // Not used on web\n },\n };\n super(mockNativeModule); // Pass the mock native module to the parent class\n\n this.mediaRecorder = null;\n this.audioChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedTime = 0;\n this.currentDuration = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 0;\n this.streamUuid = null; // Initialize UUID on first recording start\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n return await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (error) {\n console.error(\"Failed to get media stream:\", error);\n throw error;\n }\n }\n\n // Start recording with options\n async startRecording(options: RecordingOptions = {}) {\n if (this.isRecording) {\n throw new Error(\"Recording is already in progress\");\n }\n\n const stream = await this.getMediaStream();\n this.mediaRecorder = new MediaRecorder(stream);\n this.setupRecordingListeners();\n this.mediaRecorder.start(options.interval || this.currentInterval);\n this.isRecording = true;\n this.recordingStartTime = Date.now();\n this.pausedTime = 0;\n this.lastEmittedSize = 0;\n this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n return fileUri;\n }\n\n // Setup listeners for the MediaRecorder\n setupRecordingListeners() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n this.mediaRecorder.ondataavailable = (event) => {\n this.audioChunks.push(event.data);\n this.currentSize += event.data.size; // Update the size of the recording\n this.emitAudioEvent(event.data); // Emit the event with the correct payload\n this.lastEmittedSize = this.currentSize;\n };\n\n this.mediaRecorder.onstop = () => {\n this.isRecording = false;\n log(\"Recording stopped\", this.audioChunks);\n };\n\n this.mediaRecorder.onpause = () => {\n this.isPaused = true;\n };\n\n this.mediaRecorder.onresume = () => {\n this.isPaused = false;\n this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming\n };\n }\n\n emitAudioEvent(data: Blob) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: \"audio/webm\",\n from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? \"\", // Generate or manage UUID for stream identification\n };\n\n this.emit(\"AudioData\", audioEventPayload);\n }\n\n // Helper method to generate a UUID\n generateUUID() {\n // Implementation of UUID generation (use a library or custom method)\n return \"xxxx-xxxx-xxxx-xxxx\".replace(/[x]/g, (c) => {\n const r = (Math.random() * 16) | 0,\n v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioStreamResult | null> {\n this.mediaRecorder?.stop();\n this.isRecording = false;\n this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDuration,\n size: this.currentSize,\n mimeType: \"audio/webm\",\n };\n\n return result;\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n\n if (this.isRecording && !this.isPaused) {\n this.mediaRecorder.pause();\n this.pausedTime = Date.now();\n } else {\n throw new Error(\"Recording is not active or already paused\");\n }\n }\n\n // Get current status\n status() {\n return {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n duration: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n };\n }\n\n listAudioFiles() {\n // Not applicable on web\n }\n\n clearAudioFiles() {\n // Not applicable on web\n }\n}\n\nexport default new ExpoAudioStreamWeb();\n"]}
1
+ {"version":3,"file":"ExpoAudioStreamModule.web.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import debug from \"debug\";\nimport { EventEmitter } from \"expo-modules-core\";\n\nimport {\n AudioEventPayload,\n AudioStreamResult,\n RecordingOptions,\n} from \"./ExpoAudioStream.types\";\n\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\nclass ExpoAudioStreamWeb extends EventEmitter {\n mediaRecorder: MediaRecorder | null;\n audioChunks: Blob[];\n isRecording: boolean;\n isPaused: boolean;\n recordingStartTime: number;\n pausedTime: number;\n currentDurationMs: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: number;\n lastEmittedTime: number;\n streamUuid: string | null;\n\n constructor() {\n const mockNativeModule = {\n addListener: (eventName: string) => {\n // Not used on web\n },\n removeListeners: (count: number) => {\n // Not used on web\n },\n };\n super(mockNativeModule); // Pass the mock native module to the parent class\n\n this.mediaRecorder = null;\n this.audioChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedTime = 0;\n this.currentDurationMs = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = null; // Initialize UUID on first recording start\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n return await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (error) {\n console.error(\"Failed to get media stream:\", error);\n throw error;\n }\n }\n\n // Start recording with options\n async startRecording(options: RecordingOptions = {}) {\n if (this.isRecording) {\n throw new Error(\"Recording is already in progress\");\n }\n\n const stream = await this.getMediaStream();\n this.mediaRecorder = new MediaRecorder(stream);\n this.setupRecordingListeners();\n this.mediaRecorder.start(options.interval || this.currentInterval);\n this.isRecording = true;\n this.recordingStartTime = Date.now();\n this.pausedTime = 0;\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n return fileUri;\n }\n\n // Setup listeners for the MediaRecorder\n setupRecordingListeners() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n this.mediaRecorder.ondataavailable = (event) => {\n this.audioChunks.push(event.data);\n this.currentSize += event.data.size; // Update the size of the recording\n\n this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });\n this.lastEmittedTime = event.timeStamp;\n this.lastEmittedSize = this.currentSize;\n };\n\n this.mediaRecorder.onstop = () => {\n this.isRecording = false;\n log(\"Recording stopped\", this.audioChunks);\n };\n\n this.mediaRecorder.onpause = () => {\n this.isPaused = true;\n };\n\n this.mediaRecorder.onresume = () => {\n this.isPaused = false;\n this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming\n };\n }\n\n emitAudioEvent({ data, position }: { data: Blob; position: number }) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: \"audio/webm\",\n lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\n position,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? \"\", // Generate or manage UUID for stream identification\n };\n\n this.emit(\"AudioData\", audioEventPayload);\n }\n\n // Helper method to generate a UUID\n generateUUID() {\n // Implementation of UUID generation (use a library or custom method)\n return \"xxxx-xxxx-xxxx-xxxx\".replace(/[x]/g, (c) => {\n const r = (Math.random() * 16) | 0,\n v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioStreamResult | null> {\n this.mediaRecorder?.stop();\n this.isRecording = false;\n this.currentDurationMs = Date.now() - this.recordingStartTime;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDurationMs,\n size: this.currentSize,\n mimeType: \"audio/webm\",\n };\n\n return result;\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n\n if (this.isRecording && !this.isPaused) {\n this.mediaRecorder.pause();\n this.pausedTime = Date.now();\n } else {\n throw new Error(\"Recording is not active or already paused\");\n }\n }\n\n // Get current status\n status() {\n return {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n duration: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n };\n }\n\n listAudioFiles() {\n // Not applicable on web\n }\n\n clearAudioFiles() {\n // Not applicable on web\n }\n}\n\nexport default new ExpoAudioStreamWeb();\n"]}
package/build/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { type Subscription } from "expo-modules-core";
2
2
  import { AudioEventPayload } from "./ExpoAudioStream.types";
3
- import { useAudioRecorder, UseAudioRecorderState } from "./useAudioRecording";
3
+ import { useAudioRecorder, UseAudioRecorderState, AudioDataEvent } from "./useAudioRecording";
4
4
  export declare function listAudioFiles(): Promise<string[]>;
5
5
  export declare function clearAudioFiles(): Promise<void>;
6
6
  export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): Subscription;
7
- export type { AudioEventPayload, UseAudioRecorderState };
7
+ export type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };
8
8
  export { useAudioRecorder };
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAM9E,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAElD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,YAAY,CAEd;AAED,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAM7B,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAElD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,YAAY,CAEd;AAED,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,cAAc,EAAE,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter, NativeModulesProxy, } from "expo-modules-core";
2
2
  import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
3
- import { useAudioRecorder } from "./useAudioRecording";
3
+ import { useAudioRecorder, } from "./useAudioRecording";
4
4
  const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
5
5
  export function listAudioFiles() {
6
6
  return ExpoAudioStreamModule.listAudioFiles();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,qBAAqB,CAAC;AAE9E,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAC5D,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,OAAO,qBAAqB,CAAC,cAAc,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,qBAAqB,CAAC,eAAe,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import {\n EventEmitter,\n NativeModulesProxy,\n type Subscription,\n} from \"expo-modules-core\";\n\n// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts\n// and on native platforms to ExpoAudioStream.ts\nimport { AudioEventPayload } from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\nimport { useAudioRecorder, UseAudioRecorderState } from \"./useAudioRecording\";\n\nconst emitter = new EventEmitter(\n ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,\n);\n\nexport function listAudioFiles(): Promise<string[]> {\n return ExpoAudioStreamModule.listAudioFiles();\n}\n\nexport function clearAudioFiles(): Promise<void> {\n return ExpoAudioStreamModule.clearAudioFiles();\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>,\n): Subscription {\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport type { AudioEventPayload, UseAudioRecorderState };\nexport { useAudioRecorder };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAC5D,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,OAAO,qBAAqB,CAAC,cAAc,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,qBAAqB,CAAC,eAAe,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import {\n EventEmitter,\n NativeModulesProxy,\n type Subscription,\n} from \"expo-modules-core\";\n\n// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts\n// and on native platforms to ExpoAudioStream.ts\nimport { AudioEventPayload } from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\nimport {\n useAudioRecorder,\n UseAudioRecorderState,\n AudioDataEvent,\n} from \"./useAudioRecording\";\n\nconst emitter = new EventEmitter(\n ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,\n);\n\nexport function listAudioFiles(): Promise<string[]> {\n return ExpoAudioStreamModule.listAudioFiles();\n}\n\nexport function clearAudioFiles(): Promise<void> {\n return ExpoAudioStreamModule.clearAudioFiles();\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>,\n): Subscription {\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };\nexport { useAudioRecorder };\n"]}
@@ -1,4 +1,8 @@
1
1
  import { AudioStreamResult, RecordingOptions } from "./ExpoAudioStream.types";
2
+ export interface AudioDataEvent {
3
+ buffer: Blob;
4
+ position: number;
5
+ }
2
6
  export interface UseAudioRecorderState {
3
7
  startRecording: (_: RecordingOptions) => Promise<string | null>;
4
8
  stopRecording: () => Promise<AudioStreamResult | null>;
@@ -8,7 +12,8 @@ export interface UseAudioRecorderState {
8
12
  duration: number;
9
13
  size: number;
10
14
  }
11
- export declare function useAudioRecorder({ onAudioStream, }: {
12
- onAudioStream?: (buffer: Blob) => void;
15
+ export declare function useAudioRecorder({ onAudioStream, debug, }: {
16
+ onAudioStream?: (_: AudioDataEvent) => void;
17
+ debug?: boolean;
13
18
  }): UseAudioRecorderState;
14
19
  //# sourceMappingURL=useAudioRecording.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAKjC,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,GACd,EAAE;IACD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,IAAI,CAAC;CACxC,GAAG,qBAAqB,CAkIxB"}
1
+ {"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;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,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,qBAAqB,CA2IxB"}
@@ -1,11 +1,9 @@
1
1
  import { decode as atob } from "base-64";
2
- import debug from "debug";
3
2
  import { Platform } from "expo-modules-core";
4
3
  import { useCallback, useEffect, useState } from "react";
5
4
  import { addAudioEventListener } from ".";
6
5
  import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
7
- const log = debug("expo-audio-stream:useAudioRecording");
8
- export function useAudioRecorder({ onAudioStream, }) {
6
+ export function useAudioRecorder({ onAudioStream, debug = false, }) {
9
7
  const [isRecording, setIsRecording] = useState(false);
10
8
  const [isPaused, setIsPaused] = useState(false);
11
9
  const [duration, setDuration] = useState(0);
@@ -22,16 +20,18 @@ export function useAudioRecorder({ onAudioStream, }) {
22
20
  return () => null;
23
21
  }, [isRecording, isPaused]);
24
22
  useEffect(() => {
25
- const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer, }) => {
26
- log(`Received audio event:`, {
27
- fileUri,
28
- deltaSize,
29
- totalSize,
30
- mimeType,
31
- from,
32
- streamUuid,
33
- encodedLength: encoded?.length,
34
- });
23
+ const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, }) => {
24
+ if (debug) {
25
+ console.log(`[useAudioRecorder] Received audio event:`, {
26
+ fileUri,
27
+ deltaSize,
28
+ totalSize,
29
+ mimeType,
30
+ lastEmittedSize,
31
+ streamUuid,
32
+ encodedLength: encoded?.length,
33
+ });
34
+ }
35
35
  if (deltaSize > 0) {
36
36
  // Coming from native ( ios / android ) otherwise buffer is set
37
37
  if (Platform.OS !== "web") {
@@ -59,15 +59,15 @@ export function useAudioRecorder({ onAudioStream, }) {
59
59
  // }
60
60
  // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
61
61
  // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
62
- onAudioStream?.(audioBlob);
62
+ onAudioStream?.({ buffer: audioBlob, position });
63
63
  }
64
64
  catch (error) {
65
- console.error("Error reading audio file:", error);
65
+ console.error("[useAudioRecorder] Error reading audio file:", error);
66
66
  }
67
67
  }
68
68
  else if (buffer) {
69
69
  // Coming from web
70
- onAudioStream?.(buffer);
70
+ onAudioStream?.({ buffer, position });
71
71
  }
72
72
  }
73
73
  });
@@ -79,12 +79,14 @@ export function useAudioRecorder({ onAudioStream, }) {
79
79
  setSize(0);
80
80
  setDuration(0);
81
81
  try {
82
- log(`start recoding`, recordingOptions);
82
+ if (debug) {
83
+ console.log(`[useAudioRecorder] start recoding`, recordingOptions);
84
+ }
83
85
  const fileUrl = await ExpoAudioStreamModule.startRecording(recordingOptions);
84
86
  return fileUrl;
85
87
  }
86
88
  catch (error) {
87
- console.error("Error starting recording:", error);
89
+ console.error("[useAudioRecorder] Error starting recording:", error);
88
90
  setIsRecording(false);
89
91
  }
90
92
  }, []);
@@ -101,7 +103,7 @@ export function useAudioRecorder({ onAudioStream, }) {
101
103
  setIsRecording(false);
102
104
  }
103
105
  catch (error) {
104
- console.error("Error pausing recording:", error);
106
+ console.error("[useAudioRecorder] Error pausing recording:", error);
105
107
  }
106
108
  }, []);
107
109
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as atob } from \"base-64\";\nimport debug from \"debug\";\nimport { 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\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\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}: {\n onAudioStream?: (buffer: Blob) => void;\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 (isRecording || isPaused) {\n const interval = setInterval(() => {\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n setDuration(status.duration);\n setSize(status.size);\n }, 1000);\n return () => clearInterval(interval);\n }\n\n return () => null;\n }, [isRecording, isPaused]);\n\n useEffect(() => {\n const subscribe = addAudioEventListener(\n async ({\n fileUri,\n deltaSize,\n totalSize,\n from,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n }) => {\n log(`Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n mimeType,\n from,\n streamUuid,\n encodedLength: encoded?.length,\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 // convert encoded string to binary data\n const binaryData = atob(encoded);\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: mimeType });\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: from,\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\n onAudioStream?.(audioBlob);\n } catch (error) {\n console.error(\"Error reading audio file:\", error);\n }\n } else if (buffer) {\n // Coming from web\n onAudioStream?.(buffer);\n }\n }\n },\n );\n return () => subscribe.remove();\n }, [isRecording, onAudioStream]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingOptions) => {\n setIsRecording(true);\n setIsPaused(false);\n setSize(0);\n setDuration(0);\n try {\n log(`start recoding`, recordingOptions);\n const fileUrl =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n\n return fileUrl;\n } catch (error) {\n console.error(\"Error starting recording:\", error);\n setIsRecording(false);\n }\n },\n [],\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(\"Error pausing recording:\", error);\n }\n }, []);\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 { decode as atob } from \"base-64\";\nimport { 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 buffer: Blob;\n position: 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) => 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 (isRecording || isPaused) {\n const interval = setInterval(() => {\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n setDuration(status.duration);\n setSize(status.size);\n }, 1000);\n return () => clearInterval(interval);\n }\n\n return () => null;\n }, [isRecording, isPaused]);\n\n useEffect(() => {\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 if (debug) {\n console.log(`[useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\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 // convert encoded string to binary data\n const binaryData = atob(encoded);\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: mimeType });\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: from,\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\n onAudioStream?.({ buffer: audioBlob, position });\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?.({ buffer, position });\n }\n }\n },\n );\n return () => subscribe.remove();\n }, [isRecording, onAudioStream]);\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 [],\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 }, []);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n isPaused,\n isRecording,\n duration,\n size,\n };\n}\n"]}
@@ -35,6 +35,9 @@ struct RecordingResult {
35
35
  var mimeType: String
36
36
  var duration: Int64
37
37
  var size: Int64
38
+ var channels: Int
39
+ var bitDepth: Int
40
+ var sampleRate: Double
38
41
  }
39
42
 
40
43
  protocol AudioStreamManagerDelegate: AnyObject {
@@ -62,7 +65,7 @@ class AudioStreamManager: NSObject {
62
65
  private var isPaused = false
63
66
  private var pausedDuration = 0
64
67
  private var fileManager = FileManager.default
65
- private var recordingSettings: RecordingSettings?
68
+ internal var recordingSettings: RecordingSettings?
66
69
  internal var recordingUUID: UUID?
67
70
  internal var mimeType: String = "audio/wav"
68
71
  weak var delegate: AudioStreamManagerDelegate? // Define the delegate here
@@ -147,16 +150,30 @@ class AudioStreamManager: NSObject {
147
150
 
148
151
 
149
152
  func getStatus() -> [String: Any] {
150
- let currentTime = Date()
151
- let totalRecordedTime = startTime != nil ? Int(currentTime.timeIntervalSince(startTime!)) - pausedDuration : 0
153
+ // let currentTime = Date()
154
+ // let totalRecordedTime = startTime != nil ? Int(currentTime.timeIntervalSince(startTime!)) - pausedDuration : 0
155
+ guard let settings = recordingSettings else {
156
+ print("Recording settings are not available.")
157
+ return [:]
158
+ }
159
+
160
+ let sampleRate = Double(settings.sampleRate)
161
+ let channels = Double(settings.numberOfChannels)
162
+ let bitDepth = Double(settings.bitDepth)
163
+
164
+ // Calculate the duration in seconds
165
+ let durationInSeconds = Double(totalDataSize) / (sampleRate * channels * (bitDepth / 8))
166
+ let durationInMilliseconds = Int(durationInSeconds * 1000)
167
+
152
168
  return [
153
- "duration": totalRecordedTime * 1000,
169
+ "duration": durationInMilliseconds,
154
170
  "isRecording": isRecording,
155
171
  "isPaused": isPaused,
156
172
  "mimeType": mimeType,
157
173
  "size": totalDataSize,
158
174
  "interval": emissionInterval
159
175
  ]
176
+
160
177
  }
161
178
 
162
179
  func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> String? {
@@ -265,7 +282,7 @@ class AudioStreamManager: NSObject {
265
282
  audioEngine.inputNode.removeTap(onBus: 0)
266
283
  isRecording = false
267
284
 
268
- guard let fileURL = recordingFileURL, let startTime = startTime else {
285
+ guard let fileURL = recordingFileURL, let startTime = startTime, let settings = recordingSettings else {
269
286
  print("Recording or file URL is nil.")
270
287
  return nil
271
288
  }
@@ -282,7 +299,15 @@ class AudioStreamManager: NSObject {
282
299
  // Update the WAV header with the correct file size
283
300
  updateWavHeader(fileURL: fileURL, totalDataSize: fileSize - 44) // Subtract the header size to get audio data size
284
301
 
285
- let result = RecordingResult(fileUri: fileURL.absoluteString, mimeType: mimeType, duration: duration, size: fileSize)
302
+ let result = RecordingResult(
303
+ fileUri: fileURL.absoluteString,
304
+ mimeType: mimeType,
305
+ duration: duration,
306
+ size: fileSize,
307
+ channels: settings.numberOfChannels,
308
+ bitDepth: settings.bitDepth,
309
+ sampleRate: settings.sampleRate
310
+ )
286
311
  recordingFileURL = nil // Reset for next recording
287
312
  return result
288
313
  } catch {
@@ -48,6 +48,9 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
48
48
  "fileUri": recordingResult.fileUri,
49
49
  "duration": recordingResult.duration,
50
50
  "size": recordingResult.size,
51
+ "channels": recordingResult.channels,
52
+ "bitDepth": recordingResult.bitDepth,
53
+ "sampleRate": recordingResult.sampleRate,
51
54
  "mimeType": recordingResult.mimeType,
52
55
  ]
53
56
  promise.resolve(resultDict)
@@ -67,17 +70,26 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
67
70
  }
68
71
 
69
72
  func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64) {
70
- guard let fileURL = manager.recordingFileURL else { return }
73
+ guard let fileURL = manager.recordingFileURL,
74
+ let settings = manager.recordingSettings else { return }
75
+
71
76
  let encodedData = data.base64EncodedString()
72
77
 
73
78
  // Assuming `lastEmittedSize` and `streamUuid` are tracked within `AudioStreamManager`
74
79
  let deltaSize = data.count // This needs to be calculated based on what was last sent if using chunks
75
80
  let fileSize = totalDataSize // Total data size in bytes
76
81
 
82
+ // Calculate the position in milliseconds using the lastEmittedSize
83
+ let sampleRate = settings.sampleRate
84
+ let channels = Double(settings.numberOfChannels)
85
+ let bitDepth = Double(settings.bitDepth)
86
+ let position = Int((Double(manager.lastEmittedSize) / (sampleRate * channels * (bitDepth / 8))) * 1000)
87
+
77
88
  // Construct the event payload similar to Android
78
89
  let eventBody: [String: Any] = [
79
90
  "fileUri": fileURL.absoluteString,
80
- "from": manager.lastEmittedSize, // Needs to be maintained within AudioStreamManager
91
+ "lastEmittedSize": manager.lastEmittedSize, // Needs to be maintained within AudioStreamManager
92
+ "position": position, // Add position of the chunk in ms since
81
93
  "encoded": encodedData,
82
94
  "deltaSize": deltaSize,
83
95
  "totalSize": fileSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -33,8 +33,7 @@
33
33
  "open:android": "open -a \"Android Studio\" example/android"
34
34
  },
35
35
  "dependencies": {
36
- "base-64": "^1.0.0",
37
- "debug": "^4.3.4"
36
+ "base-64": "^1.0.0"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@expo/config-plugins": "^7.9.1",
@@ -49,17 +48,17 @@
49
48
  "eslint-plugin-prettier": "^5.1.3",
50
49
  "eslint-plugin-promise": "^6.1.1",
51
50
  "eslint-plugin-react": "^7.34.1",
51
+ "expo-file-system": "^16.0.9",
52
52
  "expo-module-scripts": "^3.4.2",
53
53
  "expo-modules-core": "^1.11.12",
54
54
  "prettier": "^3.2.5",
55
- "expo-file-system": "^16.0.9",
56
55
  "release-it": "^17.2.0"
57
56
  },
58
57
  "peerDependencies": {
59
58
  "expo": "*",
59
+ "expo-file-system": "*",
60
60
  "react": "*",
61
- "react-native": "*",
62
- "expo-file-system": "*"
61
+ "react-native": "*"
63
62
  },
64
63
  "publishConfig": {
65
64
  "access": "public",
@@ -2,7 +2,8 @@ export interface AudioEventPayload {
2
2
  encoded?: string;
3
3
  buffer?: Blob;
4
4
  fileUri: string;
5
- from: number;
5
+ lastEmittedSize: number;
6
+ position: number;
6
7
  deltaSize: number;
7
8
  totalSize: number;
8
9
  mimeType: string;
@@ -14,6 +15,9 @@ export interface AudioStreamResult {
14
15
  duration: number;
15
16
  size: number;
16
17
  mimeType: string;
18
+ channels?: number;
19
+ bitDepth?: number;
20
+ sampleRate?: number;
17
21
  }
18
22
 
19
23
  export interface AudioStreamStatus {
@@ -15,10 +15,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
15
15
  isPaused: boolean;
16
16
  recordingStartTime: number;
17
17
  pausedTime: number;
18
- currentDuration: number;
18
+ currentDurationMs: number;
19
19
  currentSize: number;
20
20
  currentInterval: number;
21
21
  lastEmittedSize: number;
22
+ lastEmittedTime: number;
22
23
  streamUuid: string | null;
23
24
 
24
25
  constructor() {
@@ -38,10 +39,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
38
39
  this.isPaused = false;
39
40
  this.recordingStartTime = 0;
40
41
  this.pausedTime = 0;
41
- this.currentDuration = 0;
42
+ this.currentDurationMs = 0;
42
43
  this.currentSize = 0;
43
44
  this.currentInterval = 1000; // Default interval in ms
44
45
  this.lastEmittedSize = 0;
46
+ this.lastEmittedTime = 0;
45
47
  this.streamUuid = null; // Initialize UUID on first recording start
46
48
  }
47
49
 
@@ -69,6 +71,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
69
71
  this.recordingStartTime = Date.now();
70
72
  this.pausedTime = 0;
71
73
  this.lastEmittedSize = 0;
74
+ this.lastEmittedTime = 0;
72
75
  this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
73
76
  const fileUri = `${this.streamUuid}.webm`;
74
77
  return fileUri;
@@ -82,7 +85,9 @@ class ExpoAudioStreamWeb extends EventEmitter {
82
85
  this.mediaRecorder.ondataavailable = (event) => {
83
86
  this.audioChunks.push(event.data);
84
87
  this.currentSize += event.data.size; // Update the size of the recording
85
- this.emitAudioEvent(event.data); // Emit the event with the correct payload
88
+
89
+ this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });
90
+ this.lastEmittedTime = event.timeStamp;
86
91
  this.lastEmittedSize = this.currentSize;
87
92
  };
88
93
 
@@ -101,13 +106,14 @@ class ExpoAudioStreamWeb extends EventEmitter {
101
106
  };
102
107
  }
103
108
 
104
- emitAudioEvent(data: Blob) {
109
+ emitAudioEvent({ data, position }: { data: Blob; position: number }) {
105
110
  const fileUri = `${this.streamUuid}.webm`;
106
111
  const audioEventPayload: AudioEventPayload = {
107
112
  fileUri,
108
113
  mimeType: "audio/webm",
109
- from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
114
+ lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
110
115
  deltaSize: data.size,
116
+ position,
111
117
  totalSize: this.currentSize,
112
118
  buffer: data,
113
119
  streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
@@ -130,10 +136,10 @@ class ExpoAudioStreamWeb extends EventEmitter {
130
136
  async stopRecording(): Promise<AudioStreamResult | null> {
131
137
  this.mediaRecorder?.stop();
132
138
  this.isRecording = false;
133
- this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;
139
+ this.currentDurationMs = Date.now() - this.recordingStartTime;
134
140
  const result: AudioStreamResult = {
135
141
  fileUri: `${this.streamUuid}.webm`,
136
- duration: this.currentDuration,
142
+ duration: this.currentDurationMs,
137
143
  size: this.currentSize,
138
144
  mimeType: "audio/webm",
139
145
  };
package/src/index.ts CHANGED
@@ -8,7 +8,11 @@ import {
8
8
  // and on native platforms to ExpoAudioStream.ts
9
9
  import { AudioEventPayload } from "./ExpoAudioStream.types";
10
10
  import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
11
- import { useAudioRecorder, UseAudioRecorderState } from "./useAudioRecording";
11
+ import {
12
+ useAudioRecorder,
13
+ UseAudioRecorderState,
14
+ AudioDataEvent,
15
+ } from "./useAudioRecording";
12
16
 
13
17
  const emitter = new EventEmitter(
14
18
  ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,
@@ -28,5 +32,5 @@ export function addAudioEventListener(
28
32
  return emitter.addListener<AudioEventPayload>("AudioData", listener);
29
33
  }
30
34
 
31
- export type { AudioEventPayload, UseAudioRecorderState };
35
+ export type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };
32
36
  export { useAudioRecorder };
@@ -1,5 +1,4 @@
1
1
  import { decode as atob } from "base-64";
2
- import debug from "debug";
3
2
  import { Platform } from "expo-modules-core";
4
3
  import { useCallback, useEffect, useState } from "react";
5
4
 
@@ -11,8 +10,10 @@ import {
11
10
  } from "./ExpoAudioStream.types";
12
11
  import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
13
12
 
14
- const log = debug("expo-audio-stream:useAudioRecording");
15
-
13
+ export interface AudioDataEvent {
14
+ buffer: Blob;
15
+ position: number;
16
+ }
16
17
  export interface UseAudioRecorderState {
17
18
  startRecording: (_: RecordingOptions) => Promise<string | null>;
18
19
  stopRecording: () => Promise<AudioStreamResult | null>;
@@ -25,8 +26,10 @@ export interface UseAudioRecorderState {
25
26
 
26
27
  export function useAudioRecorder({
27
28
  onAudioStream,
29
+ debug = false,
28
30
  }: {
29
- onAudioStream?: (buffer: Blob) => void;
31
+ onAudioStream?: (_: AudioDataEvent) => void;
32
+ debug?: boolean;
30
33
  }): UseAudioRecorderState {
31
34
  const [isRecording, setIsRecording] = useState(false);
32
35
  const [isPaused, setIsPaused] = useState(false);
@@ -52,21 +55,24 @@ export function useAudioRecorder({
52
55
  fileUri,
53
56
  deltaSize,
54
57
  totalSize,
55
- from,
58
+ lastEmittedSize,
59
+ position,
56
60
  streamUuid,
57
61
  encoded,
58
62
  mimeType,
59
63
  buffer,
60
64
  }) => {
61
- log(`Received audio event:`, {
62
- fileUri,
63
- deltaSize,
64
- totalSize,
65
- mimeType,
66
- from,
67
- streamUuid,
68
- encodedLength: encoded?.length,
69
- });
65
+ if (debug) {
66
+ console.log(`[useAudioRecorder] Received audio event:`, {
67
+ fileUri,
68
+ deltaSize,
69
+ totalSize,
70
+ mimeType,
71
+ lastEmittedSize,
72
+ streamUuid,
73
+ encodedLength: encoded?.length,
74
+ });
75
+ }
70
76
  if (deltaSize > 0) {
71
77
  // Coming from native ( ios / android ) otherwise buffer is set
72
78
  if (Platform.OS !== "web") {
@@ -96,13 +102,16 @@ export function useAudioRecorder({
96
102
  // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
97
103
  // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
98
104
 
99
- onAudioStream?.(audioBlob);
105
+ onAudioStream?.({ buffer: audioBlob, position });
100
106
  } catch (error) {
101
- console.error("Error reading audio file:", error);
107
+ console.error(
108
+ "[useAudioRecorder] Error reading audio file:",
109
+ error,
110
+ );
102
111
  }
103
112
  } else if (buffer) {
104
113
  // Coming from web
105
- onAudioStream?.(buffer);
114
+ onAudioStream?.({ buffer, position });
106
115
  }
107
116
  }
108
117
  },
@@ -117,13 +126,16 @@ export function useAudioRecorder({
117
126
  setSize(0);
118
127
  setDuration(0);
119
128
  try {
120
- log(`start recoding`, recordingOptions);
129
+ if (debug) {
130
+ console.log(`[useAudioRecorder] start recoding`, recordingOptions);
131
+ }
132
+
121
133
  const fileUrl =
122
134
  await ExpoAudioStreamModule.startRecording(recordingOptions);
123
135
 
124
136
  return fileUrl;
125
137
  } catch (error) {
126
- console.error("Error starting recording:", error);
138
+ console.error("[useAudioRecorder] Error starting recording:", error);
127
139
  setIsRecording(false);
128
140
  }
129
141
  },
@@ -144,7 +156,7 @@ export function useAudioRecorder({
144
156
  setIsPaused(true);
145
157
  setIsRecording(false);
146
158
  } catch (error) {
147
- console.error("Error pausing recording:", error);
159
+ console.error("[useAudioRecorder] Error pausing recording:", error);
148
160
  }
149
161
  }, []);
150
162