From 1b5daf3c736a7e81727a8e028d7902b704e0d47e Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 17 Oct 2026 05:46:52 -0400 Subject: [PATCH 1/2] Add slideshow mode to the live view Auto-advance the live grid on a timer: a 1x1 layout cycles through cameras, larger grids cycle through pages. Interval is 5-60s, persisted per browser and mirrored to ?slideshow= so wall displays and kiosks can bookmark it. The slideshow pauses during tile reorder, per-tile native fullscreen, the building plan surface, and while the tab is hidden. Manual paging restarts the countdown. Wired into both the HLS/MSE and WebRTC views. Co-Authored-By: Claude Opus 5.5 --- .../preact/LiveSlideshowControl.jsx | 48 ++++++ web/js/components/preact/LiveView.jsx | 26 +++ web/js/components/preact/WebRTCView.jsx | 26 +++ web/js/components/preact/useLiveSlideshow.js | 156 ++++++++++++++++++ web/public/locales/en.json | 5 + web/public/locales/fil.json | 5 + web/tests/liveSlideshow.spec.js | 85 ++++++++++ 7 files changed, 351 insertions(+) create mode 100644 web/js/components/preact/LiveSlideshowControl.jsx create mode 100644 web/js/components/preact/useLiveSlideshow.js create mode 100644 web/tests/liveSlideshow.spec.js diff --git a/web/js/components/preact/LiveSlideshowControl.jsx b/web/js/components/preact/LiveSlideshowControl.jsx new file mode 100644 index 00000000..337470b6 --- /dev/null +++ b/web/js/components/preact/LiveSlideshowControl.jsx @@ -0,0 +1,48 @@ +import { useI18n } from '../../i18n.js'; +import { LIVE_SLIDESHOW_INTERVALS } from './useLiveSlideshow.js'; + +/** + * Slideshow toggle for the live view toolbar, with an interval picker shown + * while it is on. In a 1x1 layout it cycles cameras; otherwise grid pages. + */ +export function LiveSlideshowControl({ enabled, interval, isSingleStream, onToggle, onIntervalChange }) { + const { t } = useI18n(); + const label = enabled + ? t('live.slideshowStop') + : t(isSingleStream ? 'live.slideshowStartCameras' : 'live.slideshowStartPages'); + return ( +
+ + {enabled && ( + + )} +
+ ); +} + +export default LiveSlideshowControl; diff --git a/web/js/components/preact/LiveView.jsx b/web/js/components/preact/LiveView.jsx index 8bef20d7..c0dc24dc 100644 --- a/web/js/components/preact/LiveView.jsx +++ b/web/js/components/preact/LiveView.jsx @@ -20,6 +20,8 @@ import { AlwaysFullscreenToggle } from './AlwaysFullscreenToggle.jsx'; import { useAlwaysFullscreenOnTap } from './useAlwaysFullscreenOnTap.js'; import { LiveDisplayModeToggle } from './LiveDisplayModeToggle.jsx'; import { useLiveDisplayMode } from './useLiveDisplayMode.js'; +import { LiveSlideshowControl } from './LiveSlideshowControl.jsx'; +import { useLiveSlideshow } from './useLiveSlideshow.js'; import { usePullToRefresh } from './usePullToRefresh.js'; import { shouldShowGestureTip } from './mobileLiveGestures.js'; import { fetchAllStreamSummaries } from '../../utils/stream-summaries.js'; @@ -522,6 +524,20 @@ export function LiveView({audioDisabled = false, isAutoDisabled = false, isWebRT }); }, [applyOperatorLayout, setOperatorSurface]); + // Auto-advance cameras (1x1) or grid pages on a timer. Paused while the + // operator is rearranging tiles, has one tile in native fullscreen, or is + // looking at the building plan instead of the grid. + const slideshow = useLiveSlideshow({ + paused: reorderMode || !!fullscreenCellStream || operatorSurface !== 'grid', + isSingleStream, + orderedStreams, + selectedStream, + setSelectedStream, + currentPage, + setCurrentPage, + maxStreams, + }); + // Ensure current page is valid when orderedStreams or layout changes useEffect(() => { if (orderedStreams.length === 0) return; @@ -824,6 +840,16 @@ export function LiveView({audioDisabled = false, isAutoDisabled = false, isWebRT /> )} + {slideshow.canCycle && ( + + )} + {orderedStreams.length > 1 && (