From aeac0004ea470900ea683449501f774f3cecccee Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 17 Jan 2022 20:27:26 -0800 Subject: [PATCH] Render the JSMPEG spinner until the last possible moment. --- package.json | 2 +- src/components/live.ts | 75 ++++++++++++++++---------------- src/components/media-carousel.ts | 40 +++++++++-------- src/scss/media-carousel.scss | 6 +++ 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 8fb94131..55963fd0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@material/rtl": "^13.0.0", "custom-card-helpers": "^1.8.0", "dayjs": "^1.10.7", - "embla-carousel": "^5.0.1", + "embla-carousel": "^6.1.0", "home-assistant-js-websocket": "^5.11.1", "lit": "^2.0.2", "lodash-es": "^4.17.21", diff --git a/src/components/live.ts b/src/components/live.ts index ee748991..3f05c191 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -754,46 +754,46 @@ export class FrigateCardLiveJSMPEG extends LitElement { /** * Create a JSMPEG player. + * @param url The URL for the player to connect to. * @returns A JSMPEG player. */ - protected _createJSMPEGPlayer(url: string): JSMpeg.VideoElement { - let videoDecoded = false; - - const jsmpegOptions = { - pauseWhenHidden: false, - protocols: [], - audio: false, - videoBufferSize: 1024 * 1024 * 4, - onVideoDecode: () => { - // This is the only callback that is called after the dimensions - // are available. It's called on every frame decode, so just - // ignore any subsequent calls. - if (!videoDecoded && this._jsmpegCanvasElement) { - videoDecoded = true; - dispatchMediaShowEvent(this, this._jsmpegCanvasElement); - } - }, - }; - - // Override with user-specified options. - Object.assign(jsmpegOptions, this.jsmpegConfig?.options); - - return new JSMpeg.VideoElement( - this, - url, - { - canvas: this._jsmpegCanvasElement, - hooks: { - play: () => { - dispatchPlayEvent(this); - }, - pause: () => { - dispatchPauseEvent(this); + protected async _createJSMPEGPlayer(url: string): Promise { + return new Promise((resolve) => { + let videoDecoded = false; + const player = new JSMpeg.VideoElement( + this, + url, + { + canvas: this._jsmpegCanvasElement, + hooks: { + play: () => { + dispatchPlayEvent(this); + }, + pause: () => { + dispatchPauseEvent(this); + }, }, }, - }, - jsmpegOptions, - ); + { + pauseWhenHidden: false, + protocols: [], + audio: false, + videoBufferSize: 1024 * 1024 * 4, + // Override with user-specified options. + ...this.jsmpegConfig?.options, + onVideoDecode: () => { + // This is the only callback that is called after the dimensions + // are available. It's called on every frame decode, so just + // ignore any subsequent calls. + if (!videoDecoded && this._jsmpegCanvasElement) { + videoDecoded = true; + dispatchMediaShowEvent(this, this._jsmpegCanvasElement); + resolve(player); + } + }, + }, + ); + }); } /** @@ -856,8 +856,7 @@ export class FrigateCardLiveJSMPEG extends LitElement { const url = await this._getURL(); if (url) { - this._jsmpegVideoPlayer = this._createJSMPEGPlayer(url); - + this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(url); this._refreshPlayerTimerID = window.setTimeout(() => { this.requestUpdate(); }, (URL_SIGN_EXPIRY_SECONDS - URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000); diff --git a/src/components/media-carousel.ts b/src/components/media-carousel.ts index 9b1b73b1..5b20e55a 100644 --- a/src/components/media-carousel.ts +++ b/src/components/media-carousel.ts @@ -92,7 +92,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel { carousel?.on('init', this._adaptiveHeightSetHandler.bind(this)); carousel?.on('select', this._adaptiveHeightSetHandler.bind(this)); carousel?.on('resize', this._adaptiveHeightSetHandler.bind(this)); - + if (this._getLazyLoadCount() != null) { // Load media as the carousel is moved (if lazy loading is in use). carousel?.on('init', this._lazyLoadMediaHandler.bind(this)); @@ -104,13 +104,13 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel { /** * Remove height restrictions on the media when the carousel is resized to let * it naturally render. - * @returns + * @returns */ protected _adaptiveHeightResizeHandler(): void { if (!this._carousel) { return; } - this._carousel.containerNode().style.removeProperty('max-height') + this._carousel.containerNode().style.removeProperty('max-height'); } /** @@ -119,20 +119,24 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel { * different aspect ratios). */ protected _adaptiveHeightSetHandler(): void { - if (!this._carousel) { - return; - } - const slides = this._carousel.slideNodes(); - const heights = this._carousel.slidesInView(true).map((index) => { - const firstChild = slides[index].querySelector("*"); - return firstChild ? firstChild.getBoundingClientRect().height : 0; - }) - const targetHeight = Math.max(...heights); - if (targetHeight > 0) { - this._carousel.containerNode().style.maxHeight = `${targetHeight}px`; - } else { - this._carousel.containerNode().style.removeProperty('max-height') - } + // Don't gather slide heights until the next browser re-paint to ensure the + // measured heights are correct on the media that has (potentially) just + // loaded. + window.requestAnimationFrame(() => { + if (!this._carousel) { + return; + } + const slides = this._carousel.slideNodes(); + const heights = this._carousel.slidesInView(true).map((index) => { + return slides[index].getBoundingClientRect().height; + }); + const targetHeight = Math.max(...heights); + if (targetHeight > 0) { + this._carousel.containerNode().style.maxHeight = `${targetHeight}px`; + } else { + this._carousel.containerNode().style.removeProperty('max-height'); + } + }); } /** @@ -270,7 +274,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel { * loaded. Adaptive height helps in that the carousel gets resized on each * img display to the correct size, but it still causes a minor noticeable * flicker until the height change is complete. - * + * * 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 diff --git a/src/scss/media-carousel.scss b/src/scss/media-carousel.scss index 5611628f..3e5c94f7 100644 --- a/src/scss/media-carousel.scss +++ b/src/scss/media-carousel.scss @@ -2,6 +2,12 @@ --video-max-height: none; } + +.embla__container { + // To support adaptive height animations. + transition: max-height 0.5s ease; +} + .embla__slide { flex: 0 0 100%; } \ No newline at end of file