mediasoup-client: the browser half of a mediasoup SFU
mediasoup client side JavaScript library
At a glance
- What is it?
- mediasoup-client is the TypeScript library that runs in the browser for mediasoup based applications, handling device loading, transports, producers and data channels. It deliberately leaves signaling to you, which makes the library small and the integration work real.
- Who is it for?
- Adopt mediasoup-client if you are building a browser client for a mediasoup server and want the transport, producer and data channel plumbing handled by the same library the server side pairs with.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One library, one side of the connection
mediasoup-client is described as a TypeScript client side library for building mediasoup based applications. That phrase does the work: this is the piece that runs in the browser, and it is meant to be paired with a mediasoup server that does the routing.
The repository topics make the domain explicit, with webrtc, sfu, client-library, javascript and nodejs. The name of the project is a little misleading if you have not met mediasoup before, because this package does not implement a Selective Forwarding Unit. It is the browser half of a system where a server does the forwarding.
The split of responsibility is visible in the example code, because the very first import of anything other than the library is the application's own signaling module. Every later step, from getting the router capabilities to creating a transport to producing a track, goes through a call into that module.
The project belongs to versatica, the same organisation behind mediasoup itself, and the authors named in the README are Iñaki Baz Castillo and José Luis Millán. Documentation lives on mediasoup.org, and there is a support forum on mediasoup.discourse.group. Funding runs through Open Collective.
The device is loaded with the router's capabilities
The library starts with a Device object, and the example shows the shape of the opening moves:
import { Device } from 'mediasoup-client';
import mySignaling from './my-signaling'; // Our own signaling stuff.
// Create a device (use browser auto-detection).
const device = new Device();The comment on the constructor matters, because the constructor does nothing on its own. A device has to be loaded with the router's RTP capabilities before it can do anything, and those capabilities come from the server, not from the browser. The application asks its signaling layer for them and hands them over:
// Load the device with the router RTP capabilities.
await device.load({ routerRtpCapabilities });This is the point where the browser's own codec knowledge and the server's router configuration have to agree. The device detects what the browser can do, the router tells the server what the application is willing to receive, and the load call is where the two are reconciled. A mismatch surfaces here rather than later at connection time, which is why the example checks device.canProduce('video') immediately after loading and warns when the browser cannot produce video to that router.
The device is also where SCTP capabilities come from. In the example they are sent to the server as part of the transport creation request, so the server can build a transport that matches what the browser can actually do over a data channel.
Transports are created server side, then mirrored locally
A transport in mediasoup is a server-side object, so the client cannot create one on its own. The example asks the server to create it and passes the browser's SCTP capabilities in the request, then mirrors the returned parameters on the client side.
The server call returns an id together with iceParameters, iceCandidates, dtlsParameters and sctpParameters, and those same values go into device.createSendTransport. The client-side object is a local representation of the server-side transport, and the two are kept in step by the application, not by the library.
That division has a consequence worth internalising: the transport is only half alive until the connect event fires. The library emits the event with the local DTLS parameters, the application forwards them to the server, and the server confirms. The library is a state machine with a callback protocol, not a connection manager that talks to the server by itself.
This is also why the library is not usable without a signaling implementation. The mySignaling import in the example is a placeholder for whatever your application already has: a WebSocket channel, a plain HTTP request, or something else. The library never assumes one, which keeps it usable in any deployment, but it does mean you write that part yourself.
connect, produce and producedata follow one callback shape
The transport's events follow a consistent pattern, and reading one tells you the rest. The connect handler receives the local parameters, forwards them, and then calls the callback on success or errback on failure:
// Set transport "connect" event handler.
sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {The produce handler has the same shape, with the kind, rtpParameters and appData passed to the server, and the resulting producer id handed back through the callback. The producedata handler does the same for a data channel, forwarding sctpStreamParameters, label, protocol and appData.
The consistency is not cosmetic. Every one of these events is a request-response pair where the client cannot proceed until the server answers, and the two-argument callback and errback protocol makes the error path explicit rather than leaving a transport half connected. The comment in the example marks the intent, something was wrong on the server side, and errback is how that gets reported back into the transport.
The produce call itself is where a media track enters the system, and the data channel has its own path:
// Produce our webcam video.
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const webcamTrack = stream.getVideoTracks()[0];
const webcamProducer = await sendTransport.produce({ track: webcamTrack });The library wraps the browser's own media APIs rather than replacing them. getUserMedia and the track objects are standard WebRTC, and the track is what you hand to produce. Data channels are produced with produceData and take an ordered flag and a label, so a data channel is a first class citizen in the same transport as the media rather than a separate mechanism.
The exports map is the API surface
The package manifest describes how the library is meant to be consumed, and the exports map is the interesting part:
{
"name": "mediasoup-client",
"version": "3.25.1",
"description": "mediasoup client side TypeScript library",
"license": "ISC",
"main": "lib/index.js",
"types": "lib/index.d.ts",
}Beyond the root entry there are subpath exports for types, for ortc, for enhancedEvents, for handlers with a wildcard pattern, and for fakeParameters, which lives under the test directory and exists so that browser-dependent code can be exercised with synthetic parameters.
That ortc subpath is the one to know about. The ORTC layer is the low level API that sits under the higher level transport and producer abstractions, and a subpath export lets an application reach for it without pulling it into the main bundle. The handlers wildcard serves the same purpose for browser specific handler code, keeping each handler importable on its own.
The distribution is plain JavaScript with declaration files, the main entry is lib/index.js and the types entry is lib/index.d.ts, and the published files are the LICENSE, the README, npm-scripts.mjs and the lib directory. The engines field requires node 22 or newer, which is a build and tooling constraint rather than a browser runtime requirement.
The repository is tooling around a small source tree
The project is a TypeScript library with a modest amount of custom tooling. The top level holds src/, tsconfig.json, jest.config.mjs, eslint.config.mjs, knip.config.mjs, .prettierrc.json, .editorconfig, .npmrc and npm-scripts.mjs, and there is a package-lock.json rather than a lockfile per package manager.
Almost every npm script delegates to npm-scripts.mjs, which is an unusual but tidy pattern. The prepare and prepublishOnly scripts call it with those task names, and the same applies to typescript:build, typescript:watch, lint, format, test and coverage, plus a publish:dry-run. The effect is that the real logic lives in one JavaScript file that the scripts call, rather than in long inline command lines in the manifest.
The CI setup is visible in the badge list at the top of the README. There are separate workflows for the library itself, for publishing to npm, for updating the mediasoup website and for updating the demo, which tells you that the demo application and the documentation site are part of the release loop rather than manual afterthoughts.
The default branch is v3, so the v3 line is where development happens, and the release cadence is visible in the tags: 3.25.1 and 3.25.0 were both published on 2026-10-19, and 3.24.5 on 2026-10-08. The last push was on 2026-10-19 as well.
What the library does not give you, and when to reach past it
The boundary is clear once you look at the example. mediasoup-client does not provide signaling, it does not provide a server, and it does not include an application. It gives you the browser side of the protocol conversation and a callback-based state machine for transports, producers and data channels.
That makes the alternative worth naming. A browser application can talk WebRTC to a peer directly, with no SFU in the middle, using the platform's own RTCPeerConnection. The difference in approach is the topology and the control: direct peer connections give every participant a connection to every other participant, while a selective forwarding unit relays, so the server decides what is forwarded to whom and the client library exists to negotiate with that relay rather than with a peer.
For one-to-one or very small calls, the platform APIs are enough and adding mediasoup buys nothing. It pays off when the server needs to be in the path, for transcoding, selective recording, bandwidth adaptation, or routing decisions that a mesh of peer connections cannot express.
Two practical notes for adoption. The library is versioned in step with the server side, so a mismatch between the client version and your mediasoup server version is the first thing to check when a connection does not come up. And the ISC licence is permissive, with the source and its licence file published alongside the build, which matters little for a browser bundle and more if you redistribute a modified copy.
Editorial conclusion
Adopt mediasoup-client if you are building a browser client for a mediasoup server and want the transport, producer and data channel plumbing handled by the same library the server side pairs with. Skip it if you have no mediasoup deployment to talk to, since the library expects a router whose RTP capabilities it can load, or if you want a component that includes signaling, because the example imports your own signaling module and every transport event is a round trip you have to implement. Before the first integration, check that the npm version matches your server, since the v3 branch is the default and 3.25.1 was published on 2026-10-19, and confirm your build tooling satisfies the node >=22 engine requirement. The licence is ISC.
Frequently asked questions
What does mediasoup-client actually do in the browser?
It handles the client side of a mediasoup connection: creating the Device, loading it with the router RTP capabilities, mirroring server transports locally, and producing media tracks and data channels. Signaling, the server and the application logic are not part of it.
How do I send a webcam track with mediasoup-client?
Get the media stream with navigator.mediaDevices.getUserMedia, take the video track from it, and pass that track to sendTransport.produce. The produce event handler on the transport forwards the RTP parameters to your server and hands the producer id back through the callback.
Can mediasoup-client send data channels as well as media?
Yes. Data channels are produced with sendTransport.produceData, taking an ordered flag and a label, and the producedata event handler on the transport forwards the stream parameters to the server in the same callback pattern used by the media events.
What are the requirements for using mediasoup-client?
The package requires node 22 or newer for its build and tooling, and the application needs a mediasoup server to talk to plus its own signaling channel, since the example imports a signaling module of your own. The licence is ISC and the default branch is v3.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/versatica-mediasoup-client)Was this useful?