Skip to content

websockets Live API starter: use realtime_input.audio/video and flush playback only on interrupted - #1433

Merged
Giom-V merged 2 commits into
google-gemini:mainfrom
fjvbn2003:ws-live-realtime-input
Oct 27, 2026
Merged

Giom-V merged 2 commits into
google-gemini:mainfrom
fjvbn2003:ws-live-realtime-input

Conversation

@fjvbn2003

Copy link
Copy Markdown
Contributor

quickstarts/websockets/Get_started_LiveAPI.py has drifted from the Live API reference and from the SDK version of the same script (quickstarts/Get_started_LiveAPI.py).

1. It sends input with the deprecated media_chunks field. The Live API reference marks BidiGenerateContentRealtimeInput.mediaChunks as "DEPRECATED: Use one of audio, video, or text instead". This PR sends microphone audio as realtime_input.audio and camera or screen frames as realtime_input.video, the same split the SDK script uses with send_realtime_input(audio=...) / send_realtime_input(video=...).

2. The screen mode wraps a single frame incorrectly. get_frames() sends {"media_chunks": [frame]} (a list), but get_screen() sends {"media_chunks": frame}, which puts a single object where the field expects a list. Switching to video, which takes one blob, removes that inconsistency.

3. The audio MIME type has no sample rate. The audio is sent as "audio/pcm". It now uses f"audio/pcm;rate={SEND_SAMPLE_RATE}", as the other Live examples do.

4. Playback is flushed on every turnComplete and only parts[0] is played. The script drops queued audio whenever a turn completes, and its comment says the server sends an end of turn when you interrupt. The signal for an interruption is serverContent.interrupted, and the SDK script already flushes only on that flag. It also plays every part of a message. This PR does the same here: it plays all inlineData parts, flushes the queue on interrupted, and only prints on turnComplete.

For context, we run a production voice-chat relay that talks to gemini-3.8-live over the raw WebSocket protocol. It uses realtimeInput.audio with audio/pcm;rate=16000 and clears client playback on interrupted (not on turnComplete), which works as expected.

The file compiles with Python 3 (compile()). I could not run it end to end here because it needs a microphone, a camera and an API key. The diff is +24/−30 and keeps the file's CRLF line endings.

(Optional, not in this PR: the SDK sends the API key in the x-goog-api-key header on the Live WebSocket, and python-genai #2564 moved Live Music keys out of the URL for the same reason, since URLs end up in logs and exception messages. If header auth is supported for raw WebSocket clients, the websockets examples could show it instead of ?key=. I'm happy to open an issue to discuss.)

This change was prepared with the help of an AI assistant (Claude); I reviewed and verified it.

🤖 Generated with Claude Code

… playback only on interrupted

media_chunks is deprecated in the Live API reference, and one call
passed a dict instead of a list. The receive loop also cleared the
playback queue on every turnComplete and only played parts[0]. Match
the SDK-based starter: send audio and video through realtime_input,
keep queued audio until the server reports an interruption, and play
every inline part.

Signed-off-by: YoungjuKim <[email protected]>
@github-actions github-actions Bot added status:awaiting review PR awaiting review from a maintainer component:quickstarts Issues/PR referencing quickstarts folder labels Oct 17, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request updates the Get_started_LiveAPI.py script to align with current Live API standards and improve robustness. By migrating away from deprecated fields and adjusting how audio playback is managed during interruptions, the script now behaves more consistently with the official SDK and production relay implementations.

Highlights

  • API Migration: Updated the Live API integration to use the non-deprecated realtime_input.audio and realtime_input.video fields instead of media_chunks.
  • Audio Configuration: Included the sample rate in the audio MIME type (audio/pcm;rate=...) to ensure correct audio processing.
  • Playback Logic: Refined the playback interruption logic to flush the audio queue only when the interrupted flag is received, rather than on every turnComplete event.
  • Message Handling: Updated the receiver to process all parts of a model turn instead of only the first part.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Live API quickstart script to use the new schema for sending video and audio inputs, replacing the deprecated media_chunks field with video and audio fields. It also refactors the receive_audio method to improve how model interruptions and multi-part messages are handled. Feedback is provided to defensively handle cases where nested JSON fields like modelTurn or inlineData might be explicitly set to null, which could otherwise trigger an AttributeError during execution.

@kkorpal kkorpal left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @fjvbn2003 , this is a bigger fix than it looks. I tested over the raw websocket with gemini-3.8-live: the same speech clip gets no reaction via media_chunks (with or without rate=) and a correct answer via realtime_input.audio. So on main the mic is effectively dead. realtime_input.video and the interrupted handling also check out.

One line to add while you're in receive_audio: the server sends outputTranscription by default, so raw_response.decode("ascii") crashes on any non-ASCII reply (reproduce on main by typing Say hello in Japanese.). Fix:

python
response = json.loads(raw_response)

Optional: if self.audio_stream: before .close() in run(), and the bot's (… or {}) nit. Happy to re-review once the decode line is in.

… fields

The server sends outputTranscription by default, so decoding each frame
as ASCII raised UnicodeDecodeError on any non-ASCII reply (for example
"Say hello in Japanese."). json.loads accepts the bytes directly and
decodes them as UTF-8. Also treat an explicit null modelTurn/inlineData
as empty, and only close the audio stream if it was opened.

Assisted-by: Claude <[email protected]>
Signed-off-by: YoungjuKim <[email protected]>
@fjvbn2003

Copy link
Copy Markdown
Contributor Author

Thanks for testing it end to end! Added json.loads(raw_response) (the setup response had the same .decode("ascii"), fixed there too), the if self.audio_stream: guard, and the (… or {}) nit. I checked locally that a frame with a Japanese outputTranscription now parses, where the old decode raised UnicodeDecodeError; I haven't rerun it against the live endpoint.

@fjvbn2003

Copy link
Copy Markdown
Contributor Author

Follow-up on verification: I also ran receive_audio() itself against a stub websocket (no network or audio, Python 3.12), feeding a Japanese outputTranscription frame, a modelTurn: null frame, a part with inlineData: null, and turnComplete. Before this change it stops on the first frame with UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3; after it, all four frames are handled and the audio chunk reaches the playback queue. Still not run against the live endpoint.

@BichengWang BichengWang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed e81009a9f05691d17062f4a4ecf5043afb3cff5a, including the earlier feedback and follow-up commits. No blocking issues found in the changed logic.

I ran eight offline checks on the actual AudioLoop class, extracted with ast to avoid importing device dependencies or initializing credentials. Stub camera, screen, and microphone inputs produce single video/audio blobs, with the microphone declaring audio/pcm;rate=16000. Receiver checks cover multiple audio parts after a text part, binary and text WebSocket frames, pending playback surviving turnComplete, and stale playback being cleared on interrupted. Unicode setup/transcription messages and the optional fields discussed earlier also pass.

The same checks against current main reproduce the old media payloads, dropped playback, and decoding failures. The full script compiles. The input fields and interruption behavior also agree with the Live API reference.

These checks use stub devices and transport; I did not exercise a real microphone, camera, speaker, or live API session.

@kkorpal

kkorpal commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Thanks @fjvbn2003, all in. Tested end to end on e81009a: voice input now works (a 5-minute spoken conversation, whereas on main the model never hears the mic because the server ignores media_chunks), screen frames sent as realtime_input.video are understood, and Say hello in Japanese. as the first message plays and prints End of turn where main crashes with UnicodeDecodeError. Good catch on the setup reply using the same decode. LGTM.

@kkorpal kkorpal self-assigned this Oct 22, 2026
@fjvbn2003

Copy link
Copy Markdown
Contributor Author

Thanks a lot for testing it end to end, including the 5-minute voice session! When you have a moment, could you update your earlier "changes requested" review so the PR can be merged?

@kkorpal
kkorpal requested a review from Giom-V October 22, 2026 09:29
@Giom-V
Giom-V merged commit db6fb41 into google-gemini:main Oct 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:quickstarts Issues/PR referencing quickstarts folder status:awaiting review PR awaiting review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants