Skip to content

Refactor: Per-device WebSocket streams to fix backpressure + cross-browser codec support#125

Merged
RoiArthurB merged 5 commits intodevfrom
scrcpy-multi-ws
Mar 6, 2026
Merged

Refactor: Per-device WebSocket streams to fix backpressure + cross-browser codec support#125
RoiArthurB merged 5 commits intodevfrom
scrcpy-multi-ws

Conversation

@RoiArthurB
Copy link
Contributor

@RoiArthurB RoiArthurB commented Mar 6, 2026

Pull Request

Checklist

  • Code is complete and ready for review
  • Tests have been added/updated (if applicable)
  • Documentation has been updated (if applicable)

Description

Problem

Streaming 6 concurrent video streams over a single shared WebSocket caused backpressure overflow

  • one slow client would stall all streams.

Additionally, h265→h264 fallback (tested with Firefox on Linux) caused crashes, canvas duplication and other visual glitches.

What changed

  • Architecture — ScrcpyServer.ts

    • Replaced the single shared video WebSocket with a per-device socket at /stream/:ip — each stream gets an independent 8 MB backpressure budget
    • The root ws('/*') is now a lightweight control channel only: codec negotiation (client→server) and stream_available announcements (server→client)
    • Stream ID is the device IP (serial.split(':')[0]), stable across ADB reconnects and port changes
    • Removed TinyH264Decoder dependency — codec options are no longer artificially restricted to its Baseline profile caps since decoding is done by the browser's WebCodecs API
  • Stability fixes — ScrcpyServer.ts

    • exited handlers now guard with scrcpyClientsByIp.get(ip) === client before cleaning up, preventing an old client's async exit from wiping a new client's registration (root cause of stream_available being lost after codec switch)
    • activeStreams is cleared immediately at codec switch start so reconnecting clients don't receive stale announcements during the transition
    • upgrade handler validates ip is non-empty before checking activeStreams
  • Client — VideoStreamManager.tsx

    • Opens a dedicated /stream/:ip WebSocket per device on stream_available, with 1s auto-reconnect
    • Codec change detection on every config packet (streamIsH265 map) resets decoder state before creating a new one
    • Old socket's onmessage is nulled before replacement, preventing queued packets from the old codec from feeding the new decoder
    • hardwareAcceleration: "prefer-software" for H264 enables Firefox's software decoder (OpenH264) and avoids DOMException: The given encoding is not supported on Linux
  • Client — PlayerScreenCanvas.tsx

    • querySelector('canvas')?.remove() before appendChild ensures the stale canvas from a previous decoder is removed from the DOM on stream restart

BEFORE MERGING !!

Need to be fully tested before merging !! :

  • Linux
    • Firefox
    • Chrome
  • Windows
    • Firefox
    • Chrome
  • MacOS
    • Safari

RoiArthurB and others added 4 commits March 5, 2026 17:37
…ix backpressure

Each Android device now gets a dedicated WebSocket at /stream/:deviceIp (IP only,
port stripped for stability across ADB reconnects). All packets (config then data)
flow in order on the same socket, eliminating config/data ordering races.

The existing ws('/*') becomes a lightweight control channel that only carries codec
negotiation and stream_available announcements — no video data. Clients open a
per-device socket when they receive an announcement, and reconnect automatically
after 1s on close. The upgrade handler validates against activeStreams dynamically
so there is no hardcoded stream count. Each stream gets an independent 8 MB
backpressure budget with safeSend() dropping frames per-stream instead of globally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Null out `existing.onmessage` before closing the old device socket so
  queued h265 packets in the receive buffer don't feed a newly created
  h264 decoder, eliminating the `Invalid data at h265SearchConfiguration` crash
- Add `streamIsH265` map to detect mid-stream codec changes and reset
  decoder state when a config packet arrives with a different codec
- Remove stale canvas element via `querySelector('canvas')?.remove()`
  before appending the new one in PlayerScreenCanvas, so the DOM never
  holds two overlapping canvases after a stream restart
…codec switch

Client (VideoStreamManager.tsx):
- Pass hardwareAcceleration: "prefer-software" to WebCodecsVideoDecoder for h264
  so Firefox uses its software decoder (OpenH264) instead of failing on the
  hardware WebCodecs path, fixing "DOMException: The given encoding is not supported"

Server (ScrcpyServer.ts):
- Remove TinyH264Decoder videoCodecOptions restriction — encoding profile/level
  is now left to the Android encoder since decoding is done by WebCodecs in
  the browser (not TinyH264), removing an unnecessary Baseline-only constraint
- Clear activeStreams + scrcpyClientsByIp immediately when a codec switch starts
  so control clients reconnecting during the transition get no stale
  stream_available announcements
- Guard exited handlers with scrcpyClientsByIp.get(streamIp) === client check
  so a departing client's async exit callback never wipes the new client's
  registration, which was the root cause of stream_available being lost after
  a codec switch
- Cleaning some weird vibe-coded left-over
- Simplify un-necessary `myself` in the backend
- Entirely remove TinyH264 dependencies
  - All fully managed by upgraded WebCodecs
  - Rebuilt package-lock without chained dependencies
- Remove old comments
@RoiArthurB RoiArthurB self-assigned this Mar 6, 2026
@RoiArthurB RoiArthurB added 🤗 Enhancement New feature or request 🎨 Frontend Issues related to HTML, CSS, JavaScript. ⚙️ Backend Issues related to server, database, API. 👀 scrcpy Issue related to casting android video 🔌 Connection Issues with how applications are connecting together. 👍 Fix to be tested labels Mar 6, 2026
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 6, 2026

@RoiArthurB RoiArthurB merged commit 3d73d6e into dev Mar 6, 2026
2 checks passed
@RoiArthurB RoiArthurB deleted the scrcpy-multi-ws branch March 6, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚙️ Backend Issues related to server, database, API. 🔌 Connection Issues with how applications are connecting together. 🤗 Enhancement New feature or request 👍 Fix to be tested 🎨 Frontend Issues related to HTML, CSS, JavaScript. 👀 scrcpy Issue related to casting android video

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant