Skip to content

Add native WebRTC session foundation (core engine + HA signaling)#7137

Draft
TimoPtr wants to merge 6 commits into
mainfrom
claude/home-assistant-companion-android-khve93
Draft

Add native WebRTC session foundation (core engine + HA signaling)#7137
TimoPtr wants to merge 6 commits into
mainfrom
claude/home-assistant-companion-android-khve93

Conversation

@TimoPtr

@TimoPtr TimoPtr commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

First slice of the native WebRTC camera streaming plan: a UI-free WebRTC session engine based on libwebrtc, plus the Home Assistant signaling implementation over the existing WebSocket layer. The motivation, alternatives considered (ExoPlayer DataSource, keeping the WebView) and the target architecture are captured in the new ADR: docs/adr/0001-native-webrtc-session-library.md.

Why: the WebView's WebRTC path cannot do two-way audio on plain-HTTP LAN setups (Chromium's secure-context rule for getUserMedia), and a doorbell "incoming call" experience (CallStyle notification, pre-negotiated media from a push) cannot be built on a WebView at all. A native session engine fixes both and gives deterministic microphone release (the #6153 class of bugs becomes structural).

What's included:

  • :webrtc-core (new module, the only one with a libwebrtc dependency — io.github.webrtc-sdk:android-prefixed, shaded to livekit.org.webrtc):
    • CameraPlayer / TwoWayAudio interfaces (the seam for a later WebRTC → HLS fallback)
    • transport-agnostic SignalingClient interface and models
    • WebRtcSession: pure-Kotlin state machine handling negotiation, trickle ICE in both directions, candidate buffering (candidate-before-answer, local-candidate-before-session-id), reconnection with capped exponential backoff (the HA signaling API has no renegotiation, so recovery is a fresh negotiation, not an ICE restart), and ordered, idempotent teardown on every exit path
    • PeerConnectionController facade + libwebrtc-backed implementation (recvonly video, sendrecv audio with the microphone track created disabled so push-to-talk needs no renegotiation)
  • :webrtc-signaling-ha (new module): SignalingClient implementation over WebSocketRepository
  • :common: camera/capabilities and camera/webrtc/* commands (get_client_config, offer subscription, candidate) with a dedicated JSON mapper, because these payloads mix snake_case keys with the camelCase W3C dictionary keys (sdpMid, iceServers) that the shared snake_case mapper would mangle
  • Unit tests for the session state machine (happy path, candidate ordering, error events, reconnection/backoff, mic lifecycle, disposal), the signaling client mapping, and entity serialization

There is no consumer wiring yet — dashboard/external-bus integration, the Compose renderer, two-way audio UI and the call flow are follow-up PRs per the ADR's phasing.

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.

Any other notes

  • This PR is part of a bigger change; see the ADR for the full phasing (renderer, dashboard integration, two-way audio UI, call notifications).
  • Verified: full unit test suite (:common 656, :webrtc-core 18, :webrtc-signaling-ha 8), ktlintCheck, and lint (no new issues) — plus the complete PR CI matrix.
  • The WebRTC signaling API requires Home Assistant Core 2024.11+ (2024.12+ for RTCIceCandidateInit trickle ICE); consumers will gate on server version and camera/capabilities when wiring up in the follow-up PRs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu

claude and others added 4 commits July 7, 2026 20:52
First slice of the native WebRTC camera streaming plan (see
docs/adr/0001-native-webrtc-session-library.md):

- :webrtc-core: UI-free session engine. CameraPlayer/TwoWayAudio
  interfaces, transport-agnostic SignalingClient, WebRtcSession state
  machine (trickle ICE both directions, candidate buffering,
  reconnection with capped backoff, ordered idempotent teardown) over a
  thin PeerConnectionController facade, plus the libwebrtc-backed
  implementation using io.github.webrtc-sdk:android-prefixed.
- :webrtc-signaling-ha: SignalingClient implementation over the
  existing WebSocket layer.
- :common: camera/capabilities and camera/webrtc/* WebSocket commands
  (get_client_config, offer subscription, candidate) with a dedicated
  JSON mapper because these payloads mix snake_case and the camelCase
  W3C dictionary keys.
- Unit tests for the session state machine, signaling client and
  entity serialization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu
The branch was authored in an environment without egress to Google
Maven, so the lockfiles for the new modules could not be generated
locally. This mirrors renovate-lockfiles.yml scoped to this branch and
will be removed once the lockfiles are committed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu
The lockfiles for the new modules are now committed, this branch-scoped
workflow is no longer needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu
- Remove the literal camera/webrtc/* from KDoc comments: Kotlin block
  comments nest, so the /* inside opened a comment that never closed
- Replace the JUnit Kotlin assertThrows (built for JVM target 17, this
  project targets 11) with runCatching assertions
- ktlintFormat pass over the new files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu
@TimoPtr
TimoPtr marked this pull request as ready for review July 8, 2026 08:18
Copilot AI review requested due to automatic review settings July 8, 2026 08:18

Copilot AI 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.

Pull request overview

This PR introduces the first “foundation” slice of a native WebRTC streaming stack for the Home Assistant Android app: a UI-free, transport-agnostic WebRTC session engine (:webrtc-core) and a Home Assistant WebSocket signaling implementation (:webrtc-signaling-ha), plus the necessary :common WebSocket commands/entities and an ADR capturing the architecture decision.

Changes:

  • Added a new :webrtc-core module with a pure-Kotlin WebRtcSession state machine and a libwebrtc-backed PeerConnectionController.
  • Added a new :webrtc-signaling-ha module implementing SignalingClient on top of WebSocketRepository.
  • Extended :common WebSocket layer with camera/capabilities, camera/webrtc/get_client_config, camera/webrtc/offer subscription, and camera/webrtc/candidate, using a dedicated JSON mapper for mixed snake_case/camelCase payloads.

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
webrtc-signaling-ha/src/test/kotlin/io/homeassistant/companion/android/webrtc/signaling/HaSignalingClientTest.kt Unit tests for HA signaling mapping and error handling
webrtc-signaling-ha/src/main/kotlin/io/homeassistant/companion/android/webrtc/signaling/HaSignalingClient.kt HA WebSocket-based SignalingClient implementation
webrtc-signaling-ha/lint-baseline.xml Lint baseline for the new module
webrtc-signaling-ha/gradle.lockfile Dependency lockfile for the new module
webrtc-signaling-ha/build.gradle.kts New module Gradle configuration
webrtc-core/src/test/kotlin/io/homeassistant/companion/android/webrtc/core/session/WebRtcSessionTest.kt Unit tests for session lifecycle/state machine behavior
webrtc-core/src/test/kotlin/io/homeassistant/companion/android/webrtc/core/session/Fakes.kt Fakes for signaling + peer connection controller used in tests
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/TwoWayAudio.kt Two-way audio capability + mic state model
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/signaling/SignalingClient.kt Transport-agnostic signaling interface + signaling models
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/session/WebRtcSession.kt Session state machine (negotiation, ICE buffering, reconnect, teardown)
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/session/PeerConnectionController.kt Facade API for libwebrtc peer connection integration
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/session/libwebrtc/LibWebRtcPeerConnectionControllerFactory.kt Shared libwebrtc PeerConnectionFactory holder + controller factory
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/session/libwebrtc/LibWebRtcPeerConnectionController.kt libwebrtc-backed peer connection controller implementation
webrtc-core/src/main/kotlin/io/homeassistant/companion/android/webrtc/core/CameraPlayer.kt Player abstraction + state/failure models
webrtc-core/lint-baseline.xml Lint baseline for the new module
webrtc-core/gradle.lockfile Dependency lockfile for the new module
webrtc-core/build.gradle.kts New module Gradle configuration (incl. libwebrtc dependency)
settings.gradle.kts Includes the two new Gradle modules
gradle/libs.versions.toml Adds the libwebrtc SDK dependency/version entry
docs/adr/0001-native-webrtc-session-library.md ADR documenting the decision/architecture/phasing
common/src/test/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImplTest.kt Adds unit tests for new camera/WebRTC WebSocket commands
common/src/test/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/WebRtcEventTest.kt Adds unit tests for WebRTC event/candidate/config serialization
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt Extends repository API with camera capabilities + WebRTC methods
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImpl.kt Implements camera/WebRTC commands and candidate send serialization
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketCoreImpl.kt Adds WebRTC subscription event decoding path
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketConstants.kt Adds WebRTC offer subscription type constant
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/WebRtcEvent.kt Adds dedicated WebRTC JSON mapper + WebRTC event/candidate entities
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/CameraWebRtcClientConfigResponse.kt Adds WebRTC client config response + ICE server models/serializer
common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/CameraCapabilitiesResponse.kt Adds camera capabilities response + known stream type constants

- start() now awaits the previous session job so a cancelled session
  always finishes disposing its peer connection before a new one starts
- Cancel the previous grace-period timer on repeated DISCONNECTED
  events so a stale timer cannot force a reconnection after recovery
- getCameraWebRtcClientConfig only decodes successful responses

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxHn9ySi8aRFvvmfcPRgVu
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.

4 participants