# Changelog ## 1.9.29-rc3 ### Fixed — autorun.zip could not be opened by a player Reported from a real automated deployment: the rc2 archive reached the player and was rejected as invalid. Two causes, both ours. - **The archive must be STORED, not compressed.** The player bootstrap extracts `autozip.brs` by itself before any script runs, and `roBrightPackage` supports a specific set of methods, of which "no compression" is the universally safe one. Both builders now store — and the server-side package builder had been using maximum deflate, so **every self-update package it produced would have failed the same way**, silently and in the field. - **`roBrightPackage`, not `roUnzip`**, is the supported reader. Converted in `autozip.brs` and in the self-update path. Both builders now assert the property instead of trusting the flag: the build script refuses a compressed member, and a test walks the archive's local file headers. A compressed package uploads, downloads and deploys perfectly and only then fails to open, which reads as a broken deployment rather than a broken zip. `autozip.brs` also adopts the shipped volume-discovery pattern — probe `USB1:`/`SD:`/`SSD:`/`FLASH:` for the archive rather than guessing, since a player may boot from internal flash. ### Fixed — muting never reached a YouTube item A YouTube item is a cross-origin iframe, so `el.muted` reaches nothing. The two browser-family players failed in opposite directions: the web player consulted autoplay policy and nothing else, so an item muted in the admin console **played with sound** and a wall follower blared alongside its leader; Tizen hardcoded `mute=1`, so YouTube there was **permanently silent** and no toggle could change it. Android was already correct. The rule now lives once in `server/lib/media-mute.js`, and the unmute prompt no longer appears on an item an operator deliberately muted. ### Fixed — screenshots reported success while sending blank frames Capture marked itself successful because the draw did not throw. On a hardware plane `drawImage(video)` returns a fully transparent image and throws nothing, so the dashboard showed a dead screen while the panel played perfectly. Capture is now proven by an alpha probe, so a genuine fade-to-black still reads as captured. ### Added - **BrightSign native synchronisation**, wired end to end and chosen per group, reusing the existing leader election. A group whose leader is offline falls back to the clock protocol rather than waiting for an announcement that never comes. - **Real telemetry and hardware identity** — temperature, player storage, model, OS version, serial and output index — instead of a block of nulls and a `wifi_ssid` of "Web Player" on a PoE appliance. - **Offline content caching** with correct range-request handling, and a **package self-update** whose version is stamped into `autorun.brs` at build time — unstamped, a player applies an update, still reports the old version, and is offered it forever. - **Command parity**: real `reboot`, real display blanking, and `set_volume` on BrightSign. ### Removed - The `user_agent` fallback in BrightSign detection. `devices` has no such column, so the branch was unreachable and passed only in a test that fabricated the field. ## 1.9.29-rc2 Fixes for three things rc1 only revealed once it was deployed and pointed at real hardware. ### Fixed - **The player assets 404'd in a container.** `/player/st-bridge.js` and `/player/st-sync.js` are served from `../brightsign` so the copy the player loads can never drift from the copy on the player's own storage — but the Dockerfile never copied that directory into the image, so both routes worked from a dev checkout and failed in Docker. Note how this fails when the route is absent entirely: the SPA fallback answers **200 with `text/html`**, so the browser gets a page where it expected JavaScript and the bridge silently never exists. - **A BrightSign kept re-pairing on every boot.** The bridge persisted `device_id` but not `device_token`. The server authenticates a claim to an existing display with the token, so an id presented without one reads as a brand-new player and gets a fresh device row. - **A BrightSign was labelled "Web Player".** It runs the same web player, so `client_type` is `player` and the device view fell through to a hardcoded label — indistinguishable from a browser tab, for a dedicated signage appliance. ### Added - **`autorun.zip` — a single-file player installer**, attached to every release and built by `scripts/build-autorun-zip.sh`. Drop it on the root of a player's storage and power-cycle. - **Booting from internal flash.** A player runs `FLASH:/autorun.brs` with no card present at all, so a failed card slot no longer ends a player's life. Confirmed on an XT245 with a physically dead microSD interface. ## 1.9.29-rc1 **BrightSign port.** The player on BrightSign is the ordinary web player running in an `roHtmlWidget` — that part already worked. This release adds the host around it, which covers what a page cannot do for itself, and a per-group choice of synchronisation protocol. Release candidate: cut for testing on alpha, not for production fleets. ### Added - **`brightsign/autorun.brs` — a supervised host, not a URL wrapper.** It owns the widget lifecycle, because a page-initiated `location.reload()` does not reliably bring an `roHtmlWidget` back: a deploy on 2026-07-28 reloaded every connected player and the BrightSign was the only one that never returned. The page now posts `{type:"restart"}` and the host rebuilds the widget. It also retries `load-error` with backoff, falls back to a local page, and runs a heartbeat watchdog that catches a page which loaded fine and then wedged — the case `load-error` never reports. - **`brightsign/st-bridge.js` — the page's half of that contract**, over `@brightsign/messageport`. Registry-backed identity (the registry outlives `localStorage` on this platform), restart-instead-of-reload, heartbeat, and sync-backend reporting. Every method degrades to a no-op off-platform, so it is served to every player rather than gated on a user agent. - **`brightsign/st-sync.js` — native SyncManager support.** Frame-accurate video sync between BrightSign players via `setSyncParams` on the standard `