Separate timeout_seconds from refresh_seconds .

This commit is contained in:
Dermot Duffy
2022-01-23 21:01:55 -08:00
parent 1f178fedc2
commit ea9d3b4809
6 changed files with 107 additions and 109 deletions
+34 -20
View File
@@ -135,12 +135,12 @@ view:
| Option | Default | Overridable | Description | | Option | Default | Overridable | Description |
| - | - | - | - | | - | - | - | - |
| `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. See [views](#views) below.| | `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. See [views](#views) below.|
| `timeout_seconds` | | :heavy_multiplication_x: | A numbers of seconds of inactivity after which the card will reset to the default configured view. Inactivity is defined as lack of interaction with the Frigate menu.| | `timeout_seconds` | `300` | :heavy_multiplication_x: | A numbers of seconds of inactivity after human interaction, after which the card will reset to the default configured view (i.e. 'screensaver' functionality). Inactivity is defined as lack of mouse/touch interaction with the Frigate card. If the default view occurs sooner (e.g. via `update_seconds` or manually) the timer will be stopped. `0` means disable this functionality. |
| `actions` | | :heavy_multiplication_x: | Actions to use for all views, individual actions may be overriden by view-specific actions. See [actions](#actions) below.| | `update_seconds` | `0` | :heavy_multiplication_x: | A number of seconds after which to automatically update/refresh the default view. See [card updates](#card-updates) below for behavior and usecases. If the default view occurs sooner (e.g. manually) the timer will start over. `0` disables this functionality.|
| `update_force` | `false` | :heavy_multiplication_x: | Whether card updates/refreshes should ignore playing media and human interaction. See [card updates](#card-updates) below for behavior and usecases.| | `update_force` | `false` | :heavy_multiplication_x: | Whether automated card updates/refreshes should ignore human interaction. See [card updates](#card-updates) below for behavior and usecases.|
| `update_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the view to reset to the default. See [card updates](#card-updates) below for behavior and usecases.| | `update_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the view to reset to the default. See [card updates](#card-updates) below for behavior and usecases.|
| `update_cycle_camera` | `false` | :heavy_multiplication_x: | When set to `true` the selected camera is cycled on each default view change. | | `update_cycle_camera` | `false` | :heavy_multiplication_x: | When set to `true` the selected camera is cycled on each default view change. |
| `actions` | | :heavy_multiplication_x: | Actions to use for all views, individual actions may be overriden by view-specific actions. See [actions](#actions) below.|
### Menu Options ### Menu Options
All configuration is under: All configuration is under:
@@ -412,8 +412,6 @@ item, that has both of the following parameters set:
| `conditions` | | :heavy_multiplication_x: | A set of conditions that must evaluate to `true` in order for the overrides to be applied. See [Frigate Card Conditions](#frigate-card-conditions). | | `conditions` | | :heavy_multiplication_x: | A set of conditions that must evaluate to `true` in order for the overrides to be applied. See [Frigate Card Conditions](#frigate-card-conditions). |
| `overrides` | | :heavy_multiplication_x: |Configuration overrides to be applied. Any configuration parameter described in this documentation as 'Overridable' is supported. | | `overrides` | | :heavy_multiplication_x: |Configuration overrides to be applied. Any configuration parameter described in this documentation as 'Overridable' is supported. |
### Using WebRTC ### Using WebRTC
WebRTC support blends the use of the ultra-realtime [WebRTC live WebRTC support blends the use of the ultra-realtime [WebRTC live
@@ -1099,31 +1097,34 @@ image:
## Card Refreshes ## Card Refreshes
Three sets of flags govern when the card will automatically re-render in the Four sets of flags govern when the card will automatically refresh in the
absence of human interaction. absence of human interaction.
The following table describes the behavior these 3 flags have. The following table describes the behavior these flags have.
### Card Update Truth Table ### Card Update Truth Table
| `view.timeout_seconds` | `view.update_force` | `view.update_entities` | Behavior | | `view.update_seconds` | `view.timeout_seconds` | `view.update_force` | `view.update_entities` | Behavior |
| :-: | :-: | :-: | - | | :-: | :-: | :-: | :-: | - |
| Unset or `0` | *(Any value)* | Unset | Card will not automatically refresh. | | `0` | `0` | *(Any value)* | Unset | Card will not automatically refresh. |
| Unset or `0` | `false` | *(Any entity)* | Card will reload default view when entity state changes, unless media is playing. | | `0` | `0` | *(Any value)* | *(Any entity)* | Card will reload default view when entity state changes. |
| Unset or `0` | `true` | *(Any entity)* | Card will reload default view when entity state changes. | | `0` | `X` seconds | *(Any value)* | Unset | Card will reload default view `X` seconds after human interaction stops. |
| `X` seconds | `false` | Unset | Card will reload default view `X` seconds after human interaction stops, unless media is playing. | | `0` | `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, or when entity state changes (as long as human interaction has not occurred in the last `X` seconds). |
| `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops or when entity state changes -- in both cases unless media is playing. | | `0` | `X` seconds | `true` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops or when entity state changes. |
| `X` seconds | `true` | Unset | Card will reload default view every `X` seconds. | | `Y` seconds | `0` | *(Any value)* | Unset | Card will reload default view every `Y` seconds. |
| `X` seconds | `true` | *(Any entity)* | Card will reload default view every `X` seconds or when entity state changes. | | `Y` seconds | `0` | *(Any value)* | *(Any entity)* | Card will reload default view every `Y` seconds, or whenever entity state changes. |
| `Y` seconds | `X` seconds | `false` | Unset | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds (as long as there hasn't been human interaction in the last `X` seconds). |
| `Y` seconds | `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds or whenever entity state changes (in both cases -- as long as there hasn't been human interaction in the last `X` seconds). |
| `Y` seconds | `X` seconds | `true` | Unset | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds. |
| `Y` seconds | `X` seconds | `true` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds or whenever entity state changes. |
### Usecases For Automated Refreshes ### Usecases For Automated Refreshes
* Refreshing the `live` thumbnails periodically. * Refreshing the `live` thumbnails every 30 seconds.
```yaml ```yaml
view: view:
default: live default: live
timeout_seconds: 30 update_seconds: 30
force: true
``` ```
* Using `clip` or `snapshot` as the default view (for the most recent clip or * Using `clip` or `snapshot` as the default view (for the most recent clip or
snapshot respectively) and having the card automatically refresh (to fetch a snapshot respectively) and having the card automatically refresh (to fetch a
@@ -1135,6 +1136,19 @@ view:
update_entities: update_entities:
- binary_sensor.office_person_motion - binary_sensor.office_person_motion
``` ```
* Cycle the live view of the camera every 60 seconds
```yaml
view:
update_cycle_camera: true
update_seconds: 60
```
* Return to the most recent clip of the default camera 30 seconds after human
interaction with the card stops.
```yaml
view:
default: clip
timeout_seconds: 30
```
## Troubleshooting ## Troubleshooting
+66 -37
View File
@@ -151,11 +151,12 @@ export class FrigateCard extends LitElement {
@query('frigate-card-elements') @query('frigate-card-elements')
_elements?: FrigateCardElements; _elements?: FrigateCardElements;
// Human interaction timer ID. // Human interaction timer ("screensaver" functionality, return to default
// view after human interaction).
protected _interactionTimerID: number | null = null; protected _interactionTimerID: number | null = null;
// Whether or not media is actively playing (live or clip). // Automated refreshes of the default view.
protected _mediaPlaying = false; protected _updateTimerID: number | null = null;
// Information about the most recently loaded media item. // Information about the most recently loaded media item.
protected _mediaShowInfo: MediaShowInfo | null = null; protected _mediaShowInfo: MediaShowInfo | null = null;
@@ -619,10 +620,6 @@ export class FrigateCard extends LitElement {
this._cameras = undefined; this._cameras = undefined;
this._view = undefined; this._view = undefined;
if (this._getConfig().view.update_force) {
// If update force is enabled, start a timer right away.
this._resetInteractionTimer();
}
this._changeView(); this._changeView();
} }
@@ -640,6 +637,7 @@ export class FrigateCard extends LitElement {
} }
if (args?.view === undefined) { if (args?.view === undefined) {
// Load the default view.
let camera = this._view?.camera; let camera = this._view?.camera;
if (this._cameras?.size) { if (this._cameras?.size) {
if (!camera) { if (!camera) {
@@ -658,6 +656,14 @@ export class FrigateCard extends LitElement {
camera: camera, camera: camera,
}); });
this._generateConditionState(); this._generateConditionState();
// The default view has been loaded, so can abandon any running
// 'screensaver' timer.
this._clearInteractionTimer();
// Restart the update timer, so the default view is refreshed at a fixed
// interval from now (if so configured).
this._startUpdateTimer();
} }
} else { } else {
this._view = args.view; this._view = args.view;
@@ -692,8 +698,7 @@ export class FrigateCard extends LitElement {
// Assistant update if there's been recent interaction (e.g. clicks on the // Assistant update if there's been recent interaction (e.g. clicks on the
// card) or if there is media active playing. // card) or if there is media active playing.
if ( if (
(this._getConfig().view.update_force || this._isAutomatedViewUpdateAllowed() &&
!(this._interactionTimerID && this._mediaPlaying)) &&
shouldUpdateBasedOnHass( shouldUpdateBasedOnHass(
this._hass, this._hass,
oldHass, oldHass,
@@ -701,9 +706,8 @@ export class FrigateCard extends LitElement {
) )
) { ) {
// If entities being monitored have changed then reset the view to the // If entities being monitored have changed then reset the view to the
// default and allow a re-render. Note that as per the Lit lifecycle, // default. Note that as per the Lit lifecycle, the setting of the view
// the setting of the view itself will not trigger an *additional* // itself will not trigger an *additional* re-render here.
// re-render here.
this._changeView(); this._changeView();
return true; return true;
} }
@@ -891,25 +895,66 @@ export class FrigateCard extends LitElement {
) { ) {
handleAction(node, this._hass as HomeAssistant, config, ev.detail.action); handleAction(node, this._hass as HomeAssistant, config, ev.detail.action);
} }
this._resetInteractionTimer();
// Set the 'screensaver' timer.
this._startInteractionTimer();
} }
protected _resetInteractionTimer(): void { /**
if (this._getConfig().view.timeout_seconds) { * Clear the human interaction ('screensaver') timer.
*/
protected _clearInteractionTimer(): void {
if (this._interactionTimerID) { if (this._interactionTimerID) {
window.clearTimeout(this._interactionTimerID); window.clearTimeout(this._interactionTimerID);
}
this._interactionTimerID = window.setTimeout(() => {
this._interactionTimerID = null; this._interactionTimerID = null;
this._changeView();
if (this._getConfig().view.update_force) {
// If force is enabled, the timer just resets and starts over.
this._resetInteractionTimer();
} }
}
/**
* Start the human interaction ('screensaver') timer to reset the view to
* default `view.timeout_seconds` after human interaction.
*/
protected _startInteractionTimer(): void {
this._clearInteractionTimer();
if (this._getConfig().view.timeout_seconds) {
this._interactionTimerID = window.setTimeout(() => {
this._changeView();
}, this._getConfig().view.timeout_seconds * 1000); }, this._getConfig().view.timeout_seconds * 1000);
} }
} }
/**
* Set the update timer to trigger an update refresh every
* `view.update_seconds`.
*/
protected _startUpdateTimer(): void {
if (this._updateTimerID) {
window.clearTimeout(this._updateTimerID);
this._updateTimerID = null;
}
if (this._getConfig().view.update_seconds) {
this._updateTimerID = window.setTimeout(() => {
if (this._isAutomatedViewUpdateAllowed()) {
this._changeView();
} else {
// Not allowed to update this time around, but try again at the next
// interval.
this._startUpdateTimer();
}
}, this._getConfig().view.update_seconds * 1000);
}
}
/**
* Determine if an automated view update is allowed.
* @returns `true` if it's allowed, `false` otherwise.
*/
protected _isAutomatedViewUpdateAllowed(): boolean {
return (
this._getConfig().view.update_force || !this._interactionTimerID
);
}
/** /**
* Render the card menu. * Render the card menu.
* @returns A rendered template. * @returns A rendered template.
@@ -929,20 +974,6 @@ export class FrigateCard extends LitElement {
`; `;
} }
/**
* Handler for media play event.
*/
protected _playHandler(): void {
this._mediaPlaying = true;
}
/**
* Handler for media pause event.
*/
protected _pauseHandler(): void {
this._mediaPlaying = false;
}
/** /**
* Set the message to display and trigger an update. * Set the message to display and trigger an update.
* @param message The message to display. * @param message The message to display.
@@ -1115,8 +1146,6 @@ export class FrigateCard extends LitElement {
@frigate-card:message=${this._messageHandler} @frigate-card:message=${this._messageHandler}
@frigate-card:change-view=${this._changeViewHandler} @frigate-card:change-view=${this._changeViewHandler}
@frigate-card:media-show=${this._mediaShowHandler} @frigate-card:media-show=${this._mediaShowHandler}
@frigate-card:pause=${this._pauseHandler}
@frigate-card:play=${this._playHandler}
> >
${this._getConfig().menu.mode == 'above' ? this._renderMenu() : ''} ${this._getConfig().menu.mode == 'above' ? this._renderMenu() : ''}
<div class="container outer" style="${styleMap(outerStyle)}"> <div class="container outer" style="${styleMap(outerStyle)}">
-16
View File
@@ -114,22 +114,6 @@ export function dispatchFrigateCardEvent<T>(
); );
} }
/**
* Dispatch a Frigate card play event.
* @param element The element to send the event.
*/
export function dispatchPlayEvent(element: HTMLElement): void {
dispatchFrigateCardEvent(element, 'play');
}
/**
* Dispatch a Frigate card pause event.
* @param element The element to send the event.
*/
export function dispatchPauseEvent(element: HTMLElement): void {
dispatchFrigateCardEvent(element, 'pause');
}
/** /**
* Create a MediaShowInfo object. * Create a MediaShowInfo object.
* @param source An event or HTMLElement that should be used as a source. * @param source An event or HTMLElement that should be used as a source.
+1 -25
View File
@@ -40,8 +40,6 @@ import {
dispatchExistingMediaShowInfoAsEvent, dispatchExistingMediaShowInfoAsEvent,
dispatchMediaShowEvent, dispatchMediaShowEvent,
dispatchMessageEvent, dispatchMessageEvent,
dispatchPauseEvent,
dispatchPlayEvent,
getCameraIcon, getCameraIcon,
getCameraTitle, getCameraTitle,
homeAssistantSignPath, homeAssistantSignPath,
@@ -701,7 +699,7 @@ export class FrigateCardLiveWebRTC extends LitElement {
* @returns The player or `null` if not found. * @returns The player or `null` if not found.
*/ */
protected _getPlayer(): HTMLVideoElement | null { protected _getPlayer(): HTMLVideoElement | null {
return this.renderRoot.querySelector('#video') as HTMLVideoElement | null; return this.renderRoot?.querySelector('#video') as HTMLVideoElement | null;
} }
/** /**
@@ -763,8 +761,6 @@ export class FrigateCardLiveWebRTC extends LitElement {
const video = this._getPlayer(); const video = this._getPlayer();
if (video) { if (video) {
const onloadedmetadata = video.onloadedmetadata; const onloadedmetadata = video.onloadedmetadata;
const onplay = video.onplay;
const onpause = video.onpause;
video.onloadedmetadata = (e) => { video.onloadedmetadata = (e) => {
if (onloadedmetadata) { if (onloadedmetadata) {
@@ -772,18 +768,6 @@ export class FrigateCardLiveWebRTC extends LitElement {
} }
dispatchMediaShowEvent(this, video); dispatchMediaShowEvent(this, video);
}; };
video.onplay = (e) => {
if (onplay) {
onplay.call(video, e);
}
dispatchPlayEvent(this);
};
video.onpause = (e) => {
if (onpause) {
onpause.call(video, e);
}
dispatchPauseEvent(this);
};
} }
}); });
} }
@@ -864,14 +848,6 @@ export class FrigateCardLiveJSMPEG extends LitElement {
url, url,
{ {
canvas: this._jsmpegCanvasElement, canvas: this._jsmpegCanvasElement,
hooks: {
play: () => {
dispatchPlayEvent(this);
},
pause: () => {
dispatchPauseEvent(this);
},
},
}, },
{ {
pauseWhenHidden: false, pauseWhenHidden: false,
+1 -8
View File
@@ -12,12 +12,7 @@
import { TemplateResult, css, html } from 'lit'; import { TemplateResult, css, html } from 'lit';
import { Ref, createRef, ref } from 'lit/directives/ref'; import { Ref, createRef, ref } from 'lit/directives/ref';
import { customElement } from 'lit/decorators.js'; import { customElement } from 'lit/decorators.js';
import { dispatchMediaShowEvent } from '../common.js';
import {
dispatchMediaShowEvent,
dispatchPauseEvent,
dispatchPlayEvent,
} from '../common.js';
customElements.whenDefined('ha-hls-player').then(() => { customElements.whenDefined('ha-hls-player').then(() => {
@customElement('frigate-card-ha-hls-player') @customElement('frigate-card-ha-hls-player')
@@ -54,8 +49,6 @@ customElements.whenDefined('ha-hls-player').then(() => {
@loadeddata=${(e) => { @loadeddata=${(e) => {
dispatchMediaShowEvent(this, e); dispatchMediaShowEvent(this, e);
}} }}
@pause=${() => dispatchPauseEvent(this)}
@play=${() => dispatchPlayEvent(this)}
></video> ></video>
`; `;
} }
+4 -2
View File
@@ -370,7 +370,8 @@ export type PictureElements = z.infer<typeof pictureElementsSchema>;
*/ */
const viewConfigDefault = { const viewConfigDefault = {
default: 'live' as const, default: 'live' as const,
timeout: 180, timeout_seconds: 300,
update_seconds: 0,
update_force: false, update_force: false,
update_cycle_camera: false, update_cycle_camera: false,
}; };
@@ -380,7 +381,8 @@ const viewConfigSchema = z
.enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED) .enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED)
.optional() .optional()
.default(viewConfigDefault.default), .default(viewConfigDefault.default),
timeout_seconds: z.number().default(viewConfigDefault.timeout), timeout_seconds: z.number().default(viewConfigDefault.timeout_seconds),
update_seconds: z.number().default(viewConfigDefault.update_seconds),
update_force: z.boolean().default(viewConfigDefault.update_force), update_force: z.boolean().default(viewConfigDefault.update_force),
update_cycle_camera: z.boolean().default(viewConfigDefault.update_cycle_camera), update_cycle_camera: z.boolean().default(viewConfigDefault.update_cycle_camera),
update_entities: z.string().array().optional(), update_entities: z.string().array().optional(),