playlists
This commit is contained in:
parent
8df2aff0ee
commit
68d2f59846
@ -6,6 +6,7 @@ from flask import Flask, render_template, redirect, request, url_for, send_from_
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.interface.ObController import ObController
|
||||
from src.utils import get_ip_address, get_safe_cron_descriptor
|
||||
from src.model.enum.AnimationSpeed import animation_speed_duration
|
||||
|
||||
|
||||
class PlayerController(ObController):
|
||||
@ -54,7 +55,8 @@ class PlayerController(ObController):
|
||||
slide_animation_enabled=self._model_store.variable().get_one_by_name('slide_animation_enabled'),
|
||||
slide_animation_entrance_effect=self._model_store.variable().get_one_by_name('slide_animation_entrance_effect'),
|
||||
slide_animation_exit_effect=self._model_store.variable().get_one_by_name('slide_animation_exit_effect'),
|
||||
slide_animation_speed=self._model_store.variable().get_one_by_name('slide_animation_speed')
|
||||
slide_animation_speed=self._model_store.variable().get_one_by_name('slide_animation_speed'),
|
||||
animation_speed_duration=animation_speed_duration
|
||||
)
|
||||
|
||||
def player_default(self):
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
animation_speed_duration = {
|
||||
'slower': 3000,
|
||||
'slow': 2000,
|
||||
'normal': 1000,
|
||||
'fast': 800,
|
||||
'faster': 500,
|
||||
}
|
||||
|
||||
|
||||
class AnimationSpeed(Enum):
|
||||
|
||||
|
||||
@ -18,23 +18,69 @@
|
||||
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/lib/is-cron-now.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="FirstSlide" class="slide" style="z-index: 1000;">
|
||||
<div id="IntroSlide" class="slide" style="z-index: 10000;">
|
||||
{% if default_slide_duration.eval() > 0 %}
|
||||
<iframe src="/player/default"></iframe>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="SecondSlide" class="slide" style="z-index: 500;">
|
||||
{% if default_slide_duration.eval() > 0 %}
|
||||
<iframe src="/player/default"></iframe>
|
||||
{% endif %}
|
||||
<div id="CronSlide" class="slide" style="z-index: 0;">
|
||||
|
||||
</div>
|
||||
<div id="FirstSlide" class="slide slide-loop" style="z-index: 500;">
|
||||
|
||||
</div>
|
||||
<div id="SecondSlide" class="slide slide-loop" style="z-index: 1000;">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// Backend config
|
||||
var items = {{items | safe}};
|
||||
var duration = {{ default_slide_duration.eval() * 1000 }};
|
||||
var playlistCheck = {{ polling_interval.eval() * 1000 }};
|
||||
var curItemIndex = 0;
|
||||
var introDuration = {{ default_slide_duration.eval() * 1000 }};
|
||||
var playlistCheckResolutionMs = {{ polling_interval.eval() * 1000 }};
|
||||
|
||||
// Backend flag updates
|
||||
var needHardRefresh = null;
|
||||
|
||||
// Frontend config
|
||||
var syncedWithTime = false;
|
||||
var tickRefreshResolutionMs = 250;
|
||||
|
||||
// Frontend flag updates
|
||||
var hasMoveOnce = false;
|
||||
|
||||
// Player states infos
|
||||
var PLAY_STATE_PLAYING = 0, PLAY_STATE_PAUSE = 1;
|
||||
var playState = PLAY_STATE_PLAYING;
|
||||
var isPlaying = function() {return playState === PLAY_STATE_PLAYING;};
|
||||
var isPaused = function() {return playState === PLAY_STATE_PAUSE;};
|
||||
var pauseClockValue = null;
|
||||
|
||||
// Animations
|
||||
var animate = {{ 'true' if slide_animation_enabled.eval() else 'false' }};
|
||||
var animate_speed = "animate__{{ slide_animation_speed.eval()|default("normal") }}";
|
||||
var animation_speed_duration = {{ animation_speed_duration[slide_animation_speed.eval()] if slide_animation_enabled.eval() else 0 }};
|
||||
var animate_transitions = [
|
||||
"animate__{{ slide_animation_entrance_effect.eval()|default("fadeIn") }}",
|
||||
"animate__{{ slide_animation_exit_effect.eval()|default("none") }}"
|
||||
];
|
||||
|
||||
// Slide flow management
|
||||
var SLIDE_TOP_Z = '1000';
|
||||
var SLIDE_BOTTOM_Z = '500';
|
||||
var clockValue = 0;
|
||||
var curItemIndex = -1;
|
||||
var secondsBeforeNext = 0;
|
||||
var nextReady = true;
|
||||
var introSlide = document.getElementById('IntroSlide');
|
||||
var cronSlide = document.getElementById('CronSlide');
|
||||
var firstSlide = document.getElementById('FirstSlide');
|
||||
var secondSlide = document.getElementById('SecondSlide');
|
||||
var curSlide = secondSlide;
|
||||
var nextSlide = firstSlide;
|
||||
var cronItemIndex = null;
|
||||
|
||||
// Functions
|
||||
var itemCheck = setInterval(function () {
|
||||
fetch('player/playlist').then(function(response) {
|
||||
if (response.ok) {
|
||||
@ -51,101 +97,181 @@
|
||||
}).catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
}, playlistCheck);
|
||||
var animate = {{ 'true' if slide_animation_enabled.eval() else 'false' }};
|
||||
var animate_transitions = [
|
||||
"animate__{{ slide_animation_entrance_effect.eval()|default("fadeIn") }}",
|
||||
"animate__{{ slide_animation_exit_effect.eval()|default("none") }}"
|
||||
];
|
||||
var animate_speed = "animate__{{ slide_animation_speed.eval()|default("normal") }}";
|
||||
var firstSlide = document.getElementById('FirstSlide');
|
||||
var secondSlide = document.getElementById('SecondSlide');
|
||||
var previousSlide = secondSlide;
|
||||
var curSlide = firstSlide;
|
||||
var cronState = {
|
||||
active: false,
|
||||
itemIndex: null,
|
||||
interval: null
|
||||
}, playlistCheckResolutionMs);
|
||||
|
||||
var getLoopDuration = function() {
|
||||
let totalDuration = 0;
|
||||
for (var i = 0; i < items.loop.length; i++) {
|
||||
var item = items.loop[i];
|
||||
totalDuration += safe_duration(item);
|
||||
}
|
||||
return totalDuration;
|
||||
};
|
||||
|
||||
var cronTick = function() {
|
||||
|
||||
|
||||
|
||||
if ((new Date()).getSeconds() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.cron.length; i++) {
|
||||
var item = items.cron[i];
|
||||
|
||||
if (cron.isActive(item.cron_schedule) && cronState.itemIndex != i) {
|
||||
cronState.active = true;
|
||||
cronState.itemIndex = i;
|
||||
var callbackReady = function (onSlideStart) {
|
||||
onSlideStart();
|
||||
moveToSlide(curSlide.attributes['id'].value, item);
|
||||
var move = function () {
|
||||
if (nextReady) {
|
||||
curItemIndex = (curItemIndex + 1) === items.loop.length ? 0 : curItemIndex + 1;
|
||||
cronState.active = false;
|
||||
cronState.itemIndex = null;
|
||||
} else {
|
||||
setTimeout(move, 1000);
|
||||
}
|
||||
}
|
||||
setTimeout(move, item.duration * 1000);
|
||||
};
|
||||
loadContent(curSlide, callbackReady, item);
|
||||
}
|
||||
}
|
||||
var resume = function() {
|
||||
playState = PLAY_STATE_PLAYING;
|
||||
};
|
||||
|
||||
function main() {
|
||||
preloadSlide('SecondSlide', items.loop[curItemIndex])
|
||||
cronState.interval = setInterval(cronTick, 1000);
|
||||
var play = function() {
|
||||
resume();
|
||||
};
|
||||
|
||||
var pause = function() {
|
||||
pauseClockValue = clockValue;
|
||||
playState = PLAY_STATE_PAUSE;
|
||||
};
|
||||
|
||||
var stop = function() {
|
||||
pause();
|
||||
};
|
||||
|
||||
var seek = function(timeInSeconds) {
|
||||
if (syncedWithTime) {
|
||||
return console.warn('You can\'t call seek with synced playlists');
|
||||
}
|
||||
|
||||
var maxDuration = getLoopDuration();
|
||||
|
||||
if (timeInSeconds > maxDuration) {
|
||||
timeInSeconds = maxDuration;
|
||||
console.warn('Max duration is ' + maxDuration + ' seconds');
|
||||
}
|
||||
|
||||
if (timeInSeconds < 0) {
|
||||
timeInSeconds = 0;
|
||||
}
|
||||
|
||||
clockValue = timeInSeconds * 1000;
|
||||
};
|
||||
|
||||
var lookupPreviousItem = function() {
|
||||
return (curItemIndex - 1 < 0) ? items.loop[items.loop.length - 1] : items.loop[curItemIndex - 1];
|
||||
};
|
||||
|
||||
var lookupNextItem = function() {
|
||||
return (curItemIndex + 1 >= items.loop.length) ? items.loop[0] : items.loop[curItemIndex + 1];
|
||||
};
|
||||
|
||||
var lookupCurrentItem = function() {
|
||||
return items.loop[curItemIndex];
|
||||
}
|
||||
|
||||
function preloadSlide(slide, item) {
|
||||
var getEmptySlide = function() {
|
||||
return Array.from(document.getElementsByClassName('slide-loop')).filter(slide => slide.innerHTML.replaceAll(/\s/g,'') === '')[0];
|
||||
};
|
||||
|
||||
var refreshSlidesOrder = function() {
|
||||
curSlide = Array.from(document.getElementsByClassName('slide')).filter(function(slide) {
|
||||
return getComputedStyle(slide).zIndex === SLIDE_TOP_Z;
|
||||
})[0];
|
||||
nextSlide = Array.from(document.getElementsByClassName('slide')).filter(function(slide) {
|
||||
return getComputedStyle(slide).zIndex === SLIDE_BOTTOM_Z;
|
||||
})[0];
|
||||
};
|
||||
|
||||
var safe_duration = function(item) {
|
||||
if (!item) {
|
||||
return tickRefreshResolutionMs/1000;
|
||||
}
|
||||
return item.duration + Math.ceil(animation_speed_duration/1000);
|
||||
};
|
||||
|
||||
var main = function() {
|
||||
setTimeout(function() {
|
||||
introSlide.remove();
|
||||
setInterval(checkAndMoveSlide, tickRefreshResolutionMs);
|
||||
setInterval(cronTick, 1000);
|
||||
}, introDuration);
|
||||
};
|
||||
|
||||
var preloadSlide = function(slide, item) {
|
||||
console.log('Preload', slide, item.name)
|
||||
var element = document.getElementById(slide);
|
||||
var callbackReady = function (onSlideStart) {
|
||||
var move = function () {
|
||||
if (nextReady && !cronState.active) {
|
||||
moveToSlide(slide, item);
|
||||
onSlideStart();
|
||||
} else {
|
||||
setTimeout(move, 1000);
|
||||
}
|
||||
var callbackReady = function(onSlideStart) {
|
||||
onSlideStart();
|
||||
};
|
||||
loadContent(element, callbackReady, item);
|
||||
};
|
||||
|
||||
var tickClockValue = function() {
|
||||
if (isPaused()) {
|
||||
return pauseClockValue;
|
||||
}
|
||||
if (syncedWithTime) {
|
||||
clockValue = Date.now();
|
||||
} else {
|
||||
clockValue += tickRefreshResolutionMs;
|
||||
}
|
||||
};
|
||||
|
||||
function checkAndMoveSlide() {
|
||||
tickClockValue();
|
||||
var timeInCurrentLoop = (clockValue/1000) % getLoopDuration();
|
||||
var accumulatedTime = 0;
|
||||
|
||||
for (var i = 0; i < items.loop.length; i++) {
|
||||
var item = items.loop[i];
|
||||
|
||||
if (i === curItemIndex) {
|
||||
secondsBeforeNext = accumulatedTime + safe_duration(item) - timeInCurrentLoop;
|
||||
console.log("remaining:", secondsBeforeNext, "clock:",clockValue, curItemIndex);
|
||||
}
|
||||
|
||||
setTimeout(move, duration);
|
||||
};
|
||||
|
||||
loadContent(element, callbackReady, item);
|
||||
if (timeInCurrentLoop < accumulatedTime + safe_duration(item)) {
|
||||
if (curItemIndex !== i) {
|
||||
console.log('change to ', i , item.name)
|
||||
curItemIndex = i;
|
||||
var emptySlide = getEmptySlide();
|
||||
if (emptySlide && !hasMoveOnce) {
|
||||
preloadSlide(emptySlide.attributes['id'].value, item);
|
||||
hasMoveOnce = true;
|
||||
}
|
||||
moveToNextSlide();
|
||||
}
|
||||
break;
|
||||
}
|
||||
accumulatedTime += safe_duration(item);
|
||||
}
|
||||
}
|
||||
|
||||
function moveToSlide(slide, item) {
|
||||
curSlide = document.getElementById(slide);
|
||||
previousSlide = curSlide == firstSlide ? secondSlide : firstSlide;
|
||||
function moveToNextSlide() {
|
||||
refreshSlidesOrder();
|
||||
nextSlide.style.zIndex = SLIDE_TOP_Z; // first
|
||||
curSlide.style.zIndex = SLIDE_BOTTOM_Z; // second
|
||||
|
||||
duration = item.duration * 1000;
|
||||
curItemIndex = (curItemIndex + 1) === items.loop.length ? 0 : curItemIndex + 1;
|
||||
|
||||
curSlide.style.zIndex = 1000;
|
||||
previousSlide.style.zIndex = 500;
|
||||
console.log(curSlide.attributes['id'].value, "to", nextSlide.attributes['id'].value)
|
||||
|
||||
if (animate) {
|
||||
curSlide.classList.add('animate__animated', animate_transitions[0], animate_speed);
|
||||
curSlide.onanimationend = () => {
|
||||
curSlide.classList.remove(animate_transitions[0], animate_speed);
|
||||
preloadSlide(previousSlide.attributes['id'].value, items.loop[curItemIndex]);
|
||||
nextSlide.classList.add('animate__animated', animate_transitions[0], animate_speed);
|
||||
var loadingNextSlide = function () {
|
||||
if (isPaused()) {
|
||||
return setTimeout(loadingNextSlide, 500);
|
||||
}
|
||||
if (secondsBeforeNext * 1000 > 1000) {
|
||||
return setTimeout(loadingNextSlide, 500);
|
||||
}
|
||||
console.log('pursuing... loading')
|
||||
nextSlide.classList.remove(animate_transitions[0], animate_speed);
|
||||
preloadSlide(curSlide.attributes['id'].value, lookupNextItem());
|
||||
refreshSlidesOrder();
|
||||
};
|
||||
previousSlide.classList.add('animate__animated', animate_transitions[1], animate_speed);
|
||||
previousSlide.onanimationend = () => {
|
||||
previousSlide.classList.remove(animate_transitions[1], animate_speed);
|
||||
nextSlide.onanimationend = function() { loadingNextSlide(); };
|
||||
|
||||
curSlide.classList.add('animate__animated', animate_transitions[1], animate_speed);
|
||||
var unloadingNextSlide = function () {
|
||||
if (isPaused()) {
|
||||
return setTimeout(unloadingNextSlide, 500);
|
||||
}
|
||||
if (secondsBeforeNext * 1000 > 1000) {
|
||||
return setTimeout(unloadingNextSlide, 500);
|
||||
}
|
||||
console.log('pursuing... unloading')
|
||||
curSlide.classList.remove(animate_transitions[1], animate_speed);
|
||||
};
|
||||
curSlide.onanimationend = function() { unloadingNextSlide(); };
|
||||
} else {
|
||||
preloadSlide(previousSlide.attributes['id'].value, items.loop[curItemIndex]);
|
||||
preloadSlide(curSlide.attributes['id'].value, lookupNextItem());
|
||||
refreshSlidesOrder();
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,12 +306,27 @@
|
||||
}
|
||||
|
||||
function loadYoutube(element, callbackReady, item) {
|
||||
element.innerHTML = ``;
|
||||
element.innerHTML = `youtube`;
|
||||
callbackReady(function () {});
|
||||
|
||||
setTimeout(function() {
|
||||
element.innerHTML = `<iframe src="https://www.youtube.com/embed/${item.location}?version=3&autoplay=1&showinfo=0&controls=0&modestbranding=1&fs=1&rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>`;
|
||||
}, Math.max(100, duration - 2000));
|
||||
var loadingDelayMs = 1000;
|
||||
var delayNoisyContentJIT = Math.max(100, (lookupCurrentItem().duration * 1000) - loadingDelayMs);
|
||||
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
|
||||
|
||||
var autoplayLoader = function() {
|
||||
if (isPaused()) {
|
||||
return setTimeout(autoplayLoader, 500);
|
||||
}
|
||||
|
||||
if (secondsBeforeNext * 1000 > loadingDelayMs) {
|
||||
return setTimeout(autoplayLoader, 500);
|
||||
}
|
||||
|
||||
if (element.innerHTML === 'youtube') {
|
||||
element.innerHTML = `<iframe src="https://www.youtube.com/embed/${item.location}?version=3&autoplay=1&showinfo=0&controls=0&modestbranding=1&fs=1&rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>`;
|
||||
}
|
||||
}
|
||||
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
||||
}
|
||||
|
||||
function loadVideo(element, callbackReady, item) {
|
||||
@ -208,6 +349,54 @@
|
||||
callbackReady(onSlideStart);
|
||||
}
|
||||
|
||||
var cronTick = function() {
|
||||
if ((new Date()).getSeconds() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.cron.length; i++) {
|
||||
var item = items.cron[i];
|
||||
|
||||
if (cron.isActive(item.cron_schedule) && cronItemIndex !== i) {
|
||||
setTimeout(function() {
|
||||
moveToCronSlide(i);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function moveToCronSlide(cronSlideIndex) {
|
||||
var item = items.cron[cronSlideIndex];
|
||||
var savedSlide = curSlide.innerHTML;
|
||||
cronItemIndex = cronSlideIndex;
|
||||
pause();
|
||||
var callbackReady = function(onSlideStart) {
|
||||
onSlideStart();
|
||||
setTimeout(function() {
|
||||
console.log('STOOOP')
|
||||
cronItemIndex = null;
|
||||
{#curSlide.innerHTML = savedSlide;#}
|
||||
|
||||
refreshSlidesOrder()
|
||||
SLIDE_TOP_Z = (parseInt(SLIDE_TOP_Z) * 5).toString();
|
||||
SLIDE_BOTTOM_Z = (parseInt(SLIDE_BOTTOM_Z) * 5).toString();
|
||||
curSlide.style.zIndex = SLIDE_BOTTOM_Z;
|
||||
cronSlide.style.zIndex = SLIDE_TOP_Z;
|
||||
moveToNextSlide();
|
||||
|
||||
play();
|
||||
}, safe_duration(item) * 1000);
|
||||
};
|
||||
|
||||
refreshSlidesOrder()
|
||||
SLIDE_TOP_Z = (parseInt(SLIDE_TOP_Z) * 5).toString();
|
||||
SLIDE_BOTTOM_Z = (parseInt(SLIDE_BOTTOM_Z) * 5).toString();
|
||||
cronSlide.style.zIndex = SLIDE_BOTTOM_Z;
|
||||
curSlide.style.zIndex = SLIDE_TOP_Z;
|
||||
moveToNextSlide();
|
||||
loadContent(cronSlide, callbackReady, item);
|
||||
}
|
||||
|
||||
main();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user