mirror of
https://github.com/Screenly/Anthias.git
synced 2026-06-10 09:08:09 -04:00
feat/sentry-device-context
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1f438d2af0 |
perf(viewer): render video via QML VideoOutput in a QQuickWidget (#2975)
* perf(viewer): render video via QML VideoOutput in a QQuickWidget - replace the QGraphicsVideoItem-on-raster-QGraphicsView substrate: QVideoFrame::toImage did an RHI offscreen render + GPU->CPU readback per frame, capping presentation at 8.3 fps (Pi 4) / 10-12 fps (Pi 5) with a saturated GUI thread while HW decode ran fine (issue 2967). Validated on both testbeds: Pi 4 30.0 fps presented at 64% total CPU, Pi 5 26.6 fps at 13-35% - VideoOutput keeps frames on the GPU: scene-graph textures with shader YUV->RGB, composited through the same QQuickRenderControl FBO machinery QWebEngineView already uses (eglfs-safe, inherits whole-screen rotation -- re-validated under QT_QPA_EGLFS_ROTATION) - log frames-rendered (QQuickWindow::afterRendering) next to frames-delivered in playback-stats so presentation-side drops are visible -- the sink-only counter is how the 8 fps regression shipped unnoticed; connection is retried from play() so the counter can't silently stay dead - fail hard (qFatal) when the QML scene is unavailable instead of decoding video to nowhere: crash-respawn is supervised and loud, a silent black-screen kiosk is not - video-rotate maps to VideoOutput.orientation (still a defensive no-op; every platform rotates the whole screen) - ship qt6-declarative-dev + qml6-module-qtquick/-qtmultimedia in the Qt6 viewer images; drop the now-unused multimediawidgets - run the C++ tests with QT_QUICK_BACKEND=software so the QML scene loads under the offscreen platform Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(image-builder): align gstreamer-drop version comment to Qt 6.5 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3091fec349 |
feat(api,viewer): viewer REST shim + rename AnthiasWebview → AnthiasViewer (#2907)
* feat(api,viewer): viewer REST shim + rename AnthiasWebview → AnthiasViewer - Add GET /api/v2/viewer/playlist returning server-evaluated active assets, next deadline, and ``now``; gated by internal token. - Add GET /api/v2/viewer/settings exposing only the viewer-relevant settings subset (shuffle/show_splash/screen_rotation/audio_output/ debug_logging) so the internal-auth path doesn't surface operator credentials. - Rename the C++ binary AnthiasWebview → AnthiasViewer (.pro file, Dockerfile copies, sh.Command spawn, test runner) and the D-Bus service anthias.webview → anthias.viewer (atomic because both endpoints ship in the same image). - Migrate runtime state paths /data/.local/share/AnthiasWebview and /data/.cache/AnthiasWebview to AnthiasViewer with a one-shot symlink so existing devices keep QtWebEngine cookies / local- storage across the upgrade. - Source tree src/anthias_webview/ stays put; the directory rename is deferred to Phase 5 when the Python viewer package is deleted. First step of GH #2906; sets up the contract the C++ viewer will consume in Phase 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api,viewer): address review feedback on viewer REST shim - ViewerPlaylistViewV2 now reloads anthias.conf on read so an in-flight settings PATCH doesn't shuffle off a stale cached value — mirrors what ViewerSettingsViewV2 already did. - AssetSerializerV2.get_is_active accepts ``now`` via context so ViewerPlaylistViewV2 can render the ``is_active`` field against the same instant the filter used; closes the millisecond race where a row right on a window boundary could be returned in ``assets`` while its ``is_active`` re-evaluated to False. - Simplify the windowed-deadline-cap test assertion: parse the ISO timestamp and compare datetimes directly instead of the awkward dual-format string-prefix check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tests): use https in viewer API fixture URI Silences SonarCloud python:S5332 on tests/test_viewer_api.py. The fixture URIs are never fetched — they just satisfy the ``uri`` field on Asset.objects.create — but matching the existing test_recheck_endpoint.py convention keeps the linter quiet without sprinkling NOSONAR comments through test data. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(viewer): drop QtWebEngine state symlink-migration on rename Validated on real hardware: a fresh AnthiasViewer cache rebuilds itself on the next page load, so the bookkeeping to preserve cookies / local-storage across the AnthiasWebview → AnthiasViewer rename isn't worth the code. Upgraded devices just get fresh state dirs alongside the (now-orphaned) old AnthiasWebview tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cc92a714e4 |
feat(viewer,webview): embed QtMultimedia in AnthiasWebview, eliminate two-process DRM contention + Pi 4 drops (#2905)
* 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> |