Table of Contents
WebRTC adaptive bitrate control helps a live publisher keep streaming when network conditions change. We have a server-side ABR mechanism, which most customers use, utilizing transcoding. This is beneficial and more often used; however, it is tied to having a Stream Manager deployment and is more of a subscriber-side ABR (e.g., the server manages which stream to deliver to the subscriber based on the subscriber’s network conditions). This example is publisher-side ABR, delivering video to the server based on network conditions. The implementation is based on the Red5 WHIP Adaptive Bitrate Controller example.
This tutorial uses Red5 Pro, our self-hosted live streaming server software. The repository loads the Red5 HTML SDK and is designed to connect to either a standalone Red5 Pro deployment or Stream Manager 2.0. The SDK provides the browser-side publishing API, but it still requires a Red5 Pro server deployment to establish the WHIP session.
To follow this tutorial, log in to your Red5 Pro account. If you do not have an account yet, sign up for a 30-day Red5 Pro trial and use the resulting Red5 Pro deployment with the example.
Why Add WebRTC Adaptive Bitrate Control?
A WebRTC publisher may start with a strong connection and then encounter congestion, packet loss, or a sudden increase in round-trip time. If the sender continues transmitting at the same quality, viewers may see frozen video, dropped frames, or a failed publishing session.
WebRTC adaptive bitrate control responds by changing the sender’s encoding parameters while the session is active. A degraded state can apply a lower maximum bitrate and a larger resolution scale factor. After several healthy samples, the controller can restore the configured quality.
- Monitor the connection continuously.
- React to sustained degradation rather than one temporary spike.
- Reduce outgoing video quality while preserving the session.
- Restore quality only after the network has demonstrated recovery.
How WebRTC Adaptive Bitrate Control Works
The example publishes video with WHIPClient from the Red5 HTML SDK. Its configuration requests a statistics report every 1,000 milliseconds. Each report is passed to StatsMonitor, which processes the relevant outbound video and candidate-pair entries.
| WebRTC report | Signals used | Purpose |
|---|---|---|
outbound-rtp for video | packetsSent, retransmittedPacketsSent, pliCount, firCount | Estimate current loss and keyframe-request activity |
candidate-pair | currentRoundTripTime | Track round-trip time for the active connection |
The browser exposes these reports through the RTCPeerConnection.getStats() API. The example receives equivalent data through the Red5 SDK’s stats event transport.
Build the WebRTC Stats Monitor
Start by defining thresholds for the conditions that should trigger a quality reduction. The example treats the network as degraded when any one of its high thresholds is exceeded. It requires three consecutive samples before changing state, which prevents a single transient spike from causing an unnecessary quality change.
const thresholds = {
PACKET_LOSS_HIGH: 5,
PACKET_LOSS_RECOVERY: 2,
RTT_HIGH: 0.3,
RTT_RECOVERY: 0.15,
PLI_FIR_RATE_HIGH: 3,
PLI_FIR_RATE_RECOVERY: 1,
CONSECUTIVE_SAMPLES_TO_TRIGGER: 3
}
These values are example defaults, not universal WebRTC requirements. Tune them with measurements from your own application, codecs, devices, and network conditions.
Detect Network Degradation
WebRTC statistics such as packetsSent, retransmittedPacketsSent, pliCount, and firCount are cumulative counters. Comparing their total values directly can make old network problems look like current problems. Instead, store the previous sample and calculate the difference between two reports.
calculatePacketLossRate(packetsSent, retransmittedPacketsSent) {
if (this.state.previousPacketsSent === null) {
this.state.previousPacketsSent = packetsSent
this.state.previousRetransmittedPacketsSent = retransmittedPacketsSent
return 0
}
const sent = packetsSent - this.state.previousPacketsSent
const retransmitted = retransmittedPacketsSent -
this.state.previousRetransmittedPacketsSent
this.state.previousPacketsSent = packetsSent
this.state.previousRetransmittedPacketsSent = retransmittedPacketsSent
if (sent <= 0) return 0
return (retransmitted / sent) * 100
}
calculatePliFirRate(pliCount, firCount) {
if (this.state.previousPliCount === null) {
this.state.previousPliCount = pliCount
this.state.previousFirCount = firCount
return 0
}
const pliRate = pliCount - this.state.previousPliCount
const firRate = firCount - this.state.previousFirCount
this.state.previousPliCount = pliCount
this.state.previousFirCount = firCount
return pliRate + firRate
}
In this implementation, PLI and FIR requests act as a sender-side signal that the remote endpoint is requesting keyframes. They are combined into a per-interval rate. The monitor also stores the current round-trip time from the candidate-pair report.
Change Bitrate and Resolution Without Renegotiation
When the monitor detects a degraded state, locate the video RTCRtpSender, read its current parameters, update the first encoding, and apply the new values. The WebRTC sender API supports changing encoding and transmission parameters with setParameters().
const videoTrackSender = publisher
.getPeerConnection()
.getSenders()
.find(sender => sender.track?.kind === 'video')
if (videoTrackSender) {
const parameters = videoTrackSender.getParameters()
parameters.encodings ??= [{}]
parameters.encodings[0].maxBitrate = 300 * 1000
parameters.encodings[0].scaleResolutionDownBy = 8
await videoTrackSender.setParameters(parameters)
}
This example lowers the maximum video bitrate to 300 kbps and scales the encoded resolution down by a factor of eight. The values are intentionally visible and easy to change. The setParameters() documentation explains the sender parameter model and browser considerations.
Complete Adaptive Bitrate Control Example
The following controller connects the monitor’s state changes to the WebRTC sender. It reduces quality after three consecutive degraded samples and restores the target bitrate and original scale after three consecutive healthy samples.
const TARGET_BITRATE = 2500 * 1000
const LOSSY_BITRATE = 300 * 1000
const getVideoTrackSender = () => {
return publisher
.getPeerConnection()
.getSenders()
.find(sender => sender.track?.kind === 'video')
}
const setVideoQuality = async (maxBitrate, scaleResolutionDownBy) => {
const sender = getVideoTrackSender()
if (!sender) return
const parameters = sender.getParameters()
parameters.encodings ??= [{}]
parameters.encodings[0].maxBitrate = maxBitrate
parameters.encodings[0].scaleResolutionDownBy = scaleResolutionDownBy
await sender.setParameters(parameters)
}
statsMonitor.setOnLossy(async ({ packetLossRate, rtt, pliFirRate }) => {
await setVideoQuality(LOSSY_BITRATE, 8)
console.log('Network degraded', { packetLossRate, rtt, pliFirRate })
})
statsMonitor.setOnRecovery(async ({ packetLossRate, rtt, pliFirRate }) => {
await setVideoQuality(TARGET_BITRATE, 1)
console.log('Network recovered', { packetLossRate, rtt, pliFirRate })
})
In the repository example, the monitor is connected to a WHIPClient configured to emit a WebRTC.Stats.Report event every second. The full working implementation also includes publishing, controls, status indicators, and event handling.
Test WebRTC Adaptive Bitrate Control
Test the controller with a real publisher and a separate subscriber. Start the WHIP stream, confirm that the normal bitrate and resolution are applied, then introduce packet loss and delay on the publishing device. The controller should enter the degraded state only after the configured consecutive-sample count is reached.
- Open the example with the correct server, application, and stream parameters.
- Grant camera and microphone permissions, then start publishing.
- Watch packet-loss rate, RTT, PLI/FIR rate, bitrate, and outgoing frame dimensions.
- Use a network-throttling tool such as Network Link Conditioner from Apple to introduce delay or packet loss on macOS.
- Confirm that the viewer remains connected while the outgoing quality is reduced.
- Restore the network and confirm that quality returns after the recovery sample count is reached.
Do not validate the feature only by watching the publisher preview. The important result is the end-to-end behavior: the subscriber should continue receiving video while the publisher adapts, and the stream should recover without a new publish session.
Tune Thresholds for Your Application
The repository’s defaults are a starting point for testing. They use packet loss above 5%, RTT above 0.3 seconds, or more than three PLI/FIR requests in an interval to identify degradation. Recovery requires packet loss below 2%, RTT below 0.15 seconds, and a PLI/FIR rate at or below 1.
Adjust the thresholds according to the application’s tolerance for quality changes and temporary instability. A stricter controller may react sooner but can change quality too often. A more tolerant controller may avoid unnecessary reductions but allow visible problems to persist longer.
const monitor = new StatsMonitor({
thresholds: {
PACKET_LOSS_HIGH: 8,
RTT_HIGH: 0.5,
CONSECUTIVE_SAMPLES_TO_TRIGGER: 5
}
})
monitor.setThresholds({
PACKET_LOSS_RECOVERY: 3,
RTT_RECOVERY: 0.2
})
Limitations and Next Steps
This implementation uses a simple two-state controller: normal quality and reduced quality. A production application may need a multi-step bitrate ladder, separate audio and video policies, codec-specific testing, subscriber-side measurements, or a coordinated server-side strategy.
- Use more quality levels: Step through several bitrate and resolution combinations instead of switching directly between two states.
- Measure the remote side: Consider
remote-inbound-rtpdata when receiver-side packet loss is important to the decision. - Protect against oscillation: Add cooldown periods or hysteresis so the controller does not move up and down too quickly.
- Test browser differences: Verify
scaleResolutionDownBy, encoder behavior, and sender parameters on the browsers and devices you support. - Separate policy from transport: Keep the monitoring logic independent from the publishing SDK so it can be adapted to another WebRTC workflow.
For broader WebRTC implementation context, see Red5’s WebRTC overview, WHIP and WHEP resources, and live streaming SDKs.
Conclusion
WebRTC adaptive bitrate control can be implemented in the browser by combining periodic WebRTC statistics with live sender-parameter updates. The essential pattern is to calculate delta-based metrics, require consecutive samples before changing state, reduce bitrate and resolution when the network degrades, and restore quality only after recovery is consistent.
Use the Red5 WHIP Adaptive Bitrate Controller example as a working starting point, then tune its thresholds and quality levels for your application. With that feedback loop in place, a temporary uplink problem can become a controlled quality reduction instead of a failed live stream.
Product marketing manager with experience at software companies, startups, and enterprises in the live streaming industry since 2018. Her core expertise is SEO, but she also collaborates closely with the product development team to integrate marketing into Red5 solutions and drive adoption. She supports growth through go-to-market strategies, release announcements, email campaigns, case studies, sales enablement materials, social media, and other channels.
