On 2026-07-09 a kernel keyring-GC oops left the player stuck in
uninterruptible D-state while systemd, SSH, and the Flask app stayed
alive -- so the box sat dead all night instead of recovering. A plain
systemd/hardware watchdog only fires on a TOTAL hang and would not have
caught that partial wedge.
mediapi-watchdog.service (Type=simple, Restart=always, runs as root)
pings mpv over its JSON IPC socket every 30s; after ~3 min of continuous
failure it forces a reboot (systemctl reboot -ff, then SysRq as a
kernel-level fallback that works even when userspace is wedged). A
D-state mpv accepts the socket connect but never replies, so the ping
times out and is caught. install.sh installs + enables it.
Verified: no false positives while mpv is healthy; correctly detects a
stopped mpv and reaches the reboot decision (tested in dry-run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kodi was overkill for a phone-driven "play this video" box: a full media
center (web server, CEC, library DB) whose surface area is exactly what
wedged the Pi -- a video-decode session left Kodi stuck in an
uninterruptible firmware-mailbox call after a kernel keyring Oops, dead
until a power cycle.
mpv is just a video player: hardware-decoded straight on KMS/DRM, no
media-center baggage. It runs as its own --idle systemd service holding
the playlist, so playback keeps going even if the app/phone/WiFi drop --
the same autonomy Kodi's native playlist gave us. The app talks to it
over its JSON IPC Unix socket.
PlayerStateManager keeps the exact same public API, so the Flask routes,
templates, and UI are unchanged (only the engine underneath swaps out).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Playback moves to Kodi (standalone on GBM/KMS -- the smooth, hardware-decoded
LibreELEC path). mediapi becomes a thin remote: it browses media and controls
Kodi over its JSON-RPC HTTP API. This drops every mpv/DRM/X-mirror problem
(DRM-master exclusivity, gpu-next "export failed" wedges, X-mirror A/V desync,
software-decode choppiness) -- none of which had a working single config.
- new mediapi/kodi.py (stdlib JSON-RPC client); player.py rewritten to poll
and drive Kodi (Player.Open/PlayPause/Seek, Application.SetVolume,
Player.GetProperties), same public interface + keep-playing auto-advance
- config.py: KODI_* settings replace MPV_SOCKET; __init__ + api_routes updated
- delete mpv_ipc.py, the mpv/X units, start-mpv/session scripts
- new systemd/mediapi-kodi.service.template (standalone Kodi on tty1) +
scripts/configure-kodi.py (headlessly enable Kodi's JSON-RPC web server)
- install.sh now bootstraps a BARE Pi OS Lite end to end: apt base packages,
installs uv, installs Kodi, enables its web API, configures the AP, installs
+ starts services (only git need be preinstalled). Auto-seeds .env from the
example on first run.
- README + .env.example updated for the Kodi architecture
Dual HDMI is handled in hardware (external splitter off one port); no software
mirror -- see git history for why that can't work smoothly on this Pi.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Pi 4's two HDMI connectors share one vc4 DRM card, and DRM master is
exclusive per card -- so the old per-connector-mpv design could never light
the second screen (the mirror mpv died with "Failed to acquire DRM master:
Permission denied"). Replace it with a minimal X server started by the
mediapi-mpv unit via xinit: X is the single DRM master, xrandr --same-as
clones the first output onto every other connected HDMI, and one fullscreen
mpv renders to both. Uses hwdec=v4l2m2m-copy (Pi HW decoder, ~1/3 the CPU of
software) and carries audio + the app's sole IPC socket.
- new scripts/mediapi-session.sh (X client: waits for connectors, mirrors,
execs mpv); rewritten mediapi-mpv unit (xinit on VT7)
- delete scripts/start-mpv.py and all mirror-socket/broadcast code in
player.py, config.py, __init__.py -- a single mpv means one socket
- install.sh installs xserver-xorg-core/xinit/x11-xserver-utils, writes
/etc/X11/Xwrapper.config, adds the user to input,tty
- README + troubleshooting updated
Verified from a cold boot on the Pi: both pixelvalve CRTCs scan the same
framebuffer; app healthy; HW decode engaged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>