Add support for go2rtc for non-Frigate cameras
This commit is contained in:
@@ -191,25 +191,6 @@ describe('getCameraEndpoints', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('when remote with superfluous slashes', () => {
|
||||
const endpoints = createEngine().getCameraEndpoints(
|
||||
createFrigateCameraConfig({
|
||||
go2rtc: {
|
||||
url: 'https://my.custom.go2rtc///',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(endpoints).toEqual(
|
||||
expect.objectContaining({
|
||||
go2rtc: {
|
||||
endpoint: 'https://my.custom.go2rtc/api/ws?src=camera-1',
|
||||
sign: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set webrtc_card endpoint without camera name', () => {
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { GenericCameraManagerEngine } from '../../../src/camera-manager/generic/engine-generic';
|
||||
import { Engine, QueryResultsType, QueryType } from '../../../src/camera-manager/types';
|
||||
import { CameraConfig, RawFrigateCardConfig } from '../../../src/config/types';
|
||||
import { EntityRegistryManager } from '../../../src/utils/ha/entity-registry';
|
||||
import {
|
||||
TestViewMedia,
|
||||
createCameraConfig,
|
||||
createHASS,
|
||||
createStateEntity,
|
||||
} from '../../test-utils';
|
||||
|
||||
const createEngine = (): GenericCameraManagerEngine => {
|
||||
return new GenericCameraManagerEngine();
|
||||
};
|
||||
|
||||
const createGenericCameraConfig = (config?: RawFrigateCardConfig): CameraConfig => {
|
||||
return createCameraConfig(config);
|
||||
};
|
||||
|
||||
describe('GenericCameraManagerEngine', () => {
|
||||
it('should get engine type', () => {
|
||||
expect(createEngine().getEngineType()).toBe(Engine.Generic);
|
||||
});
|
||||
|
||||
it('should initialize camera', async () => {
|
||||
const config = createGenericCameraConfig();
|
||||
expect(
|
||||
await createEngine().initializeCamera(
|
||||
createHASS(),
|
||||
mock<EntityRegistryManager>(),
|
||||
config,
|
||||
),
|
||||
).toEqual(config);
|
||||
});
|
||||
|
||||
it('should generate default event query', () => {
|
||||
expect(
|
||||
createEngine().generateDefaultEventQuery(
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
new Set(['camera-1']),
|
||||
{},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate default recording query', () => {
|
||||
expect(
|
||||
createEngine().generateDefaultRecordingQuery(
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
new Set(['camera-1']),
|
||||
{},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate default recording segments query', () => {
|
||||
expect(
|
||||
createEngine().generateDefaultRecordingSegmentsQuery(
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
new Set(['camera-1']),
|
||||
{},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get events', async () => {
|
||||
expect(
|
||||
await createEngine().getEvents(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera-1']) },
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get recordings', async () => {
|
||||
expect(
|
||||
await createEngine().getRecordings(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{ type: QueryType.Recording, cameraIDs: new Set(['camera-1']) },
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get recording segments', async () => {
|
||||
expect(
|
||||
await createEngine().getRecordingSegments(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{
|
||||
type: QueryType.RecordingSegments,
|
||||
cameraIDs: new Set(['camera-1']),
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate media from events', async () => {
|
||||
expect(
|
||||
createEngine().generateMediaFromEvents(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera-1']),
|
||||
},
|
||||
{
|
||||
type: QueryResultsType.Event,
|
||||
engine: Engine.Generic,
|
||||
},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate media from recordings', async () => {
|
||||
expect(
|
||||
createEngine().generateMediaFromRecordings(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{
|
||||
type: QueryType.Recording,
|
||||
cameraIDs: new Set(['camera-1']),
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
},
|
||||
{
|
||||
type: QueryResultsType.Recording,
|
||||
engine: Engine.Generic,
|
||||
},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get media download path', async () => {
|
||||
expect(
|
||||
await createEngine().getMediaDownloadPath(
|
||||
createHASS(),
|
||||
createGenericCameraConfig(),
|
||||
new TestViewMedia(),
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should favorite media', async () => {
|
||||
expect(
|
||||
await createEngine().favoriteMedia(
|
||||
createHASS(),
|
||||
createGenericCameraConfig(),
|
||||
new TestViewMedia(),
|
||||
true,
|
||||
),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should get query result max age', () => {
|
||||
expect(
|
||||
createEngine().getQueryResultMaxAge({
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera-1']),
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get media seek time', async () => {
|
||||
expect(
|
||||
await createEngine().getMediaSeekTime(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
new TestViewMedia(),
|
||||
new Date(),
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should get media metadata', async () => {
|
||||
expect(
|
||||
await createEngine().getMediaMetadata(
|
||||
createHASS(),
|
||||
new Map([['camera-1', createGenericCameraConfig()]]),
|
||||
{ type: QueryType.MediaMetadata, cameraIDs: new Set(['camera-1']) },
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
describe('should get camera metadata', () => {
|
||||
it('with empty config', async () => {
|
||||
expect(
|
||||
createEngine().getCameraMetadata(createHASS(), createGenericCameraConfig()),
|
||||
).toEqual({
|
||||
icon: 'mdi:bookmark',
|
||||
title: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('with configured title', async () => {
|
||||
expect(
|
||||
createEngine().getCameraMetadata(
|
||||
createHASS(),
|
||||
createGenericCameraConfig({
|
||||
title: 'My Camera',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:bookmark',
|
||||
title: 'My Camera',
|
||||
});
|
||||
});
|
||||
|
||||
describe('with entity title', () => {
|
||||
it('camera_entity', async () => {
|
||||
expect(
|
||||
createEngine().getCameraMetadata(
|
||||
createHASS({
|
||||
'camera.test': createStateEntity({
|
||||
attributes: { friendly_name: 'My Entity Camera' },
|
||||
}),
|
||||
}),
|
||||
createGenericCameraConfig({
|
||||
camera_entity: 'camera.test',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:bookmark',
|
||||
title: 'My Entity Camera',
|
||||
});
|
||||
});
|
||||
|
||||
it('webrtc_card.entity', async () => {
|
||||
expect(
|
||||
createEngine().getCameraMetadata(
|
||||
createHASS({
|
||||
'camera.test': createStateEntity({
|
||||
attributes: { friendly_name: 'My Entity Camera' },
|
||||
}),
|
||||
}),
|
||||
createGenericCameraConfig({
|
||||
webrtc_card: {
|
||||
entity: 'camera.test',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:bookmark',
|
||||
title: 'My Entity Camera',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get camera capabilities metadata', async () => {
|
||||
expect(createEngine().getCameraCapabilities(createGenericCameraConfig())).toEqual({
|
||||
canFavoriteEvents: false,
|
||||
canFavoriteRecordings: false,
|
||||
canSeek: false,
|
||||
supportsClips: false,
|
||||
supportsRecordings: false,
|
||||
supportsSnapshots: false,
|
||||
supportsTimeline: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should get media capabilities', () => {
|
||||
expect(createEngine().getMediaCapabilities(new TestViewMedia())).toBeNull();
|
||||
});
|
||||
|
||||
describe('should get camera endpoints', () => {
|
||||
it('default', () => {
|
||||
expect(createEngine().getCameraEndpoints(createGenericCameraConfig())).toBeNull();
|
||||
});
|
||||
|
||||
it('for go2rtc', () => {
|
||||
expect(
|
||||
createEngine().getCameraEndpoints(
|
||||
createGenericCameraConfig({
|
||||
go2rtc: {
|
||||
stream: 'stream',
|
||||
url: '/local/path',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
go2rtc: {
|
||||
endpoint: '/local/path/api/ws?src=stream',
|
||||
sign: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,10 +3,11 @@ import {
|
||||
capEndDate,
|
||||
convertRangeToCacheFriendlyTimes,
|
||||
getCameraEntityFromConfig,
|
||||
getDefaultGo2RTCEndpoint,
|
||||
sortMedia,
|
||||
} from '../../src/camera-manager/util.js';
|
||||
} from '../../src/camera-manager/utils.js';
|
||||
import { CameraConfig, cameraConfigSchema } from '../../src/config/types.js';
|
||||
import { TestViewMedia } from '../test-utils.js';
|
||||
import { TestViewMedia, createCameraConfig } from '../test-utils.js';
|
||||
|
||||
describe('convertRangeToCacheFriendlyTimes', () => {
|
||||
it('should return cache friendly within hour range', () => {
|
||||
@@ -134,3 +135,41 @@ describe('getCameraEntityFromConfig', () => {
|
||||
expect(getCameraEntityFromConfig(createCameraConfig({}))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultGo2RTCEndpoint', () => {
|
||||
it('with local configuration', () => {
|
||||
expect(
|
||||
getDefaultGo2RTCEndpoint(
|
||||
createCameraConfig({
|
||||
go2rtc: {
|
||||
stream: 'stream',
|
||||
url: '/local/path',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
endpoint: '/local/path/api/ws?src=stream',
|
||||
sign: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('with remote configuration', () => {
|
||||
expect(
|
||||
getDefaultGo2RTCEndpoint(
|
||||
createCameraConfig({
|
||||
go2rtc: {
|
||||
stream: 'stream',
|
||||
url: 'https://my-custom-go2rtc',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
endpoint: 'https://my-custom-go2rtc/api/ws?src=stream',
|
||||
sign: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('without configuration', () => {
|
||||
expect(getDefaultGo2RTCEndpoint(createCameraConfig())).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -435,3 +435,17 @@ it('should not require title controls to specify all options', () => {
|
||||
}),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should strip trailing slashes from go2rtc url', () => {
|
||||
const config = createConfig({
|
||||
cameras: [
|
||||
{
|
||||
go2rtc: {
|
||||
url: 'https://my-custom-go2rtc//',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(config).toBeTruthy();
|
||||
expect(config.cameras[0].go2rtc.url).toBe('https://my-custom-go2rtc');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user