How to Build Browser Live Streaming for Small Projects with WebRTC and SRS
This article shows how seemingly complex real‑time video requirements can be satisfied cheaply by combining the open‑source WebRTC stack with the lightweight SRS media server, detailing the architecture, step‑by‑step signaling, full JavaScript code for publishing and playing, and the resulting end‑to‑end live‑streaming solution.
Why a lightweight solution works for small projects
Clients often request real‑time video features that appear costly, but by using the open‑source WebRTC stack together with the Simple Realtime Server (SRS) the functionality can be delivered with minimal budget and development time.
WebRTC basics
WebRTC (Web Real‑Time Communication) is a Google‑led open‑source protocol that enables browsers to capture, encode and transmit audio/video without plugins. End‑to‑end latency is typically 50‑300 ms, making it suitable for live streaming and conference‑screen‑sharing scenarios.
SRS overview
SRS (Simple Realtime Server) is a lightweight, high‑performance, open‑source media server widely used in Chinese small‑project live‑streaming cases. It handles media reception, caching, and one‑to‑many distribution, matching the low‑cost, fast‑delivery needs of small projects.
Architecture
The data flow is: Browser (publisher) → SRS server (relay) → Multiple browsers (players). This chain guarantees real‑time delivery while avoiding custom client development.
Publishing workflow
Three WebRTC signaling steps are performed:
Create an SDP Offer from the browser.
Receive the SDP Answer from SRS.
Open a WebRTC data channel and start pushing the media stream.
Publishing code
The core JavaScript function startPublish obtains a screen‑capture stream, creates an RTCPeerConnection, adds tracks, forces H.264, gathers ICE candidates, sends the SDP to SRS via a POST request, sets the remote description and starts streaming.
async function startPublish() {
// 1. Get media stream (screen capture)
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {cursor: "always", width: 1920, height: 1080, frameRate: 30},
audio: false
});
// 2. Create RTCPeerConnection
const pc = new RTCPeerConnection({iceServers: [], bundlePolicy: "max-bundle", rtcpMuxPolicy: "require"});
// 3. Add tracks
stream.getTracks().forEach(track => pc.addTrack(track, stream));
// 4. Create Offer (send only)
const offer = await pc.createOffer({offerToReceiveAudio: false, offerToReceiveVideo: false});
// 5. Prefer H.264 for SRS compatibility
offer.sdp = preferH264(offer.sdp);
await pc.setLocalDescription(offer);
// 6. Wait for ICE gathering
const completeSdp = await waitForIceGathering(pc);
// 7. Send signaling to SRS
const response = await fetch("https://192.168.60.128:1990/rtc/v1/publish/", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
api: "https://192.168.60.128:1990/rtc/v1/publish/",
streamurl: "webrtc://192.168.60.128:1985/live/livestream",
sdp: completeSdp
})
});
const result = await response.json();
// 8. Set remote description (Answer)
await pc.setRemoteDescription({type: "answer", sdp: result.sdp});
console.log("Publishing connection established");
}Pulling workflow
The player creates an RTCPeerConnection, adds a recvonly transceiver for video (and audio), creates an Offer, sends it to SRS, receives the Answer, and attaches the incoming stream to a <video> element.
async function startPlay() {
const pc = new RTCPeerConnection({iceServers: [], bundlePolicy: "max-bundle", rtcpMuxPolicy: "require"});
pc.addEventListener("track", event => {
if (event.streams && event.streams[0]) {
const videoEl = document.getElementById("remoteVideo");
videoEl.srcObject = event.streams[0];
videoEl.play();
}
});
pc.addEventListener("connectionstatechange", () => {
console.log("Connection state:", pc.connectionState);
});
pc.addTransceiver("video", {direction: "recvonly"});
pc.addTransceiver("audio", {direction: "recvonly"});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const completeSdp = await waitForIceGathering(pc);
const response = await fetch("https://192.168.60.128:1990/rtc/v1/play/", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
api: "https://192.168.60.128:1990/rtc/v1/play/",
streamurl: "webrtc://192.168.60.128:1985/live/livestream",
sdp: completeSdp
})
});
const result = await response.json();
if (result.code !== 0) throw new Error(`SRS error: ${result.error || result.code}`);
await pc.setRemoteDescription({type: "answer", sdp: result.sdp});
console.log("Pulling connection established");
}HTML player element
<video id="remoteVideo" autoplay playsinline controls></video>Result
The combined WebRTC + SRS solution provides a complete end‑to‑end pipeline: browser publisher → SRS relay → multiple browser viewers, delivering low‑latency live video without additional client binaries and with minimal deployment effort.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
xkx's Tech General Store
Code with the left hand, enjoy with the right; a keystroke sweeps away worries.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
