Tuesday, July 8, 2025

How to Stream RTSP / Webcam / IP Camera Over the Web

I had a Hikvision IPC-B120 that provides a simple RTSP stream, which I could view in VLC. I also wanted to see my Logitech BRIO’s feed in a browser—without having to start a Video Call session.


To do this, I pull the RTSP (video/audio) feed from each camera, pipe it through FFmpeg to convert it into an MPEG-TS stream, and then serve it over WebSocket using JSMpeg. On my Raspberry Pi 5, I run two systemd services (one per camera) that use websocat to listen for WebSocket connections. When a client connects, websocat launches FFmpeg to start the conversion and forwards the stream; when the client disconnects, FFmpeg automatically stops.




# /etc/systemd/system/rtsp-ws.service

[Unit]

Description=On-demand WebSocket→FFmpeg bridge for JSMpeg

After=network.target

[Service]

User=pi

Group=pi

ExecStart=/usr/local/bin/websocat \

  --binary \

  --exit-on-eof \

  -s 127.0.0.1:10000 \

  sh-c:'/usr/bin/ffmpeg -hide_banner -loglevel error -rtsp_transport tcp -i "rtsp://user:pass@Local.IP.Camera:Localport/URI/channels/101" -f mpegts -codec:v mpeg1video -bf 0 -r 25 pipe:1'

Restart=on-failure

[Install]

WantedBy=multi-user.target


# /etc/systemd/system/webcam-ws.service

[Unit]

Description=On-demand WebSocket→FFmpeg bridge for Brio webcam

After=network.target

[Service]

User=pi

ExecStart=/usr/local/bin/websocat \

  --binary \

  --exit-on-eof \

  -s 127.0.0.1:10001 \

sh-c:'WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 /usr/bin/ffmpeg -hide_banner -loglevel error \

  -f v4l2 -framerate 30 -video_size 1920x1080 -i /dev/video0 \

  -f alsa -ac 2 -ar 44100 -i default \

  -f mpegts \

  -codec:v mpeg1video -bf 0 -r 30 \

  -codec:a mp2 \

  pipe:1'

Restart=on-failure

[Install]

WantedBy=multi-user.target



You can then create the related websocket proxy in apache and create a player file in html:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Live Camera (JSMpeg)</title>
<style>
/* fullscreen black background, centered content */
html, body {
margin: 0; padding: 0;
width: 100vw; height: 100vh;
background: #000;
display: flex;
align-items: center;
justify-content: center;
}

/* wrapper to constrain size, max 90% of viewport */
.player-container {
width: 90vw;
max-width: 1280px; /* or whatever max you like */
max-height: 90vh;
}

/* canvas will fill the container but keep its own aspect‐ratio */
.player-container canvas {
width: 100%;
height: auto;
display: block;
}
</style>
</head>
<body>
<div class="player-container">
<canvas id="videoCanvas"></canvas>
</div>

<script src="/jsmpeg.min.js"></script>
<script>
const url = (location.protocol==='https:'?'wss://':'ws://')
+ location.host + '/my-ws-prefix';

new JSMpeg.Player(url, {
canvas: document.getElementById('videoCanvas'),
autoplay: true,
audio: false
});
</script>
</body>
</html>

How to Stream RTSP / Webcam / IP Camera Over the Web

I had a Hikvision IPC-B120 that provides a simple RTSP stream, which I could view in VLC. I also wanted to see my Logitech BRIO’s feed in a ...