mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
Transitions have never run on BrightSign, and it was never a GPU problem.
`transitionRuntimeReady()` is a presence check on three globals and touches no
WebGL at all. A BrightSign roHtmlWidget is created with `nodejs_enabled: true`,
which puts Node's `module` into classic-script scope — so every shared module
that exported with an `else` took the CommonJS branch and never assigned its
browser global. The runtime was absent before WebGL was ever asked a question.
This is deducible from the fleet without touching the hardware: the player
pushes system.reboot / display.power / display.resolution / system.self_update
only behind BS.hasHost(), which needs require('@brightsign/messageport') to
resolve. Our XT245's stored capability row carries all four, so Node
integration was live in that page, so the CommonJS branch was taken.
Transitions are the least of it. schedule-eval.js had the same shape, and the
player falls back to "always active" when ScheduleEval is missing — so per-item
DAYPARTING silently stopped applying on that platform and scheduled content
played outside its window with nothing in any log. player-media-health.js the
same. Four files, all fixed by exporting to BOTH targets rather than either/or.
media-mute.js, orientation-style.js and wall-geometry.js already assigned their
globals in a separate unconditional block and were never affected; the audit
that reached me claimed all seven, and reading them is what separated the four
from the three.
THE GUARD, WITHOUT WHICH THE ABOVE IS A REGRESSION.
Restore the globals alone and BrightSign starts attempting video wipes it
cannot supply. On a hardware video plane drawImage(video) succeeds, throws
nothing, and paints a fully TRANSPARENT frame — so the wipe fades from nothing,
behind a video plane that is still lit. Worse than the hard cut it replaces.
The discriminator already existed: videoFrameIsCapturable() probes ALPHA, so a
genuine fade-to-black still reads as captured. It was wired into the screenshot
path and not this one, which asked isMediaReadable() — a CORS question, "am I
allowed to read this", not "did any pixels arrive". Both the outgoing frame and
the incoming warm-play snapshot now consult it, cached per platform, defaulting
to available while undetermined so a cold start is not crippled.
Net effect on BrightSign: image-to-image transitions light up, anything
involving video hard-cuts honestly, and dayparting starts working.
Full video transitions are reachable later — BrightSign documents that video
"captured as a canvas for WebGL processing must be routed to the GPU" via a
per-element hwz="off", which keeps hardware decode at an 8-bit/1080p ceiling.
That needs the hardware to validate and is not in this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS
85 lines
3.4 KiB
JavaScript
85 lines
3.4 KiB
JavaScript
// GL Transitions v1 param parser — ByteTinker, MIT
|
|
//
|
|
// Single source of truth for shader parameters. The `.glsl` files declare their own
|
|
// params using the GL Transitions comment convention, extended with an optional range:
|
|
//
|
|
// uniform float bounce; // = 0.5 [0.0..1.5]
|
|
//
|
|
// This parser is consumed by:
|
|
// - the web player renderer (uniform defaults + values from snapshot config)
|
|
// - the Tizen player renderer (same)
|
|
// - the dashboard picker (slider min/max/default/step)
|
|
//
|
|
// There is deliberately no separate param schema. If you find yourself adding one,
|
|
// the shader and the UI have already drifted.
|
|
|
|
const PARAM_RE =
|
|
/uniform\s+float\s+(\w+)\s*;\s*\/\/\s*=\s*(-?[\d.]+)\s*(?:\[\s*(-?[\d.]+)\s*\.\.\s*(-?[\d.]+)\s*\])?/g;
|
|
|
|
/**
|
|
* Parse param declarations out of a GLSL source string.
|
|
* @param {string} src
|
|
* @returns {Array<{name:string, default:number, min:number, max:number, step:number}>}
|
|
*/
|
|
function parseParams(src) {
|
|
const out = [];
|
|
let m;
|
|
PARAM_RE.lastIndex = 0;
|
|
while ((m = PARAM_RE.exec(src))) {
|
|
const def = parseFloat(m[2]);
|
|
const min = m[3] !== undefined ? parseFloat(m[3]) : Math.min(0, def);
|
|
const max = m[4] !== undefined ? parseFloat(m[4]) : (def === 0 ? 1 : def * 2);
|
|
out.push({ name: m[1], default: def, min, max, step: (max - min) / 200 });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Merge stored values over defaults, dropping unknown keys and clamping to range.
|
|
* Never trust the snapshot blob — a shader may have been edited since the playlist
|
|
* was configured, and an out-of-range uniform can produce a black frame.
|
|
* @param {Array} params result of parseParams
|
|
* @param {Object} stored values from snapshot.transition.params
|
|
*/
|
|
function resolveParams(params, stored) {
|
|
const out = {};
|
|
for (const p of params) {
|
|
const v = stored && typeof stored[p.name] === 'number' ? stored[p.name] : p.default;
|
|
out[p.name] = Math.min(p.max, Math.max(p.min, v));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// The renderer wraps each .glsl with this. Keep it identical across web, Tizen, and
|
|
// Android — the shader sources assume exactly these names and nothing else.
|
|
const PREAMBLE = `precision highp float;
|
|
varying vec2 vUv;
|
|
uniform sampler2D uFrom;
|
|
uniform sampler2D uTo;
|
|
uniform float progress;
|
|
uniform float ratio;
|
|
vec4 getFromColor(vec2 uv){ return texture2D(uFrom, uv); }
|
|
vec4 getToColor(vec2 uv){ return texture2D(uTo, uv); }
|
|
`;
|
|
|
|
const EPILOGUE = `
|
|
void main(){ gl_FragColor = transition(vUv); }`;
|
|
|
|
const VERTEX = `attribute vec2 aPos;
|
|
varying vec2 vUv;
|
|
void main(){ vUv = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;
|
|
|
|
// Export to BOTH, never either/or. A BrightSign roHtmlWidget is created with
|
|
// `nodejs_enabled: true`, which puts Node's `module` into the page's classic-script scope — so an
|
|
// `else` here means the browser branch never runs on that platform and `self.TransitionParams` is
|
|
// simply never set. The player's transitionRuntimeReady() is a presence check on exactly that
|
|
// global, so transitions were dark on every BrightSign, and the failure looked like a GPU problem
|
|
// rather than a module-format one. Anything a browser is meant to see must be assigned
|
|
// unconditionally.
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { parseParams, resolveParams, PREAMBLE, EPILOGUE, VERTEX };
|
|
}
|
|
if (typeof self !== 'undefined') {
|
|
self.TransitionParams = { parseParams, resolveParams, PREAMBLE, EPILOGUE, VERTEX }; // browser (player/demo)
|
|
}
|