UNPKG

30.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.CameraController = exports.CameraControllerEvents = exports.ResourceRequestReason = void 0;
4const tslib_1 = require("tslib");
5const crypto_1 = tslib_1.__importDefault(require("crypto"));
6const debug_1 = tslib_1.__importDefault(require("debug"));
7const events_1 = require("events");
8const camera_1 = require("../camera");
9const Characteristic_1 = require("../Characteristic");
10const datastream_1 = require("../datastream");
11const Service_1 = require("../Service");
12const hapStatusError_1 = require("../util/hapStatusError");
13const debug = (0, debug_1.default)("HAP-NodeJS:Camera:Controller");
14/**
15 * @group Camera
16 */
17var ResourceRequestReason;
18(function (ResourceRequestReason) {
19 /**
20 * The reason describes periodic resource requests.
21 * In the example of camera image snapshots those are the typical preview images every 10 seconds.
22 */
23 ResourceRequestReason[ResourceRequestReason["PERIODIC"] = 0] = "PERIODIC";
24 /**
25 * The resource request is the result of some event.
26 * In the example of camera image snapshots, requests are made due to e.g. a motion event or similar.
27 */
28 ResourceRequestReason[ResourceRequestReason["EVENT"] = 1] = "EVENT";
29})(ResourceRequestReason || (exports.ResourceRequestReason = ResourceRequestReason = {}));
30/**
31 * @group Camera
32 */
33var CameraControllerEvents;
34(function (CameraControllerEvents) {
35 /**
36 * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
37 * except the mute state. When you adjust the volume in the Camera view it will reset the muted state if it was set previously.
38 * The value of volume has nothing to do with the volume slider in the Camera view of the Home app.
39 */
40 CameraControllerEvents["MICROPHONE_PROPERTIES_CHANGED"] = "microphone-change";
41 /**
42 * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
43 * except the mute state. When you unmute the device microphone it will reset the mute state if it was set previously.
44 */
45 CameraControllerEvents["SPEAKER_PROPERTIES_CHANGED"] = "speaker-change";
46})(CameraControllerEvents || (exports.CameraControllerEvents = CameraControllerEvents = {}));
47/**
48 * Everything needed to expose a HomeKit Camera.
49 *
50 * @group Camera
51 */
52// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
53class CameraController extends events_1.EventEmitter {
54 static STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services
55 stateChangeDelegate;
56 streamCount;
57 delegate;
58 streamingOptions;
59 /**
60 * **Temporary** storage for {@link CameraRecordingOptions} and {@link CameraRecordingDelegate}.
61 * This property is reset to `undefined` after the CameraController was fully initialized.
62 * You can still access those values via the {@link CameraController.recordingManagement}.
63 */
64 recording;
65 /**
66 * Temporary storage for the sensor option.
67 */
68 sensorOptions;
69 legacyMode = false;
70 /**
71 * @private
72 */
73 streamManagements = [];
74 /**
75 * The {@link RecordingManagement} which is responsible for handling HomeKit Secure Video.
76 * This property is only present if recording was configured.
77 */
78 recordingManagement;
79 microphoneService;
80 speakerService;
81 microphoneMuted = false;
82 microphoneVolume = 100;
83 speakerMuted = false;
84 speakerVolume = 100;
85 motionService;
86 motionServiceExternallySupplied = false;
87 occupancyService;
88 occupancyServiceExternallySupplied = false;
89 constructor(options, legacyMode = false) {
90 super();
91 this.streamCount = Math.max(1, options.cameraStreamCount || 1);
92 this.delegate = options.delegate;
93 this.streamingOptions = options.streamingOptions;
94 this.recording = options.recording;
95 this.sensorOptions = options.sensors;
96 this.legacyMode = legacyMode; // legacy mode will prevent from Microphone and Speaker services to get created to avoid collisions
97 }
98 /**
99 * @private
100 */
101 controllerId() {
102 return "camera" /* DefaultControllerType.CAMERA */;
103 }
104 // ----------------------------------- STREAM API ------------------------------------
105 /**
106 * Call this method if you want to forcefully suspend an ongoing streaming session.
107 * This would be adequate if the rtp server or media encoding encountered an unexpected error.
108 *
109 * @param sessionId - id of the current ongoing streaming session
110 */
111 forceStopStreamingSession(sessionId) {
112 this.streamManagements.forEach(management => {
113 if (management.sessionIdentifier === sessionId) {
114 management.forceStop();
115 }
116 });
117 }
118 static generateSynchronisationSource() {
119 const ssrc = crypto_1.default.randomBytes(4); // range [-2.14748e+09 - 2.14748e+09]
120 ssrc[0] = 0;
121 return ssrc.readInt32BE(0);
122 }
123 // ----------------------------- MICROPHONE/SPEAKER API ------------------------------
124 setMicrophoneMuted(muted = true) {
125 if (!this.microphoneService) {
126 return;
127 }
128 this.microphoneMuted = muted;
129 this.microphoneService.updateCharacteristic(Characteristic_1.Characteristic.Mute, muted);
130 }
131 setMicrophoneVolume(volume) {
132 if (!this.microphoneService) {
133 return;
134 }
135 this.microphoneVolume = volume;
136 this.microphoneService.updateCharacteristic(Characteristic_1.Characteristic.Volume, volume);
137 }
138 setSpeakerMuted(muted = true) {
139 if (!this.speakerService) {
140 return;
141 }
142 this.speakerMuted = muted;
143 this.speakerService.updateCharacteristic(Characteristic_1.Characteristic.Mute, muted);
144 }
145 setSpeakerVolume(volume) {
146 if (!this.speakerService) {
147 return;
148 }
149 this.speakerVolume = volume;
150 this.speakerService.updateCharacteristic(Characteristic_1.Characteristic.Volume, volume);
151 }
152 emitMicrophoneChange() {
153 this.emit("microphone-change" /* CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED */, this.microphoneMuted, this.microphoneVolume);
154 }
155 emitSpeakerChange() {
156 this.emit("speaker-change" /* CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED */, this.speakerMuted, this.speakerVolume);
157 }
158 // -----------------------------------------------------------------------------------
159 /**
160 * @private
161 */
162 constructServices() {
163 for (let i = 0; i < this.streamCount; i++) {
164 const rtp = new camera_1.RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, this.rtpStreamManagementDisabledThroughOperatingMode.bind(this));
165 this.streamManagements.push(rtp);
166 }
167 if (!this.legacyMode && this.streamingOptions.audio) {
168 // In theory the Microphone Service is a necessity. In practice, it's not. lol.
169 // So we just add it if the user wants to support audio
170 this.microphoneService = new Service_1.Service.Microphone("", "");
171 this.microphoneService.setCharacteristic(Characteristic_1.Characteristic.Volume, this.microphoneVolume);
172 if (this.streamingOptions.audio.twoWayAudio) {
173 this.speakerService = new Service_1.Service.Speaker("", "");
174 this.speakerService.setCharacteristic(Characteristic_1.Characteristic.Volume, this.speakerVolume);
175 }
176 }
177 if (this.recording) {
178 this.recordingManagement = new camera_1.RecordingManagement(this.recording.options, this.recording.delegate, this.retrieveEventTriggerOptions());
179 }
180 if (this.sensorOptions?.motion) {
181 if (typeof this.sensorOptions.motion === "boolean") {
182 this.motionService = new Service_1.Service.MotionSensor("", "");
183 }
184 else {
185 this.motionService = this.sensorOptions.motion;
186 this.motionServiceExternallySupplied = true;
187 }
188 this.motionService.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
189 this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService);
190 }
191 if (this.sensorOptions?.occupancy) {
192 if (typeof this.sensorOptions.occupancy === "boolean") {
193 this.occupancyService = new Service_1.Service.OccupancySensor("", "");
194 }
195 else {
196 this.occupancyService = this.sensorOptions.occupancy;
197 this.occupancyServiceExternallySupplied = true;
198 }
199 this.occupancyService.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
200 this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService);
201 }
202 const serviceMap = {
203 microphone: this.microphoneService,
204 speaker: this.speakerService,
205 motionService: !this.motionServiceExternallySupplied ? this.motionService : undefined,
206 occupancyService: !this.occupancyServiceExternallySupplied ? this.occupancyService : undefined,
207 };
208 if (this.recordingManagement) {
209 serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService;
210 serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService;
211 serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService();
212 }
213 this.streamManagements.forEach((management, index) => {
214 serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService();
215 });
216 this.recording = undefined;
217 this.sensorOptions = undefined;
218 return serviceMap;
219 }
220 /**
221 * @private
222 */
223 initWithServices(serviceMap) {
224 const result = this._initWithServices(serviceMap);
225 if (result.updated) { // serviceMap must only be returned if anything actually changed
226 return result.serviceMap;
227 }
228 }
229 _initWithServices(serviceMap) {
230 let modifiedServiceMap = false;
231 // eslint-disable-next-line no-constant-condition
232 for (let i = 0; true; i++) {
233 const streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i];
234 if (i < this.streamCount) {
235 const operatingModeClosure = this.rtpStreamManagementDisabledThroughOperatingMode.bind(this);
236 if (streamManagementService) { // normal init
237 this.streamManagements.push(new camera_1.RTPStreamManagement(i, this.streamingOptions, this.delegate, streamManagementService, operatingModeClosure));
238 }
239 else { // stream count got bigger, we need to create a new service
240 const management = new camera_1.RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, operatingModeClosure);
241 this.streamManagements.push(management);
242 serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService();
243 modifiedServiceMap = true;
244 }
245 }
246 else {
247 if (streamManagementService) { // stream count got reduced, we need to remove old service
248 delete serviceMap[CameraController.STREAM_MANAGEMENT + i];
249 modifiedServiceMap = true;
250 }
251 else {
252 break; // we finished counting, and we got no saved service; we are finished
253 }
254 }
255 }
256 // MICROPHONE
257 if (!this.legacyMode && this.streamingOptions.audio) { // microphone should be present
258 if (serviceMap.microphone) {
259 this.microphoneService = serviceMap.microphone;
260 }
261 else {
262 // microphone wasn't created yet => create a new one
263 this.microphoneService = new Service_1.Service.Microphone("", "");
264 this.microphoneService.setCharacteristic(Characteristic_1.Characteristic.Volume, this.microphoneVolume);
265 serviceMap.microphone = this.microphoneService;
266 modifiedServiceMap = true;
267 }
268 }
269 else if (serviceMap.microphone) { // microphone service supplied, though settings seemed to have changed
270 // we need to remove it
271 delete serviceMap.microphone;
272 modifiedServiceMap = true;
273 }
274 // SPEAKER
275 if (!this.legacyMode && this.streamingOptions.audio?.twoWayAudio) { // speaker should be present
276 if (serviceMap.speaker) {
277 this.speakerService = serviceMap.speaker;
278 }
279 else {
280 // speaker wasn't created yet => create a new one
281 this.speakerService = new Service_1.Service.Speaker("", "");
282 this.speakerService.setCharacteristic(Characteristic_1.Characteristic.Volume, this.speakerVolume);
283 serviceMap.speaker = this.speakerService;
284 modifiedServiceMap = true;
285 }
286 }
287 else if (serviceMap.speaker) { // speaker service supplied, though settings seemed to have changed
288 // we need to remove it
289 delete serviceMap.speaker;
290 modifiedServiceMap = true;
291 }
292 // RECORDING
293 if (this.recording) {
294 const eventTriggers = this.retrieveEventTriggerOptions();
295 // RECORDING MANAGEMENT
296 if (serviceMap.cameraEventRecordingManagement && serviceMap.cameraOperatingMode && serviceMap.dataStreamTransportManagement) {
297 this.recordingManagement = new camera_1.RecordingManagement(this.recording.options, this.recording.delegate, eventTriggers, {
298 recordingManagement: serviceMap.cameraEventRecordingManagement,
299 operatingMode: serviceMap.cameraOperatingMode,
300 dataStreamManagement: new datastream_1.DataStreamManagement(serviceMap.dataStreamTransportManagement),
301 });
302 }
303 else {
304 this.recordingManagement = new camera_1.RecordingManagement(this.recording.options, this.recording.delegate, eventTriggers);
305 serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService;
306 serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService;
307 serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService();
308 modifiedServiceMap = true;
309 }
310 }
311 else {
312 if (serviceMap.cameraEventRecordingManagement) {
313 delete serviceMap.cameraEventRecordingManagement;
314 modifiedServiceMap = true;
315 }
316 if (serviceMap.cameraOperatingMode) {
317 delete serviceMap.cameraOperatingMode;
318 modifiedServiceMap = true;
319 }
320 if (serviceMap.dataStreamTransportManagement) {
321 delete serviceMap.dataStreamTransportManagement;
322 modifiedServiceMap = true;
323 }
324 }
325 // MOTION SENSOR
326 if (this.sensorOptions?.motion) {
327 if (typeof this.sensorOptions.motion === "boolean") {
328 if (serviceMap.motionService) {
329 this.motionService = serviceMap.motionService;
330 }
331 else {
332 // it could be the case that we previously had a manually supplied motion service
333 // at this point we can't remove the iid from the list of linked services from the recording management!
334 this.motionService = new Service_1.Service.MotionSensor("", "");
335 }
336 }
337 else {
338 this.motionService = this.sensorOptions.motion;
339 this.motionServiceExternallySupplied = true;
340 if (serviceMap.motionService) { // motion service previously supplied as bool option
341 this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService);
342 delete serviceMap.motionService;
343 modifiedServiceMap = true;
344 }
345 }
346 this.motionService.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
347 this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService);
348 }
349 else {
350 if (serviceMap.motionService) {
351 this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService);
352 delete serviceMap.motionService;
353 modifiedServiceMap = true;
354 }
355 }
356 // OCCUPANCY SENSOR
357 if (this.sensorOptions?.occupancy) {
358 if (typeof this.sensorOptions.occupancy === "boolean") {
359 if (serviceMap.occupancyService) {
360 this.occupancyService = serviceMap.occupancyService;
361 }
362 else {
363 // it could be the case that we previously had a manually supplied occupancy service
364 // at this point we can't remove the iid from the list of linked services from the recording management!
365 this.occupancyService = new Service_1.Service.OccupancySensor("", "");
366 }
367 }
368 else {
369 this.occupancyService = this.sensorOptions.occupancy;
370 this.occupancyServiceExternallySupplied = true;
371 if (serviceMap.occupancyService) { // occupancy service previously supplied as bool option
372 this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService);
373 delete serviceMap.occupancyService;
374 modifiedServiceMap = true;
375 }
376 }
377 this.occupancyService.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
378 this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService);
379 }
380 else {
381 if (serviceMap.occupancyService) {
382 this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService);
383 delete serviceMap.occupancyService;
384 modifiedServiceMap = true;
385 }
386 }
387 if (this.migrateFromDoorbell(serviceMap)) {
388 modifiedServiceMap = true;
389 }
390 this.recording = undefined;
391 this.sensorOptions = undefined;
392 return {
393 serviceMap: serviceMap,
394 updated: modifiedServiceMap,
395 };
396 }
397 // overwritten in DoorbellController (to avoid cyclic dependencies, I hate typescript for that)
398 migrateFromDoorbell(serviceMap) {
399 if (serviceMap.doorbell) { // See NOTICE in DoorbellController
400 delete serviceMap.doorbell;
401 return true;
402 }
403 return false;
404 }
405 retrieveEventTriggerOptions() {
406 if (!this.recording) {
407 return new Set();
408 }
409 const triggerOptions = new Set();
410 if (this.recording.options.overrideEventTriggerOptions) {
411 for (const option of this.recording.options.overrideEventTriggerOptions) {
412 triggerOptions.add(option);
413 }
414 }
415 if (this.sensorOptions?.motion) {
416 triggerOptions.add(1 /* EventTriggerOption.MOTION */);
417 }
418 // this method is overwritten by the `DoorbellController` to automatically configure EventTriggerOption.DOORBELL
419 return triggerOptions;
420 }
421 /**
422 * @private
423 */
424 configureServices() {
425 if (this.microphoneService) {
426 this.microphoneService.getCharacteristic(Characteristic_1.Characteristic.Mute)
427 .on("get" /* CharacteristicEventTypes.GET */, (callback) => {
428 callback(undefined, this.microphoneMuted);
429 })
430 .on("set" /* CharacteristicEventTypes.SET */, (value, callback) => {
431 this.microphoneMuted = value;
432 callback();
433 this.emitMicrophoneChange();
434 });
435 this.microphoneService.getCharacteristic(Characteristic_1.Characteristic.Volume)
436 .on("get" /* CharacteristicEventTypes.GET */, (callback) => {
437 callback(undefined, this.microphoneVolume);
438 })
439 .on("set" /* CharacteristicEventTypes.SET */, (value, callback) => {
440 this.microphoneVolume = value;
441 callback();
442 this.emitMicrophoneChange();
443 });
444 }
445 if (this.speakerService) {
446 this.speakerService.getCharacteristic(Characteristic_1.Characteristic.Mute)
447 .on("get" /* CharacteristicEventTypes.GET */, (callback) => {
448 callback(undefined, this.speakerMuted);
449 })
450 .on("set" /* CharacteristicEventTypes.SET */, (value, callback) => {
451 this.speakerMuted = value;
452 callback();
453 this.emitSpeakerChange();
454 });
455 this.speakerService.getCharacteristic(Characteristic_1.Characteristic.Volume)
456 .on("get" /* CharacteristicEventTypes.GET */, (callback) => {
457 callback(undefined, this.speakerVolume);
458 })
459 .on("set" /* CharacteristicEventTypes.SET */, (value, callback) => {
460 this.speakerVolume = value;
461 callback();
462 this.emitSpeakerChange();
463 });
464 }
465 // make the sensor services available to the RecordingManagement.
466 if (this.motionService) {
467 this.recordingManagement?.sensorServices.push(this.motionService);
468 }
469 if (this.occupancyService) {
470 this.recordingManagement?.sensorServices.push(this.occupancyService);
471 }
472 }
473 rtpStreamManagementDisabledThroughOperatingMode() {
474 return this.recordingManagement
475 ? !this.recordingManagement.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive).value
476 : false;
477 }
478 /**
479 * @private
480 */
481 handleControllerRemoved() {
482 this.handleFactoryReset();
483 for (const management of this.streamManagements) {
484 management.destroy();
485 }
486 this.streamManagements.splice(0, this.streamManagements.length);
487 this.microphoneService = undefined;
488 this.speakerService = undefined;
489 this.recordingManagement?.destroy();
490 this.recordingManagement = undefined;
491 this.removeAllListeners();
492 }
493 /**
494 * @private
495 */
496 handleFactoryReset() {
497 this.streamManagements.forEach(management => management.handleFactoryReset());
498 this.recordingManagement?.handleFactoryReset();
499 this.microphoneMuted = false;
500 this.microphoneVolume = 100;
501 this.speakerMuted = false;
502 this.speakerVolume = 100;
503 }
504 /**
505 * @private
506 */
507 serialize() {
508 const streamManagementStates = [];
509 for (const management of this.streamManagements) {
510 const serializedState = management.serialize();
511 if (serializedState) {
512 streamManagementStates.push(serializedState);
513 }
514 }
515 return {
516 streamManagements: streamManagementStates,
517 recordingManagement: this.recordingManagement?.serialize(),
518 };
519 }
520 /**
521 * @private
522 */
523 deserialize(serialized) {
524 for (const streamManagementState of serialized.streamManagements) {
525 const streamManagement = this.streamManagements[streamManagementState.id];
526 if (streamManagement) {
527 streamManagement.deserialize(streamManagementState);
528 }
529 }
530 if (serialized.recordingManagement) {
531 if (this.recordingManagement) {
532 this.recordingManagement.deserialize(serialized.recordingManagement);
533 }
534 else {
535 // Active characteristic cannot be controlled if removing HSV, ensure they are all active!
536 for (const streamManagement of this.streamManagements) {
537 streamManagement.service.updateCharacteristic(Characteristic_1.Characteristic.Active, true);
538 }
539 this.stateChangeDelegate?.();
540 }
541 }
542 }
543 /**
544 * @private
545 */
546 setupStateChangeDelegate(delegate) {
547 this.stateChangeDelegate = delegate;
548 for (const streamManagement of this.streamManagements) {
549 streamManagement.setupStateChangeDelegate(delegate);
550 }
551 this.recordingManagement?.setupStateChangeDelegate(delegate);
552 }
553 /**
554 * @private
555 */
556 handleSnapshotRequest(height, width, accessoryName, reason) {
557 // first step is to verify that the reason is applicable to our current policy
558 const streamingDisabled = this.streamManagements
559 .map(management => !management.getService().getCharacteristic(Characteristic_1.Characteristic.Active).value)
560 .reduce((previousValue, currentValue) => previousValue && currentValue);
561 if (streamingDisabled) {
562 debug("[%s] Rejecting snapshot as streaming is disabled.", accessoryName);
563 return Promise.reject(-70412 /* HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE */);
564 }
565 if (this.recordingManagement) {
566 const operatingModeService = this.recordingManagement.operatingModeService;
567 if (!operatingModeService.getCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive).value) {
568 debug("[%s] Rejecting snapshot as HomeKit camera is disabled.", accessoryName);
569 return Promise.reject(-70412 /* HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE */);
570 }
571 const eventSnapshotsActive = operatingModeService
572 .getCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive)
573 .value;
574 if (!eventSnapshotsActive) {
575 if (reason == null) {
576 debug("[%s] Rejecting snapshot as reason is required due to disabled event snapshots.", accessoryName);
577 return Promise.reject(-70401 /* HAPStatus.INSUFFICIENT_PRIVILEGES */);
578 }
579 else if (reason === 1 /* ResourceRequestReason.EVENT */) {
580 debug("[%s] Rejecting snapshot as even snapshots are disabled.", accessoryName);
581 return Promise.reject(-70412 /* HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE */);
582 }
583 }
584 const periodicSnapshotsActive = operatingModeService
585 .getCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive)
586 .value;
587 if (!periodicSnapshotsActive) {
588 if (reason == null) {
589 debug("[%s] Rejecting snapshot as reason is required due to disabled periodic snapshots.", accessoryName);
590 return Promise.reject(-70401 /* HAPStatus.INSUFFICIENT_PRIVILEGES */);
591 }
592 else if (reason === 0 /* ResourceRequestReason.PERIODIC */) {
593 debug("[%s] Rejecting snapshot as periodic snapshots are disabled.", accessoryName);
594 return Promise.reject(-70412 /* HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE */);
595 }
596 }
597 }
598 // now do the actual snapshot request.
599 return new Promise((resolve, reject) => {
600 // TODO test and make timeouts configurable!
601 let timeout = setTimeout(() => {
602 console.warn(`[${accessoryName}] The image snapshot handler for the given accessory is slow to respond! See https://homebridge.io/w/JtMGR for more info.`);
603 timeout = setTimeout(() => {
604 timeout = undefined;
605 console.warn(`[${accessoryName}] The image snapshot handler for the given accessory didn't respond at all! See https://homebridge.io/w/JtMGR for more info.`);
606 reject(-70408 /* HAPStatus.OPERATION_TIMED_OUT */);
607 }, 17000);
608 timeout.unref();
609 }, 8000);
610 timeout.unref();
611 try {
612 this.delegate.handleSnapshotRequest({
613 height: height,
614 width: width,
615 reason: reason,
616 }, (error, buffer) => {
617 if (!timeout) {
618 return;
619 }
620 else {
621 clearTimeout(timeout);
622 timeout = undefined;
623 }
624 if (error) {
625 if (typeof error === "number") {
626 reject(error);
627 }
628 else {
629 debug("[%s] Error getting snapshot: %s", accessoryName, error.stack);
630 reject(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
631 }
632 return;
633 }
634 if (!buffer || buffer.length === 0) {
635 console.warn(`[${accessoryName}] Snapshot request handler provided empty image buffer!`);
636 reject(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
637 }
638 else {
639 resolve(buffer);
640 }
641 });
642 }
643 catch (error) {
644 if (!timeout) {
645 return;
646 }
647 else {
648 clearTimeout(timeout);
649 timeout = undefined;
650 }
651 console.warn(`[${accessoryName}] Unhandled error thrown inside snapshot request handler: ${error.stack}`);
652 reject(error instanceof hapStatusError_1.HapStatusError ? error.hapStatus : -70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
653 }
654 });
655 }
656}
657exports.CameraController = CameraController;
658//# sourceMappingURL=CameraController.js.map
\No newline at end of file