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.
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.
| Component | Exact Model / Variant | Est. Price |
|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB RAM) | $80 |
| Power Supply | Official 27W USB-C PD Power Supply (White/Black) | $12 |
| Cooling | Raspberry Pi Active Cooler (PWM controlled) | $5 |
| Storage | SanDisk Extreme 64GB microSD (A2 / V30 rated) | $15 |
| Controls | 4x Sanwa OBSF-24mm Arcade Pushbuttons | $8 |
| Wiring | 24 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.
| Function | GPIO Pin (BCM) | Physical Pin # | Keyboard Emulation |
|---|---|---|---|
| Launch Netflix | GPIO 5 | 29 | F11 (Fullscreen toggle) |
| Play / Pause | GPIO 6 | 31 | Spacebar |
| Volume Up | GPIO 13 | 33 | Up Arrow |
| Volume Down | GPIO 19 | 35 | Down Arrow |
Step-by-Step: OS Prep & Widevine DRM Setup
Before writing code, the OS must be prepped to handle DRM and accept GPIO keystrokes.
- 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.
- Force X11 Display Server: Bookworm defaults to Wayland, which blocks
xdotool(our keystroke injector). Open a terminal and runsudo raspi-config. Navigate to Advanced Options > Wayland > X11. Reboot. - 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 - Verify DRM: Open Chromium, navigate to bitmovin.com/drm-test. If the Widevine test plays, your DRM pipeline is intact.
- Enable Kiosk Autostart: Create a desktop entry to launch Netflix on boot.
Paste the following:mkdir -p ~/.config/autostart nano ~/.config/autostart/netflix.desktop[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.
libwidevinecdm.so: cannot open shared object file: No such file or directory or Netflix Error Code F7355.
The First Three Things to Check:
- Is the Widevine package actually installed? Run
dpkg -l | grep widevine. If it returns nothing, runsudo apt install libwidevinecdm0. Raspberry Pi OS recently moved the 64-bit blob into the main repo, eliminating the need for third-party shims. - Are you accidentally running Wayland? If you skipped Step 2 in the setup,
xdotoolwill silently fail to send keystrokes, making it seem like the buttons are broken. Check your session type withecho $XDG_SESSION_TYPE. It must returnx11. - Is the Pi thermal throttling? DRM decoding spikes the CPU. Run
vcgencmd get_throttled. If it returns anything other thanthrottled=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.






