mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
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:
parent
170899bd71
commit
e0b3b27b8a
@ -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
|
||||
```
|
||||
|
@ -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")
|
||||
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user