feat: Add hardened error handling and retries (#2451)

- Closes #1830
 - Closes #2099
This commit is contained in:
Dermot Duffy
2026-06-30 17:45:12 -07:00
committed by dermotduffy
parent 4bc787e2b7
commit 47bcce93d3
182 changed files with 7877 additions and 4043 deletions
+23 -19
View File
@@ -8,6 +8,7 @@ import {
} from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { guard } from 'lit/directives/guard.js';
import { keyed } from 'lit/directives/keyed.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraManager } from '../../camera-manager/manager.js';
import { RemoveContextPropertyViewModifier } from '../../card-controller/view/modifiers/remove-context-property.js';
@@ -32,8 +33,8 @@ import { ViewItemClassifier } from '../../view/item-classifier.js';
import { ViewMedia } from '../../view/item.js';
import '../carousel';
import type { EmblaCarouselPlugins } from '../carousel.js';
import { renderMessage } from '../message.js';
import '../next-prev-control.js';
import { renderNoMedia } from '../notification/no-media.js';
import '../ptz.js';
import './provider.js';
@@ -314,15 +315,12 @@ export class AdvancedCameraCardViewerCarousel extends LitElement {
protected render(): TemplateResult | void {
const mediaCount = this._media?.length ?? 0;
if (!this._media || !mediaCount) {
return renderMessage({
message: localize('common.no_media'),
type: 'info',
icon: 'mdi:multimedia',
...(this.viewFilterCameraID && {
context: {
camera_id: this.viewFilterCameraID,
},
}),
return renderNoMedia({
cameraID:
this.viewFilterCameraID ??
this.viewManagerEpoch?.manager.getView()?.camera ??
null,
cameraManager: this.cameraManager ?? null,
});
}
@@ -464,16 +462,22 @@ export class AdvancedCameraCardViewerCarousel extends LitElement {
return null;
}
const mediaID = media.getID();
const mediaEpoch = mediaID ? view.context?.mediaEpoch?.[mediaID] ?? 0 : 0;
return html` <div class="embla__slide">
<advanced-camera-card-viewer-provider
.hass=${this.hass}
.viewManagerEpoch=${this.viewManagerEpoch}
.media=${media}
.viewerConfig=${this.viewerConfig}
.resolvedMediaCache=${this.resolvedMediaCache}
.cameraManager=${this.cameraManager}
.cardWideConfig=${this.cardWideConfig}
></advanced-camera-card-viewer-provider>
${keyed(
mediaEpoch,
html`<advanced-camera-card-viewer-provider
.hass=${this.hass}
.viewManagerEpoch=${this.viewManagerEpoch}
.media=${media}
.viewerConfig=${this.viewerConfig}
.resolvedMediaCache=${this.resolvedMediaCache}
.cameraManager=${this.cameraManager}
.cardWideConfig=${this.cardWideConfig}
></advanced-camera-card-viewer-provider>`,
)}
</div>`;
}
+5 -11
View File
@@ -14,11 +14,10 @@ import { CardWideConfig } from '../../config/schema/types.js';
import { ViewerConfig } from '../../config/schema/viewer.js';
import { ResolvedMediaCache } from '../../ha/resolved-media.js';
import { HomeAssistant } from '../../ha/types.js';
import { localize } from '../../localize/localize.js';
import '../../patches/ha-hls-player.js';
import viewerStyle from '../../scss/viewer.scss';
import { ViewItemClassifier } from '../../view/item-classifier.js';
import { renderMessage } from '../message.js';
import { renderNoMedia } from '../notification/no-media.js';
import './grid';
export interface MediaViewerViewContext {
@@ -81,15 +80,10 @@ export class AdvancedCameraCardViewer extends LitElement {
// Directly render an error message (instead of dispatching it upwards)
// to preserve the mini-timeline if the user pans into an area with no
// media.
const loadingMedia =
!!this.viewManagerEpoch.manager.getView()?.context?.loading?.query;
return renderMessage({
type: 'info',
message: loadingMedia
? localize('error.awaiting_media')
: localize('common.no_media'),
icon: 'mdi:multimedia',
dotdotdot: loadingMedia,
return renderNoMedia({
cameraID: this.viewManagerEpoch.manager.getView()?.camera ?? null,
cameraManager: this.cameraManager ?? null,
loading: !!this.viewManagerEpoch.manager.getView()?.context?.loading?.query,
});
}
+8 -8
View File
@@ -31,7 +31,7 @@ import { ViewItemClassifier } from '../../view/item-classifier.js';
import { VideoContentType, ViewMedia } from '../../view/item.js';
import { UnifiedQueryTransformer } from '../../view/unified-query-transformer.js';
import '../image-player.js';
import { renderMessage } from '../message.js';
import { renderNotificationBlockFromText } from '../notification/block.js';
import { renderProgressIndicator } from '../progress-indicator.js';
import '../video-player.js';
import './../media-dimensions-container';
@@ -233,13 +233,13 @@ export class AdvancedCameraCardViewerProvider extends LitElement implements Medi
const error = this._signedURLController.getError();
if (error) {
return renderMessage({
type: 'error',
message: localize(
error === 'proxy' ? 'error.failed_proxy' : 'error.failed_sign',
),
context: this.media?.getContentID(),
});
const contentID = this.media?.getContentID();
return renderNotificationBlockFromText(
localize(error === 'proxy' ? 'error.failed_proxy' : 'error.failed_sign'),
{
...(contentID && { metadata: [{ text: contentID, icon: 'mdi:identifier' }] }),
},
);
}
const url = this._signedURLController.getValue();