Merge pull request #1291 from dermotduffy/custom-go2rtc

Add support for go2rtc to non-Frigate cameras
This commit is contained in:
Dermot Duffy
2023-10-08 11:15:23 -07:00
committed by GitHub
15 changed files with 680 additions and 43 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ import { MemoryRequestCache, RecordingSegmentsCache, RequestCache } from './cach
import { CameraManagerEngine } from './engine';
import { CameraInitializationError } from './error';
import { Engine } from './types';
import { getCameraEntityFromConfig } from './util';
import { getCameraEntityFromConfig } from './utils';
export class CameraManagerEngineFactory {
protected _entityRegistryManager: EntityRegistryManager;
+11 -16
View File
@@ -62,7 +62,7 @@ import {
RecordingSegmentsQuery,
RecordingSegmentsQueryResultsMap,
} from '../types';
import { getCameraEntityFromConfig } from '../util';
import { getCameraEntityFromConfig, getDefaultGo2RTCEndpoint } from '../utils.js';
import frigateLogo from './assets/frigate-logo-dark.svg';
import { FrigateViewMediaFactory } from './media';
import { FrigateViewMediaClassifier } from './media-classifier';
@@ -1147,20 +1147,6 @@ export class FrigateCameraManagerEngine
};
};
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.go2rtc?.stream ?? cameraConfig.frigate.camera_name
}`,
sign: true,
};
};
const getJSMPEG = (): CameraEndpoint | null => {
return {
endpoint:
@@ -1183,11 +1169,20 @@ export class FrigateCameraManagerEngine
};
const ui = getUIEndpoint();
const go2rtc = getGo2RTC();
const go2rtc = getDefaultGo2RTCEndpoint(cameraConfig, {
url:
cameraConfig.go2rtc?.url ??
// go2rtc is exposed by the Frigate integration under the (slightly
// misleading) 'mse' path, even though that path can serve all go2rtc
// modes.
`/api/frigate/${cameraConfig.frigate.client_id}/mse`,
stream: cameraConfig.go2rtc?.stream ?? cameraConfig.frigate.camera_name,
});
const jsmpeg = getJSMPEG();
const webrtcCard = getWebRTCCard();
return {
...super.getCameraEndpoints(cameraConfig, context),
...(ui && { ui: ui }),
...(go2rtc && { go2rtc: go2rtc }),
...(jsmpeg && { jsmpeg: jsmpeg }),
+9 -6
View File
@@ -31,6 +31,7 @@ import {
RecordingSegmentsQuery,
RecordingSegmentsQueryResultsMap,
} from '../types';
import { getDefaultGo2RTCEndpoint } from '../utils.js';
export class GenericCameraManagerEngine implements CameraManagerEngine {
public getEngineType(): Engine {
@@ -165,10 +166,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
getEntityTitle(hass, cameraConfig.webrtc_card?.entity) ??
cameraConfig.id ??
'',
icon:
cameraConfig?.icon ??
getEntityIcon(hass, cameraConfig.camera_entity) ??
'mdi:video',
icon: cameraConfig?.icon ?? getEntityIcon(hass, cameraConfig.camera_entity),
};
}
@@ -191,9 +189,14 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
}
public getCameraEndpoints(
_cameraConfig: CameraConfig,
cameraConfig: CameraConfig,
_context?: CameraEndpointsContext,
): CameraEndpoints | null {
return null;
const go2rtc = getDefaultGo2RTCEndpoint(cameraConfig);
return go2rtc
? {
go2rtc: go2rtc,
}
: null;
}
}
+2 -2
View File
@@ -3,12 +3,12 @@ import add from 'date-fns/add';
import cloneDeep from 'lodash-es/cloneDeep';
import merge from 'lodash-es/merge.js';
import sum from 'lodash-es/sum';
import { CardCameraAPI } from '../card-controller/types.js';
import { CameraConfig, CamerasConfig } from '../config/types.js';
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
import { localize } from '../localize/localize.js';
import { allPromises, arrayify, setify } from '../utils/basic.js';
import { getCameraID } from '../utils/camera.js';
import { CardCameraAPI } from '../card-controller/types.js';
import { log } from '../utils/debug.js';
import { EntityRegistryManager } from '../utils/ha/entity-registry/index.js';
import { ViewMedia } from '../view/media.js';
@@ -51,7 +51,7 @@ import {
RecordingSegmentsQueryResultsMap,
ResultsMap,
} from './types.js';
import { sortMedia } from './util.js';
import { sortMedia } from './utils.js';
class QueryClassifier {
public static isEventQuery(query: DataQuery | PartialDataQuery): query is EventQuery {
@@ -384,8 +384,7 @@ export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine
public getCameraEndpoints(
cameraConfig: CameraConfig,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_context?: CameraEndpointsContext,
context?: CameraEndpointsContext,
): CameraEndpoints | null {
const getUIEndpoint = (): CameraEndpoint | null => {
return cameraConfig.motioneye?.url
@@ -394,9 +393,9 @@ export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine
}
: null;
};
const ui = getUIEndpoint();
return {
...super.getCameraEndpoints(cameraConfig, context),
...(ui && { ui: ui }),
};
}
@@ -8,6 +8,7 @@ import uniqBy from 'lodash-es/uniqBy';
import { CameraConfig } from '../config/types';
import { ViewMedia } from '../view/media';
import { DateRange } from './range';
import { CameraEndpoint } from './types';
export const convertRangeToCacheFriendlyTimes = (
range: DateRange,
@@ -58,3 +59,26 @@ export const sortMedia = (mediaArray: ViewMedia[]): ViewMedia[] => {
export const getCameraEntityFromConfig = (cameraConfig: CameraConfig): string | null => {
return cameraConfig.camera_entity ?? cameraConfig.webrtc_card?.entity ?? null;
};
export const getDefaultGo2RTCEndpoint = (
cameraConfig: CameraConfig,
options?: {
url?: string;
stream?: string;
},
): CameraEndpoint | null => {
const url = options?.url ?? cameraConfig.go2rtc?.url;
const stream = options?.stream ?? cameraConfig.go2rtc?.stream;
if (!url || !stream) {
return null;
}
const endpoint = `${url}/api/ws?src=${stream}`;
return {
endpoint: endpoint,
// Only sign the endpoint if it's local to HA.
sign: endpoint.startsWith('/'),
};
};
+2 -2
View File
@@ -9,10 +9,10 @@ import {
compressRanges,
} from '../camera-manager/range';
import { EventQuery, RecordingQuery, RecordingSegment } from '../camera-manager/types';
import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera-manager/util';
import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera-manager/utils';
import { ClipsOrSnapshotsOrAll } from '../types';
import { ViewMedia } from '../view/media';
import { ModifyInterface, errorToConsole } from '../utils/basic.js';
import { ViewMedia } from '../view/media';
// Allow timeline freshness to be at least this number of seconds out of date
// (caching times in the data-engine may increase the effective delay).
+2 -2
View File
@@ -10,7 +10,7 @@ import { customElement, property } from 'lit/decorators.js';
import { CameraEndpoints } from '../../camera-manager/types.js';
import { CameraConfig, MicrophoneConfig } from '../../config/types.js';
import { localize } from '../../localize/localize';
import liveMSEStyle from '../../scss/live-go2rtc.scss';
import liveGo2RTCStyle from '../../scss/live-go2rtc.scss';
import { ExtendedHomeAssistant, FrigateCardMediaPlayer } from '../../types.js';
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
import { setControlsOnVideo } from '../../utils/media.js';
@@ -167,7 +167,7 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla
}
static get styles(): CSSResultGroup {
return unsafeCSS(liveMSEStyle);
return unsafeCSS(liveGo2RTCStyle);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ import {
import { CameraManager } from '../camera-manager/manager';
import { rangesOverlap } from '../camera-manager/range';
import { MediaQuery } from '../camera-manager/types';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/util';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/utils';
import {
FrigateCardTimelineItem,
TimelineDataSource,
+5
View File
@@ -774,6 +774,11 @@ const microphoneConfigSchema = z
export type MicrophoneConfig = z.infer<typeof microphoneConfigSchema>;
const go2rtcConfigSchema = z.object({
url: z
.string()
.transform((input) => input.replace(/\/+$/, ''))
.optional(),
host: z.string().optional(),
modes: z.enum(['webrtc', 'mse', 'mp4', 'mjpeg']).array().optional(),
stream: z.string().optional(),
});