Merge pull request #794 from dermotduffy/loading-status-during-lazy-load
Refactor media load events to add unload support
This commit is contained in:
+16
-16
@@ -60,7 +60,7 @@ import {
|
|||||||
FrigateCardCustomAction,
|
FrigateCardCustomAction,
|
||||||
FrigateCardView,
|
FrigateCardView,
|
||||||
FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
||||||
MediaShowInfo,
|
MediaLoadedInfo,
|
||||||
MEDIA_TYPE_IMAGE,
|
MEDIA_TYPE_IMAGE,
|
||||||
MEDIA_TYPE_VIDEO,
|
MEDIA_TYPE_VIDEO,
|
||||||
MESSAGE_TYPE_PRIORITIES,
|
MESSAGE_TYPE_PRIORITIES,
|
||||||
@@ -96,7 +96,7 @@ import {
|
|||||||
} from './utils/ha/entity-registry.js';
|
} from './utils/ha/entity-registry.js';
|
||||||
import { ResolvedMediaCache } from './utils/ha/resolved-media.js';
|
import { ResolvedMediaCache } from './utils/ha/resolved-media.js';
|
||||||
import { supportsFeature } from './utils/ha/update.js';
|
import { supportsFeature } from './utils/ha/update.js';
|
||||||
import { isValidMediaShowInfo } from './utils/media-info.js';
|
import { isValidMediaLoadedInfo } from './utils/media-info.js';
|
||||||
import { View } from './view.js';
|
import { View } from './view.js';
|
||||||
import pkg from '../package.json';
|
import pkg from '../package.json';
|
||||||
import { ViewContext } from 'view';
|
import { ViewContext } from 'view';
|
||||||
@@ -186,8 +186,8 @@ export class FrigateCard extends LitElement {
|
|||||||
protected _updateTimerID: number | null = null;
|
protected _updateTimerID: number | null = null;
|
||||||
|
|
||||||
// Information about loaded media items.
|
// Information about loaded media items.
|
||||||
protected _currentMediaShowInfo: MediaShowInfo | null = null;
|
protected _currentMediaLoadedInfo: MediaLoadedInfo | null = null;
|
||||||
protected _lastValidMediaShowInfo: MediaShowInfo | null = null;
|
protected _lastValidMediaLoadedInfo: MediaLoadedInfo | null = null;
|
||||||
|
|
||||||
// Array of dynamic menu buttons to be added to menu.
|
// Array of dynamic menu buttons to be added to menu.
|
||||||
protected _dynamicMenuButtons: MenuButton[] = [];
|
protected _dynamicMenuButtons: MenuButton[] = [];
|
||||||
@@ -279,7 +279,7 @@ export class FrigateCard extends LitElement {
|
|||||||
fullscreen: screenfull.isEnabled && screenfull.isFullscreen,
|
fullscreen: screenfull.isEnabled && screenfull.isFullscreen,
|
||||||
camera: this._view?.camera,
|
camera: this._view?.camera,
|
||||||
state: this._hass?.states,
|
state: this._hass?.states,
|
||||||
mediaLoaded: !!this._currentMediaShowInfo,
|
mediaLoaded: !!this._currentMediaLoadedInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the components that need the new condition state. Passed directly
|
// Update the components that need the new condition state. Passed directly
|
||||||
@@ -934,7 +934,7 @@ export class FrigateCard extends LitElement {
|
|||||||
protected _changeView(args?: { view?: View; resetMessage?: boolean }): void {
|
protected _changeView(args?: { view?: View; resetMessage?: boolean }): void {
|
||||||
const changeView = (view: View): void => {
|
const changeView = (view: View): void => {
|
||||||
if (View.isMediaChange(this._view, view)) {
|
if (View.isMediaChange(this._view, view)) {
|
||||||
this._currentMediaShowInfo = null;
|
this._currentMediaLoadedInfo = null;
|
||||||
}
|
}
|
||||||
this._view = view;
|
this._view = view;
|
||||||
this._generateConditionState();
|
this._generateConditionState();
|
||||||
@@ -1629,17 +1629,17 @@ export class FrigateCard extends LitElement {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a new piece of media being shown.
|
* Handle a new piece of media being shown.
|
||||||
* @param e Event with MediaShowInfo details for the media.
|
* @param ev Event with MediaLoadedInfo details for the media.
|
||||||
*/
|
*/
|
||||||
protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void {
|
protected _mediaLoadedHandler(ev: CustomEvent<MediaLoadedInfo>): void {
|
||||||
const mediaShowInfo = e.detail;
|
const mediaLoadedInfo = ev.detail;
|
||||||
// In Safari, with WebRTC, 0x0 is occasionally returned during loading,
|
// In Safari, with WebRTC, 0x0 is occasionally returned during loading,
|
||||||
// so treat anything less than a safety cutoff as bogus.
|
// so treat anything less than a safety cutoff as bogus.
|
||||||
if (!isValidMediaShowInfo(mediaShowInfo)) {
|
if (!isValidMediaLoadedInfo(mediaLoadedInfo)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._lastValidMediaShowInfo = this._currentMediaShowInfo = mediaShowInfo;
|
this._lastValidMediaLoadedInfo = this._currentMediaLoadedInfo = mediaLoadedInfo;
|
||||||
|
|
||||||
// An update may be required to draw elements.
|
// An update may be required to draw elements.
|
||||||
this._generateConditionState();
|
this._generateConditionState();
|
||||||
@@ -1719,8 +1719,8 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
|
const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
|
||||||
if (aspectRatioMode == 'dynamic' && this._lastValidMediaShowInfo) {
|
if (aspectRatioMode == 'dynamic' && this._lastValidMediaLoadedInfo) {
|
||||||
return `${this._lastValidMediaShowInfo.width} / ${this._lastValidMediaShowInfo.height}`;
|
return `${this._lastValidMediaLoadedInfo.width} / ${this._lastValidMediaLoadedInfo.height}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio;
|
const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio;
|
||||||
@@ -1796,7 +1796,7 @@ export class FrigateCard extends LitElement {
|
|||||||
@frigate-card:message=${this._messageHandler.bind(this)}
|
@frigate-card:message=${this._messageHandler.bind(this)}
|
||||||
@frigate-card:view:change=${this._changeViewHandler.bind(this)}
|
@frigate-card:view:change=${this._changeViewHandler.bind(this)}
|
||||||
@frigate-card:view:change-context=${this._addViewContextHandler.bind(this)}
|
@frigate-card:view:change-context=${this._addViewContextHandler.bind(this)}
|
||||||
@frigate-card:media-show=${this._mediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._mediaLoadedHandler.bind(this)}
|
||||||
@frigate-card:render=${() => this.requestUpdate()}
|
@frigate-card:render=${() => this.requestUpdate()}
|
||||||
>
|
>
|
||||||
${renderMenuAbove ? this._renderMenu() : ''}
|
${renderMenuAbove ? this._renderMenu() : ''}
|
||||||
@@ -1940,8 +1940,8 @@ export class FrigateCard extends LitElement {
|
|||||||
* @returns The Lovelace card size in units of 50px.
|
* @returns The Lovelace card size in units of 50px.
|
||||||
*/
|
*/
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
if (this._lastValidMediaShowInfo) {
|
if (this._lastValidMediaLoadedInfo) {
|
||||||
return this._lastValidMediaShowInfo.height / 50;
|
return this._lastValidMediaLoadedInfo.height / 50;
|
||||||
}
|
}
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-15
@@ -35,7 +35,7 @@ import {
|
|||||||
LiveConfig,
|
LiveConfig,
|
||||||
LiveOverrides,
|
LiveOverrides,
|
||||||
LiveProvider,
|
LiveProvider,
|
||||||
MediaShowInfo,
|
MediaLoadedInfo,
|
||||||
Message,
|
Message,
|
||||||
TransitionEffect,
|
TransitionEffect,
|
||||||
WebRTCCardConfig,
|
WebRTCCardConfig,
|
||||||
@@ -46,15 +46,17 @@ import { getCameraIcon, getCameraTitle } from '../utils/camera.js';
|
|||||||
import { homeAssistantSignPath } from '../utils/ha';
|
import { homeAssistantSignPath } from '../utils/ha';
|
||||||
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
|
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
|
||||||
import {
|
import {
|
||||||
dispatchExistingMediaShowInfoAsEvent,
|
dispatchExistingMediaLoadedInfoAsEvent,
|
||||||
dispatchMediaShowEvent,
|
dispatchMediaShowEvent,
|
||||||
|
dispatchMediaUnloadedEvent,
|
||||||
} from '../utils/media-info.js';
|
} from '../utils/media-info.js';
|
||||||
import { dispatchViewContextChangeEvent, View } from '../view.js';
|
import { dispatchViewContextChangeEvent, View } from '../view.js';
|
||||||
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
||||||
import { Lazyload } from './embla-plugins/lazyload.js';
|
import { Lazyload } from './embla-plugins/lazyload.js';
|
||||||
import {
|
import {
|
||||||
FrigateCardMediaCarousel,
|
FrigateCardMediaCarousel,
|
||||||
wrapMediaShowEventForCarousel,
|
wrapMediaLoadedEventForCarousel,
|
||||||
|
wrapMediaUnloadedEventForCarousel,
|
||||||
} from './media-carousel.js';
|
} from './media-carousel.js';
|
||||||
import { dispatchErrorMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent } from './message.js';
|
||||||
import './next-prev-control.js';
|
import './next-prev-control.js';
|
||||||
@@ -101,9 +103,9 @@ export class FrigateCardLive extends LitElement {
|
|||||||
// foreground and background (in preload mode).
|
// foreground and background (in preload mode).
|
||||||
protected _intersectionObserver: IntersectionObserver;
|
protected _intersectionObserver: IntersectionObserver;
|
||||||
|
|
||||||
// MediaShowInfo object and message from the underlying live object. In the
|
// MediaLoadedInfo object and message from the underlying live object. In the
|
||||||
// case of pre-loading these may be propagated upwards later.
|
// case of pre-loading these may be propagated upwards later.
|
||||||
protected _savedMediaShowInfo: MediaShowInfo | null = null;
|
protected _savedMediaLoadedInfo: MediaLoadedInfo | null = null;
|
||||||
protected _messageReceivedPostRender = false;
|
protected _messageReceivedPostRender = false;
|
||||||
protected _renderKey = 0;
|
protected _renderKey = 0;
|
||||||
|
|
||||||
@@ -124,11 +126,11 @@ export class FrigateCardLive extends LitElement {
|
|||||||
if (
|
if (
|
||||||
!this._inBackground &&
|
!this._inBackground &&
|
||||||
!this._messageReceivedPostRender &&
|
!this._messageReceivedPostRender &&
|
||||||
this._savedMediaShowInfo
|
this._savedMediaLoadedInfo
|
||||||
) {
|
) {
|
||||||
// If this isn't being rendered in the background, the last render did not
|
// If this isn't being rendered in the background, the last render did not
|
||||||
// generate a message and there's a saved MediaInfo, dispatch it upwards.
|
// generate a message and there's a saved MediaInfo, dispatch it upwards.
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, this._savedMediaShowInfo);
|
dispatchExistingMediaLoadedInfoAsEvent(this, this._savedMediaLoadedInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger a re-render which may be necessary if the prior render resulted
|
// Trigger a re-render which may be necessary if the prior render resulted
|
||||||
@@ -219,8 +221,8 @@ export class FrigateCardLive extends LitElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@frigate-card:media-show=${(ev: CustomEvent<MediaShowInfo>) => {
|
@frigate-card:media:loaded=${(ev: CustomEvent<MediaLoadedInfo>) => {
|
||||||
this._savedMediaShowInfo = ev.detail;
|
this._savedMediaLoadedInfo = ev.detail;
|
||||||
if (this._inBackground) {
|
if (this._inBackground) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -509,8 +511,11 @@ export class FrigateCardLiveCarousel extends LitElement {
|
|||||||
.label=${getCameraTitle(this.hass, cameraConfig)}
|
.label=${getCameraTitle(this.hass, cameraConfig)}
|
||||||
.liveConfig=${config}
|
.liveConfig=${config}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@frigate-card:media-show=${(e: CustomEvent<MediaShowInfo>) => {
|
@frigate-card:media:loaded=${(ev: CustomEvent<MediaLoadedInfo>) => {
|
||||||
wrapMediaShowEventForCarousel(slideIndex, e);
|
wrapMediaLoadedEventForCarousel(slideIndex, ev);
|
||||||
|
}}
|
||||||
|
@frigate-card:media:unloaded=${(ev: CustomEvent<void>) => {
|
||||||
|
wrapMediaUnloadedEventForCarousel(slideIndex, ev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
</frigate-card-live-provider>
|
</frigate-card-live-provider>
|
||||||
@@ -739,9 +744,12 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
/**
|
/**
|
||||||
* Called before each update.
|
* Called before each update.
|
||||||
*/
|
*/
|
||||||
protected willUpdate(): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has('disabled')) {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this._isVideoMediaLoaded = false;
|
this._isVideoMediaLoaded = false;
|
||||||
|
dispatchMediaUnloadedEvent(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,7 +790,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
@frigate-card:media-show=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-ha>`
|
</frigate-card-live-ha>`
|
||||||
: provider === 'webrtc-card'
|
: provider === 'webrtc-card'
|
||||||
@@ -792,7 +800,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
.webRTCConfig=${this.liveConfig.webrtc_card}
|
.webRTCConfig=${this.liveConfig.webrtc_card}
|
||||||
@frigate-card:media-show=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-webrtc-card>`
|
</frigate-card-live-webrtc-card>`
|
||||||
: html` <frigate-card-live-jsmpeg
|
: html` <frigate-card-live-jsmpeg
|
||||||
@@ -801,7 +809,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
.jsmpegConfig=${this.liveConfig.jsmpeg}
|
.jsmpegConfig=${this.liveConfig.jsmpeg}
|
||||||
@frigate-card:media-show=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-jsmpeg>`}
|
</frigate-card-live-jsmpeg>`}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
|||||||
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||||
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
||||||
import type {
|
import type {
|
||||||
MediaShowInfo,
|
MediaLoadedInfo,
|
||||||
NextPreviousControlConfig,
|
NextPreviousControlConfig,
|
||||||
TitleControlConfig,
|
TitleControlConfig,
|
||||||
TransitionEffect,
|
TransitionEffect,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { dispatchFrigateCardEvent } from '../utils/basic';
|
import { dispatchFrigateCardEvent } from '../utils/basic';
|
||||||
import {
|
import {
|
||||||
createMediaShowInfo,
|
createMediaLoadedInfo,
|
||||||
dispatchExistingMediaShowInfoAsEvent,
|
dispatchExistingMediaLoadedInfoAsEvent,
|
||||||
isValidMediaShowInfo,
|
isValidMediaLoadedInfo,
|
||||||
} from '../utils/media-info.js';
|
} from '../utils/media-info.js';
|
||||||
import { CarouselSelect, EmblaCarouselPlugins, FrigateCardCarousel } from './carousel';
|
import { CarouselSelect, EmblaCarouselPlugins, FrigateCardCarousel } from './carousel';
|
||||||
import { AutoMediaType } from './embla-plugins/automedia.js';
|
import { AutoMediaType } from './embla-plugins/automedia.js';
|
||||||
@@ -27,58 +27,93 @@ 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`;
|
`data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
|
||||||
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
||||||
|
|
||||||
export interface CarouselMediaShowInfo {
|
export interface CarouselMediaLoadedInfo {
|
||||||
|
slide: number;
|
||||||
|
mediaLoadedInfo: MediaLoadedInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CarouselMediaUnloadedInfo {
|
||||||
slide: number;
|
slide: number;
|
||||||
mediaShowInfo: MediaShowInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a carousel media show event.
|
* Dispatch a carousel media loaded event.
|
||||||
* @param target The target to send it from.
|
* @param target The target to send it from.
|
||||||
* @param carouselMediaShowInfo The CarouselMediaShowInfo.
|
* @param carouselMediaLoadedInfo The CarouselMediaLoadedInfo.
|
||||||
*/
|
*/
|
||||||
const dispatchFrigateCardCarouselMediaShow = (
|
const dispatchFrigateCardCarouselMediaLoaded = (
|
||||||
target: EventTarget,
|
target: EventTarget,
|
||||||
carouselMediaShowInfo: CarouselMediaShowInfo,
|
carouselMediaLoadedInfo: CarouselMediaLoadedInfo,
|
||||||
): void => {
|
): void => {
|
||||||
dispatchFrigateCardEvent<CarouselMediaShowInfo>(
|
dispatchFrigateCardEvent<CarouselMediaLoadedInfo>(
|
||||||
target,
|
target,
|
||||||
'carousel:media-show',
|
'carousel:media:loaded',
|
||||||
carouselMediaShowInfo,
|
carouselMediaLoadedInfo,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn a MediaShowEvent into a CarouselMediaShowInfo.
|
* Dispatch a carousel media UNloaded event.
|
||||||
|
* @param target The target to send it from.
|
||||||
|
* @param carouselMediaUnloadedInfo The CarouselMediaUnloadedInfo.
|
||||||
|
*/
|
||||||
|
const dispatchFrigateCardCarouselMediaUnloaded = (
|
||||||
|
target: EventTarget,
|
||||||
|
carouselMediaUnloadedInfo: CarouselMediaUnloadedInfo,
|
||||||
|
): void => {
|
||||||
|
dispatchFrigateCardEvent<CarouselMediaUnloadedInfo>(
|
||||||
|
target,
|
||||||
|
'carousel:media:unloaded',
|
||||||
|
carouselMediaUnloadedInfo,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a MediaLoadedInfo into a CarouselMediaLoadedInfo.
|
||||||
* @param slide The slide number.
|
* @param slide The slide number.
|
||||||
* @param event The MediaShowEvent.
|
* @param event The MediaShowEvent.
|
||||||
*/
|
*/
|
||||||
export const wrapMediaShowEventForCarousel = (
|
export const wrapMediaLoadedEventForCarousel = (
|
||||||
slide: number,
|
slide: number,
|
||||||
event: CustomEvent<MediaShowInfo>,
|
event: CustomEvent<MediaLoadedInfo>,
|
||||||
) => {
|
) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
dispatchFrigateCardCarouselMediaShow(event.composedPath()[0], {
|
dispatchFrigateCardCarouselMediaLoaded(event.composedPath()[0], {
|
||||||
slide: slide,
|
slide: slide,
|
||||||
mediaShowInfo: event.detail,
|
mediaLoadedInfo: event.detail,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn a (stock) media load event into a CarouselMediaShowInfo.
|
* Turn a (raw, e.g. img) media load event into a CarouselMediaLoadedInfo.
|
||||||
* @param slide The slide number.
|
* @param slide The slide number.
|
||||||
* @param event The MediaShowEvent.
|
* @param event The MediaShowEvent.
|
||||||
*/
|
*/
|
||||||
export const wrapMediaLoadEventForCarousel = (slide: number, event: Event) => {
|
export const wrapRawMediaLoadedEventForCarousel = (slide: number, event: Event) => {
|
||||||
const mediaShowInfo = createMediaShowInfo(event);
|
const mediaLoadedInfo = createMediaLoadedInfo(event);
|
||||||
if (mediaShowInfo) {
|
if (mediaLoadedInfo) {
|
||||||
dispatchFrigateCardCarouselMediaShow(event.composedPath()[0], {
|
dispatchFrigateCardCarouselMediaLoaded(event.composedPath()[0], {
|
||||||
slide: slide,
|
slide: slide,
|
||||||
mediaShowInfo: mediaShowInfo,
|
mediaLoadedInfo: mediaLoadedInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a MediaUnloadedInfo into a CarouselMediaUnloadedInfo.
|
||||||
|
* @param slide The slide number.
|
||||||
|
* @param event The MediaUnloadedEvent.
|
||||||
|
*/
|
||||||
|
export const wrapMediaUnloadedEventForCarousel = (
|
||||||
|
slide: number,
|
||||||
|
event: CustomEvent<void>,
|
||||||
|
) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
dispatchFrigateCardCarouselMediaUnloaded(event.composedPath()[0], {
|
||||||
|
slide: slide,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
@customElement('frigate-card-media-carousel')
|
@customElement('frigate-card-media-carousel')
|
||||||
export class FrigateCardMediaCarousel extends LitElement {
|
export class FrigateCardMediaCarousel extends LitElement {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
@@ -99,8 +134,8 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public titlePopupConfig?: TitleControlConfig;
|
public titlePopupConfig?: TitleControlConfig;
|
||||||
|
|
||||||
// A "map" from slide number to MediaShowInfo object.
|
// A "map" from slide number to MediaLoadedInfo object.
|
||||||
protected _mediaShowInfo: Record<number, MediaShowInfo> = {};
|
protected _mediaLoadedInfo: Record<number, MediaLoadedInfo> = {};
|
||||||
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||||
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||||
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
||||||
@@ -233,13 +268,13 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this.addEventListener('frigate-card:media-show', this._boundAutoPlayHandler);
|
this.addEventListener('frigate-card:media:loaded', this._boundAutoPlayHandler);
|
||||||
this.addEventListener('frigate-card:media-show', this._boundAutoUnmuteHandler);
|
this.addEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler);
|
||||||
this.addEventListener(
|
this.addEventListener(
|
||||||
'frigate-card:media-show',
|
'frigate-card:media:loaded',
|
||||||
this._boundAdaptContainerHeightToSlide,
|
this._boundAdaptContainerHeightToSlide,
|
||||||
);
|
);
|
||||||
this.addEventListener('frigate-card:media-show', this._boundTitleHandler);
|
this.addEventListener('frigate-card:media:loaded', this._boundTitleHandler);
|
||||||
this._resizeObserver.observe(this);
|
this._resizeObserver.observe(this);
|
||||||
this._intersectionObserver.observe(this);
|
this._intersectionObserver.observe(this);
|
||||||
}
|
}
|
||||||
@@ -248,13 +283,13 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
* Component disconnected callback.
|
* Component disconnected callback.
|
||||||
*/
|
*/
|
||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
this.removeEventListener('frigate-card:media-show', this._boundAutoPlayHandler);
|
this.removeEventListener('frigate-card:media:loaded', this._boundAutoPlayHandler);
|
||||||
this.removeEventListener('frigate-card:media-show', this._boundAutoUnmuteHandler);
|
this.removeEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler);
|
||||||
this.removeEventListener(
|
this.removeEventListener(
|
||||||
'frigate-card:media-show',
|
'frigate-card:media:loaded',
|
||||||
this._boundAdaptContainerHeightToSlide,
|
this._boundAdaptContainerHeightToSlide,
|
||||||
);
|
);
|
||||||
this.removeEventListener('frigate-card:media-show', this._boundTitleHandler);
|
this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler);
|
||||||
this._resizeObserver.disconnect();
|
this._resizeObserver.disconnect();
|
||||||
this._intersectionObserver.disconnect();
|
this._intersectionObserver.disconnect();
|
||||||
|
|
||||||
@@ -312,7 +347,7 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
|
|
||||||
// Hack: This method attempts to measure the height of the selected slide in
|
// Hack: This method attempts to measure the height of the selected slide in
|
||||||
// order to set the overall carousel height to match. This method is
|
// order to set the overall carousel height to match. This method is
|
||||||
// triggered from `frigate-card:media-show` events, which are usually in
|
// triggered from `frigate-card:media:loaded` events, which are usually in
|
||||||
// turn triggered from media/metadata load events from media players.
|
// turn triggered from media/metadata load events from media players.
|
||||||
// Sufficient time needs to be allowed after these metadata load events to
|
// Sufficient time needs to be allowed after these metadata load events to
|
||||||
// allow the browser to repaint the element heights, so that we can get the
|
// allow the browser to repaint the element heights, so that we can get the
|
||||||
@@ -323,43 +358,57 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
/**
|
/**
|
||||||
* Fire a media show event when a slide is selected.
|
* Fire a media show event when a slide is selected.
|
||||||
*/
|
*/
|
||||||
protected _dispatchMediaShowInfo(): void {
|
protected _dispatchMediaLoadedInfo(): void {
|
||||||
const slideIndex = this.frigateCardCarousel()?.getCarouselSelected()?.index;
|
const slideIndex = this.frigateCardCarousel()?.getCarouselSelected()?.index;
|
||||||
if (slideIndex !== undefined && slideIndex in this._mediaShowInfo) {
|
if (slideIndex !== undefined && slideIndex in this._mediaLoadedInfo) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
|
dispatchExistingMediaLoadedInfoAsEvent(this, this._mediaLoadedInfo[slideIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a media-show event that is generated by a child component, saving the
|
* Handle a media:loaded event that is generated by a child component, saving the
|
||||||
* contents for future use when the relevant slide is actually shown.
|
* contents for future use when the relevant slide is actually shown.
|
||||||
* @param slideIndex The relevant slide index.
|
* @param slideIndex The relevant slide index.
|
||||||
* @param event The media-show event from the child component.
|
* @param event The media:loaded event from the child component.
|
||||||
*/
|
*/
|
||||||
protected _storeMediaShowInfo(event: CustomEvent<CarouselMediaShowInfo>): void {
|
protected _storeMediaLoadedInfo(event: CustomEvent<CarouselMediaLoadedInfo>): void {
|
||||||
// Don't allow the inbound event to propagate upwards, that will be
|
// Don't allow the inbound event to propagate upwards, that will be
|
||||||
// automatically done at the appropriate time as the slide is shown.
|
// automatically done at the appropriate time as the slide is shown.
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const mediaShowInfo = event.detail.mediaShowInfo;
|
const mediaLoadedInfo = event.detail.mediaLoadedInfo;
|
||||||
const slideIndex = event.detail.slide;
|
const slideIndex = event.detail.slide;
|
||||||
|
|
||||||
// isValidMediaShowInfo is used to prevent saving media info that will be
|
// isValidMediaLoadedInfo is used to prevent saving media info that will be
|
||||||
// rejected upstream (empty 1x1 images will be rejected here).
|
// rejected upstream (empty 1x1 images will be rejected here).
|
||||||
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
|
if (mediaLoadedInfo && isValidMediaLoadedInfo(mediaLoadedInfo)) {
|
||||||
this._mediaShowInfo[slideIndex] = mediaShowInfo;
|
this._mediaLoadedInfo[slideIndex] = mediaLoadedInfo;
|
||||||
if (this.frigateCardCarousel()?.getCarouselSelected()?.index === slideIndex) {
|
if (this.frigateCardCarousel()?.getCarouselSelected()?.index === slideIndex) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
dispatchExistingMediaLoadedInfoAsEvent(this, mediaLoadedInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a media loaded info (i.e. a media item has unloaded).
|
||||||
|
* @param event The CarouselMediaUnloadedInfo event.
|
||||||
|
*/
|
||||||
|
protected _removeMediaLoadedInfo(event: CustomEvent<CarouselMediaUnloadedInfo>): void {
|
||||||
|
const slideIndex = event.detail.slide;
|
||||||
|
delete this._mediaLoadedInfo[slideIndex];
|
||||||
|
|
||||||
|
// If the slide that unloaded is not visible, don't propagate the event upwards.
|
||||||
|
if (this.frigateCardCarousel()?.getCarouselSelected()?.index !== slideIndex) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html` <frigate-card-carousel
|
return html` <frigate-card-carousel
|
||||||
${ref(this._refCarousel)}
|
${ref(this._refCarousel)}
|
||||||
.carouselOptions=${this.carouselOptions}
|
.carouselOptions=${this.carouselOptions}
|
||||||
.carouselPlugins=${this.carouselPlugins}
|
.carouselPlugins=${this.carouselPlugins}
|
||||||
transitionEffect=${ifDefined(this.transitionEffect)}
|
transitionEffect=${ifDefined(this.transitionEffect)}
|
||||||
@frigate-card:carousel:init=${this._dispatchMediaShowInfo.bind(this)}
|
@frigate-card:carousel:init=${this._dispatchMediaLoadedInfo.bind(this)}
|
||||||
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
|
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
|
||||||
this._slideResizeObserver.disconnect();
|
this._slideResizeObserver.disconnect();
|
||||||
this._slideResizeObserver.observe(ev.detail.element);
|
this._slideResizeObserver.observe(ev.detail.element);
|
||||||
@@ -373,9 +422,10 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Dispatch media info.
|
// Dispatch media info.
|
||||||
this._dispatchMediaShowInfo();
|
this._dispatchMediaLoadedInfo();
|
||||||
}}
|
}}
|
||||||
@frigate-card:carousel:media-show=${this._storeMediaShowInfo.bind(this)}
|
@frigate-card:carousel:media:loaded=${this._storeMediaLoadedInfo.bind(this)}
|
||||||
|
@frigate-card:carousel:media:unloaded=${this._removeMediaLoadedInfo.bind(this)}
|
||||||
>
|
>
|
||||||
<slot slot="previous" name="previous"></slot>
|
<slot slot="previous" name="previous"></slot>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ import { guard } from 'lit/directives/guard.js';
|
|||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||||
import {
|
import { renderProgressIndicator } from '../components/message.js';
|
||||||
dispatchFrigateCardErrorEvent,
|
|
||||||
renderProgressIndicator,
|
|
||||||
} from '../components/message.js';
|
|
||||||
import viewerStyle from '../scss/viewer.scss';
|
import viewerStyle from '../scss/viewer.scss';
|
||||||
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +24,7 @@ import {
|
|||||||
FrigateBrowseMediaSource,
|
FrigateBrowseMediaSource,
|
||||||
frigateCardConfigDefaults,
|
frigateCardConfigDefaults,
|
||||||
FrigateCardMediaPlayer,
|
FrigateCardMediaPlayer,
|
||||||
MediaShowInfo,
|
MediaLoadedInfo,
|
||||||
TransitionEffect,
|
TransitionEffect,
|
||||||
ViewerConfig,
|
ViewerConfig,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
@@ -48,8 +45,8 @@ import { Lazyload } from './embla-plugins/lazyload.js';
|
|||||||
import {
|
import {
|
||||||
FrigateCardMediaCarousel,
|
FrigateCardMediaCarousel,
|
||||||
IMG_EMPTY,
|
IMG_EMPTY,
|
||||||
wrapMediaLoadEventForCarousel,
|
wrapRawMediaLoadedEventForCarousel,
|
||||||
wrapMediaShowEventForCarousel,
|
wrapMediaLoadedEventForCarousel,
|
||||||
} from './media-carousel.js';
|
} from './media-carousel.js';
|
||||||
import './next-prev-control.js';
|
import './next-prev-control.js';
|
||||||
import './title-control.js';
|
import './title-control.js';
|
||||||
@@ -619,7 +616,7 @@ export class FrigateCardViewerCarousel extends LitElement {
|
|||||||
.titlePopupConfig=${this.viewerConfig?.controls.title}
|
.titlePopupConfig=${this.viewerConfig?.controls.title}
|
||||||
transitionEffect=${this._getTransitionEffect()}
|
transitionEffect=${this._getTransitionEffect()}
|
||||||
@frigate-card:media-carousel:select=${this._setViewHandler.bind(this)}
|
@frigate-card:media-carousel:select=${this._setViewHandler.bind(this)}
|
||||||
@frigate-card:media-show=${this._recordingSeekHandler.bind(this)}
|
@frigate-card:media:loaded=${this._recordingSeekHandler.bind(this)}
|
||||||
>
|
>
|
||||||
<frigate-card-next-previous-control
|
<frigate-card-next-previous-control
|
||||||
slot="previous"
|
slot="previous"
|
||||||
@@ -706,8 +703,8 @@ export class FrigateCardViewerCarousel extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.media=${mediaToRender}
|
.media=${mediaToRender}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@frigate-card:media-show=${(e: CustomEvent<MediaShowInfo>) => {
|
@frigate-card:media:loaded=${(e: CustomEvent<MediaLoadedInfo>) => {
|
||||||
wrapMediaShowEventForCarousel(slideIndex, e);
|
wrapMediaLoadedEventForCarousel(slideIndex, e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
</frigate-card-ha-hls-player>`
|
</frigate-card-ha-hls-player>`
|
||||||
@@ -743,7 +740,7 @@ export class FrigateCardViewerCarousel extends LitElement {
|
|||||||
!lazyLoad ||
|
!lazyLoad ||
|
||||||
lazyloadPlugin?.hasLazyloaded(slideIndex)
|
lazyloadPlugin?.hasLazyloaded(slideIndex)
|
||||||
) {
|
) {
|
||||||
wrapMediaLoadEventForCarousel(slideIndex, e);
|
wrapRawMediaLoadedEventForCarousel(slideIndex, e);
|
||||||
}
|
}
|
||||||
}}"
|
}}"
|
||||||
/>`}
|
/>`}
|
||||||
|
|||||||
+1
-1
@@ -1203,7 +1203,7 @@ export interface BrowseMediaNeighbors {
|
|||||||
nextIndex: number | null;
|
nextIndex: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaShowInfo {
|
export interface MediaLoadedInfo {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-16
@@ -1,15 +1,17 @@
|
|||||||
import { MediaShowInfo } from '../types.js';
|
import { MediaLoadedInfo } from '../types.js';
|
||||||
import { dispatchFrigateCardEvent } from './basic.js';
|
import { dispatchFrigateCardEvent } from './basic.js';
|
||||||
|
|
||||||
const MEDIA_INFO_HEIGHT_CUTOFF = 50;
|
const MEDIA_INFO_HEIGHT_CUTOFF = 50;
|
||||||
const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF;
|
const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a MediaShowInfo object.
|
* Create a MediaLoadedInfo object.
|
||||||
* @param source An event or HTMLElement that should be used as a source.
|
* @param source An event or HTMLElement that should be used as a source.
|
||||||
* @returns A new MediaShowInfo object or null if one could not be created.
|
* @returns A new MediaLoadedInfo object or null if one could not be created.
|
||||||
*/
|
*/
|
||||||
export function createMediaShowInfo(source: Event | HTMLElement): MediaShowInfo | null {
|
export function createMediaLoadedInfo(
|
||||||
|
source: Event | HTMLElement,
|
||||||
|
): MediaLoadedInfo | null {
|
||||||
let target: HTMLElement | EventTarget;
|
let target: HTMLElement | EventTarget;
|
||||||
if (source instanceof Event) {
|
if (source instanceof Event) {
|
||||||
target = source.composedPath()[0];
|
target = source.composedPath()[0];
|
||||||
@@ -37,7 +39,7 @@ export function createMediaShowInfo(source: Event | HTMLElement): MediaShowInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a Frigate card media show event.
|
* Dispatch a Frigate card media loaded event.
|
||||||
* @param element The element to send the event.
|
* @param element The element to send the event.
|
||||||
* @param source An event or HTMLElement that should be used as a source.
|
* @param source An event or HTMLElement that should be used as a source.
|
||||||
*/
|
*/
|
||||||
@@ -45,31 +47,39 @@ export function dispatchMediaShowEvent(
|
|||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
source: Event | HTMLElement,
|
source: Event | HTMLElement,
|
||||||
): void {
|
): void {
|
||||||
const mediaShowInfo = createMediaShowInfo(source);
|
const mediaLoadedInfo = createMediaLoadedInfo(source);
|
||||||
if (mediaShowInfo) {
|
if (mediaLoadedInfo) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(element, mediaShowInfo);
|
dispatchExistingMediaLoadedInfoAsEvent(element, mediaLoadedInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a pre-existing MediaShowInfo object as an event.
|
* Dispatch a pre-existing MediaLoadedInfo object as an event.
|
||||||
* @param element The element to send the event.
|
* @param element The element to send the event.
|
||||||
* @param mediaShowInfo The MediaShowInfo object to send.
|
* @param MediaLoadedInfo The MediaLoadedInfo object to send.
|
||||||
*/
|
*/
|
||||||
export function dispatchExistingMediaShowInfoAsEvent(
|
export function dispatchExistingMediaLoadedInfoAsEvent(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
mediaShowInfo: MediaShowInfo,
|
MediaLoadedInfo: MediaLoadedInfo,
|
||||||
): void {
|
): void {
|
||||||
dispatchFrigateCardEvent<MediaShowInfo>(element, 'media-show', mediaShowInfo);
|
dispatchFrigateCardEvent<MediaLoadedInfo>(element, 'media:loaded', MediaLoadedInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a MediaShowInfo object is valid/acceptable.
|
* Determine if a MediaLoadedInfo object is valid/acceptable.
|
||||||
* @param info The MediaShowInfo object.
|
* @param info The MediaLoadedInfo object.
|
||||||
* @returns True if the object is valid, false otherwise.
|
* @returns True if the object is valid, false otherwise.
|
||||||
*/
|
*/
|
||||||
export function isValidMediaShowInfo(info: MediaShowInfo): boolean {
|
export function isValidMediaLoadedInfo(info: MediaLoadedInfo): boolean {
|
||||||
return (
|
return (
|
||||||
info.height >= MEDIA_INFO_HEIGHT_CUTOFF && info.width >= MEDIA_INFO_WIDTH_CUTOFF
|
info.height >= MEDIA_INFO_HEIGHT_CUTOFF && info.width >= MEDIA_INFO_WIDTH_CUTOFF
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch a media unloaded event.
|
||||||
|
* @param element The element to send the event.
|
||||||
|
*/
|
||||||
|
export function dispatchMediaUnloadedEvent(element: HTMLElement): void {
|
||||||
|
dispatchFrigateCardEvent(element, 'media:unloaded');
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -152,7 +152,7 @@ export class View {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a view is of a piece of media (including the media viewer,
|
* Determine if a view is of a piece of media (including the media viewer,
|
||||||
* live view, image view -- anything that can create a MediaShowInfo event).
|
* live view, image view -- anything that can create a MediaLoadedInfo event).
|
||||||
*/
|
*/
|
||||||
public isAnyMediaView(): boolean {
|
public isAnyMediaView(): boolean {
|
||||||
return this.isViewerView() || this.is('live') || this.is('image');
|
return this.isViewerView() || this.is('live') || this.is('image');
|
||||||
|
|||||||
Reference in New Issue
Block a user