Merge pull request #261 from dermotduffy/jsmpeg-loading-improvements

Improve JSMPEG loading experience
This commit is contained in:
Dermot Duffy
2022-01-17 20:31:44 -08:00
committed by GitHub
4 changed files with 66 additions and 57 deletions
+1 -1
View File
@@ -22,7 +22,7 @@
"@material/rtl": "^13.0.0", "@material/rtl": "^13.0.0",
"custom-card-helpers": "^1.8.0", "custom-card-helpers": "^1.8.0",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"embla-carousel": "^5.0.1", "embla-carousel": "^6.1.0",
"home-assistant-js-websocket": "^5.11.1", "home-assistant-js-websocket": "^5.11.1",
"lit": "^2.0.2", "lit": "^2.0.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
+37 -38
View File
@@ -754,46 +754,46 @@ export class FrigateCardLiveJSMPEG extends LitElement {
/** /**
* Create a JSMPEG player. * Create a JSMPEG player.
* @param url The URL for the player to connect to.
* @returns A JSMPEG player. * @returns A JSMPEG player.
*/ */
protected _createJSMPEGPlayer(url: string): JSMpeg.VideoElement { protected async _createJSMPEGPlayer(url: string): Promise<JSMpeg.VideoElement> {
let videoDecoded = false; return new Promise<JSMpeg.VideoElement>((resolve) => {
let videoDecoded = false;
const jsmpegOptions = { const player = new JSMpeg.VideoElement(
pauseWhenHidden: false, this,
protocols: [], url,
audio: false, {
videoBufferSize: 1024 * 1024 * 4, canvas: this._jsmpegCanvasElement,
onVideoDecode: () => { hooks: {
// This is the only callback that is called after the dimensions play: () => {
// are available. It's called on every frame decode, so just dispatchPlayEvent(this);
// ignore any subsequent calls. },
if (!videoDecoded && this._jsmpegCanvasElement) { pause: () => {
videoDecoded = true; dispatchPauseEvent(this);
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);
}, },
}, },
}, {
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(); const url = await this._getURL();
if (url) { if (url) {
this._jsmpegVideoPlayer = this._createJSMPEGPlayer(url); this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(url);
this._refreshPlayerTimerID = window.setTimeout(() => { this._refreshPlayerTimerID = window.setTimeout(() => {
this.requestUpdate(); this.requestUpdate();
}, (URL_SIGN_EXPIRY_SECONDS - URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000); }, (URL_SIGN_EXPIRY_SECONDS - URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
+22 -18
View File
@@ -92,7 +92,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
carousel?.on('init', this._adaptiveHeightSetHandler.bind(this)); carousel?.on('init', this._adaptiveHeightSetHandler.bind(this));
carousel?.on('select', this._adaptiveHeightSetHandler.bind(this)); carousel?.on('select', this._adaptiveHeightSetHandler.bind(this));
carousel?.on('resize', this._adaptiveHeightSetHandler.bind(this)); carousel?.on('resize', this._adaptiveHeightSetHandler.bind(this));
if (this._getLazyLoadCount() != null) { if (this._getLazyLoadCount() != null) {
// Load media as the carousel is moved (if lazy loading is in use). // Load media as the carousel is moved (if lazy loading is in use).
carousel?.on('init', this._lazyLoadMediaHandler.bind(this)); 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 * Remove height restrictions on the media when the carousel is resized to let
* it naturally render. * it naturally render.
* @returns * @returns
*/ */
protected _adaptiveHeightResizeHandler(): void { protected _adaptiveHeightResizeHandler(): void {
if (!this._carousel) { if (!this._carousel) {
return; 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). * different aspect ratios).
*/ */
protected _adaptiveHeightSetHandler(): void { protected _adaptiveHeightSetHandler(): void {
if (!this._carousel) { // Don't gather slide heights until the next browser re-paint to ensure the
return; // measured heights are correct on the media that has (potentially) just
} // loaded.
const slides = this._carousel.slideNodes(); window.requestAnimationFrame(() => {
const heights = this._carousel.slidesInView(true).map((index) => { if (!this._carousel) {
const firstChild = slides[index].querySelector("*"); return;
return firstChild ? firstChild.getBoundingClientRect().height : 0; }
}) const slides = this._carousel.slideNodes();
const targetHeight = Math.max(...heights); const heights = this._carousel.slidesInView(true).map((index) => {
if (targetHeight > 0) { return slides[index].getBoundingClientRect().height;
this._carousel.containerNode().style.maxHeight = `${targetHeight}px`; });
} else { const targetHeight = Math.max(...heights);
this._carousel.containerNode().style.removeProperty('max-height') 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 * 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 * img display to the correct size, but it still causes a minor noticeable
* flicker until the height change is complete. * flicker until the height change is complete.
* *
* To avoid this, we use a 16:9 dummy image at first (most * 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 * 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 * dummy images are replaced with dummy images that match the aspect ratio
+6
View File
@@ -2,6 +2,12 @@
--video-max-height: none; --video-max-height: none;
} }
.embla__container {
// To support adaptive height animations.
transition: max-height 0.5s ease;
}
.embla__slide { .embla__slide {
flex: 0 0 100%; flex: 0 0 100%;
} }