1 | import { EventEmitter } from "events";
|
2 | import { SessionIdentifier } from "../../types";
|
3 | import { CameraRecordingConfiguration, CameraRecordingOptions, CameraStreamingOptions, EventTriggerOption, PrepareStreamRequest, PrepareStreamResponse, RecordingManagement, RecordingManagementState, RecordingPacket, RTPStreamManagement, RTPStreamManagementState, SnapshotRequest, StreamingRequest } from "../camera";
|
4 | import { HDSProtocolSpecificErrorReason } from "../datastream";
|
5 | import { CameraOperatingMode, CameraRecordingManagement, DataStreamTransportManagement, Doorbell, Microphone, MotionSensor, OccupancySensor, Speaker } from "../definitions";
|
6 | import { HAPStatus } from "../HAPServer";
|
7 | import { Service } from "../Service";
|
8 | import { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from "./Controller";
|
9 | /**
|
10 | * @group Camera
|
11 | */
|
12 | export interface CameraControllerOptions {
|
13 | /**
|
14 | * Amount of parallel camera streams the accessory is capable of running.
|
15 | * As of the official HAP specification non SecureVideo cameras have a minimum required amount of 2 (but 1 is also fine).
|
16 | * Secure Video cameras just expose 1 stream.
|
17 | *
|
18 | * Default value: 1
|
19 | */
|
20 | cameraStreamCount?: number;
|
21 | /**
|
22 | * Delegate which handles the actual RTP/RTCP video/audio streaming and Snapshot requests.
|
23 | */
|
24 | delegate: CameraStreamingDelegate;
|
25 | /**
|
26 | * Options regarding video/audio streaming
|
27 | */
|
28 | streamingOptions: CameraStreamingOptions;
|
29 | /**
|
30 | * When supplying this option, it will enable support for HomeKit Secure Video.
|
31 | * This will create the {@link Service.CameraRecordingManagement}, {@link Service.CameraOperatingMode}
|
32 | * and {@link Service.DataStreamTransportManagement} services.
|
33 | *
|
34 | * NOTE: The controller only initializes the required characteristics for the {@link Service.CameraOperatingMode}.
|
35 | * You may add optional characteristics, if required, by accessing the service directly `CameraController.recordingManagement.operatingModeService`.
|
36 | */
|
37 | recording?: {
|
38 | /**
|
39 | * Options regarding Recordings (Secure Video)
|
40 | */
|
41 | options: CameraRecordingOptions;
|
42 | /**
|
43 | * Delegate which handles the audio/video recording data streaming on motion.
|
44 | */
|
45 | delegate: CameraRecordingDelegate;
|
46 | };
|
47 | /**
|
48 | * This config section configures optional sensors for the camera.
|
49 | * It e.g. may be used to set up a {@link EventTriggerOption.MOTION} trigger when configuring Secure Video.
|
50 | *
|
51 | * You may either specify and provide the desired {@link Service}s or specify their creation and maintenance using a `boolean` flag.
|
52 | * In this case the controller will create and maintain the service for you.
|
53 | * Otherwise, when you supply an already created instance of the {@link Service}, you are responsible yourself to manage the service
|
54 | * (e.g. creating, restoring, adding to the accessory, ...).
|
55 | *
|
56 | * The services can be accessed through the documented property after the call to {@link Accessory.configureController} has returned.
|
57 | */
|
58 | sensors?: {
|
59 | /**
|
60 | * Define if a {@link Service.MotionSensor} should be created/associated with the controller.
|
61 | *
|
62 | * You may access the created service via the {@link CameraController.motionService} property to configure listeners.
|
63 | *
|
64 | * ## HomeKit Secure Video:
|
65 | *
|
66 | * If supplied, this sensor will be used as a {@link EventTriggerOption.MOTION} trigger.
|
67 | * The characteristic {@link Characteristic.StatusActive} will be added, which is used to enable or disable the sensor.
|
68 | */
|
69 | motion?: Service | boolean;
|
70 | /**
|
71 | * Define if a {@link Service.OccupancySensor} should be created/associated with the controller.
|
72 | *
|
73 | * You may access the created service via the {@link CameraController.occupancyService} property to configure listeners.
|
74 | *
|
75 | * ## HomeKit Secure Video:
|
76 | *
|
77 | * The characteristic {@link Characteristic.StatusActive} will be added, which is used to enable or disable the sensor.
|
78 | */
|
79 | occupancy?: Service | boolean;
|
80 | };
|
81 | }
|
82 | /**
|
83 | * @group Camera
|
84 | */
|
85 | export type SnapshotRequestCallback = (error?: Error | HAPStatus, buffer?: Buffer) => void;
|
86 | /**
|
87 | * @group Camera
|
88 | */
|
89 | export type PrepareStreamCallback = (error?: Error, response?: PrepareStreamResponse) => void;
|
90 | /**
|
91 | * @group Camera
|
92 | */
|
93 | export type StreamRequestCallback = (error?: Error) => void;
|
94 | /**
|
95 | * @group Camera
|
96 | */
|
97 | export declare const enum ResourceRequestReason {
|
98 | /**
|
99 | * The reason describes periodic resource requests.
|
100 | * In the example of camera image snapshots those are the typical preview images every 10 seconds.
|
101 | */
|
102 | PERIODIC = 0,
|
103 | /**
|
104 | * The resource request is the result of some event.
|
105 | * In the example of camera image snapshots, requests are made due to e.g. a motion event or similar.
|
106 | */
|
107 | EVENT = 1
|
108 | }
|
109 | /**
|
110 | * @group Camera
|
111 | */
|
112 | export interface CameraStreamingDelegate {
|
113 | /**
|
114 | * This method is called when a HomeKit controller requests a snapshot image for the given camera.
|
115 | * The handler must respect the desired image height and width given in the {@link SnapshotRequest}.
|
116 | * The returned Buffer (via the callback) must be encoded in jpeg.
|
117 | *
|
118 | * HAP-NodeJS will complain about slow running handlers after 5 seconds and terminate the request after 15 seconds.
|
119 | *
|
120 | * @param request - Request containing image size.
|
121 | * @param callback - Callback supplied with the resulting Buffer
|
122 | */
|
123 | handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void;
|
124 | prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void;
|
125 | handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void;
|
126 | }
|
127 | /**
|
128 | * A `CameraRecordingDelegate` is responsible for handling recordings of a HomeKit Secure Video camera.
|
129 | *
|
130 | * It is responsible for maintaining the prebuffer (see {@link CameraRecordingOptions.prebufferLength},
|
131 | * once recording was activated (see {@link updateRecordingActive}).
|
132 | *
|
133 | * Before recording is considered enabled two things must happen:
|
134 | * - Recording must be enabled by the user. Signaled through {@link updateRecordingActive}.
|
135 | * - Recording configurations must be selected by a HomeKit controller through {@link updateRecordingConfiguration}.
|
136 | *
|
137 | * A typical recording event scenario happens as follows:
|
138 | * - The camera is in idle mode, maintaining the prebuffer (the duration of the prebuffer depends on the selected {@link CameraRecordingConfiguration}).
|
139 | * - A recording event is triggered (e.g. motion or doorbell button press) and the camera signals it through
|
140 | * the respective characteristics (e.g. {@link Characteristic.MotionDetected} or {@link Characteristic.ProgrammableSwitchEvent}).
|
141 | * Further, the camera saves the content of the prebuffer and starts recording the video.
|
142 | * The camera should continue to store the recording until it runs out of space.
|
143 | * In any case the camera should preserve recordings which are nearest to the triggered event.
|
144 | * A stored recording might be completely deleted if a stream request wasn't initiated for eight seconds.
|
145 | * - A HomeKit Controller will open a new recording session to download the next recording.
|
146 | * This results in a call to {@link handleRecordingStreamRequest}.
|
147 | * - Once the recording event is finished the camera will reset the state accordingly
|
148 | * (e.g. in the {@link Service.MotionSensor} or {@link Service.Doorbell} service).
|
149 | * It will continue to send the remaining fragments of the currently ongoing recording stream request.
|
150 | * - The camera will either reach the end of the recording (and signal this via {@link RecordingPacket.isLast}. Also see {@link acknowledgeStream})
|
151 | * or it will continue to stream til the HomeKit Controller closes
|
152 | * the stream {@link closeRecordingStream} with reason {@link HDSProtocolSpecificErrorReason.NORMAL}.
|
153 | * - The camera goes back into idle mode.
|
154 | *
|
155 | * @group Camera
|
156 | */
|
157 | export interface CameraRecordingDelegate {
|
158 | /**
|
159 | * A call to this method notifies the `CameraRecordingDelegate` about a change to the
|
160 | * `CameraRecordingManagement.Active` characteristic. This characteristic controls
|
161 | * if the camera should react to recording events.
|
162 | *
|
163 | * If recording is disabled the camera can stop maintaining its prebuffer.
|
164 | * If recording is enabled the camera should start recording into its prebuffer.
|
165 | *
|
166 | * A `CameraRecordingDelegate` should assume active to be `false` on startup.
|
167 | * HAP-NodeJS will persist the state of the `Active` characteristic across reboots
|
168 | * and will call {@link updateRecordingActive} accordingly on startup, if recording was previously enabled.
|
169 | *
|
170 | * NOTE: HAP-NodeJS cannot guarantee that a {@link CameraRecordingConfiguration} is present
|
171 | * when recording is activated (e.g. the selected configuration might be erased due to changes
|
172 | * in the supplied {@link CameraRecordingOptions}, but the camera is still `active`; or we can't otherwise
|
173 | * influence the order which a HomeKit Controller might call those characteristics).
|
174 | * However, HAP-NodeJS guarantees that if there is a valid {@link CameraRecordingConfiguration},
|
175 | * {@link updateRecordingConfiguration} is called before {@link updateRecordingActive} (when enabling)
|
176 | * to avoid any unnecessary and potentially expensive reconfigurations.
|
177 | *
|
178 | * @param active - Specifies if recording is active or not.
|
179 | */
|
180 | updateRecordingActive(active: boolean): void;
|
181 | /**
|
182 | * A call to this method signals that the selected (by the HomeKit Controller)
|
183 | * recording configuration of the camera has changed.
|
184 | *
|
185 | * On startup the delegate should assume `configuration = undefined`.
|
186 | * HAP-NodeJS will persist the state of both across reboots and will call
|
187 | * {@link updateRecordingConfiguration} on startup if there is a **selected configuration** present.
|
188 | *
|
189 | * NOTE: An update to the recording configuration might happen while there is still a running
|
190 | * recording stream. The camera MUST continue to use the previous configuration for the
|
191 | * currently running stream and only apply the updated configuration to the next stream.
|
192 | *
|
193 | * @param configuration - The {@link CameraRecordingConfiguration}. Reconfigure your recording pipeline accordingly.
|
194 | * The parameter might be `undefined` when the selected configuration became invalid. This typically ony happens
|
195 | * e.g. due to a factory reset (when all pairings are removed). Disable the recording pipeline in such a case
|
196 | * even if recording is still enabled for the camera.
|
197 | */
|
198 | updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void;
|
199 | /**
|
200 | * This method is called to stream the next recording event.
|
201 | * It is guaranteed that there is only ever one ongoing recording stream request at a time.
|
202 | *
|
203 | * When this method is called return the currently ongoing (or next in case of a potentially queued)
|
204 | * recording via a `AsyncGenerator`. Every `yield` of the generator represents a complete recording `packet`.
|
205 | * The first packet MUST always be the {@link PacketDataType.MEDIA_INITIALIZATION} packet.
|
206 | * Any following packet will transport the actual mp4 fragments in {@link PacketDataType.MEDIA_FRAGMENT} packets,
|
207 | * starting with the content of the prebuffer. Every {@link PacketDataType.MEDIA_FRAGMENT} starts with a key frame
|
208 | * and must not be longer than the specified duration set via the `CameraRecordingConfiguration.mediaContainerConfiguration.fragmentLength`
|
209 | * **selected** by the HomeKit Controller in {@link updateRecordingConfiguration}.
|
210 | *
|
211 | * NOTE: You MUST respect the value of {@link Characteristic.RecordingAudioActive} characteristic of the {@link Service.CameraOperatingMode}
|
212 | * service. When the characteristic is set to false you MUST NOT include audio in the mp4 fragments. You can access the characteristic via
|
213 | * the `CameraController.recordingManagement.operatingModeService` property.
|
214 | *
|
215 | * You might throw an error in this method if encountering a non-recoverable state.
|
216 | * You may throw a {@link HDSProtocolError} to manually define the {@link HDSProtocolSpecificErrorReason} for the `DATA_SEND` `CLOSE` event.
|
217 | *
|
218 | * There are three ways an ongoing recording stream can be closed:
|
219 | * - Closed by the Accessory: There are no further fragments to transmit. The delegate MUST signal this by setting {@link RecordingPacket.isLast}
|
220 | * to `true`. Once the HomeKit Controller receives this last fragment it will call {@link acknowledgeStream} to notify the accessory about
|
221 | * the successful transmission.
|
222 | * - Closed by the HomeKit Controller (expectedly): After the event trigger has been reset, the accessory continues to stream fragments.
|
223 | * At some point the HomeKit Controller will decide to shut down the stream by calling {@link closeRecordingStream} with a reason
|
224 | * of {@link HDSProtocolSpecificErrorReason.NORMAL}.
|
225 | * - Closed by the HomeKit Controller (unexpectedly): A HomeKit Controller might at any point decide to close a recording stream
|
226 | * if it encounters erroneous state. This is signaled by a call to {@link closeRecordingStream} with the respective reason.
|
227 | *
|
228 | * Once a close of stream is signaled, the `AsyncGenerator` function must return gracefully.
|
229 | *
|
230 | * For more information about `AsyncGenerator`s you might have a look at:
|
231 | * * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
232 | *
|
233 | * NOTE: HAP-NodeJS guarantees that this method is only called with a valid selected {@link CameraRecordingConfiguration}.
|
234 | *
|
235 | * NOTE: Don't rely on the streamId for unique identification. Two {@link DataStreamConnection}s might share the same identifier space.
|
236 | *
|
237 | * @param streamId - The streamId of the currently ongoing stream.
|
238 | */
|
239 | handleRecordingStreamRequest(streamId: number): AsyncGenerator<RecordingPacket>;
|
240 | /**
|
241 | * This method is called once the HomeKit Controller acknowledges the `endOfStream`.
|
242 | * A `endOfStream` is sent by the accessory by setting {@link RecordingPacket.isLast} to `true` in the last packet yielded
|
243 | * by the {@link handleRecordingStreamRequest} `AsyncGenerator`.
|
244 | *
|
245 | * @param streamId - The streamId of the acknowledged stream.
|
246 | */
|
247 | acknowledgeStream?(streamId: number): void;
|
248 | /**
|
249 | * This method is called to notify the delegate that a recording stream started via {@link handleRecordingStreamRequest} was closed.
|
250 | *
|
251 | * The method is also called if an ongoing recording stream is closed gracefully (using {@link HDSProtocolSpecificErrorReason.NORMAL}).
|
252 | * In either case, the delegate should stop supplying further fragments to the recording stream.
|
253 | * The `AsyncGenerator` function must return without yielding any further {@link RecordingPacket}s.
|
254 | * HAP-NodeJS won't send out any fragments from this point onwards.
|
255 | *
|
256 | * @param streamId - The streamId for which the close event was sent.
|
257 | * @param reason - The reason with which the stream was closed.
|
258 | * NOTE: This method is also called in case of a closed connection. This is encoded by setting the `reason` to undefined.
|
259 | */
|
260 | closeRecordingStream(streamId: number, reason: HDSProtocolSpecificErrorReason | undefined): void;
|
261 | }
|
262 | /**
|
263 | * @group Camera
|
264 | */
|
265 | export interface CameraControllerServiceMap extends ControllerServiceMap {
|
266 | microphone?: Microphone;
|
267 | speaker?: Speaker;
|
268 | cameraEventRecordingManagement?: CameraRecordingManagement;
|
269 | cameraOperatingMode?: CameraOperatingMode;
|
270 | dataStreamTransportManagement?: DataStreamTransportManagement;
|
271 | motionService?: MotionSensor;
|
272 | occupancyService?: OccupancySensor;
|
273 | doorbell?: Doorbell;
|
274 | }
|
275 | /**
|
276 | * @group Camera
|
277 | */
|
278 | export interface CameraControllerState {
|
279 | streamManagements: RTPStreamManagementState[];
|
280 | recordingManagement?: RecordingManagementState;
|
281 | }
|
282 | /**
|
283 | * @group Camera
|
284 | */
|
285 | export declare const enum CameraControllerEvents {
|
286 | /**
|
287 | * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
|
288 | * except the mute state. When you adjust the volume in the Camera view it will reset the muted state if it was set previously.
|
289 | * The value of volume has nothing to do with the volume slider in the Camera view of the Home app.
|
290 | */
|
291 | MICROPHONE_PROPERTIES_CHANGED = "microphone-change",
|
292 | /**
|
293 | * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
|
294 | * except the mute state. When you unmute the device microphone it will reset the mute state if it was set previously.
|
295 | */
|
296 | SPEAKER_PROPERTIES_CHANGED = "speaker-change"
|
297 | }
|
298 | /**
|
299 | * @group Camera
|
300 | */
|
301 | export declare interface CameraController {
|
302 | on(event: "microphone-change", listener: (muted: boolean, volume: number) => void): this;
|
303 | on(event: "speaker-change", listener: (muted: boolean, volume: number) => void): this;
|
304 | emit(event: "microphone-change", muted: boolean, volume: number): boolean;
|
305 | emit(event: "speaker-change", muted: boolean, volume: number): boolean;
|
306 | }
|
307 | /**
|
308 | * Everything needed to expose a HomeKit Camera.
|
309 | *
|
310 | * @group Camera
|
311 | */
|
312 | export declare class CameraController extends EventEmitter implements SerializableController<CameraControllerServiceMap, CameraControllerState> {
|
313 | private static readonly STREAM_MANAGEMENT;
|
314 | private stateChangeDelegate?;
|
315 | private readonly streamCount;
|
316 | private readonly delegate;
|
317 | private readonly streamingOptions;
|
318 | /**
|
319 | * **Temporary** storage for {@link CameraRecordingOptions} and {@link CameraRecordingDelegate}.
|
320 | * This property is reset to `undefined` after the CameraController was fully initialized.
|
321 | * You can still access those values via the {@link CameraController.recordingManagement}.
|
322 | */
|
323 | private recording?;
|
324 | /**
|
325 | * Temporary storage for the sensor option.
|
326 | */
|
327 | private sensorOptions?;
|
328 | private readonly legacyMode;
|
329 | /**
|
330 | * @private
|
331 | */
|
332 | streamManagements: RTPStreamManagement[];
|
333 | /**
|
334 | * The {@link RecordingManagement} which is responsible for handling HomeKit Secure Video.
|
335 | * This property is only present if recording was configured.
|
336 | */
|
337 | recordingManagement?: RecordingManagement;
|
338 | private microphoneService?;
|
339 | private speakerService?;
|
340 | private microphoneMuted;
|
341 | private microphoneVolume;
|
342 | private speakerMuted;
|
343 | private speakerVolume;
|
344 | motionService?: MotionSensor;
|
345 | private motionServiceExternallySupplied;
|
346 | occupancyService?: OccupancySensor;
|
347 | private occupancyServiceExternallySupplied;
|
348 | constructor(options: CameraControllerOptions, legacyMode?: boolean);
|
349 | /**
|
350 | * @private
|
351 | */
|
352 | controllerId(): ControllerIdentifier;
|
353 | /**
|
354 | * Call this method if you want to forcefully suspend an ongoing streaming session.
|
355 | * This would be adequate if the rtp server or media encoding encountered an unexpected error.
|
356 | *
|
357 | * @param sessionId - id of the current ongoing streaming session
|
358 | */
|
359 | forceStopStreamingSession(sessionId: SessionIdentifier): void;
|
360 | static generateSynchronisationSource(): number;
|
361 | setMicrophoneMuted(muted?: boolean): void;
|
362 | setMicrophoneVolume(volume: number): void;
|
363 | setSpeakerMuted(muted?: boolean): void;
|
364 | setSpeakerVolume(volume: number): void;
|
365 | private emitMicrophoneChange;
|
366 | private emitSpeakerChange;
|
367 | /**
|
368 | * @private
|
369 | */
|
370 | constructServices(): CameraControllerServiceMap;
|
371 | /**
|
372 | * @private
|
373 | */
|
374 | initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap;
|
375 | protected _initWithServices(serviceMap: CameraControllerServiceMap): {
|
376 | serviceMap: CameraControllerServiceMap;
|
377 | updated: boolean;
|
378 | };
|
379 | protected migrateFromDoorbell(serviceMap: ControllerServiceMap): boolean;
|
380 | protected retrieveEventTriggerOptions(): Set<EventTriggerOption>;
|
381 | /**
|
382 | * @private
|
383 | */
|
384 | configureServices(): void;
|
385 | private rtpStreamManagementDisabledThroughOperatingMode;
|
386 | /**
|
387 | * @private
|
388 | */
|
389 | handleControllerRemoved(): void;
|
390 | /**
|
391 | * @private
|
392 | */
|
393 | handleFactoryReset(): void;
|
394 | /**
|
395 | * @private
|
396 | */
|
397 | serialize(): CameraControllerState | undefined;
|
398 | /**
|
399 | * @private
|
400 | */
|
401 | deserialize(serialized: CameraControllerState): void;
|
402 | /**
|
403 | * @private
|
404 | */
|
405 | setupStateChangeDelegate(delegate?: StateChangeDelegate): void;
|
406 | /**
|
407 | * @private
|
408 | */
|
409 | handleSnapshotRequest(height: number, width: number, accessoryName?: string, reason?: ResourceRequestReason): Promise<Buffer>;
|
410 | }
|
411 | //# sourceMappingURL=CameraController.d.ts.map |
\ | No newline at end of file |