Page Stream

Headless, disposable web page video streamer for public displays

Load any URL or local HTML in a containerized headless browser, capture it with FFmpeg, and push encoded video to SRT or RTMP ingest endpoints — no broadcast software required.

How It Works

1

Virtual Display

Container launches Xvfb at your requested resolution.

2

Browser

Playwright-controlled Chromium loads the target page.

3

Capture

FFmpeg performs x11grab of the display and encodes with libx264.

4

Stream

Output is pushed via SRT, RTMP, or saved to a local file.

Features

Single-Command Streaming

Containerized streaming of any web page or local HTML file with one command. No GUI, no desktop, no broadcast software.

CSS & JS Injection

Inject custom CSS overrides and JavaScript into the streamed page for styling transformations and interactive behavior.

Multi-Source Compositor

Compose multiple page sources into collage-style layouts using FFmpeg filter graphs. Side-by-side, tiled, or custom arrangements.

📡

SRT & RTMP

Primary support for SRT (Secure Reliable Transport), secondary RTMP/RTMPS, extensible to any FFmpeg output.

🎬

Direct Video File Streaming

Loop pre-recorded video files directly to ingest endpoints without browser overhead. Auto-scales, pads, and normalizes frame rates.

🔌

Reconnect & Recovery

Exponential backoff reconnection for SRT/RTMP failures. Structured JSON health logging for monitoring.

👁

noVNC Debugging

Optional browser-based VNC viewer to interact with the headless Chromium session in real time. Disabled by default.

Production Ready

Automatic system requirements validation, per-container configuration, secret management, and operational runbooks.

🖥

Web Dashboard

Browser-based control panel for managing streams, monitoring health, and adjusting configuration in real time.

Coming Soon

Quick Start

1. Configure URLs

cp .env.stable.example .env
# Edit .env with your target URLs:
#   STANDARD_1_URL, STANDARD_2_URL, ...
#   SOURCE_LEFT_URL, SOURCE_RIGHT_URL

2. Configure Secrets (Production)

If your ingest URLs contain # characters (e.g. Kaltura stream IDs):

cp env.secrets.sh.example .env.secrets.sh
# Edit .env.secrets.sh with your actual stream IDs

3. Build & Start

docker build -t page-stream:latest .

# With production secrets:
source .env.secrets.sh && \
  docker-compose -f docker-compose.stable.yml up -d

# Or for local testing:
docker-compose -f docker-compose.stable.yml up -d

Streams appear in the out/ folder — open with VLC or ffplay.

Minimum Requirements

  • Docker
  • 6 CPU cores
  • 16 GB RAM

An automatic check validates resources at startup. For macOS with Colima:

colima stop
colima start --cpu 8 --memory 16

Examples

Stream a Web Page via SRT

docker run --rm \
  -e WIDTH=1920 -e HEIGHT=1080 \
  page-stream:latest \
  --ingest srt://example.com:9000?streamid=live.slp \
  --url https://your-dashboard.example.com

RTMP Output

docker run --rm \
  -e WIDTH=1280 -e HEIGHT=720 \
  page-stream:latest \
  --ingest rtmp://live.example.com/app/streamKey \
  --format flv \
  --url https://your-page.example.com

CSS & JS Injection

docker run --rm \
  -v $(pwd)/custom.css:/custom.css \
  -v $(pwd)/custom.js:/custom.js \
  page-stream:latest \
  --ingest srt://host:9000?streamid=demo \
  --url https://your-page.example.com \
  --inject-css /custom.css \
  --inject-js /custom.js

Loop a Video File

# Place video in ./videos/ directory
cp /path/to/video.mp4 ./videos/input.mp4

docker run --rm \
  -v $(pwd)/videos:/videos:ro \
  page-stream:latest \
  --ingest srt://host:9000?streamid=test \
  --video-file /videos/input.mp4 \
  --video-loop

Auto-Refresh Every 5 Minutes

docker run --rm \
  -e WIDTH=1280 -e HEIGHT=720 \
  page-stream:latest \
  --ingest srt://host:9000?streamid=live \
  --url https://live-data.example.com \
  --auto-refresh-seconds 300

Local File Output (Testing)

docker run --rm \
  -v $(pwd)/out:/out \
  page-stream:latest \
  --ingest /out/test.ts \
  --format mpegts \
  --url demo/index.html

CLI Reference

page-stream --ingest <URI> [options]

Required:
  -i, --ingest <uri>                Ingest URI (SRT/RTMP/file)

Page Options:
  -u, --url <url>                   Page URL or local file (default: demo)
      --width <n>                   Viewport width (default: 1280)
      --height <n>                  Viewport height (default: 720)
      --fps <n>                     Frames per second (default: 30)

Encoding:
      --preset <p>                  x264 preset (default: veryfast)
      --video-bitrate <kbps>        Video bitrate (default: 2500k)
      --audio-bitrate <kbps>        Audio bitrate (default: 128k)
      --format <fmt>                Container format (default: mpegts)
      --extra-ffmpeg <args...>      Additional raw FFmpeg arguments

Browser:
      --no-headless                  Show browser window
      --no-fullscreen                Disable fullscreen/kiosk mode
      --no-app-mode                  Show full browser chrome
      --crop-infobar <px>           Crop N pixels from top (remove banner)

Refresh & Control:
      --refresh-signal <sig>         Reload signal (default: SIGHUP)
      --graceful-stop-signal <sig>   Stop signal (default: SIGTERM)
      --auto-refresh-seconds <n>    Auto reload interval (0 = disable)

Reconnection:
      --reconnect-attempts <n>       Max retries, 0 = infinite (default: 0)
      --reconnect-initial-delay-ms   Initial backoff delay (default: 1000)
      --reconnect-max-delay-ms       Max backoff delay (default: 15000)

Monitoring:
      --health-interval-seconds <n>  Health log interval (default: 30)

Injection:
      --inject-css <file>            Inject CSS from file
      --inject-js <file>             Inject JavaScript from file

Video File (bypass browser):
      --video-file <path>            Stream video file directly
      --video-loop                   Loop video continuously

Architecture

Standard Instances

Each standard instance runs independently on the default network, streaming a single full-HD page to its own ingest endpoint.

standard-1 (1920×1080) → SRT ingest
standard-2 (1920×1080) → SRT ingest
standard-3 (1920×1080) → SRT ingest
standard-4 (1920×1080) → SRT ingest

Compositor Pipeline

Two half-width sources are composed into a single full-HD stream via an FFmpeg filter graph on an isolated network.

source-left  (960×1080) →⎯
                            → compositor → SRT ingest
source-right (960×1080) →⎯

Container Stack

┌───────────────────────────────────────────────────────────┐
│  requirements-check                                          │
│  Validates CPU/RAM before other services start               │
├────────────────────────────┼──────────────────────────────┤
│  default network             │  compositor_net              │
│  ┌────────────────────────┐  │  ┌──────────────────────────┐  │
│  │ standard-1  standard-2 │  │  │  source-left              │  │
│  │ standard-3  standard-4 │  │  │  source-right             │  │
│  │ srt-test-listener      │  │  │  compositor               │  │
│  └────────────────────────┘  │  │  srt-ingest               │  │
│                            │  └──────────────────────────┘  │
└────────────────────────────┴──────────────────────────────┘

Health & Monitoring

Structured JSON health lines are emitted at configurable intervals:

{
  "type": "health",
  "ts": "2024-01-01T00:00:00.000Z",
  "uptimeSec": 30.1,
  "ingest": "srt://example.com:9000?streamid=test",
  "protocol": "SRT",
  "restartAttempt": 0,
  "lastFfmpegExitCode": null,
  "retrying": false
}

Exit Codes

CodeMeaning
0Graceful stop
1Unhandled internal error
10SRT/RTMP reconnect attempts exhausted
11Non-retry protocol failure