Fix lazy loading in event viewer in Safari.

This commit is contained in:
Dermot Duffy
2021-11-06 22:36:39 -07:00
parent d272988ef7
commit 8141bbfb46
2 changed files with 52 additions and 20 deletions
+51 -19
View File
@@ -39,7 +39,9 @@ import './next-prev-control.js';
import viewerStyle from '../scss/viewer.scss'; import viewerStyle from '../scss/viewer.scss';
const IMG_EMPTY = 'data:,'; const getEmptyImageSrc = (width: number, height: number) =>
`data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
const IMG_EMPTY = getEmptyImageSrc(16, 9);
@customElement('frigate-card-viewer') @customElement('frigate-card-viewer')
export class FrigateCardViewer extends LitElement { export class FrigateCardViewer extends LitElement {
@@ -171,9 +173,11 @@ export class FrigateCardViewerCore extends LitElement {
// (Folders are not media items that can be rendered). // (Folders are not media items that can be rendered).
protected _slideToChild: Record<number, number> = {}; protected _slideToChild: Record<number, number> = {};
// A "map" from slide number to MediaShowInfo object or null if the slide has // A "map" from slide number to MediaShowInfo object.
// been lazy loaded, but the MediaShowInfo object is not yet available. protected _mediaShowInfo: Record<number, MediaShowInfo> = {};
protected _mediaShowInfo: Record<number, MediaShowInfo | null> = {};
// Whether or not a given slide has been successfully lazily loaded.
protected _slideHasBeenLazyLoaded: Record<number, boolean> = {};
/** /**
* The updated lifecycle callback for this element. * The updated lifecycle callback for this element.
@@ -414,16 +418,15 @@ export class FrigateCardViewerCore extends LitElement {
} }
slidesToLoad.forEach((index) => { slidesToLoad.forEach((index) => {
// Only lazy loads slides that are not already loaded. // Only lazy load slides that are not already loaded.
if (index in this._mediaShowInfo) { if (this._slideHasBeenLazyLoaded[index]) {
return; return;
} }
this._mediaShowInfo[index] = null; this._slideHasBeenLazyLoaded[index] = true;
const slide = slides[index]; const slide = slides[index];
// Snapshots. // Snapshots.
const img = slide.querySelector('img'); const img = slide.querySelector('img') as HTMLImageElement;
// Frigate >= 0.9.0+ clips. // Frigate >= 0.9.0+ clips.
const hls_player = slide.querySelector( const hls_player = slide.querySelector(
@@ -512,10 +515,7 @@ export class FrigateCardViewerCore extends LitElement {
this._carousel.slidesInView(true).forEach((slideIndex) => { this._carousel.slidesInView(true).forEach((slideIndex) => {
if (slideIndex in this._mediaShowInfo) { if (slideIndex in this._mediaShowInfo) {
const mediaShowInfo = this._mediaShowInfo[slideIndex]; dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
if (mediaShowInfo) {
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
}
} }
}); });
} }
@@ -546,13 +546,39 @@ export class FrigateCardViewerCore extends LitElement {
slideIndex: number, slideIndex: number,
mediaShowInfo?: MediaShowInfo | null, mediaShowInfo?: MediaShowInfo | null,
): void { ): void {
// isValidMediaShowInfo is used to weed out the initial load of the // isValidMediaShowInfo is used to prevent saving media info that will be
// transparent 1x1 placeholders. // rejected upstream.
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) { if (this.viewerConfig && mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
const firstMediaLoad = !Object.keys(this._mediaShowInfo).length;
this._mediaShowInfo[slideIndex] = mediaShowInfo; this._mediaShowInfo[slideIndex] = mediaShowInfo;
if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) { if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) {
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo); dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
} }
/**
* Images need a width/height from initial load, and browsers will assume
* that the aspect ratio of the initial dummy-image load will persist. In
* lazy-loading, this can cause a 1x1 pixel dummy image to cause the
* browser to assume all images will be square, so the whole carousel will
* have the wrong aspect-ratio until every single image has been lazily
* loaded. To avoid this, we use a 16:9 dummy image at first (most
* likely?) and once the first piece of real media has been loaded, all
* dummy images are replaced with dummy images that match the aspect ratio
* of the real image. It still might be wrong, but it's the best option
* available.
*/
if (firstMediaLoad && this.viewerConfig.lazy_load) {
const replacementImageSrc = getEmptyImageSrc(
mediaShowInfo.width,
mediaShowInfo.height,
);
this.renderRoot.querySelectorAll('.embla__container img').forEach((img) => {
const imageElement: HTMLImageElement = img as HTMLImageElement;
if (imageElement.src === IMG_EMPTY) {
imageElement.src = replacementImageSrc
}
});
}
} }
} }
@@ -567,7 +593,11 @@ export class FrigateCardViewerCore extends LitElement {
): TemplateResult | void { ): TemplateResult | void {
// media that can be expanded (folders) cannot be resolved to a single media // media that can be expanded (folders) cannot be resolved to a single media
// item, skip them. // item, skip them.
if (!this.view || !this.viewerConfig || !BrowseMediaUtil.isTrueMedia(mediaToRender)) { if (
!this.view ||
!this.viewerConfig ||
!BrowseMediaUtil.isTrueMedia(mediaToRender)
) {
return; return;
} }
@@ -638,8 +668,10 @@ export class FrigateCardViewerCore extends LitElement {
} }
}} }}
@load="${(e: Event) => { @load="${(e: Event) => {
this._mediaShowInfoHandler(slideIndex, createMediaShowInfo(e)); if (this.viewerConfig && (!this.viewerConfig.lazy_load || this._slideHasBeenLazyLoaded[slideIndex])) {
}}" this._mediaShowInfoHandler(slideIndex, createMediaShowInfo(e));
}
}}"
/>`} />`}
</div> </div>
`; `;
+1 -1
View File
@@ -8,7 +8,7 @@ ha-hls-player {
img,video { img,video {
width: 100%; width: 100%;
height: 100%; height: auto;
display: block; display: block;
} }