Skip to content

Commit 39ed6de

Browse files
committed
2 separate bidi routes
1 parent fac0e9e commit 39ed6de

7 files changed

Lines changed: 126 additions & 87 deletions

File tree

firebase-ai/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ You can find the implementation for each feature by clicking on the links below:
5353
- [Summarize video](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt): Summarize a video and extract important dialogue.
5454

5555
### Live API (Real-time bidrectional streaming)
56-
- [ForecastTalk](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt): Use bidirectional streaming to get information about weather conditions
57-
- [Gemini Live (Video input)](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt): Use bidirectional streaming to chat with Gemini using your phone's camera
56+
- [ForecastTalk](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt): Use bidirectional streaming to get information about weather conditions
57+
- [Gemini Live (Video input)](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt): Use bidirectional streaming to chat with Gemini using your phone's camera
5858

5959
### Document (PDFs) analysis
6060
- [Document comparison (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt): Compare the contents of 2 documents in Cloud Storage.
Lines changed: 4 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,25 @@
11
package com.google.firebase.quickstart.ai.feature.live
22

3-
import kotlinx.serialization.Serializable
4-
53
import android.annotation.SuppressLint
64
import android.graphics.Bitmap
7-
import androidx.lifecycle.SavedStateHandle
85
import androidx.lifecycle.ViewModel
96
import androidx.lifecycle.viewModelScope
10-
import com.google.firebase.Firebase
11-
import com.google.firebase.ai.ai
127
import com.google.firebase.ai.type.FunctionCallPart
13-
import com.google.firebase.ai.type.FunctionDeclaration
148
import com.google.firebase.ai.type.FunctionResponsePart
15-
import com.google.firebase.ai.type.GenerativeBackend
169
import com.google.firebase.ai.type.InlineData
1710
import com.google.firebase.ai.type.LiveSession
1811
import com.google.firebase.ai.type.PublicPreviewAPI
19-
import com.google.firebase.ai.type.ResponseModality
20-
import com.google.firebase.ai.type.Schema
21-
import com.google.firebase.ai.type.SpeechConfig
22-
import com.google.firebase.ai.type.Tool
23-
import com.google.firebase.ai.type.Voice
24-
import com.google.firebase.ai.type.liveGenerationConfig
25-
import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather
2612
import kotlinx.coroutines.launch
27-
import kotlinx.coroutines.runBlocking
2813
import kotlinx.serialization.json.JsonObject
29-
import kotlinx.serialization.json.jsonPrimitive
3014
import java.io.ByteArrayOutputStream
3115

32-
@Serializable
33-
object StreamRealtimeRoute
34-
35-
@Serializable
36-
object StreamRealtimeVideoRoute
3716

3817
@OptIn(PublicPreviewAPI::class)
39-
class BidiViewModel : ViewModel() {
40-
// Firebase AI Logic
41-
private var liveSession: LiveSession
42-
43-
init {
44-
val liveGenerationConfig = liveGenerationConfig {
45-
speechConfig = SpeechConfig(voice = Voice("CHARON"))
46-
// Change this to ContentModality.TEXT if you want text output.
47-
responseModality = ResponseModality.AUDIO
48-
}
18+
abstract class BidiViewModel : ViewModel() {
19+
protected lateinit var liveSession: LiveSession
4920

50-
@OptIn(PublicPreviewAPI::class)
51-
val liveModel =
52-
Firebase.ai(backend = GenerativeBackend.googleAI())
53-
.liveModel(
54-
// If you are using Vertex AI, change the model name to
55-
// "gemini-live-2.5-flash-preview-native-audio-09-2025"
56-
modelName = "gemini-2.5-flash-native-audio-preview-09-2025",
57-
generationConfig = liveGenerationConfig,
58-
tools = listOf(
59-
Tool.functionDeclarations(
60-
listOf(
61-
FunctionDeclaration(
62-
"fetchWeather",
63-
"Get the weather conditions for a specific US city on a specific date.",
64-
mapOf(
65-
"city" to Schema.string("The US city of the location."),
66-
"state" to Schema.string("The US state of the location."),
67-
"date" to Schema.string(
68-
"The date for which to get the weather." +
69-
" Date must be in the format: YYYY-MM-DD."
70-
),
71-
),
72-
)
73-
)
74-
)
75-
),
76-
)
77-
runBlocking { liveSession = liveModel.connect() }
78-
}
79-
80-
fun handler(fetchWeatherCall: FunctionCallPart): FunctionResponsePart {
81-
val response: JsonObject
82-
fetchWeatherCall.let {
83-
val city = it.args["city"]?.jsonPrimitive?.content
84-
val state = it.args["state"]?.jsonPrimitive?.content
85-
val date = it.args["date"]?.jsonPrimitive?.content
86-
runBlocking {
87-
response =
88-
if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) {
89-
fetchWeather(city!!, state!!, date!!)
90-
} else {
91-
JsonObject(emptyMap())
92-
}
93-
}
94-
}
95-
return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id)
21+
open fun handler(functionCall: FunctionCallPart): FunctionResponsePart {
22+
return FunctionResponsePart(functionCall.name, JsonObject(emptyMap()), functionCall.id)
9623
}
9724

9825
// The permission check is handled by the view that calls this function.
@@ -116,4 +43,3 @@ class BidiViewModel : ViewModel() {
11643
}
11744
}
11845
}
119-
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.google.firebase.quickstart.ai.feature.live
2+
3+
import com.google.firebase.Firebase
4+
import com.google.firebase.ai.ai
5+
import com.google.firebase.ai.type.FunctionCallPart
6+
import com.google.firebase.ai.type.FunctionDeclaration
7+
import com.google.firebase.ai.type.FunctionResponsePart
8+
import com.google.firebase.ai.type.GenerativeBackend
9+
import com.google.firebase.ai.type.PublicPreviewAPI
10+
import com.google.firebase.ai.type.ResponseModality
11+
import com.google.firebase.ai.type.Schema
12+
import com.google.firebase.ai.type.SpeechConfig
13+
import com.google.firebase.ai.type.Tool
14+
import com.google.firebase.ai.type.Voice
15+
import com.google.firebase.ai.type.liveGenerationConfig
16+
import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather
17+
import kotlinx.coroutines.runBlocking
18+
import kotlinx.serialization.Serializable
19+
import kotlinx.serialization.json.JsonObject
20+
import kotlinx.serialization.json.jsonPrimitive
21+
22+
@Serializable
23+
object StreamRealtimeAudioRoute
24+
25+
@OptIn(PublicPreviewAPI::class)
26+
class StreamAudioViewModel : BidiViewModel() {
27+
init {
28+
val liveGenerationConfig = liveGenerationConfig {
29+
speechConfig = SpeechConfig(voice = Voice("CHARON"))
30+
responseModality = ResponseModality.AUDIO
31+
}
32+
33+
val liveModel =
34+
Firebase.ai(backend = GenerativeBackend.googleAI())
35+
.liveModel(
36+
modelName = "gemini-2.5-flash-native-audio-preview-09-2025",
37+
generationConfig = liveGenerationConfig,
38+
tools = listOf(
39+
Tool.functionDeclarations(
40+
listOf(
41+
FunctionDeclaration(
42+
"fetchWeather",
43+
"Get the weather conditions for a specific US city on a specific date.",
44+
mapOf(
45+
"city" to Schema.string("The US city of the location."),
46+
"state" to Schema.string("The US state of the location."),
47+
"date" to Schema.string(
48+
"The date for which to get the weather." +
49+
" Date must be in the format: YYYY-MM-DD."
50+
),
51+
),
52+
)
53+
)
54+
)
55+
),
56+
)
57+
runBlocking { liveSession = liveModel.connect() }
58+
}
59+
60+
override fun handler(functionCall: FunctionCallPart): FunctionResponsePart {
61+
val response: JsonObject
62+
if (functionCall.name == "fetchWeather") {
63+
val city = functionCall.args["city"]?.jsonPrimitive?.content
64+
val state = functionCall.args["state"]?.jsonPrimitive?.content
65+
val date = functionCall.args["date"]?.jsonPrimitive?.content
66+
runBlocking {
67+
response =
68+
if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) {
69+
fetchWeather(city!!, state!!, date!!)
70+
} else {
71+
JsonObject(emptyMap())
72+
}
73+
}
74+
} else {
75+
response = JsonObject(emptyMap())
76+
}
77+
return FunctionResponsePart(functionCall.name, response, functionCall.id)
78+
}
79+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.google.firebase.quickstart.ai.feature.live
2+
3+
import com.google.firebase.Firebase
4+
import com.google.firebase.ai.ai
5+
import com.google.firebase.ai.type.GenerativeBackend
6+
import com.google.firebase.ai.type.PublicPreviewAPI
7+
import com.google.firebase.ai.type.ResponseModality
8+
import com.google.firebase.ai.type.SpeechConfig
9+
import com.google.firebase.ai.type.Voice
10+
import com.google.firebase.ai.type.liveGenerationConfig
11+
import kotlinx.coroutines.runBlocking
12+
import kotlinx.serialization.Serializable
13+
14+
@Serializable
15+
object StreamRealtimeVideoRoute
16+
17+
@OptIn(PublicPreviewAPI::class)
18+
class StreamVideoViewModel : BidiViewModel() {
19+
init {
20+
val liveGenerationConfig = liveGenerationConfig {
21+
speechConfig = SpeechConfig(voice = Voice("CHARON"))
22+
responseModality = ResponseModality.AUDIO
23+
}
24+
25+
val liveModel =
26+
Firebase.ai(backend = GenerativeBackend.googleAI())
27+
.liveModel(
28+
modelName = "gemini-2.5-flash-native-audio-preview-09-2025",
29+
generationConfig = liveGenerationConfig,
30+
)
31+
runBlocking { liveSession = liveModel.connect() }
32+
}
33+
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import kotlinx.coroutines.launch
4040

4141
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
4242
@Composable
43-
fun StreamRealtimeScreen(bidiView: BidiViewModel = viewModel<BidiViewModel>()) {
43+
fun StreamRealtimeScreen(bidiView: BidiViewModel) {
4444
val isConversationActive = remember { mutableStateOf(false) }
4545
val backgroundColor =
4646
MaterialTheme.colorScheme.background

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import kotlinx.coroutines.launch
3030

3131
@RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA])
3232
@Composable
33-
fun StreamRealtimeVideoScreen(bidiView: BidiViewModel = viewModel<BidiViewModel>()) {
33+
fun StreamRealtimeVideoScreen(bidiView: BidiViewModel) {
3434
val backgroundColor = MaterialTheme.colorScheme.background
3535

3636
val scope = rememberCoroutineScope()

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.google.firebase.quickstart.ai.ui.navigation
22

3-
import com.google.firebase.quickstart.ai.feature.live.BidiViewModel
4-
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute
3+
import com.google.firebase.quickstart.ai.feature.live.StreamAudioViewModel
4+
import com.google.firebase.quickstart.ai.feature.live.StreamVideoViewModel
5+
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeAudioRoute
56
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute
67
import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationRoute
78
import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationViewModel
@@ -163,9 +164,9 @@ val FIREBASE_AI_SAMPLES = listOf(
163164
title = "ForecastTalk",
164165
description = "Use bidirectional streaming to get information about" +
165166
" weather conditions for a specific US city on a specific date",
166-
route = StreamRealtimeRoute,
167+
route = StreamRealtimeAudioRoute,
167168
screenType = ScreenType.BIDI,
168-
viewModelClass = BidiViewModel::class,
169+
viewModelClass = StreamAudioViewModel::class,
169170
categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING)
170171
),
171172
Sample(
@@ -174,7 +175,7 @@ val FIREBASE_AI_SAMPLES = listOf(
174175
" phone's camera",
175176
route = StreamRealtimeVideoRoute,
176177
screenType = ScreenType.BIDI_VIDEO,
177-
viewModelClass = BidiViewModel::class,
178+
viewModelClass = StreamVideoViewModel::class,
178179
categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING)
179180
),
180181
Sample(

0 commit comments

Comments
 (0)