Near zero latency webrtc using mediamtx for screensharing

Last Updated or created 2026-04-07

My girlfriend and some of my nerd friends often want to show something on their desktop, or I want to show something thats easily remote viewable.
Sometimes we use screen share in Jitsi for this.

But I wanted a better solution, which is also usable for OBS usage. Or even share my webcams remote.

So I started testing forwarding rtmp/rtsp using apache, portforwarding and nginx.

These where not to my liking … until I found MediaMTX

https://github.com/bluenviron/mediamtx

This is a awesome tool, but configuring is something else.

Now I’ve got a setup which can do:

  • Forward rtmp streams from my webcams (pull from the camera)
  • Publish my OBS screen on a website
  • Share browser-tabs, complete browser windows and whole desktop screens using webrtc (With sound)

Works on all browsers supporting webrtc

I used coturn and stun in a previous test, not needed anymore

Mediamtx.yml config

webrtc: yes
webrtcAllowOrigins: ["*"]
webrtcAdditionalHosts: [xyz.henriaanstoot.nl]

webrtcEncryption: yes
webrtcServerCert: server.crt
webrtcServerKey: server.key

webrtcAddress: :8889
webrtcLocalUDPAddress: :8189

paths:
  cam:
    source: rtsp://admin:adminpass@192.168.178.2/h265Preview_01_main
  live/test:
    source: publisher
  streamme:
    runOnInit: ""

server.html

<!DOCTYPE html>
<html>
<body>

<button onclick="startStreaming()">Start Screen Share</button>
<br><br>

<video id="preview" autoplay playsinline muted style="width: 600px;"></video>

<script>
async function startStreaming() {
	const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
  ]
});

  const stream = await navigator.mediaDevices.getDisplayMedia({
    video: true,
    audio: true
  });

  const video = document.getElementById("preview");
  video.srcObject = stream;

  stream.getTracks().forEach(track => pc.addTrack(track, stream));

  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);

  const res = await fetch("https://xyz.henriaanstoot.nl/streamme/whip", {
    method: "POST",
    headers: { "Content-Type": "application/sdp" },
    body: offer.sdp
  });

  const answerSDP = await res.text();

  await pc.setRemoteDescription({
    type: "answer",
    sdp: answerSDP
  });
}
</script>

</body>
</html>

client.html

<!DOCTYPE html>
<html>
<body>

<h2></h2>

<video id="video" autoplay playsinline controls style="width: 800px;"></video>

<script>
async function startViewing() {
	const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
  ]
});

  const video = document.getElementById("video");

  // Receive tracks
  pc.ontrack = (event) => {
    video.srcObject = event.streams[0];
  };

  // Create offer (recvonly)
  const offer = await pc.createOffer({
    offerToReceiveVideo: true,
    offerToReceiveAudio: true
  });

  await pc.setLocalDescription(offer);

  // Send to MediaMTX (WHEP endpoint)
  const res = await fetch("https://xyz.henriaanstoot.nl/streamme/whep", {
    method: "POST",
    headers: { "Content-Type": "application/sdp" },
    body: offer.sdp
  });

  const answerSDP = await res.text();

  await pc.setRemoteDescription({
    type: "answer",
    sdp: answerSDP
  });
}

// auto start
startViewing();
</script>

</body>
</html>
Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *