Feature: Multi-device screen wall on the Go server
Bring the Go web backend up to parity with the MFC server's screen wall. A browser tab can now stream several devices over one WebSocket: the single `watching` field becomes a per-connection watch set guarded by the hub mutex, capped at 32 devices per socket. The challenge advertises `caps.multiWatch` and `caps.keyframeRequest`, binary frames stay keyed by the low 32 bits of the device ID, and connect/disconnect replies echo the device id and request id so the wall can route concurrent responses. Input commands (mouse, key, clipboard, rdp_reset) resolve their target from the watch set: an explicit id must be a current subscription, and an absent id is accepted only while exactly one device is watched. Legacy frontends that do not send `multiWatch` keep replace-all semantics, so a repeated device switch cannot grow the set without bound. Offline devices are removed from every watch set, and teardown is computed from a snapshot taken outside the hub lock to avoid re-entrant locking. Add the `keyframe` command: it replays the cached IDR, then asks the device to emit a fresh one, rate-limited to one request per device per second. This exposed a Go-only corruption bug: the client answers the request with TOKEN_KEYFRAME (134), which the dispatcher treated as a no-op. That token only appears on forced IDRs because the client's GOP is effectively infinite, so the dropped IDR left the browser decoding P-frames against a reference chain it never received, producing persistent garbling. Relay it as a keyframe instead. Also drop the cached IDR when the resolution changes, mirroring the C++ server, so a late joiner cannot receive an old-geometry keyframe. Add tests for the watch-set transitions and for relaying TOKEN_KEYFRAME. Co-Authored-By: deepseek-flash
This commit is contained in:
1 parent
8eab26c1f6
commit
1922f87dd2
7 files changed
+502
-80
No files matched your search
@@ -581,6 +581,13 @@ func (h *Hub) PublishResolution(deviceID string, width, height int) {
|
||||
if d, ok := h.devices[deviceID]; ok {
|
||||
d.screenWidth = width
|
||||
d.screenHeight = height
|
||||
// The cached IDR belongs to the previous capture setup. The client
|
||||
// re-sends TOKEN_BITMAPINFO on the same sub-conn after a window
|
||||
// resize / capture restart, so this is not only the
|
||||
// new-sub-conn path (BindScreenConn already clears there). Serving
|
||||
// the old-resolution IDR alongside the new dimensions leaves the
|
||||
// decoder misconfigured and garbles the picture until the next IDR.
|
||||
d.lastKeyframe = nil
|
||||
}
|
||||
h.mu.Unlock()
|
||||
for _, s := range h.snapshotSubscribers() {
|
||||
|
||||
Reference in new issue
Block a user