From bbc32278d8c6ee6744c36e00934a3a6fd351f7a9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Mar 2019 19:43:04 -0700 Subject: [PATCH] Cache thumbnails (#2924) --- src/cards/ha-media_player-card.js | 12 +++-- .../util/time-cache-function-promise.ts | 47 +++++++++++++++++++ src/data/camera.ts | 6 +++ src/data/media-player.ts | 31 ++++++++++++ src/panels/lovelace/components/hui-image.ts | 10 ++-- 5 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 src/common/util/time-cache-function-promise.ts diff --git a/src/cards/ha-media_player-card.js b/src/cards/ha-media_player-card.js index 6bbda8eab6..cb068c3afe 100644 --- a/src/cards/ha-media_player-card.js +++ b/src/cards/ha-media_player-card.js @@ -6,6 +6,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import HassMediaPlayerEntity from "../util/hass-media-player-model"; +import { fetchMediaPlayerThumbnailWithCache } from "../data/media-player"; import computeStateName from "../common/entity/compute_state_name"; import EventsMixin from "../mixins/events-mixin"; @@ -271,10 +272,13 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) { // We have a new picture url try { - const { content_type: contentType, content } = await this.hass.callWS({ - type: "media_player_thumbnail", - entity_id: playerObj.stateObj.entity_id, - }); + const { + content_type: contentType, + content, + } = await fetchMediaPlayerThumbnailWithCache( + this.hass, + playerObj.stateObj.entity_id + ); this._coverShowing = true; this._coverLoadError = false; this.$.cover.style.backgroundImage = `url(data:${contentType};base64,${content})`; diff --git a/src/common/util/time-cache-function-promise.ts b/src/common/util/time-cache-function-promise.ts new file mode 100644 index 0000000000..8dc8abcd3f --- /dev/null +++ b/src/common/util/time-cache-function-promise.ts @@ -0,0 +1,47 @@ +import { HomeAssistant } from "../../types"; + +interface ResultCache { + [entityId: string]: Promise | undefined; +} + +export const timeCachePromiseFunc = async ( + cacheKey: string, + cacheTime: number, + func: ( + hass: HomeAssistant, + entityId: string, + ...args: Array + ) => Promise, + hass: HomeAssistant, + entityId: string, + ...args: Array +): Promise => { + let cache: ResultCache | undefined = (hass as any)[cacheKey]; + + if (!cache) { + cache = hass[cacheKey] = {}; + } + + const lastResult = cache[entityId]; + + if (lastResult) { + return lastResult; + } + + const result = func(hass, entityId, ...args); + cache[entityId] = result; + + result.then( + // When successful, set timer to clear cache + () => + setTimeout(() => { + cache![entityId] = undefined; + }, cacheTime), + // On failure, clear cache right away + () => { + cache![entityId] = undefined; + } + ); + + return result; +}; diff --git a/src/data/camera.ts b/src/data/camera.ts index 64eec22d15..62f94096e4 100644 --- a/src/data/camera.ts +++ b/src/data/camera.ts @@ -1,4 +1,5 @@ import { HomeAssistant, CameraEntity } from "../types"; +import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; export interface CameraThumbnail { content_type: string; @@ -14,6 +15,11 @@ export const computeMJPEGStreamUrl = (entity: CameraEntity) => entity.attributes.access_token }`; +export const fetchThumbnailWithCache = ( + hass: HomeAssistant, + entityId: string +) => timeCachePromiseFunc("_cameraTmb", 9000, fetchThumbnail, hass, entityId); + export const fetchThumbnail = (hass: HomeAssistant, entityId: string) => hass.callWS({ type: "camera_thumbnail", diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 7d89d4d544..3aaf16e093 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -1,4 +1,35 @@ +import { HomeAssistant } from "../types"; + +import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; + export const SUPPORT_PAUSE = 1; export const SUPPORT_NEXT_TRACK = 32; export const SUPPORTS_PLAY = 16384; export const OFF_STATES = ["off", "idle"]; + +export interface MediaPlayerThumbnail { + content_type: string; + content: string; +} + +export const fetchMediaPlayerThumbnailWithCache = ( + hass: HomeAssistant, + entityId: string +) => + timeCachePromiseFunc( + "_media_playerTmb", + 9000, + fetchMediaPlayerThumbnail, + hass, + entityId + ); + +export const fetchMediaPlayerThumbnail = ( + hass: HomeAssistant, + entityId: string +) => { + return hass.callWS({ + type: "media_player_thumbnail", + entity_id: entityId, + }); +}; diff --git a/src/panels/lovelace/components/hui-image.ts b/src/panels/lovelace/components/hui-image.ts index 213220772d..7f9bd87e95 100644 --- a/src/panels/lovelace/components/hui-image.ts +++ b/src/panels/lovelace/components/hui-image.ts @@ -18,7 +18,7 @@ import { HomeAssistant } from "../../../types"; import { styleMap } from "lit-html/directives/style-map"; import { classMap } from "lit-html/directives/class-map"; import { b64toBlob } from "../../../common/file/b64-to-blob"; -import { fetchThumbnail } from "../../../data/camera"; +import { fetchThumbnailWithCache } from "../../../data/camera"; const UPDATE_INTERVAL = 10000; const DEFAULT_FILTER = "grayscale(100%)"; @@ -179,10 +179,10 @@ class HuiImage extends LitElement { return; } try { - const { content_type: contentType, content } = await fetchThumbnail( - this.hass, - this.cameraImage - ); + const { + content_type: contentType, + content, + } = await fetchThumbnailWithCache(this.hass, this.cameraImage); if (this._cameraImageSrc) { URL.revokeObjectURL(this._cameraImageSrc); }