homebridge-unifi-protect 5.5.4 → 6.0.0
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/README.md +3 -3
- package/config.schema.json +17 -16
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/protect-camera.d.ts +58 -0
- package/dist/protect-camera.js +367 -246
- package/dist/protect-camera.js.map +1 -1
- package/dist/protect-device.d.ts +48 -0
- package/dist/protect-device.js +189 -0
- package/dist/protect-device.js.map +1 -0
- package/dist/protect-doorbell.d.ts +22 -0
- package/dist/protect-doorbell.js +75 -64
- package/dist/protect-doorbell.js.map +1 -1
- package/dist/protect-ffmpeg-record.d.ts +15 -0
- package/dist/protect-ffmpeg-record.js +48 -34
- package/dist/protect-ffmpeg-record.js.map +1 -1
- package/dist/protect-ffmpeg-stream.d.ts +15 -0
- package/dist/protect-ffmpeg-stream.js +22 -12
- package/dist/protect-ffmpeg-stream.js.map +1 -1
- package/dist/protect-ffmpeg.d.ts +42 -0
- package/dist/protect-ffmpeg.js +49 -58
- package/dist/protect-ffmpeg.js.map +1 -1
- package/dist/protect-light.d.ts +13 -0
- package/dist/protect-light.js +63 -40
- package/dist/protect-light.js.map +1 -1
- package/dist/protect-liveviews.d.ts +17 -0
- package/dist/protect-liveviews.js +117 -101
- package/dist/protect-liveviews.js.map +1 -1
- package/dist/protect-mqtt.d.ts +19 -0
- package/dist/protect-mqtt.js +26 -35
- package/dist/protect-mqtt.js.map +1 -1
- package/dist/protect-nvr-events.d.ts +30 -0
- package/dist/protect-nvr-events.js +168 -431
- package/dist/protect-nvr-events.js.map +1 -1
- package/dist/protect-nvr-systeminfo.d.ts +15 -0
- package/dist/protect-nvr-systeminfo.js +43 -49
- package/dist/protect-nvr-systeminfo.js.map +1 -1
- package/dist/protect-nvr.d.ts +48 -0
- package/dist/protect-nvr.js +327 -359
- package/dist/protect-nvr.js.map +1 -1
- package/dist/protect-options.d.ts +39 -0
- package/dist/protect-options.js +172 -6
- package/dist/protect-options.js.map +1 -1
- package/dist/protect-platform.d.ts +17 -0
- package/dist/protect-platform.js +17 -30
- package/dist/protect-platform.js.map +1 -1
- package/dist/protect-record.d.ts +33 -0
- package/dist/protect-record.js +130 -126
- package/dist/protect-record.js.map +1 -1
- package/dist/protect-rtp.d.ts +29 -0
- package/dist/protect-rtp.js +133 -16
- package/dist/protect-rtp.js.map +1 -1
- package/dist/protect-securitysystem.d.ts +18 -0
- package/dist/protect-securitysystem.js +105 -109
- package/dist/protect-securitysystem.js.map +1 -1
- package/dist/protect-sensor.d.ts +28 -0
- package/dist/protect-sensor.js +79 -97
- package/dist/protect-sensor.js.map +1 -1
- package/dist/protect-stream.d.ts +41 -0
- package/dist/protect-stream.js +298 -156
- package/dist/protect-stream.js.map +1 -1
- package/dist/protect-timeshift.d.ts +30 -0
- package/dist/protect-timeshift.js +65 -48
- package/dist/protect-timeshift.js.map +1 -1
- package/dist/protect-types.d.ts +50 -0
- package/dist/protect-types.js +22 -0
- package/dist/protect-types.js.map +1 -0
- package/dist/protect-viewer.d.ts +17 -0
- package/dist/protect-viewer.js +41 -47
- package/dist/protect-viewer.js.map +1 -1
- package/dist/settings.d.ts +22 -0
- package/dist/settings.js +30 -35
- package/dist/settings.js.map +1 -1
- package/homebridge-ui/public/index.html +715 -0
- package/homebridge-ui/server.js +156 -0
- package/package.json +14 -15
- package/dist/protect-accessory.js +0 -184
- package/dist/protect-accessory.js.map +0 -1
package/dist/protect-camera.js
CHANGED
|
@@ -1,66 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const settings_1 = require("./settings");
|
|
10
|
-
const protect_stream_1 = require("./protect-stream");
|
|
11
|
-
class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
12
|
-
// Configure a camera accessory for HomeKit.
|
|
13
|
-
async configureDevice() {
|
|
14
|
-
var _a, _b, _c;
|
|
1
|
+
import { PLATFORM_NAME, PLUGIN_NAME, PROTECT_HOMEKIT_IDR_INTERVAL, PROTECT_SNAPSHOT_CACHE_REFRESH_INTERVAL } from "./settings.js";
|
|
2
|
+
import { ProtectDevice } from "./protect-device.js";
|
|
3
|
+
import { ProtectReservedNames } from "./protect-types.js";
|
|
4
|
+
import { ProtectStreamingDelegate } from "./protect-stream.js";
|
|
5
|
+
export class ProtectCamera extends ProtectDevice {
|
|
6
|
+
// Create an instance.
|
|
7
|
+
constructor(nvr, device, accessory) {
|
|
8
|
+
super(nvr, accessory);
|
|
15
9
|
this.isDoorbellConfigured = false;
|
|
16
|
-
this.
|
|
10
|
+
this.hasHksv = false;
|
|
11
|
+
this.hasHwAccel = false;
|
|
12
|
+
this.isDeleted = false;
|
|
17
13
|
this.isRinging = false;
|
|
18
14
|
this.isVideoConfigured = false;
|
|
15
|
+
this.packageCamera = null;
|
|
16
|
+
this.rtspEntries = [];
|
|
19
17
|
this.rtspQuality = {};
|
|
20
|
-
this.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
this.ufp = device;
|
|
19
|
+
this.configureHints();
|
|
20
|
+
void this.configureDevice();
|
|
21
|
+
}
|
|
22
|
+
// Configure device-specific settings for this device.
|
|
23
|
+
configureHints() {
|
|
24
|
+
// Configure our parent's hints.
|
|
25
|
+
super.configureHints();
|
|
26
|
+
// Configure our device-class specific hints.
|
|
27
|
+
this.hints.hardwareTranscoding = this.nvr.optionEnabled(this.ufp, "Video.Transcode.Hardware", false);
|
|
28
|
+
this.hints.ledStatus = this.ufp.featureFlags.hasLedStatus && this.nvr.optionEnabled(this.ufp, "Device.StatusLed", false);
|
|
29
|
+
this.hints.logDoorbell = this.nvr.optionEnabled(this.ufp, "Log.Doorbell", false);
|
|
30
|
+
this.hints.logHksv = this.nvr.optionEnabled(this.ufp, "Log.HKSV");
|
|
31
|
+
this.hints.probesize = 16384;
|
|
32
|
+
this.hints.timeshift = this.nvr.optionEnabled(this.ufp, "Video.HKSV.TimeshiftBuffer");
|
|
33
|
+
this.hints.transcode = this.nvr.optionEnabled(this.ufp, "Video.Transcode", false);
|
|
34
|
+
this.hints.transcodeHighLatency = this.nvr.optionEnabled(this.ufp, "Video.Transcode.HighLatency");
|
|
35
|
+
this.hints.twoWayAudio = this.ufp.hasSpeaker && this.nvr.optionEnabled(this.ufp, "Audio") && this.nvr.optionEnabled(this.ufp, "Audio.TwoWay");
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Configure a camera accessory for HomeKit.
|
|
39
|
+
async configureDevice() {
|
|
29
40
|
// Default to disabling the dynamic bitrate setting.
|
|
30
41
|
let dynamicBitrate = false;
|
|
31
42
|
// Save the dynamic bitrate switch state before we wipeout the context.
|
|
32
|
-
if (this.accessory.context
|
|
43
|
+
if ("dynamicBitrate" in this.accessory.context) {
|
|
33
44
|
dynamicBitrate = this.accessory.context.dynamicBitrate;
|
|
34
45
|
}
|
|
35
46
|
// Default to enabling HKSV recording.
|
|
36
47
|
let hksvRecording = true;
|
|
37
48
|
// Save the HKSV recording switch state before we wipeout the context.
|
|
38
|
-
if (this.accessory.context
|
|
49
|
+
if ("hksvRecording" in this.accessory.context) {
|
|
39
50
|
hksvRecording = this.accessory.context.hksvRecording;
|
|
40
51
|
}
|
|
41
52
|
// Clean out the context object in case it's been polluted somehow.
|
|
42
53
|
this.accessory.context = {};
|
|
43
|
-
this.accessory.context.device = device;
|
|
44
|
-
this.accessory.context.nvr = (_a = this.nvr.nvrApi.bootstrap) === null || _a === void 0 ? void 0 : _a.nvr.mac;
|
|
45
|
-
this.accessory.context.detectMotion = detectMotion;
|
|
46
54
|
this.accessory.context.dynamicBitrate = dynamicBitrate;
|
|
47
55
|
this.accessory.context.hksvRecording = hksvRecording;
|
|
56
|
+
this.accessory.context.mac = this.ufp.mac;
|
|
57
|
+
this.accessory.context.nvr = this.nvr.ufp.mac;
|
|
48
58
|
// Inform the user if we have enabled the dynamic bitrate setting.
|
|
49
|
-
if (
|
|
50
|
-
this.log.info("
|
|
59
|
+
if (this.nvr.optionEnabled(this.ufp, "Video.DynamicBitrate", false)) {
|
|
60
|
+
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.");
|
|
51
61
|
}
|
|
52
62
|
// If the camera supports it, check to see if we have smart motion events enabled.
|
|
53
|
-
if (
|
|
63
|
+
if (this.ufp.featureFlags.hasSmartDetect && this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect", false)) {
|
|
54
64
|
// We deal with smart motion detection options here and save them on the ProtectCamera instance because
|
|
55
65
|
// we're trying to optimize and reduce the number of feature option lookups we do in realtime, when possible.
|
|
56
66
|
// Reading a stream of constant events and having to perform a string comparison through a list of options multiple
|
|
57
67
|
// times a second isn't an ideal use of CPU cycles, even if you have plenty of them to spare. Instead, we perform
|
|
58
68
|
// that lookup once, here, and set the appropriate option booleans for faster lookup and use later in event
|
|
59
69
|
// detection.
|
|
60
|
-
// Check for the smart motion detection object types that UniFi Protect supports.
|
|
61
|
-
this.smartDetectTypes = device.featureFlags.smartDetectTypes.filter(x => { var _a; return (_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(device, "Motion.SmartDetect." + x); });
|
|
62
70
|
// Inform the user of what smart detection object types we're configured for.
|
|
63
|
-
this.log.info("
|
|
71
|
+
this.log.info("Smart motion detection enabled%s.", this.ufp.featureFlags.smartDetectTypes.length ? ": " + this.ufp.featureFlags.smartDetectTypes.join(", ") : "");
|
|
64
72
|
}
|
|
65
73
|
// Configure accessory information.
|
|
66
74
|
this.configureInfo();
|
|
@@ -72,13 +80,15 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
72
80
|
this.configureMotionTrigger();
|
|
73
81
|
// Configure smart motion contact sensors.
|
|
74
82
|
this.configureMotionSmartSensor();
|
|
75
|
-
// Configure two-way audio support.
|
|
76
|
-
this.configureTwoWayAudio();
|
|
77
83
|
// Configure HomeKit Secure Video suport.
|
|
78
84
|
this.configureHksv();
|
|
79
85
|
this.configureHksvRecordingSwitch();
|
|
80
86
|
// Configure our video stream.
|
|
81
87
|
await this.configureVideoStream();
|
|
88
|
+
// Configure our snapshot updates.
|
|
89
|
+
void this.configureSnapshotUpdates();
|
|
90
|
+
// Configure our package camera.
|
|
91
|
+
this.configurePackageCamera();
|
|
82
92
|
// Configure our camera details.
|
|
83
93
|
this.configureCameraDetails();
|
|
84
94
|
// Configure our bitrate switch.
|
|
@@ -87,41 +97,88 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
87
97
|
this.configureNvrRecordingSwitch();
|
|
88
98
|
// Configure the doorbell trigger.
|
|
89
99
|
this.configureDoorbellTrigger();
|
|
100
|
+
// Listen for events.
|
|
101
|
+
this.nvr.events.on("updateEvent." + this.ufp.id, this.listeners["updateEvent." + this.ufp.id] = this.eventHandler.bind(this));
|
|
102
|
+
if (this.ufp.featureFlags.hasSmartDetect && this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect", false)) {
|
|
103
|
+
this.nvr.events.on("addEvent." + this.ufp.id, this.listeners["addEvent." + this.ufp.id] = this.smartMotionEventHandler.bind(this));
|
|
104
|
+
}
|
|
90
105
|
return true;
|
|
91
106
|
}
|
|
107
|
+
// Cleanup after ourselves if we're being deleted.
|
|
108
|
+
cleanup() {
|
|
109
|
+
super.cleanup();
|
|
110
|
+
this.isDeleted = true;
|
|
111
|
+
}
|
|
112
|
+
// Handle camera-related events.
|
|
113
|
+
eventHandler(packet) {
|
|
114
|
+
const payload = packet.payload;
|
|
115
|
+
// Update the package camera, if we have one.
|
|
116
|
+
if (this.packageCamera) {
|
|
117
|
+
this.packageCamera.ufp = Object.assign({}, this.ufp, { name: this.ufp.name + " Package Camera" });
|
|
118
|
+
}
|
|
119
|
+
// Process any RTSP stream updates.
|
|
120
|
+
if (payload.channels) {
|
|
121
|
+
void this.configureVideoStream();
|
|
122
|
+
}
|
|
123
|
+
// Process motion events.
|
|
124
|
+
if (payload.isMotionDetected && payload.lastMotion) {
|
|
125
|
+
// We only want to process the motion event if we have the right payload, and either HKSV recording is enabled, or
|
|
126
|
+
// HKSV recording is disabled and we have smart motion events disabled since We handle those elsewhere.
|
|
127
|
+
if (this.stream.hksv?.isRecording || (!this.stream.hksv?.isRecording && !this.ufp.featureFlags.smartDetectTypes.length)) {
|
|
128
|
+
this.nvr.events.motionEventHandler(this, payload.lastMotion);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Process ring events.
|
|
132
|
+
if (payload.lastRing) {
|
|
133
|
+
this.nvr.events.doorbellEventHandler(this, payload.lastRing);
|
|
134
|
+
}
|
|
135
|
+
// Process camera details updates:
|
|
136
|
+
// - camera status light.
|
|
137
|
+
// - camera recording settings.
|
|
138
|
+
if ((payload.ledSettings && ("isEnabled" in payload.ledSettings)) || (payload.recordingSettings && ("mode" in payload.recordingSettings))) {
|
|
139
|
+
this.updateDevice();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Handle smart motion detection events.
|
|
143
|
+
smartMotionEventHandler(packet) {
|
|
144
|
+
const payload = packet.payload;
|
|
145
|
+
// We're only interested in smart motion detection events.
|
|
146
|
+
if ((packet.header.modelKey !== "event") || (payload.type !== "smartDetectZone") || !payload.smartDetectTypes.length) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Process the motion event.
|
|
150
|
+
this.nvr.events.motionEventHandler(this, payload.start, payload.smartDetectTypes);
|
|
151
|
+
}
|
|
92
152
|
// Configure discrete smart motion contact sensors for HomeKit.
|
|
93
153
|
configureMotionSmartSensor() {
|
|
94
|
-
|
|
95
|
-
|
|
154
|
+
// If we don't have smart motion detection, we're done.
|
|
155
|
+
if (!this.ufp.featureFlags.hasSmartDetect) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
96
158
|
// Check for object-centric contact sensors that are no longer enabled and remove them.
|
|
97
|
-
for (const objectService of this.accessory.services.filter(x =>
|
|
159
|
+
for (const objectService of this.accessory.services.filter(x => x.subtype?.startsWith(ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "."))) {
|
|
98
160
|
// If we have motion sensors as well as object contact sensors enabled, and we have this object type enabled on this camera, we're good here.
|
|
99
|
-
if (
|
|
100
|
-
((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(device, "Motion.SmartDetect.ObjectSensors", false))) {
|
|
161
|
+
if (this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect.ObjectSensors", false)) {
|
|
101
162
|
continue;
|
|
102
163
|
}
|
|
103
164
|
// We don't have this contact sensor enabled, remove it.
|
|
104
165
|
this.accessory.removeService(objectService);
|
|
105
|
-
this.log.info("
|
|
106
|
-
}
|
|
107
|
-
// Have we disabled motion sensors? If so, we're done.
|
|
108
|
-
if (!((_e = this.nvr) === null || _e === void 0 ? void 0 : _e.optionEnabled(device, "Motion.Sensor"))) {
|
|
109
|
-
return false;
|
|
166
|
+
this.log.info("Disabling smart motion contact sensor: %s.", objectService.subtype?.slice(objectService.subtype?.indexOf(".") + 1));
|
|
110
167
|
}
|
|
111
168
|
// Have we enabled discrete contact sensors for specific object types? If not, we're done here.
|
|
112
|
-
if (!
|
|
169
|
+
if (!this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect.ObjectSensors", false)) {
|
|
113
170
|
return false;
|
|
114
171
|
}
|
|
115
172
|
// Add individual contact sensors for each object detection type, if needed.
|
|
116
|
-
for (const smartDetectType of
|
|
173
|
+
for (const smartDetectType of this.ufp.featureFlags.smartDetectTypes) {
|
|
117
174
|
// See if we already have this contact sensor configured.
|
|
118
|
-
let contactService = this.accessory.getServiceById(this.hap.Service.ContactSensor,
|
|
175
|
+
let contactService = this.accessory.getServiceById(this.hap.Service.ContactSensor, ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + smartDetectType);
|
|
119
176
|
// If not, let's add it.
|
|
120
177
|
if (!contactService) {
|
|
121
|
-
contactService = new this.hap.Service.ContactSensor(this.accessory.displayName + " " + smartDetectType.charAt(0).toUpperCase() + smartDetectType.slice(1),
|
|
178
|
+
contactService = new this.hap.Service.ContactSensor(this.accessory.displayName + " " + smartDetectType.charAt(0).toUpperCase() + smartDetectType.slice(1), ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + smartDetectType);
|
|
122
179
|
// Something went wrong, we're done here.
|
|
123
180
|
if (!contactService) {
|
|
124
|
-
this.log.error("
|
|
181
|
+
this.log.error("Unable to add smart motion contact sensor for %s detection.", smartDetectType);
|
|
125
182
|
return false;
|
|
126
183
|
}
|
|
127
184
|
// Finally, add it to the camera.
|
|
@@ -130,17 +187,15 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
130
187
|
// Initialize the sensor.
|
|
131
188
|
contactService.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
|
|
132
189
|
}
|
|
133
|
-
this.log.info("
|
|
190
|
+
this.log.info("Smart motion contact sensor%s enabled: %s.", this.ufp.featureFlags.smartDetectTypes.length > 1 ? "s" : "", this.ufp.featureFlags.smartDetectTypes.join(", "));
|
|
134
191
|
return true;
|
|
135
192
|
}
|
|
136
193
|
// Configure a switch to manually trigger a motion sensor event for HomeKit.
|
|
137
194
|
configureMotionTrigger() {
|
|
138
|
-
var _a, _b, _c;
|
|
139
195
|
// Find the switch service, if it exists.
|
|
140
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch,
|
|
196
|
+
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_MOTION_TRIGGER);
|
|
141
197
|
// Motion triggers are disabled by default and primarily exist for automation purposes.
|
|
142
|
-
if (!
|
|
143
|
-
!((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Motion.Trigger", false))) {
|
|
198
|
+
if (!this.nvr.optionEnabled(this.ufp, "Motion.Trigger", false)) {
|
|
144
199
|
if (triggerService) {
|
|
145
200
|
this.accessory.removeService(triggerService);
|
|
146
201
|
}
|
|
@@ -148,63 +203,63 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
148
203
|
}
|
|
149
204
|
// Add the switch to the camera, if needed.
|
|
150
205
|
if (!triggerService) {
|
|
151
|
-
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Motion Trigger",
|
|
206
|
+
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Motion Trigger", ProtectReservedNames.SWITCH_MOTION_TRIGGER);
|
|
152
207
|
if (!triggerService) {
|
|
153
|
-
this.log.error("
|
|
208
|
+
this.log.error("Unable to add motion sensor trigger.");
|
|
154
209
|
return false;
|
|
155
210
|
}
|
|
156
211
|
this.accessory.addService(triggerService);
|
|
157
212
|
}
|
|
158
213
|
const motionService = this.accessory.getService(this.hap.Service.MotionSensor);
|
|
159
|
-
const switchService = this.accessory.getServiceById(this.hap.Service.Switch,
|
|
214
|
+
const switchService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_MOTION_SENSOR);
|
|
160
215
|
// Activate or deactivate motion detection.
|
|
161
|
-
|
|
162
|
-
.getCharacteristic(this.hap.Characteristic.On)
|
|
163
|
-
|
|
164
|
-
|
|
216
|
+
triggerService
|
|
217
|
+
.getCharacteristic(this.hap.Characteristic.On)
|
|
218
|
+
?.onGet(() => {
|
|
219
|
+
return motionService?.getCharacteristic(this.hap.Characteristic.MotionDetected).value === true;
|
|
220
|
+
})
|
|
221
|
+
.onSet((value) => {
|
|
165
222
|
if (value) {
|
|
166
223
|
// Check to see if motion events are disabled.
|
|
167
224
|
if (switchService && !switchService.getCharacteristic(this.hap.Characteristic.On).value) {
|
|
168
225
|
setTimeout(() => {
|
|
169
|
-
triggerService
|
|
226
|
+
triggerService?.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
170
227
|
}, 50);
|
|
171
228
|
}
|
|
172
229
|
else {
|
|
173
230
|
// Trigger the motion event.
|
|
174
|
-
this.nvr.events.motionEventHandler(this
|
|
175
|
-
this.log.info("
|
|
231
|
+
this.nvr.events.motionEventHandler(this, Date.now());
|
|
232
|
+
this.log.info("Motion event triggered.");
|
|
176
233
|
}
|
|
177
234
|
}
|
|
178
235
|
else {
|
|
179
236
|
// If the motion sensor is still on, we should be as well.
|
|
180
|
-
if (motionService
|
|
237
|
+
if (motionService?.getCharacteristic(this.hap.Characteristic.MotionDetected).value) {
|
|
181
238
|
setTimeout(() => {
|
|
182
|
-
triggerService
|
|
239
|
+
triggerService?.updateCharacteristic(this.hap.Characteristic.On, true);
|
|
183
240
|
}, 50);
|
|
184
241
|
}
|
|
185
242
|
}
|
|
186
243
|
});
|
|
187
244
|
// Initialize the switch.
|
|
188
245
|
triggerService.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
189
|
-
this.log.info("
|
|
246
|
+
this.log.info("Enabling motion sensor automation trigger.");
|
|
190
247
|
return true;
|
|
191
248
|
}
|
|
192
249
|
// Configure a switch to manually trigger a doorbell ring event for HomeKit.
|
|
193
250
|
configureDoorbellTrigger() {
|
|
194
|
-
var _a, _b;
|
|
195
|
-
const camera = this.accessory.context.device;
|
|
196
251
|
// Find the switch service, if it exists.
|
|
197
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch,
|
|
252
|
+
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
198
253
|
// See if we have a doorbell service configured.
|
|
199
254
|
let doorbellService = this.accessory.getService(this.hap.Service.Doorbell);
|
|
200
255
|
// Doorbell switches are disabled by default and primarily exist for automation purposes.
|
|
201
|
-
if (!
|
|
256
|
+
if (!this.nvr.optionEnabled(this.ufp, "Doorbell.Trigger", false)) {
|
|
202
257
|
if (triggerService) {
|
|
203
258
|
this.accessory.removeService(triggerService);
|
|
204
259
|
}
|
|
205
260
|
// Since we aren't enabling the doorbell trigger on this camera, remove the doorbell service if the camera
|
|
206
261
|
// isn't actually doorbell-capable hardware.
|
|
207
|
-
if (!
|
|
262
|
+
if (!this.ufp.featureFlags.hasChime && doorbellService) {
|
|
208
263
|
this.accessory.removeService(doorbellService);
|
|
209
264
|
}
|
|
210
265
|
return false;
|
|
@@ -218,41 +273,43 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
218
273
|
}
|
|
219
274
|
// Now find the doorbell service.
|
|
220
275
|
if (!(doorbellService = this.accessory.getService(this.hap.Service.Doorbell))) {
|
|
221
|
-
this.log.error("
|
|
276
|
+
this.log.error("Unable to find the doorbell service.");
|
|
222
277
|
return false;
|
|
223
278
|
}
|
|
224
279
|
}
|
|
225
280
|
// Add the switch to the camera, if needed.
|
|
226
281
|
if (!triggerService) {
|
|
227
|
-
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Doorbell Trigger",
|
|
282
|
+
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Doorbell Trigger", ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
228
283
|
if (!triggerService) {
|
|
229
|
-
this.log.error("
|
|
284
|
+
this.log.error("Unable to add the doorbell trigger.");
|
|
230
285
|
return false;
|
|
231
286
|
}
|
|
232
287
|
this.accessory.addService(triggerService);
|
|
233
288
|
}
|
|
234
289
|
// Trigger the doorbell.
|
|
235
|
-
|
|
236
|
-
.getCharacteristic(this.hap.Characteristic.On)
|
|
290
|
+
triggerService
|
|
291
|
+
.getCharacteristic(this.hap.Characteristic.On)
|
|
292
|
+
?.onGet(() => {
|
|
237
293
|
return this.isRinging;
|
|
238
|
-
})
|
|
294
|
+
})
|
|
295
|
+
.onSet((value) => {
|
|
239
296
|
if (value) {
|
|
240
297
|
// Trigger the motion event.
|
|
241
|
-
this.nvr.events.doorbellEventHandler(this
|
|
242
|
-
this.log.info("
|
|
298
|
+
this.nvr.events.doorbellEventHandler(this, Date.now());
|
|
299
|
+
this.log.info("Doorbell ring event triggered.");
|
|
243
300
|
}
|
|
244
301
|
else {
|
|
245
302
|
// If the doorbell ring event is still going, we should be as well.
|
|
246
303
|
if (this.isRinging) {
|
|
247
304
|
setTimeout(() => {
|
|
248
|
-
triggerService
|
|
305
|
+
triggerService?.updateCharacteristic(this.hap.Characteristic.On, true);
|
|
249
306
|
}, 50);
|
|
250
307
|
}
|
|
251
308
|
}
|
|
252
309
|
});
|
|
253
310
|
// Initialize the switch.
|
|
254
311
|
triggerService.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
255
|
-
this.log.info("
|
|
312
|
+
this.log.info("Enabling doorbell automation trigger.");
|
|
256
313
|
return true;
|
|
257
314
|
}
|
|
258
315
|
// Configure the doorbell service for HomeKit.
|
|
@@ -268,7 +325,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
268
325
|
if (!doorbellService) {
|
|
269
326
|
doorbellService = new this.hap.Service.Doorbell(this.accessory.displayName);
|
|
270
327
|
if (!doorbellService) {
|
|
271
|
-
this.log.error("
|
|
328
|
+
this.log.error("Unable to add doorbell.");
|
|
272
329
|
return false;
|
|
273
330
|
}
|
|
274
331
|
this.accessory.addService(doorbellService);
|
|
@@ -277,78 +334,68 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
277
334
|
this.isDoorbellConfigured = true;
|
|
278
335
|
return true;
|
|
279
336
|
}
|
|
280
|
-
// Configure two-way audio support for HomeKit.
|
|
281
|
-
configureTwoWayAudio() {
|
|
282
|
-
var _a, _b;
|
|
283
|
-
// Identify twoway-capable devices.
|
|
284
|
-
if (!this.accessory.context.device.hasSpeaker) {
|
|
285
|
-
return this.twoWayAudio = false;
|
|
286
|
-
}
|
|
287
|
-
// Enabled by default unless disabled by the user.
|
|
288
|
-
return this.twoWayAudio = ((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Audio")) &&
|
|
289
|
-
((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Audio.TwoWay"));
|
|
290
|
-
}
|
|
291
337
|
// Configure additional camera-specific characteristics for HomeKit.
|
|
292
338
|
configureCameraDetails() {
|
|
293
|
-
var _a;
|
|
294
339
|
// Find the service, if it exists.
|
|
295
|
-
const
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
340
|
+
const statusLedService = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
341
|
+
// Have we enabled the camera status LED?
|
|
342
|
+
if (this.hints.ledStatus && statusLedService) {
|
|
343
|
+
// Turn the status light on or off.
|
|
344
|
+
statusLedService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator)
|
|
345
|
+
?.onGet(() => {
|
|
346
|
+
return this.ufp.ledSettings?.isEnabled === true;
|
|
347
|
+
})
|
|
348
|
+
.onSet(async (value) => {
|
|
304
349
|
const ledState = value === true;
|
|
305
350
|
// Update the status light in Protect.
|
|
306
|
-
const newDevice = await this.nvr.
|
|
351
|
+
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { ledSettings: { isEnabled: ledState } });
|
|
307
352
|
if (!newDevice) {
|
|
308
|
-
this.log.error("
|
|
353
|
+
this.log.error("Unable to turn the status light %s. Please ensure this username has the Administrator role in UniFi Protect.", ledState ? "on" : "off");
|
|
309
354
|
return;
|
|
310
355
|
}
|
|
311
|
-
//
|
|
312
|
-
this.
|
|
356
|
+
// Update our internal view of the device configuration.
|
|
357
|
+
this.ufp = newDevice;
|
|
313
358
|
});
|
|
314
359
|
// Initialize the status light state.
|
|
315
|
-
|
|
360
|
+
statusLedService.updateCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator, this.ufp.ledSettings.isEnabled === true);
|
|
361
|
+
}
|
|
362
|
+
else if (statusLedService) {
|
|
363
|
+
// Remove the camera status light if we have it.
|
|
364
|
+
const statusLight = statusLedService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator);
|
|
365
|
+
if (statusLight) {
|
|
366
|
+
statusLedService.removeCharacteristic(statusLight);
|
|
367
|
+
}
|
|
316
368
|
}
|
|
317
369
|
return true;
|
|
318
370
|
}
|
|
319
371
|
// Configure a camera accessory for HomeKit.
|
|
320
372
|
async configureVideoStream() {
|
|
321
|
-
var _a, _b;
|
|
322
|
-
const bootstrap = this.nvr.nvrApi.bootstrap;
|
|
323
|
-
let device = this.accessory.context.device;
|
|
324
|
-
const nvr = this.nvr;
|
|
325
|
-
const nvrApi = this.nvr.nvrApi;
|
|
326
373
|
const rtspEntries = [];
|
|
327
374
|
// No channels exist on this camera or we don't have access to the bootstrap configuration.
|
|
328
|
-
if (!
|
|
375
|
+
if (!this.ufp.channels) {
|
|
329
376
|
return false;
|
|
330
377
|
}
|
|
331
378
|
// Enable RTSP on the camera if needed and get the list of RTSP streams we have ultimately configured.
|
|
332
|
-
|
|
379
|
+
this.ufp = await this.nvr.ufpApi.enableRtsp(this.ufp) ?? this.ufp;
|
|
333
380
|
// Figure out which camera channels are RTSP-enabled, and user-enabled.
|
|
334
|
-
const cameraChannels =
|
|
381
|
+
const cameraChannels = this.ufp.channels.filter(x => x.isRtspEnabled && this.nvr.optionEnabled(this.ufp, "Video.Stream." + x.name));
|
|
335
382
|
// Make sure we've got a HomeKit compatible IDR frame interval. If not, let's take care of that.
|
|
336
|
-
let idrChannels = cameraChannels.filter(x => x.idrInterval !==
|
|
383
|
+
let idrChannels = cameraChannels.filter(x => x.idrInterval !== PROTECT_HOMEKIT_IDR_INTERVAL);
|
|
337
384
|
if (idrChannels.length) {
|
|
338
385
|
// Edit the channel map.
|
|
339
386
|
idrChannels = idrChannels.map(x => {
|
|
340
|
-
x.idrInterval =
|
|
387
|
+
x.idrInterval = PROTECT_HOMEKIT_IDR_INTERVAL;
|
|
341
388
|
return x;
|
|
342
389
|
});
|
|
343
|
-
|
|
344
|
-
this.accessory.context.device = device;
|
|
390
|
+
this.ufp = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: idrChannels }) ?? this.ufp;
|
|
345
391
|
}
|
|
346
392
|
// Set the camera and shapshot URLs.
|
|
347
|
-
const cameraUrl = "rtsps://" + nvr.
|
|
348
|
-
this.snapshotUrl =
|
|
393
|
+
const cameraUrl = "rtsps://" + this.nvr.nvrOptions.address + ":" + this.nvr.ufp.ports.rtsps.toString() + "/";
|
|
394
|
+
this.snapshotUrl = this.nvr.ufpApi.getApiEndpoint(this.ufp.modelKey) + "/" + this.ufp.id + "/snapshot";
|
|
349
395
|
// No RTSP streams are available that meet our criteria - we're done.
|
|
350
396
|
if (!cameraChannels.length) {
|
|
351
|
-
this.log.info("
|
|
397
|
+
this.log.info("No RTSP stream profiles have been configured for this camera. " +
|
|
398
|
+
"Enable at least one RTSP stream profile in the UniFi Protect webUI to resolve this issue or " +
|
|
352
399
|
"assign the Administrator role to the user configured for this plugin to allow it to automatically configure itself.");
|
|
353
400
|
return false;
|
|
354
401
|
}
|
|
@@ -368,8 +415,13 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
368
415
|
// 3840x2160@30 (4k).
|
|
369
416
|
// 1920x1080@30 (1080p).
|
|
370
417
|
// 1280x720@30 (720p).
|
|
418
|
+
// 320x240@30 (Apple Watch).
|
|
371
419
|
// 320x240@15 (Apple Watch).
|
|
372
|
-
|
|
420
|
+
// 320x180@30 (Apple Watch).
|
|
421
|
+
for (const entry of [
|
|
422
|
+
[3840, 2160, 30], [1920, 1080, 30], [1280, 960, 30], [1280, 720, 30], [1024, 768, 30], [640, 480, 30],
|
|
423
|
+
[640, 360, 30], [480, 360, 30], [480, 270, 30], [320, 240, 30], [320, 240, 15], [320, 180, 30]
|
|
424
|
+
]) {
|
|
373
425
|
// We already have this resolution in our list.
|
|
374
426
|
if (rtspEntries.some(x => (x.resolution[0] === entry[0]) && (x.resolution[1] === entry[1]) && (x.resolution[2] === entry[2]))) {
|
|
375
427
|
continue;
|
|
@@ -391,70 +443,104 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
391
443
|
return true;
|
|
392
444
|
}
|
|
393
445
|
// Inform users about our RTSP entry mapping, if we're debugging.
|
|
394
|
-
if (nvr.optionEnabled(
|
|
446
|
+
if (this.nvr.optionEnabled(this.ufp, "Debug.Video.Startup", false)) {
|
|
395
447
|
for (const entry of this.rtspEntries) {
|
|
396
|
-
this.log.info("
|
|
448
|
+
this.log.info("Mapping resolution: %s.", this.getResolution(entry.resolution) + " => " + entry.name);
|
|
397
449
|
}
|
|
398
450
|
}
|
|
399
451
|
// Check for explicit RTSP profile preferences.
|
|
400
452
|
for (const rtspProfile of ["LOW", "MEDIUM", "HIGH"]) {
|
|
401
453
|
// Check to see if the user has requested a specific streaming profile for this camera.
|
|
402
|
-
if (nvr.optionEnabled(
|
|
454
|
+
if (this.nvr.optionEnabled(this.ufp, "Video.Stream.Only." + rtspProfile, false)) {
|
|
403
455
|
this.rtspQuality.StreamingDefault = rtspProfile;
|
|
404
456
|
}
|
|
405
457
|
// Check to see if the user has requested a specific recording profile for this camera.
|
|
406
|
-
if (nvr.optionEnabled(
|
|
458
|
+
if (this.nvr.optionEnabled(this.ufp, "Video.HKSV.Record.Only." + rtspProfile, false)) {
|
|
407
459
|
this.rtspQuality.RecordingDefault = rtspProfile;
|
|
408
460
|
}
|
|
409
461
|
}
|
|
410
462
|
// Inform the user if we've set a streaming default.
|
|
411
463
|
if (this.rtspQuality.StreamingDefault) {
|
|
412
|
-
this.log.info("
|
|
464
|
+
this.log.info("Video streaming configured to use only: %s.", this.rtspQuality.StreamingDefault.charAt(0) + this.rtspQuality.StreamingDefault.slice(1).toLowerCase());
|
|
413
465
|
}
|
|
414
466
|
// Inform the user if we've set a recording default.
|
|
415
467
|
if (this.rtspQuality.RecordingDefault) {
|
|
416
|
-
this.log.info("
|
|
468
|
+
this.log.info("HomeKit Secure Video event recording configured to use only: %s.", this.rtspQuality.RecordingDefault.charAt(0) + this.rtspQuality.RecordingDefault.slice(1).toLowerCase());
|
|
417
469
|
}
|
|
418
470
|
// Configure the video stream with our resolutions.
|
|
419
|
-
this.stream = new
|
|
471
|
+
this.stream = new ProtectStreamingDelegate(this, this.rtspEntries.map(x => x.resolution));
|
|
472
|
+
if (this.hasHwAccel) {
|
|
473
|
+
this.log.info("Hardware accelerated transcoding enabled.");
|
|
474
|
+
}
|
|
420
475
|
// Fire up the controller and inform HomeKit about it.
|
|
421
476
|
this.accessory.configureController(this.stream.controller);
|
|
422
477
|
this.isVideoConfigured = true;
|
|
423
478
|
return true;
|
|
424
479
|
}
|
|
425
|
-
// Configure
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
480
|
+
// Configure a periodic refresh of our snapshot images.
|
|
481
|
+
async configureSnapshotUpdates() {
|
|
482
|
+
for (;;) {
|
|
483
|
+
// If we've removed the device, make sure we stop refreshing.
|
|
484
|
+
if (this.isDeleted) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
// Refresh our snapshot cache.
|
|
488
|
+
// eslint-disable-next-line no-await-in-loop
|
|
489
|
+
await this.stream.getSnapshot(undefined, false);
|
|
490
|
+
// Sleep for 59 seconds.
|
|
491
|
+
// eslint-disable-next-line no-await-in-loop
|
|
492
|
+
await this.nvr.sleep(PROTECT_SNAPSHOT_CACHE_REFRESH_INTERVAL * 1000);
|
|
433
493
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
// Configure a package camera, if one exists.
|
|
497
|
+
configurePackageCamera() {
|
|
498
|
+
// First, confirm the device has a package camera.
|
|
499
|
+
if (!this.ufp.featureFlags.hasPackageCamera) {
|
|
437
500
|
return false;
|
|
438
501
|
}
|
|
439
|
-
|
|
440
|
-
|
|
502
|
+
// If we've already setup the package camera, we're done.
|
|
503
|
+
if (this.packageCamera) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
// Generate a UUID for the package camera.
|
|
507
|
+
const uuid = this.hap.uuid.generate(this.ufp.mac + ".PackageCamera");
|
|
508
|
+
// Let's find it if we've already created it.
|
|
509
|
+
let packageCameraAccessory = this.platform.accessories.find((x) => x.UUID === uuid) ?? null;
|
|
510
|
+
// We can't find the accessory. Let's create it.
|
|
511
|
+
if (!packageCameraAccessory) {
|
|
512
|
+
// We will use the NVR MAC address + ".NVRSystemInfo" to create our UUID. That should provide the guaranteed uniqueness we need.
|
|
513
|
+
packageCameraAccessory = new this.api.platformAccessory(this.accessory.displayName + " Package Camera", uuid);
|
|
514
|
+
if (!packageCameraAccessory) {
|
|
515
|
+
this.log.error("Unable to create the package camera accessory.");
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
// Register this accessory with homebridge and add it to the platform accessory array so we can track it.
|
|
519
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [packageCameraAccessory]);
|
|
520
|
+
this.platform.accessories.push(packageCameraAccessory);
|
|
521
|
+
}
|
|
522
|
+
// Now create the package camera accessory. We do want to modify the camera name to ensure things look pretty.
|
|
523
|
+
this.packageCamera = new ProtectPackageCamera(this.nvr, Object.assign({}, this.ufp, { name: this.ufp.name + " Package Camera" }), packageCameraAccessory);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
// Configure HomeKit Secure Video support.
|
|
527
|
+
configureHksv() {
|
|
528
|
+
this.hasHksv = true;
|
|
441
529
|
// If we have smart motion events enabled, let's warn the user that things will not work quite the way they expect.
|
|
442
|
-
if (
|
|
443
|
-
this.log.info("
|
|
530
|
+
if (this.ufp.featureFlags.hasSmartDetect && this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect", false)) {
|
|
531
|
+
this.log.info("WARNING: Smart motion detection and HomeKit Secure Video provide overlapping functionality. " +
|
|
444
532
|
"Only HomeKit Secure Video, when event recording is enabled in the Home app, will be used to trigger motion event notifications for this camera." +
|
|
445
|
-
(
|
|
446
|
-
" Smart motion contact sensors will continue to function using telemetry from UniFi Protect." : "")
|
|
533
|
+
(this.nvr.optionEnabled(this.ufp, "Motion.SmartDetect.ObjectSensors", false) ?
|
|
534
|
+
" Smart motion contact sensors will continue to function using telemetry from UniFi Protect." : ""));
|
|
447
535
|
}
|
|
448
536
|
return true;
|
|
449
537
|
}
|
|
450
538
|
// Configure a switch to manually enable or disable HKSV recording for a camera.
|
|
451
539
|
configureHksvRecordingSwitch() {
|
|
452
|
-
var _a, _b, _c;
|
|
453
540
|
// Find the switch service, if it exists.
|
|
454
|
-
let switchService = this.accessory.getServiceById(this.hap.Service.Switch,
|
|
541
|
+
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_HKSV_RECORDING);
|
|
455
542
|
// If we don't have HKSV or the HKSV recording switch enabled, disable it and we're done.
|
|
456
|
-
if (!
|
|
457
|
-
!((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.HKSV.Recording.Switch", false))) {
|
|
543
|
+
if (!this.nvr.optionEnabled(this.ufp, "Video.HKSV.Recording.Switch", false)) {
|
|
458
544
|
if (switchService) {
|
|
459
545
|
this.accessory.removeService(switchService);
|
|
460
546
|
}
|
|
@@ -464,35 +550,36 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
464
550
|
}
|
|
465
551
|
// Add the switch to the camera, if needed.
|
|
466
552
|
if (!switchService) {
|
|
467
|
-
switchService = new this.hap.Service.Switch(this.accessory.displayName + " HKSV Recording",
|
|
553
|
+
switchService = new this.hap.Service.Switch(this.accessory.displayName + " HKSV Recording", ProtectReservedNames.SWITCH_HKSV_RECORDING);
|
|
468
554
|
if (!switchService) {
|
|
469
|
-
this.log.error("
|
|
555
|
+
this.log.error("Unable to add the HomeKit Secure Video recording switch.");
|
|
470
556
|
return false;
|
|
471
557
|
}
|
|
472
558
|
this.accessory.addService(switchService);
|
|
473
559
|
}
|
|
474
560
|
// Activate or deactivate HKSV recording.
|
|
475
|
-
|
|
476
|
-
.getCharacteristic(this.hap.Characteristic.On)
|
|
561
|
+
switchService
|
|
562
|
+
.getCharacteristic(this.hap.Characteristic.On)
|
|
563
|
+
?.onGet(() => {
|
|
477
564
|
return this.accessory.context.hksvRecording;
|
|
478
|
-
})
|
|
565
|
+
})
|
|
566
|
+
.onSet((value) => {
|
|
479
567
|
if (this.accessory.context.hksvRecording !== value) {
|
|
480
|
-
this.log.info("
|
|
568
|
+
this.log.info("HomeKit Secure Video event recording has been %s.", value === true ? "enabled" : "disabled");
|
|
481
569
|
}
|
|
482
570
|
this.accessory.context.hksvRecording = value === true;
|
|
483
571
|
});
|
|
484
572
|
// Initialize the switch.
|
|
485
573
|
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.hksvRecording);
|
|
486
|
-
this.log.info("
|
|
574
|
+
this.log.info("Enabling HomeKit Secure Video recording switch.");
|
|
487
575
|
return true;
|
|
488
576
|
}
|
|
489
577
|
// Configure a switch to manually enable or disable dynamic bitrate capabilities for a camera.
|
|
490
578
|
configureDynamicBitrateSwitch() {
|
|
491
|
-
var _a, _b;
|
|
492
579
|
// Find the switch service, if it exists.
|
|
493
|
-
let switchService = this.accessory.getServiceById(this.hap.Service.Switch,
|
|
580
|
+
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_DYNAMIC_BITRATE);
|
|
494
581
|
// If we don't want a dynamic bitrate switch, disable it and we're done.
|
|
495
|
-
if (!
|
|
582
|
+
if (!this.nvr.optionEnabled(this.ufp, "Video.DynamicBitrate.Switch", false)) {
|
|
496
583
|
if (switchService) {
|
|
497
584
|
this.accessory.removeService(switchService);
|
|
498
585
|
}
|
|
@@ -502,62 +589,62 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
502
589
|
}
|
|
503
590
|
// Add the switch to the camera, if needed.
|
|
504
591
|
if (!switchService) {
|
|
505
|
-
switchService = new this.hap.Service.Switch(this.accessory.displayName + " Dynamic Bitrate",
|
|
592
|
+
switchService = new this.hap.Service.Switch(this.accessory.displayName + " Dynamic Bitrate", ProtectReservedNames.SWITCH_DYNAMIC_BITRATE);
|
|
506
593
|
if (!switchService) {
|
|
507
|
-
this.log.error("
|
|
594
|
+
this.log.error("Unable to add the dynamic bitrate switch.");
|
|
508
595
|
return false;
|
|
509
596
|
}
|
|
510
597
|
this.accessory.addService(switchService);
|
|
511
598
|
}
|
|
512
599
|
// Activate or deactivate dynamic bitrate for this device.
|
|
513
|
-
|
|
514
|
-
.getCharacteristic(this.hap.Characteristic.On)
|
|
600
|
+
switchService
|
|
601
|
+
.getCharacteristic(this.hap.Characteristic.On)
|
|
602
|
+
?.onGet(() => {
|
|
515
603
|
return this.accessory.context.dynamicBitrate;
|
|
516
|
-
})
|
|
604
|
+
})
|
|
605
|
+
.onSet(async (value) => {
|
|
517
606
|
if (this.accessory.context.dynamicBitrate === value) {
|
|
518
607
|
return;
|
|
519
608
|
}
|
|
520
609
|
// We're enabling dynamic bitrate for this device.
|
|
521
610
|
if (value) {
|
|
522
611
|
this.accessory.context.dynamicBitrate = true;
|
|
523
|
-
this.log.info("
|
|
612
|
+
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.");
|
|
524
613
|
return;
|
|
525
614
|
}
|
|
526
615
|
// We're disabling dynamic bitrate for this device.
|
|
527
|
-
const
|
|
528
|
-
const updatedChannels = device.channels;
|
|
616
|
+
const updatedChannels = this.ufp.channels;
|
|
529
617
|
// Update the channels JSON.
|
|
530
618
|
for (const channel of updatedChannels) {
|
|
531
619
|
channel.bitrate = channel.maxBitrate;
|
|
532
620
|
}
|
|
533
621
|
// Send the channels JSON to Protect.
|
|
534
|
-
const newDevice = await this.
|
|
622
|
+
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: updatedChannels });
|
|
535
623
|
// We failed.
|
|
536
624
|
if (!newDevice) {
|
|
537
|
-
this.log.error("
|
|
625
|
+
this.log.error("Unable to set the streaming bitrate to %s.", value);
|
|
538
626
|
}
|
|
539
627
|
else {
|
|
540
|
-
this.
|
|
628
|
+
this.ufp = newDevice;
|
|
541
629
|
}
|
|
542
630
|
this.accessory.context.dynamicBitrate = false;
|
|
543
|
-
this.log.info("
|
|
631
|
+
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller disabled.");
|
|
544
632
|
});
|
|
545
633
|
// Initialize the switch.
|
|
546
634
|
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
547
|
-
this.log.info("
|
|
635
|
+
this.log.info("Enabling the dynamic streaming bitrate adjustment switch.");
|
|
548
636
|
return true;
|
|
549
637
|
}
|
|
550
638
|
// Configure a series of switches to manually enable or disable recording on the UniFi Protect controller for a camera.
|
|
551
639
|
configureNvrRecordingSwitch() {
|
|
552
|
-
var _a, _b;
|
|
553
640
|
const switchesEnabled = [];
|
|
554
641
|
// The Protect controller supports three modes for recording on a camera: always, detections, and never. We create switches for each of the modes.
|
|
555
|
-
for (const ufpRecordingSwitchType of [
|
|
642
|
+
for (const ufpRecordingSwitchType of [ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
556
643
|
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
557
644
|
// Find the switch service, if it exists.
|
|
558
645
|
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, ufpRecordingSwitchType);
|
|
559
646
|
// If we don't have the feature option enabled, disable the switch and we're done.
|
|
560
|
-
if (!
|
|
647
|
+
if (!this.nvr.optionEnabled(this.ufp, "Nvr.Recording.Switch", false)) {
|
|
561
648
|
if (switchService) {
|
|
562
649
|
this.accessory.removeService(switchService);
|
|
563
650
|
}
|
|
@@ -567,17 +654,18 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
567
654
|
if (!switchService) {
|
|
568
655
|
switchService = new this.hap.Service.Switch(this.accessory.displayName + " UFP Recording " + ufpRecordingSetting.charAt(0).toUpperCase() + ufpRecordingSetting.slice(1), ufpRecordingSwitchType);
|
|
569
656
|
if (!switchService) {
|
|
570
|
-
this.log.error("
|
|
657
|
+
this.log.error("Unable to add the UniFi Protect recording switches.");
|
|
571
658
|
continue;
|
|
572
659
|
}
|
|
573
660
|
this.accessory.addService(switchService);
|
|
574
661
|
}
|
|
575
662
|
// Activate or deactivate the appropriate recording mode on the Protect controller.
|
|
576
|
-
|
|
577
|
-
.getCharacteristic(this.hap.Characteristic.On)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
663
|
+
switchService
|
|
664
|
+
.getCharacteristic(this.hap.Characteristic.On)
|
|
665
|
+
?.onGet(() => {
|
|
666
|
+
return this.ufp.recordingSettings.mode === ufpRecordingSetting;
|
|
667
|
+
})
|
|
668
|
+
.onSet(async (value) => {
|
|
581
669
|
// We only want to do something if we're being activated. Turning off the switch would really be an undefined state given that
|
|
582
670
|
// there are three different settings one can choose from. Instead, we do nothing and leave it to the user to choose what state
|
|
583
671
|
// they really want to set.
|
|
@@ -588,124 +676,107 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
588
676
|
return;
|
|
589
677
|
}
|
|
590
678
|
// Set our recording mode.
|
|
591
|
-
|
|
592
|
-
device.recordingSettings.mode = ufpRecordingSetting;
|
|
679
|
+
this.ufp.recordingSettings.mode = ufpRecordingSetting;
|
|
593
680
|
// Tell Protect about it.
|
|
594
|
-
const newDevice = await this.nvr.
|
|
681
|
+
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { recordingSettings: this.ufp.recordingSettings });
|
|
595
682
|
if (!newDevice) {
|
|
596
|
-
this.log.error("
|
|
683
|
+
this.log.error("Unable to set the UniFi Protect recording mode to %s.", ufpRecordingSetting);
|
|
597
684
|
return false;
|
|
598
685
|
}
|
|
599
686
|
// Save our updated device context.
|
|
600
|
-
this.
|
|
687
|
+
this.ufp = newDevice;
|
|
601
688
|
// Update all the other recording switches.
|
|
602
|
-
for (const otherUfpSwitch of [
|
|
689
|
+
for (const otherUfpSwitch of [ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
603
690
|
// Don't update ourselves a second time.
|
|
604
691
|
if (ufpRecordingSwitchType === otherUfpSwitch) {
|
|
605
692
|
continue;
|
|
606
693
|
}
|
|
607
694
|
// Update the other recording switches.
|
|
608
|
-
|
|
695
|
+
this.accessory.getServiceById(this.hap.Service.Switch, otherUfpSwitch)?.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
609
696
|
}
|
|
610
697
|
// Inform the user, and we're done.
|
|
611
|
-
this.log.info("
|
|
698
|
+
this.log.info("UniFi Protect recording mode set to %s.", ufpRecordingSetting);
|
|
612
699
|
});
|
|
613
700
|
// Initialize the recording switch state.
|
|
614
|
-
switchService.updateCharacteristic(this.hap.Characteristic.On, this.
|
|
701
|
+
switchService.updateCharacteristic(this.hap.Characteristic.On, this.ufp.recordingSettings.mode === ufpRecordingSetting);
|
|
615
702
|
switchesEnabled.push(ufpRecordingSetting);
|
|
616
703
|
}
|
|
617
704
|
if (switchesEnabled.length) {
|
|
618
|
-
this.log.info("
|
|
705
|
+
this.log.info("Enabling UniFi Protect recording switches: %s.", switchesEnabled.join(", "));
|
|
619
706
|
}
|
|
620
707
|
return true;
|
|
621
708
|
}
|
|
622
709
|
// Configure MQTT capabilities of this camera.
|
|
623
710
|
configureMqtt() {
|
|
624
|
-
var _a, _b;
|
|
625
|
-
const bootstrap = this.nvr.nvrApi.bootstrap;
|
|
626
|
-
const device = this.accessory.context.device;
|
|
627
711
|
// Return the RTSP URLs when requested.
|
|
628
|
-
|
|
629
|
-
var _a;
|
|
712
|
+
this.nvr.mqtt?.subscribe(this.accessory, "rtsp/get", (message) => {
|
|
630
713
|
const value = message.toString();
|
|
631
714
|
// When we get the right message, we trigger the snapshot request.
|
|
632
|
-
if (
|
|
715
|
+
if (value?.toLowerCase() !== "true") {
|
|
633
716
|
return;
|
|
634
717
|
}
|
|
635
718
|
const urlInfo = {};
|
|
636
719
|
// Grab all the available RTSP channels.
|
|
637
|
-
for (const channel of
|
|
638
|
-
if (!
|
|
720
|
+
for (const channel of this.ufp.channels) {
|
|
721
|
+
if (!channel.isRtspEnabled) {
|
|
639
722
|
continue;
|
|
640
723
|
}
|
|
641
|
-
urlInfo[channel.name] = "rtsps://" +
|
|
724
|
+
urlInfo[channel.name] = "rtsps://" + this.nvr.ufp.host + ":" + this.nvr.ufp.ports.rtsp.toString() + "/" + channel.rtspAlias + "?enableSrtp";
|
|
642
725
|
}
|
|
643
|
-
|
|
644
|
-
this.log.info("
|
|
726
|
+
this.nvr.mqtt?.publish(this.accessory, "rtsp", JSON.stringify(urlInfo));
|
|
727
|
+
this.log.info("RTSP information published via MQTT.");
|
|
645
728
|
});
|
|
646
729
|
// Trigger snapshots when requested.
|
|
647
|
-
|
|
648
|
-
var _a;
|
|
730
|
+
this.nvr.mqtt?.subscribe(this.accessory, "snapshot/trigger", (message) => {
|
|
649
731
|
const value = message.toString();
|
|
650
732
|
// When we get the right message, we trigger the snapshot request.
|
|
651
|
-
if (
|
|
733
|
+
if (value?.toLowerCase() !== "true") {
|
|
652
734
|
return;
|
|
653
735
|
}
|
|
654
|
-
void
|
|
655
|
-
this.log.info("
|
|
736
|
+
void this.stream?.handleSnapshotRequest();
|
|
737
|
+
this.log.info("Snapshot triggered via MQTT.");
|
|
656
738
|
});
|
|
657
739
|
return true;
|
|
658
740
|
}
|
|
659
741
|
// Refresh camera-specific characteristics.
|
|
660
742
|
updateDevice() {
|
|
661
|
-
var _a, _b, _c;
|
|
662
|
-
// Grab our device context.
|
|
663
|
-
const device = this.accessory.context.device;
|
|
664
743
|
// Update the camera state.
|
|
665
|
-
|
|
666
|
-
// Find the service, if it exists.
|
|
667
|
-
const service = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
744
|
+
this.accessory.getService(this.hap.Service.MotionSensor)?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.ufp.state === "CONNECTED");
|
|
668
745
|
// Check to see if this device has a status light.
|
|
669
|
-
if (
|
|
670
|
-
|
|
746
|
+
if (this.hints.ledStatus) {
|
|
747
|
+
this.accessory.getService(this.hap.Service.CameraOperatingMode)?.
|
|
748
|
+
updateCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator, this.ufp.ledSettings.isEnabled === true);
|
|
671
749
|
}
|
|
672
750
|
// Check for updates to the recording state, if we have the switches configured.
|
|
673
|
-
if (
|
|
751
|
+
if (this.nvr.optionEnabled(this.ufp, "Nvr.Recording.Switch", false)) {
|
|
674
752
|
// Update all the switch states.
|
|
675
|
-
for (const ufpRecordingSwitchType of [
|
|
753
|
+
for (const ufpRecordingSwitchType of [ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
676
754
|
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
677
755
|
// Update state based on the recording mode.
|
|
678
|
-
|
|
756
|
+
this.accessory.getServiceById(this.hap.Service.Switch, ufpRecordingSwitchType)?.
|
|
757
|
+
updateCharacteristic(this.hap.Characteristic.On, ufpRecordingSetting === this.ufp.recordingSettings.mode);
|
|
679
758
|
}
|
|
680
759
|
}
|
|
681
760
|
return true;
|
|
682
761
|
}
|
|
683
762
|
// Get the current bitrate for a specific camera channel.
|
|
684
763
|
getBitrate(channelId) {
|
|
685
|
-
var _a;
|
|
686
|
-
// Grab the device JSON.
|
|
687
|
-
const device = this.accessory.context.device;
|
|
688
764
|
// Find the right channel.
|
|
689
|
-
const channel =
|
|
690
|
-
return
|
|
765
|
+
const channel = this.ufp.channels.find(x => x.id === channelId);
|
|
766
|
+
return channel?.bitrate ?? -1;
|
|
691
767
|
}
|
|
692
768
|
// Set the bitrate for a specific camera channel.
|
|
693
769
|
async setBitrate(channelId, value) {
|
|
694
|
-
var _a, _b, _c;
|
|
695
770
|
// If we've disabled the ability to set the bitrate dynamically, silently fail. We prioritize switches over the global
|
|
696
771
|
// setting here, in case the user enabled both, using the principle that the most specific setting always wins. If the
|
|
697
772
|
// user has both the global setting and the switch enabled, the switch setting will take precedence.
|
|
698
|
-
if ((!this.accessory.context.dynamicBitrate &&
|
|
699
|
-
!
|
|
700
|
-
|
|
701
|
-
((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate", false)) &&
|
|
702
|
-
((_c = this.nvr) === null || _c === void 0 ? void 0 : _c.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate.Switch", false)))) {
|
|
773
|
+
if ((!this.accessory.context.dynamicBitrate && !this.nvr.optionEnabled(this.ufp, "Video.DynamicBitrate", false)) ||
|
|
774
|
+
(!this.accessory.context.dynamicBitrate && this.nvr.optionEnabled(this.ufp, "Video.DynamicBitrate", false) &&
|
|
775
|
+
this.nvr.optionEnabled(this.ufp, "Video.DynamicBitrate.Switch", false))) {
|
|
703
776
|
return true;
|
|
704
777
|
}
|
|
705
|
-
// Grab the device JSON.
|
|
706
|
-
const device = this.accessory.context.device;
|
|
707
778
|
// Find the right channel.
|
|
708
|
-
const channel =
|
|
779
|
+
const channel = this.ufp.channels.find(x => x.id === channelId);
|
|
709
780
|
// No channel, we're done.
|
|
710
781
|
if (!channel) {
|
|
711
782
|
return false;
|
|
@@ -717,18 +788,17 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
717
788
|
// Make sure the requested bitrate fits within the constraints of what this channel can do.
|
|
718
789
|
channel.bitrate = Math.min(channel.maxBitrate, Math.max(channel.minBitrate, value));
|
|
719
790
|
// Tell Protect about it.
|
|
720
|
-
const newDevice = await this.nvr.
|
|
791
|
+
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: this.ufp.channels });
|
|
721
792
|
if (!newDevice) {
|
|
722
|
-
this.log.error("
|
|
793
|
+
this.log.error("Unable to set the streaming bitrate to %s.", value);
|
|
723
794
|
return false;
|
|
724
795
|
}
|
|
725
796
|
// Save our updated device context.
|
|
726
|
-
this.
|
|
797
|
+
this.ufp = newDevice;
|
|
727
798
|
return true;
|
|
728
799
|
}
|
|
729
800
|
// Find an RTSP configuration for a given target resolution.
|
|
730
801
|
findRtspEntry(width, height, camera, address, rtspEntries, defaultStream = this.rtspQuality.StreamingDefault) {
|
|
731
|
-
var _a, _b;
|
|
732
802
|
// No RTSP entries to choose from, we're done.
|
|
733
803
|
if (!rtspEntries || !rtspEntries.length) {
|
|
734
804
|
return null;
|
|
@@ -753,12 +823,12 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
753
823
|
}
|
|
754
824
|
// If it's set to none, we default to our normal lookup logic.
|
|
755
825
|
if (this.rtspQuality[address] !== "None") {
|
|
756
|
-
return
|
|
826
|
+
return rtspEntries.find(x => x.channel.name.toUpperCase() === this.rtspQuality[address]) ?? null;
|
|
757
827
|
}
|
|
758
828
|
}
|
|
759
829
|
// Second, we check to see if we've set an explicit preference for stream quality.
|
|
760
830
|
if (defaultStream) {
|
|
761
|
-
return
|
|
831
|
+
return rtspEntries.find(x => x.channel.name.toUpperCase() === defaultStream) ?? null;
|
|
762
832
|
}
|
|
763
833
|
// See if we have a match for our desired resolution on the camera. We ignore FPS - HomeKit clients seem
|
|
764
834
|
// to be able to handle it just fine.
|
|
@@ -827,5 +897,56 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
827
897
|
return resolution[0].toString() + "x" + resolution[1].toString() + "@" + resolution[2].toString() + "fps";
|
|
828
898
|
}
|
|
829
899
|
}
|
|
830
|
-
|
|
900
|
+
// Package camera class.
|
|
901
|
+
export class ProtectPackageCamera extends ProtectCamera {
|
|
902
|
+
// Configure the package camera.
|
|
903
|
+
async configureDevice() {
|
|
904
|
+
this.hints.probesize = 20480; //10240;
|
|
905
|
+
// this.hints.transcode = this.nvr.optionEnabled(this.ufp, "Video.Transcode", false);
|
|
906
|
+
// this.hints.transcode = true;
|
|
907
|
+
this.hasHksv = false;
|
|
908
|
+
this.isRinging = false;
|
|
909
|
+
// Clean out the context object in case it's been polluted somehow.
|
|
910
|
+
this.accessory.context = {};
|
|
911
|
+
this.accessory.context.nvr = this.nvr.ufp.mac;
|
|
912
|
+
this.accessory.context.packageCamera = true;
|
|
913
|
+
// Configure accessory information.
|
|
914
|
+
this.configureInfo();
|
|
915
|
+
// Set the snapshot URL.
|
|
916
|
+
this.snapshotUrl = this.nvr.ufpApi.getApiEndpoint(this.ufp.modelKey) + "/" + this.ufp.id + "/package-snapshot";
|
|
917
|
+
// Configure the video stream with our required resolutions. No, package cameras don't really support any of these resolutions, but they're required
|
|
918
|
+
// by HomeKit in order to stream video.
|
|
919
|
+
this.stream = new ProtectStreamingDelegate(this, [[3840, 2160, 30], [1920, 1080, 30], [1280, 960, 30], [1280, 720, 30], [1024, 768, 30], [640, 480, 30],
|
|
920
|
+
[640, 360, 30], [480, 360, 30], [480, 270, 30], [320, 240, 30], [320, 240, 15], [320, 180, 30]]);
|
|
921
|
+
// Fire up the controller and inform HomeKit about it.
|
|
922
|
+
this.accessory.configureController(this.stream.controller);
|
|
923
|
+
// Periodically refresh our snapshot cache.
|
|
924
|
+
void this.configureSnapshotUpdates();
|
|
925
|
+
// We're done.
|
|
926
|
+
return Promise.resolve(true);
|
|
927
|
+
}
|
|
928
|
+
// Make our RTSP stream findable.
|
|
929
|
+
findRtsp() {
|
|
930
|
+
const channel = this.ufp.channels.find(x => x.name === "Package Camera");
|
|
931
|
+
if (!channel) {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
channel: channel,
|
|
936
|
+
name: channel.name,
|
|
937
|
+
resolution: [channel.width, channel.height, channel.fps],
|
|
938
|
+
url: "rtsps://" + this.nvr.nvrOptions.address + ":" + this.nvr.ufp.ports.rtsps.toString() + "/" + channel.rtspAlias + "?enableSrtp"
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
// Get the current bitrate for a specific camera channel.
|
|
942
|
+
getBitrate(channelId) {
|
|
943
|
+
// Find the right channel.
|
|
944
|
+
const channel = this.ufp.channels.find(x => x.id === channelId);
|
|
945
|
+
return channel?.bitrate ?? -1;
|
|
946
|
+
}
|
|
947
|
+
// Set the bitrate for a specific camera channel.
|
|
948
|
+
setBitrate() {
|
|
949
|
+
return Promise.resolve(true);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
831
952
|
//# sourceMappingURL=protect-camera.js.map
|