@siteed/expo-audio-stream 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -9
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +35 -21
- package/build/ExpoAudioStream.types.d.ts +5 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStreamModule.web.d.ts +6 -2
- package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.web.js +12 -7
- package/build/ExpoAudioStreamModule.web.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/useAudioRecording.d.ts +7 -5
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +31 -23
- package/build/useAudioRecording.js.map +1 -1
- package/ios/AudioStreamManager.swift +31 -6
- package/ios/ExpoAudioStreamModule.swift +14 -2
- package/package.json +5 -6
- package/src/ExpoAudioStream.types.ts +5 -1
- package/src/ExpoAudioStreamModule.web.ts +13 -7
- package/src/index.ts +6 -2
- package/src/useAudioRecording.ts +44 -24
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
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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] = (
|
|
127
|
-
header[25] = (
|
|
128
|
-
header[26] = (
|
|
129
|
-
header[27] = (
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
118
|
+
this.currentDurationMs = Date.now() - this.recordingStartTime;
|
|
114
119
|
const result = {
|
|
115
120
|
fileUri: `${this.streamUuid}.webm`,
|
|
116
|
-
duration: this.
|
|
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
|
package/build/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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();
|
package/build/index.js.map
CHANGED
|
@@ -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,
|
|
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,10 +12,8 @@ export interface UseAudioRecorderState {
|
|
|
8
12
|
duration: number;
|
|
9
13
|
size: number;
|
|
10
14
|
}
|
|
11
|
-
export declare function useAudioRecorder({ onAudioStream, }: {
|
|
12
|
-
onAudioStream?: (_:
|
|
13
|
-
|
|
14
|
-
position: number;
|
|
15
|
-
}) => void;
|
|
15
|
+
export declare function useAudioRecorder({ onAudioStream, debug, }: {
|
|
16
|
+
onAudioStream?: (_: AudioDataEvent) => void;
|
|
17
|
+
debug?: boolean;
|
|
16
18
|
}): UseAudioRecorderState;
|
|
17
19
|
//# sourceMappingURL=useAudioRecording.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"
|
|
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,CAmJxB"}
|
|
@@ -1,16 +1,17 @@
|
|
|
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
|
-
|
|
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);
|
|
12
10
|
const [size, setSize] = useState(0);
|
|
13
11
|
useEffect(() => {
|
|
12
|
+
if (debug) {
|
|
13
|
+
console.log(`[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`);
|
|
14
|
+
}
|
|
14
15
|
if (isRecording || isPaused) {
|
|
15
16
|
const interval = setInterval(() => {
|
|
16
17
|
const status = ExpoAudioStreamModule.status();
|
|
@@ -20,18 +21,23 @@ export function useAudioRecorder({ onAudioStream, }) {
|
|
|
20
21
|
return () => clearInterval(interval);
|
|
21
22
|
}
|
|
22
23
|
return () => null;
|
|
23
|
-
}, [isRecording, isPaused]);
|
|
24
|
+
}, [isRecording, isPaused, debug]);
|
|
24
25
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
log(`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
if (debug) {
|
|
27
|
+
console.log(`[useAudioRecorder] Registering audio event listener`);
|
|
28
|
+
}
|
|
29
|
+
const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, }) => {
|
|
30
|
+
if (debug) {
|
|
31
|
+
console.log(`[useAudioRecorder] Received audio event:`, {
|
|
32
|
+
fileUri,
|
|
33
|
+
deltaSize,
|
|
34
|
+
totalSize,
|
|
35
|
+
mimeType,
|
|
36
|
+
lastEmittedSize,
|
|
37
|
+
streamUuid,
|
|
38
|
+
encodedLength: encoded?.length,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
35
41
|
if (deltaSize > 0) {
|
|
36
42
|
// Coming from native ( ios / android ) otherwise buffer is set
|
|
37
43
|
if (Platform.OS !== "web") {
|
|
@@ -59,35 +65,37 @@ export function useAudioRecorder({ onAudioStream, }) {
|
|
|
59
65
|
// }
|
|
60
66
|
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
61
67
|
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
62
|
-
onAudioStream?.({ buffer: audioBlob, position
|
|
68
|
+
onAudioStream?.({ buffer: audioBlob, position });
|
|
63
69
|
}
|
|
64
70
|
catch (error) {
|
|
65
|
-
console.error("Error reading audio file:", error);
|
|
71
|
+
console.error("[useAudioRecorder] Error reading audio file:", error);
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
else if (buffer) {
|
|
69
75
|
// Coming from web
|
|
70
|
-
onAudioStream?.({ buffer, position
|
|
76
|
+
onAudioStream?.({ buffer, position });
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
});
|
|
74
80
|
return () => subscribe.remove();
|
|
75
|
-
}, [isRecording, onAudioStream]);
|
|
81
|
+
}, [isRecording, onAudioStream, debug]);
|
|
76
82
|
const startRecording = useCallback(async (recordingOptions) => {
|
|
77
83
|
setIsRecording(true);
|
|
78
84
|
setIsPaused(false);
|
|
79
85
|
setSize(0);
|
|
80
86
|
setDuration(0);
|
|
81
87
|
try {
|
|
82
|
-
|
|
88
|
+
if (debug) {
|
|
89
|
+
console.log(`[useAudioRecorder] start recoding`, recordingOptions);
|
|
90
|
+
}
|
|
83
91
|
const fileUrl = await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
84
92
|
return fileUrl;
|
|
85
93
|
}
|
|
86
94
|
catch (error) {
|
|
87
|
-
console.error("Error starting recording:", error);
|
|
95
|
+
console.error("[useAudioRecorder] Error starting recording:", error);
|
|
88
96
|
setIsRecording(false);
|
|
89
97
|
}
|
|
90
|
-
}, []);
|
|
98
|
+
}, [debug]);
|
|
91
99
|
const stopRecording = useCallback(async () => {
|
|
92
100
|
setIsRecording(false);
|
|
93
101
|
setIsPaused(false);
|
|
@@ -101,9 +109,9 @@ export function useAudioRecorder({ onAudioStream, }) {
|
|
|
101
109
|
setIsRecording(false);
|
|
102
110
|
}
|
|
103
111
|
catch (error) {
|
|
104
|
-
console.error("Error pausing recording:", error);
|
|
112
|
+
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
105
113
|
}
|
|
106
|
-
}, []);
|
|
114
|
+
}, [debug]);
|
|
107
115
|
return {
|
|
108
116
|
startRecording,
|
|
109
117
|
stopRecording,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,
|
|
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 (debug) {\n console.log(\n `[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`,\n );\n }\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, debug]);\n\n useEffect(() => {\n if (debug) {\n console.log(`[useAudioRecorder] Registering audio event listener`);\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 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, debug]);\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"]}
|
|
@@ -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
|
-
|
|
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":
|
|
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(
|
|
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
|
|
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
|
-
"
|
|
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
|
+
"version": "0.4.1",
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
139
|
+
this.currentDurationMs = Date.now() - this.recordingStartTime;
|
|
134
140
|
const result: AudioStreamResult = {
|
|
135
141
|
fileUri: `${this.streamUuid}.webm`,
|
|
136
|
-
duration: this.
|
|
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 {
|
|
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 };
|
package/src/useAudioRecording.ts
CHANGED
|
@@ -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
|
-
|
|
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?: (_:
|
|
31
|
+
onAudioStream?: (_: AudioDataEvent) => void;
|
|
32
|
+
debug?: boolean;
|
|
30
33
|
}): UseAudioRecorderState {
|
|
31
34
|
const [isRecording, setIsRecording] = useState(false);
|
|
32
35
|
const [isPaused, setIsPaused] = useState(false);
|
|
@@ -34,6 +37,11 @@ export function useAudioRecorder({
|
|
|
34
37
|
const [size, setSize] = useState(0);
|
|
35
38
|
|
|
36
39
|
useEffect(() => {
|
|
40
|
+
if (debug) {
|
|
41
|
+
console.log(
|
|
42
|
+
`[useAudioRecorder] isRecording: ${isRecording}, isPaused: ${isPaused}`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
37
45
|
if (isRecording || isPaused) {
|
|
38
46
|
const interval = setInterval(() => {
|
|
39
47
|
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
@@ -44,29 +52,35 @@ export function useAudioRecorder({
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
return () => null;
|
|
47
|
-
}, [isRecording, isPaused]);
|
|
55
|
+
}, [isRecording, isPaused, debug]);
|
|
48
56
|
|
|
49
57
|
useEffect(() => {
|
|
58
|
+
if (debug) {
|
|
59
|
+
console.log(`[useAudioRecorder] Registering audio event listener`);
|
|
60
|
+
}
|
|
50
61
|
const subscribe = addAudioEventListener(
|
|
51
62
|
async ({
|
|
52
63
|
fileUri,
|
|
53
64
|
deltaSize,
|
|
54
65
|
totalSize,
|
|
55
|
-
|
|
66
|
+
lastEmittedSize,
|
|
67
|
+
position,
|
|
56
68
|
streamUuid,
|
|
57
69
|
encoded,
|
|
58
70
|
mimeType,
|
|
59
71
|
buffer,
|
|
60
72
|
}) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
if (debug) {
|
|
74
|
+
console.log(`[useAudioRecorder] Received audio event:`, {
|
|
75
|
+
fileUri,
|
|
76
|
+
deltaSize,
|
|
77
|
+
totalSize,
|
|
78
|
+
mimeType,
|
|
79
|
+
lastEmittedSize,
|
|
80
|
+
streamUuid,
|
|
81
|
+
encodedLength: encoded?.length,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
70
84
|
if (deltaSize > 0) {
|
|
71
85
|
// Coming from native ( ios / android ) otherwise buffer is set
|
|
72
86
|
if (Platform.OS !== "web") {
|
|
@@ -96,19 +110,22 @@ export function useAudioRecorder({
|
|
|
96
110
|
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
97
111
|
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
98
112
|
|
|
99
|
-
onAudioStream?.({ buffer: audioBlob, position
|
|
113
|
+
onAudioStream?.({ buffer: audioBlob, position });
|
|
100
114
|
} catch (error) {
|
|
101
|
-
console.error(
|
|
115
|
+
console.error(
|
|
116
|
+
"[useAudioRecorder] Error reading audio file:",
|
|
117
|
+
error,
|
|
118
|
+
);
|
|
102
119
|
}
|
|
103
120
|
} else if (buffer) {
|
|
104
121
|
// Coming from web
|
|
105
|
-
onAudioStream?.({ buffer, position
|
|
122
|
+
onAudioStream?.({ buffer, position });
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
},
|
|
109
126
|
);
|
|
110
127
|
return () => subscribe.remove();
|
|
111
|
-
}, [isRecording, onAudioStream]);
|
|
128
|
+
}, [isRecording, onAudioStream, debug]);
|
|
112
129
|
|
|
113
130
|
const startRecording = useCallback(
|
|
114
131
|
async (recordingOptions: RecordingOptions) => {
|
|
@@ -117,17 +134,20 @@ export function useAudioRecorder({
|
|
|
117
134
|
setSize(0);
|
|
118
135
|
setDuration(0);
|
|
119
136
|
try {
|
|
120
|
-
|
|
137
|
+
if (debug) {
|
|
138
|
+
console.log(`[useAudioRecorder] start recoding`, recordingOptions);
|
|
139
|
+
}
|
|
140
|
+
|
|
121
141
|
const fileUrl =
|
|
122
142
|
await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
123
143
|
|
|
124
144
|
return fileUrl;
|
|
125
145
|
} catch (error) {
|
|
126
|
-
console.error("Error starting recording:", error);
|
|
146
|
+
console.error("[useAudioRecorder] Error starting recording:", error);
|
|
127
147
|
setIsRecording(false);
|
|
128
148
|
}
|
|
129
149
|
},
|
|
130
|
-
[],
|
|
150
|
+
[debug],
|
|
131
151
|
);
|
|
132
152
|
|
|
133
153
|
const stopRecording = useCallback(async () => {
|
|
@@ -144,9 +164,9 @@ export function useAudioRecorder({
|
|
|
144
164
|
setIsPaused(true);
|
|
145
165
|
setIsRecording(false);
|
|
146
166
|
} catch (error) {
|
|
147
|
-
console.error("Error pausing recording:", error);
|
|
167
|
+
console.error("[useAudioRecorder] Error pausing recording:", error);
|
|
148
168
|
}
|
|
149
|
-
}, []);
|
|
169
|
+
}, [debug]);
|
|
150
170
|
|
|
151
171
|
return {
|
|
152
172
|
startRecording,
|