Add watermark to temporary image on live view.

This commit is contained in:
Dermot Duffy
2023-10-08 11:01:07 -07:00
parent 04aef8a449
commit de7d412c2d
7 changed files with 56 additions and 52 deletions
+2 -2
View File
@@ -4107,9 +4107,9 @@ See [screenshot above](#screenshots-card-casting).
<a name="v4-troubleshooting"></a> <a name="v4-troubleshooting"></a>
### 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` ### `Forbidden media source identifier`
+21 -15
View File
@@ -3,7 +3,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { CameraConfig } from '../../config/types'; 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 { FrigateCardMediaPlayer } from '../../types.js';
import '../image.js'; import '../image.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj'; import { getStateObjOrDispatchError } from '../../utils/get-state-obj';
@@ -16,6 +16,9 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
@property({ attribute: false }) @property({ attribute: false })
public cameraConfig?: CameraConfig; public cameraConfig?: CameraConfig;
@property({ attribute: true })
public watermark?: string;
protected _refImage: Ref<Element & FrigateCardMediaPlayer> = createRef(); protected _refImage: Ref<Element & FrigateCardMediaPlayer> = createRef();
public async play(): Promise<void> { public async play(): Promise<void> {
@@ -61,24 +64,27 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
getStateObjOrDispatchError(this, this.hass, this.cameraConfig); getStateObjOrDispatchError(this, this.hass, this.cameraConfig);
return html` <frigate-card-image return html`
${ref(this._refImage)} <frigate-card-image
.imageConfig=${{ ${ref(this._refImage)}
mode: this.cameraConfig.image.url ? ('url' as const) : ('camera' as const), .imageConfig=${{
refresh_seconds: this.cameraConfig.image.refresh_seconds, mode: this.cameraConfig.image.url ? ('url' as const) : ('camera' as const),
url: this.cameraConfig.image.url, refresh_seconds: this.cameraConfig.image.refresh_seconds,
url: this.cameraConfig.image.url,
// The live provider will take care of zoom and layout options. // The live provider will take care of zoom and layout options.
zoomable: false, zoomable: false,
}} }}
.hass=${this.hass} .hass=${this.hass}
.cameraConfig=${this.cameraConfig} .cameraConfig=${this.cameraConfig}
> >
</frigate-card-image>`; </frigate-card-image>
${this.watermark ? html`<ha-icon icon="${this.watermark}"></ha-icon>` : ''}
`;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return unsafeCSS(basicBlockStyle); return unsafeCSS(liveImageStyle);
} }
} }
+5 -5
View File
@@ -51,7 +51,6 @@ import {
} from '../../utils/media-info.js'; } from '../../utils/media-info.js';
import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js'; import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js';
import { playMediaMutingIfNecessary } from '../../utils/media.js'; import { playMediaMutingIfNecessary } from '../../utils/media.js';
import { Timer } from '../../utils/timer.js';
import { dispatchViewContextChangeEvent, View } from '../../view/view.js'; import { dispatchViewContextChangeEvent, View } from '../../view/view.js';
import { EmblaCarouselPlugins } from '../carousel.js'; import { EmblaCarouselPlugins } from '../carousel.js';
import { renderMessage } from '../message.js'; import { renderMessage } from '../message.js';
@@ -61,7 +60,6 @@ import '../title-control.js';
import { import {
FrigateCardTitleControl, FrigateCardTitleControl,
getDefaultTitleConfigForView, getDefaultTitleConfigForView,
showTitleControlAfterDelay,
} from '../title-control.js'; } from '../title-control.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj.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. // Index between camera name and slide number.
protected _cameraToSlide: Record<string, number> = {}; protected _cameraToSlide: Record<string, number> = {};
protected _titleTimer = new Timer();
protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef(); protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef();
protected _getTransitionEffect(): TransitionEffect { protected _getTransitionEffect(): TransitionEffect {
@@ -657,7 +654,7 @@ export class FrigateCardLiveCarousel extends LitElement {
}} }}
@frigate-card:media:loaded=${() => { @frigate-card:media:loaded=${() => {
if (this._refTitleControl.value) { 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 .text="${cameraMetadataCurrent
? `${localize('common.live')}: ${cameraMetadataCurrent.title}` ? `${localize('common.live')}: ${cameraMetadataCurrent.title}`
: ''}" : ''}"
.logo="${cameraMetadataCurrent?.engineLogo}" .logo="${cameraMetadataCurrent.engineLogo}"
.fitInto=${this as HTMLElement} .fitInto=${this as HTMLElement}
> >
</frigate-card-title-control> ` </frigate-card-title-control> `
@@ -965,6 +962,9 @@ export class FrigateCardLiveProvider
${ref(this._refProvider)} ${ref(this._refProvider)}
.hass=${this.hass} .hass=${this.hass}
.cameraConfig=${this.cameraConfig} .cameraConfig=${this.cameraConfig}
watermark=${ifDefined(
showImageDuringLoading ? 'mdi:progress-helper' : undefined,
)}
@frigate-card:media:loaded=${(ev: Event) => { @frigate-card:media:loaded=${(ev: Event) => {
if (provider === 'image') { if (provider === 'image') {
// Only count the media has loaded if the required provider is // Only count the media has loaded if the required provider is
-24
View File
@@ -3,36 +3,12 @@ import { customElement, property } from 'lit/decorators.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { TitleControlConfig } from '../config/types'; import { TitleControlConfig } from '../config/types';
import titleStyle from '../scss/title-control.scss'; import titleStyle from '../scss/title-control.scss';
import { Timer } from '../utils/timer';
import { View } from '../view/view.js'; import { View } from '../view/view.js';
type PaperToast = HTMLElement & { type PaperToast = HTMLElement & {
opened: boolean; 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 = ( export const getDefaultTitleConfigForView = (
view?: Readonly<View>, view?: Readonly<View>,
baseConfig?: TitleControlConfig, baseConfig?: TitleControlConfig,
+1 -4
View File
@@ -66,7 +66,6 @@ import {
setControlsOnVideo, setControlsOnVideo,
} from '../utils/media.js'; } from '../utils/media.js';
import { screenshotMedia } from '../utils/screenshot.js'; import { screenshotMedia } from '../utils/screenshot.js';
import { Timer } from '../utils/timer';
import { ViewMediaClassifier } from '../view/media-classifier'; import { ViewMediaClassifier } from '../view/media-classifier';
import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { MediaQueriesClassifier } from '../view/media-queries-classifier';
import { MediaQueriesResults } from '../view/media-queries-results.js'; import { MediaQueriesResults } from '../view/media-queries-results.js';
@@ -79,7 +78,6 @@ import './title-control.js';
import { import {
FrigateCardTitleControl, FrigateCardTitleControl,
getDefaultTitleConfigForView, getDefaultTitleConfigForView,
showTitleControlAfterDelay,
} from './title-control.js'; } from './title-control.js';
export interface MediaViewerViewContext { export interface MediaViewerViewContext {
@@ -227,7 +225,6 @@ export class FrigateCardViewerCarousel extends LitElement {
protected _selected = 0; protected _selected = 0;
protected _media: ViewMedia[] | null = null; protected _media: ViewMedia[] | null = null;
protected _titleTimer = new Timer();
protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef(); protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef();
protected _player: FrigateCardMediaPlayer | null = null; protected _player: FrigateCardMediaPlayer | null = null;
@@ -466,7 +463,7 @@ export class FrigateCardViewerCarousel extends LitElement {
}} }}
@frigate-card:media:loaded=${(ev: CustomEvent<MediaLoadedInfo>) => { @frigate-card:media:loaded=${(ev: CustomEvent<MediaLoadedInfo>) => {
if (this._refTitleControl.value) { if (this._refTitleControl.value) {
showTitleControlAfterDelay(this._refTitleControl.value, this._titleTimer); this._refTitleControl.value.show();
} }
this._player = ev.detail.player ?? null; this._player = ev.detail.player ?? null;
this._seekHandler(); this._seekHandler();
+14
View File
@@ -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;
}
+12 -1
View File
@@ -1,8 +1,9 @@
:host { :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; --paper-toast-color: white;
pointer-events: none; pointer-events: none;
position: relative;
} }
paper-toast { paper-toast {
@@ -10,6 +11,16 @@ paper-toast {
min-width: unset; min-width: unset;
display: flex; display: flex;
align-items: center; 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 { paper-toast img {