node-red-contrib-homekit-bridged 2.0.0-dev.3 → 2.0.0-dev.5
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/build/lib/api.js +2 -2
- package/build/lib/camera/CameraControl.d.ts +3 -0
- package/build/lib/camera/CameraControl.js +90 -0
- package/build/lib/camera/CameraDelegate.d.ts +38 -0
- package/build/lib/camera/CameraDelegate.js +430 -0
- package/build/lib/camera/MP4StreamingServer.d.ts +26 -0
- package/build/lib/camera/MP4StreamingServer.js +150 -0
- package/build/lib/utils/ServiceUtils.js +3 -1
- package/build/lib/utils/ServiceUtils2.js +3 -1
- package/build/nodes/nrchkb.js +6 -6
- package/package.json +3 -2
package/build/lib/api.js
CHANGED
|
@@ -94,7 +94,7 @@ module.exports = (RED) => {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
catch (e) {
|
|
97
|
-
|
|
97
|
+
log.error(e);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
else if (releaseVersionFound) {
|
|
@@ -108,7 +108,7 @@ module.exports = (RED) => {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
catch (e) {
|
|
111
|
-
|
|
111
|
+
log.error(e);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
else {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureCamera = void 0;
|
|
4
|
+
const hap_nodejs_1 = require("@homebridge/hap-nodejs");
|
|
5
|
+
const CameraDelegate_1 = require("./CameraDelegate");
|
|
6
|
+
const configureCamera = (accessory, config) => {
|
|
7
|
+
const streamDelegate = new CameraDelegate_1.CameraDelegate(accessory, config);
|
|
8
|
+
const cameraController = new hap_nodejs_1.CameraController({
|
|
9
|
+
cameraStreamCount: Math.max(1, (config === null || config === void 0 ? void 0 : config.cameraConfigMaxStreams) || 2),
|
|
10
|
+
delegate: streamDelegate,
|
|
11
|
+
streamingOptions: {
|
|
12
|
+
supportedCryptoSuites: [
|
|
13
|
+
2,
|
|
14
|
+
0
|
|
15
|
+
],
|
|
16
|
+
video: {
|
|
17
|
+
codec: {
|
|
18
|
+
profiles: [0, 1, 2],
|
|
19
|
+
levels: [0, 1, 2]
|
|
20
|
+
},
|
|
21
|
+
resolutions: (() => {
|
|
22
|
+
const maxW = config === null || config === void 0 ? void 0 : config.cameraConfigMaxWidth;
|
|
23
|
+
const maxH = config === null || config === void 0 ? void 0 : config.cameraConfigMaxHeight;
|
|
24
|
+
const maxFps = config === null || config === void 0 ? void 0 : config.cameraConfigMaxFPS;
|
|
25
|
+
const defaults = [
|
|
26
|
+
[1920, 1080, 30],
|
|
27
|
+
[1280, 960, 30],
|
|
28
|
+
[1280, 720, 30],
|
|
29
|
+
[1024, 768, 30],
|
|
30
|
+
[640, 480, 30],
|
|
31
|
+
[640, 360, 30],
|
|
32
|
+
[480, 360, 30],
|
|
33
|
+
[480, 270, 30],
|
|
34
|
+
[320, 240, 30],
|
|
35
|
+
[320, 240, 15],
|
|
36
|
+
[320, 180, 30]
|
|
37
|
+
];
|
|
38
|
+
return defaults
|
|
39
|
+
.filter(([w, h]) => (!maxW || w <= maxW) && (!maxH || h <= maxH))
|
|
40
|
+
.map(([w, h, f]) => [w, h, Math.min(f, maxFps || f)]);
|
|
41
|
+
})()
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
recording: {
|
|
45
|
+
options: {
|
|
46
|
+
prebufferLength: 4000,
|
|
47
|
+
mediaContainerConfiguration: {
|
|
48
|
+
type: 0,
|
|
49
|
+
fragmentLength: 4000
|
|
50
|
+
},
|
|
51
|
+
video: {
|
|
52
|
+
type: 0,
|
|
53
|
+
parameters: {
|
|
54
|
+
profiles: [2],
|
|
55
|
+
levels: [2]
|
|
56
|
+
},
|
|
57
|
+
resolutions: [
|
|
58
|
+
[320, 180, 30],
|
|
59
|
+
[320, 240, 15],
|
|
60
|
+
[320, 240, 30],
|
|
61
|
+
[480, 270, 30],
|
|
62
|
+
[480, 360, 30],
|
|
63
|
+
[640, 360, 30],
|
|
64
|
+
[640, 480, 30],
|
|
65
|
+
[1280, 720, 30],
|
|
66
|
+
[1280, 960, 30],
|
|
67
|
+
[1920, 1080, 30],
|
|
68
|
+
[1600, 1200, 30]
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
audio: {
|
|
72
|
+
codecs: {
|
|
73
|
+
type: 1,
|
|
74
|
+
audioChannels: 1,
|
|
75
|
+
samplerate: 5,
|
|
76
|
+
bitrateMode: 0
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
delegate: streamDelegate
|
|
81
|
+
},
|
|
82
|
+
sensors: {
|
|
83
|
+
motion: true,
|
|
84
|
+
occupancy: true
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
streamDelegate.controller = cameraController;
|
|
88
|
+
accessory.configureController(cameraController);
|
|
89
|
+
};
|
|
90
|
+
exports.configureCamera = configureCamera;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { type Accessory, CameraController, type CameraRecordingConfiguration, type CameraRecordingDelegate, type CameraStreamingDelegate, type HDSProtocolSpecificErrorReason, type PrepareStreamCallback, type PrepareStreamRequest, type RecordingPacket, type SnapshotRequest, type SnapshotRequestCallback, SRTPCryptoSuites, type StreamingRequest, type StreamRequestCallback } from '@homebridge/hap-nodejs';
|
|
3
|
+
import type CameraConfigType from '../types/CameraConfigType';
|
|
4
|
+
import { MP4StreamingServer } from './MP4StreamingServer';
|
|
5
|
+
type SessionInfo = {
|
|
6
|
+
address: string;
|
|
7
|
+
videoPort: number;
|
|
8
|
+
localVideoPort: number;
|
|
9
|
+
videoCryptoSuite: SRTPCryptoSuites;
|
|
10
|
+
videoSRTP: Buffer;
|
|
11
|
+
videoSSRC: number;
|
|
12
|
+
};
|
|
13
|
+
type OngoingSession = {
|
|
14
|
+
localVideoPort: number;
|
|
15
|
+
process: ChildProcess;
|
|
16
|
+
};
|
|
17
|
+
export declare class CameraDelegate implements CameraStreamingDelegate, CameraRecordingDelegate {
|
|
18
|
+
private accessory;
|
|
19
|
+
private config?;
|
|
20
|
+
private ffmpegDebugOutput;
|
|
21
|
+
private log;
|
|
22
|
+
controller?: CameraController;
|
|
23
|
+
pendingSessions: Record<string, SessionInfo>;
|
|
24
|
+
ongoingSessions: Record<string, OngoingSession>;
|
|
25
|
+
configuration?: CameraRecordingConfiguration;
|
|
26
|
+
handlingStreamingRequest: boolean;
|
|
27
|
+
server?: MP4StreamingServer;
|
|
28
|
+
constructor(accessory: Accessory, config?: CameraConfigType | undefined);
|
|
29
|
+
handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void;
|
|
30
|
+
prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void;
|
|
31
|
+
handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void;
|
|
32
|
+
updateRecordingActive(active: boolean): void;
|
|
33
|
+
updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void;
|
|
34
|
+
handleRecordingStreamRequest(_streamId: number): AsyncGenerator<RecordingPacket>;
|
|
35
|
+
closeRecordingStream(streamId: number, reason?: HDSProtocolSpecificErrorReason): void;
|
|
36
|
+
acknowledgeStream(streamId: number): void;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,430 @@
|
|
|
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); }
|
|
9
|
+
};
|
|
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]); }
|
|
21
|
+
};
|
|
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();
|
|
35
|
+
function getPort() {
|
|
36
|
+
for (let i = 5011;; i++) {
|
|
37
|
+
if (!ports.has(i)) {
|
|
38
|
+
ports.add(i);
|
|
39
|
+
return i;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
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);
|
|
55
|
+
}
|
|
56
|
+
catch (_) {
|
|
57
|
+
}
|
|
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;
|
|
67
|
+
}
|
|
68
|
+
else if (mainSource) {
|
|
69
|
+
imageSource = mainSource;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
imageSource = `-f lavfi -i testsrc=s=${request.width}x${request.height}`;
|
|
73
|
+
}
|
|
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(' '), {
|
|
76
|
+
env: process.env
|
|
77
|
+
});
|
|
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
|
+
}
|
|
85
|
+
});
|
|
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}`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
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
|
+
}
|
|
125
|
+
};
|
|
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
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
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));
|
|
291
|
+
}
|
|
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
|
+
});
|
|
415
|
+
}
|
|
416
|
+
closeRecordingStream(streamId, reason) {
|
|
417
|
+
if (reason) {
|
|
418
|
+
this.log.error(`Closing stream ${streamId} due to error: ${reason}`);
|
|
419
|
+
}
|
|
420
|
+
if (this.server) {
|
|
421
|
+
this.server.destroy();
|
|
422
|
+
this.server = undefined;
|
|
423
|
+
}
|
|
424
|
+
this.handlingStreamingRequest = false;
|
|
425
|
+
}
|
|
426
|
+
acknowledgeStream(streamId) {
|
|
427
|
+
this.closeRecordingStream(streamId);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
exports.CameraDelegate = CameraDelegate;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { type Server, type Socket } from 'node:net';
|
|
3
|
+
interface MP4Atom {
|
|
4
|
+
header: Buffer;
|
|
5
|
+
length: number;
|
|
6
|
+
type: string;
|
|
7
|
+
data: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export declare class MP4StreamingServer {
|
|
10
|
+
readonly server: Server;
|
|
11
|
+
readonly debugMode: boolean;
|
|
12
|
+
readonly ffmpegPath: string;
|
|
13
|
+
readonly args: string[];
|
|
14
|
+
socket?: Socket;
|
|
15
|
+
childProcess?: ChildProcess;
|
|
16
|
+
destroyed: boolean;
|
|
17
|
+
connectPromise: Promise<void>;
|
|
18
|
+
connectResolve?: () => void;
|
|
19
|
+
constructor(ffmpegPath: string, ffmpegInput: Array<string>, audioOutputArgs: Array<string>, videoOutputArgs: Array<string>);
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
destroy(): void;
|
|
22
|
+
handleConnection(socket: Socket): void;
|
|
23
|
+
generator(): AsyncGenerator<MP4Atom>;
|
|
24
|
+
read(length: number): Promise<Buffer>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
12
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
13
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
14
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
15
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
16
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
17
|
+
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]); } }
|
|
18
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
19
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
20
|
+
function fulfill(value) { resume("next", value); }
|
|
21
|
+
function reject(value) { resume("throw", value); }
|
|
22
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.MP4StreamingServer = void 0;
|
|
26
|
+
const node_child_process_1 = require("node:child_process");
|
|
27
|
+
const node_events_1 = require("node:events");
|
|
28
|
+
const node_net_1 = require("node:net");
|
|
29
|
+
const logger_1 = require("@nrchkb/logger");
|
|
30
|
+
const log = (0, logger_1.logger)('NRCHKB', 'MP4StreamingServer');
|
|
31
|
+
class MP4StreamingServer {
|
|
32
|
+
constructor(ffmpegPath, ffmpegInput, audioOutputArgs, videoOutputArgs) {
|
|
33
|
+
this.debugMode = false;
|
|
34
|
+
this.destroyed = false;
|
|
35
|
+
this.connectPromise = new Promise((resolve) => {
|
|
36
|
+
this.connectResolve = resolve;
|
|
37
|
+
});
|
|
38
|
+
this.server = (0, node_net_1.createServer)(this.handleConnection.bind(this));
|
|
39
|
+
this.ffmpegPath = ffmpegPath;
|
|
40
|
+
this.args = [];
|
|
41
|
+
this.args.push(...ffmpegInput);
|
|
42
|
+
this.args.push(...audioOutputArgs);
|
|
43
|
+
this.args.push('-f', 'mp4');
|
|
44
|
+
this.args.push(...videoOutputArgs);
|
|
45
|
+
this.args.push('-fflags', '+genpts', '-reset_timestamps', '1');
|
|
46
|
+
this.args.push('-movflags', 'frag_keyframe+empty_moov+default_base_moof');
|
|
47
|
+
}
|
|
48
|
+
start() {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
var _a, _b;
|
|
51
|
+
const promise = (0, node_events_1.once)(this.server, 'listening');
|
|
52
|
+
this.server.listen();
|
|
53
|
+
yield promise;
|
|
54
|
+
if (this.destroyed) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const port = this.server.address().port;
|
|
58
|
+
this.args.push(`tcp://127.0.0.1:${port}`);
|
|
59
|
+
log.debug(`${this.ffmpegPath} ${this.args.join(' ')}`);
|
|
60
|
+
this.childProcess = (0, node_child_process_1.spawn)(this.ffmpegPath, this.args, {
|
|
61
|
+
env: process.env,
|
|
62
|
+
stdio: this.debugMode ? 'pipe' : 'ignore'
|
|
63
|
+
});
|
|
64
|
+
if (!this.childProcess) {
|
|
65
|
+
log.error('ChildProcess is undefined directly after the init!');
|
|
66
|
+
}
|
|
67
|
+
if (this.debugMode) {
|
|
68
|
+
(_a = this.childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => log.debug(data.toString()));
|
|
69
|
+
(_b = this.childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => log.debug(data.toString()));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
destroy() {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
76
|
+
(_b = this.childProcess) === null || _b === void 0 ? void 0 : _b.kill();
|
|
77
|
+
this.socket = undefined;
|
|
78
|
+
this.childProcess = undefined;
|
|
79
|
+
this.destroyed = true;
|
|
80
|
+
}
|
|
81
|
+
handleConnection(socket) {
|
|
82
|
+
var _a;
|
|
83
|
+
this.server.close();
|
|
84
|
+
this.socket = socket;
|
|
85
|
+
(_a = this.connectResolve) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
86
|
+
}
|
|
87
|
+
generator() {
|
|
88
|
+
return __asyncGenerator(this, arguments, function* generator_1() {
|
|
89
|
+
yield __await(this.connectPromise);
|
|
90
|
+
if (!this.socket || !this.childProcess) {
|
|
91
|
+
log.error('Socket undefined ' +
|
|
92
|
+
!!this.socket +
|
|
93
|
+
' childProcess undefined ' +
|
|
94
|
+
!!this.childProcess);
|
|
95
|
+
throw new Error('Unexpected state!');
|
|
96
|
+
}
|
|
97
|
+
while (true) {
|
|
98
|
+
const header = yield __await(this.read(8));
|
|
99
|
+
const length = header.readInt32BE(0) - 8;
|
|
100
|
+
const type = header.subarray(4).toString();
|
|
101
|
+
const data = yield __await(this.read(length));
|
|
102
|
+
yield yield __await({
|
|
103
|
+
header: header,
|
|
104
|
+
length: length,
|
|
105
|
+
type: type,
|
|
106
|
+
data: data
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
read(length) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
if (!this.socket) {
|
|
114
|
+
throw Error('FFMPEG tried reading from closed socket!');
|
|
115
|
+
}
|
|
116
|
+
if (!length) {
|
|
117
|
+
return Buffer.alloc(0);
|
|
118
|
+
}
|
|
119
|
+
const value = this.socket.read(length);
|
|
120
|
+
if (value) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const readHandler = () => {
|
|
125
|
+
var _a;
|
|
126
|
+
const value = (_a = this.socket) === null || _a === void 0 ? void 0 : _a.read(length);
|
|
127
|
+
if (value) {
|
|
128
|
+
cleanup();
|
|
129
|
+
resolve(value);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const endHandler = () => {
|
|
133
|
+
cleanup();
|
|
134
|
+
reject(new Error(`FFMPEG socket closed during read for ${length} bytes!`));
|
|
135
|
+
};
|
|
136
|
+
const cleanup = () => {
|
|
137
|
+
var _a, _b;
|
|
138
|
+
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.removeListener('readable', readHandler);
|
|
139
|
+
(_b = this.socket) === null || _b === void 0 ? void 0 : _b.removeListener('close', endHandler);
|
|
140
|
+
};
|
|
141
|
+
if (!this.socket) {
|
|
142
|
+
throw new Error('FFMPEG socket is closed now!');
|
|
143
|
+
}
|
|
144
|
+
this.socket.on('readable', readHandler);
|
|
145
|
+
this.socket.on('close', endHandler);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.MP4StreamingServer = MP4StreamingServer;
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
const util = __importStar(require("node:util"));
|
|
40
40
|
const hap_nodejs_1 = require("@homebridge/hap-nodejs");
|
|
41
41
|
const logger_1 = require("@nrchkb/logger");
|
|
42
|
+
const CameraControl_1 = require("../camera/CameraControl");
|
|
42
43
|
const NRCHKBError_1 = __importDefault(require("../NRCHKBError"));
|
|
43
44
|
module.exports = (node) => {
|
|
44
45
|
const log = (0, logger_1.logger)('NRCHKB', 'ServiceUtils', node.config.name, node);
|
|
@@ -267,13 +268,14 @@ module.exports = (node) => {
|
|
|
267
268
|
}
|
|
268
269
|
return service;
|
|
269
270
|
};
|
|
270
|
-
const configureCameraSource = (
|
|
271
|
+
const configureCameraSource = (accessory, _service, config) => {
|
|
271
272
|
if (config.cameraConfigSource) {
|
|
272
273
|
log.debug('Configuring Camera Source');
|
|
273
274
|
if (!config.cameraConfigVideoProcessor) {
|
|
274
275
|
log.error('Missing configuration for CameraControl: videoProcessor cannot be empty!');
|
|
275
276
|
}
|
|
276
277
|
else {
|
|
278
|
+
(0, CameraControl_1.configureCamera)(accessory, config);
|
|
277
279
|
}
|
|
278
280
|
}
|
|
279
281
|
else {
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const hap_nodejs_1 = require("@homebridge/hap-nodejs");
|
|
7
7
|
const logger_1 = require("@nrchkb/logger");
|
|
8
|
+
const CameraControl_1 = require("../camera/CameraControl");
|
|
8
9
|
const NRCHKBError_1 = __importDefault(require("../NRCHKBError"));
|
|
9
10
|
const Storage_1 = require("../Storage");
|
|
10
11
|
module.exports = (node) => {
|
|
@@ -254,13 +255,14 @@ module.exports = (node) => {
|
|
|
254
255
|
}
|
|
255
256
|
return service;
|
|
256
257
|
};
|
|
257
|
-
const configureCameraSource = (
|
|
258
|
+
const configureCameraSource = (accessory, _service, config) => {
|
|
258
259
|
if (config.cameraConfigSource) {
|
|
259
260
|
log.debug('Configuring Camera Source');
|
|
260
261
|
if (!config.cameraConfigVideoProcessor) {
|
|
261
262
|
log.error('Missing configuration for CameraControl: videoProcessor cannot be empty!');
|
|
262
263
|
}
|
|
263
264
|
else {
|
|
265
|
+
(0, CameraControl_1.configureCamera)(accessory, config);
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
else {
|
package/build/nodes/nrchkb.js
CHANGED
|
@@ -83,11 +83,11 @@ module.exports = (RED) => {
|
|
|
83
83
|
log.error('node-red restart highly recommended');
|
|
84
84
|
log.trace(error);
|
|
85
85
|
}
|
|
86
|
-
if (process.env.NRCHKB_EXPERIMENTAL === 'true') {
|
|
87
|
-
log.debug('Registering nrchkb type');
|
|
88
|
-
RED.nodes.registerType('nrchkb', function (config) {
|
|
89
|
-
RED.nodes.createNode(this, config);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
86
|
});
|
|
87
|
+
if (process.env.NRCHKB_EXPERIMENTAL === 'true') {
|
|
88
|
+
log.debug('Registering nrchkb type');
|
|
89
|
+
RED.nodes.registerType('nrchkb', function (config) {
|
|
90
|
+
RED.nodes.createNode(this, config);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
93
|
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-homekit-bridged",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.5",
|
|
4
4
|
"description": "Node-RED nodes to simulate Apple HomeKit devices.",
|
|
5
5
|
"main": "build/nodes/nrchkb.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "npm run clean && tsc",
|
|
8
8
|
"clean": "del-cli \"build/lib\" \"build/**/*.js\"",
|
|
9
9
|
"test": "vitest run",
|
|
10
|
+
"test:ci": "vitest run --no-file-parallelism",
|
|
10
11
|
"test:watch": "vitest",
|
|
11
12
|
"format": "npx @biomejs/biome check --write",
|
|
12
13
|
"lint": "npx @biomejs/biome check",
|
|
13
|
-
"prepare": "husky
|
|
14
|
+
"prepare": "husky || true"
|
|
14
15
|
},
|
|
15
16
|
"repository": {
|
|
16
17
|
"type": "git",
|