From c4bb880a08d15201d42cef4c295fb15d143bc646 Mon Sep 17 00:00:00 2001 From: Nils Bosbach Date: Sun, 26 Oct 2025 15:23:35 +0100 Subject: [PATCH 1/2] Ensure live preview is not cached On some devices, the video preview freezes when the video is hidden and then shown again (e.g, show preview during countdown; hide preview to show taken picture; show preview during countdown of the next picture). This is because the last frame can be cached by the browser when the preview is hidden, and this cached frame is displayed when the preview is shown again. To disable caching, a unique parameter is added to the URL of the stream of the stream. Signed-off-by: Nils Bosbach --- admin/captureconfig.php | 2 +- admin/wgetcaptureconfig.php | 2 +- assets/js/preview.js | 23 ++++++++++++++++++++--- assets/js/test-preview.js | 2 +- assets/sass/components/_preview.scss | 6 ------ docs/faq/index.md | 4 ++-- lib/configsetup.inc.php | 2 +- template/components/preview.php | 2 +- 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/admin/captureconfig.php b/admin/captureconfig.php index e4af93df2..5acde7ef8 100644 --- a/admin/captureconfig.php +++ b/admin/captureconfig.php @@ -34,7 +34,7 @@ $config['picture']['cheese_time'] = '0'; $config['preview']['mode'] = 'url'; -$config['preview']['url'] = 'url("http://' . Environment::getIp() . ':1984/api/stream.mjpeg?src=photobooth")'; +$config['preview']['url'] = 'http://' . Environment::getIp() . ':1984/api/stream.mjpeg?src=photobooth'; $config['preview']['camTakesPic'] = false; try { diff --git a/admin/wgetcaptureconfig.php b/admin/wgetcaptureconfig.php index 18edb01b6..4c2ad75fe 100644 --- a/admin/wgetcaptureconfig.php +++ b/admin/wgetcaptureconfig.php @@ -34,7 +34,7 @@ $config['picture']['cheese_time'] = '0'; $config['preview']['mode'] = 'url'; -$config['preview']['url'] = 'url("http://' . Environment::getIp() . ':1984/api/stream.mjpeg?src=photobooth")'; +$config['preview']['url'] = 'http://' . Environment::getIp() . ':1984/api/stream.mjpeg?src=photobooth'; $config['preview']['camTakesPic'] = false; try { diff --git a/assets/js/preview.js b/assets/js/preview.js index a044d964e..6e97864b1 100644 --- a/assets/js/preview.js +++ b/assets/js/preview.js @@ -1,5 +1,22 @@ /* eslint n/no-unsupported-features/node-builtins: "off" */ /* globals photoBooth photoboothTools */ + +function addCacheBustingParam(url) { + const timestamp = new Date().getTime(); + + if (url.includes('?')) { + return `${url}&t=${timestamp}`; + } + + return `${url}?t=${timestamp}`; +} + +function getRootProperty(property) { + const root = document.documentElement; + const style = getComputedStyle(root); + return style.getPropertyValue(property).trim(); +} + const photoboothPreview = (function () { // vars const CameraDisplayMode = { @@ -163,8 +180,8 @@ const photoboothPreview = (function () { } else if (config.preview.mode === PreviewMode.URL.valueOf()) { photoboothTools.console.logDev('Preview: Preview at countdown from URL.'); setTimeout(function () { + url.attr('src', addCacheBustingParam(getRootProperty('--background-preview'))); url.show(); - url.addClass('streaming'); }, config.preview.url_delay); } break; @@ -175,8 +192,8 @@ const photoboothPreview = (function () { } else if (config.preview.mode === PreviewMode.URL.valueOf()) { photoboothTools.console.logDev('Preview: Preview from URL.'); setTimeout(function () { + url.attr('src', addCacheBustingParam(getRootProperty('--background-preview'))); url.show(); - url.addClass('streaming'); }, config.preview.url_delay); } break; @@ -201,8 +218,8 @@ const photoboothPreview = (function () { api.stream.getTracks()[0].stop(); api.stream = null; } - url.removeClass('streaming'); url.hide(); + url.attr('src', ''); video.hide(); pictureFrame.hide(); collageFrame.hide(); diff --git a/assets/js/test-preview.js b/assets/js/test-preview.js index f24a4f9f4..dbd932137 100644 --- a/assets/js/test-preview.js +++ b/assets/js/test-preview.js @@ -96,7 +96,7 @@ const photoboothPreviewTest = (function () { if (config.preview.mode === PreviewMode.DEVICE.valueOf()) { photoboothPreview.stopVideo(); } else if (config.preview.mode === PreviewMode.URL.valueOf()) { - previewIpcam.removeClass('streaming'); + previewIpcam.attr('src', ''); previewIpcam.hide(); } diff --git a/assets/sass/components/_preview.scss b/assets/sass/components/_preview.scss index 48eda7ea9..1b39e2b44 100644 --- a/assets/sass/components/_preview.scss +++ b/assets/sass/components/_preview.scss @@ -92,9 +92,3 @@ scale: 1 -1; } } - -#preview--ipcam { - &.streaming { - background-image: var(--background-preview); - } -} diff --git a/docs/faq/index.md b/docs/faq/index.md index f4b07fefd..5b64a03d0 100644 --- a/docs/faq/index.md +++ b/docs/faq/index.md @@ -468,7 +468,7 @@ If you like to have the same preview independent of the device you access Photob Make sure to have a stream available you can use (e.g. from your Webcam, Smartphone Camera or Raspberry Pi Camera) - Admin panel config _"Preview mode"_: `from URL` -- Admin panel config _"Preview-URL"_ example (add needed IP address instead): `url(http://192.168.0.2:8081)` +- Admin panel config _"Preview-URL"_ example (add needed IP address instead): `http://192.168.0.2:8081` **Note** @@ -480,7 +480,7 @@ Make sure to have a stream available you can use (e.g. from your Webcam, Smartph If you want to use a stream from your DSLR or Pi Camera, install go2rtc and setup needed service to use. -go2rtc can be accessed at `http://localhost:1984`. Use `url("http://localhost:1984/api/stream.mjpeg?src=photobooth")` as _"Preview-URL"_ (replace `localhost` with Photobooths IP for remote access). +go2rtc can be accessed at `http://localhost:1984`. Use `http://localhost:1984/api/stream.mjpeg?src=photobooth` as _"Preview-URL"_ (replace `localhost` with Photobooths IP for remote access). To be able to also capture images you need to adjust the capture command. _"Commands"_: _"Take picture command"_: `capture %s` diff --git a/lib/configsetup.inc.php b/lib/configsetup.inc.php index f556f3a2d..1017412ac 100644 --- a/lib/configsetup.inc.php +++ b/lib/configsetup.inc.php @@ -1365,7 +1365,7 @@ 'preview_url' => [ 'type' => 'input', 'name' => 'preview[url]', - 'placeholder' => 'url(http://localhost:8081)', + 'placeholder' => 'http://localhost:8081', 'value' => htmlentities($config['preview']['url'] ?? ''), ], 'preview_url_delay' => [ diff --git a/template/components/preview.php b/template/components/preview.php index 3d2f02b79..86bf91e60 100644 --- a/template/components/preview.php +++ b/template/components/preview.php @@ -44,7 +44,7 @@ echo '
'; echo '
'; echo ''; -echo '
'; +echo ''; echo '
' . $languageService->translate('no_preview') . '
'; echo '
'; From 392eec42de39de243271499e9bb618fc449622b7 Mon Sep 17 00:00:00 2001 From: Nils Bosbach Date: Tue, 28 Oct 2025 20:26:01 +0100 Subject: [PATCH 2/2] Migrate preview.url field in config When the config has been created by a previous version, the preview.url field may contain values wrapped in `url("...")`. The updated version no longer requires this wrapper. If the preview.url field starts with `url(` and ends with `)`, the `url(...)` wrapper and any surrounding quotes are removed. Signed-off-by: Nils Bosbach --- src/Service/ConfigurationService.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Service/ConfigurationService.php b/src/Service/ConfigurationService.php index 215d92de4..5acc104cc 100644 --- a/src/Service/ConfigurationService.php +++ b/src/Service/ConfigurationService.php @@ -161,6 +161,11 @@ protected function processMigration(array $config): array $config['preview']['mode'] = 'device_cam'; } + // Migrate Preview URL + if (isset($config['preview']['url']) && substr($config['preview']['url'], 0, 4) === 'url(' && substr($config['preview']['url'], -1) === ')') { + $config['preview']['url'] = trim(substr($config['preview']['url'], 4, -1), '"\''); + } + return $config; }