Treat camera unavailability in a more friendly way.

Also creates a `components-lib` directory for major webcomponent support code.
This commit is contained in:
Dermot Duffy
2023-10-05 21:18:03 -07:00
parent 95e42ab767
commit d895c43e67
23 changed files with 207 additions and 89 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import { customElement, property } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import isEqual from 'lodash-es/isEqual';
import { CachedValueController } from '../cached-value-controller.js';
import { CachedValueController } from '../components-lib/cached-value-controller.js';
import { CameraConfig, ImageViewConfig } from '../config/types.js';
import defaultImage from '../images/frigate-bird-in-sky.jpg';
import { localize } from '../localize/localize.js';
+11 -2
View File
@@ -3,12 +3,14 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit
import { customElement, property } from 'lit/decorators.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraConfig } from '../../config/types';
import { localize } from '../../localize/localize';
import '../../patches/ha-camera-stream';
import '../../patches/ha-hls-player.js';
import '../../patches/ha-web-rtc-player.ts';
import liveHAStyle from '../../scss/live-ha.scss';
import { FrigateCardMediaPlayer } from '../../types.js';
import { getStateObjOrDispatchError } from './live.js';
import { renderMessage } from '../message';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj';
@customElement('frigate-card-live-ha')
export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPlayer {
@@ -68,7 +70,14 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla
if (!stateObj) {
return;
}
if (stateObj.state === 'unavailable') {
return renderMessage({
message: localize('error.live_camera_unavailable'),
type: 'error',
icon: 'mdi:connection',
context: this.cameraConfig,
});
}
return html` <frigate-card-ha-camera-stream
${ref(this._playerRef)}
.hass=${this.hass}
+1 -1
View File
@@ -6,7 +6,7 @@ import { CameraConfig } from '../../config/types';
import basicBlockStyle from '../../scss/basic-block.scss';
import { FrigateCardMediaPlayer } from '../../types.js';
import '../image.js';
import { getStateObjOrDispatchError } from './live.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj';
@customElement('frigate-card-live-image')
export class FrigateCardLiveImage extends LitElement implements FrigateCardMediaPlayer {
+25 -46
View File
@@ -1,5 +1,3 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import {
CSSResultGroup,
html,
@@ -16,6 +14,11 @@ import { keyed } from 'lit/directives/keyed.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraManager } from '../../camera-manager/manager.js';
import { CameraConfigs, CameraEndpoints } from '../../camera-manager/types.js';
import {
ConditionsManagerEpoch,
getOverriddenConfig,
} from '../../card-controller/conditions-manager.js';
import { MediaGridSelected } from '../../components-lib/media-grid-controller.js';
import {
CameraConfig,
CardWideConfig,
@@ -37,16 +40,11 @@ import {
} from '../../types.js';
import { stopEventFromActivatingCardWideActions } from '../../utils/action.js';
import { contentsChanged } from '../../utils/basic.js';
import {
ConditionsManagerEpoch,
getOverriddenConfig,
} from '../../card-controller/conditions-manager.js';
import { CarouselSelected } from '../../utils/embla/carousel-controller.js';
import { AutoLazyLoad } from '../../utils/embla/plugins/auto-lazy-load/auto-lazy-load.js';
import { AutoMediaActions } from '../../utils/embla/plugins/auto-media-actions/auto-media-actions.js';
import AutoMediaLoadedInfo from '../../utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.js';
import AutoSize from '../../utils/embla/plugins/auto-size/auto-size.js';
import { MediaGridSelected } from '../../utils/media-grid-controller.js';
import {
dispatchExistingMediaLoadedInfoAsEvent,
dispatchMediaUnloadedEvent,
@@ -56,7 +54,7 @@ import { playMediaMutingIfNecessary } from '../../utils/media.js';
import { Timer } from '../../utils/timer.js';
import { dispatchViewContextChangeEvent, View } from '../../view/view.js';
import { EmblaCarouselPlugins } from '../carousel.js';
import { dispatchErrorMessageEvent, dispatchMessageEvent } from '../message.js';
import { renderMessage } from '../message.js';
import '../next-prev-control.js';
import '../surround.js';
import '../title-control.js';
@@ -65,6 +63,7 @@ import {
getDefaultTitleConfigForView,
showTitleControlAfterDelay,
} from '../title-control.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj.js';
interface LiveViewContext {
// A cameraID override (used for dependencies/substreams to force a different
@@ -85,44 +84,6 @@ interface LastMediaLoadedInfo {
const FRIGATE_CARD_LIVE_PROVIDER = 'frigate-card-live-provider';
/**
* Get the state object or dispatch an error. Used in `ha` and `image` live
* providers.
* @param element HTMLElement to dispatch errors from.
* @param hass Home Assistant object.
* @param cameraConfig Camera configuration.
* @returns
*/
export const getStateObjOrDispatchError = (
element: HTMLElement,
hass: HomeAssistant,
cameraConfig?: CameraConfig,
): HassEntity | null => {
if (!cameraConfig?.camera_entity) {
dispatchErrorMessageEvent(element, localize('error.no_live_camera'), {
context: cameraConfig,
});
return null;
}
const stateObj = hass.states[cameraConfig.camera_entity];
if (!stateObj) {
dispatchErrorMessageEvent(element, localize('error.live_camera_not_found'), {
context: cameraConfig,
});
return null;
}
if (stateObj.state === 'unavailable') {
dispatchMessageEvent(element, localize('error.live_camera_unavailable'), 'info', {
icon: 'mdi:connection',
context: cameraConfig,
});
return null;
}
return stateObj;
};
@customElement('frigate-card-live')
export class FrigateCardLive extends LitElement {
@property({ attribute: false })
@@ -977,6 +938,24 @@ export class FrigateCardLiveProvider
hidden: showImageDuringLoading,
};
if (provider === 'ha' || provider === 'image') {
const stateObj = getStateObjOrDispatchError(this, this.hass, this.cameraConfig);
if (!stateObj) {
return;
}
if (stateObj.state === 'unavailable') {
// An unavailable camera gets a message rendered in place vs dispatched,
// as this may be a common occurrence (e.g. Frigate cameras that stop
// receiving frames). Otherwise a single temporarily unavailable camera
// would render a whole carousel inoperable.
return renderMessage({
message: localize('error.live_camera_unavailable'),
type: 'error',
context: this.cameraConfig,
});
}
}
return this._useZoomIfRequired(html`
${showImageDuringLoading || provider === 'image'
? html` <frigate-card-live-image
+1 -1
View File
@@ -8,9 +8,9 @@ import {
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { MediaGridController } from '../components-lib/media-grid-controller.js';
import { ViewDisplayConfig } from '../config/types';
import mediaGridStyle from '../scss/media-grid.scss';
import { MediaGridController } from '../utils/media-grid-controller.js';
@customElement('frigate-card-media-grid')
export class FrigateCardMediaGrid extends LitElement {
+4 -1
View File
@@ -30,6 +30,10 @@ import { CameraManager } from '../camera-manager/manager';
import { rangesOverlap } from '../camera-manager/range';
import { MediaQuery } from '../camera-manager/types';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/util';
import {
FrigateCardTimelineItem,
TimelineDataSource,
} from '../components-lib/timeline-source';
import {
CameraConfig,
CardWideConfig,
@@ -54,7 +58,6 @@ import {
executeMediaQueryForViewWithErrorDispatching,
findBestMediaIndex,
} from '../utils/media-to-view';
import { FrigateCardTimelineItem, TimelineDataSource } from '../utils/timeline-source';
import { ViewMedia } from '../view/media';
import { ViewMediaClassifier } from '../view/media-classifier';
import {
+1 -1
View File
@@ -11,6 +11,7 @@ import { guard } from 'lit/directives/guard.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraManager } from '../camera-manager/manager.js';
import { MediaGridSelected } from '../components-lib/media-grid-controller.js';
import {
dispatchMessageEvent,
renderMessage,
@@ -47,7 +48,6 @@ import AutoMediaLoadedInfo from '../utils/embla/plugins/auto-media-loaded-info/a
import AutoSize from '../utils/embla/plugins/auto-size/auto-size.js';
import { canonicalizeHAURL } from '../utils/ha/index.js';
import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
import { MediaGridSelected } from '../utils/media-grid-controller.js';
import {
dispatchMediaLoadedEvent,
dispatchMediaPauseEvent,
+2 -2
View File
@@ -7,12 +7,12 @@ import {
TemplateResult,
} from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { ZoomController } from '../components-lib/zoom-controller.js';
import { setOrRemoveAttribute } from '../utils/basic.js';
import { Zoom } from '../utils/zoom/zoom.js';
@customElement('frigate-card-zoomer')
export class FrigateCardZoomer extends LitElement {
protected _zoom = new Zoom(this);
protected _zoom = new ZoomController(this);
@state()
protected _zoomed = false;