Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CeeblueTV/wrts-client/llms.txt

Use this file to discover all available pages before exploring further.

Adaptive Bitrate (ABR) streaming automatically selects the highest video quality your viewer’s network can sustain, switching up or down as conditions change. In WebRTS, ABR is handled by HTTPAdaptiveSource — the default source implementation — which monitors buffer depth and bandwidth in real time, adjusting the active video rendition and playback rate to keep the stream close to the live edge while avoiding stalls.

How MBR Works

When playback starts, the player selects the middle-quality rendition from the available video tracks as a conservative starting point. From there it continuously evaluates two signals:
  1. Buffer depth (player.bufferAmount) — measured as the difference between the live point (endTime) and the current playback position.
  2. Bandwidth (player.recvByteRate) — a running average of received bytes per second, smoothed over the most recent GOP.
Based on these signals the source switches to a higher or lower video track:
  • BufferState.HIGH → buffer is ahead of the live edge; switch up to a higher-quality track if current bandwidth supports it.
  • BufferState.LOW → buffer is dangerously shallow; switch down to a lower-quality track to reduce download demand.

Playback Rate Adaptation

To stay close to the live edge and protect against stalls, the player also continuously adjusts <video>.playbackRate:
  • Buffer HIGH → increase rate up to 1.16× to drain excess buffer and re-synchronize with the live point.
  • Buffer LOW → decrease rate down to 0.84× to buy time for more data to arrive.
  • Buffer OK → reset to 1.0× (real-time).
This is handled by onBufferChange, which by default calls player.adjustPlaybackRate() with the default range (84 / 116 percent).
// Default behaviour — called automatically
player.onBufferChange = () => {
  player.adjustPlaybackRate(); // minRate=84, maxRate=116
};

iOS / Safari workaround

On iOS and Safari, playback rate changes can produce audible glitches. Detect the ManagedMediaSource global to skip rate adjustments on those platforms:
player.onBufferChange = () => {
  if (window.ManagedMediaSource) {
    return; // iOS / Safari — skip playbackRate adjustments
  }
  player.adjustPlaybackRate();
};

Narrowing the rate window

Pass custom minRate and maxRate percentages to adjustPlaybackRate() to tighten the adaptation range:
player.onBufferChange = () => {
  player.adjustPlaybackRate(92, 108); // ±8% instead of ±16%
};
The minimum increase step is internally clamped to 1.08× (PLAYBACK_RATE_MAX_FLOOR = 108), and the maximum decrease step is clamped to 0.92× (PLAYBACK_RATE_MIN_CEIL = 92). Setting maxRate=100 disables rate increases entirely; setting minRate=100 disables rate decreases.

Frame Skipping (Unreliable Mode)

When the network is severely degraded, switching down to a lower bitrate alone may not be enough to prevent a stall. In that case HTTPAdaptiveSource can skip sequence numbers to jump forward in the stream, discarding frames that will never arrive in time.
PropertyValueEffect
player.reliablefalse (default)Frame and sequence skipping enabled; player sacrifices completeness for latency
player.reliabletrueNo frames dropped; player buffers until data arrives
player.reliable must be set after onStart fires. The source is not initialized until that point — setting it on a stopped player throws: Cannot change reliability on stopped player.
When a skip occurs, the player fires onAudioSkipping(holeMs) or onVideoSkipping(holeMs) with the size of the gap in milliseconds.
player.onStart = () => {
  player.reliable = false; // default — frame skipping enabled
};

player.onAudioSkipping = (holeMs) => {
  console.warn(`Audio gap of ${holeMs} ms`);
};
player.onVideoSkipping = (holeMs) => {
  console.warn(`Video gap of ${holeMs} ms`);
};

Configuring Buffer Thresholds

The three buffer thresholds control when the player considers the buffer LOW, OK, or HIGH:
PropertyDefaultDescription
player.bufferLimitLow150 msBuffer depth below which state becomes LOW
player.bufferLimitHigh550 msBuffer depth above which state becomes HIGH
player.bufferLimitMiddle(computed)Read-only midpoint; used as the target for goLive()
bufferLimitMiddle is automatically recalculated as bufferLimitLow + (bufferLimitHigh - bufferLimitLow) / 2 whenever either threshold is set.

Network profile examples

Use these presets as a starting point for different network environments:
// Tight latency for a fast, reliable connection
player.bufferLimitLow  = 200;
player.bufferLimitHigh = 500;

Maximum Resolution Cap

By default, player.maximumResolution is set to Media.screenResolution() — the physical resolution of the viewer’s display. The ABR algorithm will never select a video track whose resolution exceeds this value, avoiding wasteful downloads on lower-resolution screens.
import { Media } from '@ceeblue/wrts-client';

// Restrict ABR to 720p maximum regardless of screen size
player.maximumResolution = { width: 1280, height: 720 };

// Remove the cap (allow any resolution)
player.maximumResolution = undefined;

Combining Audio and Video in a Single Request

player.tracksCombinable (default true) allows the source to combine audio and video into a single HTTP request, reducing the number of connections and lowering overhead on the server.
// Must be set after onStart fires
player.onStart = () => {
  // Disable combined requests (force separate audio and video fetches)
  player.tracksCombinable = false;
};
player.tracksCombinable must be set after onStart fires. Setting it on a stopped player throws: Cannot change tracksCombinable on stopped player.
Monitor player.latency to see how far behind the live edge your viewer is. It is computed as metadata.liveTime − currentTime in milliseconds. A healthy latency for WebRTS is typically under 2 seconds on a normal network.

Build docs developers (and LLMs) love