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.

Table 1: Media Player Comparison for Raspberry Pi 5 (64-bit Bookworm)
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

Table 2: GPIO Wiring for Physical Stop/Skip Button
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.

  1. Update the OS and install system dependencies:
    sudo apt update
    sudo apt install -y mpv libmpv-dev python3-dev python3-venv python3-gpiozero
  2. Create an isolated virtual environment:
    mkdir ~/pi-video-player && cd ~/pi-video-player
    python3 -m venv venv
    source venv/bin/activate
  3. Install the Python packages:
    pip install python-mpv

    Note: gpiozero is pre-installed in the system Python, but if you need it in your venv, run pip 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:

  1. 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 power for undervoltage warnings.
  2. 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.
  3. 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

Error 1: 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
Error 2: 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.

Error 3: [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(), use player.loadlist('/path/to/playlist.m3u'). You can bind a second GPIO button to player.playlist_next() to skip tracks.
  • MQTT Network Triggers: Integrate the paho-mqtt library. Subscribe to a topic like signage/pi5/play. When a message arrives, dynamically update the VIDEO_PATH and call player.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 lirc daemon to map remote presses to mpv'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.