Skip to content

Commit 56ac62b

Browse files
authored
Merge pull request #1776 from firebase/dlr/add-live-audio
[FirebaseAI] Add Live Audio screen
2 parents ac9d976 + 255181f commit 56ac62b

File tree

20 files changed

+1446
-1
lines changed

20 files changed

+1446
-1
lines changed

firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@
271271
DEVELOPMENT_TEAM = "";
272272
ENABLE_PREVIEWS = YES;
273273
GENERATE_INFOPLIST_FILE = YES;
274+
INFOPLIST_KEY_NSMicrophoneUsageDescription = "The app needs access to your microphone to enable live voice conversations with Gemini.";
274275
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
275276
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
276277
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -304,6 +305,7 @@
304305
DEVELOPMENT_TEAM = "";
305306
ENABLE_PREVIEWS = YES;
306307
GENERATE_INFOPLIST_FILE = YES;
308+
INFOPLIST_KEY_NSMicrophoneUsageDescription = "The app needs access to your microphone to enable live voice conversations with Gemini.";
307309
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
308310
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
309311
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "gemini-logo.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"scale" : "3x"
15+
}
16+
],
17+
"info" : {
18+
"author" : "xcode",
19+
"version" : 1
20+
}
21+
}
235 KB
Loading

firebaseai/FirebaseAIExample/ContentView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ struct ContentView: View {
110110
FunctionCallingScreen(backendType: selectedBackend, sample: sample)
111111
case "GroundingScreen":
112112
GroundingScreen(backendType: selectedBackend, sample: sample)
113+
case "LiveScreen":
114+
LiveScreen(backendType: selectedBackend, sample: sample)
113115
default:
114116
EmptyView()
115117
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#if canImport(FirebaseAILogic)
16+
import FirebaseAILogic
17+
#else
18+
import FirebaseAI
19+
#endif
20+
import SwiftUI
21+
import TipKit
22+
23+
struct LiveScreen: View {
24+
let backendType: BackendOption
25+
@StateObject var viewModel: LiveViewModel
26+
27+
init(backendType: BackendOption, sample: Sample? = nil) {
28+
self.backendType = backendType
29+
_viewModel =
30+
StateObject(wrappedValue: LiveViewModel(backendType: backendType,
31+
sample: sample))
32+
}
33+
34+
var body: some View {
35+
VStack(spacing: 20) {
36+
ModelAvatar(isConnected: viewModel.state == .connected)
37+
TranscriptView(typewriter: viewModel.transcriptTypewriter)
38+
39+
Spacer()
40+
if let error = viewModel.error {
41+
ErrorDetailsView(error: error)
42+
}
43+
if let tip = viewModel.tip, !viewModel.hasTranscripts {
44+
TipView(tip)
45+
}
46+
ConnectButton(
47+
state: viewModel.state,
48+
onConnect: viewModel.connect,
49+
onDisconnect: viewModel.disconnect
50+
)
51+
52+
#if targetEnvironment(simulator)
53+
AudioOutputToggle(isEnabled: $viewModel.isAudioOutputEnabled, onChange: {
54+
Task {
55+
await viewModel.onAudioPlaybackChanged()
56+
}
57+
})
58+
#endif
59+
}
60+
.padding()
61+
.navigationTitle(viewModel.title)
62+
.navigationBarTitleDisplayMode(.inline)
63+
.background(viewModel.backgroundColor ?? .clear)
64+
.onDisappear {
65+
Task {
66+
await viewModel.disconnect()
67+
}
68+
}
69+
}
70+
}
71+
72+
#Preview {
73+
LiveScreen(backendType: .googleAI)
74+
}

0 commit comments

Comments
 (0)