mediasoup-client for WebRTC
mediasoup-client is a TypeScript client-side library designed for building robust WebRTC applications that interact with a mediasoup Selective Forwarding Unit (SFU) server. Currently at version 3.19.0, it provides a low-level, signaling-agnostic API that abstracts away much of the underlying WebRTC/ORTC complexities. Unlike its v2 predecessor, v3 removes the 'Peer' concept, focusing directly on Devices, Transports, Producers, and Consumers, offering greater flexibility. It's actively maintained with a consistent release cadence to align with WebRTC standards and mediasoup server updates, making it a powerful choice for developers requiring fine-grained control over their real-time media flows.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'sctpCapabilities')
cause `device.load()` has not been called or completed successfully before attempting to access `device.sctpCapabilities` or create a transport.fixEnsure `await device.load({ routerRtpCapabilities });` is called and has resolved with valid capabilities from the server before proceeding to create transports or access device properties. -
Mediasoup-client: `Device` not loaded
cause Attempting to create a `SendTransport` or `RecvTransport` instance, or perform other operations requiring a loaded device, when `device.load()` has not been successfully invoked.fixAlways call `await device.load({ routerRtpCapabilities });` and wait for its completion before performing any operations that depend on the `Device` being initialized with router capabilities. -
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp.
cause This generic WebRTC error often indicates a mismatch in RTP capabilities negotiated between the client and the mediasoup server during the signaling phase, or incorrect SDP provided by the signaling server.fixThoroughly review the `routerRtpCapabilities` retrieved from the server, ensure `device.load()` is using the correct data, and verify that the `dtlsParameters` and other transport-related parameters exchanged via your signaling server are accurate and consistent with the mediasoup server's expectations. -
Error: Transport has already been connected
cause The `sendTransport.on('connect')` event handler's `callback()` function was invoked multiple times, or the `connect` event itself was triggered more than once for the same transport instance due to a signaling race condition.fixEnsure that your signaling server logic handles transport connection idempotently. The `callback()` should only be called once per `connect` event. Implement safeguards on the client or server to prevent duplicate connection requests.
Warnings
- breaking `mediasoup-client` v3 is strictly compatible ONLY with `mediasoup` (server) v3. Attempting to use it with a v2 `mediasoup` server will lead to incompatibility issues and errors.
- breaking The `Peer` abstraction was removed in `mediasoup-client` v3. Applications must now directly manage `Device`, `Transport`, `Producer`, and `Consumer` entities.
- breaking The signatures for `sendTransport.on('produce')` and `sendTransport.on('producedata')` event handlers, specifically their `callback` and `errback` arguments, have changed in v3.
- gotcha `mediasoup-client` is signaling-agnostic and requires developers to implement their own signaling layer (e.g., via WebSockets) to communicate with the `mediasoup` server. The `mySignaling` object in examples is a placeholder.
- gotcha While primarily a browser library, `mediasoup-client` can be used in Node.js environments but explicitly requires Node.js version 22 or higher as per its `engines` field.
Install
-
npm install mediasoup-client -
yarn add mediasoup-client -
pnpm add mediasoup-client
Imports
- Device
const Device = require('mediasoup-client').Device;import { Device } from 'mediasoup-client'; - Producer
import { MediaProducer } from 'mediasoup-client';import { Producer } from 'mediasoup-client'; - DataProducer
import { DataChannelProducer } from 'mediasoup-client';import { DataProducer } from 'mediasoup-client';
Quickstart
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();
// Communicate with our server app to retrieve router RTP capabilities.
const routerRtpCapabilities = await mySignaling.request(
'getRouterCapabilities'
);
// Load the device with the router RTP capabilities.
await device.load({ routerRtpCapabilities });
// Check whether we can produce video to the router.
if (!device.canProduce('video')) {
console.warn('cannot produce video');
// Abort next steps.
}
// Create a transport in the server for sending our media through it.
const { id, iceParameters, iceCandidates, dtlsParameters, sctpParameters } =
await mySignaling.request('createTransport', {
sctpCapabilities: device.sctpCapabilities,
});
// Create the local representation of our server-side transport.
const sendTransport = device.createSendTransport({
id,
iceParameters,
iceCandidates,
dtlsParameters,
sctpParameters,
});
// Set transport "connect" event handler.
sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
// Here we must communicate our local parameters to our remote transport.
try {
await mySignaling.request('transport-connect', {
transportId: sendTransport.id,
dtlsParameters,
});
// Done in the server, tell our transport.
callback();
} catch (error) {
// Something was wrong in server side.
errback(error);
}
});
// Set transport "produce" event handler.
sendTransport.on(
'produce',
async ({ kind, rtpParameters, appData }, callback, errback) => {
// Here we must communicate our local parameters to our remote transport.
try {
const { id } = await mySignaling.request('produce', {
transportId: sendTransport.id,
kind,
rtpParameters,
appData,
});
// Done in the server, pass the response to our transport.
callback({ id });
} catch (error) {
// Something was wrong in server side.
errback(error);
}
}
);
// Set transport "producedata" event handler.
sendTransport.on(
'producedata',
async (
{ sctpStreamParameters, label, protocol, appData },
callback,
errback
) => {
// Here we must communicate our local parameters to our remote transport.
try {
const { id } = await mySignaling.request('produceData', {
transportId: sendTransport.id,
sctpStreamParameters,
label,
protocol,
appData,
});
// Done in the server, pass the response to our transport.
callback({ id });
} catch (error) {
// Something was wrong in server side.
errback(error);
}
}
);
// Produce our webcam video.
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const webcamTrack = stream.getVideoTracks()[0];
const webcamProducer = await sendTransport.produce({ track: webcamTrack });
// Produce data (DataChannel).
const dataProducer = await sendTransport.produceData({
ordered: true,
label: 'foo',
});