@siteed/expo-audio-stream 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +9 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +62 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +4 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +445 -0
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +12 -0
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +7 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +39 -430
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +16 -0
- 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 +2 -2
- package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.web.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/useAudioRecording.d.ts +4 -2
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +18 -13
- package/build/useAudioRecording.js.map +1 -1
- package/ios/AudioStreamManager.swift +26 -21
- package/ios/ExpoAudioStreamModule.swift +12 -6
- package/package.json +1 -1
- package/src/ExpoAudioStream.types.ts +6 -5
- package/src/ExpoAudioStreamModule.web.ts +2 -2
- package/src/index.ts +4 -0
- package/src/useAudioRecording.ts +25 -18
|
@@ -1,459 +1,68 @@
|
|
|
1
1
|
package net.siteed.audiostream
|
|
2
2
|
|
|
3
|
-
import android.
|
|
4
|
-
import android.
|
|
5
|
-
import android.content.pm.PackageManager
|
|
6
|
-
import android.media.AudioFormat
|
|
7
|
-
import android.media.AudioRecord
|
|
8
|
-
import android.media.MediaRecorder
|
|
9
|
-
import android.os.Handler
|
|
10
|
-
import android.os.Looper
|
|
11
|
-
import android.os.SystemClock
|
|
12
|
-
import android.util.Base64
|
|
3
|
+
import android.os.Build
|
|
4
|
+
import android.os.Bundle
|
|
13
5
|
import android.util.Log
|
|
14
|
-
import androidx.
|
|
15
|
-
import androidx.core.os.bundleOf
|
|
6
|
+
import androidx.annotation.RequiresApi
|
|
16
7
|
import expo.modules.kotlin.Promise
|
|
17
8
|
import expo.modules.kotlin.modules.Module
|
|
18
9
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
19
|
-
import java.io.ByteArrayOutputStream
|
|
20
|
-
import java.io.File
|
|
21
|
-
import java.io.FileInputStream
|
|
22
|
-
import java.io.FileOutputStream
|
|
23
|
-
import java.io.IOException
|
|
24
|
-
import java.io.OutputStream
|
|
25
|
-
import java.io.RandomAccessFile
|
|
26
|
-
import java.util.concurrent.atomic.AtomicBoolean
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const val DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
|
|
31
|
-
const val DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
|
|
11
|
+
class ExpoAudioStreamModule() : Module(), EventSender {
|
|
12
|
+
private lateinit var audioRecorderManager: AudioRecorderManager
|
|
32
13
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
private var isRecording = AtomicBoolean(false)
|
|
40
|
-
private val isPaused = AtomicBoolean(false)
|
|
41
|
-
private var streamUuid: String? = null
|
|
42
|
-
private var audioFile: File? = null
|
|
43
|
-
private var recordingThread: Thread? = null
|
|
44
|
-
private var recordingStartTime: Long = 0
|
|
45
|
-
private var totalRecordedTime: Long = 0
|
|
46
|
-
private var totalDataSize = 0
|
|
47
|
-
private var interval = 1000L // Emit data every 1000 milliseconds (1 second)
|
|
48
|
-
private var lastEmitTime = SystemClock.elapsedRealtime()
|
|
49
|
-
private var lastPauseTime = 0L
|
|
50
|
-
private var pausedDuration = 0L
|
|
51
|
-
private var lastEmittedSize = 0L
|
|
52
|
-
private var mimeType = "audio/wav"
|
|
53
|
-
private val mainHandler = Handler(Looper.getMainLooper())
|
|
54
|
-
private var bitDepth = 16
|
|
55
|
-
private var channels = 1
|
|
56
|
-
private val audioRecordLock = Any()
|
|
14
|
+
@RequiresApi(Build.VERSION_CODES.R)
|
|
15
|
+
override fun definition() = ModuleDefinition {
|
|
16
|
+
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
|
17
|
+
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
18
|
+
// The module will be accessible from `requireNativeModule('ExpoAudioStream')` in JavaScript.
|
|
19
|
+
Name("ExpoAudioStream")
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
override fun definition() = ModuleDefinition {
|
|
60
|
-
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
|
61
|
-
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
62
|
-
// The module will be accessible from `requireNativeModule('ExpoAudioStream')` in JavaScript.
|
|
63
|
-
Name("ExpoAudioStream")
|
|
21
|
+
Events(Constants.AUDIO_EVENT_NAME)
|
|
64
22
|
|
|
65
|
-
|
|
23
|
+
// Initialize AudioRecorderManager
|
|
24
|
+
initializeManager()
|
|
66
25
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
startRecording(options, promise)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
Function("clearAudioFiles") {
|
|
73
|
-
clearAudioStorage()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
Function("status") {
|
|
77
|
-
synchronized(audioRecordLock) {
|
|
78
|
-
|
|
79
|
-
if(!isRecording.get()) {
|
|
80
|
-
return@Function bundleOf(
|
|
81
|
-
"isRecording" to false,
|
|
82
|
-
"isPaused" to false,
|
|
83
|
-
"mime" to mimeType,
|
|
84
|
-
"size" to 0,
|
|
85
|
-
"interval" to interval,
|
|
86
|
-
)
|
|
26
|
+
AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
|
|
27
|
+
audioRecorderManager.startRecording(options, promise)
|
|
87
28
|
}
|
|
88
29
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
val dataFileSize = fileSize - 44 // Assuming header is always 44 bytes
|
|
92
|
-
|
|
93
|
-
val byteRate = sampleRateInHz * channels * (bitDepth / 8)
|
|
94
|
-
val duration =
|
|
95
|
-
if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0 // Duration in milliseconds
|
|
96
|
-
|
|
97
|
-
bundleOf(
|
|
98
|
-
"duration" to duration,
|
|
99
|
-
"isRecording" to isRecording.get(),
|
|
100
|
-
"isPaused" to isPaused.get(),
|
|
101
|
-
"mime" to mimeType,
|
|
102
|
-
"size" to totalDataSize,
|
|
103
|
-
"interval" to interval,
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
AsyncFunction("listAudioFiles") { promise: Promise ->
|
|
109
|
-
try {
|
|
110
|
-
val fileList = listAudioFiles()
|
|
111
|
-
promise.resolve(fileList)
|
|
112
|
-
} catch (e: Exception) {
|
|
113
|
-
promise.reject("ERROR_LIST_FILES", "Failed to list audio files", e)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
AsyncFunction("pauseRecording") { promise: Promise ->
|
|
118
|
-
Log.d("AudioRecorderModule", "Pausing recording")
|
|
119
|
-
pauseRecording(promise)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
AsyncFunction("stopRecording") { promise: Promise ->
|
|
123
|
-
Log.d("AudioRecorderModule", "Stopping recording")
|
|
124
|
-
stopRecording(promise)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Method to write WAV file header
|
|
129
|
-
private fun writeWavHeader(out: OutputStream) {
|
|
130
|
-
val header = ByteArray(44)
|
|
131
|
-
val byteRate = sampleRateInHz * channels * bitDepth / 8
|
|
132
|
-
val blockAlign = channels * bitDepth / 8
|
|
133
|
-
|
|
134
|
-
// RIFF/WAVE header
|
|
135
|
-
"RIFF".toByteArray().copyInto(header, 0)
|
|
136
|
-
header[4] = 0 // Size will be updated later
|
|
137
|
-
"WAVE".toByteArray().copyInto(header, 8)
|
|
138
|
-
"fmt ".toByteArray().copyInto(header, 12)
|
|
139
|
-
|
|
140
|
-
// 16 for PCM
|
|
141
|
-
header[16] = 16
|
|
142
|
-
header[20] = 1 // Audio format 1 for PCM (not compressed)
|
|
143
|
-
header[22] = channels.toByte()
|
|
144
|
-
header[24] = (sampleRateInHz and 0xff).toByte()
|
|
145
|
-
header[25] = (sampleRateInHz shr 8 and 0xff).toByte()
|
|
146
|
-
header[26] = (sampleRateInHz shr 16 and 0xff).toByte()
|
|
147
|
-
header[27] = (sampleRateInHz shr 24 and 0xff).toByte()
|
|
148
|
-
header[28] = (byteRate and 0xff).toByte()
|
|
149
|
-
header[29] = (byteRate shr 8 and 0xff).toByte()
|
|
150
|
-
header[30] = (byteRate shr 16 and 0xff).toByte()
|
|
151
|
-
header[31] = (byteRate shr 24 and 0xff).toByte()
|
|
152
|
-
header[32] = blockAlign.toByte()
|
|
153
|
-
header[34] = bitDepth.toByte()
|
|
154
|
-
"data".toByteArray().copyInto(header, 36)
|
|
155
|
-
|
|
156
|
-
out.write(header, 0, 44)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private fun configureRecording(params: Map<String, Any?>) {
|
|
160
|
-
sampleRateInHz = (params["sampleRate"] as? Int) ?: DEFAULT_SAMPLE_RATE
|
|
161
|
-
channelConfig = (params["channelConfig"] as? Int) ?: DEFAULT_CHANNEL_CONFIG
|
|
162
|
-
audioFormat = (params["audioFormat"] as? Int) ?: DEFAULT_AUDIO_FORMAT
|
|
163
|
-
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
|
|
164
|
-
channels = if (channelConfig == AudioFormat.CHANNEL_IN_MONO) 1 else 2
|
|
165
|
-
bitDepth = if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) 16 else 8
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private fun listAudioFiles(): List<String> {
|
|
169
|
-
val filesDir = appContext.reactContext?.filesDir
|
|
170
|
-
// Filter to include only .wav files
|
|
171
|
-
val files = filesDir?.listFiles { file ->
|
|
172
|
-
file.isFile && file.name.endsWith(".wav")
|
|
173
|
-
}?.map { it.absolutePath } ?: listOf() // Use `listOf()` to return an empty list if null
|
|
174
|
-
return files
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
private fun startRecording(options: Map<String, Any?>, promise: Promise) {
|
|
179
|
-
if (!checkPermission()) {
|
|
180
|
-
promise.reject("PERMISSION_DENIED", "Recording permission has not been granted", null)
|
|
181
|
-
return
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (isRecording.get() && !isPaused.get()) {
|
|
185
|
-
promise.reject("ALREADY_RECORDING", "Recording is already in progress", null)
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
val intervalOption = options["interval"]
|
|
190
|
-
if (intervalOption != null) {
|
|
191
|
-
Log.d("AudioRecorderModule", "Setting interval to $intervalOption")
|
|
192
|
-
if (intervalOption is Number) {
|
|
193
|
-
val intervalValue = intervalOption.toLong()
|
|
194
|
-
if (intervalValue < 100) {
|
|
195
|
-
promise.reject("INVALID_INTERVAL", "Interval must be at least 100 ms", null)
|
|
196
|
-
return
|
|
197
|
-
} else {
|
|
198
|
-
this.interval = intervalValue
|
|
30
|
+
Function("clearAudioFiles") {
|
|
31
|
+
audioRecorderManager.clearAudioStorage()
|
|
199
32
|
}
|
|
200
|
-
} else {
|
|
201
|
-
promise.reject("INVALID_INTERVAL", "Interval must be a number", null)
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Log for new recording or resuming
|
|
207
|
-
if (!isPaused.get()) {
|
|
208
|
-
streamUuid = java.util.UUID.randomUUID().toString()
|
|
209
|
-
audioFile = File(appContext.reactContext?.filesDir, "audio_${streamUuid}.wav")
|
|
210
|
-
Log.i("AudioRecorderModule", "Starting new recording $streamUuid with sample rate: $sampleRateInHz, channel config: $channelConfig, audio format: $audioFormat, buffer size: $bufferSizeInBytes, interval: $interval")
|
|
211
|
-
|
|
212
|
-
} else {
|
|
213
|
-
Log.i("AudioRecorderModule", "Resuming recording")
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Initialize the recorder if it's a new recording
|
|
217
|
-
if (!isPaused.get() && !initializeRecorder()) {
|
|
218
|
-
promise.reject("INITIALIZATION_FAILED", "AudioRecord initialization failed", null)
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
33
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
writeWavHeader(fos)
|
|
225
|
-
}
|
|
226
|
-
} catch (e: IOException) {
|
|
227
|
-
promise.reject("FILE_CREATION_FAILED", "Failed to create audio file with WAV header", null)
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
audioRecord?.startRecording()
|
|
232
|
-
isPaused.set(false)
|
|
233
|
-
isRecording.set(true)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (!isPaused.get()) {
|
|
237
|
-
recordingStartTime = System.currentTimeMillis() // Only reset start time if it's not a resume
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
recordingThread = Thread { recordingProcess() }.apply { start() }
|
|
241
|
-
|
|
242
|
-
val result = bundleOf(
|
|
243
|
-
"fileUri" to audioFile?.toURI().toString(),
|
|
244
|
-
"channels" to channels,
|
|
245
|
-
"bitDepth" to bitDepth,
|
|
246
|
-
"sampleRate" to sampleRateInHz,
|
|
247
|
-
"mimeType" to mimeType
|
|
248
|
-
)
|
|
249
|
-
promise.resolve(result)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private fun stopRecording(promise: Promise) {
|
|
253
|
-
synchronized(audioRecordLock) {
|
|
254
|
-
|
|
255
|
-
if (!isRecording.get()) {
|
|
256
|
-
promise.reject("NOT_RECORDING", "Recording is not active", null)
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
val audioData = ByteArray(bufferSizeInBytes)
|
|
262
|
-
val bytesRead = audioRecord?.read(audioData, 0, bufferSizeInBytes) ?: -1
|
|
263
|
-
Log.d("AudioRecorderModule", "Last Read $bytesRead bytes")
|
|
264
|
-
if (bytesRead > 0) {
|
|
265
|
-
emitAudioData(audioData, bytesRead)
|
|
34
|
+
Function("status") {
|
|
35
|
+
return@Function audioRecorderManager.getStatus()
|
|
266
36
|
}
|
|
267
37
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Log.d("AudioRecorderModule", "Stopping AudioRecord");
|
|
271
|
-
audioRecord!!.stop()
|
|
38
|
+
AsyncFunction("listAudioFiles") { promise: Promise ->
|
|
39
|
+
audioRecorderManager.listAudioFiles(promise)
|
|
272
40
|
}
|
|
273
|
-
} catch (e: IllegalStateException) {
|
|
274
|
-
Log.e("AudioRecording", "Error reading from AudioRecord", e);
|
|
275
|
-
} finally {
|
|
276
|
-
audioRecord?.release()
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
val fileSize = audioFile?.length() ?: 0
|
|
281
|
-
val dataFileSize = fileSize - 44 // Subtract header size
|
|
282
|
-
val byteRate = sampleRateInHz * channels * (bitDepth / 8)
|
|
283
|
-
|
|
284
|
-
// Calculate duration based on the data size and byte rate
|
|
285
|
-
val duration = if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0
|
|
286
|
-
|
|
287
|
-
// Create result bundle
|
|
288
|
-
val result = bundleOf(
|
|
289
|
-
"fileUri" to audioFile?.toURI().toString(),
|
|
290
|
-
"duration" to duration,
|
|
291
|
-
"channels" to channels,
|
|
292
|
-
"bitDepth" to bitDepth,
|
|
293
|
-
"sampleRate" to sampleRateInHz,
|
|
294
|
-
"size" to fileSize,
|
|
295
|
-
"mimeType" to mimeType
|
|
296
|
-
)
|
|
297
|
-
promise.resolve(result)
|
|
298
|
-
|
|
299
|
-
// Reset the timing variables
|
|
300
|
-
isRecording.set(false)
|
|
301
|
-
isPaused.set(false)
|
|
302
|
-
totalRecordedTime = 0
|
|
303
|
-
pausedDuration = 0
|
|
304
|
-
} catch (e: Exception) {
|
|
305
|
-
Log.d("AudioRecorderModule", "Failed to stop recording", e)
|
|
306
|
-
promise.reject("STOP_FAILED", "Failed to stop recording", e)
|
|
307
|
-
} finally {
|
|
308
|
-
audioRecord = null
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private fun recordingProcess() {
|
|
314
|
-
FileOutputStream(audioFile, true).use { fos ->
|
|
315
|
-
// Buffer to accumulate data
|
|
316
|
-
val accumulatedAudioData = ByteArrayOutputStream()
|
|
317
|
-
writeWavHeader(accumulatedAudioData)
|
|
318
|
-
|
|
319
|
-
// Write audio data directly to the file
|
|
320
|
-
val audioData = ByteArray(bufferSizeInBytes)
|
|
321
|
-
while (isRecording.get() && !Thread.currentThread().isInterrupted) {
|
|
322
|
-
if (!isPaused.get()) {
|
|
323
|
-
val bytesRead = synchronized(audioRecordLock) {
|
|
324
|
-
// Only synchronize the read operation and the check
|
|
325
|
-
audioRecord?.let {
|
|
326
|
-
if (it.state != AudioRecord.STATE_INITIALIZED) {
|
|
327
|
-
Log.e("AudioRecorderModule", "AudioRecord not initialized")
|
|
328
|
-
return@let -1
|
|
329
|
-
}
|
|
330
|
-
it.read(audioData, 0, bufferSizeInBytes)
|
|
331
|
-
} ?: -1 // Handle null case
|
|
332
|
-
}
|
|
333
|
-
if (bytesRead > 0) {
|
|
334
|
-
fos.write(audioData, 0, bytesRead)
|
|
335
|
-
totalDataSize += bytesRead
|
|
336
|
-
accumulatedAudioData.write(audioData, 0, bytesRead)
|
|
337
41
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
emitAudioData(accumulatedAudioData.toByteArray(), accumulatedAudioData.size())
|
|
341
|
-
lastEmitTime = SystemClock.elapsedRealtime() // Reset the timer
|
|
342
|
-
accumulatedAudioData.reset() // Clear the accumulator
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
Log.d("AudioRecorderModule", "Bytes written to file: $bytesRead")
|
|
346
|
-
}
|
|
42
|
+
AsyncFunction("pauseRecording") { promise: Promise ->
|
|
43
|
+
audioRecorderManager.pauseRecording(promise)
|
|
347
44
|
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
updateWavHeader() // Update the header with the correct file size after recording stops
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
private fun updateWavHeader() {
|
|
354
|
-
try {
|
|
355
|
-
RandomAccessFile(audioFile, "rw").use { raf ->
|
|
356
|
-
val fileSize = raf.length()
|
|
357
|
-
val dataSize = fileSize - 44 // Subtract the header size
|
|
358
|
-
raf.seek(4) // Skip 'RIFF' label
|
|
359
|
-
|
|
360
|
-
// Write correct file size, excluding the first 8 bytes of the RIFF header
|
|
361
|
-
raf.writeInt(Integer.reverseBytes((dataSize + 36).toInt()))
|
|
362
|
-
|
|
363
|
-
raf.seek(40) // Go to the data size position
|
|
364
|
-
raf.writeInt(Integer.reverseBytes(dataSize.toInt())) // Write the size of the data segment
|
|
365
|
-
}
|
|
366
|
-
} catch (e: IOException) {
|
|
367
|
-
Log.e("AudioRecorderModule", "Could not update WAV header", e)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private fun clearAudioStorage() {
|
|
372
|
-
// Clear all files in the app's internal storage
|
|
373
|
-
val filesDir = appContext.reactContext?.filesDir
|
|
374
|
-
filesDir?.listFiles()?.forEach {
|
|
375
|
-
Log.d("AudioRecorderModule", "Deleting file: ${it.absolutePath}")
|
|
376
|
-
it.delete()
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
45
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
val fileSize = audioFile?.length() ?: 0 // Update file size information
|
|
384
|
-
val from = lastEmittedSize
|
|
385
|
-
val deltaSize = fileSize - lastEmittedSize
|
|
386
|
-
lastEmittedSize = fileSize // Update last emitted size
|
|
387
|
-
|
|
388
|
-
// Calculate position in milliseconds
|
|
389
|
-
val positionInMs = (from * 1000) / (sampleRateInHz * channels * (bitDepth / 8))
|
|
46
|
+
AsyncFunction("resumeRecording") { promise: Promise ->
|
|
47
|
+
audioRecorderManager.resumeRecording(promise)
|
|
48
|
+
}
|
|
390
49
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
"fileUri" to audioFile?.toURI().toString(),
|
|
395
|
-
"lastEmittedSize" to from,
|
|
396
|
-
"encoded" to encodedBuffer,
|
|
397
|
-
"deltaSize" to length,
|
|
398
|
-
"position" to positionInMs,
|
|
399
|
-
"mimeType" to mimeType,
|
|
400
|
-
"totalSize" to fileSize,
|
|
401
|
-
"streamUuid" to streamUuid
|
|
402
|
-
))
|
|
403
|
-
} catch (e: Exception) {
|
|
404
|
-
Log.e("AudioRecorderModule", "Failed to send event", e)
|
|
405
|
-
}
|
|
50
|
+
AsyncFunction("stopRecording") { promise: Promise ->
|
|
51
|
+
audioRecorderManager.stopRecording(promise)
|
|
52
|
+
}
|
|
406
53
|
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
private fun encodeAudioData(rawData: ByteArray): String {
|
|
410
|
-
return Base64.encodeToString(rawData, Base64.NO_WRAP)
|
|
411
|
-
}
|
|
412
54
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
audioRecord?.stop()
|
|
421
|
-
lastPauseTime = System.currentTimeMillis() // Record the time when the recording was paused
|
|
422
|
-
isPaused.set(true)
|
|
423
|
-
promise.resolve("Recording paused")
|
|
424
|
-
} else {
|
|
425
|
-
promise.reject("NOT_RECORDING_OR_ALREADY_PAUSED", "Recording is either not active or already paused", null)
|
|
55
|
+
private fun initializeManager() {
|
|
56
|
+
val androidContext =
|
|
57
|
+
appContext.reactContext ?: throw IllegalStateException("Android context not available")
|
|
58
|
+
val permissionUtils = PermissionUtils(androidContext)
|
|
59
|
+
val audioEncoder = AudioDataEncoder()
|
|
60
|
+
audioRecorderManager =
|
|
61
|
+
AudioRecorderManager(androidContext.filesDir, permissionUtils, audioEncoder, this)
|
|
426
62
|
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
@SuppressLint("MissingPermission")
|
|
430
|
-
private fun initializeRecorder(): Boolean {
|
|
431
|
-
synchronized(audioRecordLock) {
|
|
432
|
-
Log.d("AudioRecorderModule", "Initializing recorder")
|
|
433
|
-
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
|
|
434
|
-
Log.d("AudioRecorderModule", "Buffer size: $bufferSizeInBytes")
|
|
435
|
-
|
|
436
|
-
if (bufferSizeInBytes == AudioRecord.ERROR_BAD_VALUE) {
|
|
437
|
-
Log.e("AudioRecorderModule", "Invalid buffer size")
|
|
438
|
-
return false
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
val recorder = AudioRecord(
|
|
442
|
-
MediaRecorder.AudioSource.MIC,
|
|
443
|
-
sampleRateInHz,
|
|
444
|
-
channelConfig,
|
|
445
|
-
audioFormat,
|
|
446
|
-
bufferSizeInBytes
|
|
447
|
-
)
|
|
448
|
-
if (recorder.state != AudioRecord.STATE_INITIALIZED) {
|
|
449
|
-
Log.e("AudioRecorderModule", "AudioRecord initialization failed")
|
|
450
|
-
recorder.release() // Clean up resources if initialization fails
|
|
451
|
-
return false
|
|
452
|
-
}
|
|
453
63
|
|
|
454
|
-
|
|
455
|
-
|
|
64
|
+
override fun sendExpoEvent(eventName: String, params: Bundle) {
|
|
65
|
+
this@ExpoAudioStreamModule.sendEvent(Constants.AUDIO_EVENT_NAME, params)
|
|
456
66
|
}
|
|
457
|
-
}
|
|
458
67
|
|
|
459
68
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package net.siteed.audiostream
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import androidx.core.content.ContextCompat
|
|
6
|
+
|
|
7
|
+
class PermissionUtils(private val context: Context) {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Checks if the recording permission has been granted.
|
|
11
|
+
* @return Boolean indicating whether the RECORD_AUDIO permission is granted.
|
|
12
|
+
*/
|
|
13
|
+
fun checkRecordingPermission(): Boolean {
|
|
14
|
+
return ContextCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -33,7 +33,11 @@ export interface AudioStreamStatus {
|
|
|
33
33
|
interval: number;
|
|
34
34
|
mimeType: string;
|
|
35
35
|
}
|
|
36
|
-
export
|
|
36
|
+
export type EncodingType = "pcm_16bit" | "pcm_8bit";
|
|
37
|
+
export interface RecordingConfig {
|
|
38
|
+
sampleRate?: 16000 | 44100 | 48000;
|
|
39
|
+
channels?: 1 | 2;
|
|
40
|
+
encoding?: EncodingType;
|
|
37
41
|
interval?: number;
|
|
38
42
|
}
|
|
39
43
|
//# sourceMappingURL=ExpoAudioStream.types.d.ts.map
|
|
@@ -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,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,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,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,
|
|
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,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,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,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,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 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 StartAudioStreamResult {\n fileUri: string;\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
|
|
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 StartAudioStreamResult {\n fileUri: string;\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 type EncodingType = \"pcm_16bit\" | \"pcm_8bit\";\n\nexport interface RecordingConfig {\n sampleRate?: 16000 | 44100 | 48000;\n channels?: 1 | 2; // 1 or 2 MONO or STEREO\n encoding?: EncodingType;\n interval?: number;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "expo-modules-core";
|
|
2
|
-
import { AudioStreamResult,
|
|
2
|
+
import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
|
|
3
3
|
declare class ExpoAudioStreamWeb extends EventEmitter {
|
|
4
4
|
mediaRecorder: MediaRecorder | null;
|
|
5
5
|
audioChunks: Blob[];
|
|
@@ -15,7 +15,7 @@ declare class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
15
15
|
streamUuid: string | null;
|
|
16
16
|
constructor();
|
|
17
17
|
getMediaStream(): Promise<MediaStream>;
|
|
18
|
-
startRecording(options?:
|
|
18
|
+
startRecording(options?: RecordingConfig): Promise<StartAudioStreamResult>;
|
|
19
19
|
setupRecordingListeners(): void;
|
|
20
20
|
emitAudioEvent({ data, position }: {
|
|
21
21
|
data: Blob;
|
|
@@ -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,
|
|
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,eAAe,EACf,sBAAsB,EACvB,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,eAAoB;IAwBlD,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"}
|
|
@@ -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 StartAudioStreamResult,\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 const streamConfig: StartAudioStreamResult = {\n fileUri,\n mimeType: \"audio/webm\",\n };\n return streamConfig;\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"]}
|
|
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 RecordingConfig,\n StartAudioStreamResult,\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: RecordingConfig = {}) {\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 const streamConfig: StartAudioStreamResult = {\n fileUri,\n mimeType: \"audio/webm\",\n };\n return streamConfig;\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
|
@@ -3,6 +3,7 @@ import { AudioEventPayload } from "./ExpoAudioStream.types";
|
|
|
3
3
|
import { useAudioRecorder, UseAudioRecorderState, AudioDataEvent } from "./useAudioRecording";
|
|
4
4
|
export declare function listAudioFiles(): Promise<string[]>;
|
|
5
5
|
export declare function clearAudioFiles(): Promise<void>;
|
|
6
|
+
export declare function test(): void;
|
|
6
7
|
export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): Subscription;
|
|
7
8
|
export type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };
|
|
8
9
|
export { useAudioRecorder };
|
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,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,CAGd;AAED,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,cAAc,EAAE,CAAC;AACzE,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,IAAI,IAAI,IAAI,CAE3B;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,YAAY,CAGd;AAED,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,cAAc,EAAE,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -8,6 +8,9 @@ export function listAudioFiles() {
|
|
|
8
8
|
export function clearAudioFiles() {
|
|
9
9
|
return ExpoAudioStreamModule.clearAudioFiles();
|
|
10
10
|
}
|
|
11
|
+
export function test() {
|
|
12
|
+
return ExpoAudioStreamModule.test();
|
|
13
|
+
}
|
|
11
14
|
export function addAudioEventListener(listener) {
|
|
12
15
|
console.log(`addAudioEventListener`, listener);
|
|
13
16
|
return emitter.addListener("AudioData", listener);
|