mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
Re-render when a screen leaves a sync group or a video wall
Taking a display out of a sync group, or deleting the wall it belonged to, froze it on whatever was
playing. The clip looped forever and every later refresh took the "unchanged" branch, because the
element was attached, playing and un-errored — healthy by every check the player makes. Only a
reboot cleared it.
On the web player, reconcileAdvanceTimerForMode re-arms a solo timer for widgets and images but
skips video and YouTube, on the grounds that they "self-advance via their own end handlers". The
handler that is live at that moment, though, was built for the mode being left: a group-rendered
video was created with `loop = !!groupSync`, a wall-follower video with `isFollower` true, and both
are captured in the closure at render time. A looping element never fires `ended`, and a follower's
handler declines to advance — so nothing self-advances and nothing re-renders. It now re-renders
whenever the element on screen is still looping, rather than guessing which media types can look
after themselves.
Tizen had the same freeze by a different route. GroupSyncController.exit and WallController.exit
both call player.invalidate() for exactly this purpose, but invalidate only cleared the change
signature — and load() returns at the continuity check ("current item survives, just retarget the
index") before reaching any render, so the invalidate was a no-op. It now forces the next load to
re-render, which is what those call sites always intended. On Tizen this froze every item type, not
just video, because `single` skips the timer in all of the renderers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
This commit is contained in:
parent
e3b01a5c7f
commit
47bda040a2
|
|
@ -1606,7 +1606,21 @@
|
|||
if (!isPlaying) return;
|
||||
const item = playlist[currentIndex];
|
||||
if (!item || advanceTimer) return; // solo/leader that already has its timer armed -> nothing to do
|
||||
const needsSoloTimer = !!item.widget_id || (typeof item.mime_type === 'string' && item.mime_type.startsWith('image/'));
|
||||
// Video and YouTube were excluded here on the grounds that they "self-advance via their own
|
||||
// end handlers" — but the handler that is live right now was built for the mode we have just
|
||||
// LEFT. A group-rendered video was created with `loop = !!groupSync` and a wall-follower
|
||||
// video with `isFollower` true, and both are captured in the closure at render time. So on
|
||||
// leaving a sync group or a wall, that element loops forever and nothing re-renders: the
|
||||
// screen sits on one clip permanently, and later refreshes take the "unchanged" branch
|
||||
// because the <video> is attached, playing and un-errored, i.e. healthy.
|
||||
//
|
||||
// Re-render whatever is on screen instead of guessing which types can look after themselves.
|
||||
const mediaEl = document.querySelector('#playerContainer video');
|
||||
const staleLoop = !!(mediaEl && mediaEl.loop);
|
||||
const needsSoloTimer = !!item.widget_id
|
||||
|| (typeof item.mime_type === 'string' && item.mime_type.startsWith('image/'))
|
||||
|| staleLoop
|
||||
|| item.mime_type === 'video/youtube';
|
||||
if (needsSoloTimer) playCurrentItem(); // re-render buffered + re-arm the solo advance timer
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,10 +179,11 @@ PlaylistPlayer.prototype.load = function (assignments) {
|
|||
if (!items.length) { this.index = 0; this.startPlayback(); return; }
|
||||
|
||||
// Current item survives -> keep playing it, just retarget the index (no restart).
|
||||
if (curId) {
|
||||
if (curId && !this._forceRender) {
|
||||
var stay = this.indexOfIdentity(items, curId);
|
||||
if (stay >= 0 && this.hasContentOnScreen()) { this.index = stay; return; }
|
||||
}
|
||||
this._forceRender = false;
|
||||
|
||||
// Anchor gone: walk forward from the OLD position to the first item that still exists.
|
||||
var nextIdx = 0;
|
||||
|
|
@ -307,7 +308,16 @@ PlaylistPlayer.prototype.setTimezone = function (tz) { this.timezone = tz || nul
|
|||
// leaving wall mode (or a role flip) calls invalidate() so the next load re-renders
|
||||
// with the right semantics instead of being de-duped by the unchanged signature.
|
||||
PlaylistPlayer.prototype.setWallFollower = function (b) { this.wallFollower = !!b; };
|
||||
PlaylistPlayer.prototype.invalidate = function () { this.sig = ''; };
|
||||
PlaylistPlayer.prototype.invalidate = function () {
|
||||
this.sig = '';
|
||||
// Clearing the signature alone was not enough: load() returns at the continuity check ("current
|
||||
// item survives -> keep playing it, just retarget the index") BEFORE reaching any render, so the
|
||||
// invalidate had no effect and the item kept the semantics of the mode we had just left. Leaving
|
||||
// a sync group or a wall therefore froze the screen on one clip — rendered with `single`, so
|
||||
// looping with no timer — and every later refresh took the unchanged path because the element
|
||||
// was attached and playing, i.e. healthy. This flag makes the next load actually re-render.
|
||||
this._forceRender = true;
|
||||
};
|
||||
PlaylistPlayer.prototype.getIndex = function () { return this.index; };
|
||||
PlaylistPlayer.prototype.getItemCount = function () { return this.items.length; };
|
||||
PlaylistPlayer.prototype.isWallFollower = function () { return !!this.wallFollower; };
|
||||
|
|
|
|||
Loading…
Reference in a new issue