homebridge-plugin-utils 1.35.0 → 2.1.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.
Files changed (204) hide show
  1. package/README.md +130 -2
  2. package/build/eslint-plugin/README.md +164 -0
  3. package/build/eslint-plugin/config.mjs +308 -0
  4. package/build/eslint-plugin/index.mjs +7 -0
  5. package/build/eslint-plugin/plugin.mjs +46 -0
  6. package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
  7. package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
  8. package/build/eslint-plugin/rules/comment-style.mjs +190 -0
  9. package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
  10. package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
  11. package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
  12. package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
  13. package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
  14. package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
  15. package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
  16. package/build/eslint-plugin/test-setup.mjs +21 -0
  17. package/build/fs-ops.mjs +184 -0
  18. package/build/tsconfig.json +18 -3
  19. package/dist/backpressure.d.ts +94 -45
  20. package/dist/backpressure.js +229 -93
  21. package/dist/backpressure.js.map +1 -1
  22. package/dist/cli/index.d.ts +166 -0
  23. package/dist/cli/index.js +551 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/clock-double.d.ts +93 -0
  26. package/dist/clock-double.js +141 -0
  27. package/dist/clock-double.js.map +1 -0
  28. package/dist/clock.d.ts +39 -0
  29. package/dist/clock.js +34 -0
  30. package/dist/clock.js.map +1 -0
  31. package/dist/disposable-stack.d.ts +59 -0
  32. package/dist/disposable-stack.js +155 -0
  33. package/dist/disposable-stack.js.map +1 -0
  34. package/dist/docChrome.d.ts +260 -0
  35. package/dist/docChrome.js +361 -0
  36. package/dist/docChrome.js.map +1 -0
  37. package/dist/eslint-plugin/config.d.mts +193 -0
  38. package/dist/eslint-plugin/index.d.mts +2 -0
  39. package/dist/eslint-plugin/plugin.d.mts +87 -0
  40. package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
  41. package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
  42. package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
  43. package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
  44. package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
  45. package/dist/featureOptions-docs.d.ts +96 -0
  46. package/dist/featureOptions-docs.js +193 -0
  47. package/dist/featureOptions-docs.js.map +1 -0
  48. package/dist/featureOptions.d.ts +674 -0
  49. package/dist/featureOptions.js +870 -0
  50. package/dist/featureOptions.js.map +1 -0
  51. package/dist/ffmpeg/codecs.d.ts +256 -72
  52. package/dist/ffmpeg/codecs.js +477 -262
  53. package/dist/ffmpeg/codecs.js.map +1 -1
  54. package/dist/ffmpeg/dgram-util.d.ts +46 -0
  55. package/dist/ffmpeg/dgram-util.js +38 -0
  56. package/dist/ffmpeg/dgram-util.js.map +1 -0
  57. package/dist/ffmpeg/exec.d.ts +83 -64
  58. package/dist/ffmpeg/exec.js +77 -86
  59. package/dist/ffmpeg/exec.js.map +1 -1
  60. package/dist/ffmpeg/fmp4.d.ts +49 -2
  61. package/dist/ffmpeg/fmp4.js +47 -11
  62. package/dist/ffmpeg/fmp4.js.map +1 -1
  63. package/dist/ffmpeg/hap-enums.d.ts +214 -0
  64. package/dist/ffmpeg/hap-enums.js +92 -0
  65. package/dist/ffmpeg/hap-enums.js.map +1 -0
  66. package/dist/ffmpeg/index.d.ts +16 -9
  67. package/dist/ffmpeg/index.js +6 -0
  68. package/dist/ffmpeg/index.js.map +1 -1
  69. package/dist/ffmpeg/mp4-assembler.d.ts +161 -0
  70. package/dist/ffmpeg/mp4-assembler.js +424 -0
  71. package/dist/ffmpeg/mp4-assembler.js.map +1 -0
  72. package/dist/ffmpeg/mp4-parser.d.ts +94 -0
  73. package/dist/ffmpeg/mp4-parser.js +130 -0
  74. package/dist/ffmpeg/mp4-parser.js.map +1 -0
  75. package/dist/ffmpeg/options.d.ts +62 -149
  76. package/dist/ffmpeg/options.js +608 -499
  77. package/dist/ffmpeg/options.js.map +1 -1
  78. package/dist/ffmpeg/process.d.ts +142 -96
  79. package/dist/ffmpeg/process.js +406 -278
  80. package/dist/ffmpeg/process.js.map +1 -1
  81. package/dist/ffmpeg/record.d.ts +325 -186
  82. package/dist/ffmpeg/record.js +418 -565
  83. package/dist/ffmpeg/record.js.map +1 -1
  84. package/dist/ffmpeg/recording-process-double.d.ts +157 -0
  85. package/dist/ffmpeg/recording-process-double.js +190 -0
  86. package/dist/ffmpeg/recording-process-double.js.map +1 -0
  87. package/dist/ffmpeg/rtp-parser.d.ts +70 -0
  88. package/dist/ffmpeg/rtp-parser.js +77 -0
  89. package/dist/ffmpeg/rtp-parser.js.map +1 -0
  90. package/dist/ffmpeg/rtp.d.ts +198 -141
  91. package/dist/ffmpeg/rtp.js +474 -251
  92. package/dist/ffmpeg/rtp.js.map +1 -1
  93. package/dist/ffmpeg/settings.d.ts +5 -2
  94. package/dist/ffmpeg/settings.js +20 -5
  95. package/dist/ffmpeg/settings.js.map +1 -1
  96. package/dist/ffmpeg/stream.d.ts +57 -107
  97. package/dist/ffmpeg/stream.js +121 -150
  98. package/dist/ffmpeg/stream.js.map +1 -1
  99. package/dist/formatters.d.ts +106 -0
  100. package/dist/formatters.js +174 -0
  101. package/dist/formatters.js.map +1 -0
  102. package/dist/homebridge-enums.d.ts +30 -0
  103. package/dist/homebridge-enums.js +17 -0
  104. package/dist/homebridge-enums.js.map +1 -0
  105. package/dist/index.d.ts +13 -6
  106. package/dist/index.js +8 -2
  107. package/dist/index.js.map +1 -1
  108. package/dist/logclient/auth.d.ts +114 -0
  109. package/dist/logclient/auth.js +199 -0
  110. package/dist/logclient/auth.js.map +1 -0
  111. package/dist/logclient/cli-run.d.ts +76 -0
  112. package/dist/logclient/cli-run.js +639 -0
  113. package/dist/logclient/cli-run.js.map +1 -0
  114. package/dist/logclient/cli.d.ts +3 -0
  115. package/dist/logclient/cli.js +97 -0
  116. package/dist/logclient/cli.js.map +1 -0
  117. package/dist/logclient/client.d.ts +145 -0
  118. package/dist/logclient/client.js +600 -0
  119. package/dist/logclient/client.js.map +1 -0
  120. package/dist/logclient/config.d.ts +173 -0
  121. package/dist/logclient/config.js +199 -0
  122. package/dist/logclient/config.js.map +1 -0
  123. package/dist/logclient/endpoints.d.ts +54 -0
  124. package/dist/logclient/endpoints.js +73 -0
  125. package/dist/logclient/endpoints.js.map +1 -0
  126. package/dist/logclient/filter.d.ts +45 -0
  127. package/dist/logclient/filter.js +51 -0
  128. package/dist/logclient/filter.js.map +1 -0
  129. package/dist/logclient/frame.d.ts +93 -0
  130. package/dist/logclient/frame.js +203 -0
  131. package/dist/logclient/frame.js.map +1 -0
  132. package/dist/logclient/index.d.ts +31 -0
  133. package/dist/logclient/index.js +12 -0
  134. package/dist/logclient/index.js.map +1 -0
  135. package/dist/logclient/parser.d.ts +211 -0
  136. package/dist/logclient/parser.js +393 -0
  137. package/dist/logclient/parser.js.map +1 -0
  138. package/dist/logclient/rest.d.ts +41 -0
  139. package/dist/logclient/rest.js +111 -0
  140. package/dist/logclient/rest.js.map +1 -0
  141. package/dist/logclient/settings.d.ts +15 -0
  142. package/dist/logclient/settings.js +64 -0
  143. package/dist/logclient/settings.js.map +1 -0
  144. package/dist/logclient/socket-double.d.ts +201 -0
  145. package/dist/logclient/socket-double.js +384 -0
  146. package/dist/logclient/socket-double.js.map +1 -0
  147. package/dist/logclient/socket.d.ts +257 -0
  148. package/dist/logclient/socket.js +620 -0
  149. package/dist/logclient/socket.js.map +1 -0
  150. package/dist/logclient/stitch.d.ts +83 -0
  151. package/dist/logclient/stitch.js +146 -0
  152. package/dist/logclient/stitch.js.map +1 -0
  153. package/dist/logclient/time-expression.d.ts +42 -0
  154. package/dist/logclient/time-expression.js +181 -0
  155. package/dist/logclient/time-expression.js.map +1 -0
  156. package/dist/logclient/time-window.d.ts +38 -0
  157. package/dist/logclient/time-window.js +53 -0
  158. package/dist/logclient/time-window.js.map +1 -0
  159. package/dist/logclient/types.d.ts +107 -0
  160. package/dist/logclient/types.js +6 -0
  161. package/dist/logclient/types.js.map +1 -0
  162. package/dist/mqttClient.d.ts +287 -0
  163. package/dist/mqttClient.js +433 -0
  164. package/dist/mqttClient.js.map +1 -0
  165. package/dist/service.d.ts +64 -15
  166. package/dist/service.js +93 -66
  167. package/dist/service.js.map +1 -1
  168. package/dist/ui/featureOptions.js +870 -0
  169. package/dist/ui/featureOptions.js.map +1 -0
  170. package/dist/ui/formatters.js +174 -0
  171. package/dist/ui/formatters.js.map +1 -0
  172. package/dist/ui/pluginConfigSession.mjs +141 -0
  173. package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
  174. package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
  175. package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
  176. package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
  177. package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
  178. package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
  179. package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
  180. package/dist/ui/webUi-featureOptions/state.mjs +319 -0
  181. package/dist/ui/webUi-featureOptions/store.mjs +181 -0
  182. package/dist/ui/webUi-featureOptions/utils.mjs +213 -0
  183. package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
  184. package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
  185. package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
  186. package/dist/ui/webUi-featureOptions/views/nav.mjs +341 -0
  187. package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
  188. package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
  189. package/dist/ui/webUi-featureOptions.mjs +702 -0
  190. package/dist/ui/webUi.mjs +225 -88
  191. package/dist/util.d.ts +679 -45
  192. package/dist/util.js +830 -77
  193. package/dist/util.js.map +1 -1
  194. package/package.json +33 -15
  195. package/build/eslint-rules.mjs +0 -511
  196. package/dist/featureoptions.d.ts +0 -264
  197. package/dist/featureoptions.js +0 -480
  198. package/dist/featureoptions.js.map +0 -1
  199. package/dist/mqttclient.d.ts +0 -178
  200. package/dist/mqttclient.js +0 -310
  201. package/dist/mqttclient.js.map +0 -1
  202. package/dist/ui/featureoptions.js +0 -480
  203. package/dist/ui/featureoptions.js.map +0 -1
  204. package/dist/ui/webUi-featureoptions.mjs +0 -3765
@@ -1,36 +1,67 @@
1
1
  /**
2
- * FFmpeg process management for HomeKit Secure Video (HKSV) events and fMP4 livestreaming.
2
+ * fMP4 FFmpeg processes for HomeKit Secure Video (HKSV) events and livestreaming.
3
3
  *
4
- * This module defines classes for orchestrating FFmpeg processes that produce fMP4 segments suitable for HomeKit Secure Video and realtime livestreaming scenarios. It
5
- * handles process lifecycle, segment buffering, initialization segment detection, and streaming event generation, abstracting away the complexity of interacting directly
6
- * with FFmpeg for these workflows.
4
+ * This module defines an abstract base {@link FfmpegFMp4Process} and its concrete fMP4-mode specializations,
5
+ * {@link FfmpegRecordingProcess} for HKSV event recording (stdin pipe input, transcoded output) and
6
+ * {@link FfmpegLivestreamProcess} for fMP4 livestreaming (RTSP input, codec copy). The base exists **solely to centralize
7
+ * composition wiring** - it owns the internal {@link Mp4SegmentAssembler}, delegates `getInitSegment` / `segments` to it,
8
+ * and propagates assembler teardown reasons up to the process. It contains no pipeline logic and no command-line assembly;
9
+ * the byte-to-segment pipeline lives in the assembler so this base stays composition-only.
7
10
  *
8
- * Key features:
11
+ * Both concrete subclasses:
9
12
  *
10
- * - Automated setup and management of FFmpeg processes for HKSV event recording and livestreaming (with support for audio and video).
11
- * - Parsing and generation of fMP4 boxes/segments for HomeKit, including initialization and media segments.
12
- * - Async generator APIs for efficient, event-driven segment handling.
13
- * - Flexible error handling and timeouts for HomeKit's strict realtime requirements.
14
- * - Designed for Homebridge plugin authors or advanced users who need robust, platform-aware FFmpeg session control for HomeKit and related integrations.
13
+ * - Spawn FFmpeg on construction and expose the inherited `signal`, `ready`, `exited`, `stdin`, `stderr`, `stderrLog`,
14
+ * `abort()`, and `[Symbol.asyncDispose]`.
15
+ * - Narrow the inherited public `stdout` to `never`, because the assembler owns the stream and a concurrent external reader
16
+ * would race.
17
+ * - Build their FFmpeg arg vector via the pure helper `buildFMp4CommandLine`, which takes fully-resolved options plus
18
+ * mode-specific hook values and returns the vector. Neither subclass calls `super()` until the arg vector is finalized,
19
+ * so the constructor-before-super contract is respected.
20
+ *
21
+ * The command-line hook values that differ per mode:
22
+ *
23
+ * | Hook | Recording | Livestream |
24
+ * |--------------------------|--------------------------------------------|-----------------------------------------|
25
+ * | `inputArgs` | `-i pipe:0` + probesize + `-ss` | `-i <url>` + `-rtsp_transport tcp` |
26
+ * | `separateAudioInputArgs` | `[]` | Separate audio URL when configured |
27
+ * | `audioInputIndex` | `0` | `0` or `1` (if separate audio) |
28
+ * | `audioTarget` | `recordingConfig.audioCodec` (transcoding) | `init.audio` when provided |
29
+ * | `videoEncoderArgs` | `options.recordEncoder(...)` | `-codec:v copy` |
30
+ * | `postFilterArgs` | `[]` | `-frag_duration <segmentLength * 1000>` |
31
+ * | `metadataLabel` | `"HKSV Event"` | `"Livestream Buffer"` |
32
+ *
33
+ * The shared pipeline primitive ({@link Mp4SegmentAssembler}) also means this module avoids template-method coupling between
34
+ * the base and the concrete subclasses: no abstract hook methods, no mode-specific state on the base. The base is pure lifecycle
35
+ * composition; the subclasses are pure args builders plus (for recording) a known-error message substitution.
15
36
  *
16
37
  * @module
17
38
  */
18
- import { type CameraRecordingConfiguration } from "homebridge";
19
- import { type Nullable, type PartialWithId } from "../util.js";
20
- import type { FfmpegOptions } from "./options.js";
21
- import { FfmpegProcess } from "./process.js";
39
+ import { AudioRecordingCodecType, AudioRecordingSamplerate } from "./hap-enums.ts";
40
+ import type { HbpuAbortError, PartialWithId } from "../util.ts";
41
+ import type { CameraRecordingConfiguration } from "homebridge";
42
+ import type { FfmpegOptions } from "./options.ts";
43
+ import { FfmpegProcess } from "./process.ts";
44
+ import type { FfmpegProcessInit } from "./process.ts";
45
+ import type { Mp4Segment } from "./mp4-assembler.ts";
46
+ import type { Writable } from "node:stream";
22
47
  /**
23
48
  * Base options shared by both fMP4 recording and livestream sessions.
24
49
  *
25
- * @property audioFilters - Audio filters for FFmpeg to process. These are passed as an array of filters.
50
+ * @property audioFilters - Audio filters for FFmpeg to process. These are passed as an array of filters. Recording-only: the livestream builder ignores this
51
+ * field, driving its audio-filter decision from the `audio` target instead.
26
52
  * @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`.
53
+ * @property codec - The codec for the input video stream. Valid values are `av1`, `h264`, and `hevc` (`h265` is accepted as an alias for `hevc`).
54
+ * Defaults to `h264`.
28
55
  * @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`.
56
+ * @property hardwareDecoding - Enable hardware-accelerated video decoding if available. Defaults to what was specified in `ffmpegOptions` when FFmpeg is at least
57
+ * 8.x; on an older FFmpeg the default is always `false` regardless of what `ffmpegOptions` specifies.
30
58
  * @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`.
59
+ * @property transcodeAudio - Transcode audio to AAC. This can be set to false if the audio stream is already in AAC. Defaults to `true`. Recording-only: the
60
+ * livestream builder ignores this field, driving its transcode decision from the `audio` target instead.
32
61
  * @property videoFilters - Video filters for FFmpeg to process. These are passed as an array of filters.
33
62
  * @property videoStream - Video stream input to use, if the input contains multiple video streams. Defaults to `0` (the first video stream).
63
+ *
64
+ * @category FFmpeg
34
65
  */
35
66
  export interface FMp4BaseOptions {
36
67
  audioFilters: string[];
@@ -56,24 +87,6 @@ export interface FMp4BaseOptions {
56
87
  * @property sampleRate - Optional. Audio sample rate in Hz (e.g., `8000`). Used when `format` is set. Defaults to `8000`.
57
88
  * @property url - The URL of the audio input source.
58
89
  *
59
- * @example
60
- *
61
- * ```ts
62
- * // Raw audio from a DoorBird audio.cgi endpoint.
63
- * const rawAudioInput: FMp4AudioInputConfig = {
64
- *
65
- * format: "mulaw",
66
- * sampleRate: 8000,
67
- * url: "http://doorbird-ip/bha-api/audio.cgi"
68
- * };
69
- *
70
- * // Self-describing RTSP audio stream - only URL is needed.
71
- * const rtspAudioInput: FMp4AudioInputConfig = {
72
- *
73
- * url: "rtsp://camera-ip/audio"
74
- * };
75
- * ```
76
- *
77
90
  * @see FMp4LivestreamOptions
78
91
  *
79
92
  * @category FFmpeg
@@ -84,12 +97,33 @@ export interface FMp4AudioInputConfig {
84
97
  sampleRate?: number;
85
98
  url: string;
86
99
  }
100
+ /**
101
+ * The resolved audio-encode target for fMP4 production. Its presence on an fMP4 command line is the single signal to transcode the audio stream to this target; its
102
+ * absence means the already-encoded audio is copied through untouched. Any audio filters are carried inside the target because filtering requires transcoding - a filter
103
+ * without a transcode is unrepresentable by construction, so the filters-require-transcoding rule holds declaratively rather than through a runtime override.
104
+ *
105
+ * @property channels - Optional. Number of output audio channels. Defaults to `1` when omitted.
106
+ * @property codec - The AAC codec variant to encode to (low-complexity or enhanced low-delay).
107
+ * @property filters - Optional. Audio filters applied ahead of the encoder. Supplying filters is what makes the transcode carry them; an empty or omitted list
108
+ * transcodes without filtering.
109
+ * @property samplerate - The output audio sample rate.
110
+ *
111
+ * @category FFmpeg
112
+ */
113
+ export interface FMp4AudioTarget {
114
+ channels?: number;
115
+ codec: AudioRecordingCodecType;
116
+ filters?: string[];
117
+ samplerate: AudioRecordingSamplerate;
118
+ }
87
119
  /**
88
120
  * Options for configuring an fMP4 HKSV recording session.
89
121
  *
90
- * @property fps - The video frames per second for the session.
91
- * @property probesize - Number of bytes to analyze for stream information.
92
- * @property timeshift - Timeshift offset for event-based recording (in milliseconds).
122
+ * @property fps - The video frames per second for the session. Defaults to 30.
123
+ * @property probesize - Number of bytes to analyze for stream information. Defaults to 5,000,000 bytes (mirrors FFmpeg's own default probesize).
124
+ * @property timeshift - Timeshift offset for event-based recording (in milliseconds). Defaults to 0.
125
+ *
126
+ * @category FFmpeg
93
127
  */
94
128
  export interface FMp4RecordingOptions extends FMp4BaseOptions {
95
129
  fps: number;
@@ -112,208 +146,313 @@ export interface FMp4LivestreamOptions extends FMp4BaseOptions {
112
146
  url: string;
113
147
  }
114
148
  /**
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.
149
+ * Construction-time options for {@link FfmpegRecordingProcess}.
118
150
  *
119
- * @see FfmpegRecordingProcess
120
- * @see FfmpegLivestreamProcess
151
+ * @property recording - Optional. fMP4 recording options. Every field defaults when omitted; the interface surface matches {@link FMp4RecordingOptions} but with
152
+ * all fields optional.
153
+ * @property recordingConfig - The HomeKit recording configuration (resolution, codec profile, audio codec, sample rate, channels) produced by the HKSV delegate.
154
+ * @property verbose - Optional. When `true`, FFmpeg is invoked with verbose logging (`-loglevel level+verbose`) regardless of the global
155
+ * `codecSupport.verbose` flag. Defaults to `false`.
156
+ *
157
+ * @remarks Supplying `args` (inherited from {@link FfmpegProcessInit}) is an advanced escape hatch that replaces the auto-built command line entirely. When `args` is
158
+ * present, the mode-specific config fields (`recording`, `verbose`) do not participate in command-line assembly - they become no-ops. Typical callers omit `args` and
159
+ * let the class build the command line from the recording configuration.
160
+ *
161
+ * @see FfmpegProcessInit
162
+ * @see FMp4RecordingOptions
163
+ *
164
+ * @category FFmpeg
165
+ */
166
+ export interface FfmpegRecordingInit extends FfmpegProcessInit {
167
+ recording?: Partial<FMp4RecordingOptions>;
168
+ recordingConfig: CameraRecordingConfiguration;
169
+ verbose?: boolean;
170
+ }
171
+ /**
172
+ * Construction-time options for {@link FfmpegLivestreamProcess}.
173
+ *
174
+ * @property audio - Optional. The resolved audio-encode target. When provided, the audio stream is transcoded to it (with any filters it carries); when
175
+ * omitted, the already-encoded audio is copied through untouched. This is the livestream path's sole audio-filter source.
176
+ * @property livestream - Livestream source configuration. `url` is required; other {@link FMp4BaseOptions} fields are optional and default when omitted.
177
+ * @property segmentLength - Optional. fMP4 fragment duration in milliseconds, applied to `-frag_duration` at construction time. Defaults to 1000 ms (1 second).
178
+ * @property verbose - Optional. When `true`, FFmpeg is invoked with verbose logging (`-loglevel level+verbose`) regardless of the global
179
+ * `codecSupport.verbose` flag. Defaults to `false`.
180
+ *
181
+ * @remarks Supplying `args` (inherited from {@link FfmpegProcessInit}) is an advanced escape hatch that replaces the auto-built command line entirely. When `args` is
182
+ * present, the mode-specific config fields (`audio`, `livestream`, `segmentLength`, `verbose`) do not participate in command-line assembly - they become no-ops. Typical
183
+ * callers omit `args` and let the class build the command line from `livestream` + `audio`.
184
+ *
185
+ * @see FfmpegProcessInit
186
+ * @see FMp4AudioTarget
187
+ * @see FMp4LivestreamOptions
188
+ *
189
+ * @category FFmpeg
190
+ */
191
+ export interface FfmpegLivestreamInit extends FfmpegProcessInit {
192
+ audio?: FMp4AudioTarget;
193
+ livestream: PartialWithId<FMp4LivestreamOptions, "url">;
194
+ segmentLength?: number;
195
+ verbose?: boolean;
196
+ }
197
+ /**
198
+ * Abstract base for FFmpeg processes that produce fragmented MP4 segments on their stdout. Owns the composition wiring between the process's stdout and an internal
199
+ * {@link Mp4SegmentAssembler}, plus the bridge that propagates assembler teardown to the process when the assembler aborts for reasons the process's own exit handler
200
+ * cannot discover on its own (watchdog timeout, source stream error).
201
+ *
202
+ * This base deliberately contains **no pipeline logic** and **no command-line assembly**. The byte-to-segment pipeline lives in {@link Mp4SegmentAssembler}, and each
203
+ * concrete subclass builds its own FFmpeg arg vector. The base exists solely to consolidate the composition shape - internal assembler field, the delegating public
204
+ * methods, and the bridge registration - that would otherwise duplicate across every fMP4 subclass. The constructor takes only `segmentTimeout` as a mode-specific knob
205
+ * and holds no mode-specific state, so the base avoids template-method coupling with its subclasses.
206
+ *
207
+ * Subclasses must call `super(options, init, segmentTimeout?)` from their constructor, having already folded their subclass-specific init into a base-compatible
208
+ * {@link FfmpegProcessInit} (typically by spreading their own init and setting `args` to their built command line).
209
+ *
210
+ * @see Mp4SegmentAssembler
121
211
  * @see FfmpegProcess
122
- * @see {@link https://ffmpeg.org/ffmpeg.html | FFmpeg Documentation}
212
+ *
213
+ * @category FFmpeg
123
214
  */
124
- declare abstract class FfmpegFMp4Process extends FfmpegProcess {
125
- private isLoggingErrors;
126
- protected readonly fMp4Options: Required<FMp4BaseOptions>;
127
- protected readonly recordingConfig: CameraRecordingConfiguration;
215
+ export declare abstract class FfmpegFMp4Process extends FfmpegProcess {
216
+ #private;
128
217
  /**
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.
131
- *
132
- * @param ffmpegOptions - FFmpeg configuration options.
133
- * @param recordingConfig - HomeKit recording configuration for the session.
134
- * @param fMp4Options - Partial base options with defaults applied for any unset fields.
135
- * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
218
+ * stdout is consumed internally by the assembler. The public type is narrowed to `never` so TypeScript callers cannot accidentally attach a concurrent reader.
136
219
  */
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: number): void;
220
+ readonly stdout: never;
147
221
  /**
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().
222
+ * Construct and spawn a new fMP4 segment-producing process.
223
+ *
224
+ * @param options - Shared {@link FfmpegOptions} configuration (codec support, logger, debug flag, name).
225
+ * @param init - Base-class init (FfmpegProcessInit) plus the finalized `args` built by the subclass.
226
+ * @param segmentTimeout - Optional inter-segment watchdog in milliseconds. When set, the assembler aborts with
227
+ * `HbpuAbortError("timeout")` if no media segment arrives within the window, and the bridge
228
+ * propagates the timeout up to the process. Omit for subclasses that tolerate quiet periods
229
+ * (e.g., livestreams).
150
230
  */
151
- protected configureProcess(): void;
231
+ protected constructor(options: FfmpegOptions, init: FfmpegProcessInit, segmentTimeout?: number);
152
232
  /**
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.
233
+ * Resolve with the fMP4 initialization segment (typically `ftyp` + `moov`) once it appears on stdout. Rejects with `this.signal.reason` if the process aborts before
234
+ * the initialization segment completes.
235
+ *
236
+ * @returns A promise resolving to the initialization segment bytes.
155
237
  */
156
- protected stopProcess(): void;
238
+ getInitSegment(): Promise<Buffer>;
157
239
  /**
158
- * Stops the FFmpeg process and logs errors if specified.
240
+ * Async generator yielding each completed media segment (concatenated `moof` + `mdat` pair) as a single Buffer. Terminates cleanly when the process or the caller's
241
+ * signal aborts, or when the underlying stdout ends.
159
242
  *
160
- * @param logErrors - If `true`, logs FFmpeg errors. Defaults to the internal process logging state.
243
+ * @param init - Optional init options. `signal` composes with the process's own signal; aborting it terminates only this generator call, not the process.
161
244
  *
162
- * @example
245
+ * @returns An async generator yielding media segment buffers in stream order.
246
+ */
247
+ segments(init?: {
248
+ signal?: AbortSignal;
249
+ }): AsyncGenerator<Buffer>;
250
+ /**
251
+ * The number of assembled media segments buffered in the internal assembler but not yet pulled through {@link FfmpegFMp4Process.segments} - the consumer's catch-up
252
+ * reserve when the FFmpeg source stalls. Delegates to {@link Mp4SegmentAssembler.bufferedSegments}.
163
253
  *
164
- * ```ts
165
- * process.stop();
166
- * ```
254
+ * @returns The buffered-segment depth.
167
255
  */
168
- stop(logErrors?: boolean): void;
256
+ get bufferedSegments(): number;
169
257
  /**
170
- * Logs errors from FFmpeg process execution, handling known benign HKSV stream errors gracefully.
258
+ * Async generator yielding the whole segment stream as a kind-tagged sequence: one {@link Mp4Segment} of kind `"init"` carrying the initialization bytes, then one of
259
+ * kind `"media"` per completed media fragment. Delegates to {@link Mp4SegmentAssembler.stream}; it is a third view over the same pipeline as
260
+ * {@link FfmpegFMp4Process.getInitSegment} / {@link FfmpegFMp4Process.segments} and shares their single-consumer contract - use one view or the other, never both.
171
261
  *
172
- * @param exitCode - The exit code from the FFmpeg process.
173
- * @param signal - The signal (if any) used to terminate the process.
262
+ * @param init - Optional init options. `signal` composes with the process's own signal; aborting it terminates only this generator call, not the process.
263
+ *
264
+ * @returns An async generator yielding one `"init"` segment followed by `"media"` segments in stream order.
174
265
  */
175
- protected logFfmpegError(exitCode: Nullable<number>, signal: Nullable<NodeJS.Signals>): void;
266
+ stream(init?: {
267
+ signal?: AbortSignal;
268
+ }): AsyncGenerator<Mp4Segment>;
176
269
  }
177
270
  /**
178
- * Manages a HomeKit Secure Video recording FFmpeg process.
271
+ * The minimal surface a recording consumer reads off a recording process. This is the product half of the recording dependency-inversion seam: an HKSV recording
272
+ * delegate depends on this narrow interface rather than the concrete {@link FfmpegRecordingProcess}, so a test (or any alternative segment source) can substitute a
273
+ * fake without dragging FFmpeg into the consumer's dependency graph. The interface is type-only, so importing it costs a consumer nothing at runtime.
179
274
  *
180
- * @example
275
+ * Every member here is defined on {@link FfmpegFMp4Process} (`getInitSegment`, `segments`, `stream`, `bufferedSegments`) or inherited from {@link FfmpegProcess}
276
+ * (`abort`, `isTimedOut`, `signal`, `stderrLog`, `stdin`), so the real {@link FfmpegRecordingProcess} satisfies it by inheritance and carries only an `implements`
277
+ * annotation - zero runtime behavior change. This is deliberately the consumer's minimal surface, not the class's full surface: `ready`, `exited`, `stdout`,
278
+ * `aborted`, `hasError`, and `[Symbol.asyncDispose]` are NOT here because the recording consumer does not read them.
181
279
  *
182
- * ```ts
183
- * const process = new FfmpegRecordingProcess(ffmpegOptions, recordingConfig, 30, true, 5000000, 0);
184
- * process.start();
185
- * ```
186
- *
187
- * @see FfmpegFMp4Process
280
+ * @see FfmpegRecordingProcess
281
+ * @see RecordingProcessFactory
188
282
  *
189
283
  * @category FFmpeg
190
284
  */
191
- export declare class FfmpegRecordingProcess extends FfmpegFMp4Process {
285
+ export interface RecordingProcess {
192
286
  /**
193
- * Indicates whether the recording has timed out waiting for FFmpeg output.
287
+ * Abort the recording process and tear it down. Defaults to `HbpuAbortError("shutdown")` when no reason is supplied; explicit reasons pass through unchanged.
288
+ *
289
+ * @param reason - Optional abort reason. Typically an {@link HbpuAbortError}.
194
290
  */
195
- isTimedOut: boolean;
196
- private readonly fps;
197
- private readonly probesize;
198
- private recordingBuffer;
199
- private readonly timeshift;
291
+ abort(reason?: unknown): void;
200
292
  /**
201
- * Constructs a new FFmpeg recording process for HKSV events.
293
+ * The number of assembled media segments buffered but not yet pulled through {@link RecordingProcess.segments} - the consumer's catch-up reserve when the source
294
+ * stalls.
295
+ */
296
+ readonly bufferedSegments: number;
297
+ /**
298
+ * Resolve with the fMP4 initialization segment (typically `ftyp` + `moov`). Rejects with `signal.reason` if the process aborts before the initialization segment
299
+ * completes.
202
300
  *
203
- * @param options - FFmpeg configuration options.
204
- * @param recordingConfig - HomeKit recording configuration for the session.
205
- * @param fMp4Options - fMP4 recording options.
206
- * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
301
+ * @returns A promise resolving to the initialization segment bytes.
207
302
  */
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: number): void;
303
+ getInitSegment(): Promise<Buffer>;
216
304
  /**
217
- * Stops the FFmpeg process and performs cleanup, ensuring the segment generator can exit.
305
+ * `true` when the abort reason indicates a timeout (the inter-segment watchdog fired or the platform `TimeoutError` was raised).
218
306
  */
219
- protected stopProcess(): void;
307
+ readonly isTimedOut: boolean;
220
308
  /**
221
- * Asynchronously generates complete segments from FFmpeg output, formatted for HomeKit Secure Video.
309
+ * Async generator yielding each completed media segment (a concatenated `moof` + `mdat` pair) as a single Buffer, in stream order. Terminates cleanly when the process
310
+ * or the caller's signal aborts, or when the underlying source ends.
222
311
  *
223
- * This async generator yields fMP4 segments as Buffers, or ends on process termination or timeout.
312
+ * @param init - Optional init options. `signal` composes with the process's own signal; aborting it terminates only this generator call, not the process.
224
313
  *
225
- * @yields A Buffer containing a complete MP4 segment suitable for HomeKit.
314
+ * @returns An async generator yielding media segment buffers in stream order.
315
+ */
316
+ segments(init?: {
317
+ signal?: AbortSignal;
318
+ }): AsyncGenerator<Buffer>;
319
+ /**
320
+ * The composed abort signal representing the recording process's lifetime. Aborts exactly once; the reason on `signal.reason` names the cause.
321
+ */
322
+ readonly signal: AbortSignal;
323
+ /**
324
+ * The accumulated stderr lines the process produced, preserved across teardown for post-mortem inspection. A readonly view: callers read, they do not mutate.
325
+ */
326
+ readonly stderrLog: readonly string[];
327
+ /**
328
+ * Writable standard input stream the recording bytes are fed into.
329
+ */
330
+ readonly stdin: Writable;
331
+ /**
332
+ * Async generator yielding the whole segment stream as a kind-tagged sequence: one {@link Mp4Segment} of kind `"init"` carrying the initialization bytes, then one of
333
+ * kind `"media"` per completed media fragment. A third view over the same pipeline as {@link RecordingProcess.getInitSegment} / {@link RecordingProcess.segments} that
334
+ * shares their single-consumer contract - a consumer uses this view or that pair, never both.
226
335
  *
227
- * @example
336
+ * @param init - Optional init options. `signal` composes with the process's own signal; aborting it terminates only this generator call, not the process.
228
337
  *
229
- * ```ts
230
- * for await(const segment of process.segmentGenerator()) {
338
+ * @returns An async generator yielding one `"init"` segment followed by `"media"` segments in stream order.
339
+ */
340
+ stream(init?: {
341
+ signal?: AbortSignal;
342
+ }): AsyncGenerator<Mp4Segment>;
343
+ }
344
+ /**
345
+ * The creational half of the recording dependency-inversion seam: build a {@link RecordingProcess} from the shared options and the recording init. A consumer holds
346
+ * this factory typed as the abstraction and constructs through it, so a test can substitute a factory that returns a fake recording process. The production factory is
347
+ * {@link recordingProcessFactory}, whose `create` is exactly the {@link FfmpegRecordingProcess} constructor call - so routing construction through this seam is
348
+ * behavior-neutral, mirroring HBUP's `streamingDelegateFactory` precedent.
349
+ *
350
+ * @see recordingProcessFactory
351
+ * @see RecordingProcess
352
+ *
353
+ * @category FFmpeg
354
+ */
355
+ export interface RecordingProcessFactory {
356
+ /**
357
+ * Construct a recording process for the supplied options and recording init.
231
358
  *
232
- * // Process each segment for HomeKit.
233
- * }
234
- * ```
359
+ * @param options - Shared {@link FfmpegOptions} configuration (codec support, logger, debug flag, name).
360
+ * @param init - Recording init options. See {@link FfmpegRecordingInit}.
361
+ *
362
+ * @returns A new {@link RecordingProcess}.
235
363
  */
236
- segmentGenerator(): AsyncGenerator<Buffer>;
364
+ create(options: FfmpegOptions, init: FfmpegRecordingInit): RecordingProcess;
237
365
  }
238
366
  /**
239
- * Manages a HomeKit livestream FFmpeg process for generating fMP4 segments.
367
+ * FFmpeg process specialization for HomeKit Secure Video (HKSV) event recording. Builds its command line from the provided HKSV recording configuration and delegates
368
+ * segment production to {@link FfmpegFMp4Process}. Overrides `FfmpegProcess.logFailedTeardown` to substitute a friendly user-facing message when the stderr log
369
+ * matches one of the tolerated HKSV error patterns, suppressing the canonical ERROR dump for those known benign cases. Also overrides
370
+ * `FfmpegProcess.logTimeoutTeardown` to demote the benign inter-segment watchdog reap to debug - a recording ends exactly this way when its segment source quiets,
371
+ * so the base's WARN would be alarming.
240
372
  *
241
373
  * @example
242
374
  *
243
375
  * ```ts
244
- * const process = new FfmpegLivestreamProcess(ffmpegOptions, recordingConfig, url, 30, true);
245
- * process.start();
376
+ * await using proc = new FfmpegRecordingProcess(ffmpegOptions, {
377
+ *
378
+ * recording: { fps: 30, probesize: 5_000_000, timeshift: 0 },
379
+ * recordingConfig,
380
+ * signal: delegate.abortController.signal
381
+ * });
382
+ *
383
+ * const init = await proc.getInitSegment();
384
+ *
385
+ * for await (const segment of proc.segments()) {
246
386
  *
247
- * const initSegment = await process.getInitSegment();
387
+ * // Forward each media segment to HomeKit.
388
+ * }
248
389
  * ```
249
390
  *
250
391
  * @see FfmpegFMp4Process
392
+ * @see FfmpegProcess
251
393
  *
252
394
  * @category FFmpeg
253
395
  */
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;
264
- /**
265
- * Constructs a new FFmpeg livestream process.
266
- *
267
- * @param options - FFmpeg configuration options.
268
- * @param recordingConfig - HomeKit recording configuration for the session.
269
- * @param livestreamOptions - livestream segmenting options.
270
- * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`.
271
- */
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: number): 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;
396
+ export declare class FfmpegRecordingProcess extends FfmpegFMp4Process implements RecordingProcess {
290
397
  /**
291
- * Gets the fMP4 initialization segment generated by FFmpeg for the livestream.
292
- *
293
- * @returns A promise resolving to the initialization segment as a Buffer.
398
+ * Construct and spawn a new HKSV recording process.
294
399
  *
295
- * @example
296
- *
297
- * ```ts
298
- * const initSegment = await process.getInitSegment();
299
- * ```
400
+ * @param options - Shared {@link FfmpegOptions} configuration (codec support, logger, debug flag, name).
401
+ * @param init - Init options. See {@link FfmpegRecordingInit}.
300
402
  */
301
- getInitSegment(): Promise<Buffer>;
403
+ constructor(options: FfmpegOptions, init: FfmpegRecordingInit);
404
+ protected logFailedTeardown(reason: HbpuAbortError): void;
405
+ protected logTimeoutTeardown(_reason: HbpuAbortError): void;
406
+ }
407
+ /**
408
+ * The production {@link RecordingProcessFactory}: builds the concrete FFmpeg-backed recording process. A consumer holds this typed as the abstraction; a test substitutes
409
+ * a fake factory. `create` is exactly the {@link FfmpegRecordingProcess} constructor call, so wiring construction through this seam is behavior-neutral.
410
+ *
411
+ * @see RecordingProcessFactory
412
+ *
413
+ * @category FFmpeg
414
+ */
415
+ export declare const recordingProcessFactory: RecordingProcessFactory;
416
+ /**
417
+ * FFmpeg process specialization for fMP4 livestreaming from an RTSP source. Builds its command line from the provided livestream source and delegates segment production
418
+ * to {@link FfmpegFMp4Process}.
419
+ *
420
+ * Used by HBUP as an alternative HKSV segment source when pulling directly from an RTSP URL (bypasses the Protect livestream API for debug and diagnostic scenarios).
421
+ * Matches the polymorphic `{ getInitSegment(): Promise<Buffer>; segments(): AsyncGenerator<Buffer> }` interface HBUP uses across the native Protect livestream and this
422
+ * class. Unlike recording, livestream does not enforce an inter-segment watchdog timeout: a live camera feed legitimately quiets down during low-motion periods and does
423
+ * not carry HKSV's 5-second hard timing contract. Callers that need a liveness cap can compose their own timeout via the process's `signal`.
424
+ *
425
+ * @example
426
+ *
427
+ * ```ts
428
+ * await using proc = new FfmpegLivestreamProcess(ffmpegOptions, {
429
+ *
430
+ * audio: { codec: AudioRecordingCodecType.AAC_LC, samplerate: AudioRecordingSamplerate.KHZ_16 },
431
+ * livestream: { url: "rtsp://camera/stream" },
432
+ * segmentLength: 1000,
433
+ * signal: session.controller.signal
434
+ * });
435
+ *
436
+ * const init = await proc.getInitSegment();
437
+ *
438
+ * for await (const segment of proc.segments()) {
439
+ *
440
+ * // Forward each media segment to the downstream consumer.
441
+ * }
442
+ * ```
443
+ *
444
+ * @see FfmpegFMp4Process
445
+ * @see FfmpegProcess
446
+ *
447
+ * @category FFmpeg
448
+ */
449
+ export declare class FfmpegLivestreamProcess extends FfmpegFMp4Process {
302
450
  /**
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) {
451
+ * Construct and spawn a new fMP4 livestream process.
312
452
  *
313
- * // Use the initialization segment.
314
- * }
315
- * ```
453
+ * @param options - Shared {@link FfmpegOptions} configuration (codec support, logger, debug flag, name).
454
+ * @param init - Init options. See {@link FfmpegLivestreamInit}.
316
455
  */
317
- get initSegment(): Nullable<Buffer>;
456
+ constructor(options: FfmpegOptions, init: FfmpegLivestreamInit);
318
457
  }
319
- export {};
458
+ //# sourceMappingURL=record.d.ts.map