3ac7f836c4
Replaces the Windows port with a Raspberry Pi / Linux implementation on Raspberry Pi OS "Trixie" (Debian 13, aarch64, Wayland/labwc). The Windows code is removed here but preserved on the Windows-Player branch. Entry point ----------- linux/run_linux.py replaces windows/run_win.py. src/main.py stays platform-neutral; all Pi-specific behaviour is injected from linux/. Five bugs that prevented the port (all measured on real hardware) ---------------------------------------------------------------- 1. Kivy's PyPI wheel bundles an SDL2 built WITHOUT the wayland driver, so no window could be created (Trixie has no X server). linux/fix_kivy_sdl2.sh symlinks the system SDL2 over the bundled filename. 2. SDL2 requires WAYLAND_DISPLAY to be *set* - the socket alone is not enough, unlike wlopm. This broke every systemd/cron/autostart launch. linux_display.ensure_session_environment() detects and exports it. 3. Kivy's Clock resolves callbacks via func.__name__; a patch assigned under a different name crashed the player ~20s after a successful start. 4. The inherited signal_screen_activity() shelled out to tvservice, xdotool and ydotool - none exist on Trixie - and mis-escaped 'wlopm --on \*', so the display blanked after 10 minutes. 5. The launchers ran src/main.py directly, bypassing every platform patch and resolving the data directory one level too high. Web links --------- - --ozone-platform-hint=auto does NOT fall back to Wayland on Chromium 152; it aborts. The platform is now chosen explicitly. - The keyring password prompt is suppressed via the ENVIRONMENT, not the flags: launch_env() strips DBUS_SESSION_BUS_ADDRESS for the child so Chromium cannot reach gnome-keyring-daemon. - Teardown kills the whole process group (needs start_new_session=True); previously it silently fell back to terminate() and orphaned children. Video normalisation ------------------- A 4K video cannot play on a Pi 4: ffpyplayer decodes in software, measured at 0.90x realtime (1080p is 3.03x). Oversized media is downscaled to 1920x1080 at sync time using the hardware h264_v4l2m2m encoder (~31s for an 18s clip), triggered by resolution only so already-playable files are untouched. src/media_state.py owns the shared on-disk contract: a .kiwy-converting marker makes the player skip the item while it is being rebuilt, then the converted file is played instead. If nothing is playable at all (a single-item playlist whose only video is converting), the player loops the intro video rather than leaving a blank screen. Also fixed ---------- - network_monitor: replaced netsh/ifconfig/dhclient with nmcli (Trixie uses NetworkManager; ifconfig and dhclient are not even installed). - Removed the Windows-only focus keeper/guardian from main.py. - main.py: duplicate SDL_AUDIODRIVER setdefault (a silent no-op); Settings "Test connection" now uses tempfile.gettempdir(). - config/app_config.json: credentials blanked so a fresh clone runs the first-run setup flow. Verification ------------ linux/test_media_state.py 18/18, test_linux_patches.py 21/21, test_linux_browser_flags.py 27/27. Verified live against a real DigiServer: image -> weblink -> image -> video with correct durations, zero leaked Chromium processes, and no throttling over a 10 minute monitored run.
71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
"""Ad-hoc video playback probe (not part of the test suite).
|
|
|
|
Plays the intro video in a Kivy window and reports whether it decodes and
|
|
advances. Used to verify the ffpyplayer path on Raspberry Pi OS Trixie.
|
|
|
|
.venv/bin/python linux/_probe_video.py [path/to/video.mp4]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
# Mirror the real entry point: SDL2 does NOT discover the Wayland socket on its
|
|
# own, so WAYLAND_DISPLAY must be filled in first. Without this the probe falls
|
|
# back to x11 and dies with "Couldn't connect to X server".
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from linux_display import ensure_session_environment # noqa: E402
|
|
|
|
ensure_session_environment()
|
|
|
|
os.environ.setdefault('SDL_VIDEODRIVER', 'wayland,x11,dummy')
|
|
os.environ.setdefault('KIVY_GL_BACKEND', 'gl')
|
|
os.environ.setdefault('KIVY_VIDEO', 'ffpyplayer')
|
|
os.environ.setdefault('KIVY_AUDIO', 'ffpyplayer')
|
|
|
|
from kivy.config import Config # noqa: E402
|
|
|
|
Config.set('graphics', 'window_state', 'hidden')
|
|
Config.set('graphics', 'fullscreen', '0')
|
|
|
|
from kivy.app import App # noqa: E402
|
|
from kivy.clock import Clock # noqa: E402
|
|
from kivy.uix.video import Video # noqa: E402
|
|
|
|
SOURCE = sys.argv[1] if len(sys.argv) > 1 else 'config/resources/intro1.mp4'
|
|
DURATION = 12.0
|
|
results = {}
|
|
|
|
|
|
class Probe(App):
|
|
def build(self):
|
|
self.video = Video(
|
|
source=SOURCE, state='play', options={'eos': 'stop'},
|
|
allow_stretch=True, keep_ratio=True,
|
|
)
|
|
self.video.bind(on_eos=lambda *a: results.setdefault('eos', True))
|
|
return self.video
|
|
|
|
def on_start(self):
|
|
self.t0 = time.monotonic()
|
|
Clock.schedule_interval(self.tick, 1.5)
|
|
Clock.schedule_once(lambda dt: self.stop(), DURATION)
|
|
|
|
def tick(self, dt):
|
|
elapsed = time.monotonic() - self.t0
|
|
core = self.video._video
|
|
position = getattr(core, 'position', None) if core else None
|
|
if position is not None:
|
|
results['last_position'] = position
|
|
print(
|
|
f' t={elapsed:5.1f}s state={self.video.state} '
|
|
f'duration={self.video.duration:.1f} position={position} '
|
|
f'texture={"yes" if self.video.texture else "no"}',
|
|
flush=True,
|
|
)
|
|
|
|
|
|
Probe().run()
|
|
print(f'RESULT duration={results.get("duration")} '
|
|
f'last_position={results.get("last_position")} eos={results.get("eos")}')
|