I tried to change to 1.2.3.4:5555 same same, could this be a bug? I am using ubuntu 16.04 LTS with no firewall enabled, no additional security, just a plain new machine
I don’t know what you mean, sorry. You have said nothing about setting warn logs and all logTags. Also it seems you finally have outbound bitrate so not sure what the problem is. BTW if you call connect with up:127.0.0.1 mediasoup will send data there. Whether your Linux (Docker?) is doing same destination mangling is not up to mediasoup.
I am using
Preformatted text
const plainTransport = await mediasoupRouter.createPlainTransport(
{
listenIp : ‘127.0.0.1’
});
await plainTransport.connect({ ip: ‘1.2.3.4’, port: 5555 })
and enabled all logtags with warn level:
worker: {
rtcMinPort: 10000,
rtcMaxPort: 10100,
logLevel: 'warn',
logTags: [
'info',
'ice',
'dtls',
'rtp',
'srtp',
'rtcp',
'rtx',
'bwe',
'score',
'simulcast',
'svc',
'sctp'
Here is my config i am loading into node:
module.exports = {
listenIp: '0.0.0.0',
listenPort: 4000,
sslCrt: '/etc/letsencrypt/live/test.com/cert.pem',
sslKey: '//etc/letsencrypt/live/test.com/privkey.pem',
mediasoup: {
// Worker settings
worker: {
rtcMinPort: 10000,
rtcMaxPort: 10100,
logLevel: 'warn',
logTags: [
'info',
'ice',
'dtls',
'rtp',
'srtp',
'rtcp',
'rtx',
'bwe',
'score',
'simulcast',
'svc',
'sctp'
],
},
// Router settings
router: {
mediaCodecs:
[
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters:
{
'x-google-start-bitrate': 1000
}
}
]
},
// WebRtcTransport settings
webRtcTransport: {
listenIps: [
{
ip: 'myrealip',
announcedIp: null,
}
],
maxIncomingBitrate: 1500000,
initialAvailableOutgoingBitrate: 1000000,
},
// PlainRtpTransportOptions
plainRtpTransport: {
listenIp: { ip: "127.0.0.1", announcedIp: null }
},
client: {
// ProducerOptions
videoProducer: {
// Send video with 3 simulcast streams
// RTCRtpEncodingParameters[]
encodings: [
{
maxBitrate: 100000
// maxFramerate: 15.0,
// scaleResolutionDownBy: 1.5,
},
{
maxBitrate: 300000
},
{
maxBitrate: 900000
}
],
codecOptions: {
videoGoogleStartBitrate: 1000
}
}
},
// Target IP and port for RTP recording
recording: {
ip: "127.0.0.1",
// GStreamer's sdpdemux only supports RTCP = RTP + 1
audioPort: 5004,
audioPortRtcp: 5005,
videoPort: 5006,
videoPortRtcp: 5007
}
}
};
MY LOG on NODE:
edia soup version: 3.5.5
client connected
Producer id generated!a7395f15-fa49-4578-9791-efa12d58ef34
Media soup version: 3.5.5
client connected
mediasoup:WARN:Channel [pid:21474] webrtc::FieldTrialParser::ParseFieldTrial() | Failed to read empty key field with value: 'Enabled' in trial: "Enabled,100" +0ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966780772, now_ms:966780778] +18ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966780774, now_ms:966780803] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966780776, now_ms:966780829] +26ms
mediasoup:WARN:Channel [pid:21474] webrtc::ProbeController::Process() | kWaitingForProbingResult: timeout +930ms
[ { bitrate: 527091,
byteCount: 164716,
firCount: 0,
fractionLost: 0,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 0,
nackPacketCount: 0,
packetCount: 152,
packetsDiscarded: 0,
packetsLost: 0,
packetsRepaired: 0,
packetsRetransmitted: 0,
pliCount: 0,
rtxSsrc: 530199477,
score: 10,
ssrc: 493316327,
timestamp: 966782767,
type: 'outbound-rtp' },
{ bitrate: 703888,
byteCount: 273284,
firCount: 0,
fractionLost: 5,
jitter: 18,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 3,
nackPacketCount: 5,
packetCount: 260,
packetsDiscarded: 0,
packetsLost: 3,
packetsRepaired: 3,
packetsRetransmitted: 19,
pliCount: 1,
roundTripTime: 78.5675048828125,
rtxSsrc: 3624894605,
score: 9,
ssrc: 3980577084,
timestamp: 966782767,
type: 'inbound-rtp' } ]
[ { bitrate: 660752,
byteCount: 327523,
firCount: 0,
fractionLost: 0,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 0,
nackPacketCount: 0,
packetCount: 305,
packetsDiscarded: 0,
packetsLost: 0,
packetsRepaired: 0,
packetsRetransmitted: 0,
pliCount: 0,
rtxSsrc: 530199477,
score: 10,
ssrc: 493316327,
timestamp: 966784770,
type: 'outbound-rtp' },
{ bitrate: 655760,
byteCount: 434867,
firCount: 0,
fractionLost: 0,
jitter: 14,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 3,
nackPacketCount: 5,
packetCount: 413,
packetsDiscarded: 0,
packetsLost: 3,
packetsRepaired: 3,
packetsRetransmitted: 19,
pliCount: 1,
roundTripTime: 94.1925048828125,
rtxSsrc: 3624894605,
score: 9,
ssrc: 3980577084,
timestamp: 966784770,
type: 'inbound-rtp' } ]
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784816, now_ms:966784825] +3s
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784818, now_ms:966784852] +27ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784820, now_ms:966784876] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784822, now_ms:966784903] +26ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784824, now_ms:966784931] +29ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966784826, now_ms:966784958] +28ms
[ { bitrate: 620138,
byteCount: 482342,
firCount: 0,
fractionLost: 0,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 0,
nackPacketCount: 0,
packetCount: 447,
packetsDiscarded: 0,
packetsLost: 0,
packetsRepaired: 0,
packetsRetransmitted: 0,
pliCount: 0,
rtxSsrc: 530199477,
score: 10,
ssrc: 493316327,
timestamp: 966786778,
type: 'outbound-rtp' },
{ bitrate: 615581,
byteCount: 588550,
firCount: 0,
fractionLost: 0,
jitter: 16,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 3,
nackPacketCount: 5,
packetCount: 555,
packetsDiscarded: 0,
packetsLost: 3,
packetsRepaired: 3,
packetsRetransmitted: 19,
pliCount: 1,
roundTripTime: 81.298828125,
rtxSsrc: 3624894605,
score: 9,
ssrc: 3980577084,
timestamp: 966786778,
type: 'inbound-rtp' } ]
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966786830, now_ms:966786837] +2s
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966786832, now_ms:966786865] +28ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966786834, now_ms:966786890] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966786836, now_ms:966786916] +26ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966786838, now_ms:966786942] +26ms
[ { bitrate: 596650,
byteCount: 632025,
firCount: 0,
fractionLost: 0,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 0,
nackPacketCount: 0,
packetCount: 588,
packetsDiscarded: 0,
packetsLost: 0,
packetsRepaired: 0,
packetsRetransmitted: 0,
pliCount: 0,
rtxSsrc: 530199477,
score: 10,
ssrc: 493316327,
timestamp: 966788780,
type: 'outbound-rtp' },
{ bitrate: 592195,
byteCount: 737105,
firCount: 0,
fractionLost: 5,
jitter: 12,
kind: 'video',
mimeType: 'video/VP8',
nackCount: 8,
nackPacketCount: 10,
packetCount: 696,
packetsDiscarded: 0,
packetsLost: 6,
packetsRepaired: 6,
packetsRetransmitted: 22,
pliCount: 1,
roundTripTime: 116.5618896484375,
rtxSsrc: 3624894605,
score: 9,
ssrc: 3980577084,
timestamp: 966788780,
type: 'inbound-rtp' } ]
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788817, now_ms:966788829] +2s
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788819, now_ms:966788856] +27ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788821, now_ms:966788881] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788823, now_ms:966788906] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788825, now_ms:966788932] +25ms
mediasoup:WARN:Channel [pid:21474] webrtc::BitrateProber::TimeUntilNextProbe() | probe delay too high [next_ms:966788827, now_ms:966788957] +25ms
^C
so problem fixed, right? If not, please clarify what exactly it’s not working as expected. BTW sending UDP packets from 127.0.0.1 to 1.2.3.4 may not be correct according to network routing rules, but again that’s not something related to mediasoup.
Please say clearly what is wrong. I see tons of logs and stats and still do not understand what is wrong.
Ok, i will try to make it shorter and clearer.
The problem i am facing now is that i can clearly see traffic on outbount=RTP with a given bitrate BUT the packets are not being sent from mediasoup to the configured UDP port in the connect function.
For example: if i have plainTransport.connect({ ip: ‘1.2.3.4’, port: 5555 }) , upon media consumption i should see my linux machine sending DATA sent to DESTINATION IP 1.2.3.4 port 5555, but it is not.
I cant see any traffic going from mediasoup to the external world. I tried replacing 1.2.3.4 with a local LAN IP and even tried the loopback internal 127.0.01 and the local IP of mediasoup, BUT I CANT get any data out from mediasoup. It looks like RTP is being consumed internally in the process but not sent to the UDP port.
The plan is to get data out on a given UDP port and then se FFMPEG to bind to that UDP PORT and use the data available there
So my question is could this be a bug? do you see the data being sent to DESTINATIONIP:PORT when using netstat ?
Thanks again for your help
Now you are speaking
Ok, I’ve done PlainRTP Not outputting on UDP PORT and then:
$ sudo tshark -i lo0 -n -s0 -f 'port 5555'
571 5.037532 127.0.0.1 → 127.0.0.1 UDP 221 42891 → 5555 Len=189
572 5.058897 127.0.0.1 → 127.0.0.1 UDP 221 42891 → 5555 Len=189
573 5.058973 127.0.0.1 → 127.0.0.1 UDP 1164 49783 → 5555 Len=1132
574 5.059008 127.0.0.1 → 127.0.0.1 UDP 1164 49783 → 5555 Len=1132
575 5.059083 127.0.0.1 → 127.0.0.1 UDP 1164 49783 → 5555 Len=1132
576 5.062316 127.0.0.1 → 127.0.0.1 RTCP 88 Sender Report Source description
577 5.081323 127.0.0.1 → 127.0.0.1 UDP 221 42891 → 5555 Len=189
578 5.092216 127.0.0.1 → 127.0.0.1 UDP 1017 49783 → 5555 Len=985
579 5.092334 127.0.0.1 → 127.0.0.1 UDP 1018 49783 → 5555 Len=986
580 5.097012 127.0.0.1 → 127.0.0.1 UDP 221 42891 → 5555 Len=189
581 5.119337 127.0.0.1 → 127.0.0.1 UDP 221 42891 → 5555 Len=189
582 5.130369 127.0.0.1 → 127.0.0.1 UDP 1199 49783 → 5555 Len=1167
583 5.130402 127.0.0.1 → 127.0.0.1 UDP 1200 49783 → 5555 Len=1168
584 5.141901 127.0.0.1 → 127.0.0.1 UDP 61 42891 → 5555 Len=29
585 5.169516 127.0.0.1 → 127.0.0.1 UDP 811 49783 → 5555 Len=779
586 5.169575 127.0.0.1 → 127.0.0.1 UDP 811 49783 → 5555 Len=779
587 5.203045 127.0.0.1 → 127.0.0.1 UDP 933 49783 → 5555 Len=901
588 5.203165 127.0.0.1 → 127.0.0.1 UDP 933 49783 → 5555 Len=901
589 5.203215 127.0.0.1 → 127.0.0.1 UDP 934 49783 → 5555 Len=902
590 5.236224 127.0.0.1 → 127.0.0.1 UDP 829 49783 → 5555 Len=797
591 5.236292 127.0.0.1 → 127.0.0.1 UDP 829 49783 → 5555 Len=797
592 5.274777 127.0.0.1 → 127.0.0.1 UDP 917 49783 → 5555 Len=885
593 5.274814 127.0.0.1 → 127.0.0.1 UDP 917 49783 → 5555 Len=885
It’s clear that there is outbound traffic.
Now, still I don’t know if you are using Docker with some special settings or whatever. AFAIK when using Docker with some other system (not my zone of comfort) such a system does some magic with traffic from/to 127.0.0.1 that may prevent you from seeing/getting it. No idea, it’s just something I read time ago.
Try not using 127.0.0.1 and instead use a private or public IP in your Linux host for both listenIp
and ip
in connect().
Hello,
I tried changing the listenip and connect ip to different other ips local and public and still same problem nothing goes. I also tried on a different server same problem, i am running out of ideas
This is my full code: The publisher and consumer work fine if i use a browser to publish and another browser to consume. The only thing not working is the plainRTPPORT. Can anyone in the community help me in running this code on their side?
NODE:
const mediasoup = require('mediasoup');
const fs = require('fs');
const https = require('https');
const express = require('express');
const socketIO = require('socket.io');
const config = require('./config');
// Global variables
let worker;
let webServer;
let socketServer;
let expressApp;
let producer;
let consumer;
let producerTransport;
let consumerTransport;
let mediasoupRouter;
(async () => {
try {
await runExpressApp();
await runWebServer();
await runSocketServer();
await runMediasoupWorker();
} catch (err) {
console.error(err);
}
})();
async function runExpressApp() {
expressApp = express();
expressApp.use(express.json());
expressApp.use(express.static(__dirname));
expressApp.use((error, req, res, next) => {
if (error) {
console.warn('Express app error,', error.message);
error.status = error.status || (error.name === 'TypeError' ? 400 : 500);
res.statusMessage = error.message;
res.status(error.status).send(String(error));
} else {
next();
}
});
}
async function runWebServer() {
const { sslKey, sslCrt } = config;
if (!fs.existsSync(sslKey) || !fs.existsSync(sslCrt)) {
console.error('SSL files are not found. check your config.js file');
process.exit(0);
}
const tls = {
cert: fs.readFileSync(sslCrt),
key: fs.readFileSync(sslKey),
};
webServer = https.createServer(tls, expressApp);
webServer.on('error', (err) => {
console.error('starting web server failed:', err.message);
});
await new Promise((resolve) => {
const { listenIp, listenPort } = config;
webServer.listen(listenPort, listenIp, () => {
const listenIps = config.mediasoup.webRtcTransport.listenIps[0];
const ip = listenIps.announcedIp || listenIps.ip;
console.log('server is running');
console.log(`open https://${ip}:${listenPort} in your web browser`);
resolve();
});
});
}
async function runSocketServer() {
socketServer = socketIO(webServer, {
serveClient: false,
path: '/server',
log: false,
});
socketServer.on('connection', (socket) => {
console.log("Media soup version: "+mediasoup.version);
console.log('client connected');
// inform the client about existence of producer
if (producer) {
socket.emit('newProducer');
}
socket.on('disconnect', () => {
console.log('client disconnected');
});
socket.on('connect_error', (err) => {
console.error('client connection error', err);
});
socket.on('getRouterRtpCapabilities', (data, callback) => {
callback(mediasoupRouter.rtpCapabilities);
});
socket.on('startRecording', async (data, callback) => {
callback(await startRecording());
});
socket.on('createProducerTransport', async (data, callback) => {
try {
const { transport, params } = await createWebRtcTransport();
producerTransport = transport;
callback(params);
} catch (err) {
console.error(err);
callback({ error: err.message });
}
});
socket.on('createConsumerTransport', async (data, callback) => {
try {
const { transport, params } = await createWebRtcTransport();
consumerTransport = transport;
callback(params);
} catch (err) {
console.error(err);
callback({ error: err.message });
}
});
socket.on('connectProducerTransport', async (data, callback) => {
await producerTransport.connect({ dtlsParameters: data.dtlsParameters });
callback();
});
socket.on('connectConsumerTransport', async (data, callback) => {
await consumerTransport.connect({ dtlsParameters: data.dtlsParameters });
callback();
});
socket.on('produce', async (data, callback) => {
const {kind, rtpParameters} = data;
producer = await producerTransport.produce({ kind, rtpParameters });
console.log("Producer id generated!"+producer.id);
callback({ id: producer.id });
// inform clients about new producer
socket.broadcast.emit('newProducer');
});
socket.on('consume', async (data, callback) => {
callback(await createConsumer(producer, data.rtpCapabilities));
});
socket.on('resume', async (data, callback) => {
await consumer.resume();
callback();
});
});
}
async function runMediasoupWorker() {
worker = await mediasoup.createWorker({
logLevel: config.mediasoup.worker.logLevel,
logTags: config.mediasoup.worker.logTags,
rtcMinPort: config.mediasoup.worker.rtcMinPort,
rtcMaxPort: config.mediasoup.worker.rtcMaxPort,
});
worker.on('died', () => {
console.error('mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
setTimeout(() => process.exit(1), 2000);
});
const mediaCodecs = config.mediasoup.router.mediaCodecs;
mediasoupRouter = await worker.createRouter({ mediaCodecs });
}
async function createWebRtcTransport() {
const {
maxIncomingBitrate,
initialAvailableOutgoingBitrate
} = config.mediasoup.webRtcTransport;
const transport = await mediasoupRouter.createWebRtcTransport({
listenIps: config.mediasoup.webRtcTransport.listenIps,
enableUdp: true,
enableTcp: true,
preferUdp: true,
initialAvailableOutgoingBitrate,
});
if (maxIncomingBitrate) {
try {
await transport.setMaxIncomingBitrate(maxIncomingBitrate);
} catch (error) {
}
}
return {
transport,
params: {
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters
},
};
}
async function createConsumer(producer, rtpCapabilities) {
if (!mediasoupRouter.canConsume(
{
producerId: producer.id,
rtpCapabilities,
})
) {
console.error('can not consume');
return;
}
try {
consumer = await consumerTransport.consume({
producerId: producer.id,
rtpCapabilities,
paused: producer.kind === 'video',
});
} catch (error) {
console.error('consume failed', error);
return;
}
if (consumer.type === 'simulcast') {
await consumer.setPreferredLayers({ spatialLayer: 2, temporalLayer: 2 });
}
return {
producerId: producer.id,
id: consumer.id,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters,
type: consumer.type,
producerPaused: consumer.producerPaused
};
}
async function startRecording() {
const plainTransport = await mediasoupRouter.createPlainTransport(
{
listenIp : '127.0.0.1',
comedia: false
});
await plainTransport.connect({ ip: '1.2.3.4', port: 5555 })
const consumer = await plainTransport.consume(
{
producerId : producer.id,
rtpCapabilities : mediasoupRouter.rtpCapabilities
});
setInterval(() =>
{
consumer.getStats()
.then(stats => console.warn(stats));
}, 2000);
}
Config.js for NODE
module.exports = {
listenIp: '0.0.0.0',
listenPort: 4000,
sslCrt: '/etc/letsencrypt/live/test.com/cert.pem',
sslKey: '//etc/letsencrypt/live/test.com/privkey.pem',
mediasoup: {
// Worker settings
worker: {
rtcMinPort: 4000,
rtcMaxPort: 10100,
logLevel: 'warn',
logTags: [
'info',
'ice',
'dtls',
'rtp',
'srtp',
'rtcp',
'rtx',
'bwe',
'score',
'simulcast',
'svc',
'sctp'
],
},
// Router settings
router: {
mediaCodecs:
[
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters:
{
'x-google-start-bitrate': 1000
}
}
]
},
// WebRtcTransport settings
webRtcTransport: {
listenIps: [
{
ip: '127.0.0.1',
announcedIp: null,
}
],
maxIncomingBitrate: 1500000,
initialAvailableOutgoingBitrate: 1000000,
},
// PlainRtpTransportOptions
plainRtpTransport: {
listenIp: { ip: "127.0.0.1", announcedIp: null }
},
client: {
// ProducerOptions
videoProducer: {
// Send video with 3 simulcast streams
// RTCRtpEncodingParameters[]
encodings: [
{
maxBitrate: 100000
// maxFramerate: 15.0,
// scaleResolutionDownBy: 1.5,
},
{
maxBitrate: 300000
},
{
maxBitrate: 900000
}
],
codecOptions: {
videoGoogleStartBitrate: 1000
}
}
},
// Target IP and port for RTP recording
recording: {
ip: "127.0.0.1",
// GStreamer's sdpdemux only supports RTCP = RTP + 1
audioPort: 5004,
audioPortRtcp: 5005,
videoPort: 5006,
videoPortRtcp: 5007
}
}
};
CLIENT CODE HTML AND JS:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes">
<link rel="icon" type="image/png" href="mediasoup.png" />
<title>Mediasoup Sample App</title>
</head>
<style>
body {
font: .9rem arial, serif;
}
table {
width: 100%;
max-width: 800px;
}
table td {
width: 33.33%;
vertical-align: top;
}
fieldset {
min-height: 100%;
border: 1px solid silver;
padding: 10px;
}
button {
padding: 5px;
margin: 5px;
}
video {
width: 100%;
}
span {
font-family: monospace;
}
</style>
<body>
<table>
<tr>
<td>
<div>Local</div>
<video id="local_video" controls autoplay playsinline></video>
</td>
<td>
<div>Remote</div>
<video id="remote_video" controls autoplay playsinline></video>
</td>
</tr>
</table>
<br>
<table>
<tr>
<td>
<fieldset id="fs_connection">
<legend>Connection</legend>
<div><button id="btn_connect">Connect</button> <span id="connection_status"></span></div>
<div><button id="btn_record">Record</button> <span id="connection_status"></span></div>
</fieldset>
</td>
<td>
<fieldset id="fs_publish" disabled>
<legend>Publishing</legend>
<div><label><input type="checkbox" id="chk_simulcast"> Use Simulcast</label></div>
<div>
<button id="btn_webcam">Start Webcam</button>
<span id="webcam_status"></span>
</div>
<div>
<button id="btn_screen">Share Screen</button>
<span id="screen_status"></span>
</div>
</fieldset>
</td>
<td>
<fieldset id="fs_subscribe" disabled>
<legend>Subscription</legend>
<div><button id="btn_subscribe">Subscribe</button> <span id="sub_status"></span></div>
</fieldset>
</td>
</tr>
</table>
</body>
<script>
// window.localStorage.setItem('debug', 'mediasoup-client:WARN* mediasoup-client:ERROR*');
window.localStorage.setItem('debug', 'mediasoup-client:*');
</script>
<script type="text/javascript" src="app-bundle.js"></script>
</html>
Client.js using libmediasooup
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes">
<link rel="icon" type="image/png" href="mediasoup.png" />
<title>Mediasoup Sample App</title>
</head>
<style>
body {
font: .9rem arial, serif;
}
table {
width: 100%;
max-width: 800px;
}
table td {
width: 33.33%;
vertical-align: top;
}
fieldset {
min-height: 100%;
border: 1px solid silver;
padding: 10px;
}
button {
padding: 5px;
margin: 5px;
}
video {
width: 100%;
}
span {
font-family: monospace;
}
</style>
<body>
<table>
<tr>
<td>
<div>Local</div>
<video id="local_video" controls autoplay playsinline></video>
</td>
<td>
<div>Remote</div>
<video id="remote_video" controls autoplay playsinline></video>
</td>
</tr>
</table>
<br>
<table>
<tr>
<td>
<fieldset id="fs_connection">
<legend>Connection</legend>
<div><button id="btn_connect">Connect</button> <span id="connection_status"></span></div>
<div><button id="btn_record">Record</button> <span id="connection_status"></span></div>
</fieldset>
</td>
<td>
<fieldset id="fs_publish" disabled>
<legend>Publishing</legend>
<div><label><input type="checkbox" id="chk_simulcast"> Use Simulcast</label></div>
<div>
<button id="btn_webcam">Start Webcam</button>
<span id="webcam_status"></span>
</div>
<div>
<button id="btn_screen">Share Screen</button>
<span id="screen_status"></span>
</div>
</fieldset>
</td>
<td>
<fieldset id="fs_subscribe" disabled>
<legend>Subscription</legend>
<div><button id="btn_subscribe">Subscribe</button> <span id="sub_status"></span></div>
</fieldset>
</td>
</tr>
</table>
</body>
<script>
// window.localStorage.setItem('debug', 'mediasoup-client:WARN* mediasoup-client:ERROR*');
window.localStorage.setItem('debug', 'mediasoup-client:*');
</script>
<script type="text/javascript" src="app-bundle.js"></script>
</html>
root@cdn-01:/opt/mediasoup# cat client.js
const mediasoup = require('mediasoup-client');
const socketClient = require('socket.io-client');
const socketPromise = require('./lib/socket.io-promise').promise;
const config = require('./config');
const hostname = window.location.hostname;
let device;
let socket;
let producer;
const $ = document.querySelector.bind(document);
const $fsPublish = $('#fs_publish');
const $fsSubscribe = $('#fs_subscribe');
const $btnConnect = $('#btn_connect');
const $btnRecord = $('#btn_record');
const $btnWebcam = $('#btn_webcam');
const $btnScreen = $('#btn_screen');
const $btnSubscribe = $('#btn_subscribe');
const $chkSimulcast = $('#chk_simulcast');
const $txtConnection = $('#connection_status');
const $txtWebcam = $('#webcam_status');
const $txtScreen = $('#screen_status');
const $txtSubscription = $('#sub_status');
let $txtPublish;
$btnConnect.addEventListener('click', connect);
$btnRecord.addEventListener('click', record);
$btnWebcam.addEventListener('click', publish);
$btnScreen.addEventListener('click', publish);
$btnSubscribe.addEventListener('click', subscribe);
if (typeof navigator.mediaDevices.getDisplayMedia === 'undefined') {
$txtScreen.innerHTML = 'Not supported';
$btnScreen.disabled = true;
}
async function record() {
$btnRecord.disabled = true;
$txtConnection.innerHTML = 'Connecting...';
const opts = {
path: '/server',
transports: ['websocket'],
};
const serverUrl = `https://${hostname}:${config.listenPort}`;
socket = socketClient(serverUrl, opts);
socket.request = socketPromise(socket);
socket.on('connect', async () => {
$txtConnection.innerHTML = 'Connected';
$fsPublish.disabled = false;
$fsSubscribe.disabled = false;
const data = await socket.request('startRecording');
});
socket.on('disconnect', () => {
$txtConnection.innerHTML = 'Disconnected';
});
socket.on('connect_error', (error) => {
console.error('could not connect to %s%s (%s)', serverUrl, opts.path, error.message);
$txtConnection.innerHTML = 'Connection failed';
$btnConnect.disabled = false;
});
}
async function connect() {
$btnConnect.disabled = true;
$txtConnection.innerHTML = 'Connecting...';
const opts = {
path: '/server',
transports: ['websocket'],
};
const serverUrl = `https://${hostname}:${config.listenPort}`;
socket = socketClient(serverUrl, opts);
socket.request = socketPromise(socket);
socket.on('connect', async () => {
$txtConnection.innerHTML = 'Connected';
$fsPublish.disabled = false;
$fsSubscribe.disabled = false;
const data = await socket.request('getRouterRtpCapabilities');
await loadDevice(data);
});
socket.on('disconnect', () => {
$txtConnection.innerHTML = 'Disconnected';
$btnConnect.disabled = false;
$fsPublish.disabled = true;
$fsSubscribe.disabled = true;
});
socket.on('connect_error', (error) => {
console.error('could not connect to %s%s (%s)', serverUrl, opts.path, error.message);
$txtConnection.innerHTML = 'Connection failed';
$btnConnect.disabled = false;
});
socket.on('newProducer', () => {
$fsSubscribe.disabled = false;
});
}
async function loadDevice(routerRtpCapabilities) {
try {
device = new mediasoup.Device();
} catch (error) {
if (error.name === 'UnsupportedError') {
console.error('browser not supported');
}
}
await device.load({ routerRtpCapabilities });
}
async function publish(e) {
const isWebcam = (e.target.id === 'btn_webcam');
$txtPublish = isWebcam ? $txtWebcam : $txtScreen;
const data = await socket.request('createProducerTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities,
});
if (data.error) {
console.error(data.error);
return;
}
const transport = device.createSendTransport(data);
transport.on('connect', async ({ dtlsParameters }, callback, errback) => {
socket.request('connectProducerTransport', { dtlsParameters })
.then(callback)
.catch(errback);
});
transport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
try {
const { id } = await socket.request('produce', {
transportId: transport.id,
kind,
rtpParameters,
});
callback({ id });
} catch (err) {
errback(err);
}
});
transport.on('connectionstatechange', (state) => {
switch (state) {
case 'connecting':
$txtPublish.innerHTML = 'publishing...';
$fsPublish.disabled = true;
$fsSubscribe.disabled = true;
break;
case 'connected':
document.querySelector('#local_video').srcObject = stream;
$txtPublish.innerHTML = 'published';
$fsPublish.disabled = true;
$fsSubscribe.disabled = false;
break;
case 'failed':
transport.close();
$txtPublish.innerHTML = 'failed';
$fsPublish.disabled = false;
$fsSubscribe.disabled = true;
break;
default: break;
}
});
let stream;
try {
stream = await getUserMedia(transport, isWebcam);
const track = stream.getVideoTracks()[0];
const params = { track };
if ($chkSimulcast.checked) {
params.encodings = [
{ maxBitrate: 100000 },
{ maxBitrate: 300000 },
{ maxBitrate: 900000 },
];
params.codecOptions = {
videoGoogleStartBitrate : 1000
};
}
producer = await transport.produce(params);
} catch (err) {
$txtPublish.innerHTML = 'failed';
}
}
async function getUserMedia(transport, isWebcam) {
if (!device.canProduce('video')) {
console.error('cannot produce video');
return;
}
let stream;
try {
stream = isWebcam ?
await navigator.mediaDevices.getUserMedia({ video: true }) :
await navigator.mediaDevices.getDisplayMedia({ video: true });
} catch (err) {
console.error('getUserMedia() failed:', err.message);
throw err;
}
return stream;
}
async function subscribe() {
const data = await socket.request('createConsumerTransport', {
forceTcp: false,
});
if (data.error) {
console.error(data.error);
return;
}
const transport = device.createRecvTransport(data);
transport.on('connect', ({ dtlsParameters }, callback, errback) => {
socket.request('connectConsumerTransport', {
transportId: transport.id,
dtlsParameters
})
.then(callback)
.catch(errback);
});
transport.on('connectionstatechange', async (state) => {
switch (state) {
case 'connecting':
$txtSubscription.innerHTML = 'subscribing...';
$fsSubscribe.disabled = true;
break;
case 'connected':
document.querySelector('#remote_video').srcObject = await stream;
await socket.request('resume');
$txtSubscription.innerHTML = 'subscribed';
$fsSubscribe.disabled = true;
break;
case 'failed':
transport.close();
$txtSubscription.innerHTML = 'failed';
$fsSubscribe.disabled = false;
break;
default: break;
}
});
const stream = consume(transport);
}
async function consume(transport) {
const { rtpCapabilities } = device;
const data = await socket.request('consume', { rtpCapabilities });
const {
producerId,
id,
kind,
rtpParameters,
} = data;
let codecOptions = {};
const consumer = await transport.consume({
id,
producerId,
kind,
rtpParameters,
codecOptions,
});
const stream = new MediaStream();
stream.addTrack(consumer.track);
return stream;
}
@ibc if you dont mind sharing your code i can try to run it here too and try to compare mine and yours to know where the exact problem is and report back
I just pasted my code block above at the top of the _createConsumer()
method in mediasoup-demo/server/lib/Room.js
.
Did you find out what the problem was? I think I am having the same issue.
Maybe can you create a new post with “correctly formatted” information to understand it?
Sorry, it was not a mediasoup problem.
I was using the output in ffmpeg and for some reason couldn’t get it. Now I’m using gstreamer and it works fine.
@OG-RTC IMHO I think you have missed to “link” between the webrtc producer (your send video/audio from endpoint like Chrome) and the plainTransport to get RTP packets from.
Based in you code, create a new function:
async function createPlainTransportTX() {
plainTransportTX = await mediasoupRouter.createPlainTransport(
{
listenIp : '127.0.0.1',
rtcpMux : false,
comedia : false
});
await plainTransportTX.connect({
ip: '127.0.0.1',
port: 5004,
rtcpPort: 5005
});
}
Add a new global variable plainTransportTX
:
// Global variables
let worker;
let webServer;
let socketServer;
let expressApp;
let producer;
let consumer;
let producerTransport;
let consumerTransport;
let mediasoupRouter;
let plainTransportTX;
Call at the beginning the function:
(async () => {
try {
await runExpressApp();
await runWebServer();
await runSocketServer();
await runMediasoupWorker();
await createPlainTransportTX();
} catch (err) {
console.error(err);
}
})();
Modify produce
event handler to link (route media) with plainTransport.consume
:
socket.on('produce', async (data, callback) => {
const {kind, rtpParameters} = data;
producer = await producerTransport.produce({ kind, rtpParameters });
callback({ id: producer.id });
const plainConsumer = await plainTransportTX.consume(
{
producerId: producer.id,
rtpCapabilities: mediasoupRouter.rtpCapabilities
}
)
// inform clients about new producer
socket.broadcast.emit('newProducer');
});
Try it. Of course you can improve this, but it is for test. Configure your ip and ports.