21 kBTypeScriptView Raw
1import { EventEmitter } from "events";
2import { SessionIdentifier } from "../../types";
3import { CameraRecordingConfiguration, CameraRecordingOptions, CameraStreamingOptions, EventTriggerOption, PrepareStreamRequest, PrepareStreamResponse, RecordingManagement, RecordingManagementState, RecordingPacket, RTPStreamManagement, RTPStreamManagementState, SnapshotRequest, StreamingRequest } from "../camera";
4import { HDSProtocolSpecificErrorReason } from "../datastream";
5import { CameraOperatingMode, CameraRecordingManagement, DataStreamTransportManagement, Doorbell, Microphone, MotionSensor, OccupancySensor, Speaker } from "../definitions";
6import { HAPStatus } from "../HAPServer";
7import { Service } from "../Service";
8import { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from "./Controller";
9/**
10 * @group Camera
11 */
12export 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 */
85export type SnapshotRequestCallback = (error?: Error | HAPStatus, buffer?: Buffer) => void;
86/**
87 * @group Camera
88 */
89export type PrepareStreamCallback = (error?: Error, response?: PrepareStreamResponse) => void;
90/**
91 * @group Camera
92 */
93export type StreamRequestCallback = (error?: Error) => void;
94/**
95 * @group Camera
96 */
97export 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 */
112export 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 */
157export 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 */
265export 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 */
278export interface CameraControllerState {
279 streamManagements: RTPStreamManagementState[];
280 recordingManagement?: RecordingManagementState;
281}
282/**
283 * @group Camera
284 */
285export 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 */
301export 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 */
312export 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