diff --git a/setup.py b/setup.py index 206985f0f5..92bfb26266 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20190312.0", + version="20190313.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", 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/dialogs/more-info/controls/more-info-camera.ts b/src/dialogs/more-info/controls/more-info-camera.ts index fc97b0b370..72ec2629cf 100644 --- a/src/dialogs/more-info/controls/more-info-camera.ts +++ b/src/dialogs/more-info/controls/more-info-camera.ts @@ -10,6 +10,7 @@ type HLSModule = typeof import("hls.js"); class MoreInfoCamera extends UpdatingElement { @property() public hass?: HomeAssistant; @property() public stateObj?: CameraEntity; + private _hlsPolyfillInstance?: Hls; public disconnectedCallback() { super.disconnectedCallback(); @@ -100,6 +101,7 @@ class MoreInfoCamera extends UpdatingElement { url: string ) { const hls = new Hls(); + this._hlsPolyfillInstance = hls; await new Promise((resolve) => { hls.on(Hls.Events.MEDIA_ATTACHED, resolve); hls.attachMedia(videoEl); @@ -123,9 +125,14 @@ class MoreInfoCamera extends UpdatingElement { } private _teardownPlayback(): any { + if (this._hlsPolyfillInstance) { + this._hlsPolyfillInstance.destroy(); + this._hlsPolyfillInstance = undefined; + } while (this.lastChild) { this.removeChild(this.lastChild); } + this.stateObj = undefined; } } diff --git a/src/panels/config/cloud/cloud-remote-pref.ts b/src/panels/config/cloud/cloud-remote-pref.ts index c364af1dd5..ee79d3f7db 100644 --- a/src/panels/config/cloud/cloud-remote-pref.ts +++ b/src/panels/config/cloud/cloud-remote-pref.ts @@ -47,6 +47,16 @@ export class CloudRemotePref extends LitElement { remote_certificate, } = this.cloudStatus; + if (!remote_certificate) { + return html` + +
+ Remote access is being prepared. We will notify you when it's ready. +
+
+ `; + } + return html`