Release v2.15.2
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 4m34s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 57s

This commit is contained in:
lzstealth committed 2026-10-15 01:50:39 +01:00
1 parent ddcc665f81
commit 7295f83f04
43 files changed
+2526 -104

No files matched your search

+16 -7
View File
@@ -206,19 +206,28 @@ window.addEventListener('keydown', handlePlayerKeydown);
// Move to the previous or next active slide.
function navigateSlides(offset) {
const manualSlides = getCurrentActiveSlides();
clearSlideTimer();
applyPendingPlaylistUpdate();
let manualSlides = getCurrentActiveSlides();
if (!manualSlides.length) {
return;
}
let currentIndex = manualSlides.findIndex(function (slide) {
return slide && lastRenderedSlide && slide.id === lastRenderedSlide.id;
});
let currentIndex = Number(index);
if (!Number.isInteger(currentIndex) || !manualSlides[currentIndex] || !lastRenderedSlide || manualSlides[currentIndex].id !== lastRenderedSlide.id) {
currentIndex = manualSlides.findIndex(function (slide) {
return slide && lastRenderedSlide && slide.id === lastRenderedSlide.id;
});
}
if (currentIndex < 0) {
currentIndex = Math.min(Math.max(index, 0), manualSlides.length - 1);
}
const nextIndex = (currentIndex + offset + manualSlides.length) % manualSlides.length;
clearSlideTimer();
applyPendingPlaylistUpdate();
let nextIndex = (currentIndex + offset + manualSlides.length) % manualSlides.length;
if (offset > 0 && currentIndex + offset >= manualSlides.length) {
playlistPassNumber += 1;
clearActiveSlidesCache();
manualSlides = getCurrentActiveSlides();
nextIndex = 0;
}
renderSlideAtIndex(manualSlides, nextIndex);
}
@@ -85,6 +85,7 @@ function applyPendingPlaylistUpdate() {
var nextIndex = Number(index || 0);
slides = pendingPlaylistUpdate.slides;
currentPlaylistSignature = pendingPlaylistUpdate.signature;
playlistPassNumber = 0;
currentPlaylistFadeBetweenSlides = pendingPlaylistUpdate.fadeBetweenSlides;
currentPlaylistSkipUnavailableRtmp = Boolean(pendingPlaylistUpdate.skipUnavailableRtmp);
pendingPlaylistUpdate = null;
+79 -2
View File
@@ -21,9 +21,85 @@ function getWebpageUrls(sourceSlides) {
// Filter the slides down to those that are active right now.
function getActiveSlidesFrom(sourceSlides) {
const now = new Date();
return (Array.isArray(sourceSlides) ? sourceSlides : []).filter(function (slide) {
const eligibleSlides = (Array.isArray(sourceSlides) ? sourceSlides : []).filter(function (slide) {
return isSlideActive(slide, now);
});
const groups = new Map();
eligibleSlides.forEach(function (slide) {
const groupKey = String(slide && slide.groupKey || '');
if (!groupKey) {
return;
}
if (!groups.has(groupKey)) {
groups.set(groupKey, []);
}
groups.get(groupKey).push(slide);
});
const selectedGroups = new Map();
groups.forEach(function (members, groupKey) {
if (members[0].groupMode === 'repeat') {
const repeatCount = Math.min(100, Math.max(1, Math.floor(Number(members[0].groupRepeatCount) || 1)));
const repeated = [];
for (let repetition = 0; repetition < repeatCount; repetition += 1) {
repeated.push.apply(repeated, members);
}
selectedGroups.set(groupKey, repeated);
return;
}
const pickCount = Math.min(members.length, Math.max(1, Math.floor(Number(members[0].groupPickCount) || 1)));
const passNumber = Number(playlistPassNumber) || 0;
if (members[0].groupShuffle) {
selectedGroups.set(groupKey, members.slice().sort(function (left, right) {
return getGroupSlideSelectionScore(groupKey, left.id, passNumber) - getGroupSlideSelectionScore(groupKey, right.id, passNumber);
}).slice(0, pickCount));
} else {
selectedGroups.set(groupKey, Array.from({ length: pickCount }, function (_value, offset) {
return members[(passNumber * pickCount + offset) % members.length];
}));
}
});
const result = [];
eligibleSlides.forEach(function (slide) {
if (!slide.groupKey) {
result.push(slide);
} else if (selectedGroups.has(slide.groupKey)) {
result.push.apply(result, selectedGroups.get(slide.groupKey));
selectedGroups.delete(slide.groupKey);
}
});
return result;
}
function getGroupSlideSelectionScore(groupKey, slideId, cycleNumber) {
const value = String(groupKey || '') + '|' + String(cycleNumber || 0) + '|' + String(slideId || '');
let hash = 2166136261;
for (let index = 0; index < value.length; index += 1) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return hash >>> 0;
}
function hasSelectableGroupVariants() {
const now = new Date();
const eligibleGroups = new Map();
(Array.isArray(slides) ? slides : []).forEach(function (slide) {
const groupKey = String(slide && slide.groupKey || '');
if (!groupKey || !isSlideActive(slide, now)) {
return;
}
if (!eligibleGroups.has(groupKey)) {
eligibleGroups.set(groupKey, []);
}
eligibleGroups.get(groupKey).push(slide);
});
return Array.from(eligibleGroups.values()).some(function (members) {
if (members[0].groupMode === 'repeat') return false;
return members.length > Math.max(1, Number(members[0] && members[0].groupPickCount) || 1)
|| (members.length > 1 && Boolean(members[0].groupShuffle));
});
}
// Build a cache key for the active slide set.
@@ -36,7 +112,8 @@ function getActiveSlidesCacheKey() {
now.getDate(),
now.getHours(),
now.getMinutes(),
now.getSeconds()
now.getSeconds(),
playlistPassNumber
].join('|');
}
+11 -5
View File
@@ -193,12 +193,18 @@ function matchesScheduleRule(rule, now) {
// Determine whether a slide should be shown at the current time.
function isSlideActive(slide, now) {
const rules = Array.isArray(slide.scheduleRules) ? slide.scheduleRules : [];
if (!rules.length) {
return true;
}
return rules.some(function (rule) {
if (rules.length && !rules.some(function (rule) {
return matchesScheduleRule(rule, now);
});
})) {
return false;
}
const groupRules = parseScheduleRules(slide.groupScheduleRules || slide.group_schedule_rules_json);
if (slide.groupKey && groupRules.length && !groupRules.some(function (rule) {
return matchesScheduleRule(rule, now);
})) {
return false;
}
return true;
}
// Build the cache key for a template layout.
function getTemplateLayoutCacheKey(template) {
+11 -3
View File
@@ -122,15 +122,23 @@ function scheduleSlideAdvance(delayMs) {
pausedRemainingMs = null;
clearSlideOutroTimer();
applyPendingPlaylistUpdate();
const activeSlides = getCurrentActiveSlides();
if (activeSlides.length < 2) {
let activeSlides = getCurrentActiveSlides();
const hasGroupVariants = typeof hasSelectableGroupVariants === 'function' && hasSelectableGroupVariants();
if (activeSlides.length < 2 && !hasGroupVariants) {
showCurrent();
return;
}
if (index >= activeSlides.length) {
index = 0;
}
index = (index + 1) % activeSlides.length;
if (index + 1 >= activeSlides.length) {
playlistPassNumber += 1;
clearActiveSlidesCache();
activeSlides = getCurrentActiveSlides();
index = 0;
} else {
index += 1;
}
showCurrent();
}, holdDelayMs);
}