opuslib 0.1.4 → 0.2.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/CHANGELOG.md +107 -0
- package/README.md +140 -9
- package/android/src/main/cpp/opus_jni_wrapper.cpp +34 -0
- package/android/src/main/java/expo/modules/opuslib/AudioProcessor.kt +273 -0
- package/android/src/main/java/expo/modules/opuslib/AudioRecordManager.kt +66 -149
- package/android/src/main/java/expo/modules/opuslib/OpusEncoder.kt +14 -0
- package/android/src/main/java/expo/modules/opuslib/OpuslibModule.kt +47 -5
- package/build/Opuslib.types.d.ts +96 -2
- package/build/Opuslib.types.d.ts.map +1 -1
- package/build/Opuslib.types.js.map +1 -1
- package/build/OpuslibModule.d.ts +28 -1
- package/build/OpuslibModule.d.ts.map +1 -1
- package/build/OpuslibModule.js +25 -0
- package/build/OpuslibModule.js.map +1 -1
- package/build/OpuslibModule.web.d.ts +6 -0
- package/build/OpuslibModule.web.d.ts.map +1 -1
- package/build/OpuslibModule.web.js +6 -0
- package/build/OpuslibModule.web.js.map +1 -1
- package/ios/AudioEngineManager.swift +137 -168
- package/ios/AudioProcessor.swift +246 -0
- package/ios/OpusCtlHelpers.h +8 -0
- package/ios/OpusCtlHelpers.m +4 -0
- package/ios/OpusEncoder.swift +13 -0
- package/ios/OpuslibModule.swift +55 -6
- package/package.json +1 -1
- package/src/Opuslib.types.ts +106 -2
- package/src/OpuslibModule.ts +55 -2
- package/src/OpuslibModule.web.ts +8 -0
|
@@ -6,19 +6,16 @@ import android.media.AudioRecord
|
|
|
6
6
|
import android.media.MediaRecorder
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import java.io.File
|
|
9
|
-
import java.io.FileOutputStream
|
|
10
|
-
import java.nio.ByteBuffer
|
|
11
|
-
import java.nio.ByteOrder
|
|
12
9
|
import kotlin.concurrent.thread
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
|
-
* AudioRecordManager - Manages AudioRecord for real-time audio capture
|
|
12
|
+
* AudioRecordManager - Manages AudioRecord for real-time audio capture.
|
|
16
13
|
*
|
|
17
|
-
* This class
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
14
|
+
* This class is responsible only for audio capture: it owns the AudioRecord
|
|
15
|
+
* lifecycle and runs the PCM read loop. All Opus 1.6.1 encoding is delegated to
|
|
16
|
+
* AudioProcessor, which runs on a separate dedicated encoding thread. The
|
|
17
|
+
* capture thread calls processor.pushSamples(), which copies the PCM and posts
|
|
18
|
+
* it to the encoding thread, so the read loop never blocks on encoding.
|
|
22
19
|
*/
|
|
23
20
|
class AudioRecordManager(
|
|
24
21
|
private val context: Context,
|
|
@@ -32,28 +29,21 @@ class AudioRecordManager(
|
|
|
32
29
|
private var audioRecord: AudioRecord? = null
|
|
33
30
|
private var recordingThread: Thread? = null
|
|
34
31
|
|
|
35
|
-
//
|
|
36
|
-
private var
|
|
32
|
+
// Encoding processor (owns the encoder, runs on a separate HandlerThread)
|
|
33
|
+
private var processor: AudioProcessor? = null
|
|
37
34
|
|
|
38
35
|
// State
|
|
39
36
|
private var isRecording = false
|
|
40
37
|
private var isPaused = false
|
|
41
|
-
private var sequenceNumber = 0
|
|
42
38
|
private var loggedFirstBuffer = false
|
|
43
39
|
|
|
44
|
-
// Frame accumulation for packet duration
|
|
45
|
-
private val frameBuffer = mutableListOf<ShortArray>()
|
|
46
|
-
private val framesPerPacket: Int = (config.packetDuration / config.frameSize).toInt()
|
|
47
|
-
|
|
48
40
|
// Event callbacks
|
|
49
|
-
private var onAudioChunk: ((
|
|
41
|
+
private var onAudioChunk: ((List<EncodedFrame>, Double, Int, Double, Int) -> Unit)? = null
|
|
42
|
+
private var onStarted: ((timestamp: Double, sampleRate: Int, channels: Int, bitrate: Int, frameSize: Double, preSkip: Int) -> Unit)? = null
|
|
43
|
+
private var onEnd: ((timestamp: Double, totalDuration: Double, totalPackets: Int) -> Unit)? = null
|
|
50
44
|
private var onAmplitude: ((Float, Float, Double) -> Unit)? = null
|
|
51
45
|
private var onError: ((Exception) -> Unit)? = null
|
|
52
46
|
|
|
53
|
-
// Debug file output
|
|
54
|
-
private var pcmFileOutputStream: FileOutputStream? = null
|
|
55
|
-
private var pcmFile: File? = null
|
|
56
|
-
|
|
57
47
|
// MARK: - Public Methods
|
|
58
48
|
|
|
59
49
|
fun start() {
|
|
@@ -61,16 +51,6 @@ class AudioRecordManager(
|
|
|
61
51
|
throw AudioStreamException("ALREADY_STREAMING", "Already recording")
|
|
62
52
|
}
|
|
63
53
|
|
|
64
|
-
// Create Opus encoder with DRED support
|
|
65
|
-
val dredDuration = config.dredDuration
|
|
66
|
-
opusEncoder = OpusEncoder(
|
|
67
|
-
sampleRate = config.sampleRate,
|
|
68
|
-
channels = config.channels,
|
|
69
|
-
bitrate = config.bitrate,
|
|
70
|
-
frameSizeMs = config.frameSize,
|
|
71
|
-
dredDurationMs = dredDuration
|
|
72
|
-
)
|
|
73
|
-
|
|
74
54
|
// Calculate buffer size
|
|
75
55
|
val samplesPerFrame = (config.sampleRate * config.frameSize / 1000.0).toInt()
|
|
76
56
|
val bufferSize = samplesPerFrame * 2 // 2 bytes per sample (Int16)
|
|
@@ -118,33 +98,48 @@ class AudioRecordManager(
|
|
|
118
98
|
)
|
|
119
99
|
}
|
|
120
100
|
|
|
121
|
-
// Create
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
pcmFileOutputStream = FileOutputStream(pcmFile)
|
|
127
|
-
Log.d(TAG, "Debug PCM file created: ${pcmFile?.absolutePath}")
|
|
128
|
-
} catch (e: Exception) {
|
|
129
|
-
Log.e(TAG, "Failed to create debug file: ${e.message}")
|
|
130
|
-
}
|
|
101
|
+
// Create and start AudioProcessor (encoding thread). It owns the encoder and
|
|
102
|
+
// emits audioStarted (with preSkip) once the encoder is ready.
|
|
103
|
+
val proc = AudioProcessor(config)
|
|
104
|
+
proc.setOnAudioChunk { frames, timestamp, seq, duration, frameCount ->
|
|
105
|
+
onAudioChunk?.invoke(frames, timestamp, seq, duration, frameCount)
|
|
131
106
|
}
|
|
107
|
+
proc.setOnStarted { timestamp, sampleRate, channels, bitrate, frameSize, preSkip ->
|
|
108
|
+
onStarted?.invoke(timestamp, sampleRate, channels, bitrate, frameSize, preSkip)
|
|
109
|
+
}
|
|
110
|
+
proc.setOnEnd { timestamp, totalDuration, totalPackets ->
|
|
111
|
+
onEnd?.invoke(timestamp, totalDuration, totalPackets)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Debug PCM file (written on the encoding thread, per frame)
|
|
115
|
+
val debugFile = if (config.saveDebugAudio) {
|
|
116
|
+
val timestamp = System.currentTimeMillis()
|
|
117
|
+
File(context.filesDir, "debug_pcm_$timestamp.raw").also {
|
|
118
|
+
Log.d(TAG, "Debug PCM file: ${it.absolutePath}")
|
|
119
|
+
}
|
|
120
|
+
} else null
|
|
121
|
+
|
|
122
|
+
proc.start(debugFile)
|
|
123
|
+
processor = proc
|
|
132
124
|
|
|
133
125
|
// Start recording
|
|
134
126
|
try {
|
|
135
127
|
record.startRecording()
|
|
136
128
|
} catch (e: Exception) {
|
|
129
|
+
// Tear down the encoding thread we just started before bailing out
|
|
130
|
+
proc.flushAndStop()
|
|
131
|
+
processor = null
|
|
137
132
|
throw AudioStreamException("AUDIO_RECORD_ERROR", "Failed to start recording: ${e.message}")
|
|
138
133
|
}
|
|
139
134
|
|
|
140
135
|
isRecording = true
|
|
141
136
|
|
|
142
|
-
// Start
|
|
137
|
+
// Start capture thread — only reads PCM and pushes to the processor
|
|
143
138
|
recordingThread = thread(start = true, name = "AudioRecordThread") {
|
|
144
|
-
|
|
139
|
+
captureLoop(record, samplesPerFrame)
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
Log.d(TAG, "Started recording: ${config.sampleRate}Hz, ${config.channels}ch, DRED: ${dredDuration}ms")
|
|
142
|
+
Log.d(TAG, "Started recording: ${config.sampleRate}Hz, ${config.channels}ch, DRED: ${config.dredDuration}ms")
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
fun stop() {
|
|
@@ -154,7 +149,7 @@ class AudioRecordManager(
|
|
|
154
149
|
|
|
155
150
|
isRecording = false
|
|
156
151
|
|
|
157
|
-
// Stop
|
|
152
|
+
// Stop AudioRecord
|
|
158
153
|
audioRecord?.let { record ->
|
|
159
154
|
try {
|
|
160
155
|
if (record.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
|
|
@@ -165,26 +160,20 @@ class AudioRecordManager(
|
|
|
165
160
|
}
|
|
166
161
|
}
|
|
167
162
|
|
|
168
|
-
// Wait for
|
|
163
|
+
// Wait for capture thread to finish
|
|
169
164
|
recordingThread?.join(1000)
|
|
170
165
|
recordingThread = null
|
|
171
166
|
|
|
172
|
-
//
|
|
167
|
+
// Flush and stop the encoding thread (synchronous — drains remaining
|
|
168
|
+
// samples, pads the final partial frame with silence, emits audioEnd).
|
|
169
|
+
processor?.flushAndStop()
|
|
170
|
+
processor = null
|
|
171
|
+
|
|
172
|
+
// Release AudioRecord
|
|
173
173
|
audioRecord?.release()
|
|
174
174
|
audioRecord = null
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
opusEncoder = null
|
|
178
|
-
|
|
179
|
-
frameBuffer.clear()
|
|
180
|
-
sequenceNumber = 0
|
|
181
|
-
|
|
182
|
-
// Close debug file
|
|
183
|
-
pcmFileOutputStream?.close()
|
|
184
|
-
pcmFileOutputStream = null
|
|
185
|
-
if (pcmFile != null) {
|
|
186
|
-
Log.d(TAG, "Closed PCM debug file: ${pcmFile?.absolutePath}")
|
|
187
|
-
}
|
|
176
|
+
loggedFirstBuffer = false
|
|
188
177
|
|
|
189
178
|
Log.d(TAG, "Stopped recording")
|
|
190
179
|
}
|
|
@@ -201,10 +190,18 @@ class AudioRecordManager(
|
|
|
201
190
|
|
|
202
191
|
// MARK: - Event Handlers
|
|
203
192
|
|
|
204
|
-
fun setOnAudioChunk(callback: (
|
|
193
|
+
fun setOnAudioChunk(callback: (List<EncodedFrame>, Double, Int, Double, Int) -> Unit) {
|
|
205
194
|
this.onAudioChunk = callback
|
|
206
195
|
}
|
|
207
196
|
|
|
197
|
+
fun setOnStarted(callback: (timestamp: Double, sampleRate: Int, channels: Int, bitrate: Int, frameSize: Double, preSkip: Int) -> Unit) {
|
|
198
|
+
this.onStarted = callback
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun setOnEnd(callback: (timestamp: Double, totalDuration: Double, totalPackets: Int) -> Unit) {
|
|
202
|
+
this.onEnd = callback
|
|
203
|
+
}
|
|
204
|
+
|
|
208
205
|
fun setOnAmplitude(callback: (Float, Float, Double) -> Unit) {
|
|
209
206
|
this.onAmplitude = callback
|
|
210
207
|
}
|
|
@@ -213,12 +210,12 @@ class AudioRecordManager(
|
|
|
213
210
|
this.onError = callback
|
|
214
211
|
}
|
|
215
212
|
|
|
216
|
-
// MARK: -
|
|
213
|
+
// MARK: - Capture thread (only reads PCM, no encoding)
|
|
217
214
|
|
|
218
|
-
private fun
|
|
215
|
+
private fun captureLoop(record: AudioRecord, samplesPerFrame: Int) {
|
|
219
216
|
val buffer = ShortArray(samplesPerFrame)
|
|
220
217
|
|
|
221
|
-
Log.d(TAG, "
|
|
218
|
+
Log.d(TAG, "Capture thread started, frame size: $samplesPerFrame samples")
|
|
222
219
|
|
|
223
220
|
while (isRecording) {
|
|
224
221
|
try {
|
|
@@ -232,7 +229,7 @@ class AudioRecordManager(
|
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
if (samplesRead == 0) {
|
|
235
|
-
// No data available,
|
|
232
|
+
// No data available, wait briefly and retry
|
|
236
233
|
Thread.sleep(10)
|
|
237
234
|
continue
|
|
238
235
|
}
|
|
@@ -248,99 +245,19 @@ class AudioRecordManager(
|
|
|
248
245
|
continue
|
|
249
246
|
}
|
|
250
247
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
val byteBuffer = ByteBuffer.allocate(samplesRead * 2)
|
|
254
|
-
byteBuffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
255
|
-
for (i in 0 until samplesRead) {
|
|
256
|
-
byteBuffer.putShort(buffer[i])
|
|
257
|
-
}
|
|
258
|
-
fos.write(byteBuffer.array())
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Process the buffer
|
|
262
|
-
processBuffer(buffer.copyOf(samplesRead))
|
|
248
|
+
// Copy + post to the encoding thread (never blocks the capture loop)
|
|
249
|
+
processor?.pushSamples(buffer, samplesRead)
|
|
263
250
|
|
|
264
251
|
} catch (e: InterruptedException) {
|
|
265
|
-
Log.d(TAG, "
|
|
252
|
+
Log.d(TAG, "Capture thread interrupted")
|
|
266
253
|
break
|
|
267
254
|
} catch (e: Exception) {
|
|
268
|
-
Log.e(TAG, "Error in
|
|
255
|
+
Log.e(TAG, "Error in capture loop: ${e.message}", e)
|
|
269
256
|
onError?.invoke(e)
|
|
270
257
|
break
|
|
271
258
|
}
|
|
272
259
|
}
|
|
273
260
|
|
|
274
|
-
Log.d(TAG, "
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private fun processBuffer(pcmData: ShortArray) {
|
|
278
|
-
// Add to frame buffer
|
|
279
|
-
frameBuffer.add(pcmData)
|
|
280
|
-
|
|
281
|
-
// Calculate how many samples we need for one packet
|
|
282
|
-
val samplesPerPacket = (config.sampleRate * config.packetDuration / 1000.0).toInt()
|
|
283
|
-
val currentSampleCount = frameBuffer.sumOf { it.size }
|
|
284
|
-
|
|
285
|
-
// When we have enough samples for a packet, encode and send
|
|
286
|
-
if (currentSampleCount >= samplesPerPacket) {
|
|
287
|
-
encodeAndSendPacket()
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private fun encodeAndSendPacket() {
|
|
292
|
-
val encoder = opusEncoder ?: return
|
|
293
|
-
|
|
294
|
-
// Flatten frame buffer into continuous PCM data
|
|
295
|
-
val pcmData = ShortArray(frameBuffer.sumOf { it.size })
|
|
296
|
-
var offset = 0
|
|
297
|
-
for (frame in frameBuffer) {
|
|
298
|
-
frame.copyInto(pcmData, offset)
|
|
299
|
-
offset += frame.size
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Calculate samples per frame
|
|
303
|
-
val samplesPerFrame = (config.sampleRate * config.frameSize / 1000.0).toInt()
|
|
304
|
-
|
|
305
|
-
// We should only encode when we have at least one frame
|
|
306
|
-
if (pcmData.size < samplesPerFrame) {
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Take only ONE frame worth of samples
|
|
311
|
-
val frameData = pcmData.copyOfRange(0, samplesPerFrame)
|
|
312
|
-
|
|
313
|
-
// Encode this single frame to Opus (with DRED padding if enabled)
|
|
314
|
-
val opusData = try {
|
|
315
|
-
encoder.encode(frameData, samplesPerFrame)
|
|
316
|
-
} catch (e: Exception) {
|
|
317
|
-
Log.e(TAG, "Failed to encode Opus packet: ${e.message}")
|
|
318
|
-
frameBuffer.clear()
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (opusData == null || opusData.isEmpty()) {
|
|
323
|
-
Log.w(TAG, "Failed to encode Opus packet (null or empty)")
|
|
324
|
-
frameBuffer.clear()
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Calculate timestamp in milliseconds
|
|
329
|
-
val timestampMs = System.currentTimeMillis().toDouble()
|
|
330
|
-
|
|
331
|
-
// Emit audioChunk event with Opus packet (may be larger due to DRED)
|
|
332
|
-
onAudioChunk?.invoke(opusData, timestampMs, sequenceNumber)
|
|
333
|
-
|
|
334
|
-
sequenceNumber++
|
|
335
|
-
|
|
336
|
-
// Keep any remaining samples for next packet
|
|
337
|
-
val remainingSamples = pcmData.size - samplesPerFrame
|
|
338
|
-
if (remainingSamples > 0) {
|
|
339
|
-
val remaining = pcmData.copyOfRange(samplesPerFrame, pcmData.size)
|
|
340
|
-
frameBuffer.clear()
|
|
341
|
-
frameBuffer.add(remaining)
|
|
342
|
-
} else {
|
|
343
|
-
frameBuffer.clear()
|
|
344
|
-
}
|
|
261
|
+
Log.d(TAG, "Capture thread stopped")
|
|
345
262
|
}
|
|
346
263
|
}
|
|
@@ -35,6 +35,14 @@ class OpusEncoder(
|
|
|
35
35
|
val frameSize: Int = (sampleRate * frameSizeMs / 1000.0).toInt()
|
|
36
36
|
private var encoderPtr: Long = 0
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Encoder lookahead (pre-skip) in samples. Decoders should skip this many
|
|
40
|
+
* samples at the start of the stream to account for the encoder's
|
|
41
|
+
* algorithmic delay.
|
|
42
|
+
*/
|
|
43
|
+
var preSkip: Int = 0
|
|
44
|
+
private set
|
|
45
|
+
|
|
38
46
|
init {
|
|
39
47
|
// Create native encoder
|
|
40
48
|
encoderPtr = nativeCreate(sampleRate, channels, bitrate, dredDurationMs)
|
|
@@ -42,12 +50,16 @@ class OpusEncoder(
|
|
|
42
50
|
throw RuntimeException("Failed to create Opus encoder")
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
// Read encoder lookahead (pre-skip)
|
|
54
|
+
preSkip = nativeGetLookahead(encoderPtr)
|
|
55
|
+
|
|
45
56
|
Log.i(TAG, """
|
|
46
57
|
Opus encoder initialized:
|
|
47
58
|
- Sample rate: ${sampleRate}Hz
|
|
48
59
|
- Channels: $channels
|
|
49
60
|
- Bitrate: ${bitrate / 1000}kbps
|
|
50
61
|
- Frame size: $frameSize samples (${frameSizeMs}ms)
|
|
62
|
+
- Pre-skip: $preSkip samples
|
|
51
63
|
- DRED: ${dredDurationMs}ms
|
|
52
64
|
""".trimIndent())
|
|
53
65
|
}
|
|
@@ -100,4 +112,6 @@ class OpusEncoder(
|
|
|
100
112
|
): ByteArray?
|
|
101
113
|
|
|
102
114
|
private external fun nativeDestroy(encoderPtr: Long)
|
|
115
|
+
|
|
116
|
+
private external fun nativeGetLookahead(encoderPtr: Long): Int
|
|
103
117
|
}
|
|
@@ -23,7 +23,7 @@ class OpuslibModule : Module() {
|
|
|
23
23
|
Name("Opuslib")
|
|
24
24
|
|
|
25
25
|
// Events
|
|
26
|
-
Events("audioChunk", "amplitude", "error")
|
|
26
|
+
Events("audioChunk", "amplitude", "audioStarted", "audioEnd", "error")
|
|
27
27
|
|
|
28
28
|
// Start streaming method
|
|
29
29
|
AsyncFunction("startStreaming") { config: AudioConfig ->
|
|
@@ -79,13 +79,43 @@ class OpuslibModule : Module() {
|
|
|
79
79
|
val manager = AudioRecordManager(context, config)
|
|
80
80
|
android.util.Log.d(TAG, "✅ AudioRecordManager created")
|
|
81
81
|
|
|
82
|
-
// Set up event callbacks
|
|
82
|
+
// Set up event callbacks — audioStarted/audioEnd come from the encoding thread
|
|
83
83
|
android.util.Log.d(TAG, "🔗 Setting up event callbacks...")
|
|
84
|
-
manager.setOnAudioChunk {
|
|
84
|
+
manager.setOnAudioChunk { frames, timestamp, sequenceNumber, duration, frameCount ->
|
|
85
|
+
// Each frame is an independent Opus packet wrapped in { data, audioLevel? }.
|
|
86
|
+
val frameObjects = frames.map { frame ->
|
|
87
|
+
val obj = mutableMapOf<String, Any>("data" to frame.data)
|
|
88
|
+
frame.audioLevel?.let { obj["audioLevel"] = it }
|
|
89
|
+
obj
|
|
90
|
+
}
|
|
91
|
+
// `data` is kept for backward compatibility: the FIRST frame's Opus packet,
|
|
92
|
+
// sent in the same ByteArray representation existing consumers already read.
|
|
85
93
|
sendEvent("audioChunk", mapOf(
|
|
86
|
-
"data" to data,
|
|
94
|
+
"data" to frames.first().data,
|
|
95
|
+
"frames" to frameObjects,
|
|
87
96
|
"timestamp" to timestamp,
|
|
88
|
-
"sequenceNumber" to sequenceNumber
|
|
97
|
+
"sequenceNumber" to sequenceNumber,
|
|
98
|
+
"duration" to duration,
|
|
99
|
+
"frameCount" to frameCount
|
|
100
|
+
))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
manager.setOnStarted { timestamp, sampleRate, channels, bitrate, frameSize, preSkip ->
|
|
104
|
+
sendEvent("audioStarted", mapOf(
|
|
105
|
+
"timestamp" to timestamp,
|
|
106
|
+
"sampleRate" to sampleRate,
|
|
107
|
+
"channels" to channels,
|
|
108
|
+
"bitrate" to bitrate,
|
|
109
|
+
"frameSize" to frameSize,
|
|
110
|
+
"preSkip" to preSkip
|
|
111
|
+
))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
manager.setOnEnd { timestamp, totalDuration, totalPackets ->
|
|
115
|
+
sendEvent("audioEnd", mapOf(
|
|
116
|
+
"timestamp" to timestamp,
|
|
117
|
+
"totalDuration" to totalDuration,
|
|
118
|
+
"totalPackets" to totalPackets
|
|
89
119
|
))
|
|
90
120
|
}
|
|
91
121
|
|
|
@@ -121,6 +151,7 @@ class OpuslibModule : Module() {
|
|
|
121
151
|
return
|
|
122
152
|
}
|
|
123
153
|
|
|
154
|
+
// stop() flushes the encoding thread and emits audioEnd before tearing down.
|
|
124
155
|
audioRecordManager?.stop()
|
|
125
156
|
audioRecordManager = null
|
|
126
157
|
isStreaming = false
|
|
@@ -169,9 +200,16 @@ class AudioConfig : Record {
|
|
|
169
200
|
@Field
|
|
170
201
|
var frameSize: Double = 20.0
|
|
171
202
|
|
|
203
|
+
// Legacy field, still accepted from existing callers. Superseded by
|
|
204
|
+
// framesPerCallback; kept so existing configs continue to deserialize.
|
|
172
205
|
@Field
|
|
173
206
|
var packetDuration: Double = 20.0
|
|
174
207
|
|
|
208
|
+
// Number of independent Opus frames batched into one audioChunk event.
|
|
209
|
+
// Defaults to 1 to match the previous one-frame-per-event behavior.
|
|
210
|
+
@Field
|
|
211
|
+
var framesPerCallback: Int = 1
|
|
212
|
+
|
|
175
213
|
@Field
|
|
176
214
|
var dredDuration: Int = 100 // NEW: DRED recovery duration in ms
|
|
177
215
|
|
|
@@ -181,6 +219,10 @@ class AudioConfig : Record {
|
|
|
181
219
|
@Field
|
|
182
220
|
var amplitudeEventInterval: Double = 16.0
|
|
183
221
|
|
|
222
|
+
// Per-frame audio level (RMS-derived, 0.0..1.0) attached to each frame.
|
|
223
|
+
@Field
|
|
224
|
+
var enableAudioLevel: Boolean = false
|
|
225
|
+
|
|
184
226
|
@Field
|
|
185
227
|
var saveDebugAudio: Boolean = false
|
|
186
228
|
}
|
package/build/Opuslib.types.d.ts
CHANGED
|
@@ -12,25 +12,90 @@ export interface AudioConfig {
|
|
|
12
12
|
frameSize: number;
|
|
13
13
|
/** Packet duration in milliseconds (typically 20-100ms) */
|
|
14
14
|
packetDuration: number;
|
|
15
|
+
/**
|
|
16
|
+
* Number of independently-encoded Opus frames to batch into a single
|
|
17
|
+
* `audioChunk` event (default 1). Each entry in `AudioChunkEvent.frames` is a
|
|
18
|
+
* complete, independently decodable Opus packet — frames are never
|
|
19
|
+
* concatenated. Batching reduces the number of bridge calls.
|
|
20
|
+
*/
|
|
21
|
+
framesPerCallback?: number;
|
|
15
22
|
/** DRED recovery duration in milliseconds (0-100, default 100) - NEW in Opus 1.6 */
|
|
16
23
|
dredDuration?: number;
|
|
17
24
|
/** Enable amplitude events for waveform visualization */
|
|
18
25
|
enableAmplitudeEvents?: boolean;
|
|
19
26
|
/** Amplitude event interval in milliseconds (default 16) */
|
|
20
27
|
amplitudeEventInterval?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Enable per-frame audio level calculation (default false). When enabled,
|
|
30
|
+
* each `OpusFrame` carries an `audioLevel` (0.0 - 1.0) derived from RMS.
|
|
31
|
+
* Disabled by default to save computation.
|
|
32
|
+
*/
|
|
33
|
+
enableAudioLevel?: boolean;
|
|
21
34
|
/** Save debug PCM audio to file (development only) */
|
|
22
35
|
saveDebugAudio?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* iOS AudioSession configuration (iOS only; ignored on Android and web).
|
|
38
|
+
* Omit to keep the default recording session
|
|
39
|
+
* (`record` category, `measurement` mode, no options).
|
|
40
|
+
*/
|
|
41
|
+
iosAudioSession?: IOSAudioSessionConfig;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* iOS `AVAudioSession` configuration. iOS only — ignored on Android and web.
|
|
45
|
+
*/
|
|
46
|
+
export interface IOSAudioSessionConfig {
|
|
47
|
+
/**
|
|
48
|
+
* `AVAudioSession.Category`
|
|
49
|
+
* - `record`: pure recording (default)
|
|
50
|
+
* - `playAndRecord`: record and play simultaneously
|
|
51
|
+
* - `playback`: playback only
|
|
52
|
+
* - `ambient`: mix with other audio without interrupting it
|
|
53
|
+
*/
|
|
54
|
+
category: 'record' | 'playAndRecord' | 'playback' | 'ambient';
|
|
55
|
+
/**
|
|
56
|
+
* `AVAudioSession.Mode`
|
|
57
|
+
* - `measurement`: disable system audio processing (default)
|
|
58
|
+
* - `default`: enable system audio processing (AGC, echo cancellation)
|
|
59
|
+
* - `voiceChat`: optimized for voice calls
|
|
60
|
+
* - `spokenAudio`: optimized for spoken content
|
|
61
|
+
*/
|
|
62
|
+
mode: 'default' | 'voiceChat' | 'measurement' | 'spokenAudio';
|
|
63
|
+
/** `AVAudioSession.CategoryOptions` (combinable) */
|
|
64
|
+
options?: ('mixWithOthers' | 'defaultToSpeaker' | 'allowBluetooth' | 'allowAirPlay' | 'allowBluetoothA2DP')[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A single Opus frame — one complete `opus_encode()` output with its own TOC
|
|
68
|
+
* byte, decodable on its own.
|
|
69
|
+
*/
|
|
70
|
+
export interface OpusFrame {
|
|
71
|
+
/** Opus-encoded packet data (independent, decodable) */
|
|
72
|
+
data: ArrayBuffer;
|
|
73
|
+
/** Per-frame audio level (0.0 - 1.0). Present only when `enableAudioLevel` is true. */
|
|
74
|
+
audioLevel?: number;
|
|
23
75
|
}
|
|
24
76
|
/**
|
|
25
77
|
* Audio chunk event payload (Opus-encoded data)
|
|
26
78
|
*/
|
|
27
79
|
export interface AudioChunkEvent {
|
|
28
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* The first frame's Opus packet, kept for backward compatibility — equivalent
|
|
82
|
+
* to `frames[0].data`. With the default `framesPerCallback` of 1 this is the
|
|
83
|
+
* single packet for the event. Prefer `frames` for new code.
|
|
84
|
+
*/
|
|
29
85
|
data: ArrayBuffer;
|
|
86
|
+
/**
|
|
87
|
+
* Independently decodable Opus packets in this event (one per encoded frame).
|
|
88
|
+
* Contains a single entry unless `framesPerCallback` > 1.
|
|
89
|
+
*/
|
|
90
|
+
frames: OpusFrame[];
|
|
30
91
|
/** Timestamp in milliseconds */
|
|
31
92
|
timestamp: number;
|
|
32
|
-
/** Sequence number (increments with each
|
|
93
|
+
/** Sequence number (increments with each event) */
|
|
33
94
|
sequenceNumber: number;
|
|
95
|
+
/** Duration of all frames in milliseconds (`frameSize * frameCount`) */
|
|
96
|
+
duration: number;
|
|
97
|
+
/** Number of Opus frames in this event (= `frames.length`) */
|
|
98
|
+
frameCount: number;
|
|
34
99
|
}
|
|
35
100
|
/**
|
|
36
101
|
* Amplitude event payload (for waveform visualization)
|
|
@@ -43,6 +108,35 @@ export interface AmplitudeEvent {
|
|
|
43
108
|
/** Timestamp in milliseconds */
|
|
44
109
|
timestamp: number;
|
|
45
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Audio started event payload. Emitted once when streaming begins.
|
|
113
|
+
*/
|
|
114
|
+
export interface AudioStartedEvent {
|
|
115
|
+
/** Timestamp in milliseconds when streaming started */
|
|
116
|
+
timestamp: number;
|
|
117
|
+
/** Actual sample rate in Hz */
|
|
118
|
+
sampleRate: number;
|
|
119
|
+
/** Number of channels */
|
|
120
|
+
channels: number;
|
|
121
|
+
/** Configured bitrate in bits/second */
|
|
122
|
+
bitrate: number;
|
|
123
|
+
/** Frame duration in milliseconds */
|
|
124
|
+
frameSize: number;
|
|
125
|
+
/** Opus encoder lookahead in samples — decoders should skip this many samples at the start */
|
|
126
|
+
preSkip: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Audio end event payload. Emitted once when streaming stops, after the final
|
|
130
|
+
* buffered audio has been flushed.
|
|
131
|
+
*/
|
|
132
|
+
export interface AudioEndEvent {
|
|
133
|
+
/** Timestamp in milliseconds when streaming stopped */
|
|
134
|
+
timestamp: number;
|
|
135
|
+
/** Total session duration in milliseconds */
|
|
136
|
+
totalDuration: number;
|
|
137
|
+
/** Total number of packets (audioChunk events) emitted during the session */
|
|
138
|
+
totalPackets: number;
|
|
139
|
+
}
|
|
46
140
|
/**
|
|
47
141
|
* Error event payload
|
|
48
142
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Opuslib.types.d.ts","sourceRoot":"","sources":["../src/Opuslib.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAA;IACtB,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,4DAA4D;IAC5D,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,sDAAsD;IACtD,cAAc,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Opuslib.types.d.ts","sourceRoot":"","sources":["../src/Opuslib.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,4DAA4D;IAC5D,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,sDAAsD;IACtD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAA;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,EAAE,QAAQ,GAAG,eAAe,GAAG,UAAU,GAAG,SAAS,CAAA;IAC7D;;;;;;OAMG;IACH,IAAI,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa,CAAA;IAC7D,oDAAoD;IACpD,OAAO,CAAC,EAAE,CACN,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,cAAc,GACd,oBAAoB,CACvB,EAAE,CAAA;CACJ;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,IAAI,EAAE,WAAW,CAAA;IACjB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,IAAI,EAAE,WAAW,CAAA;IACjB;;;OAGG;IACH,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Opuslib.types.js","sourceRoot":"","sources":["../src/Opuslib.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Audio configuration for Opus encoding\n */\nexport interface AudioConfig {\n /** Sample rate in Hz (8000, 12000, 16000, 24000, 48000) */\n sampleRate: number\n /** Number of channels (1 = mono, 2 = stereo) */\n channels: number\n /** Target bitrate in bits/second (e.g., 24000 for 24kbps) */\n bitrate: number\n /** Frame duration in milliseconds (2.5, 5, 10, 20, 40, 60) */\n frameSize: number\n /** Packet duration in milliseconds (typically 20-100ms) */\n packetDuration: number\n /** DRED recovery duration in milliseconds (0-100, default 100) - NEW in Opus 1.6 */\n dredDuration?: number\n /** Enable amplitude events for waveform visualization */\n enableAmplitudeEvents?: boolean\n /** Amplitude event interval in milliseconds (default 16) */\n amplitudeEventInterval?: number\n /** Save debug PCM audio to file (development only) */\n saveDebugAudio?: boolean\n}\n\n/**\n * Audio chunk event payload (Opus-encoded data)\n */\nexport interface AudioChunkEvent {\n
|
|
1
|
+
{"version":3,"file":"Opuslib.types.js","sourceRoot":"","sources":["../src/Opuslib.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Audio configuration for Opus encoding\n */\nexport interface AudioConfig {\n /** Sample rate in Hz (8000, 12000, 16000, 24000, 48000) */\n sampleRate: number\n /** Number of channels (1 = mono, 2 = stereo) */\n channels: number\n /** Target bitrate in bits/second (e.g., 24000 for 24kbps) */\n bitrate: number\n /** Frame duration in milliseconds (2.5, 5, 10, 20, 40, 60) */\n frameSize: number\n /** Packet duration in milliseconds (typically 20-100ms) */\n packetDuration: number\n /**\n * Number of independently-encoded Opus frames to batch into a single\n * `audioChunk` event (default 1). Each entry in `AudioChunkEvent.frames` is a\n * complete, independently decodable Opus packet — frames are never\n * concatenated. Batching reduces the number of bridge calls.\n */\n framesPerCallback?: number\n /** DRED recovery duration in milliseconds (0-100, default 100) - NEW in Opus 1.6 */\n dredDuration?: number\n /** Enable amplitude events for waveform visualization */\n enableAmplitudeEvents?: boolean\n /** Amplitude event interval in milliseconds (default 16) */\n amplitudeEventInterval?: number\n /**\n * Enable per-frame audio level calculation (default false). When enabled,\n * each `OpusFrame` carries an `audioLevel` (0.0 - 1.0) derived from RMS.\n * Disabled by default to save computation.\n */\n enableAudioLevel?: boolean\n /** Save debug PCM audio to file (development only) */\n saveDebugAudio?: boolean\n /**\n * iOS AudioSession configuration (iOS only; ignored on Android and web).\n * Omit to keep the default recording session\n * (`record` category, `measurement` mode, no options).\n */\n iosAudioSession?: IOSAudioSessionConfig\n}\n\n/**\n * iOS `AVAudioSession` configuration. iOS only — ignored on Android and web.\n */\nexport interface IOSAudioSessionConfig {\n /**\n * `AVAudioSession.Category`\n * - `record`: pure recording (default)\n * - `playAndRecord`: record and play simultaneously\n * - `playback`: playback only\n * - `ambient`: mix with other audio without interrupting it\n */\n category: 'record' | 'playAndRecord' | 'playback' | 'ambient'\n /**\n * `AVAudioSession.Mode`\n * - `measurement`: disable system audio processing (default)\n * - `default`: enable system audio processing (AGC, echo cancellation)\n * - `voiceChat`: optimized for voice calls\n * - `spokenAudio`: optimized for spoken content\n */\n mode: 'default' | 'voiceChat' | 'measurement' | 'spokenAudio'\n /** `AVAudioSession.CategoryOptions` (combinable) */\n options?: (\n | 'mixWithOthers'\n | 'defaultToSpeaker'\n | 'allowBluetooth'\n | 'allowAirPlay'\n | 'allowBluetoothA2DP'\n )[]\n}\n\n/**\n * A single Opus frame — one complete `opus_encode()` output with its own TOC\n * byte, decodable on its own.\n */\nexport interface OpusFrame {\n /** Opus-encoded packet data (independent, decodable) */\n data: ArrayBuffer\n /** Per-frame audio level (0.0 - 1.0). Present only when `enableAudioLevel` is true. */\n audioLevel?: number\n}\n\n/**\n * Audio chunk event payload (Opus-encoded data)\n */\nexport interface AudioChunkEvent {\n /**\n * The first frame's Opus packet, kept for backward compatibility — equivalent\n * to `frames[0].data`. With the default `framesPerCallback` of 1 this is the\n * single packet for the event. Prefer `frames` for new code.\n */\n data: ArrayBuffer\n /**\n * Independently decodable Opus packets in this event (one per encoded frame).\n * Contains a single entry unless `framesPerCallback` > 1.\n */\n frames: OpusFrame[]\n /** Timestamp in milliseconds */\n timestamp: number\n /** Sequence number (increments with each event) */\n sequenceNumber: number\n /** Duration of all frames in milliseconds (`frameSize * frameCount`) */\n duration: number\n /** Number of Opus frames in this event (= `frames.length`) */\n frameCount: number\n}\n\n/**\n * Amplitude event payload (for waveform visualization)\n */\nexport interface AmplitudeEvent {\n /** Root mean square amplitude (0.0 - 1.0) */\n rms: number\n /** Peak amplitude (0.0 - 1.0) */\n peak: number\n /** Timestamp in milliseconds */\n timestamp: number\n}\n\n/**\n * Audio started event payload. Emitted once when streaming begins.\n */\nexport interface AudioStartedEvent {\n /** Timestamp in milliseconds when streaming started */\n timestamp: number\n /** Actual sample rate in Hz */\n sampleRate: number\n /** Number of channels */\n channels: number\n /** Configured bitrate in bits/second */\n bitrate: number\n /** Frame duration in milliseconds */\n frameSize: number\n /** Opus encoder lookahead in samples — decoders should skip this many samples at the start */\n preSkip: number\n}\n\n/**\n * Audio end event payload. Emitted once when streaming stops, after the final\n * buffered audio has been flushed.\n */\nexport interface AudioEndEvent {\n /** Timestamp in milliseconds when streaming stopped */\n timestamp: number\n /** Total session duration in milliseconds */\n totalDuration: number\n /** Total number of packets (audioChunk events) emitted during the session */\n totalPackets: number\n}\n\n/**\n * Error event payload\n */\nexport interface ErrorEvent {\n /** Error code */\n code: string\n /** Error message */\n message: string\n}\n\n/**\n * Event subscription\n */\nexport interface Subscription {\n remove: () => void\n}\n"]}
|
package/build/OpuslibModule.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AudioConfig, AudioChunkEvent, AmplitudeEvent, ErrorEvent, Subscription } from './Opuslib.types';
|
|
1
|
+
import type { AudioConfig, AudioChunkEvent, AmplitudeEvent, AudioStartedEvent, AudioEndEvent, ErrorEvent, Subscription } from './Opuslib.types';
|
|
2
2
|
/**
|
|
3
3
|
* Opuslib - Opus 1.6.1 Audio Encoding with DRED Support
|
|
4
4
|
*
|
|
@@ -49,6 +49,14 @@ declare const _default: {
|
|
|
49
49
|
* websocket.send(event.data)
|
|
50
50
|
* })
|
|
51
51
|
*
|
|
52
|
+
* // Listen for session lifecycle
|
|
53
|
+
* Opuslib.addListener('audioStarted', (event) => {
|
|
54
|
+
* console.log('Started; decoder pre-skip:', event.preSkip)
|
|
55
|
+
* })
|
|
56
|
+
* Opuslib.addListener('audioEnd', (event) => {
|
|
57
|
+
* console.log(`Ended: ${event.totalPackets} packets in ${event.totalDuration}ms`)
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
52
60
|
* // Listen for errors
|
|
53
61
|
* const errorSub = Opuslib.addListener('error', (event) => {
|
|
54
62
|
* console.error('Error:', event.message)
|
|
@@ -62,6 +70,8 @@ declare const _default: {
|
|
|
62
70
|
addListener: {
|
|
63
71
|
(eventName: "audioChunk", listener: (event: AudioChunkEvent) => void): Subscription;
|
|
64
72
|
(eventName: "amplitude", listener: (event: AmplitudeEvent) => void): Subscription;
|
|
73
|
+
(eventName: "audioStarted", listener: (event: AudioStartedEvent) => void): Subscription;
|
|
74
|
+
(eventName: "audioEnd", listener: (event: AudioEndEvent) => void): Subscription;
|
|
65
75
|
(eventName: "error", listener: (event: ErrorEvent) => void): Subscription;
|
|
66
76
|
};
|
|
67
77
|
/**
|
|
@@ -71,6 +81,23 @@ declare const _default: {
|
|
|
71
81
|
* @returns Subscription object with remove() method
|
|
72
82
|
*/
|
|
73
83
|
addAmplitudeListener: (listener: (event: AmplitudeEvent) => void) => Subscription;
|
|
84
|
+
/**
|
|
85
|
+
* Listen for the `audioStarted` event, emitted once when streaming begins.
|
|
86
|
+
* Carries the active config and the Opus encoder `preSkip` (lookahead) so a
|
|
87
|
+
* decoder knows how many samples to skip at the start of the stream.
|
|
88
|
+
*
|
|
89
|
+
* @param listener Event listener callback
|
|
90
|
+
* @returns Subscription object with remove() method
|
|
91
|
+
*/
|
|
92
|
+
addAudioStartedListener: (listener: (event: AudioStartedEvent) => void) => Subscription;
|
|
93
|
+
/**
|
|
94
|
+
* Listen for the `audioEnd` event, emitted once when streaming stops (after
|
|
95
|
+
* the final buffered audio has been flushed). Carries the session summary.
|
|
96
|
+
*
|
|
97
|
+
* @param listener Event listener callback
|
|
98
|
+
* @returns Subscription object with remove() method
|
|
99
|
+
*/
|
|
100
|
+
addAudioEndListener: (listener: (event: AudioEndEvent) => void) => Subscription;
|
|
74
101
|
/**
|
|
75
102
|
* Listen for error events
|
|
76
103
|
*
|