Tech

WebRTC live streaming

  • Sub-second Latency
  • WebRTC
  • WHIP
  • ABR
  • SFU

Publish and play live streams with ultra-low latency using WebRTC. The Media Server handles stream delivery to your viewers.

  • Web
  • iOS
  • Android

Overview

Live Streaming means that video is captured from a camera or another source and transmitted to viewers in real time.

Unlike on-demand video, a live stream has no future content to skip ahead to. Viewers typically join at the most recent point of the stream to see what's happening right now.

But here's the catch: not every live stream is truly live.

There's always some delay between capturing a video frame and displaying it on the viewer's screen. This delay, known as latency, can vary dramatically depending on the streaming technology.

The same person waving in two video frames side by side, the right frame a moment behind the left.

Some live streams achieve sub-second latency, while others can lag more than 30 seconds behind the actual event.

So what makes the difference? Let's take a closer look.

WebRTC live streaming

WebRTC offers exceptionally broad browser support and some of the lowest latency available for live streaming. We're talking about that sub-second latency everyone wants.

But how does WebRTC achieve it?

A developer holding a large stopwatch.

Two key mechanisms make this possible:

  1. UDP Transport. WebRTC typically delivers media over UDP, allowing the streaming stack to prioritize timely delivery over guaranteed delivery. Packets that arrive too late can be discarded rather than holding up playback.
  2. Congestion Control and Bitrate Targeting. WebRTC continuously evaluates network conditions using RTCP feedback. This allows the sender to adjust its bitrate, respond to packet loss, and keep latency under control as available bandwidth changes.

In other words, WebRTC isn't just sending video in one direction. It's an intensive, two-way exchange of media packets and feedback. Depending on network conditions and the implementation, RTCP feedback and other return traffic can become substantial.

And this low latency comes at a price: additional network overhead and server-side processing to continuously estimate available bandwidth, adjust transmission rates, and manage packet delivery in real time.

WebRTC vs HLS

Imagine traditional HLS delivery as a freight train traveling steadily from point A to point B. Each carriage carries a chunk of video. The train isn't particularly fast, but it moves large amounts of content efficiently and predictably.

WebRTC is more like a high-speed highway.

Instead of waiting for entire video chunks, small pieces of video travel in fast-moving cars. They leave frequently, adapt to traffic conditions, and aim to reach their destination as quickly as possible.

Some cars never make it to point B. Those are the lost UDP packets that couldn't be recovered in time.

And that's part of the design: for real-time streaming, receiving the next video frame on time can be more important than recovering every missing piece of the previous one.

How WebRTC publishing works

WebRTC publishing works the same way on every platform. The publisher's application uses an SDK to open a session with the Media Server, captures the camera and microphone, and publishes them as a stream with a unique name.

A developer surrounded by capture devices: cameras, microphones, a webcam, headphones and a megaphone.

The SDK handles session negotiation, media transport and congestion control, adjusting the bitrate to the network as it changes. The application only decides what to capture and under which name to publish it.

The Media Server receives that single stream and keeps it ready for viewers: however many of them play it, the publisher sends it only once.

Now let's see how to publish a live stream with each of the four SDKs.

The code looks a little different on each platform, but the idea is the same: capture the camera and microphone, establish a streaming session, and publish the stream to the Media Server.

Web SDK

In a browser, publishing is handled by the Web SDK. It captures the camera and microphone itself, so a few lines are enough:

session
  .createStream({
    name: 'alice-live',
    display: localPreview,
    constraints: {
      video: { width: 1280, height: 720 },
      audio: true,
    },
  })
  .publish();

The stream name is set by name: 'alice-live': viewers play the stream by this name.

Explore Web SDK↗

Android SDK

On Android the app asks for the camera and microphone permissions, then publishes the stream. The SDK captures the camera by default:

StreamOptions options = new StreamOptions("alice-live");
VideoConstraints video = new VideoConstraints();
video.setResolution(1280, 720);
options.getConstraints().setVideoConstraints(video);

Stream stream = session.createStream(options);
stream.publish();

The stream name is set by new StreamOptions("alice-live"): viewers play the stream by this name.

Explore Android SDK↗

iOS SDK

On iOS the SDK captures the camera and microphone and shows the local preview in the view you pass to it:

let options = FPWCSApi2StreamOptions()
options.name = "alice-live"
options.display = localView

let stream = try session.createStream(options)
try stream.publish()

The stream name is set by options.name = "alice-live": viewers play the stream by this name.

Explore iOS SDK↗

SFU SDK

In a video room the camera and microphone are tracks of the participant's connection. The SFU SDK publishes them to the room and tells it what each track carries:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: true,
  video: { width: 1280, height: 720 },
});

const config = {};
stream.getTracks().forEach((track) => {
  config[track.id] =
    track.kind === 'audio' ? 'mic' : 'camera';
  pc.addTransceiver(track, {
    direction: 'sendonly',
    streams: [stream],
  });
});

await room.updateState(config);

Each track is described by its id in config[track.id]: the room knows which track is the camera and which is the microphone.

Explore SFU client example↗

WebRTC publishing controls

Publishing starts with a simple stream.publish() call, available in the Web SDK, Android SDK, and iOS SDK. But starting a stream is only the beginning.

A developer at a colourful control desk, one hand on a lever and the other on a knob.

What if you want more control over the stream you're sending?

You can choose the camera and microphone, set the resolution, frame rate and bitrate, adjust the microphone gain, turn on stereo and error correction for audio, tell the browser whether to keep the frame rate or the resolution, mute either track without stopping the stream, and choose the transport and codecs to negotiate.

Let's take a closer look at the available controls.

Video

Choose the camera to publish from.

The browser captures the camera at this size.

Frames per second the camera is asked to capture.

The upper limit for the browser’s video encoder.

Motion keeps the frame rate; detail and text keep the resolution, for screen sharing.

CPU overuse detection: the browser lowers the resolution when the CPU is overloaded.

Send the video’s orientation with it, so a picture from a turned phone plays upright.

Stop sending video frames without stopping the stream.

Audio

Choose the microphone to publish from.

50%

Turn the microphone up or down while publishing, in Chromium-based browsers.

The bitrate of the Opus audio encoder.

Send stereo audio when the codec supports it.

Opus forward error correction against packet loss.

Stop sending sound without stopping the stream.

Stream

Choose the preferred transport for sending WebRTC media packets. The actual transport depends on ICE negotiation.

Exclude codecs

Exclude selected codecs from WebRTC negotiation. Use this to limit negotiation to the codecs you want to allow.

Explore Media Devices example↗

WebRTC-HTTP ingestion protocol

WHIP is a way to publish WebRTC streams without using an SDK.

Let's see how it works with OBS Studio.

A developer holding a film clapperboard labelled OBS.

OBS sends an HTTP request containing an SDP offer to the Media Server. The server responds with an SDP answer, allowing both sides to negotiate codecs and connection parameters.

Once the WebRTC connection is established, OBS starts publishing the stream, and the Media Server takes care of delivering it to viewers.

Here is what it looks like in practice. In OBS Studio, choose the WHIP service, enter the Media Server URL with the stream name and, if publishing is protected, a Bearer token. Press Start Streaming, and the stream can be played back from the Media Server like any other.

OBS Studio: choose the WHIP service and enter the server URL
Optional Bearer token to authorize publishing
OBS Studio streaming to the Media Server over WebRTC
The stream plays back from the Media Server

Explore WHIP↗

How WebRTC playback works

Playback is the other half of live streaming. Each viewer's application uses the same SDKs to open its own session with the Media Server and plays the stream by the name it was published with. A new viewer does not touch the publisher: the Media Server adds one more WebRTC connection and sends a copy of the same stream to it.

A developer holding up a screen with a play button.

The SDK negotiates the connection, receives the media and renders it, while RTCP feedback keeps each viewer's delivery tuned to their own network. The result is the same sub-second latency for every viewer, whether they watch in a browser, in a mobile app or in a video room.

Now let's see how a viewer plays the live stream with each of the four SDKs.

Again, the code differs from platform to platform, but the idea is the same: establish a streaming session, request the stream by its name, and show it on the screen.

Web SDK

In a browser, the Web SDK plays the stream into the element you pass to it:

session
  .createStream({
    name: 'alice-live',
    display: remoteVideo,
  })
  .play();

The viewer plays the stream by the name it was published with: name: 'alice-live'.

Explore Web SDK↗

Android SDK

On Android the SDK renders the stream into the view you pass to it:

StreamOptions options = new StreamOptions("alice-live");
options.setRenderer(remoteRenderer);

Stream stream = session.createStream(options);
stream.play();

The viewer plays the stream by the name it was published with: new StreamOptions("alice-live").

Explore Android SDK↗

iOS SDK

On iOS the SDK shows the stream in the view you pass to it:

let options = FPWCSApi2StreamOptions()
options.name = "alice-live"
options.display = remoteView

let stream = try session.createStream(options)
try stream.play()

The viewer plays the stream by the name it was published with: options.name = "alice-live".

Explore iOS SDK↗

SFU SDK

In a video room a viewer joins the room and receives the tracks of the other participants. The SFU SDK announces each new track, and the app requests the ones it shows:

remoteVideo.srcObject = new MediaStream();

room.on(SFU_ROOM_EVENT.ADD_TRACKS, async (e) => {
  for (const info of e.info.info) {
    const remote = await room.getRemoteTrack(info.type);
    await remote.demandTrack(info.id);
    remoteVideo.srcObject.addTrack(remote.track);
  }
});

await room.join(pc);

Each track is requested by its id in demandTrack(info.id): the viewer receives only the tracks it shows.

Explore SFU client example↗

WebRTC playback controls

Playback starts with a simple stream.play() call, available in the Web SDK, Android SDK, and iOS SDK. But starting playback is only the beginning.

What if you want more control over the stream you're watching?

You can enable or disable video, request a specific resolution, bitrate, or quality level, choose which codecs to negotiate, and control how audio is played.

Let's take a closer look at the available controls.

A developer at a colourful control desk, one hand on a lever and the other on a knob.
Video

Enable the video track during playback.

The server transcodes the video to the requested resolution.

The server transcodes the video to the requested bitrate.

The server uses the requested video transcoding quality.

Publisher video

Muted

Indicates whether the publisher has muted the video track.

Not muted
Audio

Choose the audio output device.

75%

Adjust playback volume.

Request stereo audio when supported by the stream and negotiated codec.

Publisher audio

Talking

Indicates whether audio activity is detected in the publisher's stream.

Talking
Muted

Indicates whether the publisher has muted the audio track.

Not muted
Stream

Choose the preferred transport for receiving WebRTC media packets. The actual transport depends on ICE negotiation.

Exclude codecs

Exclude selected codecs from WebRTC negotiation. Use this to limit negotiation to the codecs you want to allow.

Explore Media Devices example↗

WebRTC ABR

Not every viewer has the same network. One watches over fiber, another over a crowded mobile connection. Send everyone the same high-bitrate stream, and the weakest connections start to stall.

WebRTC ABR (Adaptive Bitrate) solves this on the server side. The Media Server transcodes the incoming stream into several qualities, for example 720p, 480p and 240p, and keeps all of them ready for playback.

A developer holding three video frames of different sizes: 720p, 480p and 240p.

The viewer starts with one quality while the player keeps an eye on the playback statistics. When bandwidth drops or packets get lost, it switches to a lower quality; when the network recovers, it switches back up. The publisher still sends a single stream.

Why the SFU SDK? On the Media Server, WebRTC ABR is built on the SFU: the server creates an SFU room named after the stream and offers the qualities as the qualities of its video track. So the ABR player joins that room with the SFU SDK, which already knows how to receive several qualities of one track and request the one that fits the network.

SFU SDK - WebRTC ABR player

In a browser, WebRTC ABR is played with the SFU SDK. The player receives the qualities of the stream and switches between them on its own, watching the playback statistics:

const display = initRemoteDisplay(
  state.room,
  document.getElementById('remoteVideo'),
  { quality: true, autoAbr: true },
  {
    thresholds: [
      { parameter: 'nackCount', maxLeap: 10 },
      { parameter: 'freezeCount', maxLeap: 10 },
      { parameter: 'packetsLost', maxLeap: 10 },
    ],
    abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY,
    abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY,
    interval: ABR_QUALITY_CHECK_PERIOD,
  },
  // ...the meeting controller, model, view and participant factory
);

await state.room.join(state.pc, null, null, 1);

autoAbr: true turns automatic switching on; the thresholds list the statistics the player watches: NACKs, freezes and lost packets.

Explore WebRTC ABR Player example↗

WebRTC live streaming demo

No setup, no installation. Press Live, allow the camera and microphone, and watch your stream come back from the Media Server over WebRTC with sub-second latency.

Try Live Demo↗

Launch your WebRTC streaming server

LLcast will deploy a dedicated Media Server just for you, ready to receive your live streams. Publish from a browser or a mobile app and experiment with different settings.