Split async/sync parts of viewer.

This commit is contained in:
Dermot Duffy
2021-10-25 00:48:28 -07:00
parent 54a9b4fca9
commit c45c2ec973
4 changed files with 181 additions and 78 deletions
+5
View File
@@ -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}
+119 -78
View File
@@ -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<ResolvedMedia | null> {
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<TemplateResult | void> {
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`
<frigate-card-viewer-core
.view=${view}
.nextPreviousControlStyle=${this.nextPreviousControlStyle}
.resolvedMediaCache=${this.resolvedMediaCache}
.autoplayClip=${autoplay}
.hass=${this.hass}
.browseMediaQueryParameters=${this.browseMediaQueryParameters}
>
</frigate-card-viewer-core>`;
}
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<BrowseMediaSource | null> {
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<TemplateResult | void> {
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` <div>
${neighbors?.previousIndex != null
? html`<frigate-card-next-previous-control
.control=${'previous'}
.controlStyle=${this.nextPreviousControlStyle}
.parent=${parent}
.parent=${this.view.target}
.childIndex=${neighbors.previousIndex}
.view=${this.view}
></frigate-card-next-previous-control>`
@@ -222,7 +263,7 @@ export class FrigateCardViewer extends LitElement {
controls
playsinline
allow-exoplayer
?autoplay="${autoplay}"
?autoplay="${this.autoplayClip}"
>
</frigate-card-ha-hls-player>`
: html`<video
@@ -230,7 +271,7 @@ export class FrigateCardViewer extends LitElement {
muted
controls
playsinline
?autoplay="${autoplay}"
?autoplay="${this.autoplayClip}"
@loadedmetadata=${(e) => dispatchMediaLoadEvent(this, e)}a
@play=${() => dispatchPlayEvent(this)}
@pause=${() => dispatchPauseEvent(this)}
@@ -259,7 +300,7 @@ export class FrigateCardViewer extends LitElement {
? html`<frigate-card-next-previous-control
.control=${'next'}
.controlStyle=${this.nextPreviousControlStyle}
.parent=${parent}
.parent=${this.view.target}
.childIndex=${neighbors.nextIndex}
.view=${this.view}
></frigate-card-next-previous-control>`
+57
View File
@@ -0,0 +1,57 @@
import { HomeAssistant } from 'custom-card-helpers';
import { homeAssistantWSRequest } from './common.js';
import {
BrowseMediaSource,
ExtendedHomeAssistant,
ResolvedMedia,
resolvedMediaSchema,
} from './types.js';
export class ResolvedMediaCache {
protected _cache: Record<string, ResolvedMedia>;
constructor() {
this._cache = {};
}
public has(id: string): boolean {
return id in this._cache;
}
public get(id: string): ResolvedMedia | undefined {
return this._cache[id];
}
public set(id: string, resolvedMedia: ResolvedMedia): void {
this._cache[id] = resolvedMedia;
}
}
export class ResolvedMediaUtil {
static async resolveMedia(
hass: HomeAssistant & ExtendedHomeAssistant,
mediaSource?: BrowseMediaSource,
cache?: ResolvedMediaCache,
): Promise<ResolvedMedia | null> {
if (!mediaSource) {
return null;
}
const cachedValue = cache ? cache.get(mediaSource.media_content_id) : undefined;
if (cachedValue) {
return cachedValue;
}
const request = {
type: 'media_source/resolve_media',
media_content_id: mediaSource.media_content_id,
};
const resolvedMedia = await homeAssistantWSRequest(
hass,
resolvedMediaSchema,
request,
);
if (cache && resolvedMedia) {
cache.set(mediaSource.media_content_id, resolvedMedia);
}
return resolvedMedia;
}
}