Skip to content

feat(agent): add full-duplex LiveAudioSession and AndroidLiveAudioSession - #75

Open
juliantoledo wants to merge 2 commits into
android:mainfrom
juliantoledo:feat/live-audio-session
Open

juliantoledo wants to merge 2 commits into
android:mainfrom
juliantoledo:feat/live-audio-session

Conversation

@juliantoledo

@juliantoledo juliantoledo commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part 3 of 5 in the Gemini Live voice assistant series (following #65 and #69):

  1. PR 1: Refactor tool discovery and execution (refactor(agent): unify AppFunction discovery, input conversion, and execution #65 — merged)
  2. PR 2: Live LLM provider layer (LiveLlmProvider abstraction + GeminiLiveProviderImpl) (feat(agent): add LiveLlmProvider abstraction and Gemini Live WebSocket provider #69 — merged)
  3. PR 3 (this PR): Full-duplex audio capture/playback (LiveAudioSession + AndroidLiveAudioSession)
  4. PR 4: Live session orchestration (LiveAgentOrchestrator)
  5. PR 5: Android VoiceInteractionService and Live UI integration

Changes in this PR

  • LiveAudioConfig, LiveAudioSession, & LiveAudioSessionFactory: Introduce a provider-agnostic domain-layer abstraction for full-duplex PCM microphone capture (listenToRecording()), software-gated pause/resume (pauseRecording() / resumeRecording()), assistant audio playback (queueAudio(pcmBytes, sampleRateHz)), interruption flushing (stopAudio()), and lifecycle teardown (release()), configured with per-provider sample rates via LiveLlmSession.audioConfig (LiveAudioConfig).
  • AndroidLiveAudioSession:
    • Implement LiveAudioSession pairing VOICE_COMMUNICATION recording with USAGE_VOICE_COMMUNICATION playback under MODE_IN_COMMUNICATION and AUDIOFOCUS_GAIN_TRANSIENT so the hardware VoIP DSP (AcousticEchoCanceler and NoiseSuppressor) cancels speaker playback from the microphone input.
    • Propagate @RequiresPermission(Manifest.permission.RECORD_AUDIO) on start(), fail fast with IllegalStateException on invalid lifecycle transitions or denied audio focus, and respect audio focus across recording and playback.
    • Route communication audio to the built-in speakerphone when no external headset/Bluetooth/USB audio device is connected, and restore communication device, audio mode, and audio focus on release().
    • Keep AudioRecord continuously recording across tool calls while gating emissions in software during pauseRecording() or transient audio focus loss.
    • Support dynamic AudioTrack recreation if queueAudio receives a chunk with a different sampleRateHz.
  • DataModule: Provide LiveAudioSessionFactory backed by AndroidLiveAudioSession.
  • Unit Tests: Add Robolectric unit tests in AndroidLiveAudioSessionTest covering communication mode & audio focus lifecycle, failed audio focus acquisition, invalid start() lifecycle transitions, speakerphone vs. external headset routing, interruption flushing, software pause/resume, dynamic playback sample rates, audio focus loss/gain across recording and playback, and concurrent release() during start().

Test Plan

  • ./gradlew spotlessCheck test validateScreenshotTest assembleDebug assembleRelease -PGEMINI_API_KEY=dummy_key

@juliantoledo
juliantoledo requested review from a team as code owners November 10, 2026 17:45
@juliantoledo
juliantoledo force-pushed the feat/live-audio-session branch from 6c11a0e to c39e1d4 Compare November 13, 2026 07:07
@juliantoledo juliantoledo changed the title feat(agent): add full-duplex LiveAudioSession and GeminiLiveAudioHelper feat(agent): add full-duplex LiveAudioSession and AndroidLiveAudioSession Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants