PlainRTP Not outputting on UDP PORT

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 textconst 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 :slight_smile:

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.

1 Like

@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.