Skip to content

Commit 9988f72

Browse files
committed
try realtime input
1 parent b361009 commit 9988f72

File tree

4 files changed

+65
-16
lines changed

4 files changed

+65
-16
lines changed

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ class _BidiPageState extends State<BidiPage> {
221221
await _audioRecorder.stopRecording();
222222
var audioPrompt = await _audioRecorder.getAudioBytes(fromFile: true);
223223
//await _streamAudioChunks(audioPrompt, 'audio/pcm');
224-
await _sendAudioPrompt(audioPrompt);
224+
//await _sendAudioPrompt(audioPrompt);
225+
await _sendAudioRealtime(audioPrompt);
225226
}
226227

227228
List<Uint8List> _splitIntoChunks(Uint8List audioData, int chunkSize) {
@@ -242,9 +243,16 @@ class _BidiPageState extends State<BidiPage> {
242243
});
243244
final chunks = _splitIntoChunks(audioData, 1024);
244245

245-
final streamController = StreamController<Uint8List>();
246+
final streamController = StreamController<InlineDataPart>();
246247
for (var chunk in chunks) {
247-
streamController.add(chunk);
248+
if (identical(chunk, chunks.last)) {
249+
final lastData =
250+
InlineDataPart('audio/pcm', chunk, willContinue: false);
251+
streamController.add(lastData);
252+
} else {
253+
final data = InlineDataPart('audio/pcm', chunk, willContinue: true);
254+
streamController.add(data);
255+
}
248256
}
249257
streamController.close();
250258
print('streamController has stream closed');
@@ -261,6 +269,32 @@ class _BidiPageState extends State<BidiPage> {
261269
});
262270
}
263271

272+
Future<void> _sendAudioRealtime(Uint8List audio) async {
273+
setState(() {
274+
_loading = true;
275+
});
276+
final chunks = _splitIntoChunks(audio, 1024);
277+
278+
final media_chunks = <InlineDataPart>[];
279+
for (var chunk in chunks) {
280+
if (identical(chunk, chunks.last)) {
281+
final lastData =
282+
InlineDataPart('audio/pcm', chunk, willContinue: false);
283+
media_chunks.add(lastData);
284+
} else {
285+
final data = InlineDataPart('audio/pcm', chunk, willContinue: true);
286+
media_chunks.add(data);
287+
}
288+
}
289+
await _session!.stream(mediaChunks: media_chunks);
290+
print('Stream realtime audio chunk to server in one request');
291+
292+
await _handle_response_audio();
293+
setState(() {
294+
_loading = false;
295+
});
296+
}
297+
264298
Future<void> _sendAudioPrompt(Uint8List audio) async {
265299
setState(() {
266300
_loading = true;

packages/firebase_vertexai/firebase_vertexai/lib/src/content.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,23 @@ final class TextPart implements Part {
124124
/// A [Part] with the byte content of a file.
125125
final class InlineDataPart implements Part {
126126
/// Constructor
127-
InlineDataPart(this.mimeType, this.bytes);
127+
InlineDataPart(this.mimeType, this.bytes, {this.willContinue});
128128

129129
/// File type of the [InlineDataPart].
130130
/// https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements
131131
final String mimeType;
132132

133133
/// Data contents in bytes.
134134
final Uint8List bytes;
135+
136+
final bool? willContinue;
135137
@override
136138
Object toJson() => {
137-
'inlineData': {'data': base64Encode(bytes), 'mimeType': mimeType}
139+
'inlineData': {
140+
'data': base64Encode(bytes),
141+
'mimeType': mimeType,
142+
if (willContinue != null) 'willContinue': willContinue,
143+
}
138144
};
139145
}
140146

packages/firebase_vertexai/firebase_vertexai/lib/src/live.dart

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,25 @@ class AsyncSession {
3939
? LiveClientContent(turns: [input], turnComplete: turnComplete)
4040
: LiveClientContent(turnComplete: turnComplete);
4141
var clientJson = jsonEncode(clientMessage.toJson());
42-
print(clientJson);
42+
print('Sending $clientJson');
43+
_ws.sink.add(clientJson);
44+
}
45+
46+
Future<void> stream({
47+
required List<InlineDataPart> mediaChunks,
48+
}) async {
49+
// var clientMessage = _parseClientMessage(input, endOfTurn);
50+
var clientMessage = LiveClientRealtimeInput(mediaChunks: mediaChunks);
51+
var clientJson = jsonEncode(clientMessage.toJson());
52+
print('Streaming $clientJson');
4353
_ws.sink.add(clientJson);
4454
}
4555

4656
Stream<LiveServerMessage> receive() async* {
4757
await for (var message in _ws.stream) {
4858
var jsonString = utf8.decode(message);
4959
var response = json.decode(jsonString);
50-
//print(response);
60+
print(response);
5161
Map<String, dynamic> responseDict;
5262

5363
responseDict = _LiveServerMessageFromVertex(response);
@@ -63,7 +73,7 @@ class AsyncSession {
6373
}
6474

6575
Stream<LiveServerMessage> startStream({
66-
required Stream<Uint8List> stream,
76+
required Stream<InlineDataPart> stream,
6777
required String mimeType,
6878
}) async* {
6979
print('beginning of startStream');
@@ -90,7 +100,7 @@ class AsyncSession {
90100
// }
91101

92102
// Wait for the send loop to complete or the websocket to close.
93-
// await Future.any([completer.future, _ws.stream.isEmpty]);
103+
await Future.any([completer.future]);
94104

95105
// Close the websocket if it's not already closed.
96106
// if (_ws.closeCode == null) {
@@ -99,19 +109,16 @@ class AsyncSession {
99109
}
100110

101111
Future<void> _sendLoop(
102-
Stream<Uint8List> dataStream,
112+
Stream<InlineDataPart> dataStream,
103113
String mimeType,
104114
Completer completer,
105115
) async {
106116
try {
107117
print('start _sendLoop');
108118
await for (final data in dataStream) {
109-
var input = Content.inlineData(
110-
mimeType, data); // No need to convert to Uint8List
111-
112-
print('send audio data with size ${data.length}');
119+
print('send audio data with size ${data.bytes.length}');
113120

114-
await send(input: input);
121+
await stream(mediaChunks: [data]);
115122
// Give a chance for the receive loop to process responses.
116123
await Future.delayed(Duration.zero);
117124
}

packages/firebase_vertexai/firebase_vertexai/lib/src/live_api.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ class LiveClientRealtimeInput {
170170
final List<InlineDataPart>? mediaChunks;
171171

172172
Map<String, dynamic> toJson() => {
173-
'mediaChunks': mediaChunks?.map((e) => e.toJson()).toList(),
173+
'realtime_input': {
174+
'media_chunks': mediaChunks?.map((e) => e.toJson()).toList(),
175+
},
174176
};
175177
}
176178

0 commit comments

Comments
 (0)