Using a Raspberry Pi for Netflix has historically been a minefield of DRM (Digital Rights Management) failures and stuttering playback. Netflix requires Widevine L3 DRM decryption, which older ARM Linux setups struggled to handle in hardware. As of 2026, the ecosystem has matured, but you still cannot just plug in a Pi and expect a smart TV experience without deliberate configuration.

This guide cuts through the outdated forum posts. We are building a dedicated, wall-mountable Netflix kiosk using a Raspberry Pi 5, complete with a physical GPIO button controller for play/pause and volume. No wireless keyboards required.

Difficulty Rating: Intermediate (3/5) | Time: 90 minutes | Cost: ~$125 USD

The Verdict: Decision Tree for Pi & OS Selection

Before buying parts, you must choose the right board and operating system. DRM decoding is highly sensitive to RAM bandwidth and OS architecture. Use this decision path to select your setup.

Criteria Option A Option B Winner
Board Variant Raspberry Pi 4 (4GB/8GB) Raspberry Pi 5 (8GB) Pi 5 8GB (Handles 4K HEVC DRM without dropping frames)
OS Architecture 32-bit Raspberry Pi OS 64-bit Raspberry Pi OS (Bookworm) 64-bit (Native Widevine support, no 32-bit userland hacks needed)
Display Server Wayland (Default in Bookworm) X11 (Legacy) X11 (Required for reliable xdotool GPIO keystroke injection)
Frontend LibreELEC / Kodi Add-on Chromium Kiosk Mode Chromium (Better UI parity with smart TVs, easier GPIO integration)

Final Pick: Buy the Raspberry Pi 5 (8GB) and install Raspberry Pi OS 64-bit (Bookworm) configured to boot into X11. This is the only combination that guarantees stable 4K Netflix playback with custom hardware controls in 2026.

Hardware Spec Sheet & Parts List

Do not substitute the power supply. The Pi 5 requires a 5V/5A USB-C PD supply to enable the full 6A current limit on the PCIe and USB buses; throttling will cause DRM decode failures.

ComponentExact Model / VariantEst. Price
MicrocontrollerRaspberry Pi 5 (8GB RAM)$80
Power SupplyOfficial 27W USB-C PD Power Supply (White/Black)$12
CoolingRaspberry Pi Active Cooler (PWM controlled)$5
StorageSanDisk Extreme 64GB microSD (A2 / V30 rated)$15
Controls4x Sanwa OBSF-24mm Arcade Pushbuttons$8
Wiring24 AWG stranded hookup wire + Dupont crimps$5

GPIO Pin Mapping for Physical Controls

We are using the internal pull-up resistors on the Pi 5. Wire one leg of each button to the designated GPIO pin, and the other leg to any common Ground (GND) pin.

FunctionGPIO Pin (BCM)Physical Pin #Keyboard Emulation
Launch NetflixGPIO 529F11 (Fullscreen toggle)
Play / PauseGPIO 631Spacebar
Volume UpGPIO 1333Up Arrow
Volume DownGPIO 1935Down Arrow

Step-by-Step: OS Prep & Widevine DRM Setup

Before writing code, the OS must be prepped to handle DRM and accept GPIO keystrokes.

  1. Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS (64-bit) to your A2 microSD card. In the OS Customization menu, enable SSH and set your Wi-Fi.
  2. Force X11 Display Server: Bookworm defaults to Wayland, which blocks xdotool (our keystroke injector). Open a terminal and run sudo raspi-config. Navigate to Advanced Options > Wayland > X11. Reboot.
  3. Install Dependencies: Install Chromium, the Widevine DRM blob, and our Python/X11 tools:
    sudo apt update
    sudo apt install chromium-browser libwidevinecdm0 xdotool python3-gpiozero python3-pip
    pip3 install pynput --break-system-packages
  4. Verify DRM: Open Chromium, navigate to bitmovin.com/drm-test. If the Widevine test plays, your DRM pipeline is intact.
  5. Enable Kiosk Autostart: Create a desktop entry to launch Netflix on boot.
    mkdir -p ~/.config/autostart
    nano ~/.config/autostart/netflix.desktop
    Paste the following:
    [Desktop Entry]
    Type=Application
    Name=Netflix
    Exec=chromium-browser --kiosk --app=https://www.netflix.com --disable-infobars --noerrdialogs

Python Kiosk Controller Code

This script runs in the background, listening to the GPIO pins and translating physical button presses into X11 keystrokes that Chromium understands. Save this as netflix_controller.py.

#!/usr/bin/env python3
import subprocess
import time
import logging
from gpiozero import Button
from signal import pause

# Configure logging for debugging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

# Pin Definitions (BCM numbering)
BTN_LAUNCH = Button(5, pull_up=True, bounce_time=0.2)
BTN_PLAY_PAUSE = Button(6, pull_up=True, bounce_time=0.2)
BTN_VOL_UP = Button(13, pull_up=True, bounce_time=0.2)
BTN_VOL_DOWN = Button(19, pull_up=True, bounce_time=0.2)

def send_keystroke(key):
    """Sends a keystroke to the active X11 window using xdotool."""
    try:
        # Set DISPLAY env var for xdotool to find the X server
        env = {'DISPLAY': ':0', 'XAUTHORITY': '/home/pi/.Xauthority'}
        subprocess.run(['xdotool', 'key', key], env=env, check=True, timeout=2)
        logging.info(f"Sent keystroke: {key}")
    except subprocess.CalledProcessError as e:
        logging.error(f"xdotool failed for key {key}: {e}")
    except FileNotFoundError:
        logging.critical("xdotool not found. Run: sudo apt install xdotool")
    except Exception as e:
        logging.error(f"Unexpected error sending keystroke: {e}")

def on_launch():
    logging.info("Launch/Fullscreen button pressed.")
    send_keystroke('F11')

def on_play_pause():
    logging.info("Play/Pause button pressed.")
    send_keystroke('space')

def on_vol_up():
    logging.info("Volume Up button pressed.")
    send_keystroke('Up')

def on_vol_down():
    logging.info("Volume Down button pressed.")
    send_keystroke('Down')

# Attach callbacks
BTN_LAUNCH.when_pressed = on_launch
BTN_PLAY_PAUSE.when_pressed = on_play_pause
BTN_VOL_UP.when_pressed = on_vol_up
BTN_VOL_DOWN.when_pressed = on_vol_down

if __name__ == "__main__":
    logging.info("Netflix GPIO Controller started. Waiting for inputs...")
    try:
        pause() # Keeps the script running efficiently
    except KeyboardInterrupt:
        logging.info("Controller stopped by user.")

Tip: Run this script on boot by adding python3 /home/pi/netflix_controller.py & to your ~/.bashrc or creating a dedicated systemd service.

Troubleshooting: Playback & DRM Failures

If Netflix refuses to play and throws an error, do not guess. Follow this diagnostic path.

Exact Error String: libwidevinecdm.so: cannot open shared object file: No such file or directory or Netflix Error Code F7355.

The First Three Things to Check:

  1. Is the Widevine package actually installed? Run dpkg -l | grep widevine. If it returns nothing, run sudo apt install libwidevinecdm0. Raspberry Pi OS recently moved the 64-bit blob into the main repo, eliminating the need for third-party shims.
  2. Are you accidentally running Wayland? If you skipped Step 2 in the setup, xdotool will silently fail to send keystrokes, making it seem like the buttons are broken. Check your session type with echo $XDG_SESSION_TYPE. It must return x11.
  3. Is the Pi thermal throttling? DRM decoding spikes the CPU. Run vcgencmd get_throttled. If it returns anything other than throttled=0x0, your power supply is inadequate or your Active Cooler is not seated properly on the Pi 5 die.

Ranked Causes for Stuttering 1080p/4K Playback:

  • Cause 1 (60%): Using a Pi 4 instead of a Pi 5. The Pi 4 lacks the hardware HEVC decode pipeline required for smooth 4K DRM streams. Fix: Upgrade to Pi 5.
  • Cause 2 (25%): Chromium hardware acceleration is disabled. Fix: Go to chrome://settings/system and ensure "Use hardware acceleration when available" is checked.
  • Cause 3 (15%): Using a Class 4 or Class 10 microSD card causing swap-file lag. Fix: Use an A2-rated SanDisk Extreme card.

Extending and Simplifying the Build

Depending on your use case, you may want to strip this down or beef it up.

How to Simplify (The "No-Code" Route):
If you don't care about physical GPIO buttons and just want Netflix on a screen, abandon the Python script and X11. Flash LibreELEC to your SD card. Install the official Kodi Netflix Add-on via the CastagnetoIT repository. This handles Inputstream Adaptive and Widevine automatically, providing a 10-foot UI experience without writing a single line of Python. However, you lose the custom kiosk hardware integration.

How to Extend (IR Remote & CEC):
To add remote control capabilities without a Python script, enable HDMI-CEC. Add dtparam=audio=on and hdmi_drive=2 to your /boot/firmware/config.txt. This allows your TV's native remote to send play/pause commands directly through the HDMI cable to Chromium, bypassing the need for physical GPIO buttons entirely. For IR sensors, wire a TSOP38238 IR receiver to GPIO 18 and use the lirc daemon to map remote codes to xdotool commands.