mirror of
https://github.com/Screenly/Anthias.git
synced 2026-06-11 01:28:23 -04:00
* feat(viewer,webview): embed QtMultimedia in AnthiasWebview, eliminate Pi 4 frame drops (#2904) Move video playback inside AnthiasWebview's Qt 6 process via QtMultimedia (QMediaPlayer + QGraphicsVideoItem). The libmpv subprocess goes away — a single Qt process owns the eglfs/wayland surface, so the two-process DRM-master contention #2885 documented (600-2800 vo drops per 60 s clip on Pi 4) no longer applies. The D-Bus contract on MainWindow (playVideo / stopVideo / videoEnded) is preserved so Python still calls a stable interface even though the playback engine swapped underneath. Architecture * src/anthias_webview/src/videoview.{cpp,h} — new VideoView wraps QMediaPlayer + QGraphicsVideoItem + QAudioOutput. Qt 6.5 dropped the upstream gstreamer media backend so Debian Trixie ships only the ffmpeg-backed libffmpegmediaplugin.so; decode runs through libavcodec against the +rpt1 libav* packages already pinned in docker/_rpt1-ffmpeg-pin.j2 (which carry --enable-v4l2-request / --enable-v4l2-m2m so rpi-hevc-dec, bcm2835-codec, Hantro G2, rkvdec all engage automatically). * QGraphicsView + QGraphicsScene + QGraphicsVideoItem (not QVideoWidget) is the rendering substrate so video-rotate actually rotates the displayed frames — QGraphicsItem::setRotation is honoured by the painter, whereas QVideoWidget has no rotation property and a setProperty("rotation", angle) shortcut would store a dynamic value nothing reads. * src/anthias_webview/src/view.cpp — adds playVideo / stopVideo surface-switching alongside loadPage / loadImage; loadImage skips hideVideoSurface() for the 'null' sentinel so a freshly-started video isn't torn down ~66 ms after the first PLAYING event by the view_image('null') call that follows media_player.play() in asset_loop. * src/anthias_viewer/media_player.py — MPVMediaPlayer.play() routes through pydbus to the AnthiasWebview proxy. Per-codec hwdec dispatch + ffprobe codec sniff are gone; libavcodec auto-engages the right decoder. _marshal_dbus_options picks the GLib.Variant signature by Python type so int / bool / float options round-trip cleanly. video-rotate is sent as int. Operational * Pi 4 switches QT_QPA_PLATFORM from linuxfb to eglfs (QtMultimedia needs a GL context for the QGraphicsVideoItem painter). QT_QPA_EGLFS_KMS_CONFIG pins 1080p so V3D 6.0 doesn't have to composite Chromium + the video graphics view on top of the connector's native 4K. QT_SCALE_FACTOR=1 pins CSS-px to physical-px on the 1080p surface. * tools/image_builder/utils.py — drops libmpv2 / mpv from the viewer image, adds libqt6multimedia6 / libqt6multimediawidgets6 / qt6-multimedia-dev / qt6-image-formats-plugins. * /data/.anthias/playback-stats.log (renamed from mpv-stats.log) is capped at 8 MB; truncate on viewer start past the cap so a long- running 15 GB SD-card device can't fill up with 1 Hz SAMPLE rows. * VideoView::resolveAlsaDevice extracts CARD=<name> from the ALSA spec and matches the QAudioDevice id on that segment; logs the resolved id at INFO so multi-HDMI Pi 4 / Pi 5 mismatches are visible from journalctl. Validation Real-device measurements via /data/.anthias/playback-stats.log on the BBB pack (1080p / 4K, 30 / 60 fps, H.264 + HEVC), median across multi-cycle plays in the PR comments. Pi 4 BBB 1080p60 H.264 dropped from 2973 frames/min on the libmpv subprocess baseline to 0 with QtMultimedia. 12 h mixed-media burn-in: zero crashes, zero early- stops, no RSS leak across x86 / Pi 4 / Pi 5. 3 h asset-churn (120 toggles × 3 boards): zero <100 ms stops, drops stable. Rock Pi 4 arm64 image is built and identical to the validated set; the testbed itself is SSH-unreliable so its end-to-end run is deferred. C++ QtTest suite (8 cases) covers VideoView construction, stop idempotency, empty / unknown audio device handling, and QGraphicsItem::rotation() actually receiving the angle for cardinal rotations and snapping non-cardinal angles to 0. Python suite (63 cases) covers options-dict composition, D-Bus marshalling for str / int / bool / float, settings reload, codec gate symmetry, proxy reset, and VLC fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(viewer,webview): polish stale comments from second PR review * MPVMediaPlayer.__init__ comment no longer says the C++ side owns a libmpv handle — it owns QMediaPlayer + QGraphicsVideoItem. * Rename _build_mpv_options to _build_video_options. The function composes options for QtMultimedia now; the "mpv" in the name is vestigial. Class names (MPVMediaPlayer / MediaPlayerProxy) are left alone — those are the public D-Bus contract. * LoadedMedia comment in videoview.cpp now reflects Qt 6's actual semantics: "metadata available, playback can start" — first decoded frame lands a hair later via videoFrameChanged. Starting the elapsed-ms clock here is still a few-ms approximation of "first frame on screen", which is the intent. * _marshal_dbus_options return type tightened from bare ``dict`` to ``dict[str, Any]`` for symmetry with the input annotation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tests): marshal test works with real PyGObject + tighten typing CI ships PyGObject so ``gi.repository.GLib`` is the real module — the prior test relied on conftest's MagicMock stub (which only kicks in when ``gi`` is missing) to invoke ``assert_any_call`` on GLib.Variant. On the real Variant class that's an AttributeError. Patch ``gi.repository.GLib.Variant`` to a sentinel-returning callable inside the test scope so the assertions work with either the stub host or the real PyGObject host. The marshal still picks signatures by Python type (``s`` / ``i`` / ``b`` / ``d``); the test now asserts on the per-key tuple rather than the spy. mypy errors: * Narrow ``_last_play_options`` / ``_last_play_uri`` return values via ``isinstance`` so they don't fall through Any (no ``# type: ignore``, no ``cast``). * Add ``gi`` / ``gi.*`` to the mypy-overrides ``ignore_missing_imports`` set so the conftest stub doesn't break the type-check on hosts without PyGObject. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
39 lines
1.2 KiB
Bash
Executable File
39 lines
1.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build and run AnthiasWebview's QtTest unit tests.
|
|
#
|
|
# Requires Qt 6 (qt6-base-dev, qt6-multimedia-dev). The viewer
|
|
# Docker image already ships those for the per-board builder stage
|
|
# — running this inside that container is the canonical
|
|
# environment.
|
|
#
|
|
# Usage: bin/test_webview_cpp.sh
|
|
#
|
|
# The tests run under ``QT_QPA_PLATFORM=offscreen`` so no real
|
|
# display server is needed; QtMultimedia's playback pipeline won't
|
|
# fully initialise (no rendering target) but the API surface plus
|
|
# the QGraphicsVideoItem rotation transform are testable that way.
|
|
# Decoder engagement + drop counts are exercised on real devices
|
|
# via the BBB test bed. CI integration is a follow-up.
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
WEBVIEW_DIR="src/anthias_webview"
|
|
BUILD_DIR="${WEBVIEW_DIR}/tests/build"
|
|
|
|
mkdir -p "${BUILD_DIR}"
|
|
pushd "${BUILD_DIR}" >/dev/null
|
|
|
|
qmake6 ../tests.pro
|
|
make -j"$(nproc)"
|
|
|
|
# QTEST_MAIN's generated binary exits non-zero on any test failure.
|
|
# ``QT_QPA_PLATFORM=offscreen`` skips connecting to a real display
|
|
# server / framebuffer — the tests don't render, they exercise
|
|
# the QMediaPlayer / QGraphicsVideoItem API surface plus the
|
|
# rotation transform.
|
|
QT_QPA_PLATFORM=offscreen ./AnthiasWebviewTests
|
|
|
|
popd >/dev/null
|