@qafka/react-native 2.3.3 → 2.3.4
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,8 +1,11 @@
|
|
|
1
1
|
package com.qafka.audio
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
4
5
|
import android.content.pm.PackageManager
|
|
6
|
+
import android.media.AudioAttributes
|
|
5
7
|
import android.media.AudioFormat
|
|
8
|
+
import android.media.AudioManager
|
|
6
9
|
import android.media.AudioRecord
|
|
7
10
|
import android.media.AudioTrack
|
|
8
11
|
import android.media.MediaRecorder
|
|
@@ -33,6 +36,11 @@ class QafkaAudioModule(reactContext: ReactApplicationContext) :
|
|
|
33
36
|
private var noiseSuppressor: NoiseSuppressor? = null
|
|
34
37
|
private var automaticGainControl: AutomaticGainControl? = null
|
|
35
38
|
|
|
39
|
+
// Audio-mode state saved on startCapture and restored on stopCapture so the
|
|
40
|
+
// host app's audio routing is left exactly as we found it.
|
|
41
|
+
private var previousAudioMode: Int? = null
|
|
42
|
+
private var previousSpeakerphoneOn: Boolean? = null
|
|
43
|
+
|
|
36
44
|
companion object {
|
|
37
45
|
private const val TAG = "QafkaAudio"
|
|
38
46
|
private const val CAPTURE_SAMPLE_RATE = 16000
|
|
@@ -110,6 +118,24 @@ class QafkaAudioModule(reactContext: ReactApplicationContext) :
|
|
|
110
118
|
|
|
111
119
|
val bufferSize = maxOf(minBufferSize, 4096)
|
|
112
120
|
|
|
121
|
+
// iOS VPIO equivalent (part 1): put the audio system into communication
|
|
122
|
+
// mode and route both capture and playback through the voice path.
|
|
123
|
+
// Without this the AudioTrack below plays on the media path, which the
|
|
124
|
+
// VOICE_COMMUNICATION capture's echo canceler has no reference to — so
|
|
125
|
+
// the AI's own playback leaks back into the mic and it talks to itself.
|
|
126
|
+
try {
|
|
127
|
+
val audioManager =
|
|
128
|
+
reactApplicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
129
|
+
previousAudioMode = audioManager.mode
|
|
130
|
+
previousSpeakerphoneOn = audioManager.isSpeakerphoneOn
|
|
131
|
+
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
132
|
+
@Suppress("DEPRECATION")
|
|
133
|
+
audioManager.isSpeakerphoneOn = true
|
|
134
|
+
dlogI("✅ AudioManager mode=IN_COMMUNICATION, speakerphone=on")
|
|
135
|
+
} catch (e: Exception) {
|
|
136
|
+
dlogW("⚠️ Failed to set communication audio mode: ${e.message}")
|
|
137
|
+
}
|
|
138
|
+
|
|
113
139
|
try {
|
|
114
140
|
val record = AudioRecord(
|
|
115
141
|
MediaRecorder.AudioSource.VOICE_COMMUNICATION,
|
|
@@ -164,7 +190,16 @@ class QafkaAudioModule(reactContext: ReactApplicationContext) :
|
|
|
164
190
|
)
|
|
165
191
|
val playBufferSize = maxOf(playMinBuffer, 4096)
|
|
166
192
|
|
|
193
|
+
// iOS VPIO equivalent (part 2): play on the voice-communication path
|
|
194
|
+
// (USAGE_VOICE_COMMUNICATION + CONTENT_TYPE_SPEECH) so the echo
|
|
195
|
+
// canceler bound to the capture session can reference and cancel it.
|
|
167
196
|
val track = AudioTrack.Builder()
|
|
197
|
+
.setAudioAttributes(
|
|
198
|
+
AudioAttributes.Builder()
|
|
199
|
+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
|
200
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
201
|
+
.build()
|
|
202
|
+
)
|
|
168
203
|
.setAudioFormat(
|
|
169
204
|
AudioFormat.Builder()
|
|
170
205
|
.setSampleRate(PLAYBACK_SAMPLE_RATE)
|
|
@@ -247,6 +282,19 @@ class QafkaAudioModule(reactContext: ReactApplicationContext) :
|
|
|
247
282
|
} catch (_: Exception) {}
|
|
248
283
|
audioTrack = null
|
|
249
284
|
|
|
285
|
+
// Restore the host app's audio mode / routing exactly as we found it.
|
|
286
|
+
try {
|
|
287
|
+
val audioManager =
|
|
288
|
+
reactApplicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
289
|
+
previousAudioMode?.let { audioManager.mode = it }
|
|
290
|
+
previousSpeakerphoneOn?.let {
|
|
291
|
+
@Suppress("DEPRECATION")
|
|
292
|
+
audioManager.isSpeakerphoneOn = it
|
|
293
|
+
}
|
|
294
|
+
} catch (_: Exception) {}
|
|
295
|
+
previousAudioMode = null
|
|
296
|
+
previousSpeakerphoneOn = null
|
|
297
|
+
|
|
250
298
|
promise.resolve(true)
|
|
251
299
|
}
|
|
252
300
|
|
package/package.json
CHANGED