Add live view lazy unloading support.
This commit is contained in:
@@ -190,7 +190,8 @@ live:
|
||||
| Option | Default | Overridable | Description |
|
||||
| - | - | - | - |
|
||||
| `preload` | `false` | :heavy_multiplication_x: | Whether or not to preload the live view. Preloading causes the live view to render in the background regardless of what view is actually shown, so it's instantly available when requested. This consumes additional network/CPU resources continually. |
|
||||
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load camera views in the camera carousel. Setting this will `false` will cause all cameras to load simultaneously when the `live` carousel is opened (or cause all cameras to load continually if both `lazy_load` and `preload` are `true`). This will result in a smoother carousel experience at a cost of (potentially) a substantial amount of continually streamed data. |
|
||||
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load cameras in the camera carousel. Setting this will `false` will cause all cameras to load simultaneously when the `live` carousel is opened (or cause all cameras to load continually if both `lazy_load` and `preload` are `true`). This will result in a smoother carousel experience at a cost of (potentially) a substantial amount of continually streamed data. |
|
||||
| `lazy_unload` | `false` | :heavy_multiplication_x: | Whether or not to lazily **un**load cameras in the camera carousel, or just leave the camera paused. Setting this to `true` will cause cameras to be entirely unloaded when they are no longer visible. This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. |
|
||||
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. |
|
||||
| `provider` | `frigate` | :white_check_mark: | The means through which the live camera view is displayed. See [Live Provider](#live-provider) below.|
|
||||
| `actions` | | :white_check_mark: | Actions to use for the `live` view. See [actions](#actions) below.|
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel';
|
||||
import { EmblaCarouselType, EmblaEventType, EmblaPluginType } from 'embla-carousel';
|
||||
|
||||
export type LazyloadOptionsType = {
|
||||
count?: number;
|
||||
lazyloadCallback: (index: number, slide: HTMLElement) => void;
|
||||
// Number of slides to lazyload left/right of selected (0 == only selected slide).
|
||||
lazyloadCount?: number;
|
||||
|
||||
lazyloadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
lazyunloadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
};
|
||||
|
||||
export const defaultOptions: Partial<LazyloadOptionsType> = {
|
||||
count: 0,
|
||||
lazyloadCount: 0,
|
||||
};
|
||||
|
||||
export type LazyloadType = EmblaPluginType<LazyloadOptionsType> & {
|
||||
@@ -20,6 +23,9 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
let slides: HTMLElement[];
|
||||
const isSlideLazyloaded: Record<number, boolean> = {};
|
||||
|
||||
const loadEvents: EmblaEventType[] = ['init', 'select', 'resize'];
|
||||
const unloadEvents: EmblaEventType[] = ['select'];
|
||||
|
||||
/**
|
||||
* Initialize the plugin.
|
||||
*/
|
||||
@@ -27,18 +33,24 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
carousel = embla;
|
||||
slides = carousel.slideNodes();
|
||||
|
||||
carousel.on('init', lazyLoadHandler);
|
||||
carousel.on('select', lazyLoadHandler);
|
||||
carousel.on('resize', lazyLoadHandler);
|
||||
if (options.lazyloadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.on(evt, lazyloadHandler));
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
unloadEvents.forEach((evt) => carousel.on(evt, lazyunloadHandler));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the plugin.
|
||||
*/
|
||||
function destroy(): void {
|
||||
carousel.off('init', lazyLoadHandler);
|
||||
carousel.off('select', lazyLoadHandler);
|
||||
carousel.off('resize', lazyLoadHandler);
|
||||
if (options.lazyloadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.off(evt, lazyloadHandler));
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
unloadEvents.forEach((evt) => carousel.off(evt, lazyunloadHandler));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,21 +65,18 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
/**
|
||||
* Lazily load media in the carousel.
|
||||
*/
|
||||
function lazyLoadHandler(): void {
|
||||
const lazyLoadCount = options.count ?? 0;
|
||||
const slidesInView = carousel.slidesInView(true);
|
||||
function lazyloadHandler(): void {
|
||||
const lazyLoadCount = options.lazyloadCount ?? 0;
|
||||
const currentIndex = carousel.selectedScrollSnap();
|
||||
const slidesToLoad = new Set<number>();
|
||||
|
||||
const minSlide = Math.min(...slidesInView);
|
||||
const maxSlide = Math.max(...slidesInView);
|
||||
|
||||
// Lazily load 'count' slides on either side of the slides in view.
|
||||
for (let i = 1; i <= lazyLoadCount && minSlide - i >= 0; i++) {
|
||||
slidesToLoad.add(minSlide - i);
|
||||
for (let i = 1; i <= lazyLoadCount && currentIndex - i >= 0; i++) {
|
||||
slidesToLoad.add(currentIndex - i);
|
||||
}
|
||||
slidesInView.forEach((index) => slidesToLoad.add(index));
|
||||
for (let i = 1; i <= lazyLoadCount && maxSlide + i < slides.length; i++) {
|
||||
slidesToLoad.add(maxSlide + i);
|
||||
slidesToLoad.add(currentIndex);
|
||||
for (let i = 1; i <= lazyLoadCount && currentIndex + i < slides.length; i++) {
|
||||
slidesToLoad.add(currentIndex + i);
|
||||
}
|
||||
|
||||
slidesToLoad.forEach((index) => {
|
||||
@@ -75,11 +84,29 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
if (isSlideLazyloaded[index]) {
|
||||
return;
|
||||
}
|
||||
isSlideLazyloaded[index] = true;
|
||||
options.lazyloadCallback(index, slides[index]);
|
||||
if (options.lazyloadCallback) {
|
||||
isSlideLazyloaded[index] = true;
|
||||
options.lazyloadCallback(index, slides[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily unload media in the carousel.
|
||||
*/
|
||||
function lazyunloadHandler(): void {
|
||||
const index = carousel.previousScrollSnap();
|
||||
|
||||
// Only lazy unload slides that are loaded.
|
||||
if (!isSlideLazyloaded[index]) {
|
||||
return;
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
options.lazyunloadCallback(index, slides[index]);
|
||||
isSlideLazyloaded[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
const self: LazyloadType = {
|
||||
name: 'Lazyload',
|
||||
options,
|
||||
|
||||
+21
-14
@@ -297,13 +297,14 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
||||
*/
|
||||
protected _getPlugins(): EmblaPluginType[] | undefined {
|
||||
return [
|
||||
...(this.liveConfig?.lazy_load
|
||||
? [
|
||||
Lazyload({
|
||||
lazyloadCallback: this._lazyLoadSlide.bind(this),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
Lazyload({
|
||||
lazyloadCallback: this.liveConfig?.lazy_load
|
||||
? (...args) => this._lazyloadOrUnloadSlide('load', ...args)
|
||||
: undefined,
|
||||
lazyunloadCallback: this.liveConfig?.lazy_unload
|
||||
? (...args) => this._lazyloadOrUnloadSlide('unload', ...args)
|
||||
: undefined,
|
||||
}),
|
||||
MediaAutoPlayPause({
|
||||
playerSelector: 'frigate-card-live-provider',
|
||||
}),
|
||||
@@ -367,12 +368,16 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
||||
* @param _index The slide number to lazy load.
|
||||
* @param slide The slide to lazy load.
|
||||
*/
|
||||
protected _lazyLoadSlide(_index: number, slide: HTMLElement): void {
|
||||
protected _lazyloadOrUnloadSlide(
|
||||
action: 'load' | 'unload',
|
||||
_index: number,
|
||||
slide: HTMLElement,
|
||||
): void {
|
||||
const liveProvider = slide.querySelector(
|
||||
'frigate-card-live-provider',
|
||||
) as FrigateCardLiveProvider;
|
||||
if (liveProvider) {
|
||||
liveProvider.disabled = false;
|
||||
liveProvider.disabled = action == 'load' ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,11 +685,13 @@ export class FrigateCardLiveWebRTC extends LitElement {
|
||||
* Play the video.
|
||||
*/
|
||||
public play(): void {
|
||||
this._getPlayer()?.play().catch(() => {
|
||||
// WebRTC appears to generate additional spurious load events, which may
|
||||
// result in loads after a play() call, which causes the browser to spam
|
||||
// the logs unless the promise rejection is handled here.
|
||||
})
|
||||
this._getPlayer()
|
||||
?.play()
|
||||
.catch(() => {
|
||||
// WebRTC appears to generate additional spurious load events, which may
|
||||
// result in loads after a play() call, which causes the browser to spam
|
||||
// the logs unless the promise rejection is handled here.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -311,14 +311,11 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
||||
*/
|
||||
protected _getPlugins(): EmblaPluginType[] | undefined {
|
||||
return [
|
||||
...(this.viewerConfig?.lazy_load
|
||||
? [
|
||||
Lazyload({
|
||||
lazyloadCallback: this._lazyLoadSlide.bind(this),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
Lazyload({
|
||||
lazyloadCallback: this.viewerConfig?.lazy_load
|
||||
? this._lazyloadSlide.bind(this)
|
||||
: undefined,
|
||||
}),
|
||||
// Don't need autoplay/pause for snapshots.
|
||||
...(this.view?.is('clip')
|
||||
? [
|
||||
@@ -493,7 +490,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
||||
* @param slide The slide to lazy load.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected _lazyLoadSlide(index: number, slide: HTMLElement): void {
|
||||
protected _lazyloadSlide(index: number, slide: HTMLElement): void {
|
||||
const childIndex: number | undefined = this._slideToChild[index];
|
||||
|
||||
if (
|
||||
|
||||
@@ -57,6 +57,7 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE =
|
||||
export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const;
|
||||
export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const;
|
||||
export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const;
|
||||
export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const;
|
||||
export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const;
|
||||
export const CONF_LIVE_WEBRTC = `${CONF_LIVE}.webrtc` as const;
|
||||
export const CONF_LIVE_WEBRTC_ENTITY = `${CONF_LIVE_WEBRTC}.entity` as const;
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
|
||||
CONF_LIVE_DRAGGABLE,
|
||||
CONF_LIVE_LAZY_LOAD,
|
||||
CONF_LIVE_LAZY_UNLOAD,
|
||||
CONF_LIVE_PRELOAD,
|
||||
CONF_MENU_BUTTONS_CLIPS,
|
||||
CONF_MENU_BUTTONS_FRIGATE,
|
||||
@@ -728,6 +729,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
CONF_LIVE_LAZY_LOAD,
|
||||
defaults.live.lazy_load,
|
||||
)}
|
||||
${this._renderSwitch(
|
||||
CONF_LIVE_LAZY_UNLOAD,
|
||||
defaults.live.lazy_unload,
|
||||
)}
|
||||
${this._renderDropdown(
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
liveNextPreviousControlStyles,
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"live": {
|
||||
"preload": "Preload live view in the background",
|
||||
"draggable": "Live cameras view can be dragged/swiped",
|
||||
"lazy_load": "Live cameras are lazily loaded in carousel",
|
||||
"lazy_load": "Live cameras are lazily loaded",
|
||||
"lazy_unload": "Live cameras are lazily unloaded",
|
||||
"controls": {
|
||||
"next_previous": {
|
||||
"style": "Live view next & previous control style",
|
||||
|
||||
@@ -432,6 +432,7 @@ export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfig
|
||||
const liveConfigDefault = {
|
||||
preload: false,
|
||||
lazy_load: true,
|
||||
lazy_unload: false,
|
||||
draggable: true,
|
||||
controls: {
|
||||
next_previous: {
|
||||
@@ -516,6 +517,7 @@ const liveConfigSchema = liveOverridableConfigSchema
|
||||
// Non-overrideable parameters.
|
||||
preload: z.boolean().default(liveConfigDefault.preload),
|
||||
lazy_load: z.boolean().default(liveConfigDefault.lazy_load),
|
||||
lazy_unload: z.boolean().default(liveConfigDefault.lazy_unload),
|
||||
draggable: z.boolean().default(liveConfigDefault.draggable),
|
||||
})
|
||||
.default(liveConfigDefault);
|
||||
|
||||
Reference in New Issue
Block a user