mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
Two halves of the same problem. A screen has to keep playing when the link is gone, and it must not keep playing the wrong thing once the link is back. CACHING FOR OFFLINE, on the players that could not: - Tizen cached nothing but the playlist, so a panel came back from a reboot knowing exactly what to show and fetched every frame of it from a server that was not there. tizen/js/media-cache.js caches the media itself to wgt-private (the store Tizen documents as surviving reboots), resumable via Range and If-Range, with the transfer async so a stalled chunk cannot freeze the player. offline.cache moves from "absent" to a runtime claim: a build with no writable private storage still says nothing. - The web player's worker stored only what a single fetch() happened to complete, which on a marginal link is nothing at all — a 200MB asset never finishes in one go and every retry starts from zero. It now accumulates in resumable chunks, driven by the player's playlist rather than by playback, so the prefetch is not competing with the video that is currently on screen for the same scarce bandwidth. BrightSign inherits this. STILL UPDATING, which caching quietly breaks: PUT /api/content/:id/replace changes an asset's bytes under a stable id. Every cache keys on that id, so before this the new bytes could not reach a panel that already held the old ones — not until the next refresh, but never. Content now carries a revision, stamped onto each item at send time like widget revs, and every player keys its cache on it. The same send-time refresh fixes a second bug: a replace writes a new randomly-named file and unlinks the old one, so the filepath in a published snapshot pointed at a deleted file and web panels 404'd on the item until somebody republished the playlist. The route now also pushes to affected devices, which it never did. Bytes are kept only where they can be built upon: no validator means no safe resume, so the partial is discarded and the attempt backs off as the failure it is rather than re-fetching the same prefix forever. Server needed no new transfer support — res.sendFile already does Range, If-Range and 416. The Tizen cache and the service worker are both driven in Node against fakes, because neither can be exercised without hardware and "the chunks assemble correctly" is not something to discover from a panel showing a corrupt video. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
67 lines
3 KiB
HTML
67 lines
3 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
<title>ScreenTinker</title>
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
<body>
|
|
<!-- Setup: enter server URL -->
|
|
<div id="setup" class="screen">
|
|
<div class="card">
|
|
<h1>ScreenTinker</h1>
|
|
<p class="sub">Digital Signage Player</p>
|
|
<label for="serverUrl">Server URL</label>
|
|
<input id="serverUrl" type="url" value="https://screentinker.com" autocomplete="off"
|
|
autocorrect="off" autocapitalize="off" spellcheck="false">
|
|
<button id="connectBtn">Connect</button>
|
|
<p id="setupStatus" class="status"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pairing: show the code -->
|
|
<div id="pairing" class="screen hidden">
|
|
<div class="card">
|
|
<h1>ScreenTinker</h1>
|
|
<p class="sub">Pair this display</p>
|
|
<div id="pairCode" class="code">------</div>
|
|
<p class="hint">Enter this code in your ScreenTinker dashboard<br>(Devices → Pair a display)</p>
|
|
<p id="pairStatus" class="status">Waiting to be paired…</p>
|
|
<button id="resetBtn" class="ghost">Change server</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- #170: Tizen AVPlay video surface. HTML5 <video> lives on a hardware plane that ignores
|
|
CSS rotate, so portrait/flipped VIDEO blacks out; AVPlay's setDisplayRotation rotates the
|
|
hardware plane. Hidden until a portrait/flipped video plays; off-hardware (no webapis) it
|
|
stays unused. Sits above the (empty-during-AV) stage so the hole-punched plane is visible. -->
|
|
<object id="avPlayer" type="application/avplayer" style="position:fixed;inset:0;width:100%;height:100%;z-index:5;display:none"></object>
|
|
|
|
<!-- Playback stage -->
|
|
<div id="stage" class="screen stage hidden"></div>
|
|
|
|
<!-- #109: PiP overlay layer — a sibling ABOVE #stage that the playlist never
|
|
touches. app.js mirrors the orientation transform onto it. -->
|
|
<div id="pip"></div>
|
|
|
|
<!-- Tiny on-screen status (offline / errors), auto-hides -->
|
|
<div id="toast" class="toast hidden"></div>
|
|
|
|
<!-- #125: Samsung device-API bridges. $WEBAPIS / $B2BAPIS are resolved by the
|
|
Tizen platform at runtime; off-hardware (browser / URL Launcher) these 404
|
|
harmlessly and the surfaces are simply absent. -->
|
|
<script src="$WEBAPIS/webapis/webapis.js"></script>
|
|
<script src="$B2BAPIS/b2bapis/b2bapis.js"></script>
|
|
<script src="js/socket.io.min.js"></script>
|
|
<script src="js/schedule-eval.js"></script>
|
|
<script src="js/transitions.js"></script>
|
|
<script src="js/player.js"></script>
|
|
<script src="js/device-control.js"></script>
|
|
<script src="js/pip-overlay.js"></script>
|
|
<script src="js/media-cache.js"></script>
|
|
<script src="js/capabilities.js"></script>
|
|
<script src="js/app.js"></script>
|
|
</body>
|
|
</html>
|