homebridge-plugin-utils 1.35.0 → 2.0.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 (198) 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 +95 -0
  23. package/dist/cli/index.js +355 -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/eslint-plugin/config.d.mts +193 -0
  32. package/dist/eslint-plugin/index.d.mts +2 -0
  33. package/dist/eslint-plugin/plugin.d.mts +87 -0
  34. package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
  35. package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
  36. package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
  37. package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
  38. package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
  39. package/dist/featureOptions-docs.d.ts +96 -0
  40. package/dist/featureOptions-docs.js +193 -0
  41. package/dist/featureOptions-docs.js.map +1 -0
  42. package/dist/featureOptions.d.ts +674 -0
  43. package/dist/featureOptions.js +870 -0
  44. package/dist/featureOptions.js.map +1 -0
  45. package/dist/ffmpeg/codecs.d.ts +256 -72
  46. package/dist/ffmpeg/codecs.js +477 -262
  47. package/dist/ffmpeg/codecs.js.map +1 -1
  48. package/dist/ffmpeg/dgram-util.d.ts +46 -0
  49. package/dist/ffmpeg/dgram-util.js +38 -0
  50. package/dist/ffmpeg/dgram-util.js.map +1 -0
  51. package/dist/ffmpeg/exec.d.ts +83 -64
  52. package/dist/ffmpeg/exec.js +77 -86
  53. package/dist/ffmpeg/exec.js.map +1 -1
  54. package/dist/ffmpeg/fmp4.d.ts +49 -2
  55. package/dist/ffmpeg/fmp4.js +47 -11
  56. package/dist/ffmpeg/fmp4.js.map +1 -1
  57. package/dist/ffmpeg/hap-enums.d.ts +214 -0
  58. package/dist/ffmpeg/hap-enums.js +92 -0
  59. package/dist/ffmpeg/hap-enums.js.map +1 -0
  60. package/dist/ffmpeg/index.d.ts +16 -9
  61. package/dist/ffmpeg/index.js +6 -0
  62. package/dist/ffmpeg/index.js.map +1 -1
  63. package/dist/ffmpeg/mp4-assembler.d.ts +120 -0
  64. package/dist/ffmpeg/mp4-assembler.js +387 -0
  65. package/dist/ffmpeg/mp4-assembler.js.map +1 -0
  66. package/dist/ffmpeg/mp4-parser.d.ts +94 -0
  67. package/dist/ffmpeg/mp4-parser.js +130 -0
  68. package/dist/ffmpeg/mp4-parser.js.map +1 -0
  69. package/dist/ffmpeg/options.d.ts +62 -149
  70. package/dist/ffmpeg/options.js +608 -499
  71. package/dist/ffmpeg/options.js.map +1 -1
  72. package/dist/ffmpeg/process.d.ts +142 -96
  73. package/dist/ffmpeg/process.js +406 -278
  74. package/dist/ffmpeg/process.js.map +1 -1
  75. package/dist/ffmpeg/record.d.ts +302 -188
  76. package/dist/ffmpeg/record.js +408 -567
  77. package/dist/ffmpeg/record.js.map +1 -1
  78. package/dist/ffmpeg/recording-process-double.d.ts +144 -0
  79. package/dist/ffmpeg/recording-process-double.js +168 -0
  80. package/dist/ffmpeg/recording-process-double.js.map +1 -0
  81. package/dist/ffmpeg/rtp-parser.d.ts +70 -0
  82. package/dist/ffmpeg/rtp-parser.js +77 -0
  83. package/dist/ffmpeg/rtp-parser.js.map +1 -0
  84. package/dist/ffmpeg/rtp.d.ts +198 -141
  85. package/dist/ffmpeg/rtp.js +473 -251
  86. package/dist/ffmpeg/rtp.js.map +1 -1
  87. package/dist/ffmpeg/settings.d.ts +5 -2
  88. package/dist/ffmpeg/settings.js +20 -5
  89. package/dist/ffmpeg/settings.js.map +1 -1
  90. package/dist/ffmpeg/stream.d.ts +57 -107
  91. package/dist/ffmpeg/stream.js +121 -150
  92. package/dist/ffmpeg/stream.js.map +1 -1
  93. package/dist/formatters.d.ts +106 -0
  94. package/dist/formatters.js +174 -0
  95. package/dist/formatters.js.map +1 -0
  96. package/dist/homebridge-enums.d.ts +30 -0
  97. package/dist/homebridge-enums.js +17 -0
  98. package/dist/homebridge-enums.js.map +1 -0
  99. package/dist/index.d.ts +12 -6
  100. package/dist/index.js +7 -2
  101. package/dist/index.js.map +1 -1
  102. package/dist/logclient/auth.d.ts +114 -0
  103. package/dist/logclient/auth.js +199 -0
  104. package/dist/logclient/auth.js.map +1 -0
  105. package/dist/logclient/cli-run.d.ts +76 -0
  106. package/dist/logclient/cli-run.js +639 -0
  107. package/dist/logclient/cli-run.js.map +1 -0
  108. package/dist/logclient/cli.d.ts +3 -0
  109. package/dist/logclient/cli.js +97 -0
  110. package/dist/logclient/cli.js.map +1 -0
  111. package/dist/logclient/client.d.ts +145 -0
  112. package/dist/logclient/client.js +600 -0
  113. package/dist/logclient/client.js.map +1 -0
  114. package/dist/logclient/config.d.ts +173 -0
  115. package/dist/logclient/config.js +199 -0
  116. package/dist/logclient/config.js.map +1 -0
  117. package/dist/logclient/endpoints.d.ts +54 -0
  118. package/dist/logclient/endpoints.js +73 -0
  119. package/dist/logclient/endpoints.js.map +1 -0
  120. package/dist/logclient/filter.d.ts +45 -0
  121. package/dist/logclient/filter.js +51 -0
  122. package/dist/logclient/filter.js.map +1 -0
  123. package/dist/logclient/frame.d.ts +93 -0
  124. package/dist/logclient/frame.js +203 -0
  125. package/dist/logclient/frame.js.map +1 -0
  126. package/dist/logclient/index.d.ts +31 -0
  127. package/dist/logclient/index.js +12 -0
  128. package/dist/logclient/index.js.map +1 -0
  129. package/dist/logclient/parser.d.ts +211 -0
  130. package/dist/logclient/parser.js +393 -0
  131. package/dist/logclient/parser.js.map +1 -0
  132. package/dist/logclient/rest.d.ts +41 -0
  133. package/dist/logclient/rest.js +111 -0
  134. package/dist/logclient/rest.js.map +1 -0
  135. package/dist/logclient/settings.d.ts +15 -0
  136. package/dist/logclient/settings.js +64 -0
  137. package/dist/logclient/settings.js.map +1 -0
  138. package/dist/logclient/socket-double.d.ts +201 -0
  139. package/dist/logclient/socket-double.js +384 -0
  140. package/dist/logclient/socket-double.js.map +1 -0
  141. package/dist/logclient/socket.d.ts +257 -0
  142. package/dist/logclient/socket.js +620 -0
  143. package/dist/logclient/socket.js.map +1 -0
  144. package/dist/logclient/stitch.d.ts +83 -0
  145. package/dist/logclient/stitch.js +146 -0
  146. package/dist/logclient/stitch.js.map +1 -0
  147. package/dist/logclient/time-expression.d.ts +42 -0
  148. package/dist/logclient/time-expression.js +181 -0
  149. package/dist/logclient/time-expression.js.map +1 -0
  150. package/dist/logclient/time-window.d.ts +38 -0
  151. package/dist/logclient/time-window.js +53 -0
  152. package/dist/logclient/time-window.js.map +1 -0
  153. package/dist/logclient/types.d.ts +107 -0
  154. package/dist/logclient/types.js +6 -0
  155. package/dist/logclient/types.js.map +1 -0
  156. package/dist/mqttClient.d.ts +287 -0
  157. package/dist/mqttClient.js +433 -0
  158. package/dist/mqttClient.js.map +1 -0
  159. package/dist/service.d.ts +64 -15
  160. package/dist/service.js +93 -66
  161. package/dist/service.js.map +1 -1
  162. package/dist/ui/featureOptions.js +870 -0
  163. package/dist/ui/featureOptions.js.map +1 -0
  164. package/dist/ui/formatters.js +174 -0
  165. package/dist/ui/formatters.js.map +1 -0
  166. package/dist/ui/pluginConfigSession.mjs +141 -0
  167. package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
  168. package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
  169. package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
  170. package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
  171. package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
  172. package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
  173. package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
  174. package/dist/ui/webUi-featureOptions/state.mjs +319 -0
  175. package/dist/ui/webUi-featureOptions/store.mjs +181 -0
  176. package/dist/ui/webUi-featureOptions/utils.mjs +200 -0
  177. package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
  178. package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
  179. package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
  180. package/dist/ui/webUi-featureOptions/views/nav.mjs +327 -0
  181. package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
  182. package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
  183. package/dist/ui/webUi-featureOptions.mjs +677 -0
  184. package/dist/ui/webUi.mjs +192 -87
  185. package/dist/util.d.ts +602 -45
  186. package/dist/util.js +783 -78
  187. package/dist/util.js.map +1 -1
  188. package/package.json +33 -15
  189. package/build/eslint-rules.mjs +0 -511
  190. package/dist/featureoptions.d.ts +0 -264
  191. package/dist/featureoptions.js +0 -480
  192. package/dist/featureoptions.js.map +0 -1
  193. package/dist/mqttclient.d.ts +0 -178
  194. package/dist/mqttclient.js +0 -310
  195. package/dist/mqttclient.js.map +0 -1
  196. package/dist/ui/featureoptions.js +0 -480
  197. package/dist/ui/featureoptions.js.map +0 -1
  198. package/dist/ui/webUi-featureoptions.mjs +0 -3765
@@ -1,205 +1,262 @@
1
+ import type { HomebridgePluginLogging } from "../util.ts";
2
+ import type { IpFamily } from "./dgram-util.ts";
1
3
  /**
2
- * RTP and RTCP packet demultiplexer and UDP port management for FFmpeg-based HomeKit livestreaming.
4
+ * Construction-time options for {@link RtpDemuxer}.
3
5
  *
4
- * This module supplies classes and helpers to support realtime streaming via FFmpeg in Homebridge and similar HomeKit environments. It enables the demultiplexing of RTP
5
- * and RTCP packets on a single UDP port, as required by HomeKit and RFC 5761, working around FFmpeg’s lack of native support for RTP/RTCP multiplexing. It also manages
6
- * the allocation and tracking of UDP ports for RTP and RTCP, helping prevent conflicts in dynamic, multi-session streaming scenarios.
6
+ * @property inactivityTimeout - Optional inactivity watchdog window, in milliseconds. The timer arms during construction (immediately after the bind call is issued)
7
+ * and re-arms on every received datagram. When the window lapses without traffic, the demuxer aborts with `HbpuAbortError("timeout")`.
8
+ * Omit to disable the watchdog entirely.
9
+ * @property inputPort - Required. The UDP port to bind to. Typically a value previously reserved via {@link RtpPortAllocator.reserve}. Pass `0` to request
10
+ * kernel-assigned ephemeral allocation: the bind succeeds atomically against whichever port the kernel hands out, eliminating the
11
+ * reserve-then-rebind race that a separate reservation step would carry. The assigned port is then observable via
12
+ * {@link RtpDemuxer.inputPort} once {@link RtpDemuxer.ready} resolves.
13
+ * @property ipFamily - Optional. `"ipv4"` or `"ipv6"`. Defaults to `"ipv4"`.
14
+ * @property log - Optional logger. Used for debug tracing of socket lifecycle and heartbeat events, and for error-path diagnostics. The signal's reason
15
+ * on abort remains the authoritative notification channel; logging is a convenience for operators.
16
+ * @property rtcpPort - Required. The destination UDP port (on the loopback interface) for classified RTCP packets. Typically FFmpeg's RTCP input port.
17
+ * @property rtpPort - Required. The destination UDP port (on the loopback interface) for classified RTP packets. Typically FFmpeg's RTP input port. The
18
+ * heartbeat replay also targets this port - FFmpeg ignores the RTCP shape on its RTP input, but the arriving traffic keeps that input
19
+ * fed during quiet periods.
20
+ * @property signal - Optional parent {@link AbortSignal} to compose with the demuxer's internal controller. When the parent aborts, the demuxer tears down.
7
21
  *
8
- * Key features:
9
- *
10
- * - Demultiplexes RTP and RTCP packets received on a single UDP port, forwarding them to the correct FFmpeg destinations for HomeKit livestream compatibility.
11
- * - Injects periodic heartbeat messages to keep two-way audio streams alive with FFmpeg’s strict timeout requirements.
12
- * - Dynamically allocates and reserves UDP ports for RTP/RTCP, supporting consecutive port pairing for correct FFmpeg operation.
13
- * - Event-driven architecture for integration with plugin or automation logic.
14
- *
15
- * Designed for plugin developers and advanced users implementing HomeKit livestreaming, audio/video bridging, or similar applications requiring precise RTP/RTCP handling
16
- * with FFmpeg.
17
- *
18
- * @module
22
+ * @category FFmpeg
19
23
  */
20
- import { EventEmitter } from "node:events";
21
- import type { HomebridgePluginLogging } from "../util.js";
24
+ export interface RtpDemuxerInit {
25
+ inactivityTimeout?: number;
26
+ inputPort: number;
27
+ ipFamily?: IpFamily;
28
+ log?: HomebridgePluginLogging;
29
+ rtcpPort: number;
30
+ rtpPort: number;
31
+ signal?: AbortSignal;
32
+ }
22
33
  /**
23
- * Utility for demultiplexing RTP and RTCP packets on a single UDP port for HomeKit compatibility.
34
+ * Signal-driven RTP/RTCP demultiplexer with FFmpeg keepalive heartbeat.
24
35
  *
25
- * FFmpeg does not support multiplexing RTP and RTCP data on a single UDP port (RFC 5761) and HomeKit requires this for livestreaming. This class listens on a UDP port
26
- * and demultiplexes RTP and RTCP traffic, forwarding them to separate RTP and RTCP ports as required by FFmpeg.
36
+ * The class owns one bound UDP socket. Inbound datagrams are classified by an internal {@link RtpPacketParser} and forwarded through the same socket to the configured
37
+ * `rtpPort` (RTP-classified) or `rtcpPort` (RTCP-classified) on the loopback interface. Sharing the bound socket between receive and send gives the relay two
38
+ * load-bearing properties:
27
39
  *
28
- * Credit to [dgreif](https://github.com/dgreif), [brandawg93](https://github.com/brandawg93), and [Sunoo](https://github.com/Sunoo) for foundational ideas and
29
- * collaboration.
40
+ * 1. **Source-endpoint symmetry.** Every forwarded datagram leaves the bound socket with source = `loopback:inputPort`. Because the demuxer holds an exclusive
41
+ * kernel bind on that port, no other non-root process can spoof that source endpoint - downstream receivers (typically FFmpeg) can therefore enforce source
42
+ * filtering against locally-injected traffic without coordinating ephemeral allocations with the demuxer.
43
+ * 2. **Minimized bound-port footprint.** Exactly one kernel-tracked socket exists per demuxer instance; a dual-socket design would bind a second ephemeral port for
44
+ * the duration of the session, expanding the attack surface and the kernel-resource count for no architectural benefit.
45
+ *
46
+ * A self-rearming {@link Watchdog} replays the last observed RTCP packet to `rtpPort` whenever the gap between inbound RTCP arrivals exceeds the configured
47
+ * {@link RTCP_HEARTBEAT_INTERVAL}. The heartbeat is part of the demuxer's invariant contract - FFmpeg-bound two-way audio is the only use case that constructs this
48
+ * class, and that use case relies on the keepalive to keep inbound traffic arriving at FFmpeg's RTP input during legitimate quiet periods on the camera's audio
49
+ * backchannel. No FFmpeg input-timeout flag (`-timeout` / `rw_timeout`) is configured anywhere - the keepalive is a defensive guard against any FFmpeg-side idle
50
+ * handling of a quiet UDP/RTP input, not a timeout this code sets. The cadence is the single exported constant {@link RTCP_HEARTBEAT_INTERVAL}.
51
+ *
52
+ * An optional second {@link Watchdog} aborts the demuxer with `HbpuAbortError("timeout")` when no inbound traffic arrives within a caller-supplied window. Both
53
+ * watchdogs compose against the same lifetime signal, so disposal cleans them up uniformly.
30
54
  *
31
55
  * @example
32
56
  *
33
57
  * ```ts
34
- * // Create an RtpDemuxer to split packets for FFmpeg compatibility.
35
- * const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log);
58
+ * await using demuxer = new RtpDemuxer({
59
+ *
60
+ * inactivityTimeout: 5_000,
61
+ * inputPort: audioIncomingPort,
62
+ * ipFamily: "ipv4",
63
+ * log,
64
+ * rtcpPort: audioIncomingRtcpPort,
65
+ * rtpPort: audioIncomingRtpPort,
66
+ * signal: session.signal
67
+ * });
68
+ *
69
+ * try {
36
70
  *
37
- * // Close the demuxer when finished.
38
- * demuxer.close();
71
+ * await demuxer.mediaReady;
72
+ * } catch {
73
+ *
74
+ * // Demuxer aborted before any inbound RTP arrived; abandon the return-audio setup.
75
+ * return;
76
+ * }
77
+ *
78
+ * // Media is now flowing - safe to launch the consuming FFmpeg process.
39
79
  * ```
40
80
  *
41
- * @see {@link https://tools.ietf.org/html/rfc5761 | RFC 5761}
42
- * @see {@link https://github.com/homebridge/homebridge-camera-ffmpeg | homebridge-camera-ffmpeg}
81
+ * @see RtpPacketParser
82
+ * @see RTCP_HEARTBEAT_INTERVAL
43
83
  *
44
84
  * @category FFmpeg
45
85
  */
46
- export declare class RtpDemuxer extends EventEmitter {
47
- private heartbeatTimer?;
48
- private heartbeatMsg?;
49
- private _isRunning;
50
- private log?;
51
- private inputPort;
52
- readonly socket: import("node:dgram").Socket;
86
+ export declare class RtpDemuxer implements AsyncDisposable {
87
+ #private;
53
88
  /**
54
- * Constructs a new RtpDemuxer for a specified IP family and port set.
55
- *
56
- * @param ipFamily - The IP family: "ipv4" or "ipv6".
57
- * @param inputPort - The UDP port to listen on for incoming packets.
58
- * @param rtcpPort - The UDP port to forward RTCP packets to.
59
- * @param rtpPort - The UDP port to forward RTP packets to.
60
- * @param log - Logger instance for debug and error messages.
61
- *
62
- * @example
63
- *
64
- * ```ts
65
- * const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log);
66
- * ```
89
+ * The composed abort signal representing this demuxer's lifetime. Aborts exactly once when the socket errors, the inactivity watchdog fires, the parent signal
90
+ * propagates, or {@link RtpDemuxer.abort} is called; the reason encoded on `signal.reason` names the cause.
67
91
  */
68
- constructor(ipFamily: ("ipv4" | "ipv6"), inputPort: number, rtcpPort: number, rtpPort: number, log: HomebridgePluginLogging);
92
+ readonly signal: AbortSignal;
69
93
  /**
70
- * Sends periodic heartbeat messages to the RTP port to keep the FFmpeg process alive.
94
+ * Promise that resolves once the underlying UDP socket has finished binding and entered its `"listening"` state. Rejects with `this.signal.reason` when the bind
95
+ * fails, the parent signal propagates, or any other abort path fires before the socket becomes ready.
71
96
  *
72
- * This is necessary because FFmpeg times out input streams if it does not receive data for more than five seconds.
73
- *
74
- * @param port - The RTP port to send the heartbeat to.
97
+ * The unhandled-rejection tracker is suppressed via {@link markHandled} so consumers that never observe readiness (fire-and-forget demuxers) do not produce warnings;
98
+ * consumers that do observe rejection receive the same structured abort reason they would from `signal.reason`.
75
99
  */
76
- private heartbeat;
100
+ readonly ready: Promise<void>;
77
101
  /**
78
- * Closes the demuxer, its socket, and any heartbeat timers.
102
+ * Promise that resolves once the demuxer has forwarded its first RTP-classified packet - the application-level readiness milestone meaning "media is now flowing
103
+ * through the relay to the downstream consumer." Rejects with `this.signal.reason` if the demuxer aborts before any RTP packet arrives.
79
104
  *
80
- * @example
105
+ * Pairs with {@link RtpDemuxer.ready} as a two-tier readiness model: `ready` resolves when the inbound socket has bound (network-level readiness); `mediaReady`
106
+ * resolves when classified RTP traffic has begun flowing (application-level readiness). The canonical gating pattern for two-way audio is "stand up the return-audio
107
+ * path, wait for inbound media before launching the consuming FFmpeg process":
81
108
  *
82
109
  * ```ts
83
- * demuxer.close();
110
+ * try {
111
+ * await demuxer.mediaReady;
112
+ * } catch {
113
+ * // Demuxer aborted before any RTP arrived; abandon the return-audio setup.
114
+ * return;
115
+ * }
116
+ * // Safe to launch the consuming FFmpeg process now that media is flowing.
84
117
  * ```
118
+ *
119
+ * The unhandled-rejection tracker is suppressed via {@link markHandled} so consumers that never observe this promise produce no warnings.
85
120
  */
86
- close(): void;
121
+ readonly mediaReady: Promise<void>;
87
122
  /**
88
- * Extracts the RTP payload type from a UDP packet.
89
- *
90
- * Used internally to distinguish RTP from RTCP messages.
91
- *
92
- * @param message - The UDP packet buffer.
123
+ * The UDP port the demuxer is bound to. For a specific-port construction (`init.inputPort` non-zero), this equals `init.inputPort` from the moment the constructor
124
+ * returns; for an ephemeral construction (`init.inputPort === 0`), the value is `0` until the kernel completes the bind, then the kernel-assigned port. Consumers
125
+ * that need the assigned ephemeral port `await demuxer.ready` before reading this property.
93
126
  *
94
- * @returns The RTP payload type as a number.
127
+ * Reads from the cached projection of `socket.address().port` captured at the `"listening"` event so the value reflects what the kernel actually bound, not just what
128
+ * was requested. The two coincide for specific-port binds; they diverge for ephemeral binds where the requested value is `0` and the bound value is the kernel's pick.
129
+ */
130
+ get inputPort(): number;
131
+ /**
132
+ * `true` once `this.signal` has aborted. Derived from the signal; no independent state.
95
133
  */
96
- private getPayloadType;
134
+ get aborted(): boolean;
97
135
  /**
98
- * Determines if the provided UDP packet is an RTP message.
136
+ * `true` when the abort reason indicates a timeout. Matches both the canonical `HbpuAbortError("timeout")` emitted by the inactivity watchdog and the platform
137
+ * `TimeoutError` emitted by `AbortSignal.timeout()` - consumers discriminate on a single getter regardless of which code path produced the timeout. The discrimination
138
+ * logic lives in {@link isTimeoutReason} so this getter stays a one-line delegation and every resource class in the library shares one definition of "timeout."
139
+ */
140
+ get isTimedOut(): boolean;
141
+ /**
142
+ * Construct and bind a new RTP demuxer.
99
143
  *
100
- * @param message - The UDP packet buffer.
144
+ * Socket binding happens synchronously as part of construction: by the time the constructor returns, the socket has been asked to bind on the configured port and
145
+ * the inactivity watchdog (if configured) is already armed. There is no separate `start()` step. The bind itself completes on a later turn; consumers that need to
146
+ * sequence work against bind completion `await demuxer.ready`.
101
147
  *
102
- * @returns `true` if the packet is RTP, `false` if RTCP.
148
+ * @param init - Required init options. See {@link RtpDemuxerInit}.
103
149
  */
104
- private isRtpMessage;
150
+ constructor(init: RtpDemuxerInit);
105
151
  /**
106
- * Indicates if the demuxer is running and accepting packets.
152
+ * Abort the demuxer and tear it down. Defaults to `HbpuAbortError("shutdown")` when no reason is supplied; explicit reasons pass through unchanged.
107
153
  *
108
- * @returns `true` if running, otherwise `false`.
154
+ * Safe to call more than once: subsequent calls are no-ops because the underlying signal only aborts once. Calling `abort()` after a natural error or watchdog
155
+ * timeout is also safe for the same reason.
109
156
  *
110
- * @example
157
+ * @param reason - Optional abort reason. Typically an {@link HbpuAbortError}; platform errors (`TimeoutError`, `AbortError`) also interoperate by convention.
158
+ */
159
+ abort(reason?: unknown): void;
160
+ /**
161
+ * `AsyncDisposable` implementation. Aborts the demuxer (defaulting to `"shutdown"`) and awaits the socket's `"close"` event so the bound port is releasable by the
162
+ * time the surrounding `await using` scope's next statement runs. Safe to invoke repeatedly: subsequent disposals collapse onto the same `#closed` promise.
111
163
  *
112
- * ```ts
113
- * if(demuxer.isRunning) {
114
- * // Demuxer is active.
115
- * }
116
- * ```
164
+ * @returns A promise that resolves once the kernel has released the bound port.
117
165
  */
118
- get isRunning(): boolean;
166
+ [Symbol.asyncDispose](): Promise<void>;
119
167
  }
120
168
  /**
121
- * Allocates and tracks UDP ports for RTP and RTCP to avoid port conflicts in environments with high network activity.
169
+ * Construction-time options for {@link RtpPortAllocator.reserve}.
170
+ *
171
+ * @property count - Optional. The number of consecutive UDP ports to reserve. `1` yields a single port, `2` yields the reserved port plus the next port so FFmpeg's
172
+ * "RTP port N implies RTCP port N+1" convention is satisfied. Defaults to `1`.
173
+ * @property ipFamily - Optional. `"ipv4"` or `"ipv6"`. Defaults to `"ipv4"`.
174
+ * @property signal - Optional caller {@link AbortSignal}. Cancels in-flight bind-retry attempts. If the signal aborts while `reserve()` is still looking for
175
+ * consecutive ports, the partially reserved port (if any) is released and the promise rejects with `signal.reason`.
122
176
  *
123
- * This utility class is used to find and reserve available UDP ports for demuxing FFmpeg streams or other network activities.
177
+ * @category FFmpeg
178
+ */
179
+ export interface PortReservationInit {
180
+ count?: 1 | 2;
181
+ ipFamily?: IpFamily;
182
+ signal?: AbortSignal;
183
+ }
184
+ /**
185
+ * AsyncDisposable handle representing one or two reserved UDP ports.
186
+ *
187
+ * Ports are held exclusively against the {@link RtpPortAllocator}'s internal pool until `[Symbol.asyncDispose]` is invoked. Callers typically
188
+ * manage the lifetime with `await using` for scope-bound reservations, or by storing the handle on a session entry and disposing explicitly when the session ends.
189
+ *
190
+ * Disposal is idempotent: the first call releases the ports back to the allocator; subsequent calls are no-ops. This guarantees `await using` combined with an
191
+ * explicit dispose (for example, on an error path that releases early and then falls through the `using` block) does not double-release.
192
+ *
193
+ * @property count - `1` or `2`. A two-port reservation guarantees `port` and `port + 1` are both reserved.
194
+ * @property ipFamily - `"ipv4"` or `"ipv6"`.
195
+ * @property port - The first (and, for single-port reservations, only) reserved UDP port.
196
+ *
197
+ * @category FFmpeg
198
+ */
199
+ export interface PortReservation extends AsyncDisposable {
200
+ readonly count: 1 | 2;
201
+ readonly ipFamily: IpFamily;
202
+ readonly port: number;
203
+ [Symbol.asyncDispose](): Promise<void>;
204
+ }
205
+ /**
206
+ * Registry that reserves consecutive UDP ports for FFmpeg-based RTP/RTCP sessions.
207
+ *
208
+ * The allocator itself is a plugin-singleton registry - it holds no OS resources of its own, only a `Set` of ports marked as in-use. Reservations are the scoped
209
+ * resource: each successful {@link RtpPortAllocator.reserve} call returns a {@link PortReservation} handle whose disposal releases the held ports back to the pool.
210
+ *
211
+ * The reservation bind-retry loop is signal-aware: passing `init.signal` to `reserve()` means an aborting parent signal cancels the pending reservation cleanly, even
212
+ * when the loop is in the middle of probing candidate ports.
124
213
  *
125
214
  * @example
126
215
  *
127
216
  * ```ts
128
- * const allocator = new RtpPortAllocator();
217
+ * // Scope-bound: the reservation releases automatically when the block exits.
218
+ * await using reservation = await allocator.reserve({ count: 2, signal: session.controller.signal });
219
+ * const rtpPort = reservation.port;
220
+ * const rtcpPort = reservation.port + 1;
221
+ * // ...use rtpPort and rtcpPort for the duration of the session.
222
+ * ```
129
223
  *
130
- * // Reserve two consecutive ports for RTP and RTCP.
131
- * const rtpPort = await allocator.reserve("ipv4", 2);
224
+ * @example
132
225
  *
133
- * // Cancel reservation if not needed.
134
- * allocator.cancel(rtpPort);
226
+ * ```ts
227
+ * // Non-scoped: store the handle on a session entry and dispose explicitly later.
228
+ * const reservation = await allocator.reserve({ count: 2, signal: session.controller.signal });
229
+ * session.ports = reservation;
230
+ * // ...later:
231
+ * await session.ports[Symbol.asyncDispose]();
135
232
  * ```
136
233
  *
137
234
  * @category FFmpeg
138
235
  */
139
236
  export declare class RtpPortAllocator {
140
- private portsInUse;
141
- /**
142
- * Instantiates a new RTP port allocator and tracker.
143
- */
144
- constructor();
145
- /**
146
- * Finds an available UDP port by attempting to bind a new socket.
147
- *
148
- * Loops until an available port not already marked as in use is found.
149
- *
150
- * @param ipFamily - "ipv4" or "ipv6".
151
- * @param port - Optional. The port to try to bind to. If 0, selects a random port.
152
- *
153
- * @returns A promise resolving to the available port number, or `-1` on error.
154
- */
155
- private getPort;
237
+ #private;
156
238
  /**
157
- * Internal method to reserve one or two consecutive UDP ports for FFmpeg or network use.
158
- *
159
- * If two ports are reserved, ensures they are consecutive for RTP and RTCP usage. Returns the first port in the sequence, or `-1` if we're unable to allocate.
160
- *
161
- * @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4".
162
- * @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1.
163
- * @param attempts - Internal. The number of allocation attempts. Used for recursion.
239
+ * The number of UDP ports currently held by live {@link PortReservation} handles. Exposed for operational diagnostics - a plugin that surfaces "how many
240
+ * reservations are in flight right now" through a status endpoint can read this directly, and it is a useful signal for detecting leaks (a monotonically growing
241
+ * count across normal session cycles indicates a reservation is not being disposed).
164
242
  *
165
- * @returns A promise resolving to the first reserved port, or `-1` if unavailable.
243
+ * @returns The number of reserved ports currently tracked by the allocator.
166
244
  */
167
- private _reserve;
245
+ get reservedCount(): number;
168
246
  /**
169
- * Reserves one or two consecutive UDP ports for FFmpeg or network use.
247
+ * Reserve one or two consecutive UDP ports.
170
248
  *
171
- * If two ports are reserved, ensures they are consecutive for RTP and RTCP usage. Returns the first port in the sequence, or `-1` if we're unable to allocate.
249
+ * For a two-port reservation, the allocator first picks a random free port, then probes the next sequential port. If the sequential port is unavailable (already in
250
+ * the in-use set or rejected by the OS), the first port is released and the allocator retries from scratch, up to `RESERVE_MAX_ATTEMPTS` attempts.
172
251
  *
173
- * @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4".
174
- * @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1.
252
+ * @param init - Optional init options. See {@link PortReservationInit}.
175
253
  *
176
- * @returns A promise resolving to the first reserved port, or `-1` if unavailable.
177
- *
178
- * @remarks FFmpeg currently lacks the ability to specify both the RTP and RTCP ports. FFmpeg always assumes, by convention, that when you specify an RTP port, the RTCP
179
- * port is the RTP port + 1. In order to work around that challenge, we need to always ensure that when we reserve multiple ports for RTP (primarily for two-way audio
180
- * use cases) that we we are reserving consecutive ports only.
181
- *
182
- * @example
183
- *
184
- * ```ts
185
- * // Reserve a single port.
186
- * const port = await allocator.reserve();
254
+ * @returns A {@link PortReservation} handle whose disposal releases the reserved ports.
187
255
  *
188
- * // Reserve two consecutive ports for RTP/RTCP.
189
- * const rtpPort = await allocator.reserve("ipv4", 2);
190
- * ```
191
- */
192
- reserve(ipFamily?: ("ipv4" | "ipv6"), portCount?: (1 | 2)): Promise<number>;
193
- /**
194
- * Cancels and releases a previously reserved port, making it available for future use.
195
- *
196
- * @param port - The port number to release.
197
- *
198
- * @example
199
- *
200
- * ```ts
201
- * allocator.cancel(50000);
202
- * ```
256
+ * @throws The caller signal's abort reason, if the signal aborts before a reservation is obtained.
257
+ * @throws `RangeError` when `count` is anything other than `1` or `2`.
258
+ * @throws `Error` when no reservation could be obtained within the attempt budget.
203
259
  */
204
- cancel(port: number): void;
260
+ reserve(init?: PortReservationInit): Promise<PortReservation>;
205
261
  }
262
+ //# sourceMappingURL=rtp.d.ts.map