homebridge-plugin-utils 1.33.0 → 1.34.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.
@@ -10,7 +10,7 @@
10
10
  * - Automated setup and management of FFmpeg processes for HKSV event recording and livestreaming (with support for audio and video).
11
11
  * - Parsing and generation of fMP4 boxes/segments for HomeKit, including initialization and media segments.
12
12
  * - Async generator APIs for efficient, event-driven segment handling.
13
- * - Flexible error handling and timeouts for HomeKits strict realtime requirements.
13
+ * - Flexible error handling and timeouts for HomeKit's strict realtime requirements.
14
14
  * - Designed for Homebridge plugin authors or advanced users who need robust, platform-aware FFmpeg session control for HomeKit and related integrations.
15
15
  *
16
16
  * @module
@@ -20,21 +20,27 @@ import { type Nullable, type PartialWithId } from "../util.js";
20
20
  import type { FfmpegOptions } from "./options.js";
21
21
  import { FfmpegProcess } from "./process.js";
22
22
  /**
23
- * Base options for configuring an fMP4 recording or livestream session. These options aren't used directly but are inherited and used by it's descendents.
24
- *
25
- * @property audioStream - Audio stream input to use, if the input contains multiple audio streams. Defaults to `0` (the first audio stream).
26
- * @property codec - The codec for the input video stream. Valid values are `av1`, `h264`, and `hevc`. Defaults to `h264`.
27
- * @property enableAudio - Indicates whether to enable audio or not.
28
- * @property hardwareDecoding - Enable hardware-accelerated video decoding if available. Defaults to what was specified in `ffmpegOptions`.
29
- * @property hardwareTranscoding - Enable hardware-accelerated video transcoding if available. Defaults to what was specified in `ffmpegOptions`.
30
- * @property videoStream - Video stream input to use, if the input contains multiple video streams. Defaults to `0` (the first video stream).
23
+ * Base options shared by both fMP4 recording and livestream sessions.
24
+ *
25
+ * @property audioFilters - Audio filters for FFmpeg to process. These are passed as an array of filters.
26
+ * @property audioStream - Audio stream input to use, if the input contains multiple audio streams. Defaults to `0` (the first audio stream).
27
+ * @property codec - The codec for the input video stream. Valid values are `av1`, `h264`, and `hevc`. Defaults to `h264`.
28
+ * @property enableAudio - Indicates whether to enable audio or not.
29
+ * @property hardwareDecoding - Enable hardware-accelerated video decoding if available. Defaults to what was specified in `ffmpegOptions`.
30
+ * @property hardwareTranscoding - Enable hardware-accelerated video transcoding if available. Defaults to what was specified in `ffmpegOptions`.
31
+ * @property transcodeAudio - Transcode audio to AAC. This can be set to false if the audio stream is already in AAC. Defaults to `true`.
32
+ * @property videoFilters - Video filters for FFmpeg to process. These are passed as an array of filters.
33
+ * @property videoStream - Video stream input to use, if the input contains multiple video streams. Defaults to `0` (the first video stream).
31
34
  */
32
35
  export interface FMp4BaseOptions {
36
+ audioFilters: string[];
33
37
  audioStream: number;
34
38
  codec: string;
35
39
  enableAudio: boolean;
36
40
  hardwareDecoding: boolean;
37
41
  hardwareTranscoding: boolean;
42
+ transcodeAudio: boolean;
43
+ videoFilters: string[];
38
44
  videoStream: number;
39
45
  }
40
46
  /**
@@ -61,7 +67,7 @@ export interface FMp4BaseOptions {
61
67
  * url: "http://doorbird-ip/bha-api/audio.cgi"
62
68
  * };
63
69
  *
64
- * // Self-describing RTSP audio stream only URL is needed.
70
+ * // Self-describing RTSP audio stream - only URL is needed.
65
71
  * const rtspAudioInput: FMp4AudioInputConfig = {
66
72
  *
67
73
  * url: "rtsp://camera-ip/audio"
@@ -79,22 +85,16 @@ export interface FMp4AudioInputConfig {
79
85
  url: string;
80
86
  }
81
87
  /**
82
- * Options for configuring an fMP4 recording or livestream session.
88
+ * Options for configuring an fMP4 HKSV recording session.
83
89
  *
84
- * @property audioFilters - Audio filters for FFmpeg to process. These are passed as an array of filters.
85
90
  * @property fps - The video frames per second for the session.
86
91
  * @property probesize - Number of bytes to analyze for stream information.
87
92
  * @property timeshift - Timeshift offset for event-based recording (in milliseconds).
88
- * @property transcodeAudio - Transcode audio to AAC. This can be set to false if the audio stream is already in AAC. Defaults to `true`.
89
- * @property videoFilters - Video filters for FFmpeg to process. These are passed as an array of filters.
90
93
  */
91
94
  export interface FMp4RecordingOptions extends FMp4BaseOptions {
92
- audioFilters: string[];
93
95
  fps: number;
94
96
  probesize: number;
95
97
  timeshift: number;
96
- transcodeAudio: boolean;
97
- videoFilters: string[];
98
98
  }
99
99
  /**
100
100
  * Options for configuring an fMP4 livestream session.
@@ -112,94 +112,48 @@ export interface FMp4LivestreamOptions extends FMp4BaseOptions {
112
112
  url: string;
113
113
  }
114
114
  /**
115
- * FFmpeg process controller for HomeKit Secure Video (HKSV) and fMP4 livestreaming and recording.
116
- *
117
- * This class manages the lifecycle and parsing of an FFmpeg process to support HKSV and livestreaming in fMP4 format. It handles initialization segments, media segment
118
- * parsing, buffering, and HomeKit segment generation, and emits events for segment and initialization.
119
- *
120
- * @example
115
+ * Abstract base class for fMP4 FFmpeg processes. Owns the shared command line skeleton (preamble, video mapping, movflags, audio encoding, output format) and the fMP4
116
+ * box-parsing loop. Subclasses provide mode-specific pieces (input args, encoder selection, box handling) via protected hook methods, following the template method
117
+ * pattern.
121
118
  *
122
- * ```ts
123
- * // Create a new recording process for an HKSV event.
124
- * const process = new FfmpegRecordingProcess(ffmpegOptions, recordingConfig, 30, true, 5000000, 0);
125
- *
126
- * // Start the process.
127
- * process.start();
128
- *
129
- * // Iterate over generated segments.
130
- * for await(const segment of process.segmentGenerator()) {
131
- *
132
- * // Send segment to HomeKit, etc.
133
- * }
134
- *
135
- * // Stop when finished.
136
- * process.stop();
137
- * ```
138
- *
139
- * @see FfmpegOptions
119
+ * @see FfmpegRecordingProcess
120
+ * @see FfmpegLivestreamProcess
140
121
  * @see FfmpegProcess
141
122
  * @see {@link https://ffmpeg.org/ffmpeg.html | FFmpeg Documentation}
142
123
  */
143
- declare class FfmpegFMp4Process extends FfmpegProcess {
144
- private hasInitSegment;
145
- private _initSegment;
146
- private isLivestream;
124
+ declare abstract class FfmpegFMp4Process extends FfmpegProcess {
147
125
  private isLoggingErrors;
148
- isTimedOut: boolean;
149
- private recordingBuffer;
150
- segmentLength?: number;
126
+ protected readonly fMp4Options: Required<FMp4BaseOptions>;
127
+ protected readonly recordingConfig: CameraRecordingConfiguration;
151
128
  /**
152
- * Constructs a new fMP4 FFmpeg process for HKSV event recording or livestreaming.
129
+ * Constructs a new fMP4 FFmpeg process. Stores shared state and applies defaults to the base options. The command line is not assembled here...subclasses call
130
+ * `buildCommandLine()` after their own initialization to trigger the template method assembly.
153
131
  *
154
132
  * @param ffmpegOptions - FFmpeg configuration options.
155
133
  * @param recordingConfig - HomeKit recording configuration for the session.
156
- * @param isAudioActive - If `true`, enables audio stream processing.
157
- * @param fMp4Options - Configuration for the fMP4 session (fps, type, url, audio input, etc.).
134
+ * @param fMp4Options - Partial base options with defaults applied for any unset fields.
158
135
  * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
159
- *
160
- * @example
161
- *
162
- * ```ts
163
- * const process = new FfmpegFMp4Process(ffmpegOptions, recordingConfig, true, { fps: 30 });
164
- * ```
165
136
  */
166
- constructor(ffmpegOptions: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, fMp4Options?: Partial<FMp4LivestreamOptions & FMp4RecordingOptions>, isVerbose?: boolean);
137
+ constructor(ffmpegOptions: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, fMp4Options?: Partial<FMp4BaseOptions>, isVerbose?: boolean);
138
+ private _isVerbose;
139
+ protected buildCommandLine(): void;
140
+ protected abstract inputArgs(): string[];
141
+ protected abstract separateAudioInputArgs(): string[];
142
+ protected abstract audioInputIndex(): number;
143
+ protected abstract videoEncoderArgs(): string[];
144
+ protected abstract postFilterArgs(): string[];
145
+ protected abstract metadataLabel(): string;
146
+ protected abstract handleParsedBox(header: Buffer, data: Buffer, dataLength: number, type: string): void;
167
147
  /**
168
- * Prepares and configures the FFmpeg process for reading and parsing output fMP4 data.
169
- *
170
- * This method is called internally by the process lifecycle and is not typically invoked directly by consumers.
148
+ * Prepares and configures the FFmpeg process for reading and parsing output fMP4 data. The box parsing loop is shared...each complete box is dispatched to the
149
+ * subclass via handleParsedBox().
171
150
  */
172
151
  protected configureProcess(): void;
173
152
  /**
174
- * Retrieves the fMP4 initialization segment generated by FFmpeg.
175
- *
176
- * Waits until the initialization segment is available, then returns it.
177
- *
178
- * @returns A promise resolving to the initialization segment as a Buffer.
179
- *
180
- * @example
181
- *
182
- * ```ts
183
- * const initSegment = await process.getInitSegment();
184
- * ```
185
- */
186
- protected getInitSegment(): Promise<Buffer>;
187
- /**
188
- * Stops the FFmpeg process and performs cleanup, including emitting termination events for segment generators.
189
- *
190
- * This is called as part of the process shutdown sequence.
153
+ * Stops the FFmpeg process and performs cleanup. Subclasses override this to emit mode-specific events before calling super, which handles the shared teardown and
154
+ * emits the "close" event.
191
155
  */
192
156
  protected stopProcess(): void;
193
- /**
194
- * Starts the FFmpeg process, adjusting segment length for livestreams if set.
195
- *
196
- * @example
197
- *
198
- * ```ts
199
- * process.start();
200
- * ```
201
- */
202
- start(): void;
203
157
  /**
204
158
  * Stops the FFmpeg process and logs errors if specified.
205
159
  *
@@ -218,40 +172,7 @@ declare class FfmpegFMp4Process extends FfmpegProcess {
218
172
  * @param exitCode - The exit code from the FFmpeg process.
219
173
  * @param signal - The signal (if any) used to terminate the process.
220
174
  */
221
- protected logFfmpegError(exitCode: number, signal: NodeJS.Signals): void;
222
- /**
223
- * Asynchronously generates complete segments from FFmpeg output, formatted for HomeKit Secure Video.
224
- *
225
- * This async generator yields fMP4 segments as Buffers, or ends on process termination or timeout.
226
- *
227
- * @yields A Buffer containing a complete MP4 segment suitable for HomeKit.
228
- *
229
- * @example
230
- *
231
- * ```ts
232
- * for await(const segment of process.segmentGenerator()) {
233
- *
234
- * // Process each segment for HomeKit.
235
- * }
236
- * ```
237
- */
238
- segmentGenerator(): AsyncGenerator<Buffer>;
239
- /**
240
- * Returns the initialization segment as a Buffer, or null if not yet available.
241
- *
242
- * @returns The initialization segment Buffer, or `null` if not yet generated.
243
- *
244
- * @example
245
- *
246
- * ```ts
247
- * const init = process.initSegment;
248
- * if(init) {
249
- *
250
- * // Use the initialization segment.
251
- * }
252
- * ```
253
- */
254
- get initSegment(): Nullable<Buffer>;
175
+ protected logFfmpegError(exitCode: Nullable<number>, signal: Nullable<NodeJS.Signals>): void;
255
176
  }
256
177
  /**
257
178
  * Manages a HomeKit Secure Video recording FFmpeg process.
@@ -268,6 +189,14 @@ declare class FfmpegFMp4Process extends FfmpegProcess {
268
189
  * @category FFmpeg
269
190
  */
270
191
  export declare class FfmpegRecordingProcess extends FfmpegFMp4Process {
192
+ /**
193
+ * Indicates whether the recording has timed out waiting for FFmpeg output.
194
+ */
195
+ isTimedOut: boolean;
196
+ private readonly fps;
197
+ private readonly probesize;
198
+ private recordingBuffer;
199
+ private readonly timeshift;
271
200
  /**
272
201
  * Constructs a new FFmpeg recording process for HKSV events.
273
202
  *
@@ -277,6 +206,34 @@ export declare class FfmpegRecordingProcess extends FfmpegFMp4Process {
277
206
  * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
278
207
  */
279
208
  constructor(options: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, fMp4Options?: Partial<FMp4RecordingOptions>, isVerbose?: boolean);
209
+ protected inputArgs(): string[];
210
+ protected separateAudioInputArgs(): string[];
211
+ protected audioInputIndex(): number;
212
+ protected videoEncoderArgs(): string[];
213
+ protected postFilterArgs(): string[];
214
+ protected metadataLabel(): string;
215
+ protected handleParsedBox(header: Buffer, data: Buffer, dataLength: number, type: string): void;
216
+ /**
217
+ * Stops the FFmpeg process and performs cleanup, ensuring the segment generator can exit.
218
+ */
219
+ protected stopProcess(): void;
220
+ /**
221
+ * Asynchronously generates complete segments from FFmpeg output, formatted for HomeKit Secure Video.
222
+ *
223
+ * This async generator yields fMP4 segments as Buffers, or ends on process termination or timeout.
224
+ *
225
+ * @yields A Buffer containing a complete MP4 segment suitable for HomeKit.
226
+ *
227
+ * @example
228
+ *
229
+ * ```ts
230
+ * for await(const segment of process.segmentGenerator()) {
231
+ *
232
+ * // Process each segment for HomeKit.
233
+ * }
234
+ * ```
235
+ */
236
+ segmentGenerator(): AsyncGenerator<Buffer>;
280
237
  }
281
238
  /**
282
239
  * Manages a HomeKit livestream FFmpeg process for generating fMP4 segments.
@@ -295,6 +252,15 @@ export declare class FfmpegRecordingProcess extends FfmpegFMp4Process {
295
252
  * @category FFmpeg
296
253
  */
297
254
  export declare class FfmpegLivestreamProcess extends FfmpegFMp4Process {
255
+ /**
256
+ * Optional override for the fMP4 fragment duration, in milliseconds. When set, the `-frag_duration` argument is updated before starting the FFmpeg process.
257
+ */
258
+ segmentLength?: number;
259
+ private _hasAudioInput;
260
+ private _initSegment;
261
+ private _initSegmentParts;
262
+ private hasInitSegment;
263
+ private readonly livestreamOptions;
298
264
  /**
299
265
  * Constructs a new FFmpeg livestream process.
300
266
  *
@@ -304,6 +270,23 @@ export declare class FfmpegLivestreamProcess extends FfmpegFMp4Process {
304
270
  * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
305
271
  */
306
272
  constructor(options: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, livestreamOptions: PartialWithId<FMp4LivestreamOptions, "url">, isVerbose?: boolean);
273
+ protected inputArgs(): string[];
274
+ protected separateAudioInputArgs(): string[];
275
+ protected audioInputIndex(): number;
276
+ protected videoEncoderArgs(): string[];
277
+ protected postFilterArgs(): string[];
278
+ protected metadataLabel(): string;
279
+ protected handleParsedBox(header: Buffer, data: Buffer, _dataLength: number, type: string): void;
280
+ /**
281
+ * Starts the FFmpeg process, adjusting the fragment duration if segmentLength has been set.
282
+ *
283
+ * @example
284
+ *
285
+ * ```ts
286
+ * process.start();
287
+ * ```
288
+ */
289
+ start(): void;
307
290
  /**
308
291
  * Gets the fMP4 initialization segment generated by FFmpeg for the livestream.
309
292
  *
@@ -316,5 +299,21 @@ export declare class FfmpegLivestreamProcess extends FfmpegFMp4Process {
316
299
  * ```
317
300
  */
318
301
  getInitSegment(): Promise<Buffer>;
302
+ /**
303
+ * Returns the initialization segment as a Buffer, or null if not yet available.
304
+ *
305
+ * @returns The initialization segment Buffer, or `null` if not yet generated.
306
+ *
307
+ * @example
308
+ *
309
+ * ```ts
310
+ * const init = process.initSegment;
311
+ * if(init) {
312
+ *
313
+ * // Use the initialization segment.
314
+ * }
315
+ * ```
316
+ */
317
+ get initSegment(): Nullable<Buffer>;
319
318
  }
320
319
  export {};