From e0b3b27b8a8274463fb0972ab6d37e002f3eb96a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 13 Jan 2023 16:27:16 -0700 Subject: [PATCH] Add option for live mode & timezone config, fix MSE check for iPad (#5079) * Add config fields * Clean up camera default values * Set recordings timezone with config if available * Adjust for timezone config * Cleanup setting of the timezone * Don't fail on MSE check iPad * Fix MSE check for birdseye * Add docs * Fix test --- docs/docs/configuration/index.md | 9 ++++++++ frigate/config.py | 10 +++++++++ web/src/routes/Birdseye.jsx | 11 +++++----- web/src/routes/Camera.jsx | 36 +++++++++++++++++++++++++------- web/src/routes/Events.jsx | 8 ++++--- web/src/routes/Recording.jsx | 9 ++++++-- 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index d9ac292ee..34ee7c4a6 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -486,4 +486,13 @@ cameras: order: 0 # Optional: Whether or not to show the camera in the Frigate UI (default: shown below) dashboard: True + +# Optional +ui: + # Optional: Set the default live mode for cameras in the UI (default: shown below) + live_mode: mse + # Optional: Set a timezone to use in the UI (default: use browser local time) + timezone: None + # Optional: Use an experimental recordings / camera view UI (default: shown below) + experimental_ui: False ``` diff --git a/frigate/config.py b/frigate/config.py index 7c4e945aa..44c1472e1 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -60,7 +60,17 @@ class FrigateBaseModel(BaseModel): extra = Extra.forbid +class LiveModeEnum(str, Enum): + jsmpeg = "jsmpeg" + mse = "mse" + webrtc = "webrtc" + + class UIConfig(FrigateBaseModel): + live_mode: LiveModeEnum = Field( + default=LiveModeEnum.mse, title="Default Live Mode." + ) + timezone: Optional[str] = Field(title="Override UI timezone.") use_experimental: bool = Field(default=False, title="Experimental UI") diff --git a/web/src/routes/Birdseye.jsx b/web/src/routes/Birdseye.jsx index 1097286d3..6571d8eab 100644 --- a/web/src/routes/Birdseye.jsx +++ b/web/src/routes/Birdseye.jsx @@ -6,7 +6,6 @@ import Heading from '../components/Heading'; import WebRtcPlayer from '../components/WebRtcPlayer'; import MsePlayer from '../components/MsePlayer'; import useSWR from 'swr'; -import videojs from 'video.js'; export default function Birdseye() { const { data: config } = useSWR('config'); @@ -20,19 +19,19 @@ export default function Birdseye() { let player; if (viewSource == 'mse' && config.restream.birdseye) { - if (videojs.browser.IS_IOS) { + if ('MediaSource' in window) { player = ( -
- MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info. +
+
); } else { player = ( -
- +
+ MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
); diff --git a/web/src/routes/Camera.jsx b/web/src/routes/Camera.jsx index 1a0a42791..8d9c0dd44 100644 --- a/web/src/routes/Camera.jsx +++ b/web/src/routes/Camera.jsx @@ -15,7 +15,6 @@ import { useApiHost } from '../api'; import useSWR from 'swr'; import WebRtcPlayer from '../components/WebRtcPlayer'; import MsePlayer from '../components/MsePlayer'; -import videojs from 'video.js'; const emptyObject = Object.freeze({}); @@ -29,7 +28,10 @@ export default function Camera({ camera }) { const jsmpegWidth = cameraConfig ? Math.round(cameraConfig.restream.jsmpeg.height * (cameraConfig.detect.width / cameraConfig.detect.height)) : 0; - const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(`${camera}-source`, 'mse'); + const [viewSource, setViewSource, sourceIsLoaded] = usePersistence( + `${camera}-source`, + getDefaultLiveMode(config, cameraConfig) + ); const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['mse', 'webrtc', 'jsmpeg'] : ['jsmpeg']; const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject); @@ -77,7 +79,13 @@ export default function Camera({ camera }) { labelPosition="after" /> - + -
- MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info. +
+
); } else { player = ( -
- +
+ MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
); @@ -191,3 +199,15 @@ export default function Camera({ camera }) {
); } + +function getDefaultLiveMode(config, cameraConfig) { + if (cameraConfig) { + if (cameraConfig.restream.enabled) { + return config.ui.live_mode; + } + + return 'jsmpeg'; + } + + return undefined; +} diff --git a/web/src/routes/Events.jsx b/web/src/routes/Events.jsx index dc123696d..2f8fd8a9d 100644 --- a/web/src/routes/Events.jsx +++ b/web/src/routes/Events.jsx @@ -122,7 +122,7 @@ export default function Events({ path, ...props }) { return memo; }, config?.objects?.track || []) .filter((value, i, self) => self.indexOf(value) === i), - sub_labels: (allSubLabels || []).length > 0 ? [...Object.values(allSubLabels), "None"] : [], + sub_labels: (allSubLabels || []).length > 0 ? [...Object.values(allSubLabels), 'None'] : [], }), [config, allSubLabels] ); @@ -295,6 +295,8 @@ export default function Events({ path, ...props }) { return ; } + const timezone = config.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone; + return (
Events @@ -512,8 +514,8 @@ export default function Events({ path, ...props }) { ({(event.top_score * 100).toFixed(0)}%)
- {new Date(event.start_time * 1000).toLocaleDateString()}{' '} - {new Date(event.start_time * 1000).toLocaleTimeString()} ( + {new Date(event.start_time * 1000).toLocaleDateString({ timeZone: timezone })}{' '} + {new Date(event.start_time * 1000).toLocaleTimeString({ timeZone: timezone })} ( {clipDuration(event.start_time, event.end_time)})
diff --git a/web/src/routes/Recording.jsx b/web/src/routes/Recording.jsx index 7b904c707..f8fcd5e54 100644 --- a/web/src/routes/Recording.jsx +++ b/web/src/routes/Recording.jsx @@ -9,7 +9,8 @@ import { useApiHost } from '../api'; import useSWR from 'swr'; export default function Recording({ camera, date, hour = '00', minute = '00', second = '00' }) { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const { data: config } = useSWR('config'); + let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const currentDate = useMemo( () => (date ? parseISO(`${date}T${hour || '00'}:${minute || '00'}:${second || '00'}`) : new Date()), [date, hour, minute, second] @@ -113,10 +114,14 @@ export default function Recording({ camera, date, hour = '00', minute = '00', se } }, [seekSeconds, playlistIndex]); - if (!recordingsSummary || !recordings) { + if (!recordingsSummary || !recordings || !config) { return ; } + if (config.ui.timezone) { + timezone = config.ui.timezone; + } + if (recordingsSummary.length === 0) { return (