Merge pull request #937 from dermotduffy/hidden-in-cast2
Filter hidden media players out of the media player menu
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
|
import { localize } from '../localize/localize';
|
||||||
import { CameraConfig, CardWideConfig } from '../types';
|
import { CameraConfig, CardWideConfig } from '../types';
|
||||||
import { EntityRegistryManager } from '../utils/ha/entity-registry';
|
import { EntityRegistryManager } from '../utils/ha/entity-registry';
|
||||||
|
import { Entity } from '../utils/ha/entity-registry/types';
|
||||||
import { RecordingSegmentsCache, RequestCache } from './cache';
|
import { RecordingSegmentsCache, RequestCache } from './cache';
|
||||||
import { CameraManagerEngine } from './engine';
|
import { CameraManagerEngine } from './engine';
|
||||||
|
import { CameraInitializationError } from './error';
|
||||||
import { FrigateCameraManagerEngine } from './frigate/engine-frigate';
|
import { FrigateCameraManagerEngine } from './frigate/engine-frigate';
|
||||||
import { GenericCameraManagerEngine } from './generic/engine-generic';
|
import { GenericCameraManagerEngine } from './generic/engine-generic';
|
||||||
import { Engine } from './types';
|
import { Engine } from './types';
|
||||||
@@ -51,7 +54,18 @@ export class CameraManagerEngineFactory {
|
|||||||
const cameraEntity = cameraConfig.camera_entity;
|
const cameraEntity = cameraConfig.camera_entity;
|
||||||
|
|
||||||
if (cameraEntity) {
|
if (cameraEntity) {
|
||||||
const entity = await this._entityRegistryManager.getEntity(hass, cameraEntity);
|
let entity: Entity | null;
|
||||||
|
try {
|
||||||
|
entity = await this._entityRegistryManager.getEntity(hass, cameraEntity);
|
||||||
|
} catch (e) {
|
||||||
|
// Throw a slightly friendlier exception (as a typo in the entity is
|
||||||
|
// likely to be a common failure mode).
|
||||||
|
throw new CameraInitializationError(
|
||||||
|
localize('error.no_camera_entity'),
|
||||||
|
cameraConfig,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (entity?.platform) {
|
switch (entity?.platform) {
|
||||||
case 'frigate':
|
case 'frigate':
|
||||||
engine = Engine.Frigate;
|
engine = Engine.Frigate;
|
||||||
|
|||||||
+105
-52
@@ -210,6 +210,8 @@ class FrigateCard extends LitElement {
|
|||||||
|
|
||||||
protected _conditionManager: CardConditionManager | null = null;
|
protected _conditionManager: CardConditionManager | null = null;
|
||||||
|
|
||||||
|
protected _mediaPlayers?: string[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._entityRegistryManager = new EntityRegistryManager(
|
this._entityRegistryManager = new EntityRegistryManager(
|
||||||
@@ -551,29 +553,12 @@ class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValidMediaPlayer = (entity: string): boolean => {
|
|
||||||
if (entity.startsWith('media_player.')) {
|
|
||||||
const stateObj = this._hass?.states[entity];
|
|
||||||
if (
|
|
||||||
stateObj &&
|
|
||||||
stateObj.state !== 'unavailable' &&
|
|
||||||
supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mediaPlayers = Object.keys(this._hass?.states || {}).filter(
|
|
||||||
isValidMediaPlayer,
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
mediaPlayers.length &&
|
this._mediaPlayers?.length &&
|
||||||
(this._view?.isViewerView() ||
|
(this._view?.isViewerView() ||
|
||||||
(this._view?.is('live') && selectedCameraConfig?.camera_entity))
|
(this._view?.is('live') && selectedCameraConfig?.camera_entity))
|
||||||
) {
|
) {
|
||||||
const mediaPlayerItems = mediaPlayers.map((playerEntityID) => {
|
const mediaPlayerItems = this._mediaPlayers.map((playerEntityID) => {
|
||||||
const title = getEntityTitle(this._hass, playerEntityID) || playerEntityID;
|
const title = getEntityTitle(this._hass, playerEntityID) || playerEntityID;
|
||||||
const state = this._hass?.states[playerEntityID];
|
const state = this._hass?.states[playerEntityID];
|
||||||
const playAction = createFrigateCardCustomAction('media_player', {
|
const playAction = createFrigateCardCustomAction('media_player', {
|
||||||
@@ -865,16 +850,45 @@ class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _initializeCameras(): Promise<void> {
|
protected async _initialize(
|
||||||
if (!this._hass || !this._cameraManager) {
|
hass: HomeAssistant,
|
||||||
return;
|
config: FrigateCardConfig,
|
||||||
}
|
cardWideConfig: CardWideConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
// Above arguments are taken (vs usage of `this`) as they must exist prior
|
||||||
|
// to initialization and this ensures it is the callers responsibility to
|
||||||
|
// verify that.
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// Side load Home Assistant elements used in the UI.
|
||||||
|
sideLoadHomeAssistantElements(),
|
||||||
|
|
||||||
|
// Load dynamic language imports.
|
||||||
|
loadLanguages(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await this._initializeCameras(hass, config, cardWideConfig);
|
||||||
|
|
||||||
|
// Don't reset the message which may be set to an error above. This sets the
|
||||||
|
// first view using the newly loaded cameras.
|
||||||
|
this._changeView({ resetMessage: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _initializeCameras(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: FrigateCardConfig,
|
||||||
|
cardWideConfig: CardWideConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
this._cameraManager = new CameraManager(
|
||||||
|
new CameraManagerEngineFactory(this._entityRegistryManager, cardWideConfig),
|
||||||
|
this._cardWideConfig,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._cameraManager.initializeCameras(
|
await this._cameraManager.initializeCameras(
|
||||||
this._hass,
|
hass,
|
||||||
this._entityRegistryManager,
|
this._entityRegistryManager,
|
||||||
this._getConfig().cameras,
|
config.cameras,
|
||||||
);
|
);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
@@ -888,36 +902,74 @@ class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't reset the message which may be set to an error above. This sets the
|
protected async _initializeMediaPlayers(hass: HomeAssistant): Promise<void> {
|
||||||
// first view using the newly loaded cameras.
|
const isValidMediaPlayer = (entityID: string): boolean => {
|
||||||
this._changeView({ resetMessage: false });
|
if (entityID.startsWith('media_player.')) {
|
||||||
|
const stateObj = this._hass?.states[entityID];
|
||||||
|
if (
|
||||||
|
stateObj &&
|
||||||
|
stateObj.state !== 'unavailable' &&
|
||||||
|
supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mediaPlayers = Object.keys(this._hass?.states || {}).filter(
|
||||||
|
isValidMediaPlayer,
|
||||||
|
);
|
||||||
|
let mediaPlayerEntities: Map<string, Entity>;
|
||||||
|
try {
|
||||||
|
mediaPlayerEntities = await this._entityRegistryManager.getEntities(
|
||||||
|
hass,
|
||||||
|
mediaPlayers,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Failing to fetch media player information is not considered
|
||||||
|
// sufficiently serious to block card startup.
|
||||||
|
errorToConsole(e as Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out entities that are marked as hidden (this information is not
|
||||||
|
// available in the HA state, only in the registry).
|
||||||
|
this._mediaPlayers = [...mediaPlayerEntities.values()]
|
||||||
|
.filter((entity) => !entity.hidden_by)
|
||||||
|
.map((entity) => entity.entity_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before each update.
|
* Called before each update.
|
||||||
*/
|
*/
|
||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
if (
|
|
||||||
this._cardWideConfig &&
|
|
||||||
(!this._cameraManager ||
|
|
||||||
changedProps.has('_config') ||
|
|
||||||
changedProps.has('_cardWideConfig'))
|
|
||||||
) {
|
|
||||||
this._cameraManager = new CameraManager(
|
|
||||||
new CameraManagerEngineFactory(
|
|
||||||
this._entityRegistryManager,
|
|
||||||
this._cardWideConfig,
|
|
||||||
),
|
|
||||||
this._cardWideConfig,
|
|
||||||
);
|
|
||||||
this._initializeCameras().then(() => this.requestUpdate());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedProps.has('_cardWideConfig')) {
|
if (changedProps.has('_cardWideConfig')) {
|
||||||
setPerformanceCSSStyles(this, this._cardWideConfig?.performance);
|
setPerformanceCSSStyles(this, this._cardWideConfig?.performance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._hass &&
|
||||||
|
!this._mediaPlayers &&
|
||||||
|
this._getConfig().menu.buttons.media_player.enabled
|
||||||
|
) {
|
||||||
|
// Media players are initialized outside the main initialization code (the
|
||||||
|
// `initialize` method) since they may be required depending on an
|
||||||
|
// overridable configuration value.
|
||||||
|
|
||||||
|
// We also want to initialize media players after since the main camera
|
||||||
|
// initialization since that may have fetched entity information that will
|
||||||
|
// be cached and re-used here (minor performance optimization). Only do
|
||||||
|
// this if the media player button is enabled to further limit the
|
||||||
|
// performance implications.
|
||||||
|
|
||||||
|
// Prevent a double initialization.
|
||||||
|
this._mediaPlayers = [];
|
||||||
|
this._initializeMediaPlayers(this._hass);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._view?.is('live')) {
|
if (this._view?.is('live')) {
|
||||||
import('./components/live/live.js');
|
import('./components/live/live.js');
|
||||||
} else if (this._view?.isGalleryView()) {
|
} else if (this._view?.isGalleryView()) {
|
||||||
@@ -1039,12 +1091,6 @@ class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the card.
|
|
||||||
*/
|
|
||||||
protected async _initialize(): Promise<void> {
|
|
||||||
await Promise.all([sideLoadHomeAssistantElements(), loadLanguages()]);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the element should be updated.
|
* Determine whether the element should be updated.
|
||||||
* @param changedProps The changed properties if any.
|
* @param changedProps The changed properties if any.
|
||||||
@@ -1053,8 +1099,15 @@ class FrigateCard extends LitElement {
|
|||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
// Load the relevant languages. Cannot do anything until then.
|
// Load the relevant languages. Cannot do anything until then.
|
||||||
if (this._initialized !== 'initialized') {
|
if (this._initialized !== 'initialized') {
|
||||||
if (this._initialized !== 'initializing') {
|
const config = this._getConfig();
|
||||||
this._initialize().then(() => {
|
if (
|
||||||
|
this._initialized !== 'initializing' &&
|
||||||
|
this._hass &&
|
||||||
|
config &&
|
||||||
|
this._cardWideConfig
|
||||||
|
) {
|
||||||
|
this._initialized = 'initializing';
|
||||||
|
this._initialize(this._hass, config, this._cardWideConfig).then(() => {
|
||||||
this._initialized = 'initialized';
|
this._initialized = 'initialized';
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-1
@@ -95,9 +95,12 @@ const LOW_PROFILE_DEFAULTS = {
|
|||||||
// Hide several buttons that are otherwise visible by default.
|
// Hide several buttons that are otherwise visible by default.
|
||||||
[`${CONF_MENU_BUTTONS_FRIGATE}.enabled`]: false,
|
[`${CONF_MENU_BUTTONS_FRIGATE}.enabled`]: false,
|
||||||
[`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false,
|
[`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false,
|
||||||
[`${CONF_MENU_BUTTONS_MEDIA_PLAYER}.enabled`]: false,
|
|
||||||
[`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false,
|
[`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false,
|
||||||
|
|
||||||
|
// If the media player button is present media player entity fetches are
|
||||||
|
// required on initialization.
|
||||||
|
[`${CONF_MENU_BUTTONS_MEDIA_PLAYER}.enabled`]: false,
|
||||||
|
|
||||||
// Disable all options in thumbnails.
|
// Disable all options in thumbnails.
|
||||||
[CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false,
|
[CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false,
|
||||||
[CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false,
|
[CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false,
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ export class EntityRegistryManager {
|
|||||||
return await this.getExtendedEntity(hass, entityID);
|
return await this.getExtendedEntity(hass, entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMatchingEntities(hass: HomeAssistant, func: (arg: Entity) => boolean): Promise<Entity[]> {
|
public async getMatchingEntities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
func: (arg: Entity) => boolean,
|
||||||
|
): Promise<Entity[]> {
|
||||||
await this.fetchEntityList(hass);
|
await this.fetchEntityList(hass);
|
||||||
return this._cache.getMatches(func);
|
return this._cache.getMatches(func);
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,21 @@ export class EntityRegistryManager {
|
|||||||
return extendedEntity;
|
return extendedEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getEntities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityIDs: string[],
|
||||||
|
): Promise<Map<string, Entity>> {
|
||||||
|
const output: Map<string, Entity> = new Map();
|
||||||
|
const _storeEntity = async (entityID: string): Promise<void> => {
|
||||||
|
const entity = await this.getEntity(hass, entityID);
|
||||||
|
if (entity) {
|
||||||
|
output.set(entityID, entity);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await Promise.all(entityIDs.map(_storeEntity));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
public async getExtendedEntities(
|
public async getExtendedEntities(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityIDs: string[],
|
entityIDs: string[],
|
||||||
|
|||||||
Reference in New Issue
Block a user