fix: Lazy unloading should not leave dangling connections (#2004)

- Closes #1992
This commit is contained in:
Dermot Duffy
2025-04-12 10:48:37 +01:00
committed by GitHub
parent a24c9628c5
commit a5ffd51728
12 changed files with 397 additions and 468 deletions
+1 -33
View File
@@ -24,7 +24,6 @@ import { HomeAssistant } from '../../ha/types.js';
import liveCarouselStyle from '../../scss/live-carousel.scss';
import { stopEventFromActivatingCardWideActions } from '../../utils/action.js';
import { CarouselSelected } from '../../utils/embla/carousel-controller.js';
import { AutoLazyLoad } from '../../utils/embla/plugins/auto-lazy-load/auto-lazy-load.js';
import AutoMediaLoadedInfo from '../../utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.js';
import AutoSize from '../../utils/embla/plugins/auto-size/auto-size.js';
import { getStreamCameraID } from '../../utils/substream.js';
@@ -36,7 +35,6 @@ import '../next-prev-control.js';
import '../ptz.js';
import { AdvancedCameraCardPTZ } from '../ptz.js';
import './provider.js';
import { AdvancedCameraCardLiveProvider } from './provider.js';
const ADVANCED_CAMERA_CARD_LIVE_PROVIDER = 'advanced-camera-card-live-provider';
@@ -140,19 +138,7 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
}
protected _getPlugins(): EmblaCarouselPlugins {
return [
AutoLazyLoad({
...(this.liveConfig?.lazy_load && {
lazyLoadCallback: (index, slide) =>
this._lazyloadOrUnloadSlide('load', index, slide),
}),
lazyUnloadConditions: this.liveConfig?.lazy_unload,
lazyUnloadCallback: (index, slide) =>
this._lazyloadOrUnloadSlide('unload', index, slide),
}),
AutoMediaLoadedInfo(),
AutoSize(),
];
return [AutoMediaLoadedInfo(), AutoSize()];
}
/**
@@ -217,23 +203,6 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
}
}
protected _lazyloadOrUnloadSlide(
action: 'load' | 'unload',
_index: number,
slide: Element,
): void {
if (slide instanceof HTMLSlotElement) {
slide = slide.assignedElements({ flatten: true })[0];
}
const liveProvider = slide?.querySelector(
ADVANCED_CAMERA_CARD_LIVE_PROVIDER,
) as AdvancedCameraCardLiveProvider | null;
if (liveProvider) {
liveProvider.load = action === 'load';
}
}
protected _renderLive(
cameraID: string,
cameraConfig: CameraConfig,
@@ -248,7 +217,6 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
return html`
<div class="embla__slide">
<advanced-camera-card-live-provider
?load=${!this.liveConfig.lazy_load}
.microphoneState=${view?.camera === cameraID
? this.microphoneState
: undefined}
+25 -12
View File
@@ -12,6 +12,7 @@ import { guard } from 'lit/directives/guard.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraEndpoints } from '../../camera-manager/types.js';
import { MicrophoneState } from '../../card-controller/types.js';
import { LazyLoadController } from '../../components-lib/lazy-load-controller.js';
import { dispatchLiveErrorEvent } from '../../components-lib/live/utils/dispatch-live-error.js';
import { PartialZoomSettings } from '../../components-lib/zoom/types.js';
import { CameraConfig, LiveProvider } from '../../config/schema/cameras.js';
@@ -45,12 +46,6 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
@property({ attribute: false })
public liveConfig?: LiveConfig;
// Whether or not to load the video for this camera. If `false`, no contents
// are rendered until this attribute is set to `true` (this is useful for lazy
// loading).
@property({ attribute: true, type: Boolean })
public load = false;
// Label that is used for ARIA support and as tooltip.
@property({ attribute: false })
public label = '';
@@ -74,6 +69,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
protected _showStreamTroubleshooting = false;
protected _refProvider: Ref<MediaPlayerElement> = createRef();
protected _lazyLoadController: LazyLoadController | null = null;
// A note on dynamic imports:
//
@@ -144,11 +140,23 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
}
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('load')) {
if (!this.load) {
this._isVideoMediaLoaded = false;
dispatchMediaUnloadedEvent(this);
}
if (
changedProps.has('liveConfig') ||
(!this._lazyLoadController && this.liveConfig)
) {
this._lazyLoadController?.destroy();
this._lazyLoadController?.removeController();
this._lazyLoadController = new LazyLoadController(
this,
this.liveConfig?.lazy_load,
this.liveConfig?.lazy_unload,
);
this._lazyLoadController.addListener((loaded: boolean) => {
if (!loaded) {
this._isVideoMediaLoaded = false;
dispatchMediaUnloadedEvent(this);
}
});
}
if (changedProps.has('liveConfig')) {
@@ -216,7 +224,12 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
}
protected render(): TemplateResult | void {
if (!this.load || !this.hass || !this.liveConfig || !this.cameraConfig) {
if (
!this._lazyLoadController?.isLoaded() ||
!this.hass ||
!this.liveConfig ||
!this.cameraConfig
) {
return;
}