@apocaliss92/nodelink-js 0.6.3 → 0.6.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/README.md +26 -0
- package/dist/{chunk-Q4AXRV2G.js → chunk-WQ2TQCYP.js} +539 -31
- package/dist/chunk-WQ2TQCYP.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +554 -43
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +645 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -48
- package/dist/index.d.ts +195 -2
- package/dist/index.js +61 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q4AXRV2G.js.map +0 -1
|
@@ -689,6 +689,7 @@ function readCache(uid, now) {
|
|
|
689
689
|
}
|
|
690
690
|
async function getServerBinding(uid, options = {}) {
|
|
691
691
|
if (!uid || typeof uid !== "string") return void 0;
|
|
692
|
+
uid = uid.toUpperCase();
|
|
692
693
|
const now = Date.now();
|
|
693
694
|
const cached = readCache(uid, now);
|
|
694
695
|
if (cached?.kind === "ok") return cached.response;
|
|
@@ -5976,9 +5977,354 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
5976
5977
|
}
|
|
5977
5978
|
}
|
|
5978
5979
|
|
|
5979
|
-
// src/baichuan/stream/
|
|
5980
|
-
|
|
5980
|
+
// src/baichuan/stream/alwaysOnTypes.ts
|
|
5981
|
+
var ALWAYS_ON_DEFAULTS = {
|
|
5982
|
+
triggers: ["motion", "doorbell"],
|
|
5983
|
+
windowMs: 15e3,
|
|
5984
|
+
idleFps: 1,
|
|
5985
|
+
primeOnStart: true,
|
|
5986
|
+
placeholder: { enabled: true, text: "Sleeping", opacity: 0.5 }
|
|
5987
|
+
};
|
|
5988
|
+
|
|
5989
|
+
// src/baichuan/stream/PlaceholderRenderer.ts
|
|
5981
5990
|
import { spawn } from "child_process";
|
|
5991
|
+
import { Jimp, JimpMime, loadFont, measureText, measureTextHeight } from "jimp";
|
|
5992
|
+
import { SANS_32_WHITE, SANS_64_WHITE, SANS_128_WHITE } from "jimp/fonts";
|
|
5993
|
+
function ffmpegCodec(videoType) {
|
|
5994
|
+
if (videoType === "H265") {
|
|
5995
|
+
return {
|
|
5996
|
+
inputFormat: "hevc",
|
|
5997
|
+
encoder: "libx265",
|
|
5998
|
+
outputFormat: "hevc"
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
return {
|
|
6002
|
+
inputFormat: "h264",
|
|
6003
|
+
encoder: "libx264",
|
|
6004
|
+
outputFormat: "h264"
|
|
6005
|
+
};
|
|
6006
|
+
}
|
|
6007
|
+
function runFfmpeg(args, input) {
|
|
6008
|
+
return new Promise((resolve, reject) => {
|
|
6009
|
+
const proc = spawn("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
6010
|
+
const stdoutChunks = [];
|
|
6011
|
+
const stderrChunks = [];
|
|
6012
|
+
proc.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
6013
|
+
proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
6014
|
+
proc.on("error", (error) => reject(error));
|
|
6015
|
+
proc.on("close", (code) => {
|
|
6016
|
+
if (code === 0) {
|
|
6017
|
+
resolve(Buffer.concat(stdoutChunks));
|
|
6018
|
+
return;
|
|
6019
|
+
}
|
|
6020
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
6021
|
+
reject(new Error(`ffmpeg exited with code ${code}: ${stderr}`));
|
|
6022
|
+
});
|
|
6023
|
+
const stdin = proc.stdin;
|
|
6024
|
+
if (!stdin) {
|
|
6025
|
+
reject(new Error("ffmpeg stdin not available"));
|
|
6026
|
+
return;
|
|
6027
|
+
}
|
|
6028
|
+
stdin.on("error", (error) => reject(error));
|
|
6029
|
+
stdin.end(input);
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
var PlaceholderRenderer = class {
|
|
6033
|
+
opts;
|
|
6034
|
+
logger;
|
|
6035
|
+
constructor(args) {
|
|
6036
|
+
this.opts = { ...ALWAYS_ON_DEFAULTS.placeholder, ...args.placeholder ?? {} };
|
|
6037
|
+
this.logger = args.logger;
|
|
6038
|
+
}
|
|
6039
|
+
/** Returns the access unit bytes to emit as placeholder, or null if none available. */
|
|
6040
|
+
async render(keyframe) {
|
|
6041
|
+
if (!keyframe) return null;
|
|
6042
|
+
if (!this.opts.enabled) return keyframe.data;
|
|
6043
|
+
try {
|
|
6044
|
+
const jpeg = await this.decodeToJpeg(keyframe);
|
|
6045
|
+
const decorated = await this.decorate(jpeg);
|
|
6046
|
+
const idr = await this.encodeIdr(decorated, keyframe.videoType);
|
|
6047
|
+
if (!idr || idr.length === 0) {
|
|
6048
|
+
throw new Error("ffmpeg produced empty IDR output");
|
|
6049
|
+
}
|
|
6050
|
+
return idr;
|
|
6051
|
+
} catch (error) {
|
|
6052
|
+
this.logger?.warn?.(
|
|
6053
|
+
"PlaceholderRenderer: decoration failed, falling back to raw keyframe",
|
|
6054
|
+
error instanceof Error ? error.message : error
|
|
6055
|
+
);
|
|
6056
|
+
return keyframe.data;
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
/** Decodes the cached keyframe access unit into a single JPEG still via ffmpeg. */
|
|
6060
|
+
async decodeToJpeg(keyframe) {
|
|
6061
|
+
const { inputFormat } = ffmpegCodec(keyframe.videoType);
|
|
6062
|
+
return runFfmpeg(
|
|
6063
|
+
[
|
|
6064
|
+
"-hide_banner",
|
|
6065
|
+
"-loglevel",
|
|
6066
|
+
"error",
|
|
6067
|
+
"-f",
|
|
6068
|
+
inputFormat,
|
|
6069
|
+
"-i",
|
|
6070
|
+
"pipe:0",
|
|
6071
|
+
"-frames:v",
|
|
6072
|
+
"1",
|
|
6073
|
+
"-f",
|
|
6074
|
+
"mjpeg",
|
|
6075
|
+
"pipe:1"
|
|
6076
|
+
],
|
|
6077
|
+
keyframe.data
|
|
6078
|
+
);
|
|
6079
|
+
}
|
|
6080
|
+
/** Dims the still and prints the overlay text using jimp, returning a JPEG buffer. */
|
|
6081
|
+
async decorate(jpeg) {
|
|
6082
|
+
const image = await Jimp.read(jpeg);
|
|
6083
|
+
const op = Math.max(0, Math.min(1, this.opts.opacity));
|
|
6084
|
+
if (op < 1) {
|
|
6085
|
+
const data = image.bitmap.data;
|
|
6086
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
6087
|
+
data[i] = data[i] * op;
|
|
6088
|
+
data[i + 1] = data[i + 1] * op;
|
|
6089
|
+
data[i + 2] = data[i + 2] * op;
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
const fontDef = image.width >= 1280 ? SANS_128_WHITE : image.width >= 640 ? SANS_64_WHITE : SANS_32_WHITE;
|
|
6093
|
+
const font = await loadFont(fontDef);
|
|
6094
|
+
const text = this.opts.text;
|
|
6095
|
+
const textWidth = measureText(font, text);
|
|
6096
|
+
const textHeight = measureTextHeight(font, text, image.width);
|
|
6097
|
+
const x = Math.max(0, Math.round((image.width - textWidth) / 2));
|
|
6098
|
+
const y = Math.max(0, Math.round((image.height - textHeight) / 2));
|
|
6099
|
+
image.print({ font, x, y, text });
|
|
6100
|
+
return image.getBuffer(JimpMime.jpeg);
|
|
6101
|
+
}
|
|
6102
|
+
/** Encodes the decorated JPEG into a single IDR access unit in the target codec. */
|
|
6103
|
+
async encodeIdr(jpeg, videoType) {
|
|
6104
|
+
const { encoder, outputFormat } = ffmpegCodec(videoType);
|
|
6105
|
+
return runFfmpeg(
|
|
6106
|
+
[
|
|
6107
|
+
"-hide_banner",
|
|
6108
|
+
"-loglevel",
|
|
6109
|
+
"error",
|
|
6110
|
+
"-f",
|
|
6111
|
+
"image2pipe",
|
|
6112
|
+
"-i",
|
|
6113
|
+
"pipe:0",
|
|
6114
|
+
"-frames:v",
|
|
6115
|
+
"1",
|
|
6116
|
+
"-c:v",
|
|
6117
|
+
encoder,
|
|
6118
|
+
"-pix_fmt",
|
|
6119
|
+
"yuv420p",
|
|
6120
|
+
"-f",
|
|
6121
|
+
outputFormat,
|
|
6122
|
+
"pipe:1"
|
|
6123
|
+
],
|
|
6124
|
+
jpeg
|
|
6125
|
+
);
|
|
6126
|
+
}
|
|
6127
|
+
};
|
|
6128
|
+
|
|
6129
|
+
// src/baichuan/stream/ContinuousVideoStream.ts
|
|
6130
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
6131
|
+
var ContinuousVideoStream = class extends EventEmitter3 {
|
|
6132
|
+
constructor(opts) {
|
|
6133
|
+
super();
|
|
6134
|
+
this.opts = opts;
|
|
6135
|
+
this.idleFps = Math.max(0.1, opts.idleFps ?? ALWAYS_ON_DEFAULTS.idleFps);
|
|
6136
|
+
this.logger = opts.logger;
|
|
6137
|
+
const rendererArgs = {};
|
|
6138
|
+
if (opts.placeholder !== void 0) rendererArgs.placeholder = opts.placeholder;
|
|
6139
|
+
if (opts.logger !== void 0) rendererArgs.logger = opts.logger;
|
|
6140
|
+
this.renderer = opts.renderer ?? new PlaceholderRenderer(rendererArgs);
|
|
6141
|
+
}
|
|
6142
|
+
live = null;
|
|
6143
|
+
lastKeyframe = null;
|
|
6144
|
+
lastMicroseconds = 0;
|
|
6145
|
+
idleFps;
|
|
6146
|
+
renderer;
|
|
6147
|
+
logger;
|
|
6148
|
+
stopped = false;
|
|
6149
|
+
starting = false;
|
|
6150
|
+
idleTimer = null;
|
|
6151
|
+
idlePlaceholder = null;
|
|
6152
|
+
hasCachedKeyframe() {
|
|
6153
|
+
return this.lastKeyframe !== null;
|
|
6154
|
+
}
|
|
6155
|
+
async goLive() {
|
|
6156
|
+
if (this.stopped || this.live || this.starting) return;
|
|
6157
|
+
this.starting = true;
|
|
6158
|
+
try {
|
|
6159
|
+
this.stopIdleLoop();
|
|
6160
|
+
const stream = await this.opts.createLiveStream();
|
|
6161
|
+
this.live = stream;
|
|
6162
|
+
stream.on("videoAccessUnit", this.onLiveAccessUnit);
|
|
6163
|
+
stream.on("additionalHeader", this.onAdditionalHeader);
|
|
6164
|
+
stream.on("audioFrame", this.onAudioFrame);
|
|
6165
|
+
stream.on("error", this.onLiveError);
|
|
6166
|
+
await stream.start().catch((e) => this.emit("error", e));
|
|
6167
|
+
} finally {
|
|
6168
|
+
this.starting = false;
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
async goIdle() {
|
|
6172
|
+
if (!this.live) return;
|
|
6173
|
+
const s = this.live;
|
|
6174
|
+
this.live = null;
|
|
6175
|
+
s.off("videoAccessUnit", this.onLiveAccessUnit);
|
|
6176
|
+
s.off("additionalHeader", this.onAdditionalHeader);
|
|
6177
|
+
s.off("audioFrame", this.onAudioFrame);
|
|
6178
|
+
s.off("error", this.onLiveError);
|
|
6179
|
+
await s.stop().catch(() => {
|
|
6180
|
+
});
|
|
6181
|
+
await this.startIdleLoop();
|
|
6182
|
+
}
|
|
6183
|
+
async stop() {
|
|
6184
|
+
this.stopped = true;
|
|
6185
|
+
await this.goIdle();
|
|
6186
|
+
this.stopIdleLoop();
|
|
6187
|
+
this.emit("close");
|
|
6188
|
+
}
|
|
6189
|
+
async startIdleLoop() {
|
|
6190
|
+
if (this.stopped) return;
|
|
6191
|
+
this.idlePlaceholder = await this.renderer.render(this.lastKeyframe);
|
|
6192
|
+
if (!this.idlePlaceholder || !this.lastKeyframe) {
|
|
6193
|
+
this.logger?.debug?.("[ContinuousVideoStream] no keyframe yet; idle loop deferred");
|
|
6194
|
+
return;
|
|
6195
|
+
}
|
|
6196
|
+
const stepUs = Math.round(1e6 / this.idleFps);
|
|
6197
|
+
const videoType = this.lastKeyframe.videoType;
|
|
6198
|
+
this.idleTimer = setInterval(() => {
|
|
6199
|
+
if (!this.idlePlaceholder) return;
|
|
6200
|
+
this.lastMicroseconds += stepUs;
|
|
6201
|
+
this.emit("videoAccessUnit", {
|
|
6202
|
+
data: this.idlePlaceholder,
|
|
6203
|
+
isKeyframe: true,
|
|
6204
|
+
videoType,
|
|
6205
|
+
microseconds: this.lastMicroseconds
|
|
6206
|
+
});
|
|
6207
|
+
}, Math.round(1e3 / this.idleFps));
|
|
6208
|
+
}
|
|
6209
|
+
stopIdleLoop() {
|
|
6210
|
+
if (this.idleTimer) {
|
|
6211
|
+
clearInterval(this.idleTimer);
|
|
6212
|
+
this.idleTimer = null;
|
|
6213
|
+
}
|
|
6214
|
+
this.idlePlaceholder = null;
|
|
6215
|
+
}
|
|
6216
|
+
onLiveAccessUnit = (au) => {
|
|
6217
|
+
if (au.isKeyframe) {
|
|
6218
|
+
this.lastKeyframe = { data: au.data, videoType: au.videoType };
|
|
6219
|
+
}
|
|
6220
|
+
this.lastMicroseconds = au.microseconds;
|
|
6221
|
+
this.emit("videoAccessUnit", au);
|
|
6222
|
+
};
|
|
6223
|
+
onAdditionalHeader = (h) => this.emit("additionalHeader", h);
|
|
6224
|
+
onAudioFrame = (a) => this.emit("audioFrame", a);
|
|
6225
|
+
onLiveError = (e) => this.emit("error", e);
|
|
6226
|
+
};
|
|
6227
|
+
|
|
6228
|
+
// src/baichuan/stream/AlwaysOnController.ts
|
|
6229
|
+
var AlwaysOnController = class {
|
|
6230
|
+
constructor(o) {
|
|
6231
|
+
this.o = o;
|
|
6232
|
+
this.triggers = new Set(o.options.triggers ?? ALWAYS_ON_DEFAULTS.triggers);
|
|
6233
|
+
this.windowMs = o.options.windowMs ?? ALWAYS_ON_DEFAULTS.windowMs;
|
|
6234
|
+
this.primeOnStart = o.options.primeOnStart ?? ALWAYS_ON_DEFAULTS.primeOnStart;
|
|
6235
|
+
this.logger = o.logger;
|
|
6236
|
+
}
|
|
6237
|
+
triggers;
|
|
6238
|
+
windowMs;
|
|
6239
|
+
primeOnStart;
|
|
6240
|
+
logger;
|
|
6241
|
+
windowTimer = null;
|
|
6242
|
+
live = false;
|
|
6243
|
+
started = false;
|
|
6244
|
+
handler = (e) => void this.onEvent(e);
|
|
6245
|
+
get windowSeconds() {
|
|
6246
|
+
return Math.round(this.windowMs / 1e3);
|
|
6247
|
+
}
|
|
6248
|
+
async start() {
|
|
6249
|
+
if (this.started) return;
|
|
6250
|
+
this.started = true;
|
|
6251
|
+
await this.o.api.onSimpleEvent(this.handler);
|
|
6252
|
+
this.logger?.info?.(
|
|
6253
|
+
`[AlwaysOnController] started ch${this.o.channel} \u2014 triggers=[${[...this.triggers].join(", ")}], window=${this.windowSeconds}s, primeOnStart=${this.primeOnStart}`
|
|
6254
|
+
);
|
|
6255
|
+
if (this.primeOnStart) {
|
|
6256
|
+
await this.openWindow("prime");
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
async stop() {
|
|
6260
|
+
if (!this.started) return;
|
|
6261
|
+
this.started = false;
|
|
6262
|
+
if (this.windowTimer) {
|
|
6263
|
+
clearTimeout(this.windowTimer);
|
|
6264
|
+
this.windowTimer = null;
|
|
6265
|
+
}
|
|
6266
|
+
await this.o.api.offSimpleEvent(this.handler).catch(() => {
|
|
6267
|
+
});
|
|
6268
|
+
if (this.live) {
|
|
6269
|
+
this.live = false;
|
|
6270
|
+
await this.o.goIdle().catch(() => {
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
this.logger?.info?.(`[AlwaysOnController] stopped ch${this.o.channel}`);
|
|
6274
|
+
}
|
|
6275
|
+
async onEvent(e) {
|
|
6276
|
+
if (e.channel !== this.o.channel) return;
|
|
6277
|
+
if (!this.triggers.has(e.type)) {
|
|
6278
|
+
this.logger?.debug?.(
|
|
6279
|
+
`[AlwaysOnController] event '${e.type}' ch${e.channel} ignored (not a configured trigger)`
|
|
6280
|
+
);
|
|
6281
|
+
return;
|
|
6282
|
+
}
|
|
6283
|
+
await this.openWindow(e.type);
|
|
6284
|
+
}
|
|
6285
|
+
async openWindow(reason) {
|
|
6286
|
+
if (this.windowTimer) clearTimeout(this.windowTimer);
|
|
6287
|
+
if (!this.live) {
|
|
6288
|
+
this.live = true;
|
|
6289
|
+
try {
|
|
6290
|
+
await this.o.api.wakeUp(this.o.channel).catch(() => {
|
|
6291
|
+
});
|
|
6292
|
+
await this.o.goLive();
|
|
6293
|
+
this.logger?.info?.(
|
|
6294
|
+
`[AlwaysOnController] live window OPENED (trigger=${reason}) \u2014 streaming real frames; will sleep in ${this.windowSeconds}s without new events`
|
|
6295
|
+
);
|
|
6296
|
+
} catch (err) {
|
|
6297
|
+
this.live = false;
|
|
6298
|
+
this.logger?.warn?.(
|
|
6299
|
+
`[AlwaysOnController] goLive failed: ${err?.message}`
|
|
6300
|
+
);
|
|
6301
|
+
return;
|
|
6302
|
+
}
|
|
6303
|
+
} else {
|
|
6304
|
+
this.logger?.info?.(
|
|
6305
|
+
`[AlwaysOnController] live window EXTENDED (trigger=${reason}) \u2014 sleep timer reset to ${this.windowSeconds}s`
|
|
6306
|
+
);
|
|
6307
|
+
}
|
|
6308
|
+
this.windowTimer = setTimeout(() => void this.closeWindow(), this.windowMs);
|
|
6309
|
+
}
|
|
6310
|
+
async closeWindow() {
|
|
6311
|
+
this.windowTimer = null;
|
|
6312
|
+
if (!this.live) return;
|
|
6313
|
+
this.live = false;
|
|
6314
|
+
this.logger?.info?.(
|
|
6315
|
+
`[AlwaysOnController] live window CLOSED \u2014 going idle (placeholder); camera can sleep`
|
|
6316
|
+
);
|
|
6317
|
+
await this.o.goIdle().catch(
|
|
6318
|
+
(err) => this.logger?.warn?.(
|
|
6319
|
+
`[AlwaysOnController] goIdle failed: ${err?.message}`
|
|
6320
|
+
)
|
|
6321
|
+
);
|
|
6322
|
+
}
|
|
6323
|
+
};
|
|
6324
|
+
|
|
6325
|
+
// src/baichuan/stream/BaichuanRtspServer.ts
|
|
6326
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
6327
|
+
import { spawn as spawn2 } from "child_process";
|
|
5982
6328
|
import * as net2 from "net";
|
|
5983
6329
|
import * as dgram2 from "dgram";
|
|
5984
6330
|
import * as crypto from "crypto";
|
|
@@ -6205,7 +6551,7 @@ function envBool(value, defaultValue) {
|
|
|
6205
6551
|
if (v === "0" || v === "false" || v === "no" || v === "off") return false;
|
|
6206
6552
|
return defaultValue;
|
|
6207
6553
|
}
|
|
6208
|
-
var BaichuanRtspServer = class _BaichuanRtspServer extends
|
|
6554
|
+
var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter4 {
|
|
6209
6555
|
api;
|
|
6210
6556
|
channel;
|
|
6211
6557
|
profile;
|
|
@@ -6220,6 +6566,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6220
6566
|
deviceId;
|
|
6221
6567
|
dedicatedSessionRelease;
|
|
6222
6568
|
externalListener;
|
|
6569
|
+
// Always-on continuous stream (battery cameras). Populated only when
|
|
6570
|
+
// `options.alwaysOn?.enabled`; the default (non-alwaysOn) path leaves these
|
|
6571
|
+
// null/undefined and is byte-for-byte equivalent in behaviour.
|
|
6572
|
+
alwaysOnOptions;
|
|
6573
|
+
continuousStream = null;
|
|
6574
|
+
alwaysOnController = null;
|
|
6223
6575
|
// Authentication
|
|
6224
6576
|
authCredentials = [];
|
|
6225
6577
|
requireAuth;
|
|
@@ -6234,6 +6586,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6234
6586
|
// Set of client IDs (IP:port)
|
|
6235
6587
|
nativeStreamActive = false;
|
|
6236
6588
|
// Whether the native stream is currently active
|
|
6589
|
+
tearingDown = false;
|
|
6590
|
+
// True while stop() is running; suppresses onEnd-driven restarts
|
|
6237
6591
|
clientConnectionServer;
|
|
6238
6592
|
// TCP server to track connections
|
|
6239
6593
|
streamMetadata = null;
|
|
@@ -6421,6 +6775,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6421
6775
|
this.requireAuth = options.requireAuth ?? this.authCredentials.length > 0;
|
|
6422
6776
|
this.AUTH_REALM = options.authRealm ?? "BaichuanRtspServer";
|
|
6423
6777
|
this.lazyMetadata = options.lazyMetadata ?? false;
|
|
6778
|
+
this.alwaysOnOptions = options.alwaysOn;
|
|
6424
6779
|
const transport = this.api.client.getTransport();
|
|
6425
6780
|
this.flow = createRtspFlow(transport, "H264");
|
|
6426
6781
|
}
|
|
@@ -7544,7 +7899,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7544
7899
|
this.rtspDebugLog(
|
|
7545
7900
|
`Spawning ffmpeg for client ${clientId}: ffmpeg ${ffmpegArgs.join(" ")}`
|
|
7546
7901
|
);
|
|
7547
|
-
ffmpeg =
|
|
7902
|
+
ffmpeg = spawn2("ffmpeg", ffmpegArgs, {
|
|
7548
7903
|
stdio
|
|
7549
7904
|
});
|
|
7550
7905
|
try {
|
|
@@ -7908,6 +8263,141 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7908
8263
|
}
|
|
7909
8264
|
});
|
|
7910
8265
|
}
|
|
8266
|
+
/**
|
|
8267
|
+
* Always-on source: bridge a {@link ContinuousVideoStream} into the existing
|
|
8268
|
+
* fanout. Yields the same frame shape that `createNativeStream` produces, so
|
|
8269
|
+
* the rest of the pipeline (prebuffer, param-set extraction, per-client
|
|
8270
|
+
* subscribe, ffmpeg/direct-RTP) is unchanged.
|
|
8271
|
+
*
|
|
8272
|
+
* The CVS itself is long-lived (created once, reused across native-stream
|
|
8273
|
+
* restarts) and is driven by the {@link AlwaysOnController}, which opens/closes
|
|
8274
|
+
* live windows from camera events. Each fanout source generator only forwards
|
|
8275
|
+
* CVS events to the fanout pump for as long as `signal` is not aborted.
|
|
8276
|
+
*/
|
|
8277
|
+
async *createContinuousSource(dedicatedClient, signal) {
|
|
8278
|
+
const cvs = this.ensureContinuousStream(dedicatedClient);
|
|
8279
|
+
const queue = [];
|
|
8280
|
+
const MAX_QUEUE = 200;
|
|
8281
|
+
let wake = null;
|
|
8282
|
+
let done = false;
|
|
8283
|
+
const push = (frame) => {
|
|
8284
|
+
queue.push(frame);
|
|
8285
|
+
if (queue.length > MAX_QUEUE) {
|
|
8286
|
+
queue.splice(0, queue.length - MAX_QUEUE);
|
|
8287
|
+
}
|
|
8288
|
+
if (wake) {
|
|
8289
|
+
const w = wake;
|
|
8290
|
+
wake = null;
|
|
8291
|
+
w();
|
|
8292
|
+
}
|
|
8293
|
+
};
|
|
8294
|
+
const onVideo = (au) => {
|
|
8295
|
+
push({
|
|
8296
|
+
audio: false,
|
|
8297
|
+
data: au.data,
|
|
8298
|
+
codec: null,
|
|
8299
|
+
sampleRate: null,
|
|
8300
|
+
microseconds: au.microseconds,
|
|
8301
|
+
videoType: au.videoType,
|
|
8302
|
+
isKeyframe: au.isKeyframe
|
|
8303
|
+
});
|
|
8304
|
+
};
|
|
8305
|
+
const onAudio = (frame) => {
|
|
8306
|
+
push({
|
|
8307
|
+
audio: true,
|
|
8308
|
+
data: frame,
|
|
8309
|
+
codec: "aac",
|
|
8310
|
+
sampleRate: 8e3,
|
|
8311
|
+
microseconds: null
|
|
8312
|
+
});
|
|
8313
|
+
};
|
|
8314
|
+
const finish = () => {
|
|
8315
|
+
done = true;
|
|
8316
|
+
if (wake) {
|
|
8317
|
+
const w = wake;
|
|
8318
|
+
wake = null;
|
|
8319
|
+
w();
|
|
8320
|
+
}
|
|
8321
|
+
};
|
|
8322
|
+
const onAbort = () => finish();
|
|
8323
|
+
cvs.on("videoAccessUnit", onVideo);
|
|
8324
|
+
cvs.on("audioFrame", onAudio);
|
|
8325
|
+
cvs.on("close", finish);
|
|
8326
|
+
if (signal.aborted) {
|
|
8327
|
+
done = true;
|
|
8328
|
+
} else {
|
|
8329
|
+
signal.addEventListener("abort", onAbort);
|
|
8330
|
+
}
|
|
8331
|
+
try {
|
|
8332
|
+
while (!done && !signal.aborted) {
|
|
8333
|
+
if (queue.length > 0) {
|
|
8334
|
+
yield queue.shift();
|
|
8335
|
+
} else {
|
|
8336
|
+
await new Promise((resolve) => {
|
|
8337
|
+
wake = resolve;
|
|
8338
|
+
if (done || signal.aborted) {
|
|
8339
|
+
wake = null;
|
|
8340
|
+
resolve();
|
|
8341
|
+
}
|
|
8342
|
+
});
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
while (queue.length > 0 && !signal.aborted) {
|
|
8346
|
+
yield queue.shift();
|
|
8347
|
+
}
|
|
8348
|
+
} finally {
|
|
8349
|
+
cvs.off("videoAccessUnit", onVideo);
|
|
8350
|
+
cvs.off("audioFrame", onAudio);
|
|
8351
|
+
cvs.off("close", finish);
|
|
8352
|
+
signal.removeEventListener("abort", onAbort);
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
/**
|
|
8356
|
+
* Lazily build the long-lived {@link ContinuousVideoStream} +
|
|
8357
|
+
* {@link AlwaysOnController} for always-on mode. Both are created once and
|
|
8358
|
+
* reused for the lifetime of the server (across native-stream restarts).
|
|
8359
|
+
*/
|
|
8360
|
+
ensureContinuousStream(dedicatedClient) {
|
|
8361
|
+
if (this.continuousStream) return this.continuousStream;
|
|
8362
|
+
const createLiveStream = async () => {
|
|
8363
|
+
const client = dedicatedClient ?? this.api.client;
|
|
8364
|
+
return new BaichuanVideoStream({
|
|
8365
|
+
client,
|
|
8366
|
+
api: this.api,
|
|
8367
|
+
channel: this.channel,
|
|
8368
|
+
profile: this.profile,
|
|
8369
|
+
...this.variant !== "default" ? { variant: this.variant } : {},
|
|
8370
|
+
...this.logger ? { logger: this.logger } : {}
|
|
8371
|
+
});
|
|
8372
|
+
};
|
|
8373
|
+
const cvsOptions = {
|
|
8374
|
+
createLiveStream,
|
|
8375
|
+
...this.alwaysOnOptions?.idleFps !== void 0 ? { idleFps: this.alwaysOnOptions.idleFps } : {},
|
|
8376
|
+
...this.alwaysOnOptions?.placeholder !== void 0 ? { placeholder: this.alwaysOnOptions.placeholder } : {},
|
|
8377
|
+
...this.logger ? { logger: this.logger } : {}
|
|
8378
|
+
};
|
|
8379
|
+
const cvs = new ContinuousVideoStream(cvsOptions);
|
|
8380
|
+
cvs.on("error", (e) => {
|
|
8381
|
+
this.logger.warn(
|
|
8382
|
+
`[BaichuanRtspServer] ContinuousVideoStream error: ${e?.message ?? e}`
|
|
8383
|
+
);
|
|
8384
|
+
});
|
|
8385
|
+
this.continuousStream = cvs;
|
|
8386
|
+
this.alwaysOnController = new AlwaysOnController({
|
|
8387
|
+
api: this.api,
|
|
8388
|
+
channel: this.channel,
|
|
8389
|
+
options: this.alwaysOnOptions,
|
|
8390
|
+
goLive: () => cvs.goLive(),
|
|
8391
|
+
goIdle: () => cvs.goIdle(),
|
|
8392
|
+
...this.logger ? { logger: this.logger } : {}
|
|
8393
|
+
});
|
|
8394
|
+
void this.alwaysOnController.start().catch((e) => {
|
|
8395
|
+
this.logger.warn(
|
|
8396
|
+
`[BaichuanRtspServer] AlwaysOnController start failed: ${e?.message ?? e}`
|
|
8397
|
+
);
|
|
8398
|
+
});
|
|
8399
|
+
return cvs;
|
|
8400
|
+
}
|
|
7911
8401
|
/**
|
|
7912
8402
|
* Start native stream (mark as active).
|
|
7913
8403
|
* Each client will create its own generator, so we just track that the stream is active.
|
|
@@ -7969,7 +8459,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7969
8459
|
await this.flow.startKeepAlive(this.api);
|
|
7970
8460
|
this.nativeFanout = new NativeStreamFanout({
|
|
7971
8461
|
maxQueueItems: 200,
|
|
7972
|
-
createSource: (signal) => createNativeStream(this.api, this.channel, this.profile, {
|
|
8462
|
+
createSource: (signal) => this.alwaysOnOptions?.enabled ? this.createContinuousSource(dedicatedClient, signal) : createNativeStream(this.api, this.channel, this.profile, {
|
|
7973
8463
|
variant: this.variant,
|
|
7974
8464
|
...dedicatedClient ? { client: dedicatedClient } : {},
|
|
7975
8465
|
signal
|
|
@@ -8052,6 +8542,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
8052
8542
|
} catch {
|
|
8053
8543
|
}
|
|
8054
8544
|
}
|
|
8545
|
+
if (this.tearingDown) return;
|
|
8055
8546
|
if (this.connectedClients.size > 0 && hadFrames) {
|
|
8056
8547
|
this.logger.info(
|
|
8057
8548
|
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
@@ -8065,7 +8556,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
8065
8556
|
});
|
|
8066
8557
|
this.nativeFanout.start();
|
|
8067
8558
|
this.clearNoFrameDeadlineTimer();
|
|
8068
|
-
if (this.nativeStreamNoFrameDeadlineMs > 0) {
|
|
8559
|
+
if (this.nativeStreamNoFrameDeadlineMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
8069
8560
|
this.noFrameDeadlineTimer = setTimeout(() => {
|
|
8070
8561
|
this.noFrameDeadlineTimer = void 0;
|
|
8071
8562
|
if (!this.firstFrameReceived && this.nativeStreamActive) {
|
|
@@ -8078,7 +8569,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
8078
8569
|
this.noFrameDeadlineTimer?.unref?.();
|
|
8079
8570
|
}
|
|
8080
8571
|
this.clearNoClientAutoStopTimer();
|
|
8081
|
-
if (this.nativeStreamPrimeIdleStopMs > 0) {
|
|
8572
|
+
if (this.nativeStreamPrimeIdleStopMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
8082
8573
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
8083
8574
|
if (this.connectedClients.size === 0) {
|
|
8084
8575
|
this.rtspDebugLog(
|
|
@@ -8165,7 +8656,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
8165
8656
|
this.emit("clientDisconnected", clientId);
|
|
8166
8657
|
if (this.connectedClients.size === 0) {
|
|
8167
8658
|
this.clearNoClientAutoStopTimer();
|
|
8168
|
-
if (this.nativeStreamIdleStopMs > 0) {
|
|
8659
|
+
if (this.nativeStreamIdleStopMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
8169
8660
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
8170
8661
|
if (this.connectedClients.size === 0) {
|
|
8171
8662
|
void this.stopNativeStream();
|
|
@@ -8236,9 +8727,22 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
8236
8727
|
if (!this.active) {
|
|
8237
8728
|
return;
|
|
8238
8729
|
}
|
|
8730
|
+
this.tearingDown = true;
|
|
8239
8731
|
this.logger.info(
|
|
8240
8732
|
`[BaichuanRtspServer] Stopping RTSP server on ${this.listenHost}:${this.listenPort}...`
|
|
8241
8733
|
);
|
|
8734
|
+
if (this.alwaysOnController) {
|
|
8735
|
+
const controller = this.alwaysOnController;
|
|
8736
|
+
this.alwaysOnController = null;
|
|
8737
|
+
await controller.stop().catch(() => {
|
|
8738
|
+
});
|
|
8739
|
+
}
|
|
8740
|
+
if (this.continuousStream) {
|
|
8741
|
+
const cvs = this.continuousStream;
|
|
8742
|
+
this.continuousStream = null;
|
|
8743
|
+
await cvs.stop().catch(() => {
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
8242
8746
|
await this.stopNativeStream();
|
|
8243
8747
|
const clientIds = Array.from(this.connectedClients);
|
|
8244
8748
|
for (const clientId of clientIds) {
|
|
@@ -9198,8 +9702,8 @@ function patchMotionSensitivityListXml(currentXml, bands) {
|
|
|
9198
9702
|
}
|
|
9199
9703
|
|
|
9200
9704
|
// src/emailPush/bus.ts
|
|
9201
|
-
import { EventEmitter as
|
|
9202
|
-
var emitter = new
|
|
9705
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
9706
|
+
var emitter = new EventEmitter5();
|
|
9203
9707
|
var cameraResolver = () => void 0;
|
|
9204
9708
|
var lastEventByCamera = /* @__PURE__ */ new Map();
|
|
9205
9709
|
var MAX_GLOBAL_EVENTS = 300;
|
|
@@ -9252,7 +9756,7 @@ function _resetEmailPushBusForTests() {
|
|
|
9252
9756
|
}
|
|
9253
9757
|
|
|
9254
9758
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
9255
|
-
import { spawn as
|
|
9759
|
+
import { spawn as spawn3 } from "child_process";
|
|
9256
9760
|
import { mkdir } from "fs/promises";
|
|
9257
9761
|
import { dirname } from "path";
|
|
9258
9762
|
import { PassThrough } from "stream";
|
|
@@ -9581,7 +10085,7 @@ function buildSetSystemGeneralXml(patch) {
|
|
|
9581
10085
|
}
|
|
9582
10086
|
|
|
9583
10087
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
9584
|
-
import { Jimp, JimpMime } from "jimp";
|
|
10088
|
+
import { Jimp as Jimp2, JimpMime as JimpMime2 } from "jimp";
|
|
9585
10089
|
|
|
9586
10090
|
// src/reolink/baichuan/utils/abilityInfo.ts
|
|
9587
10091
|
var parseAbilityInfoXml = (xml) => {
|
|
@@ -15708,12 +16212,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
15708
16212
|
let wideImg;
|
|
15709
16213
|
let teleImg;
|
|
15710
16214
|
try {
|
|
15711
|
-
wideImg = await
|
|
16215
|
+
wideImg = await Jimp2.read(wide);
|
|
15712
16216
|
} catch {
|
|
15713
16217
|
return wide;
|
|
15714
16218
|
}
|
|
15715
16219
|
try {
|
|
15716
|
-
teleImg = await
|
|
16220
|
+
teleImg = await Jimp2.read(tele);
|
|
15717
16221
|
} catch {
|
|
15718
16222
|
return wide;
|
|
15719
16223
|
}
|
|
@@ -15747,7 +16251,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
15747
16251
|
});
|
|
15748
16252
|
teleImg.resize({ w: pipW, h: pipH });
|
|
15749
16253
|
wideImg.composite(teleImg, left, top);
|
|
15750
|
-
return await wideImg.getBuffer(
|
|
16254
|
+
return await wideImg.getBuffer(JimpMime2.jpeg, { quality: 80 });
|
|
15751
16255
|
}
|
|
15752
16256
|
const ch = channel !== void 0 ? this.normalizeChannel(channel) : 0;
|
|
15753
16257
|
const variant = options?.variant ?? "default";
|
|
@@ -16705,7 +17209,7 @@ ${xml}`);
|
|
|
16705
17209
|
const chunks = [];
|
|
16706
17210
|
let stderr = "";
|
|
16707
17211
|
let timedOut = false;
|
|
16708
|
-
const ff =
|
|
17212
|
+
const ff = spawn3(params.ffmpegPath, [
|
|
16709
17213
|
"-hide_banner",
|
|
16710
17214
|
"-loglevel",
|
|
16711
17215
|
"error",
|
|
@@ -16790,7 +17294,7 @@ ${xml}`);
|
|
|
16790
17294
|
const chunks = [];
|
|
16791
17295
|
let stderr = "";
|
|
16792
17296
|
let timedOut = false;
|
|
16793
|
-
const ff =
|
|
17297
|
+
const ff = spawn3(ffmpegPath, [
|
|
16794
17298
|
"-hide_banner",
|
|
16795
17299
|
"-loglevel",
|
|
16796
17300
|
"error",
|
|
@@ -16906,7 +17410,7 @@ ${xml}`);
|
|
|
16906
17410
|
ensureEnabled: true
|
|
16907
17411
|
});
|
|
16908
17412
|
await new Promise((resolve, reject) => {
|
|
16909
|
-
const ff =
|
|
17413
|
+
const ff = spawn3(ffmpegPath, [
|
|
16910
17414
|
"-hide_banner",
|
|
16911
17415
|
"-loglevel",
|
|
16912
17416
|
"error",
|
|
@@ -16962,7 +17466,7 @@ ${stderr}`));
|
|
|
16962
17466
|
const atSeconds = Number.isFinite(params.atSeconds) && params.atSeconds >= 0 ? params.atSeconds : 0;
|
|
16963
17467
|
await mkdir(dirname(params.outputPath), { recursive: true });
|
|
16964
17468
|
await new Promise((resolve, reject) => {
|
|
16965
|
-
const ff =
|
|
17469
|
+
const ff = spawn3(ffmpegPath, [
|
|
16966
17470
|
"-hide_banner",
|
|
16967
17471
|
"-loglevel",
|
|
16968
17472
|
"error",
|
|
@@ -17527,7 +18031,7 @@ ${stderr}`)
|
|
|
17527
18031
|
* Convert a raw video keyframe to JPEG using ffmpeg.
|
|
17528
18032
|
*/
|
|
17529
18033
|
async convertFrameToJpeg(params) {
|
|
17530
|
-
const { spawn:
|
|
18034
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
17531
18035
|
const ffmpeg = params.ffmpegPath ?? "ffmpeg";
|
|
17532
18036
|
const inputFormat = params.videoCodec === "H265" ? "hevc" : "h264";
|
|
17533
18037
|
return new Promise((resolve, reject) => {
|
|
@@ -17549,7 +18053,7 @@ ${stderr}`)
|
|
|
17549
18053
|
"2",
|
|
17550
18054
|
"pipe:1"
|
|
17551
18055
|
];
|
|
17552
|
-
const proc =
|
|
18056
|
+
const proc = spawn4(ffmpeg, args, {
|
|
17553
18057
|
stdio: ["pipe", "pipe", "pipe"]
|
|
17554
18058
|
});
|
|
17555
18059
|
const chunks = [];
|
|
@@ -17692,7 +18196,7 @@ ${stderr}`)
|
|
|
17692
18196
|
* Internal helper to mux video+audio into MP4 using ffmpeg.
|
|
17693
18197
|
*/
|
|
17694
18198
|
async muxToMp4(params) {
|
|
17695
|
-
const { spawn:
|
|
18199
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
17696
18200
|
const { randomUUID: randomUUID3 } = await import("crypto");
|
|
17697
18201
|
const fs = await import("fs/promises");
|
|
17698
18202
|
const os = await import("os");
|
|
@@ -17744,7 +18248,7 @@ ${stderr}`)
|
|
|
17744
18248
|
outputPath
|
|
17745
18249
|
);
|
|
17746
18250
|
await new Promise((resolve, reject) => {
|
|
17747
|
-
const p =
|
|
18251
|
+
const p = spawn4(ffmpeg, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
17748
18252
|
let stderr = "";
|
|
17749
18253
|
p.stderr.on("data", (d) => {
|
|
17750
18254
|
stderr += d.toString();
|
|
@@ -22731,7 +23235,7 @@ ${scheduleItems}
|
|
|
22731
23235
|
"mjpeg",
|
|
22732
23236
|
"pipe:1"
|
|
22733
23237
|
];
|
|
22734
|
-
const ff =
|
|
23238
|
+
const ff = spawn3("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
22735
23239
|
const chunks = [];
|
|
22736
23240
|
let stderr = "";
|
|
22737
23241
|
ff.stdout.on("data", (d) => chunks.push(Buffer.from(d)));
|
|
@@ -22855,7 +23359,7 @@ ${scheduleItems}
|
|
|
22855
23359
|
"pipe:1"
|
|
22856
23360
|
];
|
|
22857
23361
|
}
|
|
22858
|
-
ff =
|
|
23362
|
+
ff = spawn3("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
22859
23363
|
if (!ff.stdin || !ff.stdout || !ff.stderr) {
|
|
22860
23364
|
throw new Error("ffmpeg stdio streams not available");
|
|
22861
23365
|
}
|
|
@@ -23102,7 +23606,7 @@ ${scheduleItems}
|
|
|
23102
23606
|
"mp4",
|
|
23103
23607
|
"pipe:1"
|
|
23104
23608
|
];
|
|
23105
|
-
ff =
|
|
23609
|
+
ff = spawn3("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
23106
23610
|
if (!ff.stdin || !ff.stdout || !ff.stderr) {
|
|
23107
23611
|
throw new Error("ffmpeg stdio streams not available");
|
|
23108
23612
|
}
|
|
@@ -23311,7 +23815,7 @@ ${scheduleItems}
|
|
|
23311
23815
|
"independent_segments+temp_file",
|
|
23312
23816
|
playlistPath
|
|
23313
23817
|
];
|
|
23314
|
-
ff =
|
|
23818
|
+
ff = spawn3("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
23315
23819
|
if (!ff.stdin || !ff.stderr) {
|
|
23316
23820
|
throw new Error("ffmpeg stdio streams not available");
|
|
23317
23821
|
}
|
|
@@ -24704,7 +25208,7 @@ function selectViableUdpMethods(hasUid, methods = ALL_UDP_DISCOVERY_METHODS) {
|
|
|
24704
25208
|
return methods.filter((m) => m === "local-direct");
|
|
24705
25209
|
}
|
|
24706
25210
|
function normalizeUid(uid) {
|
|
24707
|
-
const v = uid?.trim();
|
|
25211
|
+
const v = uid?.trim().toUpperCase();
|
|
24708
25212
|
return v ? v : void 0;
|
|
24709
25213
|
}
|
|
24710
25214
|
function maskUid(uid) {
|
|
@@ -24783,13 +25287,13 @@ async function pingHost(host, timeoutMs = 3e3) {
|
|
|
24783
25287
|
}
|
|
24784
25288
|
return ["-c", "1", "-W", String(Math.max(1, Math.floor(timeoutMs / 1e3))), host];
|
|
24785
25289
|
};
|
|
24786
|
-
const { spawn:
|
|
25290
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
24787
25291
|
for (const bin of pingCandidates) {
|
|
24788
25292
|
const ranOk = await new Promise((resolve) => {
|
|
24789
25293
|
let settled = false;
|
|
24790
25294
|
let child;
|
|
24791
25295
|
try {
|
|
24792
|
-
child =
|
|
25296
|
+
child = spawn4(bin, pingArgs(bin), { stdio: "ignore" });
|
|
24793
25297
|
} catch {
|
|
24794
25298
|
resolve("spawn-failed");
|
|
24795
25299
|
return;
|
|
@@ -25443,6 +25947,10 @@ export {
|
|
|
25443
25947
|
Intercom,
|
|
25444
25948
|
BaichuanEventEmitter,
|
|
25445
25949
|
createNativeStream,
|
|
25950
|
+
ALWAYS_ON_DEFAULTS,
|
|
25951
|
+
PlaceholderRenderer,
|
|
25952
|
+
ContinuousVideoStream,
|
|
25953
|
+
AlwaysOnController,
|
|
25446
25954
|
BaichuanRtspServer,
|
|
25447
25955
|
MpegTsMuxer,
|
|
25448
25956
|
flattenAbilitiesForChannel,
|
|
@@ -25496,4 +26004,4 @@ export {
|
|
|
25496
26004
|
tcpReachabilityProbe,
|
|
25497
26005
|
autoDetectDeviceType
|
|
25498
26006
|
};
|
|
25499
|
-
//# sourceMappingURL=chunk-
|
|
26007
|
+
//# sourceMappingURL=chunk-WQ2TQCYP.js.map
|