Add live view lazy unloading support.

This commit is contained in:
Dermot Duffy
2022-01-25 20:06:02 -08:00
parent da6b9f2ad1
commit 4bba7ae5fb
8 changed files with 89 additions and 48 deletions
+50 -23
View File
@@ -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
View File
@@ -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.
});
}
/**
+6 -9
View File
@@ -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 (
+1
View File
@@ -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;
+5
View File
@@ -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,
+2 -1
View File
@@ -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",
+2
View File
@@ -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);