161 lines
5.1 KiB
TypeScript
161 lines
5.1 KiB
TypeScript
import { isEqual } from 'lodash-es';
|
|
import { CameraManager } from '../camera-manager/manager';
|
|
import { StatusBarConfig, StatusBarItem } from '../config/types';
|
|
import { MediaLoadedInfo } from '../types';
|
|
import { View } from '../view/view';
|
|
import { CardStatusBarAPI } from './types';
|
|
|
|
const RESOLUTION_TOLERANCE_PCT = 0.01;
|
|
|
|
export class StatusBarItemManager {
|
|
protected _api: CardStatusBarAPI;
|
|
|
|
constructor(api: CardStatusBarAPI) {
|
|
this._api = api;
|
|
}
|
|
|
|
protected _items: StatusBarItem[] = [];
|
|
protected _dynamicItems: StatusBarItem[] = [];
|
|
|
|
public addDynamicStatusBarItem(item: StatusBarItem): void {
|
|
if (!this._dynamicItems.includes(item)) {
|
|
this._dynamicItems.push(item);
|
|
}
|
|
this._api.getCardElementManager().update();
|
|
}
|
|
|
|
public removeDynamicStatusBarItem(item: StatusBarItem): void {
|
|
this._dynamicItems = this._dynamicItems.filter(
|
|
(existingItem) => !isEqual(existingItem, item),
|
|
);
|
|
this._api.getCardElementManager().update();
|
|
}
|
|
|
|
public removeAllDynamicStatusBarItems(): void {
|
|
this._dynamicItems = [];
|
|
this._api.getCardElementManager().update();
|
|
}
|
|
|
|
public calculateItems(options?: {
|
|
statusConfig?: StatusBarConfig | null;
|
|
cameraManager?: CameraManager | null;
|
|
view?: View | null;
|
|
mediaLoadedInfo?: MediaLoadedInfo | null;
|
|
}): StatusBarItem[] {
|
|
const cameraMetadata = options?.view
|
|
? options?.cameraManager?.getCameraMetadata(options?.view?.camera)
|
|
: null;
|
|
const engineLogoIcon = cameraMetadata?.engineLogo ?? null;
|
|
const title = options?.view?.is('live')
|
|
? cameraMetadata?.title ?? null
|
|
: options?.view?.isAnyMediaView()
|
|
? options?.view.queryResults?.getSelectedResult()?.getTitle() ?? null
|
|
: null;
|
|
const resolution = options?.mediaLoadedInfo
|
|
? this._calculateResolution(options?.mediaLoadedInfo)
|
|
: null;
|
|
const technology = options?.mediaLoadedInfo?.technology?.length
|
|
? options?.mediaLoadedInfo.technology[0]
|
|
: null;
|
|
|
|
return [
|
|
...(title
|
|
? [
|
|
{
|
|
type: 'custom:frigate-card-status-bar-string' as const,
|
|
string: title,
|
|
expand: true,
|
|
sufficient: true,
|
|
...options?.statusConfig?.items.title,
|
|
},
|
|
]
|
|
: []),
|
|
|
|
...(resolution
|
|
? [
|
|
{
|
|
type: 'custom:frigate-card-status-bar-string' as const,
|
|
string: resolution,
|
|
...options?.statusConfig?.items.resolution,
|
|
},
|
|
]
|
|
: []),
|
|
|
|
...(technology && technology === 'webrtc'
|
|
? [
|
|
{
|
|
type: 'custom:frigate-card-status-bar-icon' as const,
|
|
icon: 'mdi:webrtc',
|
|
...options?.statusConfig?.items.technology,
|
|
},
|
|
]
|
|
: !!technology
|
|
? [
|
|
{
|
|
type: 'custom:frigate-card-status-bar-string' as const,
|
|
string: technology.toUpperCase(),
|
|
...options?.statusConfig?.items.technology,
|
|
},
|
|
]
|
|
: []),
|
|
|
|
...(engineLogoIcon
|
|
? [
|
|
{
|
|
type: 'custom:frigate-card-status-bar-image' as const,
|
|
image: engineLogoIcon,
|
|
...options?.statusConfig?.items.engine,
|
|
},
|
|
]
|
|
: []),
|
|
...this._dynamicItems,
|
|
];
|
|
}
|
|
|
|
protected _matchesWidthHeight(
|
|
mediaLoadedInfo: MediaLoadedInfo | null,
|
|
width: number,
|
|
height: number,
|
|
): boolean {
|
|
const widthMin = width * (1 - RESOLUTION_TOLERANCE_PCT);
|
|
const widthMax = width * (1 + RESOLUTION_TOLERANCE_PCT);
|
|
const heightMin = height * (1 - RESOLUTION_TOLERANCE_PCT);
|
|
const heightMax = height * (1 + RESOLUTION_TOLERANCE_PCT);
|
|
|
|
const matchesDimension = (val: number, min: number, max: number): boolean => {
|
|
return val >= min && val <= max;
|
|
};
|
|
|
|
// Allows matching the resolution width and height in either orientation,
|
|
// and within RESOLUTION_TOLERANCE_PCT of the resolution.
|
|
return (
|
|
!!mediaLoadedInfo &&
|
|
((matchesDimension(mediaLoadedInfo.width, widthMin, widthMax) &&
|
|
matchesDimension(mediaLoadedInfo.height, heightMin, heightMax)) ||
|
|
(matchesDimension(mediaLoadedInfo.height, widthMin, widthMax) &&
|
|
matchesDimension(mediaLoadedInfo.width, heightMin, heightMax)))
|
|
);
|
|
}
|
|
|
|
protected _calculateResolution(mediaLoadedInfo: MediaLoadedInfo): string {
|
|
// Ordered roughly by a guess at most common towards the top.
|
|
if (this._matchesWidthHeight(mediaLoadedInfo, 1920, 1080)) {
|
|
return '1080p';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 1280, 720)) {
|
|
return '720p';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 640, 480)) {
|
|
return 'VGA';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 3840, 2160)) {
|
|
return '4K';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 720, 480)) {
|
|
return '480p';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 720, 576)) {
|
|
return '576p';
|
|
} else if (this._matchesWidthHeight(mediaLoadedInfo, 7680, 4320)) {
|
|
return '8K';
|
|
} else {
|
|
return `${mediaLoadedInfo.width}x${mediaLoadedInfo.height}`;
|
|
}
|
|
}
|
|
}
|