Skip to content

fix(sfu): upgrade mediasoup to 3.31 and port the sdp bridge - #1192

Open
mcottontensor wants to merge 1 commit into
EpicGames:masterfrom
mcottontensor:fix/bump-mediasoup
Open

mcottontensor wants to merge 1 commit into
EpicGames:masterfrom
mcottontensor:fix/bump-mediasoup

Conversation

@mcottontensor

Copy link
Copy Markdown
Collaborator

Upgrades mediasoup from 3.15.5 to 3.31.0 for the SFU and the SDP bridge, ports the bridge to mediasoup-client 3.26, and fixes two SFU breakages caused by the new mediasoup API. Closes #1147.

Relevant components:

  • Signalling server
  • Common library
  • Frontend library
  • Frontend UI library
  • Matchmaker
  • Platform scripts
  • SFU

Problem statement:

mediasoup 3.15.5 has no prebuilt worker for Linux kernel 7. On a kernel 7 host, installing the SFU compiles the worker from C++ source, which needs Python, meson and a C++ toolchain and takes minutes (#1147). #1186 stopped start.sh from installing the SFU, but SFU users and the SFU image still hit it. mediasoup has shipped kernel 7 prebuilts since 3.20.7.

The upgrade can't be a version bump alone. mediasoup-client has to move with it, the bridge uses APIs that were renamed or removed, and the SFU calls a private mediasoup method that no longer exists.

Solution

Versions

  • mediasoup 3.31.0: ~3.31.0 in the SFU, 3.31.0 as a dev dependency and ^3.31.0 as a peer of the bridge.
  • mediasoup-client 3.26.0 (peer range >=3.25.0 <3.27.0).
  • sdp-transform 3.0.0 in the bridge, matching mediasoup-client. 3.0 changed how a=msid is parsed.
  • The lockfile holds one copy of each, and its diff only touches these packages and their own dependencies.

Why 3.31.0 and not 3.32.x. mediasoup 3.32.0 replaced its bandwidth estimation with a new built-in stack. With it, a Firefox viewer's estimated available bitrate decays to the 30 kbps floor within about 20 seconds, even on a lossless localhost/LAN link. The simulcast consumer then has no layer it can afford, and the video freezes. On the same SFU at the same moment, Chrome viewers stay at about 11–14 Mbps. On 3.31.0, and on master's 3.15.5, Firefox is fine. I couldn't find an existing mediasoup report of this, and it will be reported upstream. ~3.31.0 keeps a lockfile refresh from pulling 3.32 back in.

SDP bridge port (fixes all 19 type errors listed in #1147 without casts)

  • Imports from mediasoup/types.
  • validateRtpCapabilities → validateAndNormalizeRtpCapabilities.
  • getExtendedRtpCapabilities(…, false) keeps the remote codec order used before. I diffed 3.10.1 against 3.26.0: the matching logic is otherwise the same.
  • The removed planB option is dropped.
  • The SDP objects now use real sdp-transform types instead of object/any. That exposed a new required codecs argument to getRtpEncodings, which now gets the producer codecs the same way mediasoup-client's own handlers pass them. The effect is that RTX is only added when an RTX codec is negotiated.
  • The unreachable {} media fallback now throws an explicit error.
  • toffset, which mediasoup no longer supports, is removed from the browser capabilities.

SFU

  • SCTP stream ids. transport.getNextSctpStreamId() no longer exists, so a streamer opening data channels would throw. The SFU now reserves ids through mediasoup's (still private) allocateSctpStreamId() in one helper. Unlike the old method, it marks the id as used, so DataConsumers can't be given the same id. Replaying the SFU's call order on real transports gives the same ids as before, in both multiplexed and per-peer mode. The trade-off: mediasoup never frees ids it gave to producers, so per-peer mode uses two ids on the streamer transport for each player join, out of 65,535, until the streamer reconnects.
  • Signals. mediasoup now listens for SIGINT/SIGTERM itself, closes the worker, and suppresses died. Registering those listeners stops Node from exiting on the signal, so the SFU would keep running, still connected to signalling, with no worker (docker stop would then wait and SIGKILL it). The SFU now exits on those signals itself.

Documentation

Changesets for @epicgames-ps/pixelstreaming-sfu and @epicgames-ps/mediasoup-sdp-bridge (both minor). The SFU changeset notes that kernel 5 hosts (Debian 11, Amazon Linux 2, Ubuntu 22.04 on its original kernel) no longer get a prebuilt, and that the kernel 7 prebuilt needs glibc 2.38 or newer. The current node:lts image (Debian 13, glibc 2.41) is fine.

Test Plan and Compatibility

  • Install. A clean npm ci takes about 7 s, builds the bridge in prepare, and downloads the worker prebuilt rather than compiling it.
  • Bridge end to end, in real Chrome. The browser acted as both streamer and player against a real mediasoup worker, in three modes: VP8, H.264 with RTX, and three-layer simulcast. In each one, both peers connected, the data channel opened, and the player decoded video and received audio.
  • Bridge output compared with master's. I ran the same Chrome offers through master's bridge and this one. The generated SDP differs only where mediasoup itself changed: no toffset extension, no audio abs-send-time, ice2 advertised, and the AV1 dependency descriptor offered for simulcast.
  • Live test with the minimal-streamer-5.7 release: Wilbur, the SFU, and several Chrome and Firefox viewers on the LAN.
    • Video. VP8, plus three-layer simulcast via -SimulcastParameters (that build uses the original Pixel Streaming plugin). The SFU created a simulcast producer, and viewers played through it.
    • Layers. Switching Preferred Quality changed the decoded resolution in both directions.
    • Streamer restart. The SFU re-subscribed on its own and new viewers played.
    • SIGTERM. It now exits immediately, takes its worker with it, and drops out of the streamer list.
  • Compared against master's SFU (mediasoup 3.15.5). For every problem below I ran the same scenario on master, and master's SFU behaves the same:

Not tested

Backport: UE5.8 only. The SFU and bridge package names carry no UE suffix, but package-lock.json will need regenerating on that branch rather than cherry-picking.

mediasoup 3.15.5 has no kernel 7 prebuilt, so installs compiled the worker from source. 3.31.0 ships one; 3.32's new BWE freezes Firefox viewers, so it is held below 3.32. Ports the bridge to mediasoup-client 3.26 and fixes the SFU's SCTP id allocation and signal handling for the new API.
@mcottontensor mcottontensor added auto-backport Used to specify we want a PR to auto backport to a branch, must be paired with auto-backport-to-UEX. auto-backport-to-UE5.8 labels Nov 5, 2026
@changeset-bot

changeset-bot Bot commented Nov 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5ecbe59

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-backport Used to specify we want a PR to auto backport to a branch, must be paired with auto-backport-to-UEX. auto-backport-to-UE5.8

Development

Successfully merging this pull request may close these issues.

[BUG] - mediasoup 3.15.5 has no kernel 7 prebuilt; npm install compiles the SFU worker from source

1 participant