Skip to content

Commit 6d1a0da

Browse files
authored
feat(firebaseai): update of bidi input api (#17662)
* update of bidi input api * Update with api review comments * fix analyzer * make sure the existing behavior don't change for media_chunks * revert unrelated change * more detail about the realtime usage * resolve review comments
1 parent 75cafbd commit 6d1a0da

File tree

4 files changed

+92
-14
lines changed

4 files changed

+92
-14
lines changed

packages/firebase_ai/firebase_ai/example/lib/pages/bidi_page.dart

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,13 @@ class _BidiPageState extends State<BidiPage> {
133133
itemBuilder: (context, idx) {
134134
return MessageWidget(
135135
text: _messages[idx].text,
136-
image: Image.memory(
137-
_messages[idx].imageBytes!,
138-
cacheWidth: 400,
139-
cacheHeight: 400,
140-
),
136+
image: _messages[idx].imageBytes != null
137+
? Image.memory(
138+
_messages[idx].imageBytes!,
139+
cacheWidth: 400,
140+
cacheHeight: 400,
141+
)
142+
: null,
141143
isFromUser: _messages[idx].fromUser ?? false,
142144
);
143145
},
@@ -275,13 +277,10 @@ class _BidiPageState extends State<BidiPage> {
275277
try {
276278
var inputStream = await _audioInput.startRecordingStream();
277279
await _audioOutput.playStream();
278-
// Map the Uint8List stream to InlineDataPart stream
279280
if (inputStream != null) {
280-
final inlineDataStream = inputStream.map((data) {
281-
return InlineDataPart('audio/pcm', data);
282-
});
283-
284-
await _session.sendMediaStream(inlineDataStream);
281+
await for (final data in inputStream) {
282+
await _session.sendAudioRealtime(InlineDataPart('audio/pcm', data));
283+
}
285284
}
286285
} catch (e) {
287286
developer.log(e.toString());

packages/firebase_ai/firebase_ai/lib/src/live_api.dart

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,56 @@ class LiveServerResponse {
179179
/// Represents realtime input from the client in a live stream.
180180
class LiveClientRealtimeInput {
181181
/// Creates a [LiveClientRealtimeInput] instance.
182-
///
183-
/// [mediaChunks] (optional): The list of media chunks.
184-
LiveClientRealtimeInput({this.mediaChunks});
182+
LiveClientRealtimeInput({
183+
@Deprecated('Use audio, video, or text instead') this.mediaChunks,
184+
this.audio,
185+
this.video,
186+
this.text,
187+
});
188+
189+
/// Creates a [LiveClientRealtimeInput] with audio data.
190+
LiveClientRealtimeInput.audio(this.audio)
191+
// ignore: deprecated_member_use_from_same_package
192+
: mediaChunks = null,
193+
video = null,
194+
text = null;
195+
196+
/// Creates a [LiveClientRealtimeInput] with video data.
197+
LiveClientRealtimeInput.video(this.video)
198+
// ignore: deprecated_member_use_from_same_package
199+
: mediaChunks = null,
200+
audio = null,
201+
text = null;
202+
203+
/// Creates a [LiveClientRealtimeInput] with text data.
204+
LiveClientRealtimeInput.text(this.text)
205+
// ignore: deprecated_member_use_from_same_package
206+
: mediaChunks = null,
207+
audio = null,
208+
video = null;
185209

186210
/// The list of media chunks.
211+
@Deprecated('Use audio, video, or text instead')
187212
final List<InlineDataPart>? mediaChunks;
188213

214+
/// Audio data.
215+
final InlineDataPart? audio;
216+
217+
/// Video data.
218+
final InlineDataPart? video;
219+
220+
/// Text data.
221+
final String? text;
222+
189223
// ignore: public_member_api_docs
190224
Map<String, dynamic> toJson() => {
191225
'realtime_input': {
192226
'media_chunks':
227+
// ignore: deprecated_member_use_from_same_package
193228
mediaChunks?.map((e) => e.toMediaChunkJson()).toList(),
229+
if (audio != null) 'audio': audio!.toMediaChunkJson(),
230+
if (video != null) 'video': video!.toMediaChunkJson(),
231+
if (text != null) 'text': text,
194232
},
195233
};
196234
}

packages/firebase_ai/firebase_ai/lib/src/live_session.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,47 @@ class LiveSession {
7676
_ws.sink.add(clientJson);
7777
}
7878

79+
/// Sends audio data to the server in realtime.
80+
///
81+
/// Check https://ai.google.dev/api/live#bidigeneratecontentrealtimeinput for
82+
/// details about the realtime input usage.
83+
/// [audio]: The audio data to send.
84+
Future<void> sendAudioRealtime(InlineDataPart audio) async {
85+
_checkWsStatus();
86+
var clientMessage = LiveClientRealtimeInput.audio(audio);
87+
var clientJson = jsonEncode(clientMessage.toJson());
88+
_ws.sink.add(clientJson);
89+
}
90+
91+
/// Sends video data to the server in realtime.
92+
///
93+
/// Check https://ai.google.dev/api/live#bidigeneratecontentrealtimeinput for
94+
/// details about the realtime input usage.
95+
/// [video]: The video data to send.
96+
Future<void> sendVideoRealtime(InlineDataPart video) async {
97+
_checkWsStatus();
98+
var clientMessage = LiveClientRealtimeInput.video(video);
99+
var clientJson = jsonEncode(clientMessage.toJson());
100+
_ws.sink.add(clientJson);
101+
}
102+
103+
/// Sends text data to the server in realtime.
104+
///
105+
/// Check https://ai.google.dev/api/live#bidigeneratecontentrealtimeinput for
106+
/// details about the realtime input usage.
107+
/// [text]: The text data to send.
108+
Future<void> sendTextRealtime(String text) async {
109+
_checkWsStatus();
110+
var clientMessage = LiveClientRealtimeInput.text(text);
111+
var clientJson = jsonEncode(clientMessage.toJson());
112+
_ws.sink.add(clientJson);
113+
}
114+
79115
/// Sends realtime input (media chunks) to the server.
80116
///
81117
/// [mediaChunks]: The list of media chunks to send.
118+
@Deprecated(
119+
'Use sendAudioRealtime, sendVideoRealtime, or sendTextRealtime instead')
82120
Future<void> sendMediaChunks({
83121
required List<InlineDataPart> mediaChunks,
84122
}) async {
@@ -95,6 +133,7 @@ class LiveSession {
95133
///
96134
/// Parameters:
97135
/// - [mediaChunkStream]: The stream of [InlineDataPart] objects to send to the server.
136+
@Deprecated('Use sendAudio, sendVideo, or sendText with a stream instead')
98137
Future<void> sendMediaStream(Stream<InlineDataPart> mediaChunkStream) async {
99138
_checkWsStatus();
100139

@@ -111,6 +150,7 @@ class LiveSession {
111150

112151
Future<void> _sendMediaChunk(InlineDataPart chunk) async {
113152
var clientMessage = LiveClientRealtimeInput(
153+
// ignore: deprecated_member_use_from_same_package
114154
mediaChunks: [chunk]); // Create a list with the single chunk
115155
var clientJson = jsonEncode(clientMessage.toJson());
116156
_ws.sink.add(clientJson);

packages/firebase_ai/firebase_ai/test/live_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ void main() {
103103

104104
test('LiveClientRealtimeInput toJson() returns correct JSON', () {
105105
final part = InlineDataPart('audio/pcm', Uint8List.fromList([1, 2, 3]));
106+
// ignore: deprecated_member_use_from_same_package
106107
final message = LiveClientRealtimeInput(mediaChunks: [part]);
107108
expect(message.toJson(), {
108109
'realtime_input': {

0 commit comments

Comments
 (0)