Web links were implemented three times (main.py subprocess, run_win.py
subprocess + Win32 overlay, cef_browser.py embedded CEF), each owning its own
process handle, watchdog and teardown. That ambiguity caused leaked browsers,
skipped items, lost foreground and blank screens when a page failed to load.
Replace all three with a single owner in src/weblink_session.py:
- WeblinkSession: validate -> launch -> verify -> watch -> teardown.
Generation-tokened so stale callbacks are ignored, idempotent close(),
atexit-safe, never more than one browser alive.
- WeblinkAdapter: the only platform-specific part (launch / wait_visible /
is_alive / teardown / prewarm). Platform layers inject engines through
SignagePlayer.weblink_adapter_factory.
- ChromiumSubprocessAdapter: default engine (Pi chromium, Windows chrome/msedge).
- InteractionWatcher: decides when an item is finished.
- WebInputSources: /dev/input/event* (Linux) plus a GetCursorPos pointer tap
(needed on Windows for embedded CEF, which has no child process).
Interaction model: web links are an interactive surface, not timed media.
The player advances only when the configured duration has elapsed AND the
viewer has not interacted for 10s, measured from the most recent interaction.
A touch in the final seconds of a slot therefore pushes the advance 10s past
that touch, and each further touch pushes it again, so a page is never pulled
out from under someone using it. An untouched page still advances on schedule.
A drag burst counts as one interaction but the countdown tracks its last event,
so an item cannot be cut off mid-gesture. max_dwell (duration x factor, floored
by min_max_dwell) is an absolute backstop against a wedged browser or a jammed
touchscreen.
Verified start-up: the visibility wait runs on the watcher thread, never on
Kivy's main thread. If the browser window never appears the item is reported
failed and skipped, instead of resetting the error counter and leaving a black
screen up for the whole duration.
Config: new "weblink" block in config/app_config.json (engine,
interaction_postpone, interaction_debounce, interaction_grace,
max_dwell_factor, min_max_dwell, launch_timeout, prewarm) with safe defaults,
so an absent block still works.
Also add weblink_session to the PyInstaller hiddenimports so the frozen exe
bundles the new module.
- Added kivy.lib.gstplayer to excluded_imports (we use ffpyplayer)
- Added all cefpython3_py{27,34-311}.pyd to excluded_imports
- Removes ~300 lines of library-not-found warnings from build output
- Reduces .exe size from 143 MB to 96 MB
v1 created a separate Win32 window — same problem as external Chrome.
v2 creates CEF as a CHILD WINDOW of Kivy's SDL_app window:
- No separate taskbar entry
- No z-order fighting (CEF is INSIDE Kivy)
- No desktop flash
- CEF message loop pumped via Kivy Clock (main thread)
- Resize handler attached so CEF follows Kivy window changes
- build.spec includes cef_browser in hidden imports
BUG-009: _on_video_eos was empty (stub). Added Clock.schedule_once
for next_media() when video reaches end of stream.
BUG-008: download_media_files only ran when server_version >
local_version. Added download check in up-to-date branch so media
files are synced even when playlist version hasn't changed.
Windows-specific fixes:
- _windows_play_weblink: uses --start-maximized + --app=URL for true fullscreen
- Shows black Win32 overlay before opening/closing Chrome to mask desktop
- _windows_kill_process_tree: uses taskkill /F /T to kill all Chrome child processes
- _Win32Overlay class: fullscreen borderless always-on-top black window
- Updated README to note Python 3.12 requirement and local data dir behavior
- New windows/ directory with build scripts, specs, and configuration
- Windows-specific requirements (requirements_win.txt)
- Launch and runtime scripts for Windows (run_win.py, launch_player.bat)
- PyInstaller build configuration (build.spec)
- Updated .gitignore to exclude windows/venv312/
- Updated config and source files for Windows compatibility
- Moved working_files to proper directory