Skip to content

Commit d54dfb3

Browse files
authored
Merge ffa0ce9 into 80c1fbd
2 parents 80c1fbd + ffa0ce9 commit d54dfb3

File tree

3 files changed

+22
-18
lines changed

3 files changed

+22
-18
lines changed

firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/android.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package com.google.firebase.ai.common.util
1919
import android.media.AudioRecord
2020
import kotlin.time.Duration.Companion.milliseconds
2121
import kotlinx.coroutines.delay
22+
import kotlinx.coroutines.flow.callbackFlow
2223
import kotlinx.coroutines.flow.flow
24+
import kotlinx.coroutines.isActive
2325
import kotlinx.coroutines.yield
2426

2527
/**
@@ -35,20 +37,18 @@ internal val AudioRecord.minBufferSize: Int
3537
*
3638
* Will yield when this instance is not recording.
3739
*/
38-
internal fun AudioRecord.readAsFlow() = flow {
40+
internal fun AudioRecord.readAsFlow() = callbackFlow {
3941
val buffer = ByteArray(minBufferSize)
4042

41-
while (true) {
43+
while (isActive) {
4244
if (recordingState != AudioRecord.RECORDSTATE_RECORDING) {
43-
// TODO(vguthal): Investigate if both yield and delay are required.
44-
delay(10.milliseconds)
45-
yield()
45+
delay(0)
4646
continue
4747
}
4848
val bytesRead = read(buffer, 0, buffer.size)
4949
if (bytesRead > 0) {
50-
emit(buffer.copyOf(bytesRead))
50+
send(buffer.copyOf(bytesRead))
5151
}
52-
yield()
52+
delay(0)
5353
}
5454
}

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
3333
import io.ktor.websocket.Frame
3434
import io.ktor.websocket.close
3535
import io.ktor.websocket.readBytes
36+
import kotlinx.coroutines.CoroutineName
3637
import java.util.concurrent.ConcurrentLinkedQueue
3738
import java.util.concurrent.atomic.AtomicBoolean
3839
import kotlin.coroutines.CoroutineContext
3940
import kotlinx.coroutines.CoroutineScope
4041
import kotlinx.coroutines.Dispatchers
4142
import kotlinx.coroutines.cancel
4243
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
44+
import kotlinx.coroutines.delay
4345
import kotlinx.coroutines.flow.Flow
4446
import kotlinx.coroutines.flow.buffer
4547
import kotlinx.coroutines.flow.catch
@@ -49,7 +51,6 @@ import kotlinx.coroutines.flow.onCompletion
4951
import kotlinx.coroutines.flow.onEach
5052
import kotlinx.coroutines.isActive
5153
import kotlinx.coroutines.launch
52-
import kotlinx.coroutines.yield
5354
import kotlinx.serialization.ExperimentalSerializationApi
5455
import kotlinx.serialization.Serializable
5556
import kotlinx.serialization.encodeToString
@@ -120,7 +121,6 @@ internal constructor(
120121
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)? = null,
121122
enableInterruptions: Boolean = false,
122123
) {
123-
124124
val context = firebaseApp.applicationContext
125125
if (
126126
ContextCompat.checkSelfPermission(context, RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
@@ -137,8 +137,8 @@ internal constructor(
137137
)
138138
return@catchAsync
139139
}
140-
141-
scope = CoroutineScope(blockingDispatcher + childJob())
140+
// TODO: maybe it should be THREAD_PRIORITY_AUDIO anyways for playback and recording (not network though)
141+
scope = CoroutineScope(blockingDispatcher + childJob() + CoroutineName("LiveSession Scope"))
142142
audioHelper = AudioHelper.build()
143143

144144
recordUserAudio()
@@ -201,7 +201,7 @@ internal constructor(
201201
)
202202
}
203203
?.let { emit(it.toPublic()) }
204-
yield()
204+
delay(0)
205205
}
206206
}
207207
.onCompletion { stopAudioConversation() }
@@ -326,7 +326,10 @@ internal constructor(
326326
?.listenToRecording()
327327
?.buffer(UNLIMITED)
328328
?.accumulateUntil(MIN_BUFFER_SIZE)
329-
?.onEach { sendMediaStream(listOf(MediaData(it, "audio/pcm"))) }
329+
?.onEach {
330+
sendMediaStream(listOf(MediaData(it, "audio/pcm")))
331+
delay(0)
332+
}
330333
?.catch { throw FirebaseAIException.from(it) }
331334
?.launchIn(scope)
332335
}
@@ -374,7 +377,7 @@ internal constructor(
374377
if (it.interrupted) {
375378
playBackQueue.clear()
376379
} else {
377-
println("Sending audio parts")
380+
println("Queuing audio parts from model")
378381
val audioParts = it.content?.parts?.filterIsInstance<InlineDataPart>().orEmpty()
379382
for (part in audioParts) {
380383
playBackQueue.add(part.inlineData)
@@ -390,7 +393,7 @@ internal constructor(
390393
}
391394
}
392395
}
393-
.launchIn(CoroutineScope(Dispatchers.IO))
396+
.launchIn(scope)
394397
}
395398

396399
/**
@@ -401,7 +404,7 @@ internal constructor(
401404
* Launched asynchronously on [scope].
402405
*/
403406
private fun listenForModelPlayback(enableInterruptions: Boolean = false) {
404-
CoroutineScope(Dispatchers.IO).launch {
407+
scope.launch {
405408
while (isActive) {
406409
val playbackData = playBackQueue.poll()
407410
if (playbackData == null) {
@@ -410,8 +413,9 @@ internal constructor(
410413
if (!enableInterruptions) {
411414
audioHelper?.resumeRecording()
412415
}
413-
yield()
416+
delay(0)
414417
} else {
418+
println("Playing audio data")
415419
/**
416420
* We pause the recording while the model is speaking to avoid interrupting it because of
417421
* no echo cancellation

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ benchmarkMacro = "1.3.4"
1616
browser = "1.3.0"
1717
cardview = "1.0.0"
1818
constraintlayout = "2.1.4"
19-
coroutines = "1.9.0"
19+
coroutines = "1.10.2"
2020
dagger = "2.51" # Don't bump above 2.51 as it causes a bug in AppDistro FeedbackSender JPEG code
2121
datastore = "1.1.7"
2222
dexmaker = "2.28.1"

0 commit comments

Comments
 (0)