node-red-contrib-homekit-bridged 2.0.0-dev.5 → 2.0.0-dev.7

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 (128) hide show
  1. package/build/lib/HAPHostNode.js +183 -141
  2. package/build/lib/HAPServiceNode.js +199 -172
  3. package/build/lib/HAPServiceNode2.js +207 -172
  4. package/build/lib/NRCHKBError.js +23 -2
  5. package/build/lib/PairingQRCode.js +62 -0
  6. package/build/lib/Storage.js +157 -92
  7. package/build/lib/api.js +654 -288
  8. package/build/lib/camera/CameraControl.js +119 -84
  9. package/build/lib/camera/CameraDelegate.js +481 -404
  10. package/build/lib/camera/MP4StreamingServer.js +148 -139
  11. package/build/lib/hap/HAPCharacteristic.js +25 -4
  12. package/build/lib/hap/HAPService.js +25 -4
  13. package/build/lib/hap/eve-app/EveCharacteristics.js +124 -81
  14. package/build/lib/hap/eve-app/EveServices.js +50 -17
  15. package/build/lib/hap/hap-nodejs.js +32 -0
  16. package/build/lib/migration/HomeKitService2Migration.js +34 -0
  17. package/build/lib/migration/NodeMigration.js +75 -0
  18. package/build/lib/types/AccessoryInformationType.js +15 -1
  19. package/build/lib/types/CameraConfigType.js +15 -1
  20. package/build/lib/types/CustomCharacteristicType.js +15 -1
  21. package/build/lib/types/HAPHostConfigType.js +15 -1
  22. package/build/lib/types/HAPHostNodeType.js +15 -1
  23. package/build/lib/types/HAPService2ConfigType.js +15 -1
  24. package/build/lib/types/HAPService2NodeType.js +15 -1
  25. package/build/lib/types/HAPServiceConfigType.js +15 -1
  26. package/build/lib/types/HAPServiceNodeType.js +15 -1
  27. package/build/lib/types/HAPStatusConfigType.js +15 -1
  28. package/build/lib/types/HAPStatusNodeType.js +15 -1
  29. package/build/lib/types/HostType.js +28 -7
  30. package/build/lib/types/NodeType.js +15 -1
  31. package/build/lib/types/PublishTimersType.js +15 -1
  32. package/build/lib/types/UniFiControllerConfigType.js +16 -0
  33. package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.js +28 -7
  34. package/build/lib/types/hap-nodejs/HapCategories.js +64 -43
  35. package/build/lib/types/storage/SerializedHostType.js +15 -1
  36. package/build/lib/types/storage/StorageType.js +34 -10
  37. package/build/lib/unifi/ProtectDiscovery.js +80 -0
  38. package/build/lib/utils/AccessoryUtils.js +152 -110
  39. package/build/lib/utils/BridgeUtils.js +82 -39
  40. package/build/lib/utils/CharacteristicUtils.js +5 -49
  41. package/build/lib/utils/CharacteristicUtils2.js +5 -49
  42. package/build/lib/utils/CharacteristicUtilsBase.js +81 -0
  43. package/build/lib/utils/NodeStatusUtils.js +89 -40
  44. package/build/lib/utils/ServiceUtils.js +434 -375
  45. package/build/lib/utils/ServiceUtils2.js +514 -309
  46. package/build/lib/utils/index.js +10 -11
  47. package/build/nodes/bridge.html +184 -166
  48. package/build/nodes/bridge.js +27 -9
  49. package/build/nodes/locales/en-US/node-red-contrib-homekit-bridged.json +22 -0
  50. package/build/nodes/nrchkb.html +1601 -88
  51. package/build/nodes/nrchkb.js +66 -88
  52. package/build/nodes/plugin-instance.html +499 -0
  53. package/build/nodes/plugin-instance.js +46 -0
  54. package/build/nodes/service.html +517 -299
  55. package/build/nodes/service.js +5 -6
  56. package/build/nodes/service2.html +1683 -460
  57. package/build/nodes/service2.js +5 -8
  58. package/build/nodes/standalone.html +187 -174
  59. package/build/nodes/standalone.js +27 -9
  60. package/build/nodes/status.html +51 -18
  61. package/build/nodes/status.js +47 -40
  62. package/build/nodes/unifi-controller.html +92 -0
  63. package/build/nodes/unifi-controller.js +20 -0
  64. package/build/plugins/embedded/homebridge-camera-ffmpeg/index.js +479 -0
  65. package/build/plugins/embedded/homebridge-unifi-protect/index.js +521 -0
  66. package/build/plugins/embedded/index.js +58 -0
  67. package/build/plugins/nrchkb-homekit-plugins.js +17 -0
  68. package/build/plugins/registry/index.js +203 -0
  69. package/build/plugins/registry/types.js +16 -0
  70. package/build/scripts/migrate-homekit-service-flows.js +47 -0
  71. package/examples/demo/01 - ALL Demos single import.json +1885 -1885
  72. package/examples/demo/02 - Air Purifier.json +279 -279
  73. package/examples/demo/03 - Air Quality sensor with Battery.json +254 -254
  74. package/examples/demo/04 - Dimmable Bulb.json +172 -172
  75. package/examples/demo/05 - Color Bulb (HSV).json +195 -195
  76. package/examples/demo/06 - Fan (simple, 3 speeds).json +240 -240
  77. package/examples/demo/07 - Fan (with speed, oscillate, rotation direction).json +175 -175
  78. package/examples/demo/08 - CO2 detector.json +224 -224
  79. package/examples/demo/09 - CO (carbon monoxide) example.json +255 -255
  80. package/examples/demo/10 - Door window contact sensor.json +234 -234
  81. package/examples/demos (advanced)/01 - Television with inputs and speaker.json +541 -541
  82. package/examples/switch/01 - Plain Switch.json +178 -178
  83. package/package.json +95 -84
  84. package/build/lib/HAPHostNode.d.ts +0 -1
  85. package/build/lib/HAPServiceNode.d.ts +0 -1
  86. package/build/lib/HAPServiceNode2.d.ts +0 -1
  87. package/build/lib/NRCHKBError.d.ts +0 -3
  88. package/build/lib/Storage.d.ts +0 -30
  89. package/build/lib/api.d.ts +0 -1
  90. package/build/lib/camera/CameraControl.d.ts +0 -3
  91. package/build/lib/camera/CameraDelegate.d.ts +0 -38
  92. package/build/lib/camera/MP4StreamingServer.d.ts +0 -26
  93. package/build/lib/hap/HAPCharacteristic.d.ts +0 -9
  94. package/build/lib/hap/HAPService.d.ts +0 -6
  95. package/build/lib/hap/eve-app/EveCharacteristics.d.ts +0 -20
  96. package/build/lib/hap/eve-app/EveServices.d.ts +0 -5
  97. package/build/lib/types/AccessoryInformationType.d.ts +0 -11
  98. package/build/lib/types/CameraConfigType.d.ts +0 -24
  99. package/build/lib/types/CustomCharacteristicType.d.ts +0 -6
  100. package/build/lib/types/HAPHostConfigType.d.ts +0 -22
  101. package/build/lib/types/HAPHostNodeType.d.ts +0 -14
  102. package/build/lib/types/HAPService2ConfigType.d.ts +0 -6
  103. package/build/lib/types/HAPService2NodeType.d.ts +0 -7
  104. package/build/lib/types/HAPServiceConfigType.d.ts +0 -26
  105. package/build/lib/types/HAPServiceNodeType.d.ts +0 -38
  106. package/build/lib/types/HAPStatusConfigType.d.ts +0 -5
  107. package/build/lib/types/HAPStatusNodeType.d.ts +0 -12
  108. package/build/lib/types/HostType.d.ts +0 -5
  109. package/build/lib/types/NodeType.d.ts +0 -3
  110. package/build/lib/types/PublishTimersType.d.ts +0 -4
  111. package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.d.ts +0 -5
  112. package/build/lib/types/hap-nodejs/HapCategories.d.ts +0 -41
  113. package/build/lib/types/storage/SerializedHostType.d.ts +0 -5
  114. package/build/lib/types/storage/StorageType.d.ts +0 -8
  115. package/build/lib/utils/AccessoryUtils.d.ts +0 -1
  116. package/build/lib/utils/BridgeUtils.d.ts +0 -1
  117. package/build/lib/utils/CharacteristicUtils.d.ts +0 -1
  118. package/build/lib/utils/CharacteristicUtils2.d.ts +0 -1
  119. package/build/lib/utils/NodeStatusUtils.d.ts +0 -17
  120. package/build/lib/utils/ServiceUtils.d.ts +0 -1
  121. package/build/lib/utils/ServiceUtils2.d.ts +0 -1
  122. package/build/lib/utils/index.d.ts +0 -1
  123. package/build/nodes/bridge.d.ts +0 -1
  124. package/build/nodes/nrchkb.d.ts +0 -1
  125. package/build/nodes/service.d.ts +0 -1
  126. package/build/nodes/service2.d.ts +0 -1
  127. package/build/nodes/standalone.d.ts +0 -1
  128. package/build/nodes/status.d.ts +0 -1
@@ -1,430 +1,507 @@
1
1
  "use strict";
2
- var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
3
- var __asyncValues = (this && this.__asyncValues) || function (o) {
4
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
5
- var m = o[Symbol.asyncIterator], i;
6
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
7
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
8
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
9
11
  };
10
- var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
11
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
12
- var g = generator.apply(thisArg, _arguments || []), i, q = [];
13
- return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
14
- function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
15
- function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
16
- function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
17
- function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
18
- function fulfill(value) { resume("next", value); }
19
- function reject(value) { resume("throw", value); }
20
- function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
21
19
  };
22
- var __importDefault = (this && this.__importDefault) || function (mod) {
23
- return (mod && mod.__esModule) ? mod : { "default": mod };
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.CameraDelegate = void 0;
27
- const node_assert_1 = __importDefault(require("node:assert"));
28
- const node_child_process_1 = require("node:child_process");
29
- const hap_nodejs_1 = require("@homebridge/hap-nodejs");
30
- const logger_1 = require("@nrchkb/logger");
31
- const MP4StreamingServer_1 = require("./MP4StreamingServer");
32
- const FFMPEGH264ProfileNames = ['baseline', 'main', 'high'];
33
- const FFMPEGH264LevelNames = ['3.1', '3.2', '4.0'];
34
- const ports = new Set();
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var CameraDelegate_exports = {};
30
+ __export(CameraDelegate_exports, {
31
+ CameraDelegate: () => CameraDelegate
32
+ });
33
+ module.exports = __toCommonJS(CameraDelegate_exports);
34
+ var import_node_assert = __toESM(require("node:assert"));
35
+ var import_node_child_process = require("node:child_process");
36
+ var import_hap_nodejs = require("@homebridge/hap-nodejs");
37
+ var import_logger = require("@nrchkb/logger");
38
+ var import_MP4StreamingServer = require("./MP4StreamingServer");
39
+ const FFMPEGH264ProfileNames = ["baseline", "main", "high"];
40
+ const FFMPEGH264LevelNames = ["3.1", "3.2", "4.0"];
41
+ const ports = /* @__PURE__ */ new Set();
42
+ const PENDING_SESSION_TIMEOUT_MS = 3e4;
35
43
  function getPort() {
36
- for (let i = 5011;; i++) {
37
- if (!ports.has(i)) {
38
- ports.add(i);
39
- return i;
40
- }
44
+ for (let i = 5011; ; i++) {
45
+ if (!ports.has(i)) {
46
+ ports.add(i);
47
+ return i;
48
+ }
49
+ }
50
+ }
51
+ function splitCommandLine(command) {
52
+ const args = [];
53
+ let current = "";
54
+ let quote = null;
55
+ let escaped = false;
56
+ for (const char of command.trim()) {
57
+ if (escaped) {
58
+ current += char;
59
+ escaped = false;
60
+ continue;
61
+ }
62
+ if (char === "\\") {
63
+ escaped = true;
64
+ continue;
41
65
  }
66
+ if (quote) {
67
+ if (char === quote) {
68
+ quote = null;
69
+ } else {
70
+ current += char;
71
+ }
72
+ continue;
73
+ }
74
+ if (char === '"' || char === "'") {
75
+ quote = char;
76
+ continue;
77
+ }
78
+ if (char === " ") {
79
+ if (current) {
80
+ args.push(current);
81
+ current = "";
82
+ }
83
+ continue;
84
+ }
85
+ current += char;
86
+ }
87
+ if (current) {
88
+ args.push(current);
89
+ }
90
+ return args;
42
91
  }
43
92
  class CameraDelegate {
44
- constructor(accessory, config) {
45
- this.accessory = accessory;
46
- this.config = config;
47
- this.ffmpegDebugOutput = false;
48
- this.log = (0, logger_1.logger)('NRCHKB', 'CameraDelegate');
49
- this.pendingSessions = {};
50
- this.ongoingSessions = {};
51
- this.handlingStreamingRequest = false;
52
- try {
53
- const name = (accessory === null || accessory === void 0 ? void 0 : accessory.displayName) || (accessory === null || accessory === void 0 ? void 0 : accessory.UUID) || 'UnknownAccessory';
54
- this.log = (0, logger_1.logger)('NRCHKB', 'CameraDelegate', name);
93
+ constructor(accessory, config) {
94
+ this.accessory = accessory;
95
+ this.config = config;
96
+ this.ffmpegDebugOutput = false;
97
+ this.log = (0, import_logger.logger)("NRCHKB", "CameraDelegate");
98
+ // keep track of sessions
99
+ this.pendingSessions = {};
100
+ this.ongoingSessions = {};
101
+ this.handlingStreamingRequest = false;
102
+ try {
103
+ const name = accessory?.displayName || accessory?.UUID || "UnknownAccessory";
104
+ this.log = (0, import_logger.logger)("NRCHKB", "CameraDelegate", name);
105
+ } catch (_) {
106
+ }
107
+ }
108
+ handleSnapshotRequest(request, callback) {
109
+ const ffmpegPath = this.config?.cameraConfigVideoProcessor || "ffmpeg";
110
+ const stillSource = this.config?.cameraConfigStillImageSource?.trim();
111
+ const mainSource = this.config?.cameraConfigSource?.trim();
112
+ let imageSource;
113
+ if (stillSource) {
114
+ imageSource = stillSource;
115
+ } else if (mainSource) {
116
+ imageSource = mainSource;
117
+ } else {
118
+ imageSource = `-f lavfi -i testsrc=s=${request.width}x${request.height}`;
119
+ }
120
+ const ffmpegCommand = `${imageSource} -t 1 -s ${request.width}x${request.height} -f image2 -`;
121
+ const ffmpeg = (0, import_node_child_process.spawn)(ffmpegPath, splitCommandLine(ffmpegCommand), {
122
+ env: process.env
123
+ });
124
+ const snapshotBuffers = [];
125
+ ffmpeg.stdout.on("data", (data) => snapshotBuffers.push(data));
126
+ ffmpeg.stderr.on("data", (data) => {
127
+ if (this.ffmpegDebugOutput || this.config?.cameraConfigDebug) {
128
+ this.log.debug(`SNAPSHOT: ${String(data)}`);
129
+ }
130
+ });
131
+ ffmpeg.on("exit", (code, signal) => {
132
+ if (signal) {
133
+ this.log.error(
134
+ `Snapshot process was killed with signal: ${signal}`
135
+ );
136
+ callback(new Error(`killed with signal ${signal}`));
137
+ } else if (code === 0) {
138
+ this.log.debug(
139
+ `Successfully captured snapshot at ${request.width}x${request.height}`
140
+ );
141
+ callback(void 0, Buffer.concat(snapshotBuffers));
142
+ } else {
143
+ this.log.error(`Snapshot process exited with code ${code}`);
144
+ callback(new Error(`Snapshot process exited with code ${code}`));
145
+ }
146
+ });
147
+ }
148
+ // called when iOS request rtp setup
149
+ prepareStream(request, callback) {
150
+ const sessionId = request.sessionID;
151
+ const targetAddress = request.targetAddress;
152
+ const video = request.video;
153
+ const videoCryptoSuite = video.srtpCryptoSuite;
154
+ const videoSrtpKey = video.srtp_key;
155
+ const videoSrtpSalt = video.srtp_salt;
156
+ const videoSSRC = import_hap_nodejs.CameraController.generateSynchronisationSource();
157
+ const localPort = getPort();
158
+ const timeout = setTimeout(() => {
159
+ const pendingSession = this.pendingSessions[sessionId];
160
+ if (!pendingSession) {
161
+ return;
162
+ }
163
+ ports.delete(pendingSession.localVideoPort);
164
+ delete this.pendingSessions[sessionId];
165
+ this.log.error(`Stream session ${sessionId} timed out before start`);
166
+ }, PENDING_SESSION_TIMEOUT_MS);
167
+ const sessionInfo = {
168
+ address: targetAddress,
169
+ videoPort: video.port,
170
+ localVideoPort: localPort,
171
+ videoCryptoSuite,
172
+ videoSRTP: Buffer.concat([videoSrtpKey, videoSrtpSalt]),
173
+ videoSSRC,
174
+ timeout
175
+ };
176
+ const response = {
177
+ video: {
178
+ port: localPort,
179
+ ssrc: videoSSRC,
180
+ srtp_key: videoSrtpKey,
181
+ srtp_salt: videoSrtpSalt
182
+ }
183
+ // audio is omitted as we do not support audio in this example
184
+ };
185
+ this.pendingSessions[sessionId] = sessionInfo;
186
+ callback(void 0, response);
187
+ }
188
+ // called when the iOS device asks stream to start/stop/reconfigure
189
+ handleStreamRequest(request, callback) {
190
+ const sessionId = request.sessionID;
191
+ switch (request.type) {
192
+ case import_hap_nodejs.StreamRequestTypes.START: {
193
+ const sessionInfo = this.pendingSessions[sessionId];
194
+ if (!sessionInfo) {
195
+ callback(
196
+ new Error(`No pending stream session ${sessionId}`)
197
+ );
198
+ break;
55
199
  }
56
- catch (_) {
200
+ clearTimeout(sessionInfo.timeout);
201
+ const video = request.video;
202
+ const profile = FFMPEGH264ProfileNames[video.profile];
203
+ const level = FFMPEGH264LevelNames[video.level];
204
+ const width = video.width;
205
+ const height = video.height;
206
+ let fps = video.fps;
207
+ const payloadType = video.pt;
208
+ let maxBitrate = video.max_bit_rate;
209
+ const mtu = video.mtu;
210
+ const address = sessionInfo.address;
211
+ const videoPort = sessionInfo.videoPort;
212
+ const localVideoPort = sessionInfo.localVideoPort;
213
+ const ssrc = sessionInfo.videoSSRC;
214
+ const cryptoSuite = sessionInfo.videoCryptoSuite;
215
+ const videoSRTP = sessionInfo.videoSRTP.toString("base64");
216
+ const cfg = this.config;
217
+ const ffmpegPath = cfg?.cameraConfigVideoProcessor || "ffmpeg";
218
+ const source = cfg?.cameraConfigSource || `-f lavfi -i testsrc=s=${width}x${height}:r=${fps}`;
219
+ const vcodec = cfg?.cameraConfigVideoCodec || "libx264";
220
+ const additional = cfg?.cameraConfigAdditionalCommandLine?.trim();
221
+ const mapvideo = cfg?.cameraConfigMapVideo || "0:0";
222
+ const mapaudio = cfg?.cameraConfigMapAudio || "0:1";
223
+ const maxConfigBitrate = cfg?.cameraConfigMaxBitrate;
224
+ if (maxConfigBitrate && maxConfigBitrate > 0 && maxConfigBitrate < maxBitrate) {
225
+ maxBitrate = maxConfigBitrate;
57
226
  }
58
- }
59
- handleSnapshotRequest(request, callback) {
60
- var _a, _b, _c, _d, _e;
61
- const ffmpegPath = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.cameraConfigVideoProcessor) || 'ffmpeg';
62
- const stillSource = (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.cameraConfigStillImageSource) === null || _c === void 0 ? void 0 : _c.trim();
63
- const mainSource = (_e = (_d = this.config) === null || _d === void 0 ? void 0 : _d.cameraConfigSource) === null || _e === void 0 ? void 0 : _e.trim();
64
- let imageSource;
65
- if (stillSource) {
66
- imageSource = stillSource;
227
+ const maxConfigFps = cfg?.cameraConfigMaxFPS;
228
+ if (maxConfigFps && maxConfigFps > 0 && maxConfigFps < fps) {
229
+ fps = maxConfigFps;
230
+ }
231
+ const vf = [];
232
+ if (cfg?.cameraConfigVideoFilter !== null && cfg?.cameraConfigVideoFilter !== void 0 && cfg?.cameraConfigVideoFilter !== "") {
233
+ vf.push(cfg.cameraConfigVideoFilter);
234
+ }
235
+ if (cfg?.cameraConfigHorizontalFlip) vf.push("hflip");
236
+ if (cfg?.cameraConfigVerticalFlip) vf.push("vflip");
237
+ this.log.debug(
238
+ `Starting video stream (${width}x${height}, ${fps} fps, ${maxBitrate} kbps, ${mtu} mtu)...`
239
+ );
240
+ let videoffmpegCommand = `${source} -map ${mapvideo} -vcodec ${vcodec} -pix_fmt yuv420p -r ${fps} -f rawvideo ${additional ? `${additional} ` : ""}${vf.length > 0 ? `-vf ${vf.join(",")} ` : ""}-b:v ${maxBitrate}k -bufsize ${maxBitrate}k -maxrate ${maxBitrate}k -profile:v ${profile} -level:v ${level} -payload_type ${payloadType} -ssrc ${ssrc} -f rtp `;
241
+ if (cryptoSuite !== import_hap_nodejs.SRTPCryptoSuites.NONE) {
242
+ let suite;
243
+ switch (cryptoSuite) {
244
+ case import_hap_nodejs.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80:
245
+ suite = "AES_CM_128_HMAC_SHA1_80";
246
+ break;
247
+ case import_hap_nodejs.SRTPCryptoSuites.AES_CM_256_HMAC_SHA1_80:
248
+ suite = "AES_CM_256_HMAC_SHA1_80";
249
+ break;
250
+ }
251
+ videoffmpegCommand += `-srtp_out_suite ${suite} -srtp_out_params ${videoSRTP} s`;
67
252
  }
68
- else if (mainSource) {
69
- imageSource = mainSource;
253
+ videoffmpegCommand += `rtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${localVideoPort}&pkt_size=${cfg?.cameraConfigPacketSize || mtu}`;
254
+ const audioEnabled = this.config?.cameraConfigAudio === true || this.config?.cameraConfigAudio === "true";
255
+ let audioffmpegCommand = "";
256
+ if (audioEnabled) {
257
+ const acodec = cfg?.cameraConfigAudioCodec || "libfdk_aac";
258
+ const abitrate = 32;
259
+ const asamplerate = 16;
260
+ const apayloadType = 110;
261
+ audioffmpegCommand = ` -map ${mapaudio} -acodec ${acodec} -profile:a aac_eld -flags +global_header -f null -ar ${asamplerate}k -b:a ${abitrate}k -bufsize ${abitrate}k -ac 1 -payload_type ${apayloadType} `;
70
262
  }
71
- else {
72
- imageSource = `-f lavfi -i testsrc=s=${request.width}x${request.height}`;
263
+ if (this.ffmpegDebugOutput || this.config?.cameraConfigDebug) {
264
+ this.log.debug(
265
+ `FFMPEG command: ${ffmpegPath} ${videoffmpegCommand}${audioffmpegCommand}`
266
+ );
73
267
  }
74
- const ffmpegCommand = `${imageSource} -t 1 -s ${request.width}x${request.height} -f image2 -`;
75
- const ffmpeg = (0, node_child_process_1.spawn)(ffmpegPath, ffmpegCommand.split(' '), {
268
+ const cleanupOngoingSession = () => {
269
+ const ongoingSession = this.ongoingSessions[sessionId];
270
+ if (ongoingSession) {
271
+ ports.delete(ongoingSession.localVideoPort);
272
+ delete this.ongoingSessions[sessionId];
273
+ }
274
+ };
275
+ let callbackSettled = false;
276
+ const settleCallback = (error) => {
277
+ if (callbackSettled) {
278
+ return;
279
+ }
280
+ callbackSettled = true;
281
+ callback(error);
282
+ };
283
+ const ffmpegVideo = (0, import_node_child_process.spawn)(
284
+ ffmpegPath,
285
+ splitCommandLine(videoffmpegCommand + audioffmpegCommand),
286
+ {
76
287
  env: process.env
288
+ }
289
+ );
290
+ let started = false;
291
+ ffmpegVideo.stderr.on("data", (data) => {
292
+ this.log.debug(data.toString("utf8"));
293
+ if (!started) {
294
+ started = true;
295
+ this.log.debug("FFMPEG: received first frame");
296
+ settleCallback();
297
+ }
298
+ if (this.ffmpegDebugOutput || this.config?.cameraConfigDebug) {
299
+ this.log.debug(`VIDEO: ${String(data)}`);
300
+ }
77
301
  });
78
- const snapshotBuffers = [];
79
- ffmpeg.stdout.on('data', (data) => snapshotBuffers.push(data));
80
- ffmpeg.stderr.on('data', (data) => {
81
- var _a;
82
- if (this.ffmpegDebugOutput || ((_a = this.config) === null || _a === void 0 ? void 0 : _a.cameraConfigDebug)) {
83
- this.log.debug(`SNAPSHOT: ${String(data)}`);
84
- }
302
+ ffmpegVideo.on("error", (error) => {
303
+ this.log.error(
304
+ `[Video] Failed to start video stream: ${error.message}`
305
+ );
306
+ cleanupOngoingSession();
307
+ settleCallback(new Error("ffmpeg process creation failed!"));
85
308
  });
86
- ffmpeg.on('exit', (code, signal) => {
87
- if (signal) {
88
- this.log.error(`Snapshot process was killed with signal: ${signal}`);
89
- callback(new Error(`killed with signal ${signal}`));
90
- }
91
- else if (code === 0) {
92
- this.log.debug(`Successfully captured snapshot at ${request.width}x${request.height}`);
93
- callback(undefined, Buffer.concat(snapshotBuffers));
94
- }
95
- else {
96
- this.log.error(`Snapshot process exited with code ${code}`);
97
- callback(new Error(`Snapshot process exited with code ${code}`));
309
+ ffmpegVideo.on("exit", (code, signal) => {
310
+ const message = "[Video] ffmpeg exited with code: " + code + " and signal: " + signal;
311
+ if (code == null || code === 255) {
312
+ this.log.debug(`${message} (Video stream stopped!)`);
313
+ } else {
314
+ this.log.error(`${message} (error)`);
315
+ if (!started) {
316
+ settleCallback(new Error(message));
317
+ } else {
318
+ this.controller?.forceStopStreamingSession(
319
+ sessionId
320
+ );
98
321
  }
322
+ }
323
+ cleanupOngoingSession();
99
324
  });
100
- }
101
- prepareStream(request, callback) {
102
- const sessionId = request.sessionID;
103
- const targetAddress = request.targetAddress;
104
- const video = request.video;
105
- const videoCryptoSuite = video.srtpCryptoSuite;
106
- const videoSrtpKey = video.srtp_key;
107
- const videoSrtpSalt = video.srtp_salt;
108
- const videoSSRC = hap_nodejs_1.CameraController.generateSynchronisationSource();
109
- const localPort = getPort();
110
- const sessionInfo = {
111
- address: targetAddress,
112
- videoPort: video.port,
113
- localVideoPort: localPort,
114
- videoCryptoSuite: videoCryptoSuite,
115
- videoSRTP: Buffer.concat([videoSrtpKey, videoSrtpSalt]),
116
- videoSSRC: videoSSRC
117
- };
118
- const response = {
119
- video: {
120
- port: localPort,
121
- ssrc: videoSSRC,
122
- srtp_key: videoSrtpKey,
123
- srtp_salt: videoSrtpSalt
124
- }
325
+ this.ongoingSessions[sessionId] = {
326
+ localVideoPort,
327
+ process: ffmpegVideo
125
328
  };
126
- this.pendingSessions[sessionId] = sessionInfo;
127
- callback(undefined, response);
128
- }
129
- handleStreamRequest(request, callback) {
130
- var _a, _b, _c, _d;
131
- const sessionId = request.sessionID;
132
- switch (request.type) {
133
- case "start": {
134
- const sessionInfo = this.pendingSessions[sessionId];
135
- const video = request.video;
136
- const profile = FFMPEGH264ProfileNames[video.profile];
137
- const level = FFMPEGH264LevelNames[video.level];
138
- const width = video.width;
139
- const height = video.height;
140
- let fps = video.fps;
141
- const payloadType = video.pt;
142
- let maxBitrate = video.max_bit_rate;
143
- const mtu = video.mtu;
144
- const address = sessionInfo.address;
145
- const videoPort = sessionInfo.videoPort;
146
- const localVideoPort = sessionInfo.localVideoPort;
147
- const ssrc = sessionInfo.videoSSRC;
148
- const cryptoSuite = sessionInfo.videoCryptoSuite;
149
- const videoSRTP = sessionInfo.videoSRTP.toString('base64');
150
- const cfg = this.config;
151
- const ffmpegPath = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigVideoProcessor) || 'ffmpeg';
152
- const source = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigSource) ||
153
- `-f lavfi -i testsrc=s=${width}x${height}:r=${fps}`;
154
- const vcodec = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigVideoCodec) || 'libx264';
155
- const additional = (_a = cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigAdditionalCommandLine) === null || _a === void 0 ? void 0 : _a.trim();
156
- const mapvideo = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigMapVideo) || '0:0';
157
- const mapaudio = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigMapAudio) || '0:1';
158
- const maxConfigBitrate = cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigMaxBitrate;
159
- if (maxConfigBitrate &&
160
- maxConfigBitrate > 0 &&
161
- maxConfigBitrate < maxBitrate) {
162
- maxBitrate = maxConfigBitrate;
163
- }
164
- const maxConfigFps = cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigMaxFPS;
165
- if (maxConfigFps && maxConfigFps > 0 && maxConfigFps < fps) {
166
- fps = maxConfigFps;
167
- }
168
- const vf = [];
169
- if ((cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigVideoFilter) !== null &&
170
- (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigVideoFilter) !== undefined &&
171
- (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigVideoFilter) !== '') {
172
- vf.push(cfg.cameraConfigVideoFilter);
173
- if (cfg.cameraConfigHorizontalFlip)
174
- vf.push('hflip');
175
- if (cfg.cameraConfigVerticalFlip)
176
- vf.push('vflip');
177
- }
178
- this.log.debug(`Starting video stream (${width}x${height}, ${fps} fps, ${maxBitrate} kbps, ${mtu} mtu)...`);
179
- let videoffmpegCommand = `${source} -map ${mapvideo} -vcodec ${vcodec} -pix_fmt yuv420p -r ${fps} -f rawvideo ` +
180
- `${additional ? `${additional} ` : ''}` +
181
- `${vf.length > 0 ? `-vf ${vf.join(',')} ` : ''}` +
182
- `-b:v ${maxBitrate}k -bufsize ${maxBitrate}k -maxrate ${maxBitrate}k ` +
183
- `-profile:v ${profile} -level:v ${level} ` +
184
- `-payload_type ${payloadType} -ssrc ${ssrc} -f rtp `;
185
- if (cryptoSuite !== 2) {
186
- let suite;
187
- switch (cryptoSuite) {
188
- case 0:
189
- suite = 'AES_CM_128_HMAC_SHA1_80';
190
- break;
191
- case 1:
192
- suite = 'AES_CM_256_HMAC_SHA1_80';
193
- break;
194
- }
195
- videoffmpegCommand += `-srtp_out_suite ${suite} -srtp_out_params ${videoSRTP} s`;
196
- }
197
- videoffmpegCommand += `rtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${localVideoPort}&pkt_size=${(cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigPacketSize) || mtu}`;
198
- const audioEnabled = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.cameraConfigAudio) === true ||
199
- ((_c = this.config) === null || _c === void 0 ? void 0 : _c.cameraConfigAudio) === 'true';
200
- let audioffmpegCommand = '';
201
- if (audioEnabled) {
202
- const acodec = (cfg === null || cfg === void 0 ? void 0 : cfg.cameraConfigAudioCodec) || 'libfdk_aac';
203
- const abitrate = 32;
204
- const asamplerate = 16;
205
- const apayloadType = 110;
206
- audioffmpegCommand =
207
- ` -map ${mapaudio} -acodec ${acodec} -profile:a aac_eld -flags +global_header -f null -ar ${asamplerate}k -b:a ${abitrate}k -bufsize ${abitrate}k -ac 1 ` +
208
- `-payload_type ${apayloadType} `;
209
- }
210
- if (this.ffmpegDebugOutput || ((_d = this.config) === null || _d === void 0 ? void 0 : _d.cameraConfigDebug)) {
211
- this.log.debug(`FFMPEG command: ${ffmpegPath} ${videoffmpegCommand}${audioffmpegCommand}`);
212
- }
213
- const ffmpegVideo = (0, node_child_process_1.spawn)(ffmpegPath, (videoffmpegCommand + audioffmpegCommand).split(' '), {
214
- env: process.env
215
- });
216
- let started = false;
217
- ffmpegVideo.stderr.on('data', (data) => {
218
- var _a;
219
- this.log.debug(data.toString('utf8'));
220
- if (!started) {
221
- started = true;
222
- this.log.debug('FFMPEG: received first frame');
223
- callback();
224
- }
225
- if (this.ffmpegDebugOutput || ((_a = this.config) === null || _a === void 0 ? void 0 : _a.cameraConfigDebug)) {
226
- this.log.debug(`VIDEO: ${String(data)}`);
227
- }
228
- });
229
- ffmpegVideo.on('error', (error) => {
230
- this.log.error(`[Video] Failed to start video stream: ${error.message}`);
231
- callback(new Error('ffmpeg process creation failed!'));
232
- });
233
- ffmpegVideo.on('exit', (code, signal) => {
234
- var _a;
235
- const message = '[Video] ffmpeg exited with code: ' +
236
- code +
237
- ' and signal: ' +
238
- signal;
239
- if (code == null || code === 255) {
240
- this.log.debug(`${message} (Video stream stopped!)`);
241
- }
242
- else {
243
- this.log.error(`${message} (error)`);
244
- if (!started) {
245
- callback(new Error(message));
246
- }
247
- else {
248
- (_a = this.controller) === null || _a === void 0 ? void 0 : _a.forceStopStreamingSession(sessionId);
249
- }
250
- }
251
- });
252
- this.ongoingSessions[sessionId] = {
253
- localVideoPort: localVideoPort,
254
- process: ffmpegVideo
255
- };
256
- delete this.pendingSessions[sessionId];
257
- break;
258
- }
259
- case "reconfigure":
260
- this.log.debug('Received (unsupported) request to reconfigure to: ' +
261
- JSON.stringify(request.video));
262
- callback();
263
- break;
264
- case "stop": {
265
- const ongoingSession = this.ongoingSessions[sessionId];
266
- if (!ongoingSession) {
267
- callback();
268
- break;
269
- }
270
- ports.delete(ongoingSession.localVideoPort);
271
- try {
272
- ongoingSession.process.kill('SIGKILL');
273
- }
274
- catch (e) {
275
- this.log.error('Error occurred terminating the video process!');
276
- this.log.error(String(e));
277
- }
278
- delete this.ongoingSessions[sessionId];
279
- this.log.debug('Stopped streaming session!');
280
- callback();
281
- break;
282
- }
329
+ delete this.pendingSessions[sessionId];
330
+ break;
331
+ }
332
+ case import_hap_nodejs.StreamRequestTypes.RECONFIGURE:
333
+ this.log.debug(
334
+ "Received (unsupported) request to reconfigure to: " + JSON.stringify(request.video)
335
+ );
336
+ callback();
337
+ break;
338
+ case import_hap_nodejs.StreamRequestTypes.STOP: {
339
+ const ongoingSession = this.ongoingSessions[sessionId];
340
+ const pendingSession = this.pendingSessions[sessionId];
341
+ if (pendingSession) {
342
+ clearTimeout(pendingSession.timeout);
343
+ ports.delete(pendingSession.localVideoPort);
344
+ delete this.pendingSessions[sessionId];
283
345
  }
346
+ if (!ongoingSession) {
347
+ callback();
348
+ break;
349
+ }
350
+ ports.delete(ongoingSession.localVideoPort);
351
+ try {
352
+ ongoingSession.process.kill("SIGKILL");
353
+ } catch (e) {
354
+ this.log.error(
355
+ "Error occurred terminating the video process!"
356
+ );
357
+ this.log.error(String(e));
358
+ }
359
+ delete this.ongoingSessions[sessionId];
360
+ this.log.debug("Stopped streaming session!");
361
+ callback();
362
+ break;
363
+ }
284
364
  }
285
- updateRecordingActive(active) {
286
- this.log.debug(`Recording active set to ${active}`);
287
- }
288
- updateRecordingConfiguration(configuration) {
289
- this.configuration = configuration;
290
- this.log.debug(JSON.stringify(configuration));
365
+ }
366
+ updateRecordingActive(active) {
367
+ this.log.debug(`Recording active set to ${active}`);
368
+ }
369
+ updateRecordingConfiguration(configuration) {
370
+ this.configuration = configuration;
371
+ this.log.debug(JSON.stringify(configuration));
372
+ }
373
+ /**
374
+ * This is a very minimal, very experimental example of how to implement fmp4 streaming with a
375
+ * CameraController supporting HomeKit Secure Video.
376
+ *
377
+ * An ideal implementation would diverge from this in the following ways:
378
+ * * It would implement a prebuffer and respect the recording `active` characteristic for that.
379
+ * * It would start to immediately record after a trigger event occurred and not just
380
+ * when the HomeKit Controller requests it (see the documentation of `CameraRecordingDelegate`).
381
+ */
382
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
383
+ async *handleRecordingStreamRequest(_streamId) {
384
+ (0, import_node_assert.default)(!!this.configuration);
385
+ const STOP_AFTER_MOTION_STOP = false;
386
+ this.handlingStreamingRequest = true;
387
+ (0, import_node_assert.default)(this.configuration.videoCodec.type === import_hap_nodejs.VideoCodecType.H264);
388
+ const profile = this.configuration.videoCodec.parameters.profile === import_hap_nodejs.H264Profile.HIGH ? "high" : this.configuration.videoCodec.parameters.profile === import_hap_nodejs.H264Profile.MAIN ? "main" : "baseline";
389
+ const level = this.configuration.videoCodec.parameters.level === import_hap_nodejs.H264Level.LEVEL4_0 ? "4.0" : this.configuration.videoCodec.parameters.level === import_hap_nodejs.H264Level.LEVEL3_2 ? "3.2" : "3.1";
390
+ const videoArgs = [
391
+ "-an",
392
+ "-sn",
393
+ "-dn",
394
+ "-codec:v",
395
+ "libx264",
396
+ "-pix_fmt",
397
+ "yuv420p",
398
+ "-profile:v",
399
+ profile,
400
+ "-level:v",
401
+ level,
402
+ "-b:v",
403
+ `${this.configuration.videoCodec.parameters.bitRate}k`,
404
+ "-force_key_frames",
405
+ `expr:eq(t,n_forced*${this.configuration.videoCodec.parameters.iFrameInterval / 1e3})`,
406
+ "-r",
407
+ this.configuration.videoCodec.resolution[2].toString()
408
+ ];
409
+ let samplerate;
410
+ switch (this.configuration.audioCodec.samplerate) {
411
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_8:
412
+ samplerate = "8";
413
+ break;
414
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_16:
415
+ samplerate = "16";
416
+ break;
417
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_24:
418
+ samplerate = "24";
419
+ break;
420
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_32:
421
+ samplerate = "32";
422
+ break;
423
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_44_1:
424
+ samplerate = "44.1";
425
+ break;
426
+ case import_hap_nodejs.AudioRecordingSamplerate.KHZ_48:
427
+ samplerate = "48";
428
+ break;
429
+ default:
430
+ throw new Error(
431
+ "Unsupported audio samplerate: " + this.configuration.audioCodec.samplerate
432
+ );
291
433
  }
292
- handleRecordingStreamRequest(_streamId) {
293
- return __asyncGenerator(this, arguments, function* handleRecordingStreamRequest_1() {
294
- var _a, e_1, _b, _c;
295
- var _d, _e, _f;
296
- (0, node_assert_1.default)(!!this.configuration);
297
- const STOP_AFTER_MOTION_STOP = false;
298
- this.handlingStreamingRequest = true;
299
- (0, node_assert_1.default)(this.configuration.videoCodec.type === 0);
300
- const profile = this.configuration.videoCodec.parameters.profile === 2
301
- ? 'high'
302
- : this.configuration.videoCodec.parameters.profile === 1
303
- ? 'main'
304
- : 'baseline';
305
- const level = this.configuration.videoCodec.parameters.level === 2
306
- ? '4.0'
307
- : this.configuration.videoCodec.parameters.level === 1
308
- ? '3.2'
309
- : '3.1';
310
- const videoArgs = [
311
- '-an',
312
- '-sn',
313
- '-dn',
314
- '-codec:v',
315
- 'libx264',
316
- '-pix_fmt',
317
- 'yuv420p',
318
- '-profile:v',
319
- profile,
320
- '-level:v',
321
- level,
322
- '-b:v',
323
- `${this.configuration.videoCodec.parameters.bitRate}k`,
324
- '-force_key_frames',
325
- `expr:eq(t,n_forced*${this.configuration.videoCodec.parameters.iFrameInterval / 1000})`,
326
- '-r',
327
- this.configuration.videoCodec.resolution[2].toString()
328
- ];
329
- let samplerate;
330
- switch (this.configuration.audioCodec.samplerate) {
331
- case 0:
332
- samplerate = '8';
333
- break;
334
- case 1:
335
- samplerate = '16';
336
- break;
337
- case 2:
338
- samplerate = '24';
339
- break;
340
- case 3:
341
- samplerate = '32';
342
- break;
343
- case 4:
344
- samplerate = '44.1';
345
- break;
346
- case 5:
347
- samplerate = '48';
348
- break;
349
- default:
350
- throw new Error('Unsupported audio samplerate: ' +
351
- this.configuration.audioCodec.samplerate);
352
- }
353
- const audioArgs = ((_e = (_d = this.controller) === null || _d === void 0 ? void 0 : _d.recordingManagement) === null || _e === void 0 ? void 0 : _e.recordingManagementService.getCharacteristic(hap_nodejs_1.Characteristic.RecordingAudioActive))
354
- ? [
355
- '-acodec',
356
- 'libfdk_aac',
357
- ...(this.configuration.audioCodec.type ===
358
- 0
359
- ? ['-profile:a', 'aac_low']
360
- : ['-profile:a', 'aac_eld']),
361
- '-ar',
362
- `${samplerate}k`,
363
- '-b:a',
364
- `${this.configuration.audioCodec.bitrate}k`,
365
- '-ac',
366
- `${this.configuration.audioCodec.audioChannels}`
367
- ]
368
- : [];
369
- this.server = new MP4StreamingServer_1.MP4StreamingServer('ffmpeg', `-f lavfi -i \
370
- testsrc=s=${this.configuration.videoCodec.resolution[0]}x${this.configuration.videoCodec.resolution[1]}:r=${this.configuration.videoCodec.resolution[2]}`.split(/ /g), audioArgs, videoArgs);
371
- yield __await(this.server.start());
372
- if (!this.server || this.server.destroyed) {
373
- return yield __await(void 0);
374
- }
375
- const pending = [];
376
- try {
377
- try {
378
- for (var _g = true, _h = __asyncValues(this.server.generator()), _j; _j = yield __await(_h.next()), _a = _j.done, !_a; _g = true) {
379
- _c = _j.value;
380
- _g = false;
381
- const box = _c;
382
- pending.push(box.header, box.data);
383
- const motionDetected = (_f = this.accessory
384
- .getService(hap_nodejs_1.Service.MotionSensor)) === null || _f === void 0 ? void 0 : _f.getCharacteristic(hap_nodejs_1.Characteristic.MotionDetected).value;
385
- this.log.debug(`mp4 box type ${box.type} and length ${box.length}`);
386
- if (box.type === 'moov' || box.type === 'mdat') {
387
- const fragment = Buffer.concat(pending);
388
- pending.splice(0, pending.length);
389
- const isLast = STOP_AFTER_MOTION_STOP && !motionDetected;
390
- yield yield __await({
391
- data: fragment,
392
- isLast: isLast
393
- });
394
- if (isLast) {
395
- this.log.debug('Ending session due to motion stopped!');
396
- break;
397
- }
398
- }
399
- }
400
- }
401
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
402
- finally {
403
- try {
404
- if (!_g && !_a && (_b = _h.return)) yield __await(_b.call(_h));
405
- }
406
- finally { if (e_1) throw e_1.error; }
407
- }
408
- }
409
- catch (error) {
410
- if (!error.message.startsWith('FFMPEG')) {
411
- this.log.error(`Encountered unexpected error on generator ${error.stack}`);
412
- }
413
- }
414
- });
434
+ const audioArgs = this.controller?.recordingManagement?.recordingManagementService.getCharacteristic(
435
+ import_hap_nodejs.Characteristic.RecordingAudioActive
436
+ ) ? [
437
+ "-acodec",
438
+ "libfdk_aac",
439
+ ...this.configuration.audioCodec.type === import_hap_nodejs.AudioRecordingCodecType.AAC_LC ? ["-profile:a", "aac_low"] : ["-profile:a", "aac_eld"],
440
+ "-ar",
441
+ `${samplerate}k`,
442
+ "-b:a",
443
+ `${this.configuration.audioCodec.bitrate}k`,
444
+ "-ac",
445
+ `${this.configuration.audioCodec.audioChannels}`
446
+ ] : [];
447
+ this.server = new import_MP4StreamingServer.MP4StreamingServer(
448
+ "ffmpeg",
449
+ `-f lavfi -i testsrc=s=${this.configuration.videoCodec.resolution[0]}x${this.configuration.videoCodec.resolution[1]}:r=${this.configuration.videoCodec.resolution[2]}`.split(
450
+ / /g
451
+ ),
452
+ audioArgs,
453
+ videoArgs
454
+ );
455
+ await this.server.start();
456
+ if (!this.server || this.server.destroyed) {
457
+ return;
415
458
  }
416
- closeRecordingStream(streamId, reason) {
417
- if (reason) {
418
- this.log.error(`Closing stream ${streamId} due to error: ${reason}`);
459
+ const pending = [];
460
+ try {
461
+ for await (const box of this.server.generator()) {
462
+ pending.push(box.header, box.data);
463
+ const motionDetected = this.accessory.getService(import_hap_nodejs.Service.MotionSensor)?.getCharacteristic(import_hap_nodejs.Characteristic.MotionDetected).value;
464
+ this.log.debug(
465
+ `mp4 box type ${box.type} and length ${box.length}`
466
+ );
467
+ if (box.type === "moov" || box.type === "mdat") {
468
+ const fragment = Buffer.concat(pending);
469
+ pending.splice(0, pending.length);
470
+ const isLast = STOP_AFTER_MOTION_STOP && !motionDetected;
471
+ yield {
472
+ data: fragment,
473
+ isLast
474
+ };
475
+ if (isLast) {
476
+ this.log.debug("Ending session due to motion stopped!");
477
+ break;
478
+ }
419
479
  }
420
- if (this.server) {
421
- this.server.destroy();
422
- this.server = undefined;
423
- }
424
- this.handlingStreamingRequest = false;
480
+ }
481
+ } catch (error) {
482
+ if (!error.message.startsWith("FFMPEG")) {
483
+ this.log.error(
484
+ `Encountered unexpected error on generator ${error.stack}`
485
+ );
486
+ }
487
+ }
488
+ }
489
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
490
+ closeRecordingStream(streamId, reason) {
491
+ if (reason) {
492
+ this.log.error(`Closing stream ${streamId} due to error: ${reason}`);
425
493
  }
426
- acknowledgeStream(streamId) {
427
- this.closeRecordingStream(streamId);
494
+ if (this.server) {
495
+ this.server.destroy();
496
+ this.server = void 0;
428
497
  }
498
+ this.handlingStreamingRequest = false;
499
+ }
500
+ acknowledgeStream(streamId) {
501
+ this.closeRecordingStream(streamId);
502
+ }
429
503
  }
430
- exports.CameraDelegate = CameraDelegate;
504
+ // Annotate the CommonJS export names for ESM import in node:
505
+ 0 && (module.exports = {
506
+ CameraDelegate
507
+ });