feat: add RTSP server, Chromecast WebRTC casting, and multi-session support#1329
feat: add RTSP server, Chromecast WebRTC casting, and multi-session support#1329qoole wants to merge 3 commits intojetkvm:devfrom
Conversation
…upport Add three major features to the JetKVM firmware: 1. **RTSP server** (`rtsp.go`): Exposes the HDMI capture H.264 stream as `rtsp://<device-ip>:8554/stream` using gortsplib v5. Supports multiple concurrent clients with SPS/PPS caching for late-joiners. Configurable via settings UI (enable/disable, port). 2. **Chromecast casting** (`chromecast.go`): Low-latency WebRTC streaming to Chromecast / Google TV devices via a custom Cast receiver application. Uses the CASTV2 protocol directly with a buffered connection wrapper to prevent channel deadlocks. The receiver HTML (`cast-receiver/`) is hosted externally on any HTTPS server and establishes a direct WebRTC connection to the JetKVM. Includes mDNS device discovery and a Cast button in the web UI toolbar. See `cast-receiver/README.md` for setup instructions. 3. **Multi-session WebRTC**: Replaces the `currentSession` singleton with a mutex-protected session registry. Video frames are fanned out to all active WebRTC sessions, allowing the browser and Chromecast to stream simultaneously. All event broadcasting (USB, serial, OTA, network, video) updated to iterate all sessions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add CastPreferredDevice to config (persisted to kvm_config.json) - Star icon in Cast dropdown to set/clear preferred device - Preferred device shown in Settings > Video with clear button - rpcQuickCast: casts to preferred device, or first discovered if none set - rpcSetPreferredCastDevice: save/clear preferred device via RPC - LCD touch screen can call quickCast for one-tap casting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /webrtc/signaling/client endpoint is behind auth middleware, which blocks the Chromecast receiver (cannot send cookies/tokens). Add /webrtc/signaling/cast as an unauthenticated endpoint for cast receivers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
|
||
| // Unauthenticated WebRTC signaling for Chromecast cast receiver. | ||
| // The cast receiver runs on a Chromecast and cannot send auth cookies/tokens. | ||
| r.GET("/webrtc/signaling/cast", handleLocalWebRTCSignal) |
There was a problem hiding this comment.
Unauthenticated endpoint exposes full device control
High Severity
The /webrtc/signaling/cast route is registered outside the protected middleware group, making it completely unauthenticated. It reuses handleLocalWebRTCSignal, which establishes a full WebRTC session with video streaming, RPC commands, and HID input control. Any device on the network can connect to this endpoint and gain full control of the KVM, completely bypassing password authentication.
| } | ||
|
|
||
| // Connect to the app's transport | ||
| if err := conn.Send(3, &cast.ConnectHeader, castSender, transportID, nsConnection); err != nil { |
There was a problem hiding this comment.
Global ConnectHeader mutated instead of local copy
Medium Severity
At line 254, a local copy of cast.ConnectHeader is correctly made before passing its address. But at line 291, &cast.ConnectHeader is passed directly, which takes the address of the package-level global and allows Send to mutate it (e.g., via SetRequestId). This corrupts shared state and can cause incorrect behavior on subsequent cast connections.
| currentSession = nil | ||
| } | ||
| scopedLogger.Debug().Msg("ICE Connection State is closed, cleaning up session") | ||
| removeSession(session) |
There was a problem hiding this comment.
Keyboard macro not canceled on session disconnect
Medium Severity
The old code called cancelKeyboardMacro() when a session was replaced or closed. The new multi-session code in webrtc.go only calls removeSession(session) on ICE connection close, dropping the cancelKeyboardMacro() call entirely. A running keyboard macro will continue executing after the session that initiated it disconnects.
|
|
||
| // WriteNALU receives a raw H.264 Annex B frame and fans it out to all RTSP clients. | ||
| func (rs *RTSPServer) WriteNALU(frame []byte, duration time.Duration) { | ||
| rs.pts += duration |
There was a problem hiding this comment.
Data race on RTSPServer.pts field
Low Severity
rs.pts is incremented in WriteNALU without any synchronization. While the mu RWMutex protects sps/pps, the pts field is read and written outside any lock. If WriteNALU is ever called from multiple goroutines (or concurrently with any reader), this is a data race.


Summary
rtsp://<device-ip>:8554/streamusing gortsplib v5. Supports multiple concurrent clients with SPS/PPS caching for late-joiners. Configurable via Settings > Video (enable/disable, port).cast-receiver/README.mdfor setup.currentSessionsingleton with a mutex-protected session registry. Video frames fan out to all active sessions, allowing browser and Chromecast to stream simultaneously.New files
rtsp.gochromecast.gocast-receiver/index.htmlcast-receiver/README.mdui/src/components/CastButton.tsxui/src/hooks/useCast.tsModified files
native.go— Video fan-out to all WebRTC sessions + RTSPweb.go— Session registry (replaces singleton),gosync.RWMutex-protectedwebrtc.go— Session cleanup usesremoveSession()instead of clearing globalcloud.go—handleSessionRequestreturns session, usesaddSession()config.go— AddedRTSPEnabled,RTSPPort,CastReceiverAppIDfieldsjsonrpc.go— Registered RTSP, Cast config, and casting RPC methodsvideo.go,usb.go,serial.go,network.go,hw.go,ota.go— Event broadcasting to all sessions viaforEachSession()ui/src/routes/devices.$id.settings.video.tsx— RTSP + Cast App ID settingsui/src/components/ActionBar.tsx— Added CastButton to toolbarTest plan
ffplay rtsp://<device-ip>:8554/stream— verify RTSP playback🤖 Generated with Claude Code
Note
High Risk
Adds new unauthenticated signaling endpoint plus new RTSP server and Chromecast control path, and refactors core WebRTC session lifecycle from a singleton to a multi-session registry; regressions could impact streaming stability and expand network-exposed surface area.
Overview
Adds RTSP streaming of the HDMI capture (via
gortsplib) with live SPS/PPS updates for late joiners, plus JSON-RPC and UI settings to enable/disable and change the port.Introduces Chromecast / Google TV casting by adding mDNS discovery and CASTV2 launch/control of a custom receiver app (
cast-receiver/index.html+ setup docs), along with UI controls (toolbarCastbutton, preferred device selection) and related JSON-RPC methods.Refactors WebRTC from a
currentSessionsingleton to a mutex-protected multi-session registry, fanning out video frames and JSON-RPC event broadcasts to all sessions, and adjusting session creation/cleanup and update/OTA gating accordingly; also adds an unauthenticated/webrtc/signaling/castroute for Chromecast receivers.Written by Cursor Bugbot for commit 8ec10cf. This will update automatically on new commits. Configure here.