mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
chore(release): v1.9.31
This commit is contained in:
parent
b29e3b4676
commit
70b2227fa8
121
CHANGELOG.md
121
CHANGELOG.md
|
|
@ -1,6 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased
|
## 1.9.31
|
||||||
|
|
||||||
|
A patch off 1.9.30 carrying the video-wall and playlist-preview work, a QA sweep that drove real
|
||||||
|
browsers and real panels rather than reading code, and the fix for a loop stall our own maintenance
|
||||||
|
was inflicting on a customer's fleet every morning.
|
||||||
|
|
||||||
### Fixed — a wall of portrait panels had to be built backwards (#236)
|
### Fixed — a wall of portrait panels had to be built backwards (#236)
|
||||||
The wall canvas was secretly framebuffer space, not the wall as you see it. That is invisible while
|
The wall canvas was secretly framebuffer space, not the wall as you see it. That is invisible while
|
||||||
|
|
@ -8,25 +12,132 @@ every panel is the normal way up, and actively misleading the moment one isn't:
|
||||||
panels standing side by side had to be **stacked vertically** in the editor, with a pre-rotated copy
|
panels standing side by side had to be **stacked vertically** in the editor, with a pre-rotated copy
|
||||||
of every video, before the output came out right. It worked, but only after trial and error, and it
|
of every video, before the output came out right. It worked, but only after trial and error, and it
|
||||||
meant a portrait wall could never reuse existing content.
|
meant a portrait wall could never reuse existing content.
|
||||||
|
|
||||||
Each panel now carries a mounting rotation (0/90/180/270), the canvas means the physical wall, and
|
Each panel now carries a mounting rotation (0/90/180/270), the canvas means the physical wall, and
|
||||||
the player works out the mapping — so side by side is drawn side by side and landscape content plays
|
the player works out the mapping — so side by side is drawn side by side and landscape content plays
|
||||||
across portrait panels unmodified. Applied on the web, Tizen and Android players.
|
across portrait panels unmodified. Applied on the web, Tizen and Android players.
|
||||||
|
|
||||||
**Existing walls are untouched and need no migration.** Every wall in the field is rotation 0, which
|
**Existing walls are untouched and need no migration.** Every wall in the field is rotation 0, which
|
||||||
takes the original code path verbatim — an operator who upgrades will not find a wall that was
|
takes the original code path verbatim — an operator who upgrades will not find a wall that was
|
||||||
aligned yesterday has moved. Rebuilding an existing portrait wall the natural way round is an opt-in
|
aligned yesterday has moved. Rebuilding an existing portrait wall the natural way round is an opt-in
|
||||||
change the operator makes when they choose to.
|
change the operator makes when they choose to.
|
||||||
|
|
||||||
While a display is a member of a wall, its per-panel rotation replaces its own Orientation setting:
|
While a display is a member of a wall, its per-panel rotation replaces its own Orientation setting:
|
||||||
the two describe the same physical fact, and honouring both turned the content twice.
|
the two describe the same physical fact, and honouring both turned the content twice.
|
||||||
|
|
||||||
### Added — a wall no longer hides its own screens (#235)
|
### Added — a wall no longer hides its own screens (#235)
|
||||||
Grouping displays into a wall replaced their individual cards, so one dead panel of a four-panel
|
Grouping displays into a wall replaced their individual cards, so one dead panel of a four-panel
|
||||||
wall was invisible from the dashboard, and inspecting a single screen meant pulling it out of the
|
wall was invisible from the dashboard, and inspecting a single screen meant pulling it out of the
|
||||||
wall (re-syncing the live wall) and putting it back. The wall screen now lists its panels with live
|
wall (re-syncing the live wall) and putting it back. The wall screen now lists its panels with live
|
||||||
online state and a link straight to each device's page, and the wall card on the dashboard shows a
|
online state and a link straight to each device's page, and the wall card on the dashboard shows a
|
||||||
per-member status chip. A screenshot can be requested per panel without disturbing playback.
|
per-member status chip. A screenshot can be requested per panel without disturbing playback.
|
||||||
|
|
||||||
|
### Fixed — the checkpointer was stalling the event loop for seconds at a time (#240)
|
||||||
|
Reported as loop lag that grew with uptime and reset on restart, with a distinctive signature: mean,
|
||||||
|
p50, p99 and max identical to two decimal places. That signature is not a fixed cost paid on every
|
||||||
|
cycle. It is what a `perf_hooks` histogram reports when a window recorded **exactly one** delay —
|
||||||
|
the mean is the raw value and every percentile returns the bucket ceiling above it. Reproducible
|
||||||
|
against the reported figures to the decimal (`record(1329070000)` gives mean 1329.07, p50/p99/max
|
||||||
|
1329.59). So the loop took one long turn that swallowed the whole sampling second, episodically.
|
||||||
|
|
||||||
|
The long turn was ours, and it is measured rather than argued. Running the real checkpointer worker
|
||||||
|
against a real WAL with one reader mid-transaction: a single main-thread write blocked for
|
||||||
|
**4,936 ms**, and the checkpoint that blocked it reported `WAL 8.8MB -> 8.8MB` — it reclaimed
|
||||||
|
nothing. `wal_checkpoint(TRUNCATE)` is the blocking form and its locks are held across
|
||||||
|
*connections*, so moving it to a worker thread in 1.9.2-patch3 took the fsync off the loop but not
|
||||||
|
the lock. It also does not throw when it cannot get those locks: it returns `busy=1` after sitting
|
||||||
|
on SQLite's five-second busy timeout. Five seconds of stalled loop for no benefit, reported as a
|
||||||
|
success.
|
||||||
|
|
||||||
|
It was reached far too easily. The rule was "escalate if the WAL grew across three consecutive 15s
|
||||||
|
runs", which **any sustained 45-second write burst** satisfies — a fleet powering on in the morning
|
||||||
|
does it daily. Escalation now needs the WAL to be in the upper half of its budget
|
||||||
|
(`WAL_CHECKPOINT_STARVATION_FLOOR_MB`, default 8) **and** to be outside a cooldown
|
||||||
|
(`WAL_CHECKPOINT_ESCALATE_COOLDOWN_MS`, default 5 min). Both gates are needed: a size floor alone
|
||||||
|
does nothing for a server whose WAL already sits above it, which was exactly the reported case.
|
||||||
|
|
||||||
|
The 16 MB high-water escalation bypasses both gates and is untouched, so *the WAL still cannot grow
|
||||||
|
unbounded*. A checkpoint that reclaimed nothing now says so in the log instead of reading like a
|
||||||
|
success.
|
||||||
|
|
||||||
|
Also softened the recovery path: when the checkpointer worker is declared unrecoverable, inline
|
||||||
|
autocheckpoint is re-armed on the main connection — a state that lasts the life of the process, and
|
||||||
|
therefore looks exactly like "degrades with uptime, a restart fixes it". It used to also run an
|
||||||
|
unconditional blocking checkpoint on the main thread on the way in; that now happens only above the
|
||||||
|
high-water mark, and the fallback state is served on `/api/status` rather than being inferable only
|
||||||
|
from a log line that may have rolled.
|
||||||
|
|
||||||
|
### Added — loop-lag telemetry that can be read correctly (#240)
|
||||||
|
The reported numbers were interpreted, reasonably, as a per-cycle cost, because nothing in them said
|
||||||
|
how many samples they were made of. `/api/status` now carries `samples` alongside the percentiles
|
||||||
|
(around 50 in a healthy second, 1 when a single turn swallowed it), `tick_gap_ms` measured on the
|
||||||
|
wall clock independently of the histogram, and `worst_tick_gap_ms` / `worst_tick_at` — monotone, so
|
||||||
|
five-minute polling can no longer miss an episode entirely. The `debug` block adds the checkpointer's
|
||||||
|
worker, fallback and respawn state.
|
||||||
|
|
||||||
|
Band semantics are deliberately unchanged: a one-sample window during a real stall is the correct
|
||||||
|
trigger for the shed valve, and suppressing it would blind the protection at the moment it is needed.
|
||||||
|
|
||||||
|
### Fixed — `device_telemetry` grew forever for any display that stopped reporting
|
||||||
|
The only trim was a per-device row cap applied on that device's own heartbeat, so a decommissioned,
|
||||||
|
swapped or seasonally-dark panel left its rows behind permanently. There is now a matching age sweep
|
||||||
|
(`TELEMETRY_RETENTION_DAYS`, default 30), per-device so it rides the existing index rather than
|
||||||
|
scanning, chunked and yielding like the status-log sweep. The default matches the uptime report's own
|
||||||
|
default window, so it cannot remove rows that report would have shown.
|
||||||
|
|
||||||
|
### Added — a playlist preview you can skip through (#239)
|
||||||
|
Reviewing item 8 of a playlist cost seven durations of waiting. The preview takes a skip/next
|
||||||
|
control.
|
||||||
|
|
||||||
|
### Added — a video playlist item defaults to the clip's own length (#237)
|
||||||
|
Rather than the generic default duration, which had to be corrected by hand for every video.
|
||||||
|
|
||||||
|
### Fixed — the dashboard preview of a rotated display (#238)
|
||||||
|
A display rotated 90°/270° was previewed the way its framebuffer is laid out rather than the way
|
||||||
|
people see it. It now matches what the wall shows.
|
||||||
|
|
||||||
|
### Fixed — three controls that did nothing, and a parity matrix that said otherwise
|
||||||
|
An audit of all four players against their shipped sources found controls a customer can press today
|
||||||
|
that change nothing. The volume slider worked on Android only: the dashboard sends
|
||||||
|
`set_volume { level: 0..1 }`, while the web player read `payload.value` and divided by 100 and Tizen
|
||||||
|
read `payload.value ?? payload.volume` — three complete, working volume implementations that could
|
||||||
|
not be driven. Correcting only the key would have been worse than leaving it broken, since
|
||||||
|
`level: 0.5` would have become 0.5%: the scale is now chosen by which key arrived, not by the
|
||||||
|
magnitude of the number.
|
||||||
|
|
||||||
|
Tizen's offline media cache could never have worked on a panel — its adapter used the deprecated
|
||||||
|
Filesystem API in three ways the IDL rules out, so `MediaCache.create()` returned null on every panel
|
||||||
|
in the fleet. BrightSign carried calls that compile and are documented to do something else. Every
|
||||||
|
fix cites the vendor document that proves it, and the linter now fails on each next time.
|
||||||
|
|
||||||
|
Four dashboard→device socket handlers had no capability gate, and a re-register could erase a panel's
|
||||||
|
recorded platform. The parity matrix and the capability baselines are now tested against the players'
|
||||||
|
**shipped** sources in both directions, so a baseline that over-claims and a player that gains a
|
||||||
|
handler without its baseline moving both fail the build.
|
||||||
|
|
||||||
|
### Fixed — a fresh panel skipped the first item of its playlist
|
||||||
|
A newly paired panel always learns its playlist before the media arrives, so the 3-second content
|
||||||
|
re-check is what really begins playback — and it advanced *past* the index already seeded for a
|
||||||
|
playlist that had not started. The first pass ran 1, 2, 3, 0, and item 1 appeared only after the list
|
||||||
|
wrapped. Reproduced on the emulator on every fresh pair.
|
||||||
|
|
||||||
|
### Fixed — a rotated wall panel screenshotted as a black rectangle
|
||||||
|
The mounting rotation introduced with #236 is the first real rotation on an ancestor of the video
|
||||||
|
surface, and the screenshot compositor pasted the frame with an axis-aligned rectangle — so on a
|
||||||
|
rotated panel it landed outside the capture bitmap and the dashboard received plain black. A panel
|
||||||
|
that looks dead while it is playing perfectly is the worst thing a diagnostic can say.
|
||||||
|
|
||||||
|
### Fixed — the service worker claimed credit for offline widgets it never sees
|
||||||
|
`sw.js` said its cache-first widget branch was what kept a widget rendering with the network gone. It
|
||||||
|
is not: the player mounts
|
||||||
|
widgets in an iframe sandboxed without `allow-same-origin`, making it an opaque-origin client that a
|
||||||
|
service worker does not control. Measured, not reasoned — five mounts over 25 seconds of real
|
||||||
|
playback left zero widget entries in the cache.
|
||||||
|
|
||||||
|
### Fixed — CI judged the capability baselines against the wrong source
|
||||||
|
The baselines describe what an un-updated display can do, so they are checked against the shipped
|
||||||
|
source via the latest tag. The default shallow checkout has no tags, so the lookup found nothing and
|
||||||
|
the suite silently fell back to the working tree — where a player's payload bug had just been fixed,
|
||||||
|
making the build demand a baseline change for displays that cannot possibly have the fix yet. Green
|
||||||
|
locally, red in CI, for a reason visible nowhere in the diff. The test job now fetches tags, and the
|
||||||
|
bidirectional assertions skip rather than invert when there are none.
|
||||||
|
|
||||||
## 1.9.30
|
## 1.9.30
|
||||||
|
|
||||||
A patch off 1.9.29 carrying two fixes for faults that are live and silent. Both were found by a QA
|
A patch off 1.9.29 carrying two fixes for faults that are live and silent. Both were found by a QA
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ android {
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
// Env-overridable so device-owner reinstalls (which require an ever-increasing
|
// Env-overridable so device-owner reinstalls (which require an ever-increasing
|
||||||
// versionCode — downgrades are blocked) don't churn this file each build.
|
// versionCode — downgrades are blocked) don't churn this file each build.
|
||||||
versionCode = (System.getenv("VERSION_CODE") ?: findProperty("VERSION_CODE") as String? ?: "103").toInt()
|
versionCode = (System.getenv("VERSION_CODE") ?: findProperty("VERSION_CODE") as String? ?: "104").toInt()
|
||||||
versionName = System.getenv("VERSION_NAME") ?: findProperty("VERSION_NAME") as String? ?: "1.9.29"
|
versionName = System.getenv("VERSION_NAME") ?: findProperty("VERSION_NAME") as String? ?: "1.9.31"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
openapi: 3.1.0
|
openapi: 3.1.0
|
||||||
info:
|
info:
|
||||||
title: ScreenTinker Public API
|
title: ScreenTinker Public API
|
||||||
version: 1.9.30
|
version: 1.9.31
|
||||||
description: |
|
description: |
|
||||||
Public, token-scoped REST API for ScreenTinker digital signage.
|
Public, token-scoped REST API for ScreenTinker digital signage.
|
||||||
|
|
||||||
|
|
|
||||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "screentinker",
|
"name": "screentinker",
|
||||||
"version": "1.9.30",
|
"version": "1.9.31",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "screentinker",
|
"name": "screentinker",
|
||||||
"version": "1.9.30",
|
"version": "1.9.31",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-node": "^5.2.1",
|
"@azure/msal-node": "^5.2.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "screentinker",
|
"name": "screentinker",
|
||||||
"version": "1.9.30",
|
"version": "1.9.31",
|
||||||
"description": "ScreenTinker - Digital Signage Management Server",
|
"description": "ScreenTinker - Digital Signage Management Server",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
|
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
|
||||||
id="http://screentinker.com/player" version="1.9.29" viewmodes="maximized">
|
id="http://screentinker.com/player" version="1.9.31" viewmodes="maximized">
|
||||||
<tizen:application id="ScrnTinkr1.ScreenTinker" package="ScrnTinkr1" required_version="2.4"/>
|
<tizen:application id="ScrnTinkr1.ScreenTinker" package="ScrnTinkr1" required_version="2.4"/>
|
||||||
<tizen:profile name="tv"/>
|
<tizen:profile name="tv"/>
|
||||||
<name>ScreenTinker</name>
|
<name>ScreenTinker</name>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue