For reliable, hardware-accelerated raspberry pi video playback on the Pi 5 running Bookworm OS, use mpv with the drm (Direct Rendering Manager) video output driver, controlled via Python and gpiozero. This bypasses the Wayland/X11 compositor overhead, delivering raw hardware overlay performance essential for 24/7 digital signage and kiosk loops.

The Raspberry Pi Video Playback Decision Matrix

Choosing the right media backend for embedded displays is where most kiosk builds fail. The legacy omxplayer is deprecated on Pi 5, and standard desktop players introduce unacceptable latency and screen-tearing. Use this decision path to select your backend:

Condition / Requirement Recommended Backend Hardware Decode Path
Need GUI, mouse support, and streaming plugins Kodi (LibreELEC) FFmpeg V4L2
Need network streams, complex playlists, and GUI VLC (cvlc) MMAL / V4L2
Need headless/DRM kiosk, GPIO control, low latency mpv (Python bindings) V4L2-Request / DRM-Prime
Default Pick: If you are building a dedicated physical appliance with button controls, terminate your decision here: Use mpv via python-mpv with vo='drm'. It claims the display hardware directly, eliminating the 200-400MB RAM overhead of running a desktop environment.

Hardware Spec Sheet and GPIO Pin Mapping

This build targets the Raspberry Pi 5 (8GB variant). The 8GB model is mandatory for smooth 4K H.265 buffering and prevents out-of-memory crashes when looping high-bitrate local files. Total BOM cost is approximately $165 USD in 2026.

Component Exact Variant / Model Spec Notes
Compute Board Raspberry Pi 5 (8GB) Requires active cooler; unified memory architecture.
Display Waveshare 7" HDMI LCD (H) 1024x600, IPS, capacitive touch (touch unused here).
Storage SanDisk Extreme PRO 128GB A2 rating required for fast OS boot and file indexing.
Controls 3x Cherry MX or Sanwa Arcade Buttons Momentary NO (Normally Open) switches.

GPIO Pin Mapping Table

We use internal pull-up resistors configured in software, so buttons only need to connect to Ground and the respective GPIO pin. No external resistors are required.

Function BCM GPIO Pin Physical Pin (40-pin Header) Wiring
Play / Pause 17 11 Switch between Pin 11 and GND (Pin 9)
Next Track 27 13 Switch between Pin 13 and GND (Pin 14)
Exit / Shutdown 22 15 Switch between Pin 15 and GND (Pin 20)

Complete Python Playback Controller Code

Before running this, install the dependencies on your Pi 5: sudo apt install libmpv-dev mpv and pip3 install python-mpv gpiozero. This script targets the Pi 5 board variant and assumes your video files are in a /home/pi/videos/ directory.

import mpv
import sys
import os
import glob
from gpiozero import Button
from signal import pause

# --- Pin Definitions (BCM Numbering) ---
PIN_PLAY_PAUSE = 17
PIN_NEXT = 27
PIN_EXIT = 22

VIDEO_DIR = '/home/pi/videos/'

def get_playlist():
    """Fetches all mp4/mkv files in the target directory."""
    extensions = ['*.mp4', '*.mkv', '*.avi']
    files = []
    for ext in extensions:
        files.extend(glob.glob(os.path.join(VIDEO_DIR, ext)))
    return sorted(files)

def main():
    playlist = get_playlist()
    if not playlist:
        print('Error: No video files found in', VIDEO_DIR)
        sys.exit(1)

    try:
        # Initialize MPV with Direct Rendering Manager (bypasses X11/Wayland)
        player = mpv.MPV(
            vo='drm',
            hwdec='auto',
            loop_playlist='inf',
            osd_level=0,
            input_default_bindings=False,
            input_vo_keyboard=False
        )
    except Exception as e:
        print(f'Failed to initialize mpv DRM backend: {e}')
        sys.exit(1)

    # Load playlist
    for video in playlist:
        player.loadfile(video, mode='append-play')

    # --- GPIO Button Setup ---
    # pull_up=None relies on gpiozero default internal pull-ups
    btn_play = Button(PIN_PLAY_PAUSE, pull_up=True, bounce_time=0.05)
    btn_next = Button(PIN_NEXT, pull_up=True, bounce_time=0.05)
    btn_exit = Button(PIN_EXIT, pull_up=True, bounce_time=0.05)

    def toggle_pause():
        player.pause = not player.pause
        print('Paused' if player.pause else 'Playing')

    def next_video():
        player.playlist_next()
        print('Skipped to next track')

    def safe_exit():
        print('Exit button pressed. Shutting down gracefully...')
        player.terminate()
        sys.exit(0)

    btn_play.when_pressed = toggle_pause
    btn_next.when_pressed = next_video
    btn_exit.when_pressed = safe_exit

    print('Raspberry Pi Video Playback Kiosk Active. Press Exit to quit.')
    
    try:
        pause() # Keep script alive to listen for GPIO events
    except KeyboardInterrupt:
        safe_exit()

if __name__ == '__main__':
    main()

Debugging: 'Failed to initialize EGL display' and Hardware Failures

When migrating raspberry pi video playback scripts from Pi 4 to Pi 5, or moving from X11 to Wayland, you will inevitably hit display server errors. The most notorious is:

[vo/gpu] Failed to initialize EGL display
[vo/gpu] Failed to create EGL context!
Error opening/initializing the selected video_out (-vo) device.

Ranked Causes and Fixes

  1. Cause: Running vo='gpu' headless (No Compositor). The GPU output requires an active Wayland or X11 session to create an EGL context.
    Fix: Change vo='gpu' to vo='drm' in the Python script to render directly to the framebuffer via KMS/DRM.
  2. Cause: Missing Mesa DRM libraries. Pi OS Bookworm Lite strips GUI libraries by default.
    Fix: Run sudo apt install libgl1-mesa-dri libegl1.
  3. Cause: User lacks /dev/dri permissions. The drm backend requires direct hardware access.
    Fix: Add your user to the render group: sudo usermod -aG render,video $USER and reboot.

The First 3 Things to Check When Playback Fails

If the script runs but the screen remains black or stutters, execute these diagnostic steps in order:

  1. Verify KMS Display Detection: Run kmsprint in the terminal. If your Waveshare display doesn't show up as a connected connector, check the HDMI cable and ensure dtoverlay=vc4-kms-v3d is active in /boot/firmware/config.txt.
  2. Check Hardware Decode Status: Run mpv --hwdec=auto test_video.mp4 manually. Watch the terminal output for Using hardware decoding (drmprime). If it falls back to software decoding, your CPU will throttle at 100% on 1080p60 content.
  3. Inspect Power Supply Throttling: The Pi 5 requires a 27W USB-C PD supply. Run vcgencmd get_throttled. If it returns anything other than 0x0, the Pi is throttling the GPU clock, causing frame drops.

Extending and Simplifying the Build

Depending on your deployment environment, you may need to scale this architecture up or strip it down.

How to Simplify (The Zero-Code Route)

If you do not need physical GPIO buttons and just want a monitor to turn on and loop a single video forever, delete the Python script entirely. Instead, use systemd to launch mpv directly. Create a service file at /etc/systemd/system/kiosk.service:

[Service]
ExecStart=/usr/bin/mpv --vo=drm --hwdec=auto --loop-file=inf /home/pi/videos/loop.mp4
Restart=always
User=pi

This removes Python overhead, reduces boot-to-video time to under 8 seconds, and eliminates gpiozero dependency conflicts.

How to Extend (Networked Signage)

To convert this into a networked digital signage node, integrate MQTT via the Paho library. Subscribe to an MQTT topic like signage/node_01/cmd. When a 'next' or 'download' payload arrives, trigger the player.playlist_next() method or initiate an rsync pull to update the local /home/pi/videos/ directory dynamically without rebooting the Pi. For comprehensive mpv configuration flags, refer to the official manual to fine-tune cache sizes for high-bitrate 4K streams over SMB shares.

Final Recommendation: Stick to the Pi 5 8GB, use the drm video output, and rely on python-mpv for control. It is the most robust, crash-resistant pipeline for embedded raspberry pi video playback available in 2026.