@matterbridge/core 3.10.9-dev-20260908-a3f4064 → 3.10.9-dev-20260910-ca91bcb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behaviors/cameraAvSettingsUserLevelManagementServer.d.ts +24 -0
- package/dist/behaviors/cameraAvSettingsUserLevelManagementServer.js +53 -0
- package/dist/behaviors/cameraAvStreamManagementServer.d.ts +80 -0
- package/dist/behaviors/cameraAvStreamManagementServer.js +414 -0
- package/dist/behaviors/chimeServer.d.ts +12 -0
- package/dist/behaviors/chimeServer.js +37 -0
- package/dist/behaviors/clients.d.ts +4 -0
- package/dist/behaviors/clients.js +33 -0
- package/dist/behaviors/ffmpeg.d.ts +4 -0
- package/dist/behaviors/ffmpeg.js +103 -0
- package/dist/behaviors/snapshot.d.ts +15 -0
- package/dist/behaviors/snapshot.js +139 -0
- package/dist/behaviors/webRtcTransportProviderServer.d.ts +28 -0
- package/dist/behaviors/webRtcTransportProviderServer.js +422 -0
- package/dist/behaviors/webRtcTransportRequestorServer.d.ts +10 -0
- package/dist/behaviors/webRtcTransportRequestorServer.js +24 -0
- package/dist/behaviors/weriftSession.d.ts +63 -0
- package/dist/behaviors/weriftSession.js +538 -0
- package/dist/devices/audioDoorbell.d.ts +20 -0
- package/dist/devices/audioDoorbell.js +49 -0
- package/dist/devices/camera.d.ts +37 -0
- package/dist/devices/camera.js +64 -0
- package/dist/devices/chime.d.ts +16 -0
- package/dist/devices/chime.js +37 -0
- package/dist/devices/doorbell.d.ts +11 -0
- package/dist/devices/doorbell.js +31 -0
- package/dist/devices/floodlightCamera.d.ts +19 -0
- package/dist/devices/floodlightCamera.js +81 -0
- package/dist/devices/intercom.d.ts +22 -0
- package/dist/devices/intercom.js +55 -0
- package/dist/devices/snapshotCamera.d.ts +20 -0
- package/dist/devices/snapshotCamera.js +51 -0
- package/dist/devices/videoDoorbell.d.ts +21 -0
- package/dist/devices/videoDoorbell.js +82 -0
- package/dist/matterbridgeEndpoint.d.ts +4 -1
- package/dist/matterbridgeEndpoint.js +4 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +3 -0
- package/dist/matterbridgeEndpointHelpers.js +51 -2
- package/dist/matterbridgeEndpointTypes.d.ts +15 -0
- package/dist/matterbridgeEndpointTypes.js +1 -2
- package/package.json +6 -5
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CameraAvSettingsUserLevelManagementServer } from '@matter/node/behaviors/camera-av-settings-user-level-management';
|
|
2
|
+
import { CameraAvSettingsUserLevelManagement } from '@matter/types/clusters/camera-av-settings-user-level-management';
|
|
3
|
+
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
4
|
+
declare const MatterbridgeCameraAvSettingsUserLevelManagementServer_base: import("@matter/node").ClusterBehavior.Type<typeof CameraAvSettingsUserLevelManagementServer, import("@matter/types").ClusterType.WithSupportedFeatures<CameraAvSettingsUserLevelManagement, {
|
|
5
|
+
digitalPtz: false;
|
|
6
|
+
mechanicalPan: true;
|
|
7
|
+
mechanicalPresets: false;
|
|
8
|
+
mechanicalTilt: true;
|
|
9
|
+
mechanicalZoom: true;
|
|
10
|
+
}>, import("@matter/types").ClusterType.Concrete, new () => {}, "cameraAvSettingsUserLevelManagement">;
|
|
11
|
+
export declare class MatterbridgeCameraAvSettingsUserLevelManagementServer extends MatterbridgeCameraAvSettingsUserLevelManagementServer_base {
|
|
12
|
+
mptzSetPosition(request: CameraAvSettingsUserLevelManagement.MptzSetPositionRequest): void;
|
|
13
|
+
mptzRelativeMove(request: CameraAvSettingsUserLevelManagement.MptzRelativeMoveRequest): void;
|
|
14
|
+
}
|
|
15
|
+
export interface CameraAvSettingsUserLevelManagementClusterOptions {
|
|
16
|
+
panMin: number;
|
|
17
|
+
panMax: number;
|
|
18
|
+
tiltMin: number;
|
|
19
|
+
tiltMax: number;
|
|
20
|
+
zoomMax: number;
|
|
21
|
+
mptzPosition: CameraAvSettingsUserLevelManagement.Mptz;
|
|
22
|
+
}
|
|
23
|
+
export declare function createDefaultCameraAvSettingsUserLevelManagementClusterServer(endpoint: MatterbridgeEndpoint, options: CameraAvSettingsUserLevelManagementClusterOptions): MatterbridgeEndpoint;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CameraAvSettingsUserLevelManagementServer } from '@matter/node/behaviors/camera-av-settings-user-level-management';
|
|
2
|
+
import { Status, StatusResponseError } from '@matter/types';
|
|
3
|
+
import { CameraAvSettingsUserLevelManagement } from '@matter/types/clusters/camera-av-settings-user-level-management';
|
|
4
|
+
import { emitCommand } from '../matterbridgeEndpointHelpers.js';
|
|
5
|
+
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
6
|
+
function clamp(value, min, max) {
|
|
7
|
+
return Math.min(Math.max(value, min), max);
|
|
8
|
+
}
|
|
9
|
+
export class MatterbridgeCameraAvSettingsUserLevelManagementServer extends CameraAvSettingsUserLevelManagementServer.with(CameraAvSettingsUserLevelManagement.Feature.MechanicalPan, CameraAvSettingsUserLevelManagement.Feature.MechanicalTilt, CameraAvSettingsUserLevelManagement.Feature.MechanicalZoom) {
|
|
10
|
+
mptzSetPosition(request) {
|
|
11
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
12
|
+
const { pan, tilt, zoom } = request;
|
|
13
|
+
if (pan === undefined && tilt === undefined && zoom === undefined) {
|
|
14
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: mptzSetPosition requires at least one of pan, tilt or zoom to be present (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidCommand);
|
|
15
|
+
}
|
|
16
|
+
if (pan !== undefined && (pan < this.state.panMin || pan > this.state.panMax)) {
|
|
17
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: pan ${pan} is outside of the supported range [${this.state.panMin}, ${this.state.panMax}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
18
|
+
}
|
|
19
|
+
if (tilt !== undefined && (tilt < this.state.tiltMin || tilt > this.state.tiltMax)) {
|
|
20
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: tilt ${tilt} is outside of the supported range [${this.state.tiltMin}, ${this.state.tiltMax}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
21
|
+
}
|
|
22
|
+
if (zoom !== undefined && (zoom < 1 || zoom > this.state.zoomMax)) {
|
|
23
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: zoom ${zoom} is outside of the supported range [1, ${this.state.zoomMax}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
24
|
+
}
|
|
25
|
+
this.state.mptzPosition = {
|
|
26
|
+
pan: pan ?? this.state.mptzPosition.pan,
|
|
27
|
+
tilt: tilt ?? this.state.mptzPosition.tilt,
|
|
28
|
+
zoom: zoom ?? this.state.mptzPosition.zoom,
|
|
29
|
+
};
|
|
30
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: set mechanical PTZ position to pan ${this.state.mptzPosition.pan}°, tilt ${this.state.mptzPosition.tilt}°, zoom ${this.state.mptzPosition.zoom} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
31
|
+
emitCommand(this.endpoint, CameraAvSettingsUserLevelManagementServer.id, 'mptzSetPosition', request, this.context);
|
|
32
|
+
}
|
|
33
|
+
mptzRelativeMove(request) {
|
|
34
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
35
|
+
if (request.panDelta === undefined && request.tiltDelta === undefined && request.zoomDelta === undefined) {
|
|
36
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: mptzRelativeMove requires at least one of panDelta, tiltDelta or zoomDelta to be present (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidCommand);
|
|
37
|
+
}
|
|
38
|
+
const current = this.state.mptzPosition;
|
|
39
|
+
const pan = request.panDelta === undefined ? current.pan : clamp((current.pan ?? 0) + request.panDelta, this.state.panMin, this.state.panMax);
|
|
40
|
+
const tilt = request.tiltDelta === undefined ? current.tilt : clamp((current.tilt ?? 0) + request.tiltDelta, this.state.tiltMin, this.state.tiltMax);
|
|
41
|
+
const zoom = request.zoomDelta === undefined ? current.zoom : clamp((current.zoom ?? 1) + request.zoomDelta, 1, this.state.zoomMax);
|
|
42
|
+
this.state.mptzPosition = { pan, tilt, zoom };
|
|
43
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: moved mechanical PTZ position by pan ${request.panDelta ?? 0}°, tilt ${request.tiltDelta ?? 0}°, zoom ${request.zoomDelta ?? 0} to pan ${pan}°, tilt ${tilt}°, zoom ${zoom} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
44
|
+
emitCommand(this.endpoint, CameraAvSettingsUserLevelManagementServer.id, 'mptzRelativeMove', request, this.context);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function createDefaultCameraAvSettingsUserLevelManagementClusterServer(endpoint, options) {
|
|
48
|
+
endpoint.behaviors.require(MatterbridgeCameraAvSettingsUserLevelManagementServer.with(CameraAvSettingsUserLevelManagement.Feature.MechanicalPan, CameraAvSettingsUserLevelManagement.Feature.MechanicalTilt, CameraAvSettingsUserLevelManagement.Feature.MechanicalZoom), {
|
|
49
|
+
...options,
|
|
50
|
+
movementState: CameraAvSettingsUserLevelManagement.PhysicalMovement.Idle,
|
|
51
|
+
});
|
|
52
|
+
return endpoint;
|
|
53
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CameraAvStreamManagementServer } from '@matter/node/behaviors/camera-av-stream-management';
|
|
2
|
+
import { StreamUsage } from '@matter/types';
|
|
3
|
+
import type { Viewport } from '@matter/types';
|
|
4
|
+
import { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
5
|
+
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
6
|
+
export interface CameraColorTestJpeg {
|
|
7
|
+
data: Buffer;
|
|
8
|
+
resolution: CameraAvStreamManagement.VideoResolution;
|
|
9
|
+
}
|
|
10
|
+
export declare function cameraColorTestJpegForResolution(requestedResolution: CameraAvStreamManagement.VideoResolution): CameraColorTestJpeg;
|
|
11
|
+
declare const MatterbridgeCameraAvStreamManagementServer_base: import("@matter/node").ClusterBehavior.Type<typeof CameraAvStreamManagementServer, import("@matter/types").ClusterType.WithSupportedFeatures<CameraAvStreamManagement, {
|
|
12
|
+
audio: true;
|
|
13
|
+
highDynamicRange: false;
|
|
14
|
+
imageControl: true;
|
|
15
|
+
localStorage: false;
|
|
16
|
+
nightVision: false;
|
|
17
|
+
onScreenDisplay: false;
|
|
18
|
+
privacy: false;
|
|
19
|
+
snapshot: true;
|
|
20
|
+
speaker: false;
|
|
21
|
+
video: true;
|
|
22
|
+
watermark: false;
|
|
23
|
+
}>, import("@matter/types").ClusterType.Concrete, new () => {}, "cameraAvStreamManagement">;
|
|
24
|
+
export declare class MatterbridgeCameraAvStreamManagementServer extends MatterbridgeCameraAvStreamManagementServer_base {
|
|
25
|
+
#private;
|
|
26
|
+
initialize(): void;
|
|
27
|
+
setStreamPriorities(request: CameraAvStreamManagement.SetStreamPrioritiesRequest): void;
|
|
28
|
+
videoStreamAllocate(request: CameraAvStreamManagement.VideoStreamAllocateRequest): CameraAvStreamManagement.VideoStreamAllocateResponse;
|
|
29
|
+
videoStreamDeallocate(request: CameraAvStreamManagement.VideoStreamDeallocateRequest): void;
|
|
30
|
+
audioStreamAllocate(request: CameraAvStreamManagement.AudioStreamAllocateRequest): CameraAvStreamManagement.AudioStreamAllocateResponse;
|
|
31
|
+
audioStreamDeallocate(request: CameraAvStreamManagement.AudioStreamDeallocateRequest): void;
|
|
32
|
+
snapshotStreamAllocate(request: CameraAvStreamManagement.SnapshotStreamAllocateRequest): Promise<CameraAvStreamManagement.SnapshotStreamAllocateResponse>;
|
|
33
|
+
snapshotStreamDeallocate(request: CameraAvStreamManagement.SnapshotStreamDeallocateRequest): Promise<void>;
|
|
34
|
+
captureSnapshot(request: CameraAvStreamManagement.CaptureSnapshotRequest): CameraAvStreamManagement.CaptureSnapshotResponse;
|
|
35
|
+
}
|
|
36
|
+
export interface CameraAvStreamManagementClusterOptions {
|
|
37
|
+
maxContentBufferSize: number;
|
|
38
|
+
maxNetworkBandwidth: number;
|
|
39
|
+
supportedStreamUsages: StreamUsage[];
|
|
40
|
+
streamUsagePriorities: StreamUsage[];
|
|
41
|
+
maxConcurrentEncoders: number;
|
|
42
|
+
maxEncodedPixelRate: number;
|
|
43
|
+
videoSensorParams: CameraAvStreamManagement.VideoSensorParams;
|
|
44
|
+
minViewportResolution: CameraAvStreamManagement.VideoResolution;
|
|
45
|
+
rateDistortionTradeOffPoints: CameraAvStreamManagement.RateDistortionTradeOffPoints[];
|
|
46
|
+
currentFrameRate: number;
|
|
47
|
+
viewport: Viewport;
|
|
48
|
+
microphoneCapabilities: CameraAvStreamManagement.AudioCapabilities;
|
|
49
|
+
snapshotCapabilities: CameraAvStreamManagement.SnapshotCapabilities[];
|
|
50
|
+
}
|
|
51
|
+
export declare function createDefaultCameraAvStreamManagementClusterServer(endpoint: MatterbridgeEndpoint, options: CameraAvStreamManagementClusterOptions): MatterbridgeEndpoint;
|
|
52
|
+
export interface AudioCameraAvStreamManagementClusterOptions {
|
|
53
|
+
maxContentBufferSize: number;
|
|
54
|
+
maxNetworkBandwidth: number;
|
|
55
|
+
supportedStreamUsages: StreamUsage[];
|
|
56
|
+
streamUsagePriorities: StreamUsage[];
|
|
57
|
+
microphoneCapabilities: CameraAvStreamManagement.AudioCapabilities;
|
|
58
|
+
}
|
|
59
|
+
export declare function createDefaultAudioCameraAvStreamManagementClusterServer(endpoint: MatterbridgeEndpoint, options: AudioCameraAvStreamManagementClusterOptions): MatterbridgeEndpoint;
|
|
60
|
+
export interface IntercomCameraAvStreamManagementClusterOptions {
|
|
61
|
+
maxContentBufferSize: number;
|
|
62
|
+
maxNetworkBandwidth: number;
|
|
63
|
+
supportedStreamUsages: StreamUsage[];
|
|
64
|
+
streamUsagePriorities: StreamUsage[];
|
|
65
|
+
microphoneCapabilities: CameraAvStreamManagement.AudioCapabilities;
|
|
66
|
+
speakerCapabilities: CameraAvStreamManagement.AudioCapabilities;
|
|
67
|
+
twoWayTalkSupport: CameraAvStreamManagement.TwoWayTalkSupportType;
|
|
68
|
+
}
|
|
69
|
+
export declare function createDefaultIntercomCameraAvStreamManagementClusterServer(endpoint: MatterbridgeEndpoint, options: IntercomCameraAvStreamManagementClusterOptions): MatterbridgeEndpoint;
|
|
70
|
+
export interface SnapshotCameraAvStreamManagementClusterOptions {
|
|
71
|
+
maxConcurrentEncoders: number;
|
|
72
|
+
maxEncodedPixelRate: number;
|
|
73
|
+
maxContentBufferSize: number;
|
|
74
|
+
snapshotCapabilities: CameraAvStreamManagement.SnapshotCapabilities[];
|
|
75
|
+
maxNetworkBandwidth: number;
|
|
76
|
+
supportedStreamUsages: StreamUsage[];
|
|
77
|
+
streamUsagePriorities: StreamUsage[];
|
|
78
|
+
}
|
|
79
|
+
export declare function createDefaultSnapshotCameraAvStreamManagementClusterServer(endpoint: MatterbridgeEndpoint, options: SnapshotCameraAvStreamManagementClusterOptions): MatterbridgeEndpoint;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { CameraAvStreamManagementServer } from '@matter/node/behaviors/camera-av-stream-management';
|
|
3
|
+
import { Status, StatusResponseError, StreamUsage, ThreeLevelAuto } from '@matter/types';
|
|
4
|
+
import { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
5
|
+
import { emitCommand } from '../matterbridgeEndpointHelpers.js';
|
|
6
|
+
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
7
|
+
const DEFAULT_CAMERA_COLOR_TEST_RESOLUTION = '640x480';
|
|
8
|
+
const AUDIO_STREAM_BIT_DEPTHS = [8, 16, 24, 32];
|
|
9
|
+
function numericEnumValues(enumObject) {
|
|
10
|
+
return Object.values(enumObject).filter((value) => typeof value === 'number');
|
|
11
|
+
}
|
|
12
|
+
const AUDIO_CODECS = numericEnumValues(CameraAvStreamManagement.AudioCodec);
|
|
13
|
+
const VIDEO_CODECS = numericEnumValues(CameraAvStreamManagement.VideoCodec);
|
|
14
|
+
const cameraColorTestJpegs = {
|
|
15
|
+
'640x480': { data: readFileSync(new URL('../../assets/camera-color-test-640-480.jpeg', import.meta.url)), resolution: { width: 640, height: 480 } },
|
|
16
|
+
'1280x720': { data: readFileSync(new URL('../../assets/camera-color-test-1280-720.jpeg', import.meta.url)), resolution: { width: 1280, height: 720 } },
|
|
17
|
+
'1920x1080': { data: readFileSync(new URL('../../assets/camera-color-test-1920-1080.jpeg', import.meta.url)), resolution: { width: 1920, height: 1080 } },
|
|
18
|
+
};
|
|
19
|
+
export function cameraColorTestJpegForResolution(requestedResolution) {
|
|
20
|
+
return cameraColorTestJpegs[`${requestedResolution.width}x${requestedResolution.height}`] ?? cameraColorTestJpegs[DEFAULT_CAMERA_COLOR_TEST_RESOLUTION];
|
|
21
|
+
}
|
|
22
|
+
export class MatterbridgeCameraAvStreamManagementServer extends CameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Video, CameraAvStreamManagement.Feature.Audio, CameraAvStreamManagement.Feature.Snapshot, CameraAvStreamManagement.Feature.ImageControl) {
|
|
23
|
+
#isAutoAllocateSkipped() {
|
|
24
|
+
return process.env.MATTERBRIDGE_SKIP_AUTO_ALLOCATE_CAMERA_AV_STREAM_MANAGEMENT === '1';
|
|
25
|
+
}
|
|
26
|
+
initialize() {
|
|
27
|
+
if (this.#isAutoAllocateSkipped())
|
|
28
|
+
return;
|
|
29
|
+
if (this.features.video && this.state.allocatedVideoStreams.length === 0 && this.state.rateDistortionTradeOffPoints.length > 0 && this.state.streamUsagePriorities.length > 0) {
|
|
30
|
+
const { rateDistortionTradeOffPoints } = this.state;
|
|
31
|
+
let largestTradeOffPoint = rateDistortionTradeOffPoints[0];
|
|
32
|
+
for (const point of rateDistortionTradeOffPoints) {
|
|
33
|
+
if (point.resolution.width * point.resolution.height > largestTradeOffPoint.resolution.width * largestTradeOffPoint.resolution.height)
|
|
34
|
+
largestTradeOffPoint = point;
|
|
35
|
+
}
|
|
36
|
+
const { codec, resolution, minBitRate } = largestTradeOffPoint;
|
|
37
|
+
this.state.allocatedVideoStreams = [
|
|
38
|
+
{
|
|
39
|
+
videoStreamId: 0,
|
|
40
|
+
streamUsage: this.state.streamUsagePriorities[0],
|
|
41
|
+
videoCodec: codec,
|
|
42
|
+
minFrameRate: 1,
|
|
43
|
+
maxFrameRate: this.state.videoSensorParams.maxFps,
|
|
44
|
+
minResolution: this.state.minViewportResolution,
|
|
45
|
+
maxResolution: resolution,
|
|
46
|
+
minBitRate,
|
|
47
|
+
maxBitRate: minBitRate * 4,
|
|
48
|
+
keyFrameInterval: 4000,
|
|
49
|
+
...(this.features.watermark ? { watermarkEnabled: false } : {}),
|
|
50
|
+
...(this.features.onScreenDisplay ? { osdEnabled: false } : {}),
|
|
51
|
+
referenceCount: 0,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
if (this.features.audio &&
|
|
56
|
+
this.state.allocatedAudioStreams.length === 0 &&
|
|
57
|
+
this.state.microphoneCapabilities.supportedCodecs.length > 0 &&
|
|
58
|
+
this.state.streamUsagePriorities.length > 0) {
|
|
59
|
+
const { microphoneCapabilities } = this.state;
|
|
60
|
+
this.state.allocatedAudioStreams = [
|
|
61
|
+
{
|
|
62
|
+
audioStreamId: 0,
|
|
63
|
+
streamUsage: this.state.streamUsagePriorities[0],
|
|
64
|
+
audioCodec: microphoneCapabilities.supportedCodecs[0],
|
|
65
|
+
channelCount: microphoneCapabilities.maxNumberOfChannels,
|
|
66
|
+
sampleRate: microphoneCapabilities.supportedSampleRates[0],
|
|
67
|
+
bitRate: 32_000,
|
|
68
|
+
bitDepth: microphoneCapabilities.supportedBitDepths[0],
|
|
69
|
+
referenceCount: 0,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
if (this.features.snapshot && this.state.allocatedSnapshotStreams.length === 0 && this.state.snapshotCapabilities.length > 0) {
|
|
74
|
+
const { snapshotCapabilities } = this.state;
|
|
75
|
+
let smallestCapability = snapshotCapabilities[0];
|
|
76
|
+
let largestCapability = snapshotCapabilities[0];
|
|
77
|
+
for (const capability of snapshotCapabilities) {
|
|
78
|
+
if (capability.resolution.width * capability.resolution.height < smallestCapability.resolution.width * smallestCapability.resolution.height)
|
|
79
|
+
smallestCapability = capability;
|
|
80
|
+
if (capability.resolution.width * capability.resolution.height > largestCapability.resolution.width * largestCapability.resolution.height)
|
|
81
|
+
largestCapability = capability;
|
|
82
|
+
}
|
|
83
|
+
this.state.allocatedSnapshotStreams = [
|
|
84
|
+
{
|
|
85
|
+
snapshotStreamId: 0,
|
|
86
|
+
imageCodec: largestCapability.imageCodec,
|
|
87
|
+
frameRate: largestCapability.maxFrameRate,
|
|
88
|
+
minResolution: smallestCapability.resolution,
|
|
89
|
+
maxResolution: largestCapability.resolution,
|
|
90
|
+
quality: 90,
|
|
91
|
+
referenceCount: 0,
|
|
92
|
+
encodedPixels: false,
|
|
93
|
+
hardwareEncoder: false,
|
|
94
|
+
...(this.features.watermark ? { watermarkEnabled: false } : {}),
|
|
95
|
+
...(this.features.onScreenDisplay ? { osdEnabled: false } : {}),
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
setStreamPriorities(request) {
|
|
101
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
102
|
+
if ((this.features.snapshot && this.state.allocatedSnapshotStreams.length > 0) ||
|
|
103
|
+
(this.features.video && this.state.allocatedVideoStreams.length > 0) ||
|
|
104
|
+
(this.features.audio && this.state.allocatedAudioStreams.length > 0)) {
|
|
105
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: setStreamPriorities cannot be invoked while snapshot, video or audio streams are allocated (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
|
|
106
|
+
}
|
|
107
|
+
if (!request.streamPriorities.every((usage) => this.state.supportedStreamUsages.includes(usage))) {
|
|
108
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: streamPriorities shall only contain entries found in supportedStreamUsages (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
109
|
+
}
|
|
110
|
+
if (new Set(request.streamPriorities).size !== request.streamPriorities.length) {
|
|
111
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: streamPriorities shall not contain duplicate values (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.AlreadyExists);
|
|
112
|
+
}
|
|
113
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: setting stream priorities to [${request.streamPriorities.join(', ')}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
114
|
+
this.state.streamUsagePriorities = request.streamPriorities;
|
|
115
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'setStreamPriorities', request, this.context);
|
|
116
|
+
}
|
|
117
|
+
videoStreamAllocate(request) {
|
|
118
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
119
|
+
if (request.streamUsage === StreamUsage.Internal) {
|
|
120
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: stream usage Internal is not allowed for VideoStreamAllocate (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
121
|
+
}
|
|
122
|
+
if (!this.state.streamUsagePriorities.includes(request.streamUsage)) {
|
|
123
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: stream usage ${request.streamUsage} is not present in streamUsagePriorities (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
|
|
124
|
+
}
|
|
125
|
+
if (!VIDEO_CODECS.includes(request.videoCodec)) {
|
|
126
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: videoCodec ${request.videoCodec} is not a valid VideoCodecEnum value (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
127
|
+
}
|
|
128
|
+
if (request.minFrameRate > request.maxFrameRate) {
|
|
129
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: minFrameRate ${request.minFrameRate} must not be greater than MaxFrameRate ${request.maxFrameRate} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
130
|
+
}
|
|
131
|
+
if (request.minBitRate > request.maxBitRate) {
|
|
132
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: minBitRate ${request.minBitRate} must not be greater than MaxBitRate ${request.maxBitRate} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
133
|
+
}
|
|
134
|
+
const matchesTradeOffPoint = this.state.rateDistortionTradeOffPoints.some((point) => point.codec === request.videoCodec &&
|
|
135
|
+
point.resolution.width >= request.minResolution.width &&
|
|
136
|
+
point.resolution.width <= request.maxResolution.width &&
|
|
137
|
+
point.resolution.height >= request.minResolution.height &&
|
|
138
|
+
point.resolution.height <= request.maxResolution.height &&
|
|
139
|
+
point.minBitRate <= request.maxBitRate);
|
|
140
|
+
if (!matchesTradeOffPoint || request.maxFrameRate > this.state.videoSensorParams.maxFps) {
|
|
141
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: videoStreamAllocate requested parameters do not match any entry in rateDistortionTradeOffPoints or exceed videoSensorParams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
142
|
+
}
|
|
143
|
+
const existingStream = this.state.allocatedVideoStreams.find((stream) => stream.streamUsage === request.streamUsage &&
|
|
144
|
+
stream.videoCodec === request.videoCodec &&
|
|
145
|
+
stream.minFrameRate === request.minFrameRate &&
|
|
146
|
+
stream.maxFrameRate === request.maxFrameRate &&
|
|
147
|
+
stream.minResolution.width === request.minResolution.width &&
|
|
148
|
+
stream.minResolution.height === request.minResolution.height &&
|
|
149
|
+
stream.maxResolution.width === request.maxResolution.width &&
|
|
150
|
+
stream.maxResolution.height === request.maxResolution.height &&
|
|
151
|
+
stream.minBitRate === request.minBitRate &&
|
|
152
|
+
stream.maxBitRate === request.maxBitRate &&
|
|
153
|
+
stream.keyFrameInterval === request.keyFrameInterval);
|
|
154
|
+
if (existingStream) {
|
|
155
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: reused video stream ${existingStream.videoStreamId} for usage ${request.streamUsage} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
156
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'videoStreamAllocate', request, this.context);
|
|
157
|
+
return { videoStreamId: existingStream.videoStreamId };
|
|
158
|
+
}
|
|
159
|
+
if (this.state.allocatedVideoStreams.length >= this.state.maxConcurrentEncoders) {
|
|
160
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: videoStreamAllocate would exceed maxConcurrentEncoders (${this.state.maxConcurrentEncoders}) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ResourceExhausted);
|
|
161
|
+
}
|
|
162
|
+
let videoStreamId = 0;
|
|
163
|
+
for (const stream of this.state.allocatedVideoStreams) {
|
|
164
|
+
videoStreamId = Math.max(videoStreamId, stream.videoStreamId + 1);
|
|
165
|
+
}
|
|
166
|
+
this.state.allocatedVideoStreams = [
|
|
167
|
+
...this.state.allocatedVideoStreams,
|
|
168
|
+
{
|
|
169
|
+
videoStreamId,
|
|
170
|
+
streamUsage: request.streamUsage,
|
|
171
|
+
videoCodec: request.videoCodec,
|
|
172
|
+
minFrameRate: request.minFrameRate,
|
|
173
|
+
maxFrameRate: request.maxFrameRate,
|
|
174
|
+
minResolution: request.minResolution,
|
|
175
|
+
maxResolution: request.maxResolution,
|
|
176
|
+
minBitRate: request.minBitRate,
|
|
177
|
+
maxBitRate: request.maxBitRate,
|
|
178
|
+
keyFrameInterval: request.keyFrameInterval,
|
|
179
|
+
watermarkEnabled: request.watermarkEnabled,
|
|
180
|
+
osdEnabled: request.osdEnabled,
|
|
181
|
+
referenceCount: 0,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: allocated video stream ${videoStreamId} for usage ${request.streamUsage} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
185
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'videoStreamAllocate', request, this.context);
|
|
186
|
+
return { videoStreamId };
|
|
187
|
+
}
|
|
188
|
+
videoStreamDeallocate(request) {
|
|
189
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
190
|
+
if (!this.state.allocatedVideoStreams.some((stream) => stream.videoStreamId === request.videoStreamId)) {
|
|
191
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: video stream ${request.videoStreamId} is not present in allocatedVideoStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
192
|
+
}
|
|
193
|
+
this.state.allocatedVideoStreams = this.state.allocatedVideoStreams.filter((stream) => stream.videoStreamId !== request.videoStreamId);
|
|
194
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated video stream ${request.videoStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
195
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'videoStreamDeallocate', request, this.context);
|
|
196
|
+
}
|
|
197
|
+
audioStreamAllocate(request) {
|
|
198
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
199
|
+
if (request.streamUsage === StreamUsage.Internal) {
|
|
200
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: stream usage Internal is not allowed for AudioStreamAllocate (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
201
|
+
}
|
|
202
|
+
if (!this.state.streamUsagePriorities.includes(request.streamUsage)) {
|
|
203
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: stream usage ${request.streamUsage} is not present in streamUsagePriorities (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
|
|
204
|
+
}
|
|
205
|
+
if (!AUDIO_CODECS.includes(request.audioCodec)) {
|
|
206
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audioCodec ${request.audioCodec} is not a valid AudioCodecEnum value (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
207
|
+
}
|
|
208
|
+
if (!AUDIO_STREAM_BIT_DEPTHS.includes(request.bitDepth)) {
|
|
209
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: bitDepth ${request.bitDepth} is not one of ${AUDIO_STREAM_BIT_DEPTHS.join(', ')} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
210
|
+
}
|
|
211
|
+
const { microphoneCapabilities } = this.state;
|
|
212
|
+
if (!microphoneCapabilities.supportedCodecs.includes(request.audioCodec) ||
|
|
213
|
+
request.channelCount < 1 ||
|
|
214
|
+
request.channelCount > microphoneCapabilities.maxNumberOfChannels ||
|
|
215
|
+
!microphoneCapabilities.supportedSampleRates.includes(request.sampleRate) ||
|
|
216
|
+
!microphoneCapabilities.supportedBitDepths.includes(request.bitDepth)) {
|
|
217
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audioStreamAllocate requested audioCodec, channelCount, sampleRate or bitDepth is not supported by microphoneCapabilities (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
218
|
+
}
|
|
219
|
+
const existingStream = this.state.allocatedAudioStreams.find((stream) => stream.streamUsage === request.streamUsage &&
|
|
220
|
+
stream.audioCodec === request.audioCodec &&
|
|
221
|
+
stream.channelCount === request.channelCount &&
|
|
222
|
+
stream.sampleRate === request.sampleRate &&
|
|
223
|
+
stream.bitRate === request.bitRate &&
|
|
224
|
+
stream.bitDepth === request.bitDepth);
|
|
225
|
+
if (existingStream) {
|
|
226
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: reused audio stream ${existingStream.audioStreamId} for usage ${request.streamUsage} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
227
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'audioStreamAllocate', request, this.context);
|
|
228
|
+
return { audioStreamId: existingStream.audioStreamId };
|
|
229
|
+
}
|
|
230
|
+
let audioStreamId = 0;
|
|
231
|
+
for (const stream of this.state.allocatedAudioStreams) {
|
|
232
|
+
audioStreamId = Math.max(audioStreamId, stream.audioStreamId + 1);
|
|
233
|
+
}
|
|
234
|
+
this.state.allocatedAudioStreams = [
|
|
235
|
+
...this.state.allocatedAudioStreams,
|
|
236
|
+
{
|
|
237
|
+
audioStreamId,
|
|
238
|
+
streamUsage: request.streamUsage,
|
|
239
|
+
audioCodec: request.audioCodec,
|
|
240
|
+
channelCount: request.channelCount,
|
|
241
|
+
sampleRate: request.sampleRate,
|
|
242
|
+
bitRate: request.bitRate,
|
|
243
|
+
bitDepth: request.bitDepth,
|
|
244
|
+
referenceCount: 0,
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: allocated audio stream ${audioStreamId} for usage ${request.streamUsage} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
248
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'audioStreamAllocate', request, this.context);
|
|
249
|
+
return { audioStreamId };
|
|
250
|
+
}
|
|
251
|
+
audioStreamDeallocate(request) {
|
|
252
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
253
|
+
if (!this.state.allocatedAudioStreams.some((stream) => stream.audioStreamId === request.audioStreamId)) {
|
|
254
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audio stream ${request.audioStreamId} is not present in allocatedAudioStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
255
|
+
}
|
|
256
|
+
this.state.allocatedAudioStreams = this.state.allocatedAudioStreams.filter((stream) => stream.audioStreamId !== request.audioStreamId);
|
|
257
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated audio stream ${request.audioStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
258
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'audioStreamDeallocate', request, this.context);
|
|
259
|
+
}
|
|
260
|
+
async snapshotStreamAllocate(request) {
|
|
261
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
262
|
+
const { minResolution, maxResolution } = request;
|
|
263
|
+
const matchesCapability = this.state.snapshotCapabilities.some((capability) => capability.resolution.width >= minResolution.width &&
|
|
264
|
+
capability.resolution.width <= maxResolution.width &&
|
|
265
|
+
capability.resolution.height >= minResolution.height &&
|
|
266
|
+
capability.resolution.height <= maxResolution.height);
|
|
267
|
+
if (!matchesCapability) {
|
|
268
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: snapshotStreamAllocate requested minResolution/maxResolution range does not match any entry in snapshotCapabilities (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
269
|
+
}
|
|
270
|
+
const existingStream = this.state.allocatedSnapshotStreams.find((stream) => stream.imageCodec === request.imageCodec &&
|
|
271
|
+
stream.frameRate === request.maxFrameRate &&
|
|
272
|
+
stream.quality === request.quality &&
|
|
273
|
+
stream.watermarkEnabled === request.watermarkEnabled &&
|
|
274
|
+
stream.osdEnabled === request.osdEnabled &&
|
|
275
|
+
stream.minResolution.width <= request.maxResolution.width &&
|
|
276
|
+
stream.maxResolution.width >= request.minResolution.width &&
|
|
277
|
+
stream.minResolution.height <= request.maxResolution.height &&
|
|
278
|
+
stream.maxResolution.height >= request.minResolution.height);
|
|
279
|
+
if (existingStream) {
|
|
280
|
+
this.state.allocatedSnapshotStreams = this.state.allocatedSnapshotStreams.map((stream) => stream.snapshotStreamId === existingStream.snapshotStreamId
|
|
281
|
+
? {
|
|
282
|
+
snapshotStreamId: stream.snapshotStreamId,
|
|
283
|
+
imageCodec: stream.imageCodec,
|
|
284
|
+
frameRate: stream.frameRate,
|
|
285
|
+
minResolution: request.minResolution,
|
|
286
|
+
maxResolution: request.maxResolution,
|
|
287
|
+
quality: stream.quality,
|
|
288
|
+
referenceCount: stream.referenceCount,
|
|
289
|
+
encodedPixels: stream.encodedPixels,
|
|
290
|
+
hardwareEncoder: stream.hardwareEncoder,
|
|
291
|
+
watermarkEnabled: stream.watermarkEnabled,
|
|
292
|
+
osdEnabled: stream.osdEnabled,
|
|
293
|
+
}
|
|
294
|
+
: stream);
|
|
295
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: reused snapshot stream ${existingStream.snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
296
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'snapshotStreamAllocate', request, this.context);
|
|
297
|
+
return { snapshotStreamId: existingStream.snapshotStreamId };
|
|
298
|
+
}
|
|
299
|
+
let snapshotStreamId = 0;
|
|
300
|
+
for (const stream of this.state.allocatedSnapshotStreams) {
|
|
301
|
+
snapshotStreamId = Math.max(snapshotStreamId, stream.snapshotStreamId + 1);
|
|
302
|
+
}
|
|
303
|
+
this.state.allocatedSnapshotStreams = [
|
|
304
|
+
...this.state.allocatedSnapshotStreams,
|
|
305
|
+
{
|
|
306
|
+
snapshotStreamId,
|
|
307
|
+
imageCodec: request.imageCodec,
|
|
308
|
+
frameRate: request.maxFrameRate,
|
|
309
|
+
minResolution: request.minResolution,
|
|
310
|
+
maxResolution: request.maxResolution,
|
|
311
|
+
quality: request.quality,
|
|
312
|
+
referenceCount: 0,
|
|
313
|
+
encodedPixels: false,
|
|
314
|
+
hardwareEncoder: false,
|
|
315
|
+
watermarkEnabled: request.watermarkEnabled,
|
|
316
|
+
osdEnabled: request.osdEnabled,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: allocated snapshot stream ${snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
320
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'snapshotStreamAllocate', request, this.context);
|
|
321
|
+
return { snapshotStreamId };
|
|
322
|
+
}
|
|
323
|
+
async snapshotStreamDeallocate(request) {
|
|
324
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
325
|
+
if (!this.state.allocatedSnapshotStreams.some((stream) => stream.snapshotStreamId === request.snapshotStreamId)) {
|
|
326
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: snapshot stream ${request.snapshotStreamId} is not present in allocatedSnapshotStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
327
|
+
}
|
|
328
|
+
this.state.allocatedSnapshotStreams = this.state.allocatedSnapshotStreams.filter((stream) => stream.snapshotStreamId !== request.snapshotStreamId);
|
|
329
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated snapshot stream ${request.snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
330
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'snapshotStreamDeallocate', request, this.context);
|
|
331
|
+
}
|
|
332
|
+
captureSnapshot(request) {
|
|
333
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
334
|
+
const { snapshotStreamId } = request;
|
|
335
|
+
const stream = this.state.allocatedSnapshotStreams.find((s) => snapshotStreamId === null || s.snapshotStreamId === snapshotStreamId);
|
|
336
|
+
if (!stream) {
|
|
337
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: snapshot stream ${snapshotStreamId ?? 'auto'} is not present in allocatedSnapshotStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
338
|
+
}
|
|
339
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: capturing snapshot ${snapshotStreamId ?? 'auto'} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
340
|
+
device.log.debug(`MatterbridgeCameraAvStreamManagementServer: captureSnapshot called with snapshotStreamId ${request.snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
341
|
+
const { data, resolution } = cameraColorTestJpegForResolution(request.requestedResolution);
|
|
342
|
+
emitCommand(this.endpoint, CameraAvStreamManagementServer.id, 'captureSnapshot', request, this.context);
|
|
343
|
+
return {
|
|
344
|
+
data,
|
|
345
|
+
imageCodec: CameraAvStreamManagement.ImageCodec.Jpeg,
|
|
346
|
+
resolution,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
export function createDefaultCameraAvStreamManagementClusterServer(endpoint, options) {
|
|
351
|
+
endpoint.behaviors.require(MatterbridgeCameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Video, CameraAvStreamManagement.Feature.Audio, CameraAvStreamManagement.Feature.Snapshot, CameraAvStreamManagement.Feature.ImageControl), {
|
|
352
|
+
...options,
|
|
353
|
+
hardPrivacyModeOn: false,
|
|
354
|
+
statusLightEnabled: false,
|
|
355
|
+
statusLightBrightness: ThreeLevelAuto.Auto,
|
|
356
|
+
allocatedVideoStreams: [],
|
|
357
|
+
allocatedAudioStreams: [],
|
|
358
|
+
allocatedSnapshotStreams: [],
|
|
359
|
+
microphoneMuted: false,
|
|
360
|
+
microphoneVolumeLevel: 128,
|
|
361
|
+
microphoneMaxLevel: 254,
|
|
362
|
+
microphoneMinLevel: 0,
|
|
363
|
+
microphoneAgcEnabled: false,
|
|
364
|
+
imageRotation: 0,
|
|
365
|
+
imageFlipVertical: false,
|
|
366
|
+
imageFlipHorizontal: false,
|
|
367
|
+
});
|
|
368
|
+
return endpoint;
|
|
369
|
+
}
|
|
370
|
+
export function createDefaultAudioCameraAvStreamManagementClusterServer(endpoint, options) {
|
|
371
|
+
endpoint.behaviors.require(MatterbridgeCameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Audio), {
|
|
372
|
+
...options,
|
|
373
|
+
hardPrivacyModeOn: false,
|
|
374
|
+
statusLightEnabled: false,
|
|
375
|
+
allocatedAudioStreams: [],
|
|
376
|
+
microphoneMuted: false,
|
|
377
|
+
microphoneVolumeLevel: 128,
|
|
378
|
+
microphoneMaxLevel: 254,
|
|
379
|
+
microphoneMinLevel: 0,
|
|
380
|
+
microphoneAgcEnabled: false,
|
|
381
|
+
});
|
|
382
|
+
return endpoint;
|
|
383
|
+
}
|
|
384
|
+
export function createDefaultIntercomCameraAvStreamManagementClusterServer(endpoint, options) {
|
|
385
|
+
endpoint.behaviors.require(MatterbridgeCameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Audio, CameraAvStreamManagement.Feature.Speaker), {
|
|
386
|
+
...options,
|
|
387
|
+
hardPrivacyModeOn: false,
|
|
388
|
+
statusLightEnabled: false,
|
|
389
|
+
allocatedAudioStreams: [],
|
|
390
|
+
microphoneMuted: false,
|
|
391
|
+
microphoneVolumeLevel: 128,
|
|
392
|
+
microphoneMaxLevel: 254,
|
|
393
|
+
microphoneMinLevel: 0,
|
|
394
|
+
microphoneAgcEnabled: false,
|
|
395
|
+
speakerMuted: false,
|
|
396
|
+
speakerVolumeLevel: 128,
|
|
397
|
+
speakerMaxLevel: 254,
|
|
398
|
+
speakerMinLevel: 0,
|
|
399
|
+
});
|
|
400
|
+
return endpoint;
|
|
401
|
+
}
|
|
402
|
+
export function createDefaultSnapshotCameraAvStreamManagementClusterServer(endpoint, options) {
|
|
403
|
+
endpoint.behaviors.require(MatterbridgeCameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Snapshot), {
|
|
404
|
+
maxContentBufferSize: options.maxContentBufferSize,
|
|
405
|
+
maxNetworkBandwidth: options.maxNetworkBandwidth,
|
|
406
|
+
supportedStreamUsages: options.supportedStreamUsages,
|
|
407
|
+
streamUsagePriorities: options.streamUsagePriorities,
|
|
408
|
+
maxConcurrentEncoders: options.maxConcurrentEncoders,
|
|
409
|
+
maxEncodedPixelRate: options.maxEncodedPixelRate,
|
|
410
|
+
snapshotCapabilities: options.snapshotCapabilities,
|
|
411
|
+
allocatedSnapshotStreams: [],
|
|
412
|
+
});
|
|
413
|
+
return endpoint;
|
|
414
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChimeServer } from '@matter/node/behaviors/chime';
|
|
2
|
+
import type { Chime } from '@matter/types/clusters/chime';
|
|
3
|
+
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
4
|
+
export type ChimeId = number;
|
|
5
|
+
declare const ChimeServerBase: import("@matter/node").ClusterBehavior.Type<typeof ChimeServer, import("@matter/types").ClusterType.WithEnabledAttributes<import("@matter/types").ClusterType.WithEnabledEvents<Chime, "chimeStartedPlaying">, never>, import("@matter/types").ClusterType.Concrete, new () => {}, "chime">;
|
|
6
|
+
export declare class MatterbridgeChimeServer extends ChimeServerBase {
|
|
7
|
+
#private;
|
|
8
|
+
initialize(): void;
|
|
9
|
+
playChimeSound(request: Chime.PlayChimeSoundRequest): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createDefaultChimeClusterServer(endpoint: MatterbridgeEndpoint, installedChimeSounds: Chime.ChimeSound[], selectedChime: ChimeId, enabled?: boolean): MatterbridgeEndpoint;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ChimeServer } from '@matter/node/behaviors/chime';
|
|
2
|
+
import { Status, StatusResponseError } from '@matter/types';
|
|
3
|
+
import { emitCommand } from '../matterbridgeEndpointHelpers.js';
|
|
4
|
+
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
5
|
+
const ChimeServerBase = ChimeServer.enable({ events: { chimeStartedPlaying: true } });
|
|
6
|
+
export class MatterbridgeChimeServer extends ChimeServerBase {
|
|
7
|
+
initialize() {
|
|
8
|
+
this.reactTo(this.events.selectedChime$Changing, this.#assertSelectedChime);
|
|
9
|
+
}
|
|
10
|
+
#assertSelectedChime(chimeId) {
|
|
11
|
+
if (!this.state.installedChimeSounds.some((chimeSound) => chimeSound.chimeId === chimeId)) {
|
|
12
|
+
throw new StatusResponseError(`MatterbridgeChimeServer: chime sound ${chimeId} is not present in installedChimeSounds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async playChimeSound(request) {
|
|
16
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
17
|
+
if (!this.state.enabled) {
|
|
18
|
+
device.log.debug(`MatterbridgeChimeServer: playChimeSound called but chime is disabled (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const chimeId = request.chimeId ?? this.state.selectedChime;
|
|
22
|
+
if (!this.state.installedChimeSounds.some((chimeSound) => chimeSound.chimeId === chimeId)) {
|
|
23
|
+
throw new StatusResponseError(`MatterbridgeChimeServer: chime sound ${chimeId} is not present in installedChimeSounds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
24
|
+
}
|
|
25
|
+
device.log.info(`MatterbridgeChimeServer: playing chime sound with chimeId ${chimeId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
26
|
+
this.events.chimeStartedPlaying.emit({ chimeId }, this.context);
|
|
27
|
+
emitCommand(this.endpoint, ChimeServer.id, 'playChimeSound', request, this.context);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function createDefaultChimeClusterServer(endpoint, installedChimeSounds, selectedChime, enabled = true) {
|
|
31
|
+
endpoint.behaviors.require(MatterbridgeChimeServer, {
|
|
32
|
+
installedChimeSounds,
|
|
33
|
+
selectedChime,
|
|
34
|
+
enabled,
|
|
35
|
+
});
|
|
36
|
+
return endpoint;
|
|
37
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
2
|
+
export declare function addChimeClient(endpoint: MatterbridgeEndpoint): MatterbridgeEndpoint;
|
|
3
|
+
export declare function addWebRtcTransportRequestorClient(endpoint: MatterbridgeEndpoint): MatterbridgeEndpoint;
|
|
4
|
+
export declare function addWebRtcTransportProviderClient(endpoint: MatterbridgeEndpoint): MatterbridgeEndpoint;
|