homebridge-plugin-utils 1.15.2 → 1.16.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.
- package/README.md +4 -1
- package/build/eslint-rules.mjs +1 -0
- package/dist/featureoptions.d.ts +83 -5
- package/dist/featureoptions.js +65 -6
- package/dist/featureoptions.js.map +1 -1
- package/dist/ffmpeg/codecs.d.ts +172 -0
- package/dist/ffmpeg/codecs.js +374 -0
- package/dist/ffmpeg/codecs.js.map +1 -0
- package/dist/ffmpeg/exec.d.ts +108 -0
- package/dist/ffmpeg/exec.js +122 -0
- package/dist/ffmpeg/exec.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +8 -0
- package/dist/ffmpeg/index.js +13 -0
- package/dist/ffmpeg/index.js.map +1 -0
- package/dist/ffmpeg/options.d.ts +345 -0
- package/dist/ffmpeg/options.js +750 -0
- package/dist/ffmpeg/options.js.map +1 -0
- package/dist/ffmpeg/process.d.ts +155 -0
- package/dist/ffmpeg/process.js +344 -0
- package/dist/ffmpeg/process.js.map +1 -0
- package/dist/ffmpeg/record.d.ts +230 -0
- package/dist/ffmpeg/record.js +504 -0
- package/dist/ffmpeg/record.js.map +1 -0
- package/dist/ffmpeg/rtp.d.ts +205 -0
- package/dist/ffmpeg/rtp.js +335 -0
- package/dist/ffmpeg/rtp.js.map +1 -0
- package/dist/ffmpeg/settings.d.ts +6 -0
- package/dist/ffmpeg/settings.js +17 -0
- package/dist/ffmpeg/settings.js.map +1 -0
- package/dist/ffmpeg/stream.d.ts +143 -0
- package/dist/ffmpeg/stream.js +186 -0
- package/dist/ffmpeg/stream.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mqttclient.d.ts +161 -1
- package/dist/mqttclient.js +161 -9
- package/dist/mqttclient.js.map +1 -1
- package/dist/service.d.ts +9 -2
- package/dist/service.js +6 -0
- package/dist/service.js.map +1 -1
- package/dist/ui/featureoptions.js +65 -6
- package/dist/ui/featureoptions.js.map +1 -1
- package/dist/ui/webUi-featureoptions.mjs +5 -4
- package/dist/util.d.ts +203 -12
- package/dist/util.js +95 -12
- package/dist/util.js.map +1 -1
- package/package.json +13 -9
- package/dist/rtp.d.ts +0 -32
- package/dist/rtp.js +0 -178
- package/dist/rtp.js.map +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTP and RTCP packet demultiplexer and UDP port management for FFmpeg-based HomeKit livestreaming.
|
|
3
|
+
*
|
|
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.
|
|
7
|
+
*
|
|
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
|
|
19
|
+
*/
|
|
20
|
+
import { EventEmitter } from "node:events";
|
|
21
|
+
import type { HomebridgePluginLogging } from "../util.js";
|
|
22
|
+
/**
|
|
23
|
+
* Utility for demultiplexing RTP and RTCP packets on a single UDP port for HomeKit compatibility.
|
|
24
|
+
*
|
|
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.
|
|
27
|
+
*
|
|
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.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* // Create an RtpDemuxer to split packets for FFmpeg compatibility.
|
|
35
|
+
* const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log);
|
|
36
|
+
*
|
|
37
|
+
* // Close the demuxer when finished.
|
|
38
|
+
* demuxer.close();
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @see {@link https://tools.ietf.org/html/rfc5761 | RFC 5761}
|
|
42
|
+
* @see {@link https://github.com/homebridge/homebridge-camera-ffmpeg | homebridge-camera-ffmpeg}
|
|
43
|
+
*
|
|
44
|
+
* @category FFmpeg
|
|
45
|
+
*/
|
|
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("dgram").Socket;
|
|
53
|
+
/**
|
|
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
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
constructor(ipFamily: ("ipv4" | "ipv6"), inputPort: number, rtcpPort: number, rtpPort: number, log: HomebridgePluginLogging);
|
|
69
|
+
/**
|
|
70
|
+
* Sends periodic heartbeat messages to the RTP port to keep the FFmpeg process alive.
|
|
71
|
+
*
|
|
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.
|
|
75
|
+
*/
|
|
76
|
+
private heartbeat;
|
|
77
|
+
/**
|
|
78
|
+
* Closes the demuxer, its socket, and any heartbeat timers.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* demuxer.close();
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
close(): void;
|
|
87
|
+
/**
|
|
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.
|
|
93
|
+
*
|
|
94
|
+
* @returns The RTP payload type as a number.
|
|
95
|
+
*/
|
|
96
|
+
private getPayloadType;
|
|
97
|
+
/**
|
|
98
|
+
* Determines if the provided UDP packet is an RTP message.
|
|
99
|
+
*
|
|
100
|
+
* @param message - The UDP packet buffer.
|
|
101
|
+
*
|
|
102
|
+
* @returns `true` if the packet is RTP, `false` if RTCP.
|
|
103
|
+
*/
|
|
104
|
+
private isRtpMessage;
|
|
105
|
+
/**
|
|
106
|
+
* Indicates if the demuxer is running and accepting packets.
|
|
107
|
+
*
|
|
108
|
+
* @returns `true` if running, otherwise `false`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* if(demuxer.isRunning) {
|
|
114
|
+
* // Demuxer is active.
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
get isRunning(): boolean;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Allocates and tracks UDP ports for RTP and RTCP to avoid port conflicts in environments with high network activity.
|
|
122
|
+
*
|
|
123
|
+
* This utility class is used to find and reserve available UDP ports for demuxing FFmpeg streams or other network activities.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* const allocator = new RtpPortAllocator();
|
|
129
|
+
*
|
|
130
|
+
* // Reserve two consecutive ports for RTP and RTCP.
|
|
131
|
+
* const rtpPort = await allocator.reserve("ipv4", 2);
|
|
132
|
+
*
|
|
133
|
+
* // Cancel reservation if not needed.
|
|
134
|
+
* allocator.cancel(rtpPort);
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @category FFmpeg
|
|
138
|
+
*/
|
|
139
|
+
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;
|
|
156
|
+
/**
|
|
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.
|
|
164
|
+
*
|
|
165
|
+
* @returns A promise resolving to the first reserved port, or `-1` if unavailable.
|
|
166
|
+
*/
|
|
167
|
+
private _reserve;
|
|
168
|
+
/**
|
|
169
|
+
* Reserves one or two consecutive UDP ports for FFmpeg or network use.
|
|
170
|
+
*
|
|
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.
|
|
172
|
+
*
|
|
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.
|
|
175
|
+
*
|
|
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();
|
|
187
|
+
*
|
|
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
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
cancel(port: number): void;
|
|
205
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2025, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* ffmpeg/rtp.ts: RTP-related utilities to slice and dice RTP streams.
|
|
4
|
+
*
|
|
5
|
+
* This module is heavily inspired by the homebridge and homebridge-camera-ffmpeg source code and borrows from both. Thank you for your contributions to the community.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* RTP and RTCP packet demultiplexer and UDP port management for FFmpeg-based HomeKit livestreaming.
|
|
9
|
+
*
|
|
10
|
+
* This module supplies classes and helpers to support realtime streaming via FFmpeg in Homebridge and similar HomeKit environments. It enables the demultiplexing of RTP
|
|
11
|
+
* 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
|
|
12
|
+
* the allocation and tracking of UDP ports for RTP and RTCP, helping prevent conflicts in dynamic, multi-session streaming scenarios.
|
|
13
|
+
*
|
|
14
|
+
* Key features:
|
|
15
|
+
*
|
|
16
|
+
* - Demultiplexes RTP and RTCP packets received on a single UDP port, forwarding them to the correct FFmpeg destinations for HomeKit livestream compatibility.
|
|
17
|
+
* - Injects periodic heartbeat messages to keep two-way audio streams alive with FFmpeg’s strict timeout requirements.
|
|
18
|
+
* - Dynamically allocates and reserves UDP ports for RTP/RTCP, supporting consecutive port pairing for correct FFmpeg operation.
|
|
19
|
+
* - Event-driven architecture for integration with plugin or automation logic.
|
|
20
|
+
*
|
|
21
|
+
* Designed for plugin developers and advanced users implementing HomeKit livestreaming, audio/video bridging, or similar applications requiring precise RTP/RTCP handling
|
|
22
|
+
* with FFmpeg.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { EventEmitter, once } from "node:events";
|
|
27
|
+
import { createSocket } from "node:dgram";
|
|
28
|
+
// How often, in seconds, should we heartbeat FFmpeg in two-way audio sessions. This should be less than 5 seconds, which is FFmpeg's input timeout interval.
|
|
29
|
+
const TWOWAY_HEARTBEAT_INTERVAL = 3;
|
|
30
|
+
/**
|
|
31
|
+
* Utility for demultiplexing RTP and RTCP packets on a single UDP port for HomeKit compatibility.
|
|
32
|
+
*
|
|
33
|
+
* 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
|
|
34
|
+
* and demultiplexes RTP and RTCP traffic, forwarding them to separate RTP and RTCP ports as required by FFmpeg.
|
|
35
|
+
*
|
|
36
|
+
* Credit to [dgreif](https://github.com/dgreif), [brandawg93](https://github.com/brandawg93), and [Sunoo](https://github.com/Sunoo) for foundational ideas and
|
|
37
|
+
* collaboration.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* // Create an RtpDemuxer to split packets for FFmpeg compatibility.
|
|
43
|
+
* const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log);
|
|
44
|
+
*
|
|
45
|
+
* // Close the demuxer when finished.
|
|
46
|
+
* demuxer.close();
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @see {@link https://tools.ietf.org/html/rfc5761 | RFC 5761}
|
|
50
|
+
* @see {@link https://github.com/homebridge/homebridge-camera-ffmpeg | homebridge-camera-ffmpeg}
|
|
51
|
+
*
|
|
52
|
+
* @category FFmpeg
|
|
53
|
+
*/
|
|
54
|
+
export class RtpDemuxer extends EventEmitter {
|
|
55
|
+
heartbeatTimer;
|
|
56
|
+
heartbeatMsg;
|
|
57
|
+
_isRunning;
|
|
58
|
+
log;
|
|
59
|
+
inputPort;
|
|
60
|
+
socket;
|
|
61
|
+
/**
|
|
62
|
+
* Constructs a new RtpDemuxer for a specified IP family and port set.
|
|
63
|
+
*
|
|
64
|
+
* @param ipFamily - The IP family: "ipv4" or "ipv6".
|
|
65
|
+
* @param inputPort - The UDP port to listen on for incoming packets.
|
|
66
|
+
* @param rtcpPort - The UDP port to forward RTCP packets to.
|
|
67
|
+
* @param rtpPort - The UDP port to forward RTP packets to.
|
|
68
|
+
* @param log - Logger instance for debug and error messages.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
constructor(ipFamily, inputPort, rtcpPort, rtpPort, log) {
|
|
77
|
+
super();
|
|
78
|
+
this._isRunning = false;
|
|
79
|
+
this.log = log;
|
|
80
|
+
this.inputPort = inputPort;
|
|
81
|
+
this.socket = createSocket(ipFamily === "ipv6" ? "udp6" : "udp4");
|
|
82
|
+
// Catch errors when they happen on our demuxer.
|
|
83
|
+
this.socket.on("error", (error) => {
|
|
84
|
+
this.log?.error("RtpDemuxer Error: %s", error);
|
|
85
|
+
this.socket.close();
|
|
86
|
+
});
|
|
87
|
+
// Split the message into RTP and RTCP packets.
|
|
88
|
+
this.socket.on("message", (msg) => {
|
|
89
|
+
// Send RTP packets to the RTP port.
|
|
90
|
+
if (this.isRtpMessage(msg)) {
|
|
91
|
+
this.emit("rtp");
|
|
92
|
+
this.socket.send(msg, rtpPort);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Save this RTCP message for heartbeat purposes for the RTP port. This works because RTCP packets will be ignored by ffmpeg on the RTP port, effectively
|
|
96
|
+
// providing a heartbeat to ensure FFmpeg doesn't timeout if there's an extended delay between data transmission.
|
|
97
|
+
this.heartbeatMsg = Buffer.from(msg);
|
|
98
|
+
// Clear the old heartbeat timer.
|
|
99
|
+
clearTimeout(this.heartbeatTimer);
|
|
100
|
+
this.heartbeat(rtpPort);
|
|
101
|
+
// RTCP control packets should go to the RTCP port.
|
|
102
|
+
this.socket.send(msg, rtcpPort);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this.log?.debug("Creating an RtpDemuxer instance - inbound port: %s, RTCP port: %s, RTP port: %s.", this.inputPort, rtcpPort, rtpPort);
|
|
106
|
+
// Take the socket live.
|
|
107
|
+
this.socket.bind(this.inputPort);
|
|
108
|
+
this._isRunning = true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sends periodic heartbeat messages to the RTP port to keep the FFmpeg process alive.
|
|
112
|
+
*
|
|
113
|
+
* This is necessary because FFmpeg times out input streams if it does not receive data for more than five seconds.
|
|
114
|
+
*
|
|
115
|
+
* @param port - The RTP port to send the heartbeat to.
|
|
116
|
+
*/
|
|
117
|
+
heartbeat(port) {
|
|
118
|
+
// Clear the old heartbeat timer.
|
|
119
|
+
clearTimeout(this.heartbeatTimer);
|
|
120
|
+
// Send a heartbeat to FFmpeg every few seconds to keep things open. FFmpeg has a five-second timeout in reading input, and we want to be comfortably within the
|
|
121
|
+
// margin for error to ensure the process continues to run.
|
|
122
|
+
this.heartbeatTimer = setTimeout(() => {
|
|
123
|
+
this.log?.debug("Sending ffmpeg a heartbeat.");
|
|
124
|
+
this.socket.send(this.heartbeatMsg, port);
|
|
125
|
+
this.heartbeat(port);
|
|
126
|
+
}, TWOWAY_HEARTBEAT_INTERVAL * 1000);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Closes the demuxer, its socket, and any heartbeat timers.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* demuxer.close();
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
close() {
|
|
138
|
+
this.log?.debug("Closing the RtpDemuxer instance on port %s.", this.inputPort);
|
|
139
|
+
clearTimeout(this.heartbeatTimer);
|
|
140
|
+
this.socket.close();
|
|
141
|
+
this._isRunning = false;
|
|
142
|
+
this.emit("rtp");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extracts the RTP payload type from a UDP packet.
|
|
146
|
+
*
|
|
147
|
+
* Used internally to distinguish RTP from RTCP messages.
|
|
148
|
+
*
|
|
149
|
+
* @param message - The UDP packet buffer.
|
|
150
|
+
*
|
|
151
|
+
* @returns The RTP payload type as a number.
|
|
152
|
+
*/
|
|
153
|
+
getPayloadType(message) {
|
|
154
|
+
return message.readUInt8(1) & 0x7f;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Determines if the provided UDP packet is an RTP message.
|
|
158
|
+
*
|
|
159
|
+
* @param message - The UDP packet buffer.
|
|
160
|
+
*
|
|
161
|
+
* @returns `true` if the packet is RTP, `false` if RTCP.
|
|
162
|
+
*/
|
|
163
|
+
isRtpMessage(message) {
|
|
164
|
+
const payloadType = this.getPayloadType(message);
|
|
165
|
+
return (payloadType > 90) || (payloadType === 0);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Indicates if the demuxer is running and accepting packets.
|
|
169
|
+
*
|
|
170
|
+
* @returns `true` if running, otherwise `false`.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
*
|
|
174
|
+
* ```ts
|
|
175
|
+
* if(demuxer.isRunning) {
|
|
176
|
+
* // Demuxer is active.
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
get isRunning() {
|
|
181
|
+
return this._isRunning;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Allocates and tracks UDP ports for RTP and RTCP to avoid port conflicts in environments with high network activity.
|
|
186
|
+
*
|
|
187
|
+
* This utility class is used to find and reserve available UDP ports for demuxing FFmpeg streams or other network activities.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
*
|
|
191
|
+
* ```ts
|
|
192
|
+
* const allocator = new RtpPortAllocator();
|
|
193
|
+
*
|
|
194
|
+
* // Reserve two consecutive ports for RTP and RTCP.
|
|
195
|
+
* const rtpPort = await allocator.reserve("ipv4", 2);
|
|
196
|
+
*
|
|
197
|
+
* // Cancel reservation if not needed.
|
|
198
|
+
* allocator.cancel(rtpPort);
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @category FFmpeg
|
|
202
|
+
*/
|
|
203
|
+
export class RtpPortAllocator {
|
|
204
|
+
portsInUse;
|
|
205
|
+
/**
|
|
206
|
+
* Instantiates a new RTP port allocator and tracker.
|
|
207
|
+
*/
|
|
208
|
+
constructor() {
|
|
209
|
+
// Initialize our in use tracker.
|
|
210
|
+
this.portsInUse = {};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Finds an available UDP port by attempting to bind a new socket.
|
|
214
|
+
*
|
|
215
|
+
* Loops until an available port not already marked as in use is found.
|
|
216
|
+
*
|
|
217
|
+
* @param ipFamily - "ipv4" or "ipv6".
|
|
218
|
+
* @param port - Optional. The port to try to bind to. If 0, selects a random port.
|
|
219
|
+
*
|
|
220
|
+
* @returns A promise resolving to the available port number, or `-1` on error.
|
|
221
|
+
*/
|
|
222
|
+
async getPort(ipFamily, port = 0) {
|
|
223
|
+
try {
|
|
224
|
+
// Keep looping until we find what we're looking for: local UDP ports that are unspoken for.
|
|
225
|
+
for (;;) {
|
|
226
|
+
// Create a datagram socket, so we can use it to find a port.
|
|
227
|
+
const socket = createSocket(ipFamily === "ipv6" ? "udp6" : "udp4");
|
|
228
|
+
// Exclude this socket from Node's reference counting so we don't have issues later.
|
|
229
|
+
socket.unref();
|
|
230
|
+
// Listen for the bind event.
|
|
231
|
+
const eventListener = once(socket, "listening");
|
|
232
|
+
// Bind to the port in question. If port is set to 0, we'll get a randomly generated port generated for us.
|
|
233
|
+
socket.bind(port);
|
|
234
|
+
// Ensure we wait for the socket to be bound.
|
|
235
|
+
// eslint-disable-next-line no-await-in-loop
|
|
236
|
+
await eventListener;
|
|
237
|
+
// Retrieve the port number we've gotten from the bind request.
|
|
238
|
+
const assignedPort = socket.address().port;
|
|
239
|
+
// We're done with the socket, let's cleanup.
|
|
240
|
+
socket.close();
|
|
241
|
+
// Check to see if the port is one we're already using. If it is, try again.
|
|
242
|
+
if (this.portsInUse[assignedPort]) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
// Now let's mark the port in use.
|
|
246
|
+
this.portsInUse[assignedPort] = true;
|
|
247
|
+
// Return the port.
|
|
248
|
+
return assignedPort;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
return -1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Internal method to reserve one or two consecutive UDP ports for FFmpeg or network use.
|
|
257
|
+
*
|
|
258
|
+
* 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.
|
|
259
|
+
*
|
|
260
|
+
* @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4".
|
|
261
|
+
* @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1.
|
|
262
|
+
* @param attempts - Internal. The number of allocation attempts. Used for recursion.
|
|
263
|
+
*
|
|
264
|
+
* @returns A promise resolving to the first reserved port, or `-1` if unavailable.
|
|
265
|
+
*/
|
|
266
|
+
async _reserve(ipFamily = "ipv4", portCount = 1, attempts = 0) {
|
|
267
|
+
// Sanity check and make sure we're not requesting any more than two ports at a time, or if we've exceeded our attempt limit.
|
|
268
|
+
if (![1, 2].includes(portCount) || (attempts > 10)) {
|
|
269
|
+
return -1;
|
|
270
|
+
}
|
|
271
|
+
let firstPort = 0;
|
|
272
|
+
// Find the appropriate number of ports being requested.
|
|
273
|
+
for (let i = 0; i < portCount; i++) {
|
|
274
|
+
// eslint-disable-next-line no-await-in-loop
|
|
275
|
+
const assignedPort = await this.getPort(ipFamily, firstPort ? firstPort + 1 : 0);
|
|
276
|
+
// We haven't gotten a port, let's try again.
|
|
277
|
+
if (assignedPort === -1) {
|
|
278
|
+
// If we've gotten the first port of a pair of ports, make sure we release it here.
|
|
279
|
+
if (firstPort) {
|
|
280
|
+
this.cancel(firstPort);
|
|
281
|
+
}
|
|
282
|
+
// We still haven't found what we're looking for...keep looking.
|
|
283
|
+
return this._reserve(ipFamily, portCount, attempts++);
|
|
284
|
+
}
|
|
285
|
+
// We've seen the first port we may be looking for, let's save it.
|
|
286
|
+
if (!firstPort) {
|
|
287
|
+
firstPort = assignedPort;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Return the first port we've found.
|
|
291
|
+
return firstPort;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Reserves one or two consecutive UDP ports for FFmpeg or network use.
|
|
295
|
+
*
|
|
296
|
+
* 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.
|
|
297
|
+
*
|
|
298
|
+
* @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4".
|
|
299
|
+
* @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1.
|
|
300
|
+
*
|
|
301
|
+
* @returns A promise resolving to the first reserved port, or `-1` if unavailable.
|
|
302
|
+
*
|
|
303
|
+
* @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
|
|
304
|
+
* 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
|
|
305
|
+
* use cases) that we we are reserving consecutive ports only.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
*
|
|
309
|
+
* ```ts
|
|
310
|
+
* // Reserve a single port.
|
|
311
|
+
* const port = await allocator.reserve();
|
|
312
|
+
*
|
|
313
|
+
* // Reserve two consecutive ports for RTP/RTCP.
|
|
314
|
+
* const rtpPort = await allocator.reserve("ipv4", 2);
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
async reserve(ipFamily = "ipv4", portCount = 1) {
|
|
318
|
+
return this._reserve(ipFamily, portCount);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Cancels and releases a previously reserved port, making it available for future use.
|
|
322
|
+
*
|
|
323
|
+
* @param port - The port number to release.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
*
|
|
327
|
+
* ```ts
|
|
328
|
+
* allocator.cancel(50000);
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
cancel(port) {
|
|
332
|
+
delete this.portsInUse[port];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=rtp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rtp.js","sourceRoot":"","sources":["../../src/ffmpeg/rtp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const HOMEKIT_IDR_INTERVAL = 5;
|
|
2
|
+
export declare const HOMEKIT_STREAMING_HEADROOM = 64;
|
|
3
|
+
export declare const HKSV_FRAGMENT_LENGTH = 4000;
|
|
4
|
+
export declare const HKSV_IDR_INTERVAL = 4;
|
|
5
|
+
export declare const HKSV_TIMEOUT = 4500;
|
|
6
|
+
export declare const RPI_GPU_MINIMUM = 128;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Copyright(C) 2022-2025, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* ffmpeg/settings.ts: Settings and constants for HomeKit FFmpeg utilities.
|
|
4
|
+
*/
|
|
5
|
+
// HomeKit prefers an I-frame interval of 5 seconds when livestreaming.
|
|
6
|
+
export const HOMEKIT_IDR_INTERVAL = 5;
|
|
7
|
+
// Additional headroom for bitrates beyond what HomeKit is requesting when streaming to improve quality with a minor additional bandwidth cost.
|
|
8
|
+
export const HOMEKIT_STREAMING_HEADROOM = 64;
|
|
9
|
+
// HomeKit Secure Video fragment length, in milliseconds. HomeKit only supports this value currently.
|
|
10
|
+
export const HKSV_FRAGMENT_LENGTH = 4000;
|
|
11
|
+
// HomeKit prefers a default I-frame interval of 4 seconds for HKSV event recordings.
|
|
12
|
+
export const HKSV_IDR_INTERVAL = 4;
|
|
13
|
+
// HomeKit Secure Video communication timeout threshold, in milliseconds. HKSV has a strict 5 second threshold for communication, so we set this a little below that.
|
|
14
|
+
export const HKSV_TIMEOUT = 4500;
|
|
15
|
+
// Minimum required GPU memory on a Raspberry Pi to enable hardware acceleration.
|
|
16
|
+
export const RPI_GPU_MINIMUM = 128;
|
|
17
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/ffmpeg/settings.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC,+IAA+I;AAC/I,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C,qGAAqG;AACrG,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,qFAAqF;AACrF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC,qKAAqK;AACrK,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AAEjC,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC"}
|