-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Add native WebRTC session foundation (core engine + HA signaling) #7137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Draft
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0d44592
Add native WebRTC session foundation (core engine + HA signaling)
claude 92075f1
Add temporary CI workflow to generate lockfiles for this branch
claude b76e4f6
Update dependency lockfiles
github-actions[bot] 9e7b513
Remove temporary lockfile workflow
claude 04bd5e5
Fix compilation and apply ktlint formatting
claude b64bb25
Address review feedback on session lifecycle and config decoding
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...stant/companion/android/common/data/websocket/impl/entities/CameraCapabilitiesResponse.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package io.homeassistant.companion.android.common.data.websocket.impl.entities | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| /** | ||
| * Response for the `camera/capabilities` WebSocket command. | ||
| * | ||
| * The server reports which stream types the frontend can use for a camera entity. Consumers | ||
| * should check for [CameraStreamTypes.WEB_RTC] before starting a WebRTC session and fall back to | ||
| * HLS otherwise. | ||
| */ | ||
| @Serializable | ||
| data class CameraCapabilitiesResponse(val frontendStreamTypes: List<String> = emptyList()) | ||
|
|
||
| /** | ||
| * Known values of [CameraCapabilitiesResponse.frontendStreamTypes], matching the | ||
| * `StreamType` enum of Home Assistant Core. | ||
| */ | ||
| object CameraStreamTypes { | ||
| const val HLS = "hls" | ||
| const val WEB_RTC = "web_rtc" | ||
| } |
52 changes: 52 additions & 0 deletions
52
...companion/android/common/data/websocket/impl/entities/CameraWebRtcClientConfigResponse.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package io.homeassistant.companion.android.common.data.websocket.impl.entities | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
| import kotlinx.serialization.builtins.ListSerializer | ||
| import kotlinx.serialization.builtins.serializer | ||
| import kotlinx.serialization.json.JsonArray | ||
| import kotlinx.serialization.json.JsonElement | ||
| import kotlinx.serialization.json.JsonTransformingSerializer | ||
|
|
||
| /** | ||
| * Response for the `camera/webrtc/get_client_config` WebSocket command. | ||
| * | ||
| * The keys are camelCase on the wire (they mirror the W3C `RTCConfiguration` dictionary), so this | ||
| * class must be deserialized with [webRtcJsonMapper] and not the shared snake_case mapper. | ||
| * | ||
| * @property configuration the `RTCConfiguration` to create the peer connection with | ||
| * @property dataChannel label of a data channel the client should open, used by some WebRTC | ||
| * providers (like go2rtc) to negotiate additional features. `null` when the provider does not use | ||
| * a data channel. | ||
| */ | ||
| @Serializable | ||
| data class CameraWebRtcClientConfigResponse( | ||
| val configuration: WebRtcConfiguration = WebRtcConfiguration(), | ||
| val dataChannel: String? = null, | ||
| ) | ||
|
|
||
| /** | ||
| * The subset of the W3C `RTCConfiguration` dictionary sent by Home Assistant Core. | ||
| */ | ||
| @Serializable | ||
| data class WebRtcConfiguration(val iceServers: List<WebRtcIceServer> = emptyList()) | ||
|
|
||
| /** | ||
| * A single `RTCIceServer` entry (STUN or TURN server) of an `RTCConfiguration`. | ||
| */ | ||
| @Serializable | ||
| data class WebRtcIceServer( | ||
| @Serializable(with = StringOrStringListSerializer::class) | ||
| val urls: List<String> = emptyList(), | ||
| val username: String? = null, | ||
| val credential: String? = null, | ||
| ) | ||
|
|
||
| /** | ||
| * The `urls` member of `RTCIceServer` is allowed to be either a single string or a list of | ||
| * strings. This serializer normalizes both shapes to a list. | ||
| */ | ||
| private object StringOrStringListSerializer : | ||
| JsonTransformingSerializer<List<String>>(ListSerializer(String.serializer())) { | ||
| override fun transformDeserialize(element: JsonElement): JsonElement = | ||
| element as? JsonArray ?: JsonArray(listOf(element)) | ||
| } |
87 changes: 87 additions & 0 deletions
87
...lin/io/homeassistant/companion/android/common/data/websocket/impl/entities/WebRtcEvent.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package io.homeassistant.companion.android.common.data.websocket.impl.entities | ||
|
|
||
| import io.homeassistant.companion.android.common.util.UnknownJsonContent | ||
| import io.homeassistant.companion.android.common.util.UnknownJsonContentBuilder | ||
| import io.homeassistant.companion.android.common.util.UnknownJsonContentDeserializer | ||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
| import kotlinx.serialization.json.Json | ||
| import kotlinx.serialization.json.JsonElement | ||
| import kotlinx.serialization.modules.SerializersModule | ||
|
|
||
| /** | ||
| * JSON mapper dedicated to the `camera/webrtc/*` WebSocket commands. | ||
| * | ||
| * These payloads cannot be handled by the shared [io.homeassistant.companion.android.common.util.kotlinJsonMapper]: | ||
| * its global snake_case naming strategy would also rewrite explicit `@SerialName` values, while | ||
| * the WebRTC API mixes snake_case keys (`session_id`) with the camelCase keys of the standard | ||
| * `RTCIceCandidateInit`/`RTCConfiguration` dictionaries (`sdpMid`, `iceServers`, ...). | ||
| */ | ||
| internal val webRtcJsonMapper = Json { | ||
| ignoreUnknownKeys = true | ||
| // Omit null values so optional candidate fields are sent the same way as the frontend, | ||
| // which leaves undefined values out of the JSON payload | ||
| explicitNulls = false | ||
| serializersModule = SerializersModule { | ||
| polymorphicDefaultDeserializer(WebRtcEvent::class) { className -> | ||
| object : UnknownJsonContentDeserializer<WebRtcEvent.Unknown>() { | ||
| override val builder = UnknownJsonContentBuilder { content -> | ||
| WebRtcEvent.Unknown(className, content) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Event received on a `camera/webrtc/offer` subscription. | ||
| * | ||
| * The server pushes these events while a WebRTC session is being negotiated: first a [Session] | ||
| * with the identifier needed to send candidates back, then an [Answer], then zero or more | ||
| * [Candidate]s (trickle ICE). An [Error] can arrive at any time and ends the negotiation. | ||
| */ | ||
| @Serializable | ||
| sealed interface WebRtcEvent { | ||
|
|
||
| /** The server created a session and assigned it an identifier. */ | ||
| @Serializable | ||
| @SerialName("session") | ||
| data class Session(@SerialName("session_id") val sessionId: String) : WebRtcEvent | ||
|
|
||
| /** The SDP answer from the camera or its WebRTC provider. */ | ||
| @Serializable | ||
| @SerialName("answer") | ||
| data class Answer(val answer: String) : WebRtcEvent | ||
|
|
||
| /** A remote ICE candidate discovered by the camera or its WebRTC provider. */ | ||
| @Serializable | ||
| @SerialName("candidate") | ||
| data class Candidate(val candidate: WebRtcCandidate) : WebRtcEvent | ||
|
|
||
| /** Negotiation failed, for example `webrtc_offer_failed`. */ | ||
| @Serializable | ||
| @SerialName("error") | ||
| data class Error(val code: String, val message: String? = null) : WebRtcEvent | ||
|
|
||
| /** | ||
| * Fallback for event types this version of the app does not know, so that a server-side | ||
| * addition never breaks an ongoing subscription. | ||
| */ | ||
| data class Unknown(override val discriminator: String?, override val content: JsonElement) : | ||
| WebRtcEvent, | ||
| UnknownJsonContent | ||
| } | ||
|
|
||
| /** | ||
| * A standard `RTCIceCandidateInit` dictionary, exchanged in both directions during trickle ICE. | ||
| * | ||
| * The keys are camelCase on the wire (like in the W3C WebRTC specification), which is why this | ||
| * class must be serialized with [webRtcJsonMapper] and not the shared snake_case mapper. | ||
| */ | ||
| @Serializable | ||
| data class WebRtcCandidate( | ||
| val candidate: String, | ||
| val sdpMid: String? = null, | ||
| val sdpMLineIndex: Int? = null, | ||
| val usernameFragment: String? = null, | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.