Add new method of casting live cameras based on dashboards.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { CameraConfig } from '../config/types';
|
||||
import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA } from '../const';
|
||||
import { ViewMedia } from '../view/media';
|
||||
import { ViewMediaClassifier } from '../view/media-classifier';
|
||||
import { localize } from '../localize/localize';
|
||||
import { errorToConsole } from '../utils/basic';
|
||||
import { Entity } from '../utils/ha/entity-registry/types';
|
||||
import { supportsFeature } from '../utils/ha/update';
|
||||
import { ViewMedia } from '../view/media';
|
||||
import { ViewMediaClassifier } from '../view/media-classifier';
|
||||
import { CardMediaPlayerAPI } from './types';
|
||||
|
||||
export class MediaPlayerManager {
|
||||
@@ -76,11 +78,27 @@ export class MediaPlayerManager {
|
||||
}
|
||||
|
||||
public async playLive(mediaPlayer: string, cameraID: string): Promise<void> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
const cameraConfig = this._api
|
||||
.getCameraManager()
|
||||
.getStore()
|
||||
.getCameraConfig(cameraID);
|
||||
if (!cameraConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cameraConfig.cast?.method === 'dashboard') {
|
||||
await this._playLiveDashboard(mediaPlayer, cameraConfig);
|
||||
} else {
|
||||
await this._playLiveStandard(mediaPlayer, cameraID, cameraConfig);
|
||||
}
|
||||
}
|
||||
|
||||
protected async _playLiveStandard(
|
||||
mediaPlayer: string,
|
||||
cameraID: string,
|
||||
cameraConfig: CameraConfig,
|
||||
): Promise<void> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
const cameraEntity = cameraConfig?.camera_entity ?? null;
|
||||
|
||||
if (!hass || !cameraEntity) {
|
||||
@@ -102,6 +120,34 @@ export class MediaPlayerManager {
|
||||
});
|
||||
}
|
||||
|
||||
protected async _playLiveDashboard(
|
||||
mediaPlayer: string,
|
||||
cameraConfig: CameraConfig,
|
||||
): Promise<void> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
if (!hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dashboardConfig = cameraConfig.cast?.dashboard;
|
||||
if (!dashboardConfig?.dashboard_path || !dashboardConfig?.view_path) {
|
||||
this._api.getMessageManager().setMessageIfHigherPriority({
|
||||
type: 'error',
|
||||
icon: 'mdi:cast',
|
||||
message: localize('error.no_dashboard_or_view'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// When this bug is closed, a query string could be included:
|
||||
// https://github.com/home-assistant/core/issues/98316
|
||||
await hass.callService('cast', 'show_lovelace_view', {
|
||||
entity_id: mediaPlayer,
|
||||
dashboard_path: dashboardConfig.dashboard_path,
|
||||
view_path: dashboardConfig.view_path,
|
||||
});
|
||||
}
|
||||
|
||||
public async playMedia(mediaPlayer: string, media?: ViewMedia | null): Promise<void> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
|
||||
|
||||
@@ -23,16 +23,53 @@ export class QueryStringManager {
|
||||
|
||||
public executeNonViewRelated = (): void => {
|
||||
this._executeNonViewRelated(this._calculateIntent());
|
||||
}
|
||||
};
|
||||
|
||||
public executeViewRelated = (): void => {
|
||||
this._executeViewRelated(this._calculateIntent());
|
||||
}
|
||||
};
|
||||
|
||||
public executeAll = (): void => {
|
||||
const intent = this._calculateIntent();
|
||||
this._executeViewRelated(intent);
|
||||
this._executeNonViewRelated(intent);
|
||||
};
|
||||
|
||||
public generateQueryString(action: FrigateCardCustomAction): string | null {
|
||||
const baseKey =
|
||||
'frigate-card-action.' + (action.card_id ? `${action.card_id}.` : '');
|
||||
|
||||
switch (action.frigate_card_action) {
|
||||
case 'camera_select':
|
||||
case 'live_substream_select':
|
||||
return new URLSearchParams([
|
||||
[baseKey + action.frigate_card_action, action.camera],
|
||||
]).toString();
|
||||
case 'camera_ui':
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'default':
|
||||
case 'diagnostics':
|
||||
case 'download':
|
||||
case 'expand':
|
||||
case 'image':
|
||||
case 'live':
|
||||
case 'menu_toggle':
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
case 'snapshot':
|
||||
case 'snapshots':
|
||||
case 'timeline':
|
||||
return new URLSearchParams([
|
||||
[baseKey + action.frigate_card_action, ''],
|
||||
]).toString();
|
||||
default:
|
||||
console.warn(
|
||||
`Frigate card cannot convert unsupported action to query string:`,
|
||||
action,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected _executeViewRelated(intent: QueryStringViewIntent): void {
|
||||
|
||||
@@ -163,6 +163,8 @@ export interface CardMediaPlayerAPI {
|
||||
getHASSManager(): HASSManager;
|
||||
getCameraManager(): CameraManager;
|
||||
getEntityRegistryManager(): EntityRegistryManager;
|
||||
getMessageManager(): MessageManager;
|
||||
getQueryStringManager(): QueryStringManager;
|
||||
}
|
||||
|
||||
export interface CardMessageAPI {
|
||||
|
||||
@@ -32,6 +32,8 @@ export const FRIGATE_CARD_VIEWS_USER_SPECIFIED = [
|
||||
'image',
|
||||
'timeline',
|
||||
] as const;
|
||||
export type FrigateCardUserSpecifiedView =
|
||||
(typeof FRIGATE_CARD_VIEWS_USER_SPECIFIED)[number];
|
||||
|
||||
const FRIGATE_CARD_VIEWS = [
|
||||
...FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
||||
@@ -213,6 +215,7 @@ const FRIGATE_CARD_GENERAL_ACTIONS = [
|
||||
'screenshot',
|
||||
'unmute',
|
||||
] as const;
|
||||
export type FrigateCardGeneralAction = (typeof FRIGATE_CARD_GENERAL_ACTIONS)[number];
|
||||
|
||||
const FRIGATE_CARD_ACTIONS = [
|
||||
...FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
||||
@@ -237,6 +240,9 @@ const frigateCardCameraSelectActionSchema = frigateCardCustomActionsBaseSchema.e
|
||||
frigate_card_action: z.literal('camera_select'),
|
||||
camera: z.string(),
|
||||
});
|
||||
export type FrigateCardCameraSelectAction = z.infer<
|
||||
typeof frigateCardCameraSelectActionSchema
|
||||
>;
|
||||
|
||||
const frigateCardLiveDependencySelectActionSchema =
|
||||
frigateCardCustomActionsBaseSchema.extend({
|
||||
@@ -920,6 +926,24 @@ const liveOverridesSchema = z
|
||||
.optional();
|
||||
export type LiveOverrides = z.infer<typeof liveOverridesSchema>;
|
||||
|
||||
// *************************************************************************
|
||||
// Cast Configuration
|
||||
// *************************************************************************
|
||||
|
||||
const castConfigDefault = {
|
||||
method: 'standard' as const,
|
||||
};
|
||||
|
||||
export const castSchema = z.object({
|
||||
method: z.enum(['standard', 'dashboard']).default(castConfigDefault.method).optional(),
|
||||
dashboard: z
|
||||
.object({
|
||||
dashboard_path: z.string().optional(),
|
||||
view_path: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// *************************************************************************
|
||||
// Camera Configuration
|
||||
// *************************************************************************
|
||||
@@ -1035,6 +1059,8 @@ export const cameraConfigSchema = z
|
||||
image: liveImageConfigSchema.default(cameraConfigDefault.image),
|
||||
jsmpeg: jsmpegConfigSchema.optional(),
|
||||
webrtc_card: webrtcCardConfigSchema.optional(),
|
||||
|
||||
cast: castSchema.optional(),
|
||||
})
|
||||
.default(cameraConfigDefault);
|
||||
export type CameraConfig = z.infer<typeof cameraConfigSchema>;
|
||||
|
||||
@@ -444,6 +444,7 @@
|
||||
"no_camera_entity_for_triggers": "A camera entity is required in order to autodetect triggers",
|
||||
"no_camera_id": "Could not determine camera id for the following camera, may need to set 'id' parameter manually",
|
||||
"no_camera_name": "Could not determine a Frigate camera name for camera (or one of its dependents), please specify either 'camera_entity' or 'camera_name'",
|
||||
"no_dashboard_or_view": "Both 'dashboard_path' and 'view_path' parameters are required for the 'dashboard' cast method",
|
||||
"no_live_camera": "The camera_entity parameter must be set and valid for this live provider",
|
||||
"no_visible_cameras": "No visible cameras found, you must configure at least one non-hidden camera",
|
||||
"reconnecting": "Reconnecting",
|
||||
|
||||
@@ -436,6 +436,7 @@
|
||||
"no_camera_entity_for_triggers": "È necessaria un'entità telecamera per rilevare automaticamente i trigger",
|
||||
"no_camera_id": "Impossibile determinare l'ID della telecamera , potrebbe essere necessario impostare manualmente il parametro 'ID'",
|
||||
"no_camera_name": "Impossibile determinare un nome della telecamera in Frigate, si prega di specificare 'camera_enty' o 'camera_name'",
|
||||
"no_dashboard_or_view": "",
|
||||
"no_live_camera": "Il parametro fotocamera_enty deve essere impostato e valido per questo provider live",
|
||||
"no_visible_cameras": "Nessuna telecamera visibile trovata, è necessario configurare almeno una telecamera non nascosta",
|
||||
"reconnecting": "Riconnessione",
|
||||
|
||||
@@ -443,6 +443,7 @@
|
||||
"no_camera_entity_for_triggers": "Uma entidade de câmera é necessária para detectar automaticamente os gatilhos",
|
||||
"no_camera_id": "Não foi possível determinar o ID da câmera para a câmera a seguir, pode ser necessário definir o parâmetro 'id' manualmente",
|
||||
"no_camera_name": "Não foi possível determinar o nome da câmera da Frigate, especifique 'camera_entity' ou 'camera_name' para a câmera a seguir",
|
||||
"no_dashboard_or_view": "",
|
||||
"no_live_camera": "O parâmetro camera_entity deve ser definido e válido para este provedor ativo",
|
||||
"no_visible_cameras": "Nenhuma câmera visível encontrada, você deve configurar pelo menos uma câmera não oculta",
|
||||
"reconnecting": "Reconectando",
|
||||
|
||||
@@ -429,6 +429,7 @@
|
||||
"no_camera_entity_for_triggers": "Não existe camera para a acção",
|
||||
"no_camera_id": "Não foi possível determinar o ID da câmera para a câmera a seguir, pode ser necessário definir o parâmetro 'id' manualmente",
|
||||
"no_camera_name": "Não foi possível determinar o nome da câmera da Frigate, especifique 'camera_entity' ou 'camera_name' para a câmera a seguir",
|
||||
"no_dashboard_or_view": "",
|
||||
"no_live_camera": "O parâmetro camera_entity deve ser definido e válido para este serviço ativo",
|
||||
"no_visible_cameras": "Sem camaras visiveis",
|
||||
"reconnecting": "A voltar a ligar",
|
||||
|
||||
Reference in New Issue
Block a user