mediapi/scripts/mediapi-session.sh
Jean-Michel Tremblay ee72d8b735 Drive both HDMI outputs via X kiosk + single mirrored mpv
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>
2026-07-07 20:59:05 -04:00

78 lines
2.6 KiB
Bash
Executable file

#!/bin/sh
#
# mediapi X session -- the X client launched by xinit from mediapi-mpv.service.
#
# Mirrors every extra HDMI output onto the first, then runs ONE fullscreen mpv.
#
# Why an X server at all (we used to run mpv straight on DRM/KMS for fast boot):
# DRM "master" is exclusive per card. The Pi 4's two HDMI connectors live on a
# single vc4 card, so two independent mpv processes cannot both modeset -- the
# second gets "Failed to acquire DRM master: Permission denied" and shows
# nothing. A single X server is the one DRM master and drives BOTH connectors;
# `xrandr --same-as` clones the first output's framebuffer onto the others, so
# one mpv appears on every screen. See git history for the full diagnosis.
#
set -u
RUNTIME_DIR="${MEDIAPI_RUNTIME_DIR:-/run/mediapi}"
SOCKET="${MEDIAPI_MPV_SOCKET:-$RUNTIME_DIR/mpv.sock}"
# Always-on car display: never blank or DPMS-off.
xset -dpms 2>/dev/null || true
xset s off 2>/dev/null || true
connected() { xrandr --query 2>/dev/null | awk '/ connected/{print $1}'; }
# Cold-boot race: the second HDMI connector may not be probed as "connected"
# when X starts. Wait for connected outputs to appear, then let the set settle
# so a screen that lights up a beat later is still mirrored (bounded so a
# genuinely single-screen setup doesn't hang the boot).
deadline=$(( $(date +%s) + 30 ))
outs="$(connected)"
while [ -z "$outs" ] && [ "$(date +%s)" -lt "$deadline" ]; do
sleep 1
outs="$(connected)"
done
stable=0
settle_end=$(( $(date +%s) + 8 ))
while [ "$(date +%s)" -lt "$settle_end" ]; do
sleep 1
cur="$(connected)"
if [ "$cur" != "$outs" ]; then
outs="$cur"
stable=0
else
stable=$(( stable + 1 ))
[ "$stable" -ge 2 ] && break
fi
done
primary=""
rest=""
for o in $outs; do
if [ -z "$primary" ]; then primary="$o"; else rest="$rest $o"; fi
done
echo "mediapi-session: connected outputs:$outs (primary=${primary:-none})"
for o in $rest; do
echo "mediapi-session: mirroring $o onto $primary"
xrandr --output "$o" --same-as "$primary" || true
done
# Single mpv, on X (so one DRM master drives every mirrored connector). Carries
# audio and the app's IPC socket; the app talks only to this one socket now.
# hwdec=v4l2m2m-copy uses the Pi's hardware H.264/HEVC decoder (falls back to
# software for codecs it can't handle). Roughly a third of the CPU of software
# decoding 1080p -- important for an always-on player in a warm car. The single
# mpv has the HW decoder to itself now, so there's no contention.
exec mpv \
--idle=yes \
--vo=gpu-next \
--gpu-context=x11egl \
--hwdec=v4l2m2m-copy \
--fullscreen \
--no-terminal \
--keep-open=no \
--input-ipc-server="$SOCKET" \
--ao=alsa