From de7d412c2deb5d84c29db39db3f4659541b94ca0 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 8 Oct 2023 11:01:07 -0700 Subject: [PATCH] Add watermark to temporary image on live view. --- README.md | 4 ++-- src/components/live/live-image.ts | 36 ++++++++++++++++++------------- src/components/live/live.ts | 10 ++++----- src/components/title-control.ts | 24 --------------------- src/components/viewer.ts | 5 +---- src/scss/live-image.scss | 14 ++++++++++++ src/scss/title-control.scss | 15 +++++++++++-- 7 files changed, 56 insertions(+), 52 deletions(-) create mode 100644 src/scss/live-image.scss diff --git a/README.md b/README.md index 68209889..658322e4 100644 --- a/README.md +++ b/README.md @@ -4107,9 +4107,9 @@ See [screenshot above](#screenshots-card-casting). -### v4 doesn't show recordings / clips +### Small circular logo/watermark continually shown on livestream -You must be using a version of the [Frigate integration](https://github.com/blakeblackshear/frigate-hass-integration) >= 3.0.0-rc.2 to see recordings. Using an older version of the integration may also show blank thumbnails in the events viewer. Please upgrade your integration accordingly. +If the `live.show_image_during_load` option is enabled (the default), a temporary image from Home Assistant is rendered and refreshed every `1s` while the full stream is loading. When this temporary image is being shown, a small circular icon is rendered on the top-right of the livestream to indicate to the user that this is not the true stream. If the icon persists, it means your underlying stream is not actually loading and may be misconfigured / broken. ### `Forbidden media source identifier` diff --git a/src/components/live/live-image.ts b/src/components/live/live-image.ts index a4fe0a3c..7e2c8859 100644 --- a/src/components/live/live-image.ts +++ b/src/components/live/live-image.ts @@ -3,7 +3,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { CameraConfig } from '../../config/types'; -import basicBlockStyle from '../../scss/basic-block.scss'; +import liveImageStyle from '../../scss/live-image.scss'; import { FrigateCardMediaPlayer } from '../../types.js'; import '../image.js'; import { getStateObjOrDispatchError } from '../../utils/get-state-obj'; @@ -16,6 +16,9 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia @property({ attribute: false }) public cameraConfig?: CameraConfig; + @property({ attribute: true }) + public watermark?: string; + protected _refImage: Ref = createRef(); public async play(): Promise { @@ -61,24 +64,27 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia getStateObjOrDispatchError(this, this.hass, this.cameraConfig); - return html` - `; + // The live provider will take care of zoom and layout options. + zoomable: false, + }} + .hass=${this.hass} + .cameraConfig=${this.cameraConfig} + > + + ${this.watermark ? html`` : ''} + `; } static get styles(): CSSResultGroup { - return unsafeCSS(basicBlockStyle); + return unsafeCSS(liveImageStyle); } } diff --git a/src/components/live/live.ts b/src/components/live/live.ts index 283e1969..1056387b 100644 --- a/src/components/live/live.ts +++ b/src/components/live/live.ts @@ -51,7 +51,6 @@ import { } from '../../utils/media-info.js'; import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js'; import { playMediaMutingIfNecessary } from '../../utils/media.js'; -import { Timer } from '../../utils/timer.js'; import { dispatchViewContextChangeEvent, View } from '../../view/view.js'; import { EmblaCarouselPlugins } from '../carousel.js'; import { renderMessage } from '../message.js'; @@ -61,7 +60,6 @@ import '../title-control.js'; import { FrigateCardTitleControl, getDefaultTitleConfigForView, - showTitleControlAfterDelay, } from '../title-control.js'; import { getStateObjOrDispatchError } from '../../utils/get-state-obj.js'; @@ -388,7 +386,6 @@ export class FrigateCardLiveCarousel extends LitElement { // Index between camera name and slide number. protected _cameraToSlide: Record = {}; - protected _titleTimer = new Timer(); protected _refTitleControl: Ref = createRef(); protected _getTransitionEffect(): TransitionEffect { @@ -657,7 +654,7 @@ export class FrigateCardLiveCarousel extends LitElement { }} @frigate-card:media:loaded=${() => { if (this._refTitleControl.value) { - showTitleControlAfterDelay(this._refTitleControl.value, this._titleTimer); + this._refTitleControl.value.show(); } }} > @@ -698,7 +695,7 @@ export class FrigateCardLiveCarousel extends LitElement { .text="${cameraMetadataCurrent ? `${localize('common.live')}: ${cameraMetadataCurrent.title}` : ''}" - .logo="${cameraMetadataCurrent?.engineLogo}" + .logo="${cameraMetadataCurrent.engineLogo}" .fitInto=${this as HTMLElement} > ` @@ -965,6 +962,9 @@ export class FrigateCardLiveProvider ${ref(this._refProvider)} .hass=${this.hass} .cameraConfig=${this.cameraConfig} + watermark=${ifDefined( + showImageDuringLoading ? 'mdi:progress-helper' : undefined, + )} @frigate-card:media:loaded=${(ev: Event) => { if (provider === 'image') { // Only count the media has loaded if the required provider is diff --git a/src/components/title-control.ts b/src/components/title-control.ts index e93a1c6c..f472ab7f 100644 --- a/src/components/title-control.ts +++ b/src/components/title-control.ts @@ -3,36 +3,12 @@ import { customElement, property } from 'lit/decorators.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; import { TitleControlConfig } from '../config/types'; import titleStyle from '../scss/title-control.scss'; -import { Timer } from '../utils/timer'; import { View } from '../view/view.js'; type PaperToast = HTMLElement & { opened: boolean; }; -export const showTitleControlAfterDelay = ( - control: FrigateCardTitleControl, - timer: Timer, - delay = 0.5, -): void => { - const show = () => { - timer.stop(); - control.show(); - }; - - if (control.isVisible()) { - // If it's already visible, update it immediately (but also update it - // after the timer expires to ensure it re-positions if necessary, see - // comment below). - show(); - } - - // Allow a brief pause after the media loads, but before the title is - // displayed. This allows for a pleasant appearance/disappear of the title, - // and allows for the browser to finish rendering the carousel. - timer.start(delay, show); -}; - export const getDefaultTitleConfigForView = ( view?: Readonly, baseConfig?: TitleControlConfig, diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 6a217155..6a58edde 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -66,7 +66,6 @@ import { setControlsOnVideo, } from '../utils/media.js'; import { screenshotMedia } from '../utils/screenshot.js'; -import { Timer } from '../utils/timer'; import { ViewMediaClassifier } from '../view/media-classifier'; import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { MediaQueriesResults } from '../view/media-queries-results.js'; @@ -79,7 +78,6 @@ import './title-control.js'; import { FrigateCardTitleControl, getDefaultTitleConfigForView, - showTitleControlAfterDelay, } from './title-control.js'; export interface MediaViewerViewContext { @@ -227,7 +225,6 @@ export class FrigateCardViewerCarousel extends LitElement { protected _selected = 0; protected _media: ViewMedia[] | null = null; - protected _titleTimer = new Timer(); protected _refTitleControl: Ref = createRef(); protected _player: FrigateCardMediaPlayer | null = null; @@ -466,7 +463,7 @@ export class FrigateCardViewerCarousel extends LitElement { }} @frigate-card:media:loaded=${(ev: CustomEvent) => { if (this._refTitleControl.value) { - showTitleControlAfterDelay(this._refTitleControl.value, this._titleTimer); + this._refTitleControl.value.show(); } this._player = ev.detail.player ?? null; this._seekHandler(); diff --git a/src/scss/live-image.scss b/src/scss/live-image.scss new file mode 100644 index 00000000..cec0abb6 --- /dev/null +++ b/src/scss/live-image.scss @@ -0,0 +1,14 @@ +:host { + width: 100%; + height: 100%; + display: block; + position: relative; +} + +ha-icon { + position: absolute; + top: 10px; + right: 10px; + opacity: 50%; + color: white; +} diff --git a/src/scss/title-control.scss b/src/scss/title-control.scss index ca49513a..cf1e798c 100644 --- a/src/scss/title-control.scss +++ b/src/scss/title-control.scss @@ -1,8 +1,9 @@ :host { - --paper-toast-background-color: rgba(0,0,0,0.6); + --paper-toast-background-color: rgba(0, 0, 0, 0.6); --paper-toast-color: white; pointer-events: none; + position: relative; } paper-toast { @@ -10,9 +11,19 @@ paper-toast { min-width: unset; display: flex; align-items: center; + + // Without this the paper-toast will consume vertical space before being + // opened, which causes the card to render blank space needlessly. It also + // won't work with 'display: none', it appears to need something with + // width/height properties even before being opened. + position: absolute; +} + +paper-toast.paper-toast-open { + position: relative; } paper-toast img { max-height: 24px; padding-left: 10px; -} \ No newline at end of file +}