From c45c2ec973d2750a6e95920c88f926c7ae649638 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 25 Oct 2021 00:48:28 -0700 Subject: [PATCH] Split async/sync parts of viewer. --- .prettierrc.js => .prettierrc.cjs | 0 src/card.ts | 5 + src/components/viewer.ts | 197 ++++++++++++++++++------------ src/resolved-media.ts | 57 +++++++++ 4 files changed, 181 insertions(+), 78 deletions(-) rename .prettierrc.js => .prettierrc.cjs (100%) create mode 100644 src/resolved-media.ts diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/src/card.ts b/src/card.ts index b736ece6..0c0cc169 100644 --- a/src/card.ts +++ b/src/card.ts @@ -51,6 +51,7 @@ import './patches/ha-camera-stream.js'; import './patches/ha-hls-player.js'; import cardStyle from './scss/card.scss'; +import { ResolvedMediaCache } from './resolved-media.js'; const MEDIA_HEIGHT_CUTOFF = 50; const MEDIA_WIDTH_CUTOFF = MEDIA_HEIGHT_CUTOFF; @@ -133,6 +134,9 @@ export class FrigateCard extends LitElement { // Error/info message to render. protected _message: Message | null = null; + // A cache of resolved media URLs/mimetypes for use in the whole card. + protected _resolvedMediaCache = new ResolvedMediaCache(); + set hass(hass: HomeAssistant & ExtendedHomeAssistant) { this._hass = hass; @@ -724,6 +728,7 @@ export class FrigateCard extends LitElement { .browseMediaQueryParameters=${mediaQueryParameters} .nextPreviousControlStyle=${this.config.controls?.nextprev ?? 'thumbnails'} .autoplayClip=${this.config.autoplay_clip} + .resolvedMediaCache=${this._resolvedMediaCache} class="${classMap(viewerClasses)}" @frigate-card:change-view=${this._changeViewHandler} @frigate-card:media-load=${this._mediaLoadHandler} diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 0cceb894..ce75298f 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -6,14 +6,12 @@ import { HomeAssistant } from 'custom-card-helpers'; import dayjs from 'dayjs'; import dayjs_custom_parse_format from 'dayjs/plugin/customParseFormat.js'; -import { resolvedMediaSchema } from '../types.js'; import type { BrowseMediaNeighbors, BrowseMediaQueryParameters, BrowseMediaSource, ExtendedHomeAssistant, NextPreviousControlStyle, - ResolvedMedia, } from '../types.js'; import { localize } from '../localize/localize.js'; import { @@ -24,7 +22,6 @@ import { dispatchPauseEvent, dispatchPlayEvent, getFirstTrueMediaChildIndex, - homeAssistantWSRequest, } from '../common.js'; import { View } from '../view.js'; @@ -35,6 +32,7 @@ import { import './next-prev-control.js'; import viewerStyle from '../scss/viewer.scss'; +import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js'; // Load dayjs plugin(s). dayjs.extend(dayjs_custom_parse_format); @@ -42,33 +40,111 @@ dayjs.extend(dayjs_custom_parse_format); @customElement('frigate-card-viewer') export class FrigateCardViewer extends LitElement { @property({ attribute: false }) - protected hass!: HomeAssistant & ExtendedHomeAssistant; + protected hass?: HomeAssistant & ExtendedHomeAssistant; @property({ attribute: false }) - protected view!: View; + protected view?: View; @property({ attribute: false }) - protected browseMediaQueryParameters!: BrowseMediaQueryParameters; + protected browseMediaQueryParameters?: BrowseMediaQueryParameters; @property({ attribute: false }) - protected nextPreviousControlStyle!: NextPreviousControlStyle; + protected nextPreviousControlStyle?: NextPreviousControlStyle; @property({ attribute: false }) - protected autoplayClip!: boolean; + protected autoplayClip?: boolean; - protected async _resolveMedia( - mediaSource: BrowseMediaSource | null, - ): Promise { - if (!mediaSource) { - return null; - } - const request = { - type: 'media_source/resolve_media', - media_content_id: mediaSource.media_content_id, - }; - return homeAssistantWSRequest(this.hass, resolvedMediaSchema, request); + @property({ attribute: false }) + protected resolvedMediaCache?: ResolvedMediaCache; + + protected render(): TemplateResult | void { + return html`${until(this._render(), renderProgressIndicator())}`; } + protected async _render(): Promise { + if (!this.hass || !this.view || !this.browseMediaQueryParameters) { + return html``; + } + + let autoplay = true; + let view = this.view; + + if (!view.target) { + let parent: BrowseMediaSource | null = null; + try { + parent = await browseMediaQuery(this.hass, this.browseMediaQueryParameters); + } catch (e) { + return dispatchErrorMessageEvent(this, (e as Error).message); + } + const childIndex = getFirstTrueMediaChildIndex(parent); + if (!parent || !parent.children || childIndex == null) { + return dispatchMessageEvent( + this, + this.view.is('clip') + ? localize('common.no_clip') + : localize('common.no_snapshot'), + this.view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off', + ); + } + view = new View({ + view: this.view.view, + target: parent, + childIndex: childIndex, + }) + + // In this block, no clip has been manually selected, so this is loading + // the most recent clip on card load. In this mode, autoplay of the clip + // may be disabled by configuration. If does not make sense to disable + // autoplay when the user has explicitly picked an event to play in the + // gallery. + autoplay = this.autoplayClip ?? true; + } + + const resolvedMedia = await ResolvedMediaUtil.resolveMedia( + this.hass, view.media, this.resolvedMediaCache); + if (!resolvedMedia) { + // Home Assistant could not resolve media item. + return dispatchErrorMessageEvent(this, localize('error.could_not_resolve')); + } + + return html` + + `; + } + + static get styles(): CSSResultGroup { + return unsafeCSS(viewerStyle); + } +} + + +@customElement('frigate-card-viewer-core') +export class FrigateCardViewerCore extends LitElement { + @property({ attribute: false }) + protected view?: View; + + @property({ attribute: false }) + protected nextPreviousControlStyle?: NextPreviousControlStyle; + + @property({ attribute: false }) + protected resolvedMediaCache?: ResolvedMediaCache; + + @property({ attribute: false }) + protected autoplayClip?: boolean; + + @property({ attribute: false }) + protected hass?: HomeAssistant & ExtendedHomeAssistant; + + @property({ attribute: false }) + protected browseMediaQueryParameters?: BrowseMediaQueryParameters; + protected _extractEventStartTimeFromBrowseMedia( browseMedia: BrowseMediaSource, ): number | null { @@ -87,18 +163,18 @@ export class FrigateCardViewer extends LitElement { } // Get the previous and next real media items, given the index - protected _getMediaNeighbors( - parent: BrowseMediaSource, - index: number | null, - ): BrowseMediaNeighbors | null { - if (index == null || !parent.children) { + protected _getMediaNeighbors() : BrowseMediaNeighbors | null { + if (!this.view || + !this.view.target || + !this.view.target.children || + this.view.childIndex === undefined) { return null; } // Work backwards from the index to get the previous real media. let prevIndex: number | null = null; - for (let i = index - 1; i >= 0; i--) { - const media = parent.children[i]; + for (let i = this.view.childIndex - 1; i >= 0; i--) { + const media = this.view.target.children[i]; if (media && !media.can_expand) { prevIndex = i; break; @@ -107,8 +183,8 @@ export class FrigateCardViewer extends LitElement { // Work forwards from the index to get the next real media. let nextIndex: number | null = null; - for (let i = index + 1; i < parent.children.length; i++) { - const media = parent.children[i]; + for (let i = this.view.childIndex + 1; i < this.view.target.children.length; i++) { + const media = this.view.target.children[i]; if (media && !media.can_expand) { nextIndex = i; break; @@ -117,9 +193,9 @@ export class FrigateCardViewer extends LitElement { return { previousIndex: prevIndex, - previous: prevIndex != null ? parent.children[prevIndex] : null, + previous: prevIndex != null ? this.view.target.children[prevIndex] : null, nextIndex: nextIndex, - next: nextIndex != null ? parent.children[nextIndex] : null, + next: nextIndex != null ? this.view.target.children[nextIndex] : null, }; } @@ -127,7 +203,7 @@ export class FrigateCardViewer extends LitElement { protected async _findRelatedClips( snapshot: BrowseMediaSource | null, ): Promise { - if (!snapshot) { + if (!snapshot || !this.hass || !this.browseMediaQueryParameters) { return null; } @@ -155,59 +231,24 @@ export class FrigateCardViewer extends LitElement { } protected render(): TemplateResult | void { - return html`${until(this._render(), renderProgressIndicator())}`; - } - - protected async _render(): Promise { - let autoplay = true; - - let parent: BrowseMediaSource | null = null; - let childIndex: number | null = null; - let mediaToRender: BrowseMediaSource | null = null; - - if (this.view.target) { - parent = this.view.target; - childIndex = this.view.childIndex ?? null; - mediaToRender = this.view.media ?? null; - } else { - try { - parent = await browseMediaQuery(this.hass, this.browseMediaQueryParameters); - } catch (e) { - return dispatchErrorMessageEvent(this, (e as Error).message); - } - childIndex = getFirstTrueMediaChildIndex(parent); - if (!parent || !parent.children || childIndex == null) { - return dispatchMessageEvent( - this, - this.view.is('clip') - ? localize('common.no_clip') - : localize('common.no_snapshot'), - this.view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off', - ); - } - mediaToRender = parent.children[childIndex]; - - // In this block, no clip has been manually selected, so this is loading - // the most recent clip on card load. In this mode, autoplay of the clip - // may be disabled by configuration. If does not make sense to disable - // autoplay when the user has explicitly picked an event to play in the - // gallery. - autoplay = this.autoplayClip; + if (!this.view || !this.resolvedMediaCache) { + return html``; } - const resolvedMedia = await this._resolveMedia(mediaToRender); + + const mediaToRender = this.view.media; + const resolvedMedia = mediaToRender ? this.resolvedMediaCache.get( + mediaToRender.media_content_id) : undefined; if (!mediaToRender || !resolvedMedia) { - // Home Assistant could not resolve media item. - return dispatchErrorMessageEvent(this, localize('error.could_not_resolve')); + return html``; } - const neighbors = this._getMediaNeighbors(parent, childIndex); - + const neighbors = this._getMediaNeighbors(); return html`
${neighbors?.previousIndex != null ? html`` @@ -222,7 +263,7 @@ export class FrigateCardViewer extends LitElement { controls playsinline allow-exoplayer - ?autoplay="${autoplay}" + ?autoplay="${this.autoplayClip}" > ` : html`