Pass iceServers to your sendTransport / recvTransport. Done.
mediasoup is an SFU. Each browser client opens a sendTransport (to push its camera/mic) and a recvTransport (to pull other producers). Both transports are RTCPeerConnections under the hood, so both need TURN configured for users on restrictive networks.
// npm install mediasoup-client
import { Device } from 'mediasoup-client';
const device = new Device();
await device.load({ routerRtpCapabilities });
const iceServers = [
{ urls: 'stun:stun.expressturn.com:3478' },
{
urls: [
'turn:relay1.expressturn.com:3478?transport=udp',
'turn:relay1.expressturn.com:3478?transport=tcp',
'turns:relay1.expressturn.com:443?transport=tcp'
],
username: 'YOUR_EXPRESSTURN_USERNAME',
credential: 'YOUR_EXPRESSTURN_PASSWORD'
}
];
const sendTransport = device.createSendTransport({
id, iceParameters, iceCandidates, dtlsParameters,
iceServers,
iceTransportPolicy: 'all'
});
const recvTransport = device.createRecvTransport({
id, iceParameters, iceCandidates, dtlsParameters,
iceServers
});
// Validate the TURN path actually works
const sendTransport = device.createSendTransport({
/* ... */, iceServers, iceTransportPolicy: 'relay'
});
mediasoup the server does not connect through TURN — only clients do. The mediasoup worker listens on a public IP/port pair you configure with announcedIp. Clients reach the worker either directly (UDP), via TCP fallback (mediasoup supports protocol: 'tcp' in webRtcTransport listenIps), or via TURN (relayed). All three should be available so ICE picks whichever works.
For production at scale, mint per-session credentials server-side instead of shipping a static password in your client bundle. See shared-secret examples. In a Node mediasoup signaling server:
import crypto from 'crypto';
function makeTurnCreds(secret) {
const ttl = 3600; // 1 hour
const username = `${Math.floor(Date.now()/1000) + ttl}:user`;
const credential = crypto.createHmac('sha1', secret).update(username).digest('base64');
return { username, credential };
}
// In your join-room handler, send to client alongside transport params:
socket.emit('joined', { iceServers: [{ urls: [...], ...makeTurnCreds(EXPRESSTURN_SECRET) }] });
Related: LiveKit recipe · Janus recipe · TURN for live streaming