Skip to content

Add Linux virtual camera output via v4l2loopback#4

Open
pubino wants to merge 1 commit into
mainfrom
feature/virtual-camera-linux
Open

Add Linux virtual camera output via v4l2loopback#4
pubino wants to merge 1 commit into
mainfrom
feature/virtual-camera-linux

Conversation

@pubino

@pubino pubino commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds --virtual-camera <device> (and --virtual-camera-pix-fmt) so page-stream can write to a Linux v4l2loopback device. The stream then appears as a webcam to any host app (Chromium/Firefox, Zoom, Meet, OBS, GStreamer, etc.).
  • Works with both browser/x11grab and --video-file inputs; ffmpeg branches to a raw v4l2 output (no libx264, no audio) when virtual-camera mode is on.
  • --ingest is now optional when --virtual-camera is supplied. Health logs and reconnect/backoff machinery understand the v4l2 target.
  • Linux-only by design — macOS support requires a CoreMediaIO DAL plugin (or third-party tools like OBS) and is intentionally out of scope for this iteration.

Includes:

  • scripts/setup-virtual-camera.sh — zsh-friendly helper to load/unload v4l2loopback with sensible defaults (devices=1 video_nr=10 card_label=PageStream), plus --device/--label/--status/--teardown.
  • README "Virtual Camera Output (Linux)" section with install commands, examples, and verification tips (ffplay -f v4l2 /dev/video10).
  • 9 new tests in tests/virtual-camera.test.ts covering CLI parsing, ffmpeg arg construction for both input modes, and platform/device guards.

Test plan

Real-world local validation before merging to main:

  • Install module: sudo apt install v4l2loopback-dkms
  • Load device: sudo ./scripts/setup-virtual-camera.sh (creates /dev/video10, label PageStream)
  • Build: docker build -t page-stream:vc . (or run directly with locally-built dist/)
  • Page → camera: node dist/index.js --virtual-camera /dev/video10 --url demo/index.html --width 1280 --height 720 --fps 30
  • Verify output frames: ffplay -f v4l2 /dev/video10
  • Verify in browser: chromium --use-fake-ui-for-media-streamhttps://webcamtests.com (or any getUserMedia page) and pick "PageStream" from the camera list
  • Verify in a real client app: Zoom / Google Meet / OBS — confirm the device shows up under its label and renders the page
  • Looping video → camera: node dist/index.js --virtual-camera /dev/video10 --video-file ./videos/loop.mp4 --video-loop
  • Negative path: node dist/index.js --virtual-camera /dev/nonexistent should exit with a clear error pointing at setup-virtual-camera.sh
  • Teardown: sudo ./scripts/setup-virtual-camera.sh --teardown

Automated tests (28 pass): docker run --rm --entrypoint /bin/bash page-stream:vc -c "node --test --loader ts-node/esm tests/basic.test.ts tests/crop-infobar.test.ts tests/input-flags.test.ts tests/video-file.test.ts tests/virtual-camera.test.ts"

Notes & follow-ups

  • The Docker workflow defaults to network ingests; running virtual-camera mode from inside a container requires --device /dev/video10:/dev/video10 and is documented as such. Most users will run this on the host.
  • macOS virtual camera support is a natural follow-up; the architecture there is significantly heavier (CoreMediaIO DAL plugin) and out of scope for this PR.

Add --virtual-camera <device> (and --virtual-camera-pix-fmt) so streams can
be written to a local v4l2loopback device and consumed by host apps —
browsers, video conferencing, OBS, etc. — as a webcam source. Works for
both browser/x11grab and --video-file inputs; ffmpeg switches to raw v4l2
output (no libx264, no audio) when the flag is set. --ingest is optional
when --virtual-camera is provided. Linux-only; refuses to run elsewhere.

Includes scripts/setup-virtual-camera.sh helper to load/unload the kernel
module with sensible defaults, README docs, and tests covering CLI parsing,
ffmpeg arg construction for both input modes, and platform/device guards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant