Play RTSP Streams in the Browser Without Plugins

When a surveillance project reaches “view cameras on a web page”, it hits the RTSP wall: browsers don’t support RTSP, and historically you either installed NPAPI plugins (long dead) or ran endless server-side transcoding (FFmpeg pull-convert-push, CPU on fire). ZWPlayer’s route is lighter: paste the rtsp:// URL straight into the player and let a media gateway translate protocols in the middle — zero plugins, zero transcoding scripts on the web side. This article covers the whole chain, the config, and troubleshooting.

Why Browsers Can’t Play RTSP

RTSP is a LAN-oriented real-time streaming protocol on dedicated TCP/UDP ports; the browser sandbox has neither its protocol stack nor permission for such connections. So “RTSP in the browser” always involves protocol translation — the question is where it happens:

  • The old route (server transcoding): FFmpeg/ZLMediaKit continuously pulls RTSP, converts to HLS/FLV, and serves the page. resident process per camera, CPU burned on transcoding, plus a layer of latency;
  • The ZWPlayer route (gateway translation): deploy a media gateway (xmc); the player talks to it over WebSocket, and the gateway pulls RTSP on demand and converts to browser-friendly output. Zero transcode in bypass mode, pull-on-demand, no idle spinning.

RTSP playback path Figure 1: camera ←RTSP→ media gateway ←WebSocket→ browser player

Three Lines of Config

new ZWPlayer({
  playerElm: 'player',
  url: 'rtsp://admin:password@192.168.1.64:554/main',  // camera RTSP address
  xmc_url: 'ws://xmc.zwplayer.cn:8138',                // media gateway
  rtsp_over_tcp: true,                                  // TCP transport (default)
  rtsp_bypass: true,                                    // zero transcode (source codec browser-supported)
});

The four knobs:

Option Default Notes
url — RTSP address (with credentials); main stream = quality, sub stream = smoothness
xmc_url — Gateway address (ws:// or http://); pulls the stream and translates protocols
rtsp_over_tcp true RTSP transport: TCP is stable across networks; LAN-only may try false (UDP, lower latency)
rtsp_bypass false true = no re-encode (lowest latency; requires browser support for the source codec, e.g. H.264); false = forced transcode (best compatibility)

Where do addresses come from? Hikvision/Dahua/Huawei devices follow fixed RTSP patterns (rtsp://user:pass@IP:554/channel-path) — see our RTSP URL format cheat sheet, plus main stream vs sub stream. Web viewing usually picks the sub stream: enough resolution, bitrate tier lower.

Three Field Scenarios

  • Factory / retail video walls: a dozen cameras in a grid, ZWPlayer instance each with its rtsp:// URL, all managed by the gateway; sub stream + rtsp_bypass: true keeps the wall smooth;
  • Remote equipment inspection: cameras live on a customer’s LAN — put the gateway inside that LAN, let the web page reach the gateway over the public network, and cameras never need public IPs;
  • Fisheye/panoramic cameras: their footage drops straight into VR mode for a natural view with full freedom to look around.

Troubleshooting

Black screen / eternal loading: open the same rtsp:// in VLC on the same machine first — if VLC fails too, it’s network or addressing (credentials, channel path, port 554), not the player.

Plays but stutters every few seconds: bitrate exceeding decode or bandwidth — switch to the sub stream address, or set rtsp_bypass: false to let the gateway transcode down.

Nothing across networks / over the internet: rtsp_over_tcp must be true (UDP cannot route across subnets).

H.265 cameras: Safari and hardware-decoding Edge/Chrome can bypass H.265; otherwise rtsp_bypass: false transcodes to H.264 at the gateway.

It Composes With Everything Else

Gateway output enters ZWPlayer’s full capability stack: multi-bitrate switching, subtitle preload (when the gateway emits HLS), anti-record watermarks, progress thumbnails for recordings. Camera feeds and on-demand content share player experience.