Using a raspberry pi as a media streamer remains one of the most reliable ways to bypass the sluggish, ad-heavy interfaces baked into modern smart TVs. While commercial streaming boxes are convenient, they lock you out of local network playback, custom add-ons, and lossless audio routing. By building your own embedded streamer, you gain total control over the hardware pipeline.

This guide cuts through the noise to give you a definitive hardware decision, a precise wiring schematic for a physical GPIO control panel, and the complete Python code to bridge those physical buttons to Kodi's JSON-RPC API. We are targeting the Raspberry Pi 5 (4GB) running LibreELEC 12 (Omega), as it is the current sweet spot for hardware AV1 decoding and dual-4K output.

The Verdict: Choosing Your Pi Board

Before buying parts, you need to match the silicon to your display and network requirements. The Pi 5 introduced a dedicated RP1 I/O controller and a hardware AV1 decoder, which fundamentally changed its viability for modern streaming compared to the Pi 4.

Decision Tree: Which Pi for Media Streaming?
If your requirement is... Choose this board Why it wins
Dual 4K @ 60Hz, AV1 hardware decode, local AI upscaling Raspberry Pi 5 (4GB) AV1 decode prevents CPU bottlenecking on modern YouTube/Netflix streams; RP1 chip handles dual micro-HDMI smoothly.
Single 4K HDR, lowest possible power draw, passive cooling Raspberry Pi 4 Model B (4GB) Mature ecosystem, runs cool enough for a passive aluminum case, handles HEVC/H.265 perfectly.
Portable 1080p streaming, battery-powered, ultra-compact Raspberry Pi Zero 2 W Draws under 1.5W, fits behind any monitor, but lacks hardware 4K decode and full-size HDMI.
The Concrete Pick: Buy the Raspberry Pi 5 (4GB). The 8GB variant is overkill for LibreELEC unless you are running a local Jellyfin server simultaneously. The 4GB model provides enough headroom for Kodi's skin engine and background PVR buffering without paying the silicon tax for unused RAM.

Parts List & GPIO Pin Mapping

This build integrates a physical 3-button control panel (Play/Pause, Volume Up, Volume Down) directly into the Pi's GPIO header. This is highly useful for headless setups, kiosk modes, or when the TV remote's CEC handshake inevitably fails.

Bill of Materials (BOM)

  • Compute: Raspberry Pi 5 (4GB) - ~$60
  • Thermal: Official Raspberry Pi Active Cooler - ~$5 (Do not use passive cases on the Pi 5; the BCM2712 throttles at 80°C under video decode loads).
  • Power: Official 27W USB-C PD Power Supply - ~$12 (Critical: Third-party 5V/3A supplies will trigger peripheral brownout warnings on the Pi 5).
  • Storage: SanDisk Extreme 64GB A2 U3 microSD - ~$15
  • Inputs: 3x 12mm Momentary Tactile Push Button Switches
  • Passives: 3x 10kΩ Resistors (for external pull-up stability, though internal pull-ups are enabled in software)
  • Wiring: Female-to-Male Dupont jumper wires

Pin Mapping Table

We are wiring the switches between the 3.3V rail and the GPIO pins. The script will configure the GPIO pins with internal pull-downs, registering a HIGH state when pressed.

Component Switch Pin 1 Switch Pin 2 Pi 5 GPIO / Power Pin BCM GPIO Number
Play / Pause Button 3.3V (Pin 1) Signal Pin 11 GPIO 17
Volume Up Button 3.3V (Pin 1) Signal Pin 13 GPIO 27
Volume Down Button 3.3V (Pin 17) Signal Pin 15 GPIO 22

Wiring the Physical Control Panel

Safety & Power Callout: Always disconnect the USB-C power supply before seating jumper wires on the GPIO header. Shorting the 3.3V rail to the 5V rail or GND while energized will instantly destroy the RP1 I/O controller on the Pi 5.
  1. Seat the Active Cooler: Align the thermal pads with the BCM2712 CPU and RP1 chip. Press down firmly until the push-pins click into the mounting holes. Connect the 4-pin PWM fan cable to the dedicated JST fan header on the Pi 5 (located near the USB ports), not the 5V GPIO pins.
  2. Wire the Power Rail: Connect a jumper wire from Pin 1 (3.3V) to the positive rail on your breadboard.
  3. Wire the Switches: Insert the three tactile switches across the breadboard center trench. Connect one leg of each switch to the 3.3V positive rail.
  4. Wire the GPIO Signals: Connect the opposite leg of the Play/Pause switch to Pin 11 (GPIO 17), Volume Up to Pin 13 (GPIO 27), and Volume Down to Pin 15 (GPIO 22).
  5. Flash LibreELEC: Use the official Raspberry Pi Imager to flash LibreELEC 12 (Omega) for the Pi 5 onto your microSD card. In the Imager settings, enable SSH and set a static IP if preferred, though Kodi's web server will use localhost for this script.

The Code: GPIO to Kodi JSON-RPC Bridge

To control Kodi without relying on flaky HDMI-CEC commands, we use Kodi's built-in JSON-RPC API. The following Python script uses the gpiozero library to detect button presses and the requests library to send HTTP POST payloads to Kodi.

Target Board: Raspberry Pi 5 (4GB). Requires LibreELEC or Raspberry Pi OS with sudo apt install python3-gpiozero python3-lgpio python3-requests.

#!/usr/bin/env python3
import requests
import json
import logging
from gpiozero import Button
from signal import pause

# --- Configuration & Pin Definitions ---
PIN_PLAY_PAUSE = 17
PIN_VOL_UP = 27
PIN_VOL_DOWN = 22

KODI_IP = '127.0.0.1'
KODI_PORT = 8080
KODI_USER = 'kodi'
KODI_PASS = ''  # Leave blank if no password is set in Kodi

KODI_URL = f'http://{KODI_IP}:{KODI_PORT}/jsonrpc'

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Initialize Buttons with bounce_time to prevent contact chatter
btn_play = Button(PIN_PLAY_PAUSE, bounce_time=0.05, pull_up=False)
btn_vol_up = Button(PIN_VOL_UP, bounce_time=0.05, pull_up=False)
btn_vol_down = Button(PIN_VOL_DOWN, bounce_time=0.05, pull_up=False)

def send_kodi_rpc(method, params=None):
    """Sends a JSON-RPC payload to Kodi and handles connection errors."""
    headers = {'Content-Type': 'application/json'}
    payload = {
        'jsonrpc': '2.0',
        'method': method,
        'id': 1
    }
    if params:
        payload['params'] = params

    try:
        response = requests.post(
            KODI_URL, 
            data=json.dumps(payload), 
            headers=headers, 
            auth=(KODI_USER, KODI_PASS), 
            timeout=2
        )
        response.raise_for_status()
        logging.info(f'Success: {method}')
    except requests.exceptions.ConnectionError as e:
        logging.error(f'Connection failed. Is Kodi running and JSON-RPC enabled? Error: {e}')
    except requests.exceptions.RequestException as e:
        logging.error(f'RPC Request failed: {e}')

# --- Button Callbacks ---
def on_play_pause():
    logging.info('Play/Pause pressed')
    send_kodi_rpc('Player.PlayPause', {'playerid': 0})  # 0 is usually the active video player
    # Fallback to playerid 1 if audio is playing
    send_kodi_rpc('Player.PlayPause', {'playerid': 1})

def on_vol_up():
    logging.info('Volume Up pressed')
    # Increment volume by 5%
    send_kodi_rpc('Application.SetVolume', {'volume': 'increment'})

def on_vol_down():
    logging.info('Volume Down pressed')
    send_kodi_rpc('Application.SetVolume', {'volume': 'decrement'})

# --- Bind Events ---
btn_play.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('Kodi GPIO Bridge started. Waiting for button presses...')
    # Keep the script running
    pause()

Deployment Note: Save this as kodi_gpio_bridge.py. To run it on boot in LibreELEC, place it in /storage/.config/autostart.sh or create a systemd service in Raspberry Pi OS. Ensure you enable the web server in Kodi first.

Debugging: When the API Refuses to Connect

The most common failure point in this build is not the hardware wiring, but the network handshake between the Python script and Kodi. If your buttons do nothing and your logs spit out the following error, follow the ranked troubleshooting path below.

Exact Error String:
requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8080): Max retries exceeded with url: /jsonrpc (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f8b9c0>: Failed to establish a new connection: [Errno 111] Connection refused'))

The First Three Things to Check

  1. Kodi Web Server is Disabled (90% of cases): By default, Kodi's JSON-RPC API is turned off for security. Navigate in Kodi to Settings (Gear Icon) > Services > Control. Toggle Allow remote control via HTTP to ON. Ensure the port is set to 8080.
  2. Missing lgpio Backend on Pi 5: If the script crashes immediately with a PinFactoryFallback or ModuleNotFoundError: No module named 'lgpio', it means gpiozero cannot talk to the Pi 5's RP1 chip. The legacy RPi.GPIO library is deprecated on Pi 5. Fix this by running sudo apt install python3-lgpio in the terminal.
  3. Local Firewall Blocking Loopback: If you are running a hardened Raspberry Pi OS build with ufw enabled, port 8080 might be blocked even on localhost. Run sudo ufw allow 8080/tcp or temporarily disable the firewall to isolate the issue.

Extending or Simplifying the Build

Once the baseline GPIO bridge is stable, you can adapt the hardware to fit your specific living room constraints.

How to Simplify (The No-Wire Route): If soldering and breadboarding aren't your preference, ditch the GPIO script entirely. Purchase a Flirc USB IR Receiver (~$20). It emulates a standard USB keyboard, allowing you to map any old TV remote directly to Kodi without writing a single line of Python or dealing with GPIO pull-up resistors.

How to Extend (Audiophile Output): The Pi 5's native 3.5mm audio jack is PWM-based and suffers from a high noise floor, making it unsuitable for high-end stereo receivers. To fix this, stack an I2S DAC HAT like the HiFiBerry DAC+ Standard onto the GPIO header. This bypasses the Pi's internal audio routing entirely, feeding lossless digital audio directly to a Texas Instruments PCM5102a DAC chip. You will need to add dtoverlay=hifiberry-dacplus to your config.txt file to enable it, but the resulting signal-to-noise ratio is indistinguishable from dedicated $500 streamers.

Building your own streamer eliminates the planned obsolescence of commercial dongles. By anchoring your build on the Pi 5's AV1 decode capabilities and bridging physical controls directly to the Kodi API, you create a media endpoint that is both future-proof and entirely under your command.