homebridge-unifi-protect 6.22.0 → 7.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 (82) hide show
  1. package/README.md +29 -29
  2. package/config.schema.json +5 -15
  3. package/dist/devices/protect-camera-package.d.ts +0 -2
  4. package/dist/devices/protect-camera-package.js +4 -12
  5. package/dist/devices/protect-camera-package.js.map +1 -1
  6. package/dist/devices/protect-camera.d.ts +0 -3
  7. package/dist/devices/protect-camera.js +21 -119
  8. package/dist/devices/protect-camera.js.map +1 -1
  9. package/dist/devices/protect-chime.js +2 -6
  10. package/dist/devices/protect-chime.js.map +1 -1
  11. package/dist/devices/protect-device.d.ts +9 -5
  12. package/dist/devices/protect-device.js +17 -20
  13. package/dist/devices/protect-device.js.map +1 -1
  14. package/dist/devices/protect-doorbell.d.ts +0 -2
  15. package/dist/devices/protect-doorbell.js +39 -58
  16. package/dist/devices/protect-doorbell.js.map +1 -1
  17. package/dist/devices/protect-light.js +1 -3
  18. package/dist/devices/protect-light.js.map +1 -1
  19. package/dist/devices/protect-liveviews.js +2 -1
  20. package/dist/devices/protect-liveviews.js.map +1 -1
  21. package/dist/devices/protect-nvr-systeminfo.js +1 -1
  22. package/dist/devices/protect-nvr-systeminfo.js.map +1 -1
  23. package/dist/devices/protect-securitysystem.js +4 -4
  24. package/dist/devices/protect-securitysystem.js.map +1 -1
  25. package/dist/devices/protect-sensor.js +2 -6
  26. package/dist/devices/protect-sensor.js.map +1 -1
  27. package/dist/ffmpeg/protect-ffmpeg-codecs.js.map +1 -1
  28. package/dist/ffmpeg/protect-ffmpeg-exec.js.map +1 -1
  29. package/dist/ffmpeg/protect-ffmpeg-options.js +2 -20
  30. package/dist/ffmpeg/protect-ffmpeg-options.js.map +1 -1
  31. package/dist/ffmpeg/protect-ffmpeg-record.js +2 -1
  32. package/dist/ffmpeg/protect-ffmpeg-record.js.map +1 -1
  33. package/dist/ffmpeg/protect-ffmpeg-stream.js +14 -8
  34. package/dist/ffmpeg/protect-ffmpeg-stream.js.map +1 -1
  35. package/dist/ffmpeg/protect-ffmpeg.d.ts +2 -2
  36. package/dist/ffmpeg/protect-ffmpeg.js.map +1 -1
  37. package/dist/protect-events.d.ts +1 -1
  38. package/dist/protect-events.js +36 -33
  39. package/dist/protect-events.js.map +1 -1
  40. package/dist/protect-nvr.d.ts +7 -11
  41. package/dist/protect-nvr.js +14 -60
  42. package/dist/protect-nvr.js.map +1 -1
  43. package/dist/protect-options.d.ts +8 -23
  44. package/dist/protect-options.js +4 -128
  45. package/dist/protect-options.js.map +1 -1
  46. package/dist/protect-platform.d.ts +2 -4
  47. package/dist/protect-platform.js +3 -28
  48. package/dist/protect-platform.js.map +1 -1
  49. package/dist/protect-record.d.ts +2 -1
  50. package/dist/protect-record.js +21 -27
  51. package/dist/protect-record.js.map +1 -1
  52. package/dist/protect-snapshot.d.ts +2 -2
  53. package/dist/protect-snapshot.js +7 -3
  54. package/dist/protect-snapshot.js.map +1 -1
  55. package/dist/protect-stream.d.ts +3 -3
  56. package/dist/protect-stream.js +87 -90
  57. package/dist/protect-stream.js.map +1 -1
  58. package/dist/protect-timeshift.d.ts +9 -3
  59. package/dist/protect-timeshift.js +76 -24
  60. package/dist/protect-timeshift.js.map +1 -1
  61. package/dist/protect-types.d.ts +0 -7
  62. package/dist/protect-types.js +0 -1
  63. package/dist/protect-types.js.map +1 -1
  64. package/dist/settings.d.ts +2 -1
  65. package/dist/settings.js +4 -2
  66. package/dist/settings.js.map +1 -1
  67. package/homebridge-ui/public/index.html +42 -21
  68. package/homebridge-ui/public/lib/featureoptions.js +376 -0
  69. package/homebridge-ui/public/lib/featureoptions.js.map +1 -0
  70. package/homebridge-ui/public/lib/webUi-featureoptions.mjs +836 -0
  71. package/homebridge-ui/public/lib/webUi.mjs +184 -0
  72. package/homebridge-ui/public/ui.mjs +208 -124
  73. package/homebridge-ui/server.js +13 -39
  74. package/package.json +22 -21
  75. package/dist/protect-mqtt.d.ts +0 -20
  76. package/dist/protect-mqtt.js +0 -177
  77. package/dist/protect-mqtt.js.map +0 -1
  78. package/dist/protect-rtp.d.ts +0 -26
  79. package/dist/protect-rtp.js +0 -180
  80. package/dist/protect-rtp.js.map +0 -1
  81. package/homebridge-ui/public/lib/featureoptions.mjs +0 -201
  82. package/homebridge-ui/public/protect-featureoptions.mjs +0 -736
@@ -7,7 +7,7 @@
7
7
  */
8
8
  "use strict";
9
9
 
10
- import { featureOptionCategories, featureOptions, isOptionEnabled } from "../dist/protect-options.js";
10
+ import { featureOptionCategories, featureOptions } from "../dist/protect-options.js";
11
11
  import { HomebridgePluginUiServer } from "@homebridge/plugin-ui-utils";
12
12
  import { ProtectApi } from "unifi-protect";
13
13
  import util from "node:util";
@@ -16,7 +16,8 @@ class PluginUiServer extends HomebridgePluginUiServer {
16
16
 
17
17
  errorInfo;
18
18
 
19
- constructor () {
19
+ constructor() {
20
+
20
21
  super();
21
22
 
22
23
  this.errorInfo = "";
@@ -44,6 +45,7 @@ class PluginUiServer extends HomebridgePluginUiServer {
44
45
  return this.errorInfo;
45
46
  } catch(err) {
46
47
 
48
+ // eslint-disable-next-line no-console
47
49
  console.log(err);
48
50
 
49
51
  // Return nothing if we error out for some reason.
@@ -62,22 +64,17 @@ class PluginUiServer extends HomebridgePluginUiServer {
62
64
 
63
65
  const log = {
64
66
 
65
- debug: (message, parameters) => {},
67
+ debug: () => {},
66
68
  error: (message, parameters = []) => {
67
69
 
68
70
  // Save the error to inform the user in the webUI.
69
- if(!!parameters?.[Symbol.iterator]) {
70
-
71
- this.errorInfo = util.format(message, ...parameters);
72
- } else {
73
-
74
- this.errorInfo = util.format(message, parameters);
75
- }
71
+ this.errorInfo = util.format(message, ...(Array.isArray(parameters) ? parameters : [parameters]));
76
72
 
73
+ // eslint-disable-next-line no-console
77
74
  console.error(this.errorInfo);
78
75
  },
79
- info: (message, parameters) => {},
80
- warn: (message, parameters = []) => {}
76
+ info: () => {},
77
+ warn: () => {}
81
78
  };
82
79
 
83
80
  // Connect to the Protect controller.
@@ -142,9 +139,11 @@ class PluginUiServer extends HomebridgePluginUiServer {
142
139
  return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
143
140
  });
144
141
 
145
- return [ ufpApi.bootstrap.nvr, ...ufpApi.bootstrap.cameras, ...ufpApi.bootstrap.chimes, ...ufpApi.bootstrap.lights, ...ufpApi.bootstrap.sensors, ...ufpApi.bootstrap.viewers ];
142
+ return [ ufpApi.bootstrap.nvr, ...ufpApi.bootstrap.cameras, ...ufpApi.bootstrap.chimes, ...ufpApi.bootstrap.lights, ...ufpApi.bootstrap.sensors,
143
+ ...ufpApi.bootstrap.viewers ];
146
144
  } catch(err) {
147
145
 
146
+ // eslint-disable-next-line no-console
148
147
  console.log(err);
149
148
 
150
149
  // Return nothing if we error out for some reason.
@@ -157,32 +156,7 @@ class PluginUiServer extends HomebridgePluginUiServer {
157
156
  #registerGetOptions() {
158
157
 
159
158
  // Return the list of options configured for a given Protect device.
160
- this.onRequest("/getOptions", async(request) => {
161
-
162
- try {
163
-
164
- const optionSet = {};
165
-
166
- // Loop through all the feature option categories.
167
- for(const category of featureOptionCategories) {
168
-
169
- optionSet[category.name] = [];
170
-
171
- for(const options of featureOptions[category.name]) {
172
-
173
- options.value = isOptionEnabled(request.configOptions, request.nvrUfp, request.deviceUfp, category.name + "." + options.name, options.default);
174
- optionSet[category.name].push(options);
175
- }
176
- }
177
-
178
- return { categories: featureOptionCategories, options: optionSet };
179
-
180
- } catch(err) {
181
-
182
- // Return nothing if we error out for some reason.
183
- return {};
184
- }
185
- });
159
+ this.onRequest("/getOptions", () => ({ categories: featureOptionCategories, options: featureOptions }));
186
160
  }
187
161
  }
188
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-unifi-protect",
3
- "version": "6.22.0",
3
+ "version": "7.0.0",
4
4
  "displayName": "Homebridge UniFi Protect",
5
5
  "description": "Homebridge UniFi Protect plugin providing complete HomeKit integration for the UniFi Protect ecosystem with full support for most features including autoconfiguration, motion detection, multiple controllers, and realtime updates.",
6
6
  "author": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "type": "module",
20
20
  "engines": {
21
- "homebridge": ">=1.6.0",
21
+ "homebridge": ">=1.8.0",
22
22
  "node": ">=18"
23
23
  },
24
24
  "keywords": [
@@ -64,34 +64,35 @@
64
64
  "rtsp"
65
65
  ],
66
66
  "scripts": {
67
- "build": "rimraf ./dist && tsc",
68
- "clean": "rimraf ./dist",
69
- "lint": "eslint src/**.ts src/devices/**.ts src/ffmpeg/**.ts",
70
- "jlint": "eslint homebridge-ui/public/**.mjs",
67
+ "prebuild": "npm run clean && npm run build-ui",
68
+ "build": "tsc",
69
+ "build-ui": "shx mkdir -p homebridge-ui/public/lib && shx cp \"node_modules/homebridge-plugin-utils/dist/ui/**/*.@(js|mjs){,.map}\" homebridge-ui/public/lib",
70
+ "clean": "shx rm -rf dist homebridge-ui/public/lib",
71
+ "prelint": "npm run build-ui",
72
+ "lint": "eslint eslint.config.mjs src/**.ts src/devices/**.ts src/ffmpeg/**.ts homebridge-ui/*.js homebridge-ui/public/**/*.mjs",
71
73
  "postpublish": "npm run clean",
72
- "prepublishOnly": "npm run lint && npm run build",
73
- "test": "eslint src/**.ts src/devices/**.ts src/ffmpeg/**.ts",
74
- "watch": "npm run build && npm link && nodemon"
74
+ "prepublishOnly": "npm run lint && npm run build"
75
75
  },
76
76
  "main": "dist/index.js",
77
77
  "dependencies": {
78
78
  "@homebridge/plugin-ui-utils": "1.0.3",
79
79
  "ffmpeg-for-homebridge": "2.1.1",
80
- "mqtt": "5.5.4",
81
- "unifi-protect": "^4.9.0",
82
- "ws": "8.16.0"
80
+ "homebridge-plugin-utils": "^1.3.0",
81
+ "unifi-protect": "^4.11.0",
82
+ "ws": "8.17.0"
83
83
  },
84
84
  "devDependencies": {
85
- "@stylistic/eslint-plugin": "1.7.2",
86
- "@types/node": "20.12.7",
87
- "@types/readable-stream": "4.0.11",
85
+ "@stylistic/eslint-plugin": "2.1.0",
86
+ "@types/node": "20.14.0",
87
+ "@types/readable-stream": "4.0.14",
88
88
  "@types/ws": "8.5.10",
89
- "@typescript-eslint/eslint-plugin": "7.7.1",
90
- "@typescript-eslint/parser": "7.7.1",
91
89
  "eslint": "8.57.0",
92
- "homebridge": "1.8.1",
93
- "nodemon": "3.1.0",
94
- "rimraf": "5.0.5",
95
- "typescript": "5.4.5"
90
+ "homebridge": "1.8.2",
91
+ "shx": "^0.3.4",
92
+ "typescript": "5.4.5",
93
+ "typescript-eslint": "^7.12.0"
94
+ },
95
+ "optionalDependencies": {
96
+ "bufferutil": "^4.0.8"
96
97
  }
97
98
  }
@@ -1,20 +0,0 @@
1
- /// <reference types="node" />
2
- import { PlatformAccessory } from "homebridge";
3
- import { ProtectNvr } from "./protect-nvr.js";
4
- export declare class ProtectMqtt {
5
- private config;
6
- private isConnected;
7
- private log;
8
- private mqtt;
9
- private nvr;
10
- private subscriptions;
11
- private ufpApi;
12
- constructor(nvr: ProtectNvr);
13
- private configure;
14
- publish(accessory: PlatformAccessory | string, topic: string, message: string): void;
15
- subscribe(accessory: PlatformAccessory | string, topic: string, callback: (cbBuffer: Buffer) => void): void;
16
- subscribeGet(accessory: PlatformAccessory | string, topic: string, type: string, getValue: () => string): void;
17
- subscribeSet(accessory: PlatformAccessory | string, topic: string, type: string, setValue: (value: string, rawValue: string) => Promise<void> | void): void;
18
- unsubscribe(accessory: PlatformAccessory | string, topic: string): void;
19
- private expandTopic;
20
- }
@@ -1,177 +0,0 @@
1
- /* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
- *
3
- * protect-mqtt.ts: MQTT connectivity class for UniFi Protect.
4
- */
5
- import mqtt from "mqtt";
6
- import { PROTECT_MQTT_RECONNECT_INTERVAL } from "./settings.js";
7
- export class ProtectMqtt {
8
- config;
9
- isConnected;
10
- log;
11
- mqtt;
12
- nvr;
13
- subscriptions;
14
- ufpApi;
15
- constructor(nvr) {
16
- this.config = nvr.config;
17
- this.isConnected = false;
18
- this.log = nvr.log;
19
- this.mqtt = null;
20
- this.nvr = nvr;
21
- this.subscriptions = {};
22
- this.ufpApi = nvr.ufpApi;
23
- if (!this.config.mqttUrl) {
24
- return;
25
- }
26
- this.configure();
27
- }
28
- // Connect to the MQTT broker.
29
- configure() {
30
- // Try to connect to the MQTT broker and make sure we catch any URL errors.
31
- try {
32
- this.mqtt = mqtt.connect(this.config.mqttUrl, { reconnectPeriod: PROTECT_MQTT_RECONNECT_INTERVAL * 1000, rejectUnauthorized: false });
33
- }
34
- catch (error) {
35
- if (error instanceof Error) {
36
- switch (error.message) {
37
- case "Missing protocol":
38
- this.log.error("MQTT Broker: Invalid URL provided: %s.", this.config.mqttUrl);
39
- break;
40
- default:
41
- this.log.error("MQTT Broker: Error: %s.", error.message);
42
- break;
43
- }
44
- }
45
- }
46
- // We've been unable to even attempt to connect. It's likely we have a configuration issue - we're done here.
47
- if (!this.mqtt) {
48
- return;
49
- }
50
- // Notify the user when we connect to the broker.
51
- this.mqtt.on("connect", () => {
52
- this.isConnected = true;
53
- // Magic incantation to redact passwords.
54
- const redact = /^(?<pre>.*:\/{0,2}.*:)(?<pass>.*)(?<post>@.*)/;
55
- this.log.info("Connected to MQTT broker: %s (topic: %s).", this.config.mqttUrl.replace(redact, "$<pre>REDACTED$<post>"), this.config.mqttTopic);
56
- });
57
- // Notify the user when we've disconnected.
58
- this.mqtt.on("close", () => {
59
- if (this.isConnected) {
60
- this.isConnected = false;
61
- // Magic incantation to redact passwords.
62
- const redact = /^(?<pre>.*:\/{0,2}.*:)(?<pass>.*)(?<post>@.*)/;
63
- this.log.info("Disconnected from MQTT broker: %s.", this.config.mqttUrl.replace(redact, "$<pre>REDACTED$<post>"));
64
- }
65
- });
66
- // Process inbound messages and pass it to the right message handler.
67
- this.mqtt.on("message", (topic, message) => {
68
- if (this.subscriptions[topic]) {
69
- this.subscriptions[topic](message);
70
- }
71
- });
72
- // Notify the user when there's a connectivity error.
73
- this.mqtt.on("error", (error) => {
74
- switch (error.code) {
75
- case "ECONNREFUSED":
76
- this.log.error("MQTT Broker: Connection refused (url: %s). Will retry again in %s minute%s.", this.config.mqttUrl, PROTECT_MQTT_RECONNECT_INTERVAL / 60, PROTECT_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
77
- break;
78
- case "ECONNRESET":
79
- this.log.error("MQTT Broker: Connection reset (url: %s). Will retry again in %s minute%s.", this.config.mqttUrl, PROTECT_MQTT_RECONNECT_INTERVAL / 60, PROTECT_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
80
- break;
81
- case "ENOTFOUND":
82
- this.mqtt?.end(true);
83
- this.log.error("MQTT Broker: Hostname or IP address not found. (url: %s).", this.config.mqttUrl);
84
- break;
85
- default:
86
- this.log.error("MQTT Broker: %s (url: %s). Will retry again in %s minute%s.", error, this.config.mqttUrl, PROTECT_MQTT_RECONNECT_INTERVAL / 60, PROTECT_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
87
- break;
88
- }
89
- });
90
- }
91
- // Publish an MQTT event to a broker.
92
- publish(accessory, topic, message) {
93
- const expandedTopic = this.expandTopic(accessory, topic);
94
- // No valid topic returned, we're done.
95
- if (!expandedTopic) {
96
- return;
97
- }
98
- this.log.debug("MQTT publish: %s Message: %s.", expandedTopic, message);
99
- // By default, we publish as: unifi/protect/mac/event/name
100
- this.mqtt?.publish(expandedTopic, message);
101
- }
102
- // Subscribe to an MQTT topic.
103
- subscribe(accessory, topic, callback) {
104
- const expandedTopic = this.expandTopic(accessory, topic);
105
- // No valid topic returned, we're done.
106
- if (!expandedTopic) {
107
- return;
108
- }
109
- this.log.debug("MQTT subscribe: %s.", expandedTopic);
110
- // Add to our callback list.
111
- this.subscriptions[expandedTopic] = callback;
112
- // Tell MQTT we're subscribing to this event.
113
- // By default, we subscribe as: unifi/protect/mac/event/name.
114
- this.mqtt?.subscribe(expandedTopic);
115
- }
116
- // Subscribe to a specific MQTT topic and publish a value on a get request.
117
- subscribeGet(accessory, topic, type, getValue) {
118
- // Return the current status of a given sensor.
119
- this.subscribe(accessory, topic + "/get", (message) => {
120
- const value = message.toString().toLowerCase();
121
- // When we get the right message, we return the system information JSON.
122
- if (value !== "true") {
123
- return;
124
- }
125
- this.publish(accessory, topic, getValue());
126
- ((typeof accessory === "string") ? this.log : (this.nvr.configuredDevices[accessory.UUID]?.log ?? this.log)).info("MQTT: %s status published.", type);
127
- });
128
- }
129
- // Subscribe to a specific MQTT topic and set a value on a set request.
130
- subscribeSet(accessory, topic, type, setValue) {
131
- // Return the current status of a given sensor.
132
- this.subscribe(accessory, topic + "/set", (message) => {
133
- const value = message.toString().toLowerCase();
134
- const logResult = () => {
135
- ((typeof accessory === "string") ? this.log : (this.nvr.configuredDevices[accessory.UUID]?.log ?? this.log))
136
- .info("MQTT: set message received for %s: %s.", type, value);
137
- };
138
- // Set our value and inform the user.
139
- const result = setValue(value, message.toString());
140
- if (result && typeof result.then === "function") {
141
- result.then(() => {
142
- logResult();
143
- }).catch(error => {
144
- ((typeof accessory === "string") ? this.log : (this.nvr.configuredDevices[accessory.UUID]?.log ?? this.log))
145
- .error("MQTT: error seting message received for %s: %s. %s", type, value, error);
146
- });
147
- return;
148
- }
149
- logResult();
150
- });
151
- }
152
- // Unsubscribe to an MQTT topic.
153
- unsubscribe(accessory, topic) {
154
- const expandedTopic = this.expandTopic(accessory, topic);
155
- // No valid topic returned, we're done.
156
- if (!expandedTopic) {
157
- return;
158
- }
159
- delete this.subscriptions[expandedTopic];
160
- }
161
- // Expand a topic to a unique, fully formed one.
162
- expandTopic(accessory, topic) {
163
- // No accessory, we're done.
164
- if (!accessory) {
165
- return null;
166
- }
167
- // Check if we were passed the MAC as an input. Otherwise, assume it's the controller's MAC initially.
168
- let mac = (typeof accessory === "string") ? accessory : accessory.context.nvr;
169
- // Check to see if it's really a Protect device...if it is, use it's MAC address.
170
- if ((typeof accessory !== "string") && ("mac" in accessory.context)) {
171
- mac = accessory.context.mac;
172
- }
173
- // Return our fully expanded topic string.
174
- return this.config.mqttTopic + "/" + mac + "/" + topic;
175
- }
176
- }
177
- //# sourceMappingURL=protect-mqtt.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protect-mqtt.js","sourceRoot":"","sources":["../src/protect-mqtt.ts"],"names":[],"mappings":"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"}
@@ -1,26 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { EventEmitter } from "node:events";
4
- import { ProtectStreamingDelegate } from "./protect-stream.js";
5
- export declare class RtpDemuxer extends EventEmitter {
6
- private delegate;
7
- private heartbeatTimer;
8
- private heartbeatMsg;
9
- private _isRunning;
10
- private log;
11
- private inputPort;
12
- readonly socket: import("dgram").Socket;
13
- constructor(streamingDelegate: ProtectStreamingDelegate, ipFamily: ("ipv4" | "ipv6"), inputPort: number, rtcpPort: number, rtpPort: number);
14
- private heartbeat;
15
- close(): void;
16
- private getPayloadType;
17
- private isRtpMessage;
18
- get isRunning(): boolean;
19
- }
20
- export declare class RtpPortAllocator {
21
- private portsInUse;
22
- constructor();
23
- private getPort;
24
- reserve(ipFamily?: ("ipv4" | "ipv6"), portCount?: (1 | 2), attempts?: number): Promise<number>;
25
- cancel(port: number): void;
26
- }
@@ -1,180 +0,0 @@
1
- /* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
- *
3
- * protect-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
6
- * borrows heavily from both. Thank you for your contributions to the HomeKit world.
7
- */
8
- import { EventEmitter, once } from "node:events";
9
- import { PROTECT_TWOWAY_HEARTBEAT_INTERVAL } from "./settings.js";
10
- import { createSocket } from "node:dgram";
11
- /*
12
- * Here's the problem this class solves: FFmpeg doesn't support multiplexing RTP and RTCP data on a single UDP port (RFC 5761). If it did, we wouldn't need this
13
- * workaround for HomeKit compatibility, which does multiplex RTP and RTCP over a single UDP port.
14
- *
15
- * This class inspects all packets coming in from inputPort and demultiplexes RTP and RTCP traffic to rtpPort and rtcpPort, respectively.
16
- *
17
- * Credit to @dgreif and @brandawg93 who graciously shared their code as a starting point, and their collaboration in answering the questions needed to bring all this
18
- * together. A special thank you to @Sunoo for the many hours of discussion and brainstorming on this and other topics.
19
- */
20
- export class RtpDemuxer extends EventEmitter {
21
- delegate;
22
- heartbeatTimer;
23
- heartbeatMsg;
24
- _isRunning;
25
- log;
26
- inputPort;
27
- socket;
28
- // Create an instance of RtpDemuxer.
29
- constructor(streamingDelegate, ipFamily, inputPort, rtcpPort, rtpPort) {
30
- super();
31
- this._isRunning = false;
32
- this.delegate = streamingDelegate;
33
- this.log = streamingDelegate.log;
34
- this.inputPort = inputPort;
35
- this.socket = createSocket(ipFamily === "ipv6" ? "udp6" : "udp4");
36
- // Catch errors when they happen on our demuxer.
37
- this.socket.on("error", (error) => {
38
- this.log.error("RtpDemuxer Error: %s", error);
39
- this.socket.close();
40
- });
41
- // Split the message into RTP and RTCP packets.
42
- this.socket.on("message", (msg) => {
43
- // Send RTP packets to the RTP port.
44
- if (this.isRtpMessage(msg)) {
45
- this.emit("rtp");
46
- this.socket.send(msg, rtpPort);
47
- }
48
- else {
49
- // Save this RTCP message for heartbeat purposes for the RTP port. This works because RTCP packets will be ignored
50
- // by ffmpeg on the RTP port, effectively providing a heartbeat to ensure FFmpeg doesn't timeout if there's an
51
- // extended delay between data transmission.
52
- this.heartbeatMsg = Buffer.from(msg);
53
- // Clear the old heartbeat timer.
54
- clearTimeout(this.heartbeatTimer);
55
- this.heartbeat(rtpPort);
56
- // RTCP control packets should go to the RTCP port.
57
- this.socket.send(msg, rtcpPort);
58
- }
59
- });
60
- this.log.debug("Creating an RtpDemuxer instance - inbound port: %s, RTCP port: %s, RTP port: %s.", this.inputPort, rtcpPort, rtpPort);
61
- // Take the socket live.
62
- this.socket.bind(this.inputPort);
63
- this._isRunning = true;
64
- }
65
- // Send a regular heartbeat to FFmpeg to ensure the pipe remains open and the process alive.
66
- heartbeat(port) {
67
- // Clear the old heartbeat timer.
68
- clearTimeout(this.heartbeatTimer);
69
- // Send a heartbeat to FFmpeg every few seconds to keep things open. FFmpeg has a five-second timeout
70
- // in reading input, and we want to be comfortably within the margin for error to ensure the process
71
- // continues to run.
72
- this.heartbeatTimer = setTimeout(() => {
73
- this.log.debug("Sending ffmpeg a heartbeat.");
74
- this.socket.send(this.heartbeatMsg, port);
75
- this.heartbeat(port);
76
- }, PROTECT_TWOWAY_HEARTBEAT_INTERVAL * 1000);
77
- }
78
- // Close the socket and cleanup.
79
- close() {
80
- this.log.debug("Closing the RtpDemuxer instance on port %s.", this.inputPort);
81
- clearTimeout(this.heartbeatTimer);
82
- this.socket.close();
83
- this._isRunning = false;
84
- this.emit("rtp");
85
- }
86
- // Retrieve the payload information from a packet to discern what the packet payload is.
87
- getPayloadType(message) {
88
- return message.readUInt8(1) & 0x7f;
89
- }
90
- // Return whether or not a packet is RTP (or not).
91
- isRtpMessage(message) {
92
- const payloadType = this.getPayloadType(message);
93
- return (payloadType > 90) || (payloadType === 0);
94
- }
95
- // Inform people whether we are up and running or not.
96
- get isRunning() {
97
- return this._isRunning;
98
- }
99
- }
100
- /* RTP port allocator class that keeps track of UDP ports that are currently earmarked for use. We need this when allocating ports that we use for various network
101
- * activities such as demuxing FFmpeg or opening up other sockets. Otherwise, we run a risk (especially in environment where there are many such requests) of allocating
102
- * the same port multiple times and end up erroring out unceremoniously.
103
- */
104
- export class RtpPortAllocator {
105
- portsInUse;
106
- // Instantiate our port retrieval.
107
- constructor() {
108
- // Initialize our in use tracker.
109
- this.portsInUse = {};
110
- }
111
- // Find an available UDP port by binding to one to validate it's availability.
112
- async getPort(ipFamily, port = 0) {
113
- try {
114
- // Keep looping until we find what we're looking for: local UDP ports that are unspoken for.
115
- for (;;) {
116
- // Create a datagram socket, so we can use it to find a port.
117
- const socket = createSocket(ipFamily === "ipv6" ? "udp6" : "udp4");
118
- // Exclude this socket from Node's reference counting so we don't have issues later.
119
- socket.unref();
120
- // Listen for the bind event.
121
- const eventListener = once(socket, "listening");
122
- // Bind to the port in question. If port is set to 0, we'll get a randomly generated port generated for us.
123
- socket.bind(port);
124
- // Ensure we wait for the socket to be bound.
125
- // eslint-disable-next-line no-await-in-loop
126
- await eventListener;
127
- // Retrieve the port number we've gotten from the bind request.
128
- const assignedPort = socket.address().port;
129
- // We're done with the socket, let's cleanup.
130
- socket.close();
131
- // Check to see if the port is one we're already using. If it is, try again.
132
- if (this.portsInUse[assignedPort]) {
133
- continue;
134
- }
135
- // Now let's mark the port in use.
136
- this.portsInUse[assignedPort] = true;
137
- // Return the port.
138
- return assignedPort;
139
- }
140
- }
141
- catch (error) {
142
- return -1;
143
- }
144
- }
145
- // Reserve consecutive ports for use with FFmpeg. FFmpeg currently lacks the ability to specify both the RTP and RTCP ports. FFmpeg always assumes, by convention, that
146
- // when you specify an RTP port, the RTCP port is the RTP port + 1. In order to work around that challenge, we need to always ensure that when we reserve multiple ports
147
- // for RTP (primarily for two-way audio) that we we are reserving consecutive ports only.
148
- async reserve(ipFamily = "ipv4", portCount = 1, attempts = 0) {
149
- // 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.
150
- if (![1, 2].includes(portCount) || (attempts > 10)) {
151
- return -1;
152
- }
153
- let firstPort = 0;
154
- // Find the appropriate number of ports being requested.
155
- for (let i = 0; i < portCount; i++) {
156
- // eslint-disable-next-line no-await-in-loop
157
- const assignedPort = await this.getPort(ipFamily, firstPort ? firstPort + 1 : 0);
158
- // We haven't gotten a port, let's try again.
159
- if (assignedPort === -1) {
160
- // If we've gotten the first port of a pair of ports, make sure we release it here.
161
- if (firstPort) {
162
- this.cancel(firstPort);
163
- }
164
- // We still haven't found what we're looking for...keep looking.
165
- return this.reserve(ipFamily, portCount, attempts++);
166
- }
167
- // We've seen the first port we may be looking for, let's save it.
168
- if (!firstPort) {
169
- firstPort = assignedPort;
170
- }
171
- }
172
- // Return the first port we've found.
173
- return firstPort;
174
- }
175
- // Delete a port reservation that's no longer needed.
176
- cancel(port) {
177
- delete this.portsInUse[port];
178
- }
179
- }
180
- //# sourceMappingURL=protect-rtp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protect-rtp.js","sourceRoot":"","sources":["../src/protect-rtp.ts"],"names":[],"mappings":"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"}