WebRTC on Robots: How to Stream Live Video from Your Rover to Any Browser
Tom Cosgrove,

TL;DR: WebRTC on robots lets you stream live camera feeds from a mobile robot to modern browsers, often with sub-200 ms glass-to-glass latency on good networks, no plugins, and DTLS-SRTP encryption built in. This guide shows how to build a working WebRTC pipeline on a ROS 2 rover using gstreamer, webrtcbin, and a minimal signaling server, compares it to RTSP for teleoperation, and explains when WebRTC is the right tool for field robotics, inspection, and remote operations.
Why WebRTC on Robots Beats Traditional Streaming
Remote operation of a UGV depends on one thing more than any other: how fast you see what the robot sees. RTSP, MJPEG over HTTP, and custom TCP streams often add buffering, require client-side players, or break behind NAT. WebRTC on robots solves these problems in a single stack designed for real-time peer-to-peer media.
WebRTC is a browser-native protocol suite (RFC 8825) that combines ICE for NAT traversal, DTLS-SRTP for encryption, and adaptive bitrate media transport, usually over UDP. It was built for video conferencing, but its properties map cleanly onto teleoperated robotics:
- Low latency: typical glass-to-glass latency of 100-250 ms over a decent LTE link, compared to 500-2000 ms for RTSP with default client buffering.
- NAT traversal: ICE plus STUN/TURN works through carrier-grade NAT, which is standard on 4G/5G modems used in field robotics.
- No client install: the operator opens a browser. No VLC, no ROS install, no proprietary HMI.
- Bidirectional data channels: the same peer connection can carry control commands (joystick, waypoints) alongside video.
For a Leo Rover deployed in inspection or research, this means a field engineer can hand a URL to a remote expert and have them piloting or observing within seconds.
What a WebRTC Pipeline Looks Like on a ROS 2 Rover
A working WebRTC stack on a robot has four moving parts. Understanding the signaling flow is the difference between a stream that works on your bench and one that survives a real network.
The Four Components
- Media source: a camera publishing to a ROS 2 topic (
sensor_msgs/Image) or directly to a GStreamer pipeline. On Leo Rover, this is often the onboard camera exposed viav4l2src. - WebRTC peer (robot side): a GStreamer pipeline using
webrtcbin, or an aiortc Python process, that encodes video (H.264/VP8) and negotiates with the browser. - Signaling server: a small WebSocket service that exchanges SDP offers/answers and ICE candidates between robot and browser. It does not touch media.
- Browser client: standard JavaScript using
RTCPeerConnection, rendering into a<video>element.
Signaling Flow
The handshake follows this sequence:
- Robot and browser both connect to the signaling server via WebSocket.
- Browser sends an SDP offer describing what it can receive.
- Robot replies with an SDP answer describing what it will send.
- Both sides exchange ICE candidates (possible network paths) through the signaling server.
- ICE picks the best path. DTLS handshake completes. SRTP media flows peer-to-peer.
After step 5, the signaling server is no longer in the media data path. Video goes directly between rover and browser, optionally through a TURN relay if both sides are behind restrictive NAT.
How to Build the WebRTC Stack: Working Building Blocks
The example below uses GStreamer 1.20+ with the gst-plugins-bad WebRTC element. It runs on Ubuntu 22.04, which is the standard base for ROS 2 Humble and is commonly used on Leo Rover-compatible ROS 2 setups.
Step 1: Install Dependencies
On the rover, install GStreamer and the WebRTC plugins:
sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-nice python3-gst-1.0 python3-websockets
Step 2: Minimal Signaling Server
Run this on any reachable host (a VPS, or the rover itself if it has a public endpoint). It just relays JSON messages between two peers in a room.
import asyncio, websockets
rooms = {}
async def handler(ws, path):
room = path.strip("/")
rooms.setdefault(room, set()).add(ws)
try:
async for msg in ws:
for peer in list(rooms[room]):
if peer is not ws: await peer.send(msg)
finally:
rooms[room].discard(ws)
if not rooms[room]: del rooms[room]
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
await asyncio.Future()
asyncio.run(main())
Step 3: GStreamer WebRTC Sender on the Robot
This pipeline fragment grabs the USB camera, encodes with H.264, and feeds webrtcbin. It will not complete WebRTC negotiation by itself from gst-launch-1.0; in production, create the same elements from a Python or C process and connect webrtcbin‘s SDP and ICE callbacks to your signaling URL.
gst-launch-1.0 -v \
webrtcbin name=sendrecv bundle-policy=max-bundle \
stun-server=stun://stun.l.google.com:19302 \
v4l2src device=/dev/video0 ! video/x-raw,width=1280,height=720,framerate=30/1 \
! videoconvert ! queue ! x264enc tune=zerolatency speed-preset=ultrafast bitrate=2000 \
! rtph264pay config-interval=-1 ! application/x-rtp,media=video,encoding-name=H264,payload=96 \
! sendrecv.
For production use, wrap this in a Python script using Gst bindings so you can handle SDP and ICE callbacks. The official GStreamer repo includes a reference at subprojects/gst-examples/webrtc/sendrecv/gst.
Step 4: Browser Client
The HTML side is short. It opens the WebSocket, creates the offer, and renders the incoming track:
const pc = new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});
const ws = new WebSocket("ws://your-server:8765/room1");
pc.addTransceiver("video", {direction:"recvonly"});
pc.ontrack = e => document.querySelector("video").srcObject = e.streams[0];
pc.onicecandidate = e => e.candidate && ws.send(JSON.stringify({ice:e.candidate}));
ws.onopen = async () => {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({sdp:pc.localDescription}));
};
ws.onmessage = async ev => {
const m = JSON.parse(ev.data);
if (m.sdp) await pc.setRemoteDescription(m.sdp);
else if (m.ice) await pc.addIceCandidate(m.ice);
};
Step 5: Bridge to ROS 2 (Optional)
If the camera frames originate from a ROS 2 topic instead of a raw V4L2 device, use a custom node that wraps frames into an appsrc element. Alternatively, use aiortc with rclpy in the same process. The data channel on the same peer connection can carry geometry_msgs/Twist commands as JSON, giving you teleop and video on one connection.
How WebRTC Compares to RTSP for Robot Teleoperation
RTSP remains useful for fixed IP cameras and NVR systems, but for mobile robots the trade-offs favor WebRTC in most field scenarios. The comparison below reflects typical behavior observed in teleoperation testing; exact numbers depend on network conditions and encoder settings.
- Latency: WebRTC pipelines tuned with
zerolatencypresets typically deliver 100-250 ms glass-to-glass. RTSP with default client buffering is commonly 500-2000 ms. RTSP can be tuned lower with MediaMTX, formerlyrtsp-simple-server, and low-latency clients, but it requires effort on both ends. - NAT traversal: WebRTC handles it natively through ICE. RTSP usually needs port forwarding or a VPN, which is impractical on LTE modems with carrier NAT.
- Encryption: WebRTC mandates DTLS-SRTP. RTSP typically needs RTSP over TLS, SRTP support, or a VPN.
- Adaptive bitrate: WebRTC reacts to packet loss and bandwidth estimation (REMB, transport-cc) within seconds. RTSP over TCP can stall; over UDP it can drop packets without application-level recovery logic.
- Client side: WebRTC runs in modern browsers that support the chosen codec. RTSP needs VLC, ffplay, or a custom player.
- Multi-camera: WebRTC bundles multiple tracks on one connection. RTSP needs one session per stream.
For inspection tasks where an operator pilots a rover through a substation or pipeline from a control room, the latency and browser-native delivery of WebRTC are decisive. See Fictionlab’s inspection applications page for examples of how these requirements shape robot configurations.
What to Watch Out for in Production
A WebRTC pipeline that works on a LAN can fail in the field. The recurring issues:
- TURN server: STUN alone fails when both peers are behind restrictive or symmetric NAT, which is common on cellular networks. Deploy a
coturninstance on a public VPS. Budget for the bandwidth, since all media traverses the relay in that case. - Hardware encoding: the
x264encCPU encoder will saturate a Raspberry Pi at 1080p30. Use hardware H.264 encoding where available, such asv4l2h264encon supported Raspberry Pi setups ornvv4l2h264encon Jetson platforms. - Bitrate adaptation: set target and maximum bitrate limits on the encoder where available, and adapt encoder bitrate based on WebRTC stats or transport-cc feedback to avoid frame drops on weak links.
- Reconnection logic: WebRTC peer connections do not always auto-recover reliably. Wrap the pipeline in a supervisor that restarts or performs an ICE restart on ICE failure.
- Time sync: if you overlay telemetry on video, synchronize the robot clock via NTP or PTP to avoid drift between data channel messages and video frames.
The Leo Rover platform supports ROS 2-compatible setups that make installing GStreamer and running this pipeline straightforward, especially when the onboard computer exposes the camera via V4L2.
Frequently Asked Questions
What latency can you realistically expect from WebRTC on a robot?
On a LAN, 50-120 ms glass-to-glass with hardware encoding. Over good LTE, 150-300 ms. Over a congested or long-distance link, 300-600 ms. These are typical figures from teleoperation deployments; your numbers depend on encoder, network, and display chain.
Do you need a TURN server?
For lab and same-network use, no. For any deployment where the robot uses cellular or sits behind enterprise NAT, yes. Without TURN, a non-trivial share of connections can fail to establish in field conditions.
Can WebRTC carry ROS 2 messages alongside video?
Yes. The RTCDataChannel on the same peer connection can be reliable or unreliable, ordered or unordered. Serialize messages as JSON or CBOR and route them to rclpy or rclcpp publishers on the robot.
How does WebRTC handle multiple viewers?
Natively, peer-to-peer WebRTC is one-to-one. For multiple viewers, deploy an SFU (Selective Forwarding Unit) such as mediasoup, Janus, or LiveKit. The robot sends one stream to the SFU, which fans it out.
Is WebRTC secure enough for industrial use?
DTLS-SRTP encrypts all media and data channels by default. Combine it with authenticated signaling (JWT over WSS), a TURN server with credentials, access control, and network monitoring, and the security posture can meet many common industrial control requirements depending on the threat model.
What about H.265 or AV1?
Safari and Chrome/Edge support H.265 in WebRTC on some platforms and supported hardware, and AV1 support is improving in major browsers but is not universal. For robotics, H.264 remains the safest default because of broad browser compatibility and hardware encoder support on embedded boards.
Can you record the stream on the robot while streaming?
Yes. Use a tee element in the GStreamer pipeline to split the encoded H.264 to both webrtcbin and a matroskamux file sink. Recording does not affect the live stream if each branch is queued and storage keeps up.
Build Your Custom Streaming Robot
If you need a rover with WebRTC streaming, custom sensor payloads, or ROS 2 integration tailored to your inspection or research workflow, Fictionlab’s engineering team can build it. Explore custom robotics services to discuss your specifications.