Generalize camera endpoints.
This commit is contained in:
@@ -18,9 +18,10 @@ import {
|
||||
CameraManagerCameraCapabilities,
|
||||
CameraManagerMediaCapabilities,
|
||||
CameraManagerCameraMetadata,
|
||||
CameraURLContext,
|
||||
CameraEndpointsContext,
|
||||
CameraConfigs,
|
||||
Engine,
|
||||
CameraEndpoints,
|
||||
} from './types';
|
||||
|
||||
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
|
||||
@@ -118,8 +119,8 @@ export interface CameraManagerEngine {
|
||||
|
||||
getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null;
|
||||
|
||||
getCameraURL(
|
||||
getCameraEndpoints(
|
||||
cameraConfig: CameraConfig,
|
||||
context?: CameraURLContext,
|
||||
): string | null;
|
||||
context?: CameraEndpointsContext,
|
||||
): CameraEndpoints | null;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,10 @@ import {
|
||||
RecordingSegment,
|
||||
RecordingSegmentsQuery,
|
||||
RecordingSegmentsQueryResultsMap,
|
||||
CameraURLContext,
|
||||
CameraEndpointsContext,
|
||||
CameraConfigs,
|
||||
CameraEndpoints,
|
||||
CameraEndpoint,
|
||||
} from '../types';
|
||||
import { FrigateRecording } from './types';
|
||||
import {
|
||||
@@ -1006,47 +1008,91 @@ export class FrigateCameraManagerEngine
|
||||
};
|
||||
}
|
||||
|
||||
public getCameraURL(
|
||||
public getCameraEndpoints(
|
||||
cameraConfig: CameraConfig,
|
||||
context?: CameraURLContext,
|
||||
): string | null {
|
||||
if (!cameraConfig.frigate.url) {
|
||||
return null;
|
||||
}
|
||||
if (!cameraConfig.frigate.camera_name) {
|
||||
return cameraConfig.frigate.url;
|
||||
}
|
||||
context?: CameraEndpointsContext,
|
||||
): CameraEndpoints | null {
|
||||
const getUIEndpoint = (): CameraEndpoint | null => {
|
||||
if (!cameraConfig.frigate.url) {
|
||||
return null;
|
||||
}
|
||||
if (!cameraConfig.frigate.camera_name) {
|
||||
return { endpoint: cameraConfig.frigate.url };
|
||||
}
|
||||
|
||||
const eventsURL =
|
||||
`${cameraConfig.frigate.url}/events?camera=` + cameraConfig.frigate.camera_name;
|
||||
const recordingsURL =
|
||||
`${cameraConfig.frigate.url}/recording/` + cameraConfig.frigate.camera_name;
|
||||
const cameraURL =
|
||||
`${cameraConfig.frigate.url}/cameras/` + cameraConfig.frigate.camera_name;
|
||||
|
||||
// If media is available, use it since it may result in a more precisely
|
||||
// correct URL.
|
||||
switch (context?.media?.getMediaType()) {
|
||||
case 'clip':
|
||||
case 'snapshot':
|
||||
return eventsURL;
|
||||
case 'recording':
|
||||
const startTime = context.media.getStartTime();
|
||||
if (startTime) {
|
||||
return recordingsURL + format(startTime, 'yyyy-MM-dd/HH');
|
||||
if (context?.view === 'live') {
|
||||
return { endpoint: cameraURL };
|
||||
}
|
||||
|
||||
const eventsURL =
|
||||
`${cameraConfig.frigate.url}/events?camera=` + cameraConfig.frigate.camera_name;
|
||||
const recordingsURL =
|
||||
`${cameraConfig.frigate.url}/recording/` + cameraConfig.frigate.camera_name;
|
||||
|
||||
// If media is available, use it since it may result in a more precisely
|
||||
// correct URL.
|
||||
switch (context?.media?.getMediaType()) {
|
||||
case 'clip':
|
||||
case 'snapshot':
|
||||
return { endpoint: eventsURL };
|
||||
case 'recording':
|
||||
const startTime = context.media.getStartTime();
|
||||
if (startTime) {
|
||||
return { endpoint: recordingsURL + format(startTime, 'yyyy-MM-dd/HH') };
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to just using the view if we have that.
|
||||
switch (context?.view) {
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'snapshots':
|
||||
case 'snapshot':
|
||||
return { endpoint: eventsURL };
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
return { endpoint: recordingsURL };
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: cameraURL,
|
||||
};
|
||||
};
|
||||
|
||||
const getGo2RTC = (): CameraEndpoint | null => {
|
||||
return {
|
||||
endpoint:
|
||||
`/api/frigate/${cameraConfig.frigate.client_id}` +
|
||||
// go2rtc is exposed by the integration under the (slightly
|
||||
// misleading) 'mse' path, even though that path can serve all go2rtc
|
||||
// modes.
|
||||
`/mse/api/ws?src=${cameraConfig.frigate.camera_name}`,
|
||||
sign: true,
|
||||
};
|
||||
};
|
||||
|
||||
const getJSMPEG = (): CameraEndpoint | null => {
|
||||
return {
|
||||
endpoint:
|
||||
`/api/frigate/${cameraConfig.frigate.client_id}` +
|
||||
`/jsmpeg/${cameraConfig.frigate.camera_name}`,
|
||||
sign: true,
|
||||
};
|
||||
};
|
||||
|
||||
const ui = getUIEndpoint();
|
||||
const go2rtc = getGo2RTC();
|
||||
const jsmpeg = getJSMPEG();
|
||||
|
||||
return ui || go2rtc || jsmpeg
|
||||
? {
|
||||
...(ui && { ui: ui }),
|
||||
...(go2rtc && { go2rtc: go2rtc }),
|
||||
...(jsmpeg && { jsmpeg: jsmpeg }),
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to just using the view if we have that.
|
||||
switch (context?.view) {
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'snapshots':
|
||||
case 'snapshot':
|
||||
return eventsURL;
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
return recordingsURL;
|
||||
}
|
||||
|
||||
return `${cameraConfig.frigate.url}/cameras/${cameraConfig.frigate.camera_name}`;
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@ import {
|
||||
RecordingQueryResultsMap,
|
||||
RecordingSegmentsQuery,
|
||||
RecordingSegmentsQueryResultsMap,
|
||||
CameraURLContext,
|
||||
CameraEndpointsContext,
|
||||
CameraConfigs,
|
||||
RecordingQuery,
|
||||
QueryReturnType,
|
||||
CameraManagerCameraCapabilities,
|
||||
Engine,
|
||||
CameraEndpoints,
|
||||
} from '../types';
|
||||
import { getEntityIcon, getEntityTitle } from '../../utils/ha';
|
||||
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
|
||||
@@ -177,10 +178,10 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getCameraURL(
|
||||
public getCameraEndpoints(
|
||||
_cameraConfig: CameraConfig,
|
||||
_context?: CameraURLContext,
|
||||
): string | null {
|
||||
_context?: CameraEndpointsContext,
|
||||
): CameraEndpoints | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
CameraManagerCameraMetadata,
|
||||
CameraManagerCapabilities,
|
||||
CameraManagerMediaCapabilities,
|
||||
CameraURLContext,
|
||||
CameraEndpointsContext,
|
||||
DataQuery,
|
||||
EventQuery,
|
||||
EventQueryResults,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
RecordingSegmentsQueryResults,
|
||||
RecordingSegmentsQueryResultsMap,
|
||||
ResultsMap,
|
||||
CameraEndpoints,
|
||||
} from './types.js';
|
||||
import orderBy from 'lodash-es/orderBy';
|
||||
import { CameraManagerEngineFactory } from './engine-factory.js';
|
||||
@@ -643,13 +644,16 @@ export class CameraManager {
|
||||
);
|
||||
}
|
||||
|
||||
public getCameraURL(cameraID: string, context?: CameraURLContext): string | null {
|
||||
public getCameraEndpoints(
|
||||
cameraID: string,
|
||||
context?: CameraEndpointsContext,
|
||||
): CameraEndpoints | null {
|
||||
const cameraConfig = this._store.getCameraConfig(cameraID);
|
||||
const engine = this._store.getEngineForCameraID(cameraID);
|
||||
if (!cameraConfig || !engine) {
|
||||
return null;
|
||||
}
|
||||
return engine.getCameraURL(cameraConfig, context);
|
||||
return engine.getCameraEndpoints(cameraConfig, context);
|
||||
}
|
||||
|
||||
public getCameraMetadata(
|
||||
|
||||
@@ -106,11 +106,22 @@ export interface CameraManagerCameraMetadata {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface CameraURLContext {
|
||||
export interface CameraEndpointsContext {
|
||||
media?: ViewMedia;
|
||||
view?: FrigateCardView;
|
||||
}
|
||||
|
||||
export interface CameraEndpoint {
|
||||
endpoint: string;
|
||||
sign?: boolean;
|
||||
}
|
||||
|
||||
export interface CameraEndpoints {
|
||||
ui?: CameraEndpoint;
|
||||
go2rtc?: CameraEndpoint;
|
||||
jsmpeg?: CameraEndpoint;
|
||||
}
|
||||
|
||||
export type CameraConfigs = Map<string, CameraConfig>;
|
||||
|
||||
// ===========
|
||||
|
||||
+12
-9
@@ -1461,15 +1461,18 @@ class FrigateCard extends LitElement {
|
||||
* @returns The URL or null if unavailable.
|
||||
*/
|
||||
protected _getCameraURLFromContext(): string | null {
|
||||
const view = this._view;
|
||||
const selectedCameraID = view?.camera;
|
||||
const media = view?.queryResults?.getSelectedResult() ?? null;
|
||||
return this._hass && view && selectedCameraID
|
||||
? this._cameraManager?.getCameraURL(selectedCameraID, {
|
||||
...(media && { media: media }),
|
||||
...(view && { view: view.view }),
|
||||
}) ?? null
|
||||
: null;
|
||||
if (!this._view) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedCameraID = this._view.camera;
|
||||
const media = this._view.queryResults?.getSelectedResult() ?? null;
|
||||
const endpoints =
|
||||
this._cameraManager?.getCameraEndpoints(selectedCameraID, {
|
||||
view: this._view.view,
|
||||
...(media && { media: media }),
|
||||
}) ?? null;
|
||||
return endpoints?.ui?.endpoint ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import liveMSEStyle from '../../scss/live-mse-webrtc.scss';
|
||||
import liveMSEStyle from '../../scss/live-go2rtc.scss';
|
||||
import { CameraConfig, ExtendedHomeAssistant } from '../../types.js';
|
||||
import '../image.js';
|
||||
import {
|
||||
@@ -18,8 +18,8 @@ import { dispatchMediaLoadedEvent } from '../../utils/media-info';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { dispatchErrorMessageEvent } from '../message';
|
||||
import { VideoRTC } from '../../external/go2rtc/video-rtc';
|
||||
import { homeAssistantSignPath } from '../../utils/ha';
|
||||
import { errorToConsole } from '../../utils/basic';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
|
||||
|
||||
// Note (2023-02-18): Depending on the behavior of the player / browser is
|
||||
// possible this URL will need to be re-signed in order to avoid HA spamming
|
||||
@@ -58,6 +58,9 @@ export class FrigateCardGo2RTC extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraConfig?: CameraConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraEndpoints?: CameraEndpoints;
|
||||
|
||||
protected _player?: FrigateCardGo2RTCPlayer;
|
||||
|
||||
public play(): void {
|
||||
@@ -87,36 +90,29 @@ export class FrigateCardGo2RTC extends LitElement {
|
||||
}
|
||||
|
||||
protected async _createPlayer(): Promise<void> {
|
||||
if (
|
||||
!this.hass ||
|
||||
!this.cameraConfig?.frigate.client_id ||
|
||||
!this.cameraConfig.frigate.camera_name
|
||||
) {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
let response: string | null | undefined;
|
||||
try {
|
||||
response = await homeAssistantSignPath(
|
||||
this.hass,
|
||||
`/api/frigate/${this.cameraConfig.frigate.client_id}` +
|
||||
`/mse/api/ws?src=${this.cameraConfig.frigate.camera_name}`,
|
||||
GO2RTC_URL_SIGN_EXPIRY_SECONDS,
|
||||
);
|
||||
} catch (e) {
|
||||
errorToConsole(e as Error);
|
||||
return;
|
||||
const endpoint = this.cameraEndpoints?.go2rtc;
|
||||
if (!endpoint) {
|
||||
return dispatchErrorMessageEvent(this, localize('error.live_camera_no_endpoint'), {
|
||||
context: this.cameraConfig,
|
||||
});
|
||||
}
|
||||
if (!response) {
|
||||
|
||||
const address = await getEndpointAddressOrDispatchError(
|
||||
this,
|
||||
this.hass,
|
||||
endpoint,
|
||||
GO2RTC_URL_SIGN_EXPIRY_SECONDS,
|
||||
);
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
const url = response.replace(/^http/i, 'ws');
|
||||
if (!url) {
|
||||
return dispatchErrorMessageEvent(this, localize('error.failed_sign'));
|
||||
}
|
||||
|
||||
this._player = new FrigateCardGo2RTCPlayer();
|
||||
this._player.src = url;
|
||||
this._player.src = address;
|
||||
this._player.visibilityCheck = false;
|
||||
this._player.background = true;
|
||||
this._player.mode = 'webrtc';
|
||||
@@ -125,7 +121,7 @@ export class FrigateCardGo2RTC extends LitElement {
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('cameraConfig')) {
|
||||
if (changedProps.has('cameraEndpoints')) {
|
||||
this._createPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
ExtendedHomeAssistant,
|
||||
JSMPEGConfig,
|
||||
} from '../../types.js';
|
||||
import { homeAssistantSignPath } from '../../utils/ha';
|
||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
||||
import { dispatchErrorMessageEvent } from '../message.js';
|
||||
import { contentsChanged, errorToConsole } from '../../utils/basic.js';
|
||||
import { contentsChanged } from '../../utils/basic.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint.js';
|
||||
|
||||
// Number of seconds a signed URL is valid for.
|
||||
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||
@@ -27,6 +28,9 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraConfig?: CameraConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraEndpoints?: CameraEndpoints;
|
||||
|
||||
@property({ attribute: false, hasChanged: contentsChanged })
|
||||
public jsmpegConfig?: JSMPEGConfig;
|
||||
|
||||
@@ -83,37 +87,6 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
||||
// JSMPEG does not support seeking.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a signed player URL.
|
||||
* @returns A URL or null.
|
||||
*/
|
||||
protected async _getURL(): Promise<string | null> {
|
||||
if (
|
||||
!this.hass ||
|
||||
!this.cameraConfig?.frigate.client_id ||
|
||||
!this.cameraConfig?.frigate.camera_name
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let response: string | null | undefined;
|
||||
try {
|
||||
response = await homeAssistantSignPath(
|
||||
this.hass,
|
||||
`/api/frigate/${this.cameraConfig.frigate.client_id}` +
|
||||
`/jsmpeg/${this.cameraConfig.frigate.camera_name}`,
|
||||
JSMPEG_URL_SIGN_EXPIRY_SECONDS,
|
||||
);
|
||||
} catch (e) {
|
||||
errorToConsole(e as Error);
|
||||
return null;
|
||||
}
|
||||
if (!response) {
|
||||
return null;
|
||||
}
|
||||
return response.replace(/^http/i, 'ws');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSMPEG player.
|
||||
* @param url The URL for the player to connect to.
|
||||
@@ -205,26 +178,35 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
||||
* Refresh the JSMPEG player.
|
||||
*/
|
||||
protected async _refreshPlayer(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._resetPlayer();
|
||||
|
||||
this._jsmpegCanvasElement = document.createElement('canvas');
|
||||
this._jsmpegCanvasElement.className = 'media';
|
||||
|
||||
if (!this.cameraConfig?.frigate.camera_name) {
|
||||
return dispatchErrorMessageEvent(this, localize('error.no_camera_name'), {
|
||||
const endpoint = this.cameraEndpoints?.jsmpeg;
|
||||
if (!endpoint) {
|
||||
return dispatchErrorMessageEvent(this, localize('error.live_camera_no_endpoint'), {
|
||||
context: this.cameraConfig,
|
||||
});
|
||||
}
|
||||
|
||||
const url = await this._getURL();
|
||||
if (url) {
|
||||
this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(url);
|
||||
this._refreshPlayerTimerID = window.setTimeout(() => {
|
||||
this.requestUpdate();
|
||||
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
||||
} else {
|
||||
dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_sign'));
|
||||
const address = await getEndpointAddressOrDispatchError(
|
||||
this,
|
||||
this.hass,
|
||||
endpoint,
|
||||
JSMPEG_URL_SIGN_EXPIRY_SECONDS,
|
||||
);
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(address);
|
||||
this._refreshPlayerTimerID = window.setTimeout(() => {
|
||||
this.requestUpdate();
|
||||
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { HomeAssistant } from 'custom-card-helpers';
|
||||
import { dispatchMessageEvent, dispatchErrorMessageEvent } from '../message.js';
|
||||
import { HassEntity } from 'home-assistant-js-websocket';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
|
||||
/**
|
||||
* Get the state object or dispatch an error. Used in `ha` and `image` live
|
||||
@@ -514,6 +515,10 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
<frigate-card-live-provider
|
||||
?disabled=${this.liveConfig.lazy_load}
|
||||
.cameraConfig=${cameraConfig}
|
||||
.cameraEndpoints=${guard(
|
||||
[this.cameraManager],
|
||||
() => this.cameraManager?.getCameraEndpoints(cameraID),
|
||||
)}
|
||||
.label=${cameraMetadata?.title ?? ''}
|
||||
.liveConfig=${config}
|
||||
.hass=${this.hass}
|
||||
@@ -666,6 +671,9 @@ export class FrigateCardLiveProvider extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraConfig?: CameraConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraEndpoints?: CameraEndpoints;
|
||||
|
||||
@property({ attribute: false })
|
||||
public liveConfig?: LiveConfig;
|
||||
|
||||
@@ -858,6 +866,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
||||
class=${classMap(providerClasses)}
|
||||
.hass=${this.hass}
|
||||
.cameraConfig=${this.cameraConfig}
|
||||
.cameraEndpoints=${this.cameraEndpoints}
|
||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||
>
|
||||
</frigate-card-live-webrtc-card>`
|
||||
@@ -878,6 +887,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
||||
class=${classMap(providerClasses)}
|
||||
.hass=${this.hass}
|
||||
.cameraConfig=${this.cameraConfig}
|
||||
.cameraEndpoints=${this.cameraEndpoints}
|
||||
.jsmpegConfig=${this.liveConfig.jsmpeg}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
"invalid_elements_config": "Invalid picture elements configuration",
|
||||
"invalid_response": "Received invalid response from Home Assistant for request",
|
||||
"jsmpeg_no_player": "Could not start JSMPEG player",
|
||||
"jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path",
|
||||
"live_camera_no_endpoint": "Could not get camera endpoint for this live provider (incomplete configuration?)",
|
||||
"live_camera_not_found": "The configured camera_entity was not found",
|
||||
"live_camera_unavailable": "Camera unavailable",
|
||||
"no_camera_engine": "Could not determine suitable engine for camera",
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
"invalid_elements_config": "Configurazione degli elementi di immagine non valida",
|
||||
"invalid_response": "Ricevuta una risposta non valida da Home Assistant per la richiesta",
|
||||
"jsmpeg_no_player": "Impossibile avviare JSMPEG Player",
|
||||
"jsmpeg_no_sign": "Impossibile recuperare o firmare il percorso WebSocket JSMPEG",
|
||||
"live_camera_no_endpoint": "",
|
||||
"live_camera_not_found": "La telecamera configurata non è stata trovata",
|
||||
"live_camera_unavailable": "Telecamera non disponibile",
|
||||
"no_camera_engine": "",
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
"invalid_elements_config": "Configuração de elementos de imagem inválida",
|
||||
"invalid_response": "Resposta inválida recebida do Home Assistant para a solicitação",
|
||||
"jsmpeg_no_player": "Não foi possível iniciar o player JSMPEG",
|
||||
"jsmpeg_no_sign": "Não foi possível recuperar ou assinar o caminho do websocket JSMPEG",
|
||||
"live_camera_no_endpoint": "",
|
||||
"live_camera_not_found": "",
|
||||
"live_camera_unavailable": "",
|
||||
"no_camera_engine": "",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CameraEndpoint } from '../camera-manager/types';
|
||||
import { dispatchErrorMessageEvent } from '../components/message';
|
||||
import { localize } from '../localize/localize';
|
||||
import { ExtendedHomeAssistant } from '../types';
|
||||
import { errorToConsole } from './basic';
|
||||
import { homeAssistantSignPath } from './ha';
|
||||
|
||||
export const getEndpointAddressOrDispatchError = async (
|
||||
element: HTMLElement,
|
||||
hass: ExtendedHomeAssistant,
|
||||
endpoint: CameraEndpoint,
|
||||
expires?: number,
|
||||
): Promise<string | null> => {
|
||||
let address: string | null;
|
||||
if (!endpoint.sign) {
|
||||
address = endpoint.endpoint;
|
||||
} else {
|
||||
let response: string | null | undefined;
|
||||
try {
|
||||
response = await homeAssistantSignPath(hass, endpoint.endpoint, expires);
|
||||
} catch (e) {
|
||||
errorToConsole(e as Error);
|
||||
return null;
|
||||
}
|
||||
address = response ? response.replace(/^http/i, 'ws') : null;
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
dispatchErrorMessageEvent(element, localize('error.failed_sign'));
|
||||
return null;
|
||||
}
|
||||
return address;
|
||||
};
|
||||
Reference in New Issue
Block a user