@siteed/expo-audio-stream 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,459 +1,68 @@
1
1
  package net.siteed.audiostream
2
2
 
3
- import android.Manifest
4
- import android.annotation.SuppressLint
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.core.content.ContextCompat
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
- const val AUDIO_EVENT_NAME = "AudioData"
29
- const val DEFAULT_SAMPLE_RATE = 16000 // Default sample rate for audio recording
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
- class ExpoAudioStreamModule() : Module() {
34
- private var audioRecord: AudioRecord? = null
35
- private var sampleRateInHz = DEFAULT_SAMPLE_RATE
36
- private var channelConfig = DEFAULT_CHANNEL_CONFIG
37
- private var audioFormat = DEFAULT_AUDIO_FORMAT
38
- private var bufferSizeInBytes: Int = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
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
- @SuppressLint("MissingPermission")
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
- Events(AUDIO_EVENT_NAME)
23
+ // Initialize AudioRecorderManager
24
+ initializeManager()
66
25
 
67
- AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
68
- configureRecording(options)
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
- // Ensure you update this to check if audioFile is null or not
90
- val fileSize = audioFile?.length() ?: 0
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
- try {
223
- FileOutputStream(audioFile, true).use { fos ->
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
- Log.d("AudioRecorderModule", "Stopping recording state = ${audioRecord?.state}")
269
- if (audioRecord != null && audioRecord!!.state == AudioRecord.STATE_INITIALIZED) {
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
- // Emit audio data at defined intervals
339
- if (SystemClock.elapsedRealtime() - lastEmitTime >= interval) {
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
- private fun emitAudioData(audioData: ByteArray, length: Int) {
381
- // Encode the part of the buffer that contains new audio data.
382
- val encodedBuffer = encodeAudioData(audioData)
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
- mainHandler.post {
392
- try {
393
- this@ExpoAudioStreamModule.sendEvent(AUDIO_EVENT_NAME, bundleOf(
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
- private fun checkPermission(): Boolean {
414
- val reactContext = appContext.reactContext ?: return false // If reactContext is null, permissions cannot be checked
415
- return ContextCompat.checkSelfPermission(reactContext, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
416
- }
417
-
418
- private fun pauseRecording(promise: Promise) {
419
- if (isRecording.get() && !isPaused.get()) {
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
- this.audioRecord = recorder // Properly assign the recorder to the class member
455
- return true
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 interface RecordingOptions {
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,gBAAgB;IAK/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,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 interface RecordingOptions {\n // TODO align Android and IOS options\n // sampleRate?: number;\n // channelConfig?: number; // numberOfChannel\n // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)\n interval?: number;\n}\n"]}
1
+ {"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface AudioEventPayload {\n encoded?: string;\n buffer?: Blob;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n}\n\nexport interface AudioStreamResult {\n fileUri: string;\n duration: number;\n size: number;\n mimeType: string;\n channels?: number;\n bitDepth?: number;\n sampleRate?: number;\n}\n\nexport interface 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, RecordingOptions, StartAudioStreamResult } from "./ExpoAudioStream.types";
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?: RecordingOptions): Promise<StartAudioStreamResult>;
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,gBAAgB,EAChB,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,gBAAqB;IAwBnD,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
+ {"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 };
@@ -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);