Skip to content

Latest commit

 

History

129 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZeroPlay

License: MIT Buy Me a Coffee

A lightweight H.264 video player for Linux SBCs, built as a modern replacement for the discontinued omxplayer. Uses the V4L2 M2M hardware decoder, DRM/KMS display, and ALSA audio — zero CPU video decode, zero X11 dependency.

have a nice day ;)


Supported Hardware

ZeroPlay runs on any Linux device with a V4L2 M2M hardware decoder and DRM/KMS display. Tested on:

Device OS
Pi Zero W Raspberry Pi OS Lite 32-bit (Trixie)
Pi Zero 2 W Raspberry Pi OS Lite 32/64-bit (Trixie)
Pi Zero 2 W balenaOS (Bookworm)
Pi 3 / 3+ Raspberry Pi OS Lite 32/64-bit (Trixie)
Pi 4 Raspberry Pi OS Lite 32/64-bit (Trixie)
Pi 1 Model B

Both 32-bit and 64-bit builds are supported. The install script builds from source automatically for the correct architecture.


Supported Formats

ZeroPlay decodes H.264 only. Other codecs will fail with an "unsupported codec" error — transcode them to H.264 first.

Codec Profiles Container
H.264 Baseline to High, up to level 4.2 MP4, MKV, MOV, HLS (.m3u8)

H.264 is hardware decoded via the bcm2835 VPU on Pi Zero W, Pi Zero 2W, and Pi 3, and the V4L2 stateful decoder on Pi 4.

Audio

Codec Notes
AAC / HE-AAC SBR rate mismatch auto-detected and corrected
MP3
AC3
FLAC
Opus

Installation

curl -fsSL https://raw.githubusercontent.com/HorseyofCoursey/zeroplay/main/install.sh | sudo bash

This installs dependencies, builds from source, and places the binary at /usr/local/bin/zeroplay.

Manual build

sudo apt install git gcc make pkgconf \
  libavformat-dev libavcodec-dev libavutil-dev libswresample-dev libswscale-dev \
  libdrm-dev libasound2-dev libcjson-dev libfreetype-dev

git clone https://github.com/HorseyofCoursey/zeroplay.git
cd zeroplay
make
sudo make install

libfreetype-dev is optional but recommended — without it subtitles use a built-in bitmap font fallback.

YouTube support (optional)

To play YouTube URLs directly, install yt-dlp:

sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp \
  -o /usr/local/bin/yt-dlp && sudo chmod +x /usr/local/bin/yt-dlp

Keep yt-dlp up to date — YouTube changes frequently and old versions stop working.

WebSocket remote control (optional)

sudo apt install libwebsockets-dev
make WS=1
sudo make install

Cross-compile from x86 Linux

./cross-build.sh

Uses Docker with buildx to produce a native ARM binary. Copy the result to your Pi with scp.


Usage

zeroplay [options] <path> [path2 ...]

Each path can be a video file, image, .txt/.m3u playlist, directory, URL, or YouTube URL. Up to 4 paths may be given — each is assigned to a connected display in DRM enumeration order.

Options

Flag Description
--loop Loop playback indefinitely
--loop-seamless Loop a single track indefinitely without a pipeline restart between loops
--shuffle Randomise playlist order
--recursive Load files from folder recursively
--no-audio Disable audio
--vol n Initial volume, 0–200 (default: 100)
--pos n Start position in seconds
--audio-device dev ALSA device override
--sub path External subtitle file (.srt)
--hls-bitrate bps Cap HLS variant bitrate in bps (or HLS_MAX_BANDWIDTH env)
--yt-quality n YouTube stream height: 360, 480, 720, 1080 (default: 480)
--image-duration n Seconds per image (default: 10, 0 = hold forever)
--spi-fill SPI/DBI panel: crop video to fill instead of the default letterboxed fit — see SPI/DBI Panels
--verbose Print decoder and driver info
--help Show usage

Examples

# Play a local file
zeroplay movie.mp4

# Play a YouTube video (requires yt-dlp)
zeroplay "https://www.youtube.com/watch?v=..."

# Play YouTube at 720p (Pi 4/5)
zeroplay --yt-quality 720 "https://www.youtube.com/watch?v=..."

# Play YouTube at 360p (Pi Zero W)
zeroplay --yt-quality 360 "https://www.youtube.com/watch?v=..."

# Play an HLS stream
zeroplay https://example.com/stream.m3u8

# Play separate video and audio streams (advanced)
zeroplay "(https://example.com/video.m3u8)(https://example.com/audio.m3u8)"

# Play with subtitles (auto-detected if .srt has the same name as the video)
zeroplay movie.mp4

# Play with an explicit subtitle file
zeroplay --sub subtitles.srt movie.mp4

# Play all media in a directory
zeroplay /home/pi/media/

# Play all media in a directory and its subdirectories
zeroplay --recursive /home/pi/media/

# Play a playlist file, loop and shuffle
zeroplay --loop --shuffle playlist.txt

# Mix images and videos in a directory, 15 seconds per image
zeroplay --loop --image-duration 15 /home/pi/media/

# Display a static image indefinitely
zeroplay --image-duration 0 photo.jpg

# Loop a directory of media
zeroplay --loop /home/pi/media/

# Shuffle a playlist
zeroplay --loop --shuffle playlist.txt

# Dual display on Pi 4
zeroplay file1.mp4 file2.mp4

# Start at 1h 30min
zeroplay --pos 5400 movie.mp4

Playlist files

A plain .txt or .m3u file with one path or URL per line. Lines starting with # are ignored.

# My playlist
/home/pi/media/intro.mp4
/home/pi/media/photo.jpg
https://example.com/stream.m3u8

Controls

Key Action
p / Space Pause / resume
← / → Seek −/+ 1 minute
↑ / ↓ Seek −/+ 5 minutes
+ / = Volume up 10%
- Volume down 10%
m Mute / unmute
n Next playlist item
b Previous playlist item
i / o Previous / next chapter
q / Esc Quit

Subtitles

ZeroPlay renders subtitles via a DRM overlay plane — composited by the display hardware with no CPU overhead.

  • Auto-detection — a .srt file with the same name as the video is loaded automatically
  • Explicit file — use --sub subtitles.srt
  • Embedded — MKV files with embedded SRT/subrip subtitle tracks work automatically
  • Font — DejaVu Sans Bold 36px when libfreetype-dev is installed; built-in bitmap font otherwise

YouTube

ZeroPlay detects YouTube URLs automatically and resolves them via yt-dlp:

zeroplay "https://www.youtube.com/watch?v=..."
zeroplay "https://youtu.be/..."
zeroplay "https://www.youtube.com/shorts/..."

Use --yt-quality to set the maximum stream height. The default is 480p which works well on Pi Zero 2W. Pi 4/5 users can use 720p or 1080p.

Device Recommended quality
Pi Zero W 360p
Pi Zero 2 W 480p
Pi 3 480p
Pi 4 / 5 720p or 1080p

Note: Rapid seeking and pausing during YouTube playback may cause instability due to the dual-stream sync mechanism. Normal playback and occasional seeking works reliably.


Audio

ZeroPlay auto-detects the HDMI audio device and routes through the hdmi: ALSA device, which uses the IEC958 plugin chain built into vc4-hdmi. Using plughw: directly bypasses this chain and produces noise.

The hardware's native sample rate is probed at startup to avoid pitch distortion from resampling.

# Override the audio device
zeroplay --audio-device plughw:CARD=Headphones,DEV=0 movie.mp4

# List available devices
aplay -L

SPI/DBI Panels

ZeroPlay can drive small SPI TFT panels (ILI9341, ST7789V via panel-mipi-dbi, and other drm/tiny MIPI DBI controllers) with no HDMI attached — the decoder hands the ISP an RGB565 frame instead of NV12, and it's presented on the panel's own plane.

Requirements:

  • The panel must bind through DRM/KMS, not fbtft. fbtft gives you /dev/fb1 and no DRM node at all, which ZeroPlay can't use. Check with ls /dev/dri/ — you need a cardN entry. If you only get a framebuffer, switch the panel's overlay from an fbtft-style one to a drm/tiny one (a dedicated overlay for your controller, or the generic dtoverlay=mipi-dbi-spi for ST7789 and other panels without a dedicated driver).
  • A reasonably fast SPI clock. A full-panel refresh at low frame rates needs real bandwidth — 40 MHz worked cleanly on the panels this was tested on; push it as high as your panel and wiring tolerate without flickering.

Two display modes, picked with --spi-fill (default is fit):

Mode Behaviour Requirements
Fit (default) Whole picture, letterboxed to the panel's aspect. CPU-scales the decoded frame into a panel-sized buffer. None — works on any kernel.
--spi-fill Crops to fill the panel edge-to-edge, no CPU scaling (zero-copy: the decoder's own buffer is scanned out directly, cropped by the plane's source rectangle). Needs the panel's drm/tiny driver to accept a framebuffer larger than the panel and honour a non-zero plane source offset — merged in raspberrypi/linux rpi-7.2.y+ and submitted upstream dri-devel, waiting on merge status as of 9/15/26. On an older kernel this mode will fail to allocate the framebuffer; fit mode still works everywhere.

If the panel can't keep the requested frame rate (a slow SPI clock, a large panel, or a high-frame-rate source), ZeroPlay drops late frames rather than falling into slow motion — you'll see fewer frames displayed, at the correct real-time speed.

# Default: whole picture, letterboxed
zeroplay movie.mp4

# Crop to fill the panel (needs a current kernel, see table above)
zeroplay --spi-fill movie.mp4

Seamless loop

To play a video file seamlessly and indefinitely, start zeroplay with the --loop-seamless flag. Instead of tearing the pipeline down and rebuilding it at the end of every pass, the demuxer seeks back to the start and keeps feeding packets, so there is no gap between loops.

Restriction:

  • Does not support external audio or subtitle files.

MP4 Requirements:

  • If the audio stream is longer than the video stream, it will be trimmed to match the video duration.
  • If the video stream is longer than the audio stream, zeroplay will fail.
  • For best results, ensure the MP4 file has matching audio and video durations.

In --control mode the flag applies per clip: loadloop loops seamlessly, while load plays once and still emits ended.


WebSocket Remote Control

Requires make WS=1. No libwebsockets dependency in the base build.

Runs ZeroPlay as a remotely controlled player, receiving commands from a backend and reporting state every 5 seconds.

zeroplay --ws-url ws://backend.local:8080/ws --device-token <token>

Control Mode

Runs ZeroPlay as a long-lived process controlled via stdin/stdout. Unlike standard mode, the DRM display is held open between clips — no console flash between videos and the last frame stays on screen until the next clip starts.

zeroplay --control [initial_path]

If an initial path is given it auto-loops at startup so the screen is live immediately.

Commands (stdin, newline-terminated)

Command Description
load <path> Play a file once, emit ended on stdout at end
loadloop <path> Play a file, seamlessly re-looping at end
pause Pause playback
resume Resume playback
stop Stop and hold last frame
quit Exit cleanly

Events (stdout)

Event Description
ready Emitted at startup when display is initialised
ended Emitted when a non-looping clip finishes

Useful for Python scripts, kiosk controllers, or any local process that needs seamless clip switching without WebSocket overhead.

Options

Flag Env var Description
--ws-url URL BACKEND_WS_URL Backend WebSocket URL (ws:// or wss://)
--device-token TOKEN DEVICE_TOKEN Device auth token (required)
--health-port PORT HEALTH_PORT HTTP health endpoint port (default: 3000)

Commands

Command Fields Description
load url Load and play a media URL
play — Resume
pause — Pause
stop — Stop and unload
seek positionMs Seek to position in milliseconds

Reconnects automatically with exponential backoff (1s → 30s).


Running as a service

sudo nano /etc/systemd/system/zeroplay.service
[Unit]
Description=ZeroPlay video player
After=multi-user.target

[Service]
User=pi
Group=video
Environment=HOME=/home/pi
ExecStart=/usr/local/bin/zeroplay --loop /home/pi/media/
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now zeroplay
sudo usermod -aG video pi

How It Works

  • Demux — libavformat reads the container and routes packets (local files, HLS, network streams, YouTube via yt-dlp)
  • Video decode — V4L2 M2M hardware decoder
  • Display — DRM/KMS atomic modesetting with DMABUF zero-copy from decoder to scanout
  • Subtitles — rendered into an ARGB8888 DRM overlay plane, composited by the display hardware
  • Audio — libavcodec → libswresample (hardware rate probing, HE-AAC SBR correction) → ALSA
  • Sync — wall-clock pacing against video PTS; A/V sync throttling for separate stream playback

No X11, no Wayland, no GPU compositing. Runs from a TTY or SSH session.


Differences from omxplayer

Feature omxplayer ZeroPlay
Hardware decode OpenMAX (deprecated) V4L2 M2M
Display dispmanx (deprecated) DRM/KMS
Dual display No Yes (Pi 4)
Playlist / directory No Yes
Image display No Yes
HLS streaming No Yes
YouTube No Yes (via yt-dlp)
Subtitles (SRT) Yes Yes
Subtitles (embedded MKV) No Yes
WebSocket remote control No Yes (opt-in)
Chapter navigation No Yes
Seeking Yes Yes
Volume control Yes Yes
Loop Yes Yes
Runs on modern OS No Yes

About

Command line media player inspired by omxplayer that works in trixie.

Topics

Resources

Stars

47 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages