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 && (