Chatham Rabbit Hole Logo

Simple RPi Video Camera with HTTP Live Streaming (HLS)

The simple JavaScript express() server and video client code samples below demonstrate the essential elements of  HTTP Live Streaming (HLS) by a Raspberry Pi and Camera. The code was tested on the RPi Zero 2W test set shown in the accompanying figure.

The libcamera-vid and ffmpeg tools comprise the software pipeline for the video stream. The libcamera-vid output is incompatible with the HTML5 <video> element in the stream client; the ffmpeg tool formats the video stream for HTML5 compatibility.

Project Requirements 


  • Raspberry Pi with Video Camera installed


  • Node 
  • NPM
  • libcamera and tools
  • ffmpeg


RPi Zero2W Test Set - Camera Mount
RPi Camera Test Set (w/ plugboard mounted HC-SR04 Ultrasonic Sensor in background, right)
					/** Nodejs Server Code 
Streams Raspberry Pi Camera video

const express = require('express');
const { spawn } = require('child_process');
const fs = require('fs');
/** @global  */
const logger = require('morgan');

const path = require('path');
const app = express();
const port = 8554;


// Serve static files from public directory

// Route to start streaming
app.get('/start-stream', (req, res) => {
    // Start the camera stream using libcamera-vid piped into FFmpeg for HLS conversion
    const cameraStream = spawn('libcamera-vid', ['-t', '0', '-n', '--inline', '--listen', '-o', '-']);
    console.log('Spawned cameraStream ...');
    const ffmpeg = spawn('ffmpeg', [
        '-i', '-',
        '-c:v', 'copy',
        '-f', 'hls',
        '-preset', 'ultrafast',
        '-tune', 'zerolatency',
        '-g', '30', 
        '-hls_time', '2',
        '-hls_list_size', '3',
        '-hls_flags', 'delete_segments+append_list',
        '-loglevel', 'error',  
    console.log('Spawned ffmpeg transcoder ...');    


    console.log('Setup cameraStream - ffmpeg pipe ...');
    cameraStream.stderr.on('data', (data) => {
        console.error(`libcamera-vid stderr: ${data}`);

    ffmpeg.stderr.on('data', (data) => {
        console.error(`ffmpeg stderr: ${data}`);

    res.send('Streaming started');

// Start the Express server
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);

					<!-- Client-Side HTML Code for Video Display -->
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Camera Stream</title>
    <video id="videoPlayer" controls autoplay></video>
        document.getElementById('videoPlayer').src = '/stream.m3u8';

Video Pipeline Attributes

  • -t 0.   Run indefinitely (ie, -t specifies the stream duration as N seconds; if N=0, the stream runs indefinitely)
  • -n    Run without the libcamera preview window (the preview would disrupt the video pipeline in this configuration)
  • — inline   Embeds the camera controls in the video stream
  • — listen    
  • -o –  Send the output video to stdout.
  • -i – Use stdin as input
  • -c:v  copy    Copies the video stream (-c:v) without recoding the stream.
  • -f  hls   Format the libcamera-vid H.264 video stream for HLS to the client
  • -preset ultrafast
  • -tune zerolatency 
  • -g 30   The ‘-g’ flag sets the number of pictures (frames) in a group
  • -hls_time 2
  • -hls_list_size 3
  • -loglevel error
  • ./public/stream.m3u8  Serve the stream to the static directory “.public” with name ‘stream.m3u8’