From 4491c15215e19020412bc828367b10eb8a0e86ac Mon Sep 17 00:00:00 2001 From: Mark Rapson Date: Mon, 10 Aug 2026 00:32:28 +0100 Subject: [PATCH] Release 2.6.24 --- CHANGELOG.md | 6 + build/package.player.json | 2 +- build/package.web.json | 2 +- package.json | 2 +- src/player/public/js/player-page-playback.js | 7 +- test/player-page-playback.test.js | 116 +++++++++++++++++++ 6 files changed, 131 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6037c..613bcd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## 2.6.24 - 2026-08-10 + +### Fixed + +- Deferred playlist updates now keep the current slide index when the next playlist snapshot is applied, so playback no longer jumps back to the first slide mid-cycle. + ## 2.6.23 - 2026-08-09 ### Fixed diff --git a/build/package.player.json b/build/package.player.json index d685ba3..abd59ca 100644 --- a/build/package.player.json +++ b/build/package.player.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-player", - "version": "2.6.23", + "version": "2.6.24", "private": false, "description": "Pulse Signage player application bundle", "main": "src/common.js", diff --git a/build/package.web.json b/build/package.web.json index 96542ed..f6ab8b9 100644 --- a/build/package.web.json +++ b/build/package.web.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-web", - "version": "2.6.23", + "version": "2.6.24", "private": false, "description": "Pulse Signage web and bridge application bundle", "main": "src/common.js", diff --git a/package.json b/package.json index 38406de..63de599 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "2.6.23", + "version": "2.6.24", "private": false, "description": "Pulse Signage application with MySQL and media storage", "repository": { diff --git a/src/player/public/js/player-page-playback.js b/src/player/public/js/player-page-playback.js index 9907067..129f769 100644 --- a/src/player/public/js/player-page-playback.js +++ b/src/player/public/js/player-page-playback.js @@ -70,6 +70,7 @@ function applyPendingPlaylistUpdate() { if (!pendingPlaylistUpdate) { return false; } + var nextIndex = Number(index || 0); slides = pendingPlaylistUpdate.slides; currentPlaylistSignature = pendingPlaylistUpdate.signature; currentPlaylistFadeBetweenSlides = pendingPlaylistUpdate.fadeBetweenSlides; @@ -80,7 +81,11 @@ function applyPendingPlaylistUpdate() { templateLayoutCache = Object.create(null); templateRenderPlanCache = Object.create(null); renderCacheViewportKey = window.innerWidth + 'x' + window.innerHeight; - index = 0; + const activeSlides = getCurrentActiveSlides(); + if (!Number.isFinite(nextIndex) || nextIndex < 0) { + nextIndex = 0; + } + index = activeSlides.length ? Math.min(nextIndex, activeSlides.length - 1) : 0; logDebug('Applied updated playlist on slide transition.'); return true; } diff --git a/test/player-page-playback.test.js b/test/player-page-playback.test.js index 0c09fb0..713b5c7 100644 --- a/test/player-page-playback.test.js +++ b/test/player-page-playback.test.js @@ -279,6 +279,122 @@ test('single-slide playlists re-render the active slide instead of refreshing af assert.deepEqual(sandbox.slides, [{ id: 2, duration_seconds: 12 }]); }); +test('deferred playlist updates preserve the current slide index', async () => { + const sandbox = { + window: null, + location: { origin: 'http://localhost', href: 'http://localhost/screen/test2' }, + Date, + JSON, + Math, + Number, + String, + Boolean, + Array, + Object, + Promise, + setTimeout, + clearTimeout, + console, + currentPlaylistSignature: 'old-signature', + currentPlaylistFadeBetweenSlides: false, + currentPlaylistSkipUnavailableRtmp: false, + slug: 'test2', + pendingPlaylistUpdate: null, + slides: [ + { id: 1, duration_seconds: 12 }, + { id: 2, duration_seconds: 12 }, + { id: 3, duration_seconds: 12 } + ], + lastRenderedSlide: { id: 2, duration_seconds: 12 }, + index: 1, + timer: null, + slideExpiresAt: null, + pausedRemainingMs: null, + app: null, + currentPlaylistEtag: '', + activeSlidesCacheKey: '', + activeSlidesCacheValue: [], + renderCacheViewportKey: '', + slideMarkupCache: Object.create(null), + templateLayoutCache: Object.create(null), + templateRenderPlanCache: Object.create(null), + getCurrentActiveSlides() { + return sandbox.slides; + }, + getPlaylistRevision(data) { + return data.signature; + }, + normalizeSlide(slide) { + return slide; + }, + getActiveSlidesFrom(slideList) { + return slideList; + }, + savePlaylistSnapshot() {}, + markRefreshHealthy() {}, + setOfflineBannerVisible() {}, + scheduleRefreshRetry() {}, + syncWebpagePreloads() {}, + syncRtmpWarmups() {}, + clearActiveSlidesCache() {}, + showCurrent() {}, + sendCommandState() {}, + logDebug() {} + }; + sandbox.window = sandbox; + + const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playback.js'); + const script = fs.readFileSync(scriptPath, 'utf8'); + const prelude = ` + var pendingPlaylistUpdate = null; + var slides = [ + { id: 1, duration_seconds: 12 }, + { id: 2, duration_seconds: 12 }, + { id: 3, duration_seconds: 12 } + ]; + var currentPlaylistSignature = 'old-signature'; + var currentPlaylistFadeBetweenSlides = false; + var currentPlaylistSkipUnavailableRtmp = false; + var currentPlaylistEtag = ''; + var lastRenderedSlide = { id: 2, duration_seconds: 12 }; + var index = 1; + var timer = null; + var slideExpiresAt = null; + var pausedRemainingMs = null; + var app = null; + var activeSlidesCacheKey = ''; + var activeSlidesCacheValue = []; + var renderCacheViewportKey = ''; + var slideMarkupCache = Object.create(null); + var templateLayoutCache = Object.create(null); + var templateRenderPlanCache = Object.create(null); + `; + vm.runInNewContext(prelude + '\n' + script, sandbox, { filename: scriptPath }); + + sandbox.pendingPlaylistUpdate = { + slides: [ + { id: 10, duration_seconds: 12 }, + { id: 11, duration_seconds: 12 }, + { id: 12, duration_seconds: 12 } + ], + signature: 'next-signature', + fadeBetweenSlides: false, + skipUnavailableRtmp: false + }; + + const applied = sandbox.applyPendingPlaylistUpdate(); + + assert.equal(applied, true); + assert.equal(sandbox.currentPlaylistSignature, 'next-signature'); + assert.equal(sandbox.pendingPlaylistUpdate, null); + assert.deepEqual(sandbox.slides, [ + { id: 10, duration_seconds: 12 }, + { id: 11, duration_seconds: 12 }, + { id: 12, duration_seconds: 12 } + ]); + assert.equal(sandbox.index, 1); +}); + test('removing the currently visible slide from a two-slide playlist applies the one-slide update immediately', async () => { const calls = { showCurrent: 0,