Add template animation editor

This commit is contained in:
2026-08-03 17:27:42 +01:00
parent e95928f31c
commit ed8d51580e
35 changed files with 3173 additions and 44 deletions
+78 -8
View File
@@ -6,6 +6,8 @@ function getCurrentViewport() {
};
}
var slideOutroTimers = [];
// Command websocket and player-state helpers.
// Send the current playback state to the command websocket.
function sendCommandState(currentSlide) {
@@ -65,6 +67,56 @@ function clearSlideTimer() {
window.clearTimeout(timer);
timer = null;
}
clearSlideOutroTimer();
}
// Cancel any pending outro triggers for the current slide.
function clearSlideOutroTimer() {
if (!Array.isArray(slideOutroTimers) || !slideOutroTimers.length) {
slideOutroTimers = [];
return;
}
slideOutroTimers.forEach(function (timerId) {
window.clearTimeout(timerId);
});
slideOutroTimers = [];
}
// Return the rendered slide root that is currently on screen.
function getCurrentSlideRoot() {
var shells = Array.prototype.slice.call(app ? app.querySelectorAll('.slide-shell') : []);
if (shells.length) {
return shells[shells.length - 1];
}
return app && app.firstElementChild ? app.firstElementChild : app;
}
// Schedule the outgoing slide animation for each region so it finishes before removal.
function scheduleSlideOutro(holdDelayMs) {
clearSlideOutroTimer();
var currentRoot = getCurrentSlideRoot();
if (!currentRoot || typeof getRegionAnimationPhaseTimings !== 'function' || typeof playRegionAnimation !== 'function') {
return;
}
var regionTimings = getRegionAnimationPhaseTimings(currentRoot, 'outro');
if (!regionTimings.length) {
return;
}
var slideDurationMs = Math.max(1, Math.round(Number(holdDelayMs || 0)));
slideOutroTimers = regionTimings.map(function (entry) {
var timingMs = Math.max(0, Math.round(Number(entry && entry.timingMs || 0)));
var triggerDelayMs = Math.max(0, slideDurationMs - timingMs);
return window.setTimeout(function () {
if (!entry || !entry.element || !entry.element.isConnected) {
return;
}
playRegionAnimation(entry.element, 'outro');
}, triggerDelayMs);
});
}
// Schedule the next slide transition.
@@ -72,10 +124,12 @@ function scheduleSlideAdvance(delayMs) {
clearSlideTimer();
var holdDelayMs = Math.max(1, Number(delayMs || 0));
slideExpiresAt = Date.now() + holdDelayMs;
scheduleSlideOutro(holdDelayMs);
timer = window.setTimeout(function () {
timer = null;
slideExpiresAt = null;
pausedRemainingMs = null;
clearSlideOutroTimer();
const activeSlides = getCurrentActiveSlides();
if (activeSlides.length < 2) {
refresh();
@@ -89,13 +143,10 @@ function scheduleSlideAdvance(delayMs) {
}, holdDelayMs);
}
// Account for fade only when it is enabled so the transition is centered on the slide boundary.
// Return the slide duration without shifting it for fade timing.
function getSlideHoldDelay(delayMs) {
var holdDelayMs = Math.max(1, Number(delayMs || 0));
if (!currentPlaylistFadeBetweenSlides) {
return holdDelayMs;
}
return Math.max(1, holdDelayMs - (slideFadeDurationMs / 2));
return holdDelayMs;
}
// Cancel any pending fade-transition cleanup.
@@ -114,11 +165,19 @@ function getPlayerRegionModules() {
return window.pulsePlayerRegionTypes.list();
}
function isThumbnailPreview() {
return Boolean(window.__pulseThumbnailPreview);
}
function runRegionLifecycle(root, lifecycleName) {
if (!root) {
return;
}
if (isThumbnailPreview() && lifecycleName === 'initRegion') {
return;
}
getPlayerRegionModules().forEach(function (entry) {
var module = entry && entry.definition ? entry.definition : null;
if (!module || typeof module[lifecycleName] !== 'function') {
@@ -208,8 +267,15 @@ function renderSlideMarkup(markup, shouldFade) {
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(app);
}
initializeRegionInstances(app);
if (!isThumbnailPreview()) {
initializeRegionInstances(app);
}
initializeRenderedVideoPlayback(app);
if (typeof playRegionAnimations === 'function') {
window.requestAnimationFrame(function () {
playRegionAnimations(app, 'intro');
});
}
return app.firstElementChild;
}
@@ -277,14 +343,18 @@ function renderSlideMarkup(markup, shouldFade) {
window.requestAnimationFrame(function () {
nextShell.style.opacity = '1';
previousShell.style.opacity = '0';
if (typeof playRegionAnimations === 'function') {
playRegionAnimations(nextShell, 'intro');
}
});
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(nextShell);
}
initializeRegionInstances(nextShell);
if (!isThumbnailPreview()) {
initializeRegionInstances(nextShell);
}
initializeRenderedVideoPlayback(nextShell, slideFadeDurationMs / 2);
slideTransitionTimer = window.setTimeout(function () {