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
This commit is contained in:
Nicolas Mowen 2023-01-13 16:27:16 -07:00 committed by GitHub
parent 170899bd71
commit e0b3b27b8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 19 deletions

View File

@ -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
```

View File

@ -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")

View File

@ -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 = (
<Fragment>
<div className="w-5xl text-center text-sm">
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
<div className="max-w-5xl">
<MsePlayer camera="birdseye" />
</div>
</Fragment>
);
} else {
player = (
<Fragment>
<div className="max-w-5xl">
<MsePlayer camera="birdseye" />
<div className="w-5xl text-center text-sm">
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
</div>
</Fragment>
);

View File

@ -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"
/>
<Switch checked={options['zones']} id="zones" onChange={handleSetOption} label="Zones" labelPosition="after" />
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} label="Motion Masks" labelPosition="after" />
<Switch
checked={options['mask']}
id="mask"
onChange={handleSetOption}
label="Motion Masks"
labelPosition="after"
/>
<Switch
checked={options['motion']}
id="motion"
@ -99,19 +107,19 @@ export default function Camera({ camera }) {
let player;
if (viewMode === 'live') {
if (viewSource == 'mse' && cameraConfig.restream.enabled) {
if (videojs.browser.IS_IOS) {
if ('MediaSource' in window) {
player = (
<Fragment>
<div className="w-5xl text-center text-sm">
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
<div className="max-w-5xl">
<MsePlayer camera={camera} />
</div>
</Fragment>
);
} else {
player = (
<Fragment>
<div className="max-w-5xl">
<MsePlayer camera={camera} />
<div className="w-5xl text-center text-sm">
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
</div>
</Fragment>
);
@ -191,3 +199,15 @@ export default function Camera({ camera }) {
</div>
);
}
function getDefaultLiveMode(config, cameraConfig) {
if (cameraConfig) {
if (cameraConfig.restream.enabled) {
return config.ui.live_mode;
}
return 'jsmpeg';
}
return undefined;
}

View File

@ -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 <ActivityIndicator />;
}
const timezone = config.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
return (
<div className="space-y-4 p-2 px-4 w-full">
<Heading>Events</Heading>
@ -512,8 +514,8 @@ export default function Events({ path, ...props }) {
({(event.top_score * 100).toFixed(0)}%)
</div>
<div className="text-sm">
{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)})
</div>
<div className="capitalize text-sm flex align-center mt-1">

View File

@ -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 <ActivityIndicator />;
}
if (config.ui.timezone) {
timezone = config.ui.timezone;
}
if (recordingsSummary.length === 0) {
return (
<div className="space-y-4">