Skip to content

Commit f36fe24

Browse files
committed
add idle loop screensaver
1 parent ebd2b87 commit f36fe24

File tree

10 files changed

+348
-4
lines changed

10 files changed

+348
-4
lines changed

api/settings.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@
4040
if (!empty($config['background']['chroma'])) {
4141
$config['background']['chroma'] = PathUtility::getPublicPath($config['background']['chroma']);
4242
}
43+
if (!empty($config['idle']['image_source']) && $config['idle']['mode'] !== 'folder') {
44+
$config['idle']['image_source'] = PathUtility::getPublicPath($config['idle']['image_source']);
45+
}
46+
if (!empty($config['idle']['video_source'])) {
47+
$config['idle']['video_source'] = PathUtility::getPublicPath($config['idle']['video_source']);
48+
}
4349

4450
echo 'const config = ' . json_encode($config) . ';';
4551
echo 'const environment = ' . json_encode(new Environment()) . ';';

assets/js/core.js

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ const photoBooth = (function () {
4040
loaderMessage = loader.find('.stage-message'),
4141
loaderImage = loader.find('.stage-image'),
4242
resultPage = $('.stage[data-stage="result"]'),
43+
idleOverlay = $('#idle-overlay'),
44+
idleVideo = $('#idle-video'),
45+
idleImage = $('#idle-image'),
46+
idleTextTop = $('#idle-text-top'),
47+
idleTextBottom = $('#idle-text-bottom'),
4348
previewIpcam = $('#preview--ipcam'),
4449
previewVideo = $('#preview--video'),
4550
previewFramePicture = $('#previewframe--picture'),
@@ -61,7 +66,16 @@ const photoBooth = (function () {
6166
timeToLive = config.picture.time_to_live * 1000,
6267
continuousCollageTime = config.collage.continuous_time * 1000,
6368
retryTimeout = config.picture.retry_timeout * 1000,
64-
notificationTimeout = config.ui.notification_timeout * 1000;
69+
notificationTimeout = config.ui.notification_timeout * 1000,
70+
idleMode = config.idle.mode,
71+
idleEnabled =
72+
config.idle.enabled &&
73+
config.idle.timeout_minutes > 0 &&
74+
(idleMode === 'gallery' ||
75+
idleMode === 'folder' ||
76+
(idleMode === 'video' ? !!config.idle.video_source : !!config.idle.image_source)),
77+
idleTimeoutMs = (config.idle.timeout_minutes || 0) * 60000,
78+
idleSwitchMs = (config.idle.switch_minutes || 1) * 60000;
6579

6680
let timeOut,
6781
chromaFile = '',
@@ -71,7 +85,10 @@ const photoBooth = (function () {
7185
command,
7286
startTime,
7387
endTime,
74-
totalTime;
88+
totalTime,
89+
idleTimeout,
90+
idleSwitchTimeout,
91+
idleLastTextTop = true;
7592

7693
api.takingPic = false;
7794
api.nextCollageNumber = 0;
@@ -136,6 +153,136 @@ const photoBooth = (function () {
136153
rotaryController.focusSet(startPage);
137154

138155
initPhotoSwipeFromDOM('#galimages');
156+
157+
api.idle.resetTimer();
158+
};
159+
160+
api.idle = {
161+
pickImageFromGallery: function () {
162+
const anchors = $('#galimages a');
163+
if (!anchors.length) {
164+
return '';
165+
}
166+
const randomIndex = Math.floor(Math.random() * anchors.length);
167+
return $(anchors[randomIndex]).attr('href');
168+
},
169+
resolveSource: function () {
170+
const base = environment.publicFolders.api;
171+
switch (idleMode) {
172+
case 'video':
173+
return config.idle.video_source;
174+
case 'image':
175+
return config.idle.image_source;
176+
case 'folder':
177+
return base + '/randomImg.php?dir=' + encodeURIComponent('screensavers');
178+
case 'gallery':
179+
return api.idle.pickImageFromGallery();
180+
default:
181+
return '';
182+
}
183+
},
184+
hide: function () {
185+
if (!idleOverlay.length) {
186+
return;
187+
}
188+
idleOverlay.removeClass('idle-overlay--active');
189+
startPage.removeClass('stage--idle');
190+
clearTimeout(idleSwitchTimeout);
191+
if (idleVideo.length) {
192+
const vid = idleVideo.get(0);
193+
vid.pause();
194+
vid.currentTime = 0;
195+
idleVideo.attr('src', '');
196+
}
197+
idleImage.hide().attr('src', '');
198+
idleTextTop.text('').hide();
199+
idleTextBottom.text('').hide();
200+
},
201+
toggleGalleryText: function () {
202+
const text = config.idle.gallery_text;
203+
204+
if (!text) {
205+
idleTextTop.hide();
206+
idleTextBottom.hide();
207+
return;
208+
}
209+
210+
if (idleLastTextTop) {
211+
idleTextBottom.text(text).show();
212+
idleTextTop.hide();
213+
} else {
214+
idleTextTop.text(text).show();
215+
idleTextBottom.hide();
216+
}
217+
218+
idleLastTextTop = !idleLastTextTop;
219+
},
220+
show: function () {
221+
if (!idleEnabled || !idleOverlay.length) {
222+
return;
223+
}
224+
if (!startPage.hasClass('stage--active')) {
225+
api.idle.resetTimer();
226+
return;
227+
}
228+
229+
const mode = idleMode;
230+
const source = api.idle.resolveSource();
231+
if (!source) {
232+
api.idle.resetTimer();
233+
return;
234+
}
235+
236+
if (mode === 'video') {
237+
idleOverlay.css('background-image', 'none');
238+
idleVideo.attr('src', source || '');
239+
idleVideo.show();
240+
const vid = idleVideo.get(0);
241+
vid.play().catch(() => {});
242+
idleImage.hide();
243+
idleTextTop.hide();
244+
idleTextBottom.hide();
245+
} else if (mode === 'gallery') {
246+
idleVideo.hide();
247+
idleOverlay.css('background-image', 'none');
248+
idleImage.attr('src', source).show();
249+
api.idle.toggleGalleryText();
250+
} else {
251+
idleVideo.hide();
252+
idleImage.hide();
253+
idleTextTop.hide();
254+
idleTextBottom.hide();
255+
idleOverlay.css('background-image', source ? `url(${source})` : 'none');
256+
idleOverlay.css('background-size', 'cover');
257+
}
258+
259+
startPage.addClass('stage--idle');
260+
idleOverlay.addClass('idle-overlay--active');
261+
262+
clearTimeout(idleSwitchTimeout);
263+
if ((mode === 'folder' || mode === 'gallery') && idleSwitchMs > 0) {
264+
idleSwitchTimeout = setTimeout(function nextIdleFrame() {
265+
const nextSource = api.idle.resolveSource();
266+
if (nextSource) {
267+
if (mode === 'folder') {
268+
idleOverlay.css('background-image', `url(${nextSource})`);
269+
} else if (mode === 'gallery') {
270+
idleImage.attr('src', nextSource).show();
271+
api.idle.toggleGalleryText();
272+
}
273+
}
274+
idleSwitchTimeout = setTimeout(nextIdleFrame, idleSwitchMs);
275+
}, idleSwitchMs);
276+
}
277+
},
278+
resetTimer: function () {
279+
if (!idleEnabled) {
280+
return;
281+
}
282+
clearTimeout(idleTimeout);
283+
api.idle.hide();
284+
idleTimeout = setTimeout(api.idle.show, idleTimeoutMs);
285+
}
139286
};
140287

141288
api.navbar = {
@@ -503,6 +650,7 @@ const photoBooth = (function () {
503650
videoBackground.hide();
504651
startPage.removeClass('stage--active');
505652
loader.addClass('stage--active');
653+
api.idle.hide();
506654

507655
if (config.get_request.countdown) {
508656
let getMode;
@@ -1250,6 +1398,8 @@ const photoBooth = (function () {
12501398
if (config.commands.post_photo) {
12511399
api.shellCommand('post-command', filename);
12521400
}
1401+
1402+
api.idle.resetTimer();
12531403
};
12541404

12551405
api.addImage = function (imageName) {
@@ -1460,6 +1610,17 @@ const photoBooth = (function () {
14601610
rotaryController.focusSet(startPage);
14611611
});
14621612

1613+
if (idleEnabled) {
1614+
$(document).on('click touchstart keydown mousemove', function () {
1615+
api.idle.resetTimer();
1616+
});
1617+
1618+
idleOverlay.on('click touchstart', function (e) {
1619+
e.preventDefault();
1620+
api.idle.resetTimer();
1621+
});
1622+
}
1623+
14631624
$('.cups-button').on('click', function (ev) {
14641625
ev.preventDefault();
14651626

assets/sass/components/_stage.scss

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,52 @@
177177
}
178178
}
179179
}
180+
181+
.idle-overlay {
182+
position: absolute;
183+
inset: 0;
184+
display: none;
185+
align-items: center;
186+
justify-content: center;
187+
background: rgba(0, 0, 0, 0.9);
188+
background-size: contain;
189+
background-position: center;
190+
z-index: 20;
191+
cursor: pointer;
192+
193+
&--active {
194+
display: flex;
195+
}
196+
197+
video {
198+
max-width: 100%;
199+
max-height: 100%;
200+
object-fit: contain;
201+
}
202+
203+
&__image {
204+
max-width: 100%;
205+
max-height: 100%;
206+
object-fit: contain;
207+
display: none;
208+
}
209+
210+
&__text {
211+
position: absolute;
212+
width: 100%;
213+
text-align: center;
214+
color: #ffffff;
215+
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.7);
216+
font-size: clamp(1rem, 2vw, 2rem);
217+
padding: 1rem 2rem;
218+
box-sizing: border-box;
219+
220+
&--top {
221+
top: 2rem;
222+
}
223+
224+
&--bottom {
225+
bottom: 2rem;
226+
}
227+
}
228+
}

lib/boot.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
FileUtility::createDirectory(FolderEnum::TEMP->absolute());
3232
FileUtility::createDirectory(FolderEnum::PRIVATE->absolute());
3333
FileUtility::createDirectory(PathUtility::getAbsolutePath('private/fonts'));
34+
FileUtility::createDirectory(PathUtility::getAbsolutePath('private/screensavers'));
3435
FileUtility::createDirectory(PathUtility::getAbsolutePath('private/images/background'));
3536
FileUtility::createDirectory(PathUtility::getAbsolutePath('private/images/frames'));
3637
FileUtility::createDirectory(PathUtility::getAbsolutePath('private/images/keyingBackgrounds'));

lib/configsetup.inc.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,83 @@
370370
'current' => $config['theme']['current'] ?? '',
371371
],
372372
],
373+
'idle' => [
374+
'view' => 'basic',
375+
'platform' => 'all',
376+
'idle_enabled' => [
377+
'view' => 'basic',
378+
'type' => 'checkbox',
379+
'name' => 'idle[enabled]',
380+
'value' => $config['idle']['enabled'],
381+
],
382+
'idle_mode' => [
383+
'view' => 'basic',
384+
'type' => 'select',
385+
'name' => 'idle[mode]',
386+
'placeholder' => $defaultConfig['idle']['mode'],
387+
'data-theme-field' => 'true',
388+
'options' => [
389+
'image' => 'image',
390+
'video' => 'video',
391+
'folder' => 'folder',
392+
'gallery' => 'gallery',
393+
],
394+
'value' => $config['idle']['mode'],
395+
],
396+
'idle_image_source' => [
397+
'view' => 'basic',
398+
'type' => 'image',
399+
'name' => 'idle[image_source]',
400+
'data-theme-field' => 'true',
401+
'placeholder' => $defaultConfig['idle']['image_source'],
402+
'value' => htmlentities($config['idle']['image_source'] ?? ''),
403+
'paths' => [
404+
PathUtility::getAbsolutePath('private/screensavers'),
405+
],
406+
],
407+
'idle_video_source' => [
408+
'view' => 'basic',
409+
'type' => 'video',
410+
'name' => 'idle[video_source]',
411+
'data-theme-field' => 'true',
412+
'placeholder' => $defaultConfig['idle']['video_source'] ?? '',
413+
'value' => htmlentities($config['idle']['video_source'] ?? ''),
414+
'paths' => [
415+
PathUtility::getAbsolutePath('private/screensavers'),
416+
PathUtility::getAbsolutePath('private/videos'),
417+
PathUtility::getAbsolutePath('resources/videos'),
418+
],
419+
],
420+
'idle_gallery_text' => [
421+
'view' => 'basic',
422+
'type' => 'input',
423+
'name' => 'idle[gallery_text]',
424+
'placeholder' => $defaultConfig['idle']['gallery_text'],
425+
'value' => htmlentities($config['idle']['gallery_text'] ?? ''),
426+
],
427+
'idle_timeout_minutes' => [
428+
'view' => 'basic',
429+
'type' => 'number',
430+
'name' => 'idle[timeout_minutes]',
431+
'placeholder' => $defaultConfig['idle']['timeout_minutes'],
432+
'value' => $config['idle']['timeout_minutes'],
433+
'range_min' => 0,
434+
'range_max' => 120,
435+
'range_step' => 1,
436+
'unit' => 'min',
437+
],
438+
'idle_switch_minutes' => [
439+
'view' => 'basic',
440+
'type' => 'number',
441+
'name' => 'idle[switch_minutes]',
442+
'placeholder' => $defaultConfig['idle']['switch_minutes'],
443+
'value' => $config['idle']['switch_minutes'],
444+
'range_min' => 1,
445+
'range_max' => 120,
446+
'range_step' => 1,
447+
'unit' => 'min',
448+
],
449+
],
373450
'frontpage' => [
374451
'view' => 'basic',
375452
'ui_show_fork' => [

resources/lang/de.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@
244244
"general:start_screen_subtitle_visible": "Untertitel auf Startbildschirm anzeigen",
245245
"general:start_screen_title": "Startbildschirm (Titel)",
246246
"general:start_screen_title_visible": "Titel auf Startbildschirm anzeigen",
247+
"idle": "Idle-Loop",
248+
"idle:idle_enabled": "Idle-Loop aktivieren",
249+
"idle:idle_mode": "Idle-Medientyp",
250+
"idle:idle_image_source": "Idle-Bildpfad",
251+
"idle:idle_video_source": "Idle-Videopfad",
252+
"idle:idle_gallery_text": "Idle-Galerietext",
253+
"idle:idle_timeout_minutes": "Zeit bis Idle (Minuten)",
254+
"idle:idle_switch_minutes": "Wechselintervall (Minuten)",
247255
"general:translate": "Übersetzung fehlt?",
248256
"general:ui_language": "Sprache auswählen",
249257
"general:ui_notification_timeout": "Benachrichtigungszeit",

0 commit comments

Comments
 (0)