feat: Add hardened error handling and retries (#2451)
- Closes #1830 - Closes #2099
This commit is contained in:
committed by
dermotduffy
parent
4bc787e2b7
commit
47bcce93d3
@@ -31,7 +31,7 @@ import { fireAdvancedCameraCardEvent } from '../../utils/fire-advanced-camera-ca
|
||||
import { getResolvedLiveProvider } from '../../utils/live-provider.js';
|
||||
import { dispatchMediaUnloadedEvent } from '../../utils/media-info.js';
|
||||
import '../icon.js';
|
||||
import { renderMessage } from '../message.js';
|
||||
import { renderNotificationBlockFromText } from '../notification/block.js';
|
||||
import './../media-dimensions-container';
|
||||
|
||||
@customElement('advanced-camera-card-live-provider')
|
||||
@@ -73,6 +73,12 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
@state()
|
||||
private _hasProviderError = false;
|
||||
|
||||
// Whether the camera entity has ever been in a non-unavailable state. Used
|
||||
// to suppress transient unavailability errors for entities that were
|
||||
// previously working (e.g. during PTZ operations).
|
||||
// See: https://github.com/dermotduffy/advanced-camera-card/issues/2124
|
||||
private _entityHasBeenAvailable = false;
|
||||
|
||||
private _refProvider: Ref<MediaPlayerElement> = createRef();
|
||||
|
||||
private _lazyLoadController: LazyLoadController = new LazyLoadController(this);
|
||||
@@ -121,6 +127,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
this._isVideoMediaLoaded = false;
|
||||
this._entityHasBeenAvailable = false;
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@@ -132,11 +139,12 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
ev.stopPropagation();
|
||||
this._hasProviderError = true;
|
||||
|
||||
const cameraID = this.camera?.getID();
|
||||
if (cameraID) {
|
||||
fireAdvancedCameraCardEvent(this, 'problem:trigger', {
|
||||
key: 'stream_not_loading' as const,
|
||||
cameraID,
|
||||
// this.camera is already substream-aware (resolved by the carousel layer).
|
||||
const targetID = this.camera?.getID();
|
||||
if (targetID) {
|
||||
fireAdvancedCameraCardEvent(this, 'issue:trigger', {
|
||||
key: 'media_load' as const,
|
||||
targetID,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -162,6 +170,10 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
}
|
||||
|
||||
if (changedProps.has('camera')) {
|
||||
this._isVideoMediaLoaded = false;
|
||||
this._hasProviderError = false;
|
||||
this._entityHasBeenAvailable = false;
|
||||
|
||||
const provider = getResolvedLiveProvider(this.camera?.getConfig());
|
||||
if (provider === 'jsmpeg') {
|
||||
this._importPromises.push(import('./providers/jsmpeg.js'));
|
||||
@@ -256,9 +268,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
) {
|
||||
if (!cameraConfig?.camera_entity) {
|
||||
dispatchLiveErrorEvent(this);
|
||||
return renderMessage({
|
||||
message: localize('error.no_live_camera'),
|
||||
type: 'error',
|
||||
return renderNotificationBlockFromText(localize('error.no_live_camera'), {
|
||||
icon: 'mdi:camera',
|
||||
context: cameraConfig,
|
||||
});
|
||||
@@ -267,25 +277,28 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
const stateObj = this.hass.states[cameraConfig.camera_entity];
|
||||
if (!stateObj) {
|
||||
dispatchLiveErrorEvent(this);
|
||||
return renderMessage({
|
||||
message: localize('error.live_camera_not_found'),
|
||||
type: 'error',
|
||||
return renderNotificationBlockFromText(localize('error.live_camera_not_found'), {
|
||||
icon: 'mdi:camera',
|
||||
context: cameraConfig,
|
||||
});
|
||||
}
|
||||
|
||||
if (stateObj.state === 'unavailable') {
|
||||
dispatchLiveErrorEvent(this);
|
||||
dispatchMediaUnloadedEvent(this);
|
||||
return renderMessage({
|
||||
message: `${localize('error.live_camera_unavailable')}${
|
||||
this.label ? `: ${this.label}` : ''
|
||||
}`,
|
||||
type: 'info',
|
||||
icon: 'mdi:cctv-off',
|
||||
dotdotdot: true,
|
||||
});
|
||||
if (
|
||||
!this._entityHasBeenAvailable ||
|
||||
cameraConfig.always_error_if_entity_unavailable
|
||||
) {
|
||||
dispatchLiveErrorEvent(this);
|
||||
dispatchMediaUnloadedEvent(this);
|
||||
return renderNotificationBlockFromText(
|
||||
`${localize('error.live_camera_unavailable')}${
|
||||
this.label ? `: ${this.label}` : ''
|
||||
}`,
|
||||
{ icon: 'mdi:cctv-off', in_progress: true },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._entityHasBeenAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,8 +386,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
? html`<advanced-camera-card-icon
|
||||
title=${localize('error.awaiting_live')}
|
||||
.icon=${{ icon: 'mdi:progress-helper' }}
|
||||
@click=${() =>
|
||||
fireAdvancedCameraCardEvent(this, 'problem:notify', 'stream_not_loading')}
|
||||
@click=${() => fireAdvancedCameraCardEvent(this, 'issue:notify', 'media_load')}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user