From 95b8d1b2936a1c8ecc19e938a8b8b699264b5965 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Fri, 7 Aug 2026 10:36:50 -0500 Subject: [PATCH] Export shared modules to the browser even when Node is in the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS --- server/lib/player-media-health.js | 7 +- server/lib/schedule-eval.js | 9 +- server/player/index.html | 31 +++- .../player-transition-hardware-plane.test.js | 142 ++++++++++++++++++ shared/Transitions/params.js | 10 +- shared/Transitions/renderer.js | 8 +- tizen/js/schedule-eval.js | 9 +- tizen/js/transitions.js | 9 +- 8 files changed, 212 insertions(+), 13 deletions(-) create mode 100644 server/test/player-transition-hardware-plane.test.js diff --git a/server/lib/player-media-health.js b/server/lib/player-media-health.js index 4322fd8..b0bc2d9 100644 --- a/server/lib/player-media-health.js +++ b/server/lib/player-media-health.js @@ -13,8 +13,11 @@ // // Dependency-free UMD: Node (require) + browser/Tizen (window.PlayerMediaHealth). (function (root, factory) { - if (typeof module === 'object' && module.exports) module.exports = factory(); - else root.PlayerMediaHealth = factory(); + // BOTH, not either/or — see schedule-eval.js. Node integration in a BrightSign widget made the + // browser branch unreachable, so the player ran without its media-health decision there. + var api = factory(); + if (typeof module === 'object' && module.exports) module.exports = api; + if (root) root.PlayerMediaHealth = api; })(typeof self !== 'undefined' ? self : this, function () { 'use strict'; diff --git a/server/lib/schedule-eval.js b/server/lib/schedule-eval.js index 79b76fa..01a8de2 100644 --- a/server/lib/schedule-eval.js +++ b/server/lib/schedule-eval.js @@ -22,8 +22,13 @@ // Dependency-free UMD: Node (require) + browser/Tizen (window.ScheduleEval). (function (root, factory) { - if (typeof module === 'object' && module.exports) module.exports = factory(); - else root.ScheduleEval = factory(); + // BOTH, not either/or: a BrightSign widget runs with Node integration, so `module` exists in + // page scope and an `else` left root.ScheduleEval undefined there. The player falls back to + // "always active" when it is missing — i.e. per-item DAYPARTING silently stopped applying on + // that platform, and scheduled content played outside its window with nothing in any log. + var api = factory(); + if (typeof module === 'object' && module.exports) module.exports = api; + if (root) root.ScheduleEval = api; })(typeof self !== 'undefined' ? self : this, function () { 'use strict'; diff --git a/server/player/index.html b/server/player/index.html index fb11b79..a695560 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -3152,12 +3152,36 @@ // the frame on screen now, as a texturable (CORS-clean) source: the live , or — so a wipe can // start FROM a playing clip — a snapshot canvas of the outgoing