@siteed/expo-audio-stream 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +43 -392
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +16 -0
- package/build/ExpoAudioStream.types.d.ts +12 -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 +5 -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 +5 -4
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +38 -27
- package/build/useAudioRecording.js.map +1 -1
- package/ios/AudioStreamManager.swift +46 -16
- package/ios/ExpoAudioStreamModule.swift +2 -2
- package/package.json +1 -1
- package/src/ExpoAudioStream.types.ts +14 -5
- package/src/ExpoAudioStreamModule.web.ts +8 -3
- package/src/index.ts +4 -0
- package/src/useAudioRecording.ts +48 -34
|
@@ -1,417 +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
|
|
3
|
+
import android.os.Build
|
|
4
|
+
import android.os.Bundle
|
|
9
5
|
import android.util.Log
|
|
10
|
-
import androidx.
|
|
11
|
-
import
|
|
6
|
+
import androidx.annotation.RequiresApi
|
|
7
|
+
import expo.modules.kotlin.Promise
|
|
12
8
|
import expo.modules.kotlin.modules.Module
|
|
13
9
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
14
|
-
import expo.modules.kotlin.Promise
|
|
15
|
-
import android.util.Base64
|
|
16
|
-
import android.os.Handler
|
|
17
|
-
import android.os.SystemClock
|
|
18
|
-
import java.io.ByteArrayOutputStream
|
|
19
|
-
import android.os.Looper
|
|
20
|
-
import expo.modules.core.interfaces.Function
|
|
21
|
-
import java.util.concurrent.atomic.AtomicBoolean
|
|
22
|
-
import java.io.File
|
|
23
|
-
import java.io.FileOutputStream
|
|
24
|
-
import java.io.IOException
|
|
25
|
-
import java.io.RandomAccessFile
|
|
26
|
-
|
|
27
|
-
const val AUDIO_EVENT_NAME = "AudioData"
|
|
28
|
-
const val DEFAULT_SAMPLE_RATE = 16000 // Default sample rate for audio recording
|
|
29
|
-
const val DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
|
|
30
|
-
const val DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
|
|
31
|
-
|
|
32
|
-
class ExpoAudioStreamModule() : Module() {
|
|
33
|
-
private var audioRecord: AudioRecord? = null
|
|
34
|
-
private var sampleRateInHz = DEFAULT_SAMPLE_RATE
|
|
35
|
-
private var channelConfig = DEFAULT_CHANNEL_CONFIG
|
|
36
|
-
private var audioFormat = DEFAULT_AUDIO_FORMAT
|
|
37
|
-
private var bufferSizeInBytes: Int = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
|
|
38
|
-
private var isRecording = AtomicBoolean(false)
|
|
39
|
-
private val isPaused = AtomicBoolean(false)
|
|
40
|
-
private var streamUuid: String? = null
|
|
41
|
-
private var audioFile: File? = null
|
|
42
|
-
private var recordingThread: Thread? = null
|
|
43
|
-
private var recordingStartTime: Long = 0
|
|
44
|
-
private var totalRecordedTime: Long = 0
|
|
45
|
-
private var totalDataSize = 0
|
|
46
|
-
private val audioDataBuffer = ByteArrayOutputStream()
|
|
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
|
-
|
|
57
|
-
@SuppressLint("MissingPermission")
|
|
58
|
-
override fun definition() = ModuleDefinition {
|
|
59
|
-
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
|
60
|
-
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
61
|
-
// The module will be accessible from `requireNativeModule('ExpoAudioStream')` in JavaScript.
|
|
62
|
-
Name("ExpoAudioStream")
|
|
63
|
-
|
|
64
|
-
Events(AUDIO_EVENT_NAME)
|
|
65
|
-
|
|
66
|
-
AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
|
|
67
|
-
configureRecording(options)
|
|
68
|
-
startRecording(options, promise)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Function("clearAudioFiles") {
|
|
72
|
-
clearAudioStorage()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
Function("status") {
|
|
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
|
|
82
|
-
|
|
83
|
-
bundleOf(
|
|
84
|
-
"duration" to duration,
|
|
85
|
-
"isRecording" to isRecording.get(),
|
|
86
|
-
"isPaused" to isPaused.get(),
|
|
87
|
-
"mime" to mimeType,
|
|
88
|
-
"size" to totalDataSize,
|
|
89
|
-
"interval" to interval,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
AsyncFunction("listAudioFiles") { promise: Promise ->
|
|
94
|
-
try {
|
|
95
|
-
val fileList = listAudioFiles()
|
|
96
|
-
promise.resolve(fileList)
|
|
97
|
-
} catch (e: Exception) {
|
|
98
|
-
promise.reject("ERROR_LIST_FILES", "Failed to list audio files", e)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
AsyncFunction("pauseRecording") { promise: Promise ->
|
|
103
|
-
Log.d("AudioRecorderModule", "Pausing recording")
|
|
104
|
-
pauseRecording(promise)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
AsyncFunction("stopRecording") { promise: Promise ->
|
|
108
|
-
Log.d("AudioRecorderModule", "Stopping recording")
|
|
109
|
-
stopRecording(promise)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Method to write WAV file header
|
|
114
|
-
private fun writeWavHeader(out: FileOutputStream) {
|
|
115
|
-
val header = ByteArray(44)
|
|
116
|
-
val byteRate = sampleRateInHz * channels * bitDepth / 8
|
|
117
|
-
val blockAlign = channels * bitDepth / 8
|
|
118
|
-
|
|
119
|
-
// RIFF/WAVE header
|
|
120
|
-
"RIFF".toByteArray().copyInto(header, 0)
|
|
121
|
-
header[4] = 0 // Size will be updated later
|
|
122
|
-
"WAVE".toByteArray().copyInto(header, 8)
|
|
123
|
-
"fmt ".toByteArray().copyInto(header, 12)
|
|
124
10
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
header[20] = 1 // Audio format 1 for PCM (not compressed)
|
|
128
|
-
header[22] = channels.toByte()
|
|
129
|
-
header[24] = (sampleRateInHz and 0xff).toByte()
|
|
130
|
-
header[25] = (sampleRateInHz shr 8 and 0xff).toByte()
|
|
131
|
-
header[26] = (sampleRateInHz shr 16 and 0xff).toByte()
|
|
132
|
-
header[27] = (sampleRateInHz shr 24 and 0xff).toByte()
|
|
133
|
-
header[28] = (byteRate and 0xff).toByte()
|
|
134
|
-
header[29] = (byteRate shr 8 and 0xff).toByte()
|
|
135
|
-
header[30] = (byteRate shr 16 and 0xff).toByte()
|
|
136
|
-
header[31] = (byteRate shr 24 and 0xff).toByte()
|
|
137
|
-
header[32] = blockAlign.toByte()
|
|
138
|
-
header[34] = bitDepth.toByte()
|
|
139
|
-
"data".toByteArray().copyInto(header, 36)
|
|
11
|
+
class ExpoAudioStreamModule() : Module(), EventSender {
|
|
12
|
+
private lateinit var audioRecorderManager: AudioRecorderManager
|
|
140
13
|
|
|
141
|
-
|
|
142
|
-
|
|
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")
|
|
143
20
|
|
|
144
|
-
|
|
145
|
-
sampleRateInHz = (params["sampleRate"] as? Int) ?: DEFAULT_SAMPLE_RATE
|
|
146
|
-
channelConfig = (params["channelConfig"] as? Int) ?: DEFAULT_CHANNEL_CONFIG
|
|
147
|
-
audioFormat = (params["audioFormat"] as? Int) ?: DEFAULT_AUDIO_FORMAT
|
|
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
|
|
151
|
-
}
|
|
21
|
+
Events(Constants.AUDIO_EVENT_NAME)
|
|
152
22
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Filter to include only .wav files
|
|
156
|
-
val files = filesDir?.listFiles { file ->
|
|
157
|
-
file.isFile && file.name.endsWith(".wav")
|
|
158
|
-
}?.map { it.absolutePath } ?: listOf() // Use `listOf()` to return an empty list if null
|
|
159
|
-
return files
|
|
160
|
-
}
|
|
23
|
+
// Initialize AudioRecorderManager
|
|
24
|
+
initializeManager()
|
|
161
25
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (!checkPermission()) {
|
|
165
|
-
promise.reject("PERMISSION_DENIED", "Recording permission has not been granted", null)
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (isRecording.get() && !isPaused.get()) {
|
|
170
|
-
promise.reject("ALREADY_RECORDING", "Recording is already in progress", null)
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
val intervalOption = options["interval"]
|
|
175
|
-
if (intervalOption != null) {
|
|
176
|
-
Log.d("AudioRecorderModule", "Setting interval to $intervalOption")
|
|
177
|
-
if (intervalOption is Number) {
|
|
178
|
-
val intervalValue = intervalOption.toLong()
|
|
179
|
-
if (intervalValue < 100) {
|
|
180
|
-
promise.reject("INVALID_INTERVAL", "Interval must be at least 100 ms", null)
|
|
181
|
-
return
|
|
182
|
-
} else {
|
|
183
|
-
this.interval = intervalValue
|
|
26
|
+
AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
|
|
27
|
+
audioRecorderManager.startRecording(options, promise)
|
|
184
28
|
}
|
|
185
|
-
} else {
|
|
186
|
-
promise.reject("INVALID_INTERVAL", "Interval must be a number", null)
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Log for new recording or resuming
|
|
192
|
-
if (!isPaused.get()) {
|
|
193
|
-
streamUuid = java.util.UUID.randomUUID().toString()
|
|
194
|
-
audioFile = File(appContext.reactContext?.filesDir, "audio_${streamUuid}.wav")
|
|
195
|
-
Log.i("AudioRecorderModule", "Starting new recording $streamUuid with sample rate: $sampleRateInHz, channel config: $channelConfig, audio format: $audioFormat, buffer size: $bufferSizeInBytes, interval: $interval")
|
|
196
|
-
|
|
197
|
-
} else {
|
|
198
|
-
Log.i("AudioRecorderModule", "Resuming recording")
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Initialize the recorder if it's a new recording
|
|
202
|
-
if (!isPaused.get() && !initializeRecorder()) {
|
|
203
|
-
promise.reject("INITIALIZATION_FAILED", "AudioRecord initialization failed", null)
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
FileOutputStream(audioFile, true).use { fos ->
|
|
209
|
-
writeWavHeader(fos)
|
|
210
|
-
}
|
|
211
|
-
} catch (e: IOException) {
|
|
212
|
-
promise.reject("FILE_CREATION_FAILED", "Failed to create audio file with WAV header", null)
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
audioRecord?.startRecording()
|
|
217
|
-
isPaused.set(false)
|
|
218
|
-
isRecording.set(true)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!isPaused.get()) {
|
|
222
|
-
recordingStartTime = System.currentTimeMillis() // Only reset start time if it's not a resume
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
recordingThread = Thread { recordingProcess() }.apply { start() }
|
|
226
|
-
promise.resolve(audioFile?.toURI().toString())
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private fun stopRecording(promise: Promise) {
|
|
230
|
-
if (!isRecording.get()) {
|
|
231
|
-
promise.reject("NOT_RECORDING", "Recording is not active", null)
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
audioRecord?.stop()
|
|
237
|
-
audioRecord?.release()
|
|
238
29
|
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
245
|
-
|
|
246
|
-
// Create result bundle
|
|
247
|
-
val result = bundleOf(
|
|
248
|
-
"fileUri" to audioFile?.toURI().toString(),
|
|
249
|
-
"duration" to duration,
|
|
250
|
-
"channels" to channels,
|
|
251
|
-
"bitDepth" to bitDepth,
|
|
252
|
-
"sampleRate" to sampleRateInHz,
|
|
253
|
-
"size" to fileSize,
|
|
254
|
-
"mimeType" to mimeType
|
|
255
|
-
)
|
|
256
|
-
promise.resolve(result)
|
|
257
|
-
|
|
258
|
-
// Reset the timing variables
|
|
259
|
-
isRecording.set(false)
|
|
260
|
-
isPaused.set(false)
|
|
261
|
-
totalRecordedTime = 0
|
|
262
|
-
pausedDuration = 0
|
|
263
|
-
} catch (e: Exception) {
|
|
264
|
-
promise.reject("STOP_FAILED", "Failed to stop recording", e)
|
|
265
|
-
} finally {
|
|
266
|
-
audioRecord = null
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private fun recordingProcess() {
|
|
271
|
-
FileOutputStream(audioFile, true).use { fos ->
|
|
272
|
-
// Buffer to accumulate data
|
|
273
|
-
val accumulatedAudioData = ByteArrayOutputStream()
|
|
274
|
-
|
|
275
|
-
// Write audio data directly to the file
|
|
276
|
-
val audioData = ByteArray(bufferSizeInBytes)
|
|
277
|
-
while (isRecording.get()) {
|
|
278
|
-
if (!isPaused.get()) {
|
|
279
|
-
val bytesRead = audioRecord?.read(audioData, 0, bufferSizeInBytes) ?: -1
|
|
280
|
-
if (bytesRead < 0) {
|
|
281
|
-
Log.e("AudioRecorderModule", "Read error: $bytesRead")
|
|
282
|
-
break
|
|
283
|
-
}
|
|
284
|
-
if (bytesRead > 0) {
|
|
285
|
-
fos.write(audioData, 0, bytesRead)
|
|
286
|
-
totalDataSize += bytesRead
|
|
287
|
-
accumulatedAudioData.write(audioData, 0, bytesRead)
|
|
288
|
-
|
|
289
|
-
// Emit audio data at defined intervals
|
|
290
|
-
if (SystemClock.elapsedRealtime() - lastEmitTime >= interval) {
|
|
291
|
-
emitAudioData(accumulatedAudioData.toByteArray(), accumulatedAudioData.size())
|
|
292
|
-
lastEmitTime = SystemClock.elapsedRealtime() // Reset the timer
|
|
293
|
-
accumulatedAudioData.reset() // Clear the accumulator
|
|
294
|
-
}
|
|
295
|
-
}
|
|
30
|
+
Function("clearAudioFiles") {
|
|
31
|
+
audioRecorderManager.clearAudioStorage()
|
|
296
32
|
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
updateWavHeader() // Update the header with the correct file size after recording stops
|
|
300
|
-
}
|
|
301
33
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
val fileSize = raf.length()
|
|
306
|
-
val dataSize = fileSize - 44 // Subtract the header size
|
|
307
|
-
raf.seek(4) // Skip 'RIFF' label
|
|
308
|
-
|
|
309
|
-
// Write correct file size, excluding the first 8 bytes of the RIFF header
|
|
310
|
-
raf.writeInt(Integer.reverseBytes((dataSize + 36).toInt()))
|
|
311
|
-
|
|
312
|
-
raf.seek(40) // Go to the data size position
|
|
313
|
-
raf.writeInt(Integer.reverseBytes(dataSize.toInt())) // Write the size of the data segment
|
|
314
|
-
}
|
|
315
|
-
} catch (e: IOException) {
|
|
316
|
-
Log.e("AudioRecorderModule", "Could not update WAV header", e)
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private fun clearAudioStorage() {
|
|
321
|
-
// Clear all files in the app's internal storage
|
|
322
|
-
val filesDir = appContext.reactContext?.filesDir
|
|
323
|
-
filesDir?.listFiles()?.forEach {
|
|
324
|
-
Log.d("AudioRecorderModule", "Deleting file: ${it.absolutePath}")
|
|
325
|
-
it.delete()
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private fun emitAudioData(audioData: ByteArray, length: Int) {
|
|
330
|
-
// Since audioData now contains the actual bytes read, you do not need to read from audioDataBuffer.
|
|
331
|
-
// Encode the part of the buffer that contains new audio data.
|
|
332
|
-
val encodedBuffer = encodeAudioData(audioData)
|
|
333
|
-
val fileSize = audioFile?.length() ?: 0 // Update file size information
|
|
334
|
-
val from = lastEmittedSize
|
|
335
|
-
val deltaSize = fileSize - lastEmittedSize
|
|
336
|
-
lastEmittedSize = fileSize // Update last emitted size
|
|
337
|
-
|
|
338
|
-
// Calculate position in milliseconds
|
|
339
|
-
val positionInMs = (from * 1000) / (sampleRateInHz * channels * (bitDepth / 8))
|
|
340
|
-
|
|
341
|
-
mainHandler.post {
|
|
342
|
-
try {
|
|
343
|
-
this@ExpoAudioStreamModule.sendEvent(AUDIO_EVENT_NAME, bundleOf(
|
|
344
|
-
"fileUri" to audioFile?.toURI().toString(),
|
|
345
|
-
"lastEmittedSize" to from,
|
|
346
|
-
"encoded" to encodedBuffer,
|
|
347
|
-
"deltaSize" to length,
|
|
348
|
-
"position" to positionInMs,
|
|
349
|
-
"mimeType" to mimeType,
|
|
350
|
-
"totalSize" to fileSize,
|
|
351
|
-
"streamUuid" to streamUuid
|
|
352
|
-
))
|
|
353
|
-
} catch (e: Exception) {
|
|
354
|
-
Log.e("AudioRecorderModule", "Failed to send event", e)
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
// audioDataBuffer.reset() is no longer needed here as we do not use the buffer to store ongoing data
|
|
358
|
-
}
|
|
34
|
+
Function("status") {
|
|
35
|
+
return@Function audioRecorderManager.getStatus()
|
|
36
|
+
}
|
|
359
37
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
38
|
+
AsyncFunction("listAudioFiles") { promise: Promise ->
|
|
39
|
+
audioRecorderManager.listAudioFiles(promise)
|
|
40
|
+
}
|
|
363
41
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
FileOutputStream(audioFile, true).use { output ->
|
|
368
|
-
// Write rawData to the file.
|
|
369
|
-
output.write(rawData)
|
|
370
|
-
}
|
|
371
|
-
true
|
|
372
|
-
} catch (e: IOException) {
|
|
373
|
-
// Handle exceptions here
|
|
374
|
-
Log.e("AudioRecorderModule", "Could not write to file: ${audioFile?.absolutePath}", e)
|
|
375
|
-
false
|
|
376
|
-
}
|
|
377
|
-
}
|
|
42
|
+
AsyncFunction("pauseRecording") { promise: Promise ->
|
|
43
|
+
audioRecorderManager.pauseRecording(promise)
|
|
44
|
+
}
|
|
378
45
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
46
|
+
AsyncFunction("resumeRecording") { promise: Promise ->
|
|
47
|
+
audioRecorderManager.resumeRecording(promise)
|
|
48
|
+
}
|
|
383
49
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
lastPauseTime = System.currentTimeMillis() // Record the time when the recording was paused
|
|
388
|
-
isPaused.set(true)
|
|
389
|
-
promise.resolve("Recording paused")
|
|
390
|
-
} else {
|
|
391
|
-
promise.reject("NOT_RECORDING_OR_ALREADY_PAUSED", "Recording is either not active or already paused", null)
|
|
50
|
+
AsyncFunction("stopRecording") { promise: Promise ->
|
|
51
|
+
audioRecorderManager.stopRecording(promise)
|
|
52
|
+
}
|
|
392
53
|
}
|
|
393
|
-
}
|
|
394
54
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
Log.e("AudioRecorderModule", "Invalid buffer size")
|
|
403
|
-
return false
|
|
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)
|
|
404
62
|
}
|
|
405
63
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
Log.e("AudioRecorderModule", "AudioRecord initialization failed")
|
|
409
|
-
recorder.release() // Clean up resources if initialization fails
|
|
410
|
-
return false
|
|
64
|
+
override fun sendExpoEvent(eventName: String, params: Bundle) {
|
|
65
|
+
this@ExpoAudioStreamModule.sendEvent(Constants.AUDIO_EVENT_NAME, params)
|
|
411
66
|
}
|
|
412
67
|
|
|
413
|
-
this.audioRecord = recorder // Properly assign the recorder to the class member
|
|
414
|
-
return true
|
|
415
|
-
}
|
|
416
|
-
|
|
417
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
|
+
}
|
|
@@ -18,6 +18,13 @@ export interface AudioStreamResult {
|
|
|
18
18
|
bitDepth?: number;
|
|
19
19
|
sampleRate?: number;
|
|
20
20
|
}
|
|
21
|
+
export interface StartAudioStreamResult {
|
|
22
|
+
fileUri: string;
|
|
23
|
+
mimeType: string;
|
|
24
|
+
channels?: number;
|
|
25
|
+
bitDepth?: number;
|
|
26
|
+
sampleRate?: number;
|
|
27
|
+
}
|
|
21
28
|
export interface AudioStreamStatus {
|
|
22
29
|
isRecording: boolean;
|
|
23
30
|
isPaused: boolean;
|
|
@@ -26,7 +33,11 @@ export interface AudioStreamStatus {
|
|
|
26
33
|
interval: number;
|
|
27
34
|
mimeType: string;
|
|
28
35
|
}
|
|
29
|
-
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;
|
|
30
41
|
interval?: number;
|
|
31
42
|
}
|
|
32
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,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 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"}
|
|
@@ -63,7 +63,11 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
63
63
|
this.lastEmittedTime = 0;
|
|
64
64
|
this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
|
|
65
65
|
const fileUri = `${this.streamUuid}.webm`;
|
|
66
|
-
|
|
66
|
+
const streamConfig = {
|
|
67
|
+
fileUri,
|
|
68
|
+
mimeType: "audio/webm",
|
|
69
|
+
};
|
|
70
|
+
return streamConfig;
|
|
67
71
|
}
|
|
68
72
|
// Setup listeners for the MediaRecorder
|
|
69
73
|
setupRecordingListeners() {
|
|
@@ -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 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"]}
|
|
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);
|
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,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,CAAC,GAAG,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;IAC/C,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 console.log(`addAudioEventListener`, listener);\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };\nexport { useAudioRecorder };\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAC5D,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,OAAO,qBAAqB,CAAC,cAAc,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,qBAAqB,CAAC,eAAe,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,qBAAqB,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;IAC/C,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 test(): void {\n return ExpoAudioStreamModule.test();\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>,\n): Subscription {\n console.log(`addAudioEventListener`, listener);\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport type { AudioEventPayload, UseAudioRecorderState, AudioDataEvent };\nexport { useAudioRecorder };\n"]}
|