@matterbridge/core 3.10.13 → 3.10.14-dev-20261010-607ee18
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/dist/behaviors/ffmpeg.js +50 -25
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +10 -15
- package/dist/matterbridge.js +6 -5
- package/package.json +8 -8
package/dist/behaviors/ffmpeg.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { constants } from 'node:fs';
|
|
3
|
-
import { access, readdir } from 'node:fs/promises';
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants, readdirSync } from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { getErrorMessage } from '@matterbridge/utils';
|
|
5
|
+
import { hasAnyParameter } from '@matterbridge/utils/cli';
|
|
6
6
|
import { AnsiLogger, MAGENTA } from 'node-ansi-logger';
|
|
7
|
-
const log = new AnsiLogger({
|
|
8
|
-
|
|
7
|
+
const log = new AnsiLogger({
|
|
8
|
+
logName: 'Ffmpeg',
|
|
9
|
+
logLevel: hasAnyParameter('debug', 'verbose') ? "debug" : "info",
|
|
10
|
+
logNameColor: MAGENTA,
|
|
11
|
+
logTimestampFormat: 4,
|
|
12
|
+
});
|
|
13
|
+
const PROBE_TIMEOUT_MS = 5000;
|
|
14
|
+
function runProbe(command, args) {
|
|
15
|
+
const child = spawnSync(command, args, { stdio: 'ignore', timeout: PROBE_TIMEOUT_MS });
|
|
16
|
+
if (child.error)
|
|
17
|
+
throw child.error;
|
|
18
|
+
if (child.status !== 0) {
|
|
19
|
+
throw new Error(`${command} exited with code ${child.status ?? -1}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function runCommand(command, args) {
|
|
9
23
|
await new Promise((resolve, reject) => {
|
|
10
24
|
const child = spawn(command, args, { stdio: 'ignore' });
|
|
11
25
|
child.once('error', reject);
|
|
@@ -18,10 +32,10 @@ async function runProbe(command, args) {
|
|
|
18
32
|
});
|
|
19
33
|
});
|
|
20
34
|
}
|
|
21
|
-
|
|
35
|
+
function isRunnable(command) {
|
|
22
36
|
for (const versionArg of ['--version', '-version']) {
|
|
23
37
|
try {
|
|
24
|
-
|
|
38
|
+
runProbe(command, [versionArg]);
|
|
25
39
|
return true;
|
|
26
40
|
}
|
|
27
41
|
catch {
|
|
@@ -29,20 +43,20 @@ async function isRunnable(command) {
|
|
|
29
43
|
}
|
|
30
44
|
return false;
|
|
31
45
|
}
|
|
32
|
-
|
|
46
|
+
function getWindowsCommandCandidates() {
|
|
33
47
|
if (process.platform !== 'win32')
|
|
34
48
|
return [];
|
|
35
49
|
const candidates = [];
|
|
36
50
|
if (process.env.LOCALAPPDATA) {
|
|
37
51
|
const wingetPackages = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WinGet', 'Packages');
|
|
38
52
|
try {
|
|
39
|
-
const packageDirs =
|
|
53
|
+
const packageDirs = readdirSync(wingetPackages, { withFileTypes: true });
|
|
40
54
|
for (const packageDir of packageDirs) {
|
|
41
55
|
if (!packageDir.isDirectory() || !packageDir.name.startsWith('Gyan.FFmpeg_'))
|
|
42
56
|
continue;
|
|
43
57
|
const packagePath = path.join(wingetPackages, packageDir.name);
|
|
44
58
|
try {
|
|
45
|
-
const versionDirs =
|
|
59
|
+
const versionDirs = readdirSync(packagePath, { withFileTypes: true });
|
|
46
60
|
for (const versionDir of versionDirs) {
|
|
47
61
|
if (versionDir.isDirectory() && versionDir.name.startsWith('ffmpeg-'))
|
|
48
62
|
candidates.push(path.join(packagePath, versionDir.name, 'bin', 'ffmpeg.exe'));
|
|
@@ -62,20 +76,20 @@ async function getWindowsCommandCandidates() {
|
|
|
62
76
|
}
|
|
63
77
|
return candidates;
|
|
64
78
|
}
|
|
65
|
-
|
|
66
|
-
const candidates = ['ffmpeg', '/usr/bin/ffmpeg', '/bin/ffmpeg', '/usr/local/bin/ffmpeg', ...
|
|
79
|
+
function resolveFfmpeg() {
|
|
80
|
+
const candidates = ['ffmpeg', '/usr/bin/ffmpeg', '/bin/ffmpeg', '/usr/local/bin/ffmpeg', ...getWindowsCommandCandidates()];
|
|
67
81
|
log.debug(`Resolving ffmpeg: trying ${candidates.length} candidate(s)`);
|
|
68
82
|
for (const candidate of candidates) {
|
|
69
83
|
if (path.isAbsolute(candidate)) {
|
|
70
84
|
try {
|
|
71
|
-
|
|
85
|
+
accessSync(candidate, constants.X_OK);
|
|
72
86
|
}
|
|
73
87
|
catch {
|
|
74
88
|
log.debug(`Candidate ${candidate} is not accessible`);
|
|
75
89
|
continue;
|
|
76
90
|
}
|
|
77
91
|
}
|
|
78
|
-
if (
|
|
92
|
+
if (isRunnable(candidate)) {
|
|
79
93
|
log.debug(`Found ffmpeg in ${candidate}`);
|
|
80
94
|
return candidate;
|
|
81
95
|
}
|
|
@@ -84,18 +98,27 @@ async function resolveFfmpeg() {
|
|
|
84
98
|
log.debug('Could not resolve ffmpeg');
|
|
85
99
|
return undefined;
|
|
86
100
|
}
|
|
87
|
-
let ffmpegCommand
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
let ffmpegCommand;
|
|
102
|
+
let ffmpegResolved = false;
|
|
103
|
+
function ensureFfmpegResolved() {
|
|
104
|
+
if (ffmpegResolved)
|
|
105
|
+
return;
|
|
106
|
+
ffmpegResolved = true;
|
|
107
|
+
ffmpegCommand = resolveFfmpeg();
|
|
108
|
+
if (ffmpegCommand)
|
|
109
|
+
log.debug(`Using ffmpeg: ${ffmpegCommand}`);
|
|
110
|
+
else
|
|
111
|
+
log.warn('ffmpeg could not be resolved on this host; ffmpeg-dependent features will not work');
|
|
112
|
+
}
|
|
92
113
|
export function hasFfmpeg() {
|
|
114
|
+
ensureFfmpegResolved();
|
|
93
115
|
return ffmpegCommand !== undefined;
|
|
94
116
|
}
|
|
95
117
|
export function redactSource(source) {
|
|
96
118
|
return source.replace(/^([a-z][a-z\d+.-]*:\/\/)[^/]*@/i, '$1***@');
|
|
97
119
|
}
|
|
98
120
|
export function runFfmpeg(args) {
|
|
121
|
+
ensureFfmpegResolved();
|
|
99
122
|
if (!ffmpegCommand) {
|
|
100
123
|
throw new Error('Cannot run ffmpeg: not found on this host');
|
|
101
124
|
}
|
|
@@ -168,6 +191,7 @@ export function getPlayWebcamArgs(videoSource, audioSource) {
|
|
|
168
191
|
return ['-hide_banner', '-window_title', videoSource, '-f', 'v4l2', '-i', videoSource];
|
|
169
192
|
}
|
|
170
193
|
export function playWebcam(videoSource, audioSource) {
|
|
194
|
+
ensureFfmpegResolved();
|
|
171
195
|
if (!ffmpegCommand) {
|
|
172
196
|
throw new Error('Cannot play the webcam: ffmpeg not found on this host');
|
|
173
197
|
}
|
|
@@ -186,12 +210,12 @@ export async function installFfmpeg() {
|
|
|
186
210
|
return false;
|
|
187
211
|
}
|
|
188
212
|
try {
|
|
189
|
-
if (
|
|
190
|
-
await
|
|
213
|
+
if (isRunnable('apk')) {
|
|
214
|
+
await runCommand('apk', ['add', '--no-cache', 'ffmpeg']);
|
|
191
215
|
}
|
|
192
|
-
else if (
|
|
193
|
-
await
|
|
194
|
-
await
|
|
216
|
+
else if (isRunnable('apt-get')) {
|
|
217
|
+
await runCommand('apt-get', ['update']);
|
|
218
|
+
await runCommand('env', ['DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '--no-install-recommends', 'ffmpeg']);
|
|
195
219
|
}
|
|
196
220
|
else {
|
|
197
221
|
log.warn('Cannot install ffmpeg: no supported package manager found (apk, apt-get)');
|
|
@@ -202,7 +226,8 @@ export async function installFfmpeg() {
|
|
|
202
226
|
log.error(`Failed to install ffmpeg: ${getErrorMessage(error)}`);
|
|
203
227
|
return false;
|
|
204
228
|
}
|
|
205
|
-
ffmpegCommand =
|
|
229
|
+
ffmpegCommand = resolveFfmpeg();
|
|
230
|
+
ffmpegResolved = true;
|
|
206
231
|
log.info(ffmpegCommand ? `Installed ffmpeg: ${ffmpegCommand}` : 'ffmpeg was installed but could not be resolved');
|
|
207
232
|
return ffmpegCommand !== undefined;
|
|
208
233
|
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { ThreadsManager } from '@matterbridge/thread/manager';
|
|
4
|
+
import { BroadcastServer } from '@matterbridge/thread/server';
|
|
4
5
|
import { hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
|
|
5
6
|
import { inspectError } from '@matterbridge/utils/error';
|
|
6
7
|
import { formatBytes, formatUptime } from '@matterbridge/utils/format';
|
|
@@ -13,11 +14,9 @@ logModuleLoaded('Cli');
|
|
|
13
14
|
export let instance;
|
|
14
15
|
export const tracker = new Tracker('Cli', false, false);
|
|
15
16
|
export const inspector = new Inspector('Cli', false, false);
|
|
17
|
+
const log = new AnsiLogger({ logName: 'Cli', logTimestampFormat: 4, logLevel: hasAnyParameter('debug', 'verbose') ? "debug" : "info" });
|
|
16
18
|
const manager = new ThreadsManager(path.dirname(fileURLToPath(import.meta.url)));
|
|
17
|
-
const
|
|
18
|
-
if (!colorEnabled)
|
|
19
|
-
process.env.NO_COLOR = '1';
|
|
20
|
-
const log = new AnsiLogger({ logName: 'Cli', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
19
|
+
const server = new BroadcastServer('cli', log);
|
|
21
20
|
function startCpuMemoryCheck() {
|
|
22
21
|
log.debug(`Cpu memory check starting...`);
|
|
23
22
|
tracker.start();
|
|
@@ -55,8 +54,8 @@ function registerHandlers() {
|
|
|
55
54
|
instance.on('shutdown', () => void shutdown().catch((error) => inspectError(log, 'Failed to shutdown', error)));
|
|
56
55
|
instance.on('restart', () => void restart().catch((error) => inspectError(log, 'Failed to restart', error)));
|
|
57
56
|
instance.on('update', () => void update().catch((error) => inspectError(log, 'Failed to update', error)));
|
|
58
|
-
instance.on('startmemorycheck', () =>
|
|
59
|
-
instance.on('stopmemorycheck', () =>
|
|
57
|
+
instance.on('startmemorycheck', () => startCpuMemoryCheck());
|
|
58
|
+
instance.on('stopmemorycheck', () => stopCpuMemoryCheck());
|
|
60
59
|
instance.on('startinspector', () => void startInspector().catch((error) => inspectError(log, 'Failed to start inspector', error)));
|
|
61
60
|
instance.on('stopinspector', () => void stopInspector().catch((error) => inspectError(log, 'Failed to stop inspector', error)));
|
|
62
61
|
instance.on('takeheapsnapshot', () => void takeHeapSnapshot().catch((error) => inspectError(log, 'Failed to take heap snapshot', error)));
|
|
@@ -68,6 +67,7 @@ async function shutdown() {
|
|
|
68
67
|
if (hasParameter('inspect'))
|
|
69
68
|
await stopInspector();
|
|
70
69
|
stopCpuMemoryCheck();
|
|
70
|
+
server.close();
|
|
71
71
|
manager.destroy();
|
|
72
72
|
cliEmitter.emit('shutdown');
|
|
73
73
|
log.debug('Received shutdown event, exit with code 0');
|
|
@@ -85,16 +85,10 @@ async function update() {
|
|
|
85
85
|
instance = await Matterbridge.loadInstance(true);
|
|
86
86
|
registerHandlers();
|
|
87
87
|
}
|
|
88
|
-
function
|
|
89
|
-
log.debug('Received start memory check event');
|
|
90
|
-
startCpuMemoryCheck();
|
|
91
|
-
}
|
|
92
|
-
function stop() {
|
|
93
|
-
log.debug('Received stop memory check event');
|
|
94
|
-
stopCpuMemoryCheck();
|
|
95
|
-
}
|
|
96
|
-
async function main() {
|
|
88
|
+
export async function main() {
|
|
97
89
|
log.debug(`Cli main() started`);
|
|
90
|
+
if (!(!process.env.NO_COLOR && process.env.TERM !== 'dumb' && process.env.FORCE_COLOR !== '0' && !hasParameter('no-ansi')))
|
|
91
|
+
process.env.NO_COLOR = '1';
|
|
98
92
|
startCpuMemoryCheck();
|
|
99
93
|
if (hasParameter('inspect'))
|
|
100
94
|
await startInspector();
|
|
@@ -175,6 +169,7 @@ function help() {
|
|
|
175
169
|
--novirtual: disable the creation of the virtual devices Restart, Update and Reboot Matterbridge
|
|
176
170
|
--root-power-source: add a Power Source cluster for the mains power feed on the Root endpoint
|
|
177
171
|
--ssl: enable SSL for the frontend and the WebSocketServer (the server will use the certificates and switch to https)
|
|
172
|
+
--tls: enable SSL for the frontend and the WebSocketServer (the server will use the certificates and switch to https)
|
|
178
173
|
--mtls: enable mTLS for the frontend and the WebSocketServer (both server and client will use and require the certificates and switch to https); it also enables https, so --ssl --tls is not needed
|
|
179
174
|
--vendorId: override the default vendorId 0xfff1
|
|
180
175
|
--vendorName: override the default vendorName "Matterbridge"
|
package/dist/matterbridge.js
CHANGED
|
@@ -863,12 +863,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
863
863
|
(hasParameter('controller') ? 'mode controller ' : '') +
|
|
864
864
|
(this.restartMode === 'none' ? '' : 'restart mode ' + this.restartMode + ' ') +
|
|
865
865
|
'running on ' + this.systemInformation.osType + ' (v.' + this.systemInformation.osRelease + ') platform ' + this.systemInformation.osPlatform + ' arch ' + this.systemInformation.osArch);
|
|
866
|
-
const minNodeVersion =
|
|
866
|
+
const minNodeVersion = 22;
|
|
867
|
+
const minNodeMinorVersion = 13;
|
|
867
868
|
const nodeVersion = process.versions.node;
|
|
868
|
-
const [versionMajor] = nodeVersion.split('.').map(Number);
|
|
869
|
-
if (versionMajor < minNodeVersion) {
|
|
870
|
-
this.log.error(`Node version ${
|
|
871
|
-
throw new Error(`Node version ${
|
|
869
|
+
const [versionMajor, versionMinor] = nodeVersion.split('.').map(Number);
|
|
870
|
+
if (versionMajor < minNodeVersion || (versionMajor === minNodeVersion && versionMinor < minNodeMinorVersion)) {
|
|
871
|
+
this.log.error(`Node version ${nodeVersion} is not supported. Please upgrade to ${minNodeVersion}.${minNodeMinorVersion}.0 or above.`);
|
|
872
|
+
throw new Error(`Node version ${nodeVersion} is not supported. Please upgrade to ${minNodeVersion}.${minNodeMinorVersion}.0 or above.`);
|
|
872
873
|
}
|
|
873
874
|
await this.parseCommandLine();
|
|
874
875
|
this.emit('initialize_completed');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterbridge/core",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.14-dev-20261010-607ee18",
|
|
4
4
|
"description": "Matterbridge core library",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"homepage": "https://matterbridge.io/",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"engines": {
|
|
123
|
-
"node": ">=
|
|
123
|
+
"node": ">=22.13.0 <23.0.0 || >=24.0.0 <25.0.0 || >=26.0.0 <27.0.0"
|
|
124
124
|
},
|
|
125
125
|
"files": [
|
|
126
126
|
"bin",
|
|
@@ -136,17 +136,17 @@
|
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
138
|
"@matter/main": "0.17.9",
|
|
139
|
-
"@matterbridge/dgram": "3.10.
|
|
140
|
-
"@matterbridge/thread": "3.10.
|
|
141
|
-
"@matterbridge/types": "3.10.
|
|
142
|
-
"@matterbridge/utils": "3.10.
|
|
139
|
+
"@matterbridge/dgram": "3.10.14-dev-20261010-607ee18",
|
|
140
|
+
"@matterbridge/thread": "3.10.14-dev-20261010-607ee18",
|
|
141
|
+
"@matterbridge/types": "3.10.14-dev-20261010-607ee18",
|
|
142
|
+
"@matterbridge/utils": "3.10.14-dev-20261010-607ee18",
|
|
143
143
|
"escape-html": "1.0.3",
|
|
144
144
|
"express": "5.2.1",
|
|
145
|
-
"express-rate-limit": "8.7.
|
|
145
|
+
"express-rate-limit": "8.7.1",
|
|
146
146
|
"multer": "2.4.0",
|
|
147
147
|
"node-ansi-logger": "3.3.1",
|
|
148
148
|
"node-persist-manager": "2.1.1",
|
|
149
|
-
"werift": "0.
|
|
149
|
+
"werift": "0.25.0",
|
|
150
150
|
"ws": "8.22.0"
|
|
151
151
|
}
|
|
152
152
|
}
|