Release v2.14.1
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
lzstealth committed 2026-10-11 18:54:43 +01:00
1 parent 635e1a9942
commit c3299abaac
174 files changed
+4483 -1687

No files matched your search

+67 -8
View File
@@ -1,5 +1,15 @@
// Slide media startup and teardown helpers.
var renderedVideoPlaybackCleanup = new WeakMap();
function cancelRenderedVideoPlayback(video) {
var cleanup = renderedVideoPlaybackCleanup.get(video);
if (cleanup) {
cleanup();
renderedVideoPlaybackCleanup.delete(video);
}
}
function initializeRenderedVideoPlayback(root, delayMs) {
if (!root) {
return;
@@ -12,14 +22,37 @@ function initializeRenderedVideoPlayback(root, delayMs) {
return;
}
cancelRenderedVideoPlayback(video);
var playbackScheduled = false;
var playbackCancelled = false;
var playbackTimer = null;
function removeReadinessListeners() {
video.removeEventListener('canplay', schedulePlaybackStart);
video.removeEventListener('loadedmetadata', onLoadedMetadata);
}
// A cancelled callback can already be queued for dispatch, so it also checks this flag.
renderedVideoPlaybackCleanup.set(video, function () {
playbackCancelled = true;
if (playbackTimer !== null) {
window.clearTimeout(playbackTimer);
playbackTimer = null;
}
removeReadinessListeners();
});
// Script-controlled startup aligns playback with the incoming slide's fade timing.
video.autoplay = false;
video.loop = !(video.dataset && video.dataset.loop === '0');
video.muted = !(video.dataset && video.dataset.disableAudio === '0');
video.playsInline = true;
function startPlayback() {
playbackTimer = null;
if (playbackCancelled || video.isConnected === false || root.isConnected === false) {
return;
}
var playPromise = video.play && video.play();
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(function () {
@@ -29,28 +62,33 @@ function initializeRenderedVideoPlayback(root, delayMs) {
}
function schedulePlaybackStart() {
if (playbackScheduled) {
// Metadata and canplay can both fire; schedule only one start for this video.
if (playbackCancelled || playbackScheduled) {
return;
}
playbackScheduled = true;
removeReadinessListeners();
if (startDelayMs > 0) {
window.setTimeout(startPlayback, startDelayMs);
playbackTimer = window.setTimeout(startPlayback, startDelayMs);
return;
}
startPlayback();
}
function onLoadedMetadata() {
if (video.readyState >= 2) {
schedulePlaybackStart();
}
}
// HAVE_CURRENT_DATA means a frame is available, not just the video's metadata.
if (video.readyState >= 2) {
schedulePlaybackStart();
return;
}
video.addEventListener('canplay', schedulePlaybackStart, { once: true });
video.addEventListener('loadedmetadata', function () {
if (video.readyState >= 2) {
schedulePlaybackStart();
}
}, { once: true });
video.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
});
}
@@ -87,7 +125,28 @@ function initializeSlideMedia(root, delayMs) {
initializeRenderedVideoPlayback(root, delayMs);
}
function destroySlideMedia(root) {
function destroySlideMedia(root, options) {
if (!root) {
return;
}
var preserveVideoFrame = Boolean(options && options.preserveVideoFrame);
var videos = root.querySelectorAll('.template-region.video video, .slide-media video');
Array.prototype.forEach.call(videos, function (video) {
cancelRenderedVideoPlayback(video);
if (typeof video.pause === 'function') {
video.pause();
}
// Keep the paused frame through a crossfade; release decoder/network resources at removal.
if (!preserveVideoFrame) {
video.removeAttribute('src');
if (typeof video.load === 'function') {
video.load();
}
}
});
// RTMP players hold independent HLS resources beyond the lifetime of their DOM nodes.
if (typeof destroyRtmpRegions === 'function') {
destroyRtmpRegions(root);
}