If you want to play video on Raspberry Pi 5 with hardware acceleration and physical GPIO control, the definitive stack in 2026 is Raspberry Pi OS (64-bit Bookworm), the mpv media player, and the python-mpv binding. The legacy omxplayer is officially deprecated and lacks HEVC support, while VLC is notoriously clunky for headless or kiosk-style Python integrations.
This guide walks through building a robust, hardware-accelerated video player triggered by a physical button, specifically targeting the Raspberry Pi 5 (8GB variant). We will cover the exact media player landscape, wiring, complete Python code, and the specific debugging paths for when hardware decode fails.
Media Player Selection and Hardware Decode Support
The Raspberry Pi 5 features a dedicated hardware video decoder capable of handling 4Kp60 H.265 (HEVC) and H.264. However, not all Linux media players can access the Pi's V4L2 stateful decode APIs or the newer RP1 chip pathways out of the box. Choosing the wrong player results in software decoding, which will peg your CPU at 100% and drop frames on high-bitrate 4K files.
| Player | HW Decode (H.264 / H.265) | Python API Quality | Kiosk / Headless Ready | Current Status |
|---|---|---|---|---|
| mpv | Yes / Yes (via v4l2m2m) | Excellent (python-mpv) |
Yes (Native IPC & CLI) | Active, Recommended |
| VLC | Yes / Yes (via mmal/v4l2) | Poor (python-vlc is brittle) |
Moderate (Requires X11/Wayland) | Active, but heavy |
| omxplayer | Yes / No (H.264 only) | None (CLI only) | Yes (but deprecated) | Dead / Removed |
| ffplay | Yes / Yes (via v4l2m2m) | None (Subprocess only) | No (Requires window manager) | Active (Testing only) |
As the table shows, mpv is the only modern player that offers seamless hardware decoding alongside a robust, event-driven Python API. For a deep dive into the underlying V4L2 stateful decode architecture, refer to the official Raspberry Pi hardware documentation.
Parts List and GPIO Pin Mapping
The Pi 5 uses the new RP1 southbridge chip for I/O. While the physical 40-pin header layout remains identical to the Pi 4, the underlying GPIO library has shifted from RPi.GPIO to lgpio. We will use gpiozero, which automatically routes to the correct backend on Bookworm.
Required Components
- Board: Raspberry Pi 5 (8GB RAM)
- Power: Official 27W USB-C PD Power Supply (5V/5A) — Do not use a standard 15W phone charger; the Pi 5 will throttle and disable USB peripherals under load.
- Display: Micro-HDMI to HDMI 2.0 cable (18Gbps bandwidth for 4K@60)
- Storage: NVMe SSD via PCIe HAT or high-endurance microSD (A2 class)
- Input: 1x Momentary tactile pushbutton (NO)
- Resistor: 10kΩ (optional pull-up, though Pi 5 internal pull-ups are sufficient for short runs)
Pin Mapping Table
| Component Pin | Pi 5 Physical Pin | BCM GPIO Number | Function |
|---|---|---|---|
| Button Leg 1 | Pin 11 | GPIO 17 | Signal (Input, Pull-Up enabled) |
| Button Leg 2 | Pin 9 | GND | Ground Reference |
Software Setup and Environment Preparation
Before writing code, we must install the system-level libmpv C-bindings and the Python wrappers. The Pi 5 runs Debian Bookworm, which ships with libmpv2. This causes a known pathing issue with older Python wrappers, which we will address in the debugging section.
- Update the OS and install system dependencies:
sudo apt update sudo apt install -y mpv libmpv-dev python3-dev python3-venv python3-gpiozero - Create an isolated virtual environment:
mkdir ~/pi-video-player && cd ~/pi-video-player python3 -m venv venv source venv/bin/activate - Install the Python packages:
pip install python-mpvNote:
gpiozerois pre-installed in the system Python, but if you need it in your venv, runpip install gpiozero lgpio.
The Python Playback Script
This script initializes mpv with hardware decoding forced on, maps a physical button to stop playback, and includes robust error handling for missing libraries and file paths. It targets the Raspberry Pi 5 (8GB) running a 64-bit OS.
import sys
import os
import mpv
from gpiozero import Button
from signal import pause
# --- Configuration ---
VIDEO_PATH = '/home/pi/videos/sample_4k.mp4'
BUTTON_PIN = 17 # BCM 17 / Physical Pin 11
def setup_player():
"""Initialize MPV with hardware decoding and fullscreen kiosk settings."""
try:
player = mpv.MPV(
input_default_bindings=True,
input_vo_keyboard=True,
osc=False, # Hide on-screen controls for kiosk
fullscreen=True,
hwdec='auto', # Enable V4L2 hardware decode
vo='gpu', # Use GPU rendering
audio_device='alsa/hdmi' # Force audio over HDMI (Pi 5 has no analog jack)
)
return player
except OSError as e:
print(f'[FATAL] MPV Library Error: {e}')
print('Fix: Ensure libmpv-dev is installed. If libmpv.so.1 is missing, symlink libmpv.so.2.')
sys.exit(1)
except Exception as e:
print(f'[FATAL] Unexpected MPV init error: {e}')
sys.exit(1)
def main():
if not os.path.exists(VIDEO_PATH):
print(f'[ERROR] Video file not found at: {VIDEO_PATH}')
sys.exit(1)
player = setup_player()
# Setup GPIO Button (Active Low, using internal pull-up)
stop_button = Button(BUTTON_PIN, pull_up=True, bounce_time=0.05)
def handle_stop():
print('[INFO] Button pressed. Stopping playback.')
player.stop()
# Optionally, use player.quit() to exit the script entirely
sys.exit(0)
stop_button.when_pressed = handle_stop
print(f'[INFO] Playing {VIDEO_PATH}... Press physical button to stop.')
try:
player.play(VIDEO_PATH)
# Wait for playback to finish or button press
player.wait_for_playback()
except mpv.ShutdownError:
print('[INFO] Player shut down gracefully.')
except Exception as e:
print(f'[ERROR] Playback failed: {e}')
finally:
player.terminate()
if __name__ == '__main__':
main()
Debugging: When Video Playback Fails
When building embedded media players, things will break. If your screen stays black or the script crashes, here are the first three things to check:
- Power Supply Negotiation: The Pi 5 requires a 27W USB-C PD supply to enable full current limits. If you use a 15W charger, the RP1 chip will restrict USB and HDMI bandwidth. Check
dmesg | grep -i powerfor undervoltage warnings. - Micro-HDMI Cable Bandwidth: 4K@60Hz requires an 18Gbps HDMI 2.0 connection. Cheap micro-HDMI adapters often fail the handshake, resulting in a fallback to 1080p or a blank screen. Verify with
tvservice -s. - Codec vs. Container: Hardware decoding relies on the codec (H.264/HEVC), not the container (.mkv, .mp4). If your file uses AV1 or VP9, the Pi 5 must use software decoding, which will stutter at 4K. Re-encode to H.265 using FFmpeg.
Common Error Strings and Ranked Causes
OSError: libmpv.so.1: cannot open shared object file: No such file or directory
Cause: Debian Bookworm ships with libmpv2, but the python-mpv wrapper often looks for the legacy .so.1 symlink.
Fix: Create a manual symlink in the terminal:
sudo ln -s /usr/lib/aarch64-linux-gnu/libmpv.so.2 /usr/lib/aarch64-linux-gnu/libmpv.so.1
AO: [alsa] Failed to initialize audio output
Cause: The Raspberry Pi 5 completely removed the 3.5mm analog audio jack. If mpv tries to route audio to the default ALSA device, it fails.
Fix: Force the audio device to HDMI in the Python config (as shown in the code above) or via CLI: mpv --audio-device=alsa/hdmi video.mp4.
[vo/gpu] opengl context not found or Failed to create EGL context
Cause: You are running the script headless (via SSH) without an active Wayland/X11 session, or the GPU driver (v3d) is failing to allocate memory.
Fix: Ensure you are running the script from the local desktop terminal, not SSH. If running a true headless kiosk, you must configure a dummy DRM/KMS backend or use vo=x11 with a lightweight window manager like matchbox. For more on display servers, see the mpv video output documentation.
Extending and Simplifying the Build
How to Simplify (The Bash Route)
If you don't need GPIO integration and just want a looping kiosk display, skip Python entirely. Create a systemd service that executes a raw bash command. This reduces overhead and eliminates Python library dependency rot.
# /etc/systemd/system/video-kiosk.service
[Service]
ExecStart=/usr/bin/mpv --fullscreen --loop-file --hwdec=auto --audio-device=alsa/hdmi /home/pi/videos/loop.mp4
Restart=always
User=pi
How to Extend (Playlists and Network Triggers)
To scale this into a digital signage network, you can extend the Python script in two directions:
- Playlist Management: Instead of
player.play(), useplayer.loadlist('/path/to/playlist.m3u'). You can bind a second GPIO button toplayer.playlist_next()to skip tracks. - MQTT Network Triggers: Integrate the
paho-mqttlibrary. Subscribe to a topic likesignage/pi5/play. When a message arrives, dynamically update theVIDEO_PATHand callplayer.play(). This allows a central server to push video updates to a fleet of Pi 5s over Wi-Fi. - IR Remote Control: Wire an IR receiver (like the TSOP38238) to GPIO 18, and use the
lircdaemon to map remote presses tompv's native IPC socket, bypassing Python entirely for input handling.
By leveraging mpv and the Pi 5's dedicated HEVC decode silicon, you bypass the CPU bottlenecks that plagued older Pi models. Just remember to respect the 27W power envelope and force your audio over HDMI, and your embedded video player will run indefinitely.






