Generalize camera endpoints.
This commit is contained in:
@@ -18,9 +18,10 @@ import {
|
|||||||
CameraManagerCameraCapabilities,
|
CameraManagerCameraCapabilities,
|
||||||
CameraManagerMediaCapabilities,
|
CameraManagerMediaCapabilities,
|
||||||
CameraManagerCameraMetadata,
|
CameraManagerCameraMetadata,
|
||||||
CameraURLContext,
|
CameraEndpointsContext,
|
||||||
CameraConfigs,
|
CameraConfigs,
|
||||||
Engine,
|
Engine,
|
||||||
|
CameraEndpoints,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
|
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
|
||||||
@@ -118,8 +119,8 @@ export interface CameraManagerEngine {
|
|||||||
|
|
||||||
getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null;
|
getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null;
|
||||||
|
|
||||||
getCameraURL(
|
getCameraEndpoints(
|
||||||
cameraConfig: CameraConfig,
|
cameraConfig: CameraConfig,
|
||||||
context?: CameraURLContext,
|
context?: CameraEndpointsContext,
|
||||||
): string | null;
|
): CameraEndpoints | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ import {
|
|||||||
RecordingSegment,
|
RecordingSegment,
|
||||||
RecordingSegmentsQuery,
|
RecordingSegmentsQuery,
|
||||||
RecordingSegmentsQueryResultsMap,
|
RecordingSegmentsQueryResultsMap,
|
||||||
CameraURLContext,
|
CameraEndpointsContext,
|
||||||
CameraConfigs,
|
CameraConfigs,
|
||||||
|
CameraEndpoints,
|
||||||
|
CameraEndpoint,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { FrigateRecording } from './types';
|
import { FrigateRecording } from './types';
|
||||||
import {
|
import {
|
||||||
@@ -1006,15 +1008,23 @@ export class FrigateCameraManagerEngine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCameraURL(
|
public getCameraEndpoints(
|
||||||
cameraConfig: CameraConfig,
|
cameraConfig: CameraConfig,
|
||||||
context?: CameraURLContext,
|
context?: CameraEndpointsContext,
|
||||||
): string | null {
|
): CameraEndpoints | null {
|
||||||
|
const getUIEndpoint = (): CameraEndpoint | null => {
|
||||||
if (!cameraConfig.frigate.url) {
|
if (!cameraConfig.frigate.url) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!cameraConfig.frigate.camera_name) {
|
if (!cameraConfig.frigate.camera_name) {
|
||||||
return cameraConfig.frigate.url;
|
return { endpoint: cameraConfig.frigate.url };
|
||||||
|
}
|
||||||
|
|
||||||
|
const cameraURL =
|
||||||
|
`${cameraConfig.frigate.url}/cameras/` + cameraConfig.frigate.camera_name;
|
||||||
|
|
||||||
|
if (context?.view === 'live') {
|
||||||
|
return { endpoint: cameraURL };
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventsURL =
|
const eventsURL =
|
||||||
@@ -1027,11 +1037,11 @@ export class FrigateCameraManagerEngine
|
|||||||
switch (context?.media?.getMediaType()) {
|
switch (context?.media?.getMediaType()) {
|
||||||
case 'clip':
|
case 'clip':
|
||||||
case 'snapshot':
|
case 'snapshot':
|
||||||
return eventsURL;
|
return { endpoint: eventsURL };
|
||||||
case 'recording':
|
case 'recording':
|
||||||
const startTime = context.media.getStartTime();
|
const startTime = context.media.getStartTime();
|
||||||
if (startTime) {
|
if (startTime) {
|
||||||
return recordingsURL + format(startTime, 'yyyy-MM-dd/HH');
|
return { endpoint: recordingsURL + format(startTime, 'yyyy-MM-dd/HH') };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,12 +1051,48 @@ export class FrigateCameraManagerEngine
|
|||||||
case 'clips':
|
case 'clips':
|
||||||
case 'snapshots':
|
case 'snapshots':
|
||||||
case 'snapshot':
|
case 'snapshot':
|
||||||
return eventsURL;
|
return { endpoint: eventsURL };
|
||||||
case 'recording':
|
case 'recording':
|
||||||
case 'recordings':
|
case 'recordings':
|
||||||
return recordingsURL;
|
return { endpoint: recordingsURL };
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${cameraConfig.frigate.url}/cameras/${cameraConfig.frigate.camera_name}`;
|
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 }),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ import {
|
|||||||
RecordingQueryResultsMap,
|
RecordingQueryResultsMap,
|
||||||
RecordingSegmentsQuery,
|
RecordingSegmentsQuery,
|
||||||
RecordingSegmentsQueryResultsMap,
|
RecordingSegmentsQueryResultsMap,
|
||||||
CameraURLContext,
|
CameraEndpointsContext,
|
||||||
CameraConfigs,
|
CameraConfigs,
|
||||||
RecordingQuery,
|
RecordingQuery,
|
||||||
QueryReturnType,
|
QueryReturnType,
|
||||||
CameraManagerCameraCapabilities,
|
CameraManagerCameraCapabilities,
|
||||||
Engine,
|
Engine,
|
||||||
|
CameraEndpoints,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getEntityIcon, getEntityTitle } from '../../utils/ha';
|
import { getEntityIcon, getEntityTitle } from '../../utils/ha';
|
||||||
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
|
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
|
||||||
@@ -177,10 +178,10 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCameraURL(
|
public getCameraEndpoints(
|
||||||
_cameraConfig: CameraConfig,
|
_cameraConfig: CameraConfig,
|
||||||
_context?: CameraURLContext,
|
_context?: CameraEndpointsContext,
|
||||||
): string | null {
|
): CameraEndpoints | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
CameraManagerCameraMetadata,
|
CameraManagerCameraMetadata,
|
||||||
CameraManagerCapabilities,
|
CameraManagerCapabilities,
|
||||||
CameraManagerMediaCapabilities,
|
CameraManagerMediaCapabilities,
|
||||||
CameraURLContext,
|
CameraEndpointsContext,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
EventQuery,
|
EventQuery,
|
||||||
EventQueryResults,
|
EventQueryResults,
|
||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
RecordingSegmentsQueryResults,
|
RecordingSegmentsQueryResults,
|
||||||
RecordingSegmentsQueryResultsMap,
|
RecordingSegmentsQueryResultsMap,
|
||||||
ResultsMap,
|
ResultsMap,
|
||||||
|
CameraEndpoints,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import orderBy from 'lodash-es/orderBy';
|
import orderBy from 'lodash-es/orderBy';
|
||||||
import { CameraManagerEngineFactory } from './engine-factory.js';
|
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 cameraConfig = this._store.getCameraConfig(cameraID);
|
||||||
const engine = this._store.getEngineForCameraID(cameraID);
|
const engine = this._store.getEngineForCameraID(cameraID);
|
||||||
if (!cameraConfig || !engine) {
|
if (!cameraConfig || !engine) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return engine.getCameraURL(cameraConfig, context);
|
return engine.getCameraEndpoints(cameraConfig, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCameraMetadata(
|
public getCameraMetadata(
|
||||||
|
|||||||
@@ -106,11 +106,22 @@ export interface CameraManagerCameraMetadata {
|
|||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CameraURLContext {
|
export interface CameraEndpointsContext {
|
||||||
media?: ViewMedia;
|
media?: ViewMedia;
|
||||||
view?: FrigateCardView;
|
view?: FrigateCardView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CameraEndpoint {
|
||||||
|
endpoint: string;
|
||||||
|
sign?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CameraEndpoints {
|
||||||
|
ui?: CameraEndpoint;
|
||||||
|
go2rtc?: CameraEndpoint;
|
||||||
|
jsmpeg?: CameraEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
export type CameraConfigs = Map<string, CameraConfig>;
|
export type CameraConfigs = Map<string, CameraConfig>;
|
||||||
|
|
||||||
// ===========
|
// ===========
|
||||||
|
|||||||
+11
-8
@@ -1461,15 +1461,18 @@ class FrigateCard extends LitElement {
|
|||||||
* @returns The URL or null if unavailable.
|
* @returns The URL or null if unavailable.
|
||||||
*/
|
*/
|
||||||
protected _getCameraURLFromContext(): string | null {
|
protected _getCameraURLFromContext(): string | null {
|
||||||
const view = this._view;
|
if (!this._view) {
|
||||||
const selectedCameraID = view?.camera;
|
return null;
|
||||||
const media = view?.queryResults?.getSelectedResult() ?? null;
|
}
|
||||||
return this._hass && view && selectedCameraID
|
|
||||||
? this._cameraManager?.getCameraURL(selectedCameraID, {
|
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 }),
|
...(media && { media: media }),
|
||||||
...(view && { view: view.view }),
|
}) ?? null;
|
||||||
}) ?? null
|
return endpoints?.ui?.endpoint ?? null;
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
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 { CameraConfig, ExtendedHomeAssistant } from '../../types.js';
|
||||||
import '../image.js';
|
import '../image.js';
|
||||||
import {
|
import {
|
||||||
@@ -18,8 +18,8 @@ import { dispatchMediaLoadedEvent } from '../../utils/media-info';
|
|||||||
import { localize } from '../../localize/localize';
|
import { localize } from '../../localize/localize';
|
||||||
import { dispatchErrorMessageEvent } from '../message';
|
import { dispatchErrorMessageEvent } from '../message';
|
||||||
import { VideoRTC } from '../../external/go2rtc/video-rtc';
|
import { VideoRTC } from '../../external/go2rtc/video-rtc';
|
||||||
import { homeAssistantSignPath } from '../../utils/ha';
|
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||||
import { errorToConsole } from '../../utils/basic';
|
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
|
||||||
|
|
||||||
// Note (2023-02-18): Depending on the behavior of the player / browser is
|
// 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
|
// 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 })
|
@property({ attribute: false })
|
||||||
public cameraConfig?: CameraConfig;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraEndpoints?: CameraEndpoints;
|
||||||
|
|
||||||
protected _player?: FrigateCardGo2RTCPlayer;
|
protected _player?: FrigateCardGo2RTCPlayer;
|
||||||
|
|
||||||
public play(): void {
|
public play(): void {
|
||||||
@@ -87,36 +90,29 @@ export class FrigateCardGo2RTC extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async _createPlayer(): Promise<void> {
|
protected async _createPlayer(): Promise<void> {
|
||||||
if (
|
if (!this.hass) {
|
||||||
!this.hass ||
|
|
||||||
!this.cameraConfig?.frigate.client_id ||
|
|
||||||
!this.cameraConfig.frigate.camera_name
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: string | null | undefined;
|
const endpoint = this.cameraEndpoints?.go2rtc;
|
||||||
try {
|
if (!endpoint) {
|
||||||
response = await homeAssistantSignPath(
|
return dispatchErrorMessageEvent(this, localize('error.live_camera_no_endpoint'), {
|
||||||
|
context: this.cameraConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const address = await getEndpointAddressOrDispatchError(
|
||||||
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
`/api/frigate/${this.cameraConfig.frigate.client_id}` +
|
endpoint,
|
||||||
`/mse/api/ws?src=${this.cameraConfig.frigate.camera_name}`,
|
|
||||||
GO2RTC_URL_SIGN_EXPIRY_SECONDS,
|
GO2RTC_URL_SIGN_EXPIRY_SECONDS,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
if (!address) {
|
||||||
errorToConsole(e as Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!response) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = response.replace(/^http/i, 'ws');
|
|
||||||
if (!url) {
|
|
||||||
return dispatchErrorMessageEvent(this, localize('error.failed_sign'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this._player = new FrigateCardGo2RTCPlayer();
|
this._player = new FrigateCardGo2RTCPlayer();
|
||||||
this._player.src = url;
|
this._player.src = address;
|
||||||
this._player.visibilityCheck = false;
|
this._player.visibilityCheck = false;
|
||||||
this._player.background = true;
|
this._player.background = true;
|
||||||
this._player.mode = 'webrtc';
|
this._player.mode = 'webrtc';
|
||||||
@@ -125,7 +121,7 @@ export class FrigateCardGo2RTC extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
if (changedProps.has('cameraConfig')) {
|
if (changedProps.has('cameraEndpoints')) {
|
||||||
this._createPlayer();
|
this._createPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import {
|
|||||||
ExtendedHomeAssistant,
|
ExtendedHomeAssistant,
|
||||||
JSMPEGConfig,
|
JSMPEGConfig,
|
||||||
} from '../../types.js';
|
} from '../../types.js';
|
||||||
import { homeAssistantSignPath } from '../../utils/ha';
|
|
||||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
||||||
import { dispatchErrorMessageEvent } from '../message.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.
|
// Number of seconds a signed URL is valid for.
|
||||||
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||||
@@ -27,6 +28,9 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public cameraConfig?: CameraConfig;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraEndpoints?: CameraEndpoints;
|
||||||
|
|
||||||
@property({ attribute: false, hasChanged: contentsChanged })
|
@property({ attribute: false, hasChanged: contentsChanged })
|
||||||
public jsmpegConfig?: JSMPEGConfig;
|
public jsmpegConfig?: JSMPEGConfig;
|
||||||
|
|
||||||
@@ -83,37 +87,6 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
// JSMPEG does not support seeking.
|
// 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.
|
* Create a JSMPEG player.
|
||||||
* @param url The URL for the player to connect to.
|
* @param url The URL for the player to connect to.
|
||||||
@@ -205,26 +178,35 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
* Refresh the JSMPEG player.
|
* Refresh the JSMPEG player.
|
||||||
*/
|
*/
|
||||||
protected async _refreshPlayer(): Promise<void> {
|
protected async _refreshPlayer(): Promise<void> {
|
||||||
|
if (!this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._resetPlayer();
|
this._resetPlayer();
|
||||||
|
|
||||||
this._jsmpegCanvasElement = document.createElement('canvas');
|
this._jsmpegCanvasElement = document.createElement('canvas');
|
||||||
this._jsmpegCanvasElement.className = 'media';
|
this._jsmpegCanvasElement.className = 'media';
|
||||||
|
|
||||||
if (!this.cameraConfig?.frigate.camera_name) {
|
const endpoint = this.cameraEndpoints?.jsmpeg;
|
||||||
return dispatchErrorMessageEvent(this, localize('error.no_camera_name'), {
|
if (!endpoint) {
|
||||||
|
return dispatchErrorMessageEvent(this, localize('error.live_camera_no_endpoint'), {
|
||||||
context: this.cameraConfig,
|
context: this.cameraConfig,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = await this._getURL();
|
const address = await getEndpointAddressOrDispatchError(
|
||||||
if (url) {
|
this,
|
||||||
this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(url);
|
this.hass,
|
||||||
|
endpoint,
|
||||||
|
JSMPEG_URL_SIGN_EXPIRY_SECONDS,
|
||||||
|
);
|
||||||
|
if (!address) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._jsmpegVideoPlayer = await this._createJSMPEGPlayer(address);
|
||||||
this._refreshPlayerTimerID = window.setTimeout(() => {
|
this._refreshPlayerTimerID = window.setTimeout(() => {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
||||||
} else {
|
|
||||||
dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_sign'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import { CameraManager } from '../../camera-manager/manager.js';
|
|||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import { dispatchMessageEvent, dispatchErrorMessageEvent } from '../message.js';
|
import { dispatchMessageEvent, dispatchErrorMessageEvent } from '../message.js';
|
||||||
import { HassEntity } from 'home-assistant-js-websocket';
|
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
|
* 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
|
<frigate-card-live-provider
|
||||||
?disabled=${this.liveConfig.lazy_load}
|
?disabled=${this.liveConfig.lazy_load}
|
||||||
.cameraConfig=${cameraConfig}
|
.cameraConfig=${cameraConfig}
|
||||||
|
.cameraEndpoints=${guard(
|
||||||
|
[this.cameraManager],
|
||||||
|
() => this.cameraManager?.getCameraEndpoints(cameraID),
|
||||||
|
)}
|
||||||
.label=${cameraMetadata?.title ?? ''}
|
.label=${cameraMetadata?.title ?? ''}
|
||||||
.liveConfig=${config}
|
.liveConfig=${config}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -666,6 +671,9 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public cameraConfig?: CameraConfig;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraEndpoints?: CameraEndpoints;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public liveConfig?: LiveConfig;
|
public liveConfig?: LiveConfig;
|
||||||
|
|
||||||
@@ -858,6 +866,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
|
.cameraEndpoints=${this.cameraEndpoints}
|
||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-webrtc-card>`
|
</frigate-card-live-webrtc-card>`
|
||||||
@@ -878,6 +887,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
|
.cameraEndpoints=${this.cameraEndpoints}
|
||||||
.jsmpegConfig=${this.liveConfig.jsmpeg}
|
.jsmpegConfig=${this.liveConfig.jsmpeg}
|
||||||
.cardWideConfig=${this.cardWideConfig}
|
.cardWideConfig=${this.cardWideConfig}
|
||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@
|
|||||||
"invalid_elements_config": "Invalid picture elements configuration",
|
"invalid_elements_config": "Invalid picture elements configuration",
|
||||||
"invalid_response": "Received invalid response from Home Assistant for request",
|
"invalid_response": "Received invalid response from Home Assistant for request",
|
||||||
"jsmpeg_no_player": "Could not start JSMPEG player",
|
"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_not_found": "The configured camera_entity was not found",
|
||||||
"live_camera_unavailable": "Camera unavailable",
|
"live_camera_unavailable": "Camera unavailable",
|
||||||
"no_camera_engine": "Could not determine suitable engine for camera",
|
"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_elements_config": "Configurazione degli elementi di immagine non valida",
|
||||||
"invalid_response": "Ricevuta una risposta non valida da Home Assistant per la richiesta",
|
"invalid_response": "Ricevuta una risposta non valida da Home Assistant per la richiesta",
|
||||||
"jsmpeg_no_player": "Impossibile avviare JSMPEG Player",
|
"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_not_found": "La telecamera configurata non è stata trovata",
|
||||||
"live_camera_unavailable": "Telecamera non disponibile",
|
"live_camera_unavailable": "Telecamera non disponibile",
|
||||||
"no_camera_engine": "",
|
"no_camera_engine": "",
|
||||||
|
|||||||
@@ -342,7 +342,7 @@
|
|||||||
"invalid_elements_config": "Configuração de elementos de imagem inválida",
|
"invalid_elements_config": "Configuração de elementos de imagem inválida",
|
||||||
"invalid_response": "Resposta inválida recebida do Home Assistant para a solicitação",
|
"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_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_not_found": "",
|
||||||
"live_camera_unavailable": "",
|
"live_camera_unavailable": "",
|
||||||
"no_camera_engine": "",
|
"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