@apocaliss92/nodelink-js 0.6.4 → 0.6.6
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-UL34MR4L.js → chunk-JQ5NSEVD.js} +597 -42
- package/dist/chunk-JQ5NSEVD.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +612 -54
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +703 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -51
- package/dist/index.d.ts +179 -5
- package/dist/index.js +61 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UL34MR4L.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -7678,12 +7678,12 @@ var init_ReolinkCgiApi = __esm({
|
|
|
7678
7678
|
"getVideoclipThumbnailJpeg",
|
|
7679
7679
|
`Extracting thumbnail from VOD URL (FLV): ${vodUrl.substring(0, 100)}... (seek=${seekSeconds}s)`
|
|
7680
7680
|
);
|
|
7681
|
-
const { spawn:
|
|
7681
|
+
const { spawn: spawn14 } = await import("child_process");
|
|
7682
7682
|
return new Promise((resolve, reject) => {
|
|
7683
7683
|
const chunks = [];
|
|
7684
7684
|
let stderr = "";
|
|
7685
7685
|
let timedOut = false;
|
|
7686
|
-
const ffmpeg =
|
|
7686
|
+
const ffmpeg = spawn14(ffmpegPath, [
|
|
7687
7687
|
"-y",
|
|
7688
7688
|
"-analyzeduration",
|
|
7689
7689
|
"10000000",
|
|
@@ -8267,7 +8267,9 @@ var init_ReolinkCgiApi = __esm({
|
|
|
8267
8267
|
var index_exports = {};
|
|
8268
8268
|
__export(index_exports, {
|
|
8269
8269
|
ALL_UDP_DISCOVERY_METHODS: () => ALL_UDP_DISCOVERY_METHODS,
|
|
8270
|
+
ALWAYS_ON_DEFAULTS: () => ALWAYS_ON_DEFAULTS,
|
|
8270
8271
|
AesStreamDecryptor: () => AesStreamDecryptor,
|
|
8272
|
+
AlwaysOnController: () => AlwaysOnController,
|
|
8271
8273
|
AutodiscoveryClient: () => AutodiscoveryClient,
|
|
8272
8274
|
BC_AES_IV: () => BC_AES_IV,
|
|
8273
8275
|
BC_CLASS_FILE_DOWNLOAD: () => BC_CLASS_FILE_DOWNLOAD,
|
|
@@ -8424,6 +8426,7 @@ __export(index_exports, {
|
|
|
8424
8426
|
BcUdpStream: () => BcUdpStream,
|
|
8425
8427
|
CompositeRtspServer: () => CompositeRtspServer,
|
|
8426
8428
|
CompositeStream: () => CompositeStream,
|
|
8429
|
+
ContinuousVideoStream: () => ContinuousVideoStream,
|
|
8427
8430
|
DEFAULT_SHELTER_CANVAS: () => DEFAULT_SHELTER_CANVAS,
|
|
8428
8431
|
DUAL_LENS_DUAL_MOTION_MODELS: () => DUAL_LENS_DUAL_MOTION_MODELS,
|
|
8429
8432
|
DUAL_LENS_MODELS: () => DUAL_LENS_MODELS,
|
|
@@ -8437,6 +8440,7 @@ __export(index_exports, {
|
|
|
8437
8440
|
MpegTsMuxer: () => MpegTsMuxer,
|
|
8438
8441
|
NVR_HUB_EXACT_TYPES: () => NVR_HUB_EXACT_TYPES,
|
|
8439
8442
|
NVR_HUB_MODEL_PATTERNS: () => NVR_HUB_MODEL_PATTERNS,
|
|
8443
|
+
PlaceholderRenderer: () => PlaceholderRenderer,
|
|
8440
8444
|
ReolinkBaichuanApi: () => ReolinkBaichuanApi,
|
|
8441
8445
|
ReolinkCgiApi: () => ReolinkCgiApi,
|
|
8442
8446
|
ReolinkHttpClient: () => ReolinkHttpClient,
|
|
@@ -14068,14 +14072,14 @@ init_ReolinkHttpClient();
|
|
|
14068
14072
|
init_ReolinkCgiApi();
|
|
14069
14073
|
|
|
14070
14074
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
14071
|
-
var
|
|
14075
|
+
var import_node_child_process4 = require("child_process");
|
|
14072
14076
|
var import_promises2 = require("fs/promises");
|
|
14073
14077
|
var import_node_path2 = require("path");
|
|
14074
14078
|
var import_node_stream = require("stream");
|
|
14075
14079
|
|
|
14076
14080
|
// src/baichuan/stream/BaichuanRtspServer.ts
|
|
14077
|
-
var
|
|
14078
|
-
var
|
|
14081
|
+
var import_node_events5 = require("events");
|
|
14082
|
+
var import_node_child_process3 = require("child_process");
|
|
14079
14083
|
var net2 = __toESM(require("net"), 1);
|
|
14080
14084
|
var dgram2 = __toESM(require("dgram"), 1);
|
|
14081
14085
|
var crypto = __toESM(require("crypto"), 1);
|
|
@@ -14436,6 +14440,358 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
14436
14440
|
}
|
|
14437
14441
|
}
|
|
14438
14442
|
|
|
14443
|
+
// src/baichuan/stream/BaichuanRtspServer.ts
|
|
14444
|
+
init_BaichuanVideoStream();
|
|
14445
|
+
|
|
14446
|
+
// src/baichuan/stream/ContinuousVideoStream.ts
|
|
14447
|
+
var import_node_events4 = require("events");
|
|
14448
|
+
|
|
14449
|
+
// src/baichuan/stream/PlaceholderRenderer.ts
|
|
14450
|
+
var import_node_child_process2 = require("child_process");
|
|
14451
|
+
var import_jimp = require("jimp");
|
|
14452
|
+
var import_fonts = require("jimp/fonts");
|
|
14453
|
+
|
|
14454
|
+
// src/baichuan/stream/alwaysOnTypes.ts
|
|
14455
|
+
var ALWAYS_ON_DEFAULTS = {
|
|
14456
|
+
triggers: ["motion", "doorbell"],
|
|
14457
|
+
windowMs: 15e3,
|
|
14458
|
+
idleFps: 1,
|
|
14459
|
+
primeOnStart: true,
|
|
14460
|
+
placeholder: { enabled: true, text: "Sleeping", opacity: 0.5 }
|
|
14461
|
+
};
|
|
14462
|
+
|
|
14463
|
+
// src/baichuan/stream/PlaceholderRenderer.ts
|
|
14464
|
+
function ffmpegCodec(videoType) {
|
|
14465
|
+
if (videoType === "H265") {
|
|
14466
|
+
return {
|
|
14467
|
+
inputFormat: "hevc",
|
|
14468
|
+
encoder: "libx265",
|
|
14469
|
+
outputFormat: "hevc"
|
|
14470
|
+
};
|
|
14471
|
+
}
|
|
14472
|
+
return {
|
|
14473
|
+
inputFormat: "h264",
|
|
14474
|
+
encoder: "libx264",
|
|
14475
|
+
outputFormat: "h264"
|
|
14476
|
+
};
|
|
14477
|
+
}
|
|
14478
|
+
function runFfmpeg(args, input) {
|
|
14479
|
+
return new Promise((resolve, reject) => {
|
|
14480
|
+
const proc = (0, import_node_child_process2.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
14481
|
+
const stdoutChunks = [];
|
|
14482
|
+
const stderrChunks = [];
|
|
14483
|
+
proc.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
14484
|
+
proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
14485
|
+
proc.on("error", (error) => reject(error));
|
|
14486
|
+
proc.on("close", (code) => {
|
|
14487
|
+
if (code === 0) {
|
|
14488
|
+
resolve(Buffer.concat(stdoutChunks));
|
|
14489
|
+
return;
|
|
14490
|
+
}
|
|
14491
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
14492
|
+
reject(new Error(`ffmpeg exited with code ${code}: ${stderr}`));
|
|
14493
|
+
});
|
|
14494
|
+
const stdin = proc.stdin;
|
|
14495
|
+
if (!stdin) {
|
|
14496
|
+
reject(new Error("ffmpeg stdin not available"));
|
|
14497
|
+
return;
|
|
14498
|
+
}
|
|
14499
|
+
stdin.on("error", (error) => reject(error));
|
|
14500
|
+
stdin.end(input);
|
|
14501
|
+
});
|
|
14502
|
+
}
|
|
14503
|
+
var PlaceholderRenderer = class {
|
|
14504
|
+
opts;
|
|
14505
|
+
logger;
|
|
14506
|
+
constructor(args) {
|
|
14507
|
+
this.opts = { ...ALWAYS_ON_DEFAULTS.placeholder, ...args.placeholder ?? {} };
|
|
14508
|
+
this.logger = args.logger;
|
|
14509
|
+
}
|
|
14510
|
+
/** Returns the access unit bytes to emit as placeholder, or null if none available. */
|
|
14511
|
+
async render(keyframe) {
|
|
14512
|
+
if (!keyframe) return null;
|
|
14513
|
+
if (!this.opts.enabled) return keyframe.data;
|
|
14514
|
+
try {
|
|
14515
|
+
const jpeg = await this.decodeToJpeg(keyframe);
|
|
14516
|
+
const decorated = await this.decorate(jpeg);
|
|
14517
|
+
const idr = await this.encodeIdr(decorated, keyframe.videoType);
|
|
14518
|
+
if (!idr || idr.length === 0) {
|
|
14519
|
+
throw new Error("ffmpeg produced empty IDR output");
|
|
14520
|
+
}
|
|
14521
|
+
return idr;
|
|
14522
|
+
} catch (error) {
|
|
14523
|
+
this.logger?.warn?.(
|
|
14524
|
+
"PlaceholderRenderer: decoration failed, falling back to raw keyframe",
|
|
14525
|
+
error instanceof Error ? error.message : error
|
|
14526
|
+
);
|
|
14527
|
+
return keyframe.data;
|
|
14528
|
+
}
|
|
14529
|
+
}
|
|
14530
|
+
/** Decodes the cached keyframe access unit into a single JPEG still via ffmpeg. */
|
|
14531
|
+
async decodeToJpeg(keyframe) {
|
|
14532
|
+
const { inputFormat } = ffmpegCodec(keyframe.videoType);
|
|
14533
|
+
return runFfmpeg(
|
|
14534
|
+
[
|
|
14535
|
+
"-hide_banner",
|
|
14536
|
+
"-loglevel",
|
|
14537
|
+
"error",
|
|
14538
|
+
"-f",
|
|
14539
|
+
inputFormat,
|
|
14540
|
+
"-i",
|
|
14541
|
+
"pipe:0",
|
|
14542
|
+
"-frames:v",
|
|
14543
|
+
"1",
|
|
14544
|
+
"-f",
|
|
14545
|
+
"mjpeg",
|
|
14546
|
+
"pipe:1"
|
|
14547
|
+
],
|
|
14548
|
+
keyframe.data
|
|
14549
|
+
);
|
|
14550
|
+
}
|
|
14551
|
+
/** Dims the still and prints the overlay text using jimp, returning a JPEG buffer. */
|
|
14552
|
+
async decorate(jpeg) {
|
|
14553
|
+
const image = await import_jimp.Jimp.read(jpeg);
|
|
14554
|
+
const op = Math.max(0, Math.min(1, this.opts.opacity));
|
|
14555
|
+
if (op < 1) {
|
|
14556
|
+
const data = image.bitmap.data;
|
|
14557
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
14558
|
+
data[i] = data[i] * op;
|
|
14559
|
+
data[i + 1] = data[i + 1] * op;
|
|
14560
|
+
data[i + 2] = data[i + 2] * op;
|
|
14561
|
+
}
|
|
14562
|
+
}
|
|
14563
|
+
const fontDef = image.width >= 1280 ? import_fonts.SANS_128_WHITE : image.width >= 640 ? import_fonts.SANS_64_WHITE : import_fonts.SANS_32_WHITE;
|
|
14564
|
+
const font = await (0, import_jimp.loadFont)(fontDef);
|
|
14565
|
+
const text = this.opts.text;
|
|
14566
|
+
const textWidth = (0, import_jimp.measureText)(font, text);
|
|
14567
|
+
const textHeight = (0, import_jimp.measureTextHeight)(font, text, image.width);
|
|
14568
|
+
const x = Math.max(0, Math.round((image.width - textWidth) / 2));
|
|
14569
|
+
const y = Math.max(0, Math.round((image.height - textHeight) / 2));
|
|
14570
|
+
image.print({ font, x, y, text });
|
|
14571
|
+
return image.getBuffer(import_jimp.JimpMime.jpeg);
|
|
14572
|
+
}
|
|
14573
|
+
/** Encodes the decorated JPEG into a single IDR access unit in the target codec. */
|
|
14574
|
+
async encodeIdr(jpeg, videoType) {
|
|
14575
|
+
const { encoder, outputFormat } = ffmpegCodec(videoType);
|
|
14576
|
+
return runFfmpeg(
|
|
14577
|
+
[
|
|
14578
|
+
"-hide_banner",
|
|
14579
|
+
"-loglevel",
|
|
14580
|
+
"error",
|
|
14581
|
+
"-f",
|
|
14582
|
+
"image2pipe",
|
|
14583
|
+
"-i",
|
|
14584
|
+
"pipe:0",
|
|
14585
|
+
"-frames:v",
|
|
14586
|
+
"1",
|
|
14587
|
+
"-c:v",
|
|
14588
|
+
encoder,
|
|
14589
|
+
"-pix_fmt",
|
|
14590
|
+
"yuv420p",
|
|
14591
|
+
"-f",
|
|
14592
|
+
outputFormat,
|
|
14593
|
+
"pipe:1"
|
|
14594
|
+
],
|
|
14595
|
+
jpeg
|
|
14596
|
+
);
|
|
14597
|
+
}
|
|
14598
|
+
};
|
|
14599
|
+
|
|
14600
|
+
// src/baichuan/stream/ContinuousVideoStream.ts
|
|
14601
|
+
var ContinuousVideoStream = class extends import_node_events4.EventEmitter {
|
|
14602
|
+
constructor(opts) {
|
|
14603
|
+
super();
|
|
14604
|
+
this.opts = opts;
|
|
14605
|
+
this.idleFps = Math.max(0.1, opts.idleFps ?? ALWAYS_ON_DEFAULTS.idleFps);
|
|
14606
|
+
this.logger = opts.logger;
|
|
14607
|
+
const rendererArgs = {};
|
|
14608
|
+
if (opts.placeholder !== void 0) rendererArgs.placeholder = opts.placeholder;
|
|
14609
|
+
if (opts.logger !== void 0) rendererArgs.logger = opts.logger;
|
|
14610
|
+
this.renderer = opts.renderer ?? new PlaceholderRenderer(rendererArgs);
|
|
14611
|
+
}
|
|
14612
|
+
live = null;
|
|
14613
|
+
lastKeyframe = null;
|
|
14614
|
+
lastMicroseconds = 0;
|
|
14615
|
+
idleFps;
|
|
14616
|
+
renderer;
|
|
14617
|
+
logger;
|
|
14618
|
+
stopped = false;
|
|
14619
|
+
starting = false;
|
|
14620
|
+
idleTimer = null;
|
|
14621
|
+
idlePlaceholder = null;
|
|
14622
|
+
hasCachedKeyframe() {
|
|
14623
|
+
return this.lastKeyframe !== null;
|
|
14624
|
+
}
|
|
14625
|
+
async goLive() {
|
|
14626
|
+
if (this.stopped || this.live || this.starting) return;
|
|
14627
|
+
this.starting = true;
|
|
14628
|
+
try {
|
|
14629
|
+
this.stopIdleLoop();
|
|
14630
|
+
const stream = await this.opts.createLiveStream();
|
|
14631
|
+
this.live = stream;
|
|
14632
|
+
stream.on("videoAccessUnit", this.onLiveAccessUnit);
|
|
14633
|
+
stream.on("additionalHeader", this.onAdditionalHeader);
|
|
14634
|
+
stream.on("audioFrame", this.onAudioFrame);
|
|
14635
|
+
stream.on("error", this.onLiveError);
|
|
14636
|
+
await stream.start().catch((e) => this.emit("error", e));
|
|
14637
|
+
} finally {
|
|
14638
|
+
this.starting = false;
|
|
14639
|
+
}
|
|
14640
|
+
}
|
|
14641
|
+
async goIdle() {
|
|
14642
|
+
if (!this.live) return;
|
|
14643
|
+
const s = this.live;
|
|
14644
|
+
this.live = null;
|
|
14645
|
+
s.off("videoAccessUnit", this.onLiveAccessUnit);
|
|
14646
|
+
s.off("additionalHeader", this.onAdditionalHeader);
|
|
14647
|
+
s.off("audioFrame", this.onAudioFrame);
|
|
14648
|
+
s.off("error", this.onLiveError);
|
|
14649
|
+
await s.stop().catch(() => {
|
|
14650
|
+
});
|
|
14651
|
+
await this.startIdleLoop();
|
|
14652
|
+
}
|
|
14653
|
+
async stop() {
|
|
14654
|
+
this.stopped = true;
|
|
14655
|
+
await this.goIdle();
|
|
14656
|
+
this.stopIdleLoop();
|
|
14657
|
+
this.emit("close");
|
|
14658
|
+
}
|
|
14659
|
+
async startIdleLoop() {
|
|
14660
|
+
if (this.stopped) return;
|
|
14661
|
+
this.idlePlaceholder = await this.renderer.render(this.lastKeyframe);
|
|
14662
|
+
if (!this.idlePlaceholder || !this.lastKeyframe) {
|
|
14663
|
+
this.logger?.debug?.("[ContinuousVideoStream] no keyframe yet; idle loop deferred");
|
|
14664
|
+
return;
|
|
14665
|
+
}
|
|
14666
|
+
const stepUs = Math.round(1e6 / this.idleFps);
|
|
14667
|
+
const videoType = this.lastKeyframe.videoType;
|
|
14668
|
+
this.idleTimer = setInterval(() => {
|
|
14669
|
+
if (!this.idlePlaceholder) return;
|
|
14670
|
+
this.lastMicroseconds += stepUs;
|
|
14671
|
+
this.emit("videoAccessUnit", {
|
|
14672
|
+
data: this.idlePlaceholder,
|
|
14673
|
+
isKeyframe: true,
|
|
14674
|
+
videoType,
|
|
14675
|
+
microseconds: this.lastMicroseconds
|
|
14676
|
+
});
|
|
14677
|
+
}, Math.round(1e3 / this.idleFps));
|
|
14678
|
+
}
|
|
14679
|
+
stopIdleLoop() {
|
|
14680
|
+
if (this.idleTimer) {
|
|
14681
|
+
clearInterval(this.idleTimer);
|
|
14682
|
+
this.idleTimer = null;
|
|
14683
|
+
}
|
|
14684
|
+
this.idlePlaceholder = null;
|
|
14685
|
+
}
|
|
14686
|
+
onLiveAccessUnit = (au) => {
|
|
14687
|
+
if (au.isKeyframe) {
|
|
14688
|
+
this.lastKeyframe = { data: au.data, videoType: au.videoType };
|
|
14689
|
+
}
|
|
14690
|
+
this.lastMicroseconds = au.microseconds;
|
|
14691
|
+
this.emit("videoAccessUnit", au);
|
|
14692
|
+
};
|
|
14693
|
+
onAdditionalHeader = (h) => this.emit("additionalHeader", h);
|
|
14694
|
+
onAudioFrame = (a) => this.emit("audioFrame", a);
|
|
14695
|
+
onLiveError = (e) => this.emit("error", e);
|
|
14696
|
+
};
|
|
14697
|
+
|
|
14698
|
+
// src/baichuan/stream/AlwaysOnController.ts
|
|
14699
|
+
var AlwaysOnController = class {
|
|
14700
|
+
constructor(o) {
|
|
14701
|
+
this.o = o;
|
|
14702
|
+
this.triggers = new Set(o.options.triggers ?? ALWAYS_ON_DEFAULTS.triggers);
|
|
14703
|
+
this.windowMs = o.options.windowMs ?? ALWAYS_ON_DEFAULTS.windowMs;
|
|
14704
|
+
this.primeOnStart = o.options.primeOnStart ?? ALWAYS_ON_DEFAULTS.primeOnStart;
|
|
14705
|
+
this.logger = o.logger;
|
|
14706
|
+
}
|
|
14707
|
+
triggers;
|
|
14708
|
+
windowMs;
|
|
14709
|
+
primeOnStart;
|
|
14710
|
+
logger;
|
|
14711
|
+
windowTimer = null;
|
|
14712
|
+
live = false;
|
|
14713
|
+
started = false;
|
|
14714
|
+
handler = (e) => void this.onEvent(e);
|
|
14715
|
+
get windowSeconds() {
|
|
14716
|
+
return Math.round(this.windowMs / 1e3);
|
|
14717
|
+
}
|
|
14718
|
+
async start() {
|
|
14719
|
+
if (this.started) return;
|
|
14720
|
+
this.started = true;
|
|
14721
|
+
await this.o.api.onSimpleEvent(this.handler);
|
|
14722
|
+
this.logger?.info?.(
|
|
14723
|
+
`[AlwaysOnController] started ch${this.o.channel} \u2014 triggers=[${[...this.triggers].join(", ")}], window=${this.windowSeconds}s, primeOnStart=${this.primeOnStart}`
|
|
14724
|
+
);
|
|
14725
|
+
if (this.primeOnStart) {
|
|
14726
|
+
await this.openWindow("prime");
|
|
14727
|
+
}
|
|
14728
|
+
}
|
|
14729
|
+
async stop() {
|
|
14730
|
+
if (!this.started) return;
|
|
14731
|
+
this.started = false;
|
|
14732
|
+
if (this.windowTimer) {
|
|
14733
|
+
clearTimeout(this.windowTimer);
|
|
14734
|
+
this.windowTimer = null;
|
|
14735
|
+
}
|
|
14736
|
+
await this.o.api.offSimpleEvent(this.handler).catch(() => {
|
|
14737
|
+
});
|
|
14738
|
+
if (this.live) {
|
|
14739
|
+
this.live = false;
|
|
14740
|
+
await this.o.goIdle().catch(() => {
|
|
14741
|
+
});
|
|
14742
|
+
}
|
|
14743
|
+
this.logger?.info?.(`[AlwaysOnController] stopped ch${this.o.channel}`);
|
|
14744
|
+
}
|
|
14745
|
+
async onEvent(e) {
|
|
14746
|
+
if (e.channel !== this.o.channel) return;
|
|
14747
|
+
if (!this.triggers.has(e.type)) {
|
|
14748
|
+
this.logger?.debug?.(
|
|
14749
|
+
`[AlwaysOnController] event '${e.type}' ch${e.channel} ignored (not a configured trigger)`
|
|
14750
|
+
);
|
|
14751
|
+
return;
|
|
14752
|
+
}
|
|
14753
|
+
await this.openWindow(e.type);
|
|
14754
|
+
}
|
|
14755
|
+
async openWindow(reason) {
|
|
14756
|
+
if (this.windowTimer) clearTimeout(this.windowTimer);
|
|
14757
|
+
if (!this.live) {
|
|
14758
|
+
this.live = true;
|
|
14759
|
+
try {
|
|
14760
|
+
await this.o.api.wakeUp(this.o.channel).catch(() => {
|
|
14761
|
+
});
|
|
14762
|
+
await this.o.goLive();
|
|
14763
|
+
this.logger?.info?.(
|
|
14764
|
+
`[AlwaysOnController] live window OPENED (trigger=${reason}) \u2014 streaming real frames; will sleep in ${this.windowSeconds}s without new events`
|
|
14765
|
+
);
|
|
14766
|
+
} catch (err) {
|
|
14767
|
+
this.live = false;
|
|
14768
|
+
this.logger?.warn?.(
|
|
14769
|
+
`[AlwaysOnController] goLive failed: ${err?.message}`
|
|
14770
|
+
);
|
|
14771
|
+
return;
|
|
14772
|
+
}
|
|
14773
|
+
} else {
|
|
14774
|
+
this.logger?.info?.(
|
|
14775
|
+
`[AlwaysOnController] live window EXTENDED (trigger=${reason}) \u2014 sleep timer reset to ${this.windowSeconds}s`
|
|
14776
|
+
);
|
|
14777
|
+
}
|
|
14778
|
+
this.windowTimer = setTimeout(() => void this.closeWindow(), this.windowMs);
|
|
14779
|
+
}
|
|
14780
|
+
async closeWindow() {
|
|
14781
|
+
this.windowTimer = null;
|
|
14782
|
+
if (!this.live) return;
|
|
14783
|
+
this.live = false;
|
|
14784
|
+
this.logger?.info?.(
|
|
14785
|
+
`[AlwaysOnController] live window CLOSED \u2014 going idle (placeholder); camera can sleep`
|
|
14786
|
+
);
|
|
14787
|
+
await this.o.goIdle().catch(
|
|
14788
|
+
(err) => this.logger?.warn?.(
|
|
14789
|
+
`[AlwaysOnController] goIdle failed: ${err?.message}`
|
|
14790
|
+
)
|
|
14791
|
+
);
|
|
14792
|
+
}
|
|
14793
|
+
};
|
|
14794
|
+
|
|
14439
14795
|
// src/baichuan/stream/rtspFlow.ts
|
|
14440
14796
|
init_H264Converter();
|
|
14441
14797
|
init_H265Converter();
|
|
@@ -14662,7 +15018,7 @@ function envBool(value, defaultValue) {
|
|
|
14662
15018
|
if (v === "0" || v === "false" || v === "no" || v === "off") return false;
|
|
14663
15019
|
return defaultValue;
|
|
14664
15020
|
}
|
|
14665
|
-
var BaichuanRtspServer = class _BaichuanRtspServer extends
|
|
15021
|
+
var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events5.EventEmitter {
|
|
14666
15022
|
api;
|
|
14667
15023
|
channel;
|
|
14668
15024
|
profile;
|
|
@@ -14677,6 +15033,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14677
15033
|
deviceId;
|
|
14678
15034
|
dedicatedSessionRelease;
|
|
14679
15035
|
externalListener;
|
|
15036
|
+
// Always-on continuous stream (battery cameras). Populated only when
|
|
15037
|
+
// `options.alwaysOn?.enabled`; the default (non-alwaysOn) path leaves these
|
|
15038
|
+
// null/undefined and is byte-for-byte equivalent in behaviour.
|
|
15039
|
+
alwaysOnOptions;
|
|
15040
|
+
continuousStream = null;
|
|
15041
|
+
alwaysOnController = null;
|
|
14680
15042
|
// Authentication
|
|
14681
15043
|
authCredentials = [];
|
|
14682
15044
|
requireAuth;
|
|
@@ -14691,6 +15053,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14691
15053
|
// Set of client IDs (IP:port)
|
|
14692
15054
|
nativeStreamActive = false;
|
|
14693
15055
|
// Whether the native stream is currently active
|
|
15056
|
+
tearingDown = false;
|
|
15057
|
+
// True while stop() is running; suppresses onEnd-driven restarts
|
|
14694
15058
|
clientConnectionServer;
|
|
14695
15059
|
// TCP server to track connections
|
|
14696
15060
|
streamMetadata = null;
|
|
@@ -14878,6 +15242,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14878
15242
|
this.requireAuth = options.requireAuth ?? this.authCredentials.length > 0;
|
|
14879
15243
|
this.AUTH_REALM = options.authRealm ?? "BaichuanRtspServer";
|
|
14880
15244
|
this.lazyMetadata = options.lazyMetadata ?? false;
|
|
15245
|
+
this.alwaysOnOptions = options.alwaysOn;
|
|
14881
15246
|
const transport = this.api.client.getTransport();
|
|
14882
15247
|
this.flow = createRtspFlow(transport, "H264");
|
|
14883
15248
|
}
|
|
@@ -16001,7 +16366,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16001
16366
|
this.rtspDebugLog(
|
|
16002
16367
|
`Spawning ffmpeg for client ${clientId}: ffmpeg ${ffmpegArgs.join(" ")}`
|
|
16003
16368
|
);
|
|
16004
|
-
ffmpeg = (0,
|
|
16369
|
+
ffmpeg = (0, import_node_child_process3.spawn)("ffmpeg", ffmpegArgs, {
|
|
16005
16370
|
stdio
|
|
16006
16371
|
});
|
|
16007
16372
|
try {
|
|
@@ -16365,6 +16730,141 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16365
16730
|
}
|
|
16366
16731
|
});
|
|
16367
16732
|
}
|
|
16733
|
+
/**
|
|
16734
|
+
* Always-on source: bridge a {@link ContinuousVideoStream} into the existing
|
|
16735
|
+
* fanout. Yields the same frame shape that `createNativeStream` produces, so
|
|
16736
|
+
* the rest of the pipeline (prebuffer, param-set extraction, per-client
|
|
16737
|
+
* subscribe, ffmpeg/direct-RTP) is unchanged.
|
|
16738
|
+
*
|
|
16739
|
+
* The CVS itself is long-lived (created once, reused across native-stream
|
|
16740
|
+
* restarts) and is driven by the {@link AlwaysOnController}, which opens/closes
|
|
16741
|
+
* live windows from camera events. Each fanout source generator only forwards
|
|
16742
|
+
* CVS events to the fanout pump for as long as `signal` is not aborted.
|
|
16743
|
+
*/
|
|
16744
|
+
async *createContinuousSource(dedicatedClient, signal) {
|
|
16745
|
+
const cvs = this.ensureContinuousStream(dedicatedClient);
|
|
16746
|
+
const queue = [];
|
|
16747
|
+
const MAX_QUEUE = 200;
|
|
16748
|
+
let wake = null;
|
|
16749
|
+
let done = false;
|
|
16750
|
+
const push = (frame) => {
|
|
16751
|
+
queue.push(frame);
|
|
16752
|
+
if (queue.length > MAX_QUEUE) {
|
|
16753
|
+
queue.splice(0, queue.length - MAX_QUEUE);
|
|
16754
|
+
}
|
|
16755
|
+
if (wake) {
|
|
16756
|
+
const w = wake;
|
|
16757
|
+
wake = null;
|
|
16758
|
+
w();
|
|
16759
|
+
}
|
|
16760
|
+
};
|
|
16761
|
+
const onVideo = (au) => {
|
|
16762
|
+
push({
|
|
16763
|
+
audio: false,
|
|
16764
|
+
data: au.data,
|
|
16765
|
+
codec: null,
|
|
16766
|
+
sampleRate: null,
|
|
16767
|
+
microseconds: au.microseconds,
|
|
16768
|
+
videoType: au.videoType,
|
|
16769
|
+
isKeyframe: au.isKeyframe
|
|
16770
|
+
});
|
|
16771
|
+
};
|
|
16772
|
+
const onAudio = (frame) => {
|
|
16773
|
+
push({
|
|
16774
|
+
audio: true,
|
|
16775
|
+
data: frame,
|
|
16776
|
+
codec: "aac",
|
|
16777
|
+
sampleRate: 8e3,
|
|
16778
|
+
microseconds: null
|
|
16779
|
+
});
|
|
16780
|
+
};
|
|
16781
|
+
const finish = () => {
|
|
16782
|
+
done = true;
|
|
16783
|
+
if (wake) {
|
|
16784
|
+
const w = wake;
|
|
16785
|
+
wake = null;
|
|
16786
|
+
w();
|
|
16787
|
+
}
|
|
16788
|
+
};
|
|
16789
|
+
const onAbort = () => finish();
|
|
16790
|
+
cvs.on("videoAccessUnit", onVideo);
|
|
16791
|
+
cvs.on("audioFrame", onAudio);
|
|
16792
|
+
cvs.on("close", finish);
|
|
16793
|
+
if (signal.aborted) {
|
|
16794
|
+
done = true;
|
|
16795
|
+
} else {
|
|
16796
|
+
signal.addEventListener("abort", onAbort);
|
|
16797
|
+
}
|
|
16798
|
+
try {
|
|
16799
|
+
while (!done && !signal.aborted) {
|
|
16800
|
+
if (queue.length > 0) {
|
|
16801
|
+
yield queue.shift();
|
|
16802
|
+
} else {
|
|
16803
|
+
await new Promise((resolve) => {
|
|
16804
|
+
wake = resolve;
|
|
16805
|
+
if (done || signal.aborted) {
|
|
16806
|
+
wake = null;
|
|
16807
|
+
resolve();
|
|
16808
|
+
}
|
|
16809
|
+
});
|
|
16810
|
+
}
|
|
16811
|
+
}
|
|
16812
|
+
while (queue.length > 0 && !signal.aborted) {
|
|
16813
|
+
yield queue.shift();
|
|
16814
|
+
}
|
|
16815
|
+
} finally {
|
|
16816
|
+
cvs.off("videoAccessUnit", onVideo);
|
|
16817
|
+
cvs.off("audioFrame", onAudio);
|
|
16818
|
+
cvs.off("close", finish);
|
|
16819
|
+
signal.removeEventListener("abort", onAbort);
|
|
16820
|
+
}
|
|
16821
|
+
}
|
|
16822
|
+
/**
|
|
16823
|
+
* Lazily build the long-lived {@link ContinuousVideoStream} +
|
|
16824
|
+
* {@link AlwaysOnController} for always-on mode. Both are created once and
|
|
16825
|
+
* reused for the lifetime of the server (across native-stream restarts).
|
|
16826
|
+
*/
|
|
16827
|
+
ensureContinuousStream(dedicatedClient) {
|
|
16828
|
+
if (this.continuousStream) return this.continuousStream;
|
|
16829
|
+
const createLiveStream = async () => {
|
|
16830
|
+
const client = dedicatedClient ?? this.api.client;
|
|
16831
|
+
return new BaichuanVideoStream({
|
|
16832
|
+
client,
|
|
16833
|
+
api: this.api,
|
|
16834
|
+
channel: this.channel,
|
|
16835
|
+
profile: this.profile,
|
|
16836
|
+
...this.variant !== "default" ? { variant: this.variant } : {},
|
|
16837
|
+
...this.logger ? { logger: this.logger } : {}
|
|
16838
|
+
});
|
|
16839
|
+
};
|
|
16840
|
+
const cvsOptions = {
|
|
16841
|
+
createLiveStream,
|
|
16842
|
+
...this.alwaysOnOptions?.idleFps !== void 0 ? { idleFps: this.alwaysOnOptions.idleFps } : {},
|
|
16843
|
+
...this.alwaysOnOptions?.placeholder !== void 0 ? { placeholder: this.alwaysOnOptions.placeholder } : {},
|
|
16844
|
+
...this.logger ? { logger: this.logger } : {}
|
|
16845
|
+
};
|
|
16846
|
+
const cvs = new ContinuousVideoStream(cvsOptions);
|
|
16847
|
+
cvs.on("error", (e) => {
|
|
16848
|
+
this.logger.warn(
|
|
16849
|
+
`[BaichuanRtspServer] ContinuousVideoStream error: ${e?.message ?? e}`
|
|
16850
|
+
);
|
|
16851
|
+
});
|
|
16852
|
+
this.continuousStream = cvs;
|
|
16853
|
+
this.alwaysOnController = new AlwaysOnController({
|
|
16854
|
+
api: this.api,
|
|
16855
|
+
channel: this.channel,
|
|
16856
|
+
options: this.alwaysOnOptions,
|
|
16857
|
+
goLive: () => cvs.goLive(),
|
|
16858
|
+
goIdle: () => cvs.goIdle(),
|
|
16859
|
+
...this.logger ? { logger: this.logger } : {}
|
|
16860
|
+
});
|
|
16861
|
+
void this.alwaysOnController.start().catch((e) => {
|
|
16862
|
+
this.logger.warn(
|
|
16863
|
+
`[BaichuanRtspServer] AlwaysOnController start failed: ${e?.message ?? e}`
|
|
16864
|
+
);
|
|
16865
|
+
});
|
|
16866
|
+
return cvs;
|
|
16867
|
+
}
|
|
16368
16868
|
/**
|
|
16369
16869
|
* Start native stream (mark as active).
|
|
16370
16870
|
* Each client will create its own generator, so we just track that the stream is active.
|
|
@@ -16426,7 +16926,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16426
16926
|
await this.flow.startKeepAlive(this.api);
|
|
16427
16927
|
this.nativeFanout = new NativeStreamFanout({
|
|
16428
16928
|
maxQueueItems: 200,
|
|
16429
|
-
createSource: (signal) => createNativeStream(this.api, this.channel, this.profile, {
|
|
16929
|
+
createSource: (signal) => this.alwaysOnOptions?.enabled ? this.createContinuousSource(dedicatedClient, signal) : createNativeStream(this.api, this.channel, this.profile, {
|
|
16430
16930
|
variant: this.variant,
|
|
16431
16931
|
...dedicatedClient ? { client: dedicatedClient } : {},
|
|
16432
16932
|
signal
|
|
@@ -16509,6 +17009,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16509
17009
|
} catch {
|
|
16510
17010
|
}
|
|
16511
17011
|
}
|
|
17012
|
+
if (this.tearingDown) return;
|
|
16512
17013
|
if (this.connectedClients.size > 0 && hadFrames) {
|
|
16513
17014
|
this.logger.info(
|
|
16514
17015
|
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
@@ -16522,7 +17023,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16522
17023
|
});
|
|
16523
17024
|
this.nativeFanout.start();
|
|
16524
17025
|
this.clearNoFrameDeadlineTimer();
|
|
16525
|
-
if (this.nativeStreamNoFrameDeadlineMs > 0) {
|
|
17026
|
+
if (this.nativeStreamNoFrameDeadlineMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
16526
17027
|
this.noFrameDeadlineTimer = setTimeout(() => {
|
|
16527
17028
|
this.noFrameDeadlineTimer = void 0;
|
|
16528
17029
|
if (!this.firstFrameReceived && this.nativeStreamActive) {
|
|
@@ -16535,7 +17036,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16535
17036
|
this.noFrameDeadlineTimer?.unref?.();
|
|
16536
17037
|
}
|
|
16537
17038
|
this.clearNoClientAutoStopTimer();
|
|
16538
|
-
if (this.nativeStreamPrimeIdleStopMs > 0) {
|
|
17039
|
+
if (this.nativeStreamPrimeIdleStopMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
16539
17040
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
16540
17041
|
if (this.connectedClients.size === 0) {
|
|
16541
17042
|
this.rtspDebugLog(
|
|
@@ -16622,7 +17123,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16622
17123
|
this.emit("clientDisconnected", clientId);
|
|
16623
17124
|
if (this.connectedClients.size === 0) {
|
|
16624
17125
|
this.clearNoClientAutoStopTimer();
|
|
16625
|
-
if (this.nativeStreamIdleStopMs > 0) {
|
|
17126
|
+
if (this.nativeStreamIdleStopMs > 0 && !this.alwaysOnOptions?.enabled) {
|
|
16626
17127
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
16627
17128
|
if (this.connectedClients.size === 0) {
|
|
16628
17129
|
void this.stopNativeStream();
|
|
@@ -16693,9 +17194,22 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
16693
17194
|
if (!this.active) {
|
|
16694
17195
|
return;
|
|
16695
17196
|
}
|
|
17197
|
+
this.tearingDown = true;
|
|
16696
17198
|
this.logger.info(
|
|
16697
17199
|
`[BaichuanRtspServer] Stopping RTSP server on ${this.listenHost}:${this.listenPort}...`
|
|
16698
17200
|
);
|
|
17201
|
+
if (this.alwaysOnController) {
|
|
17202
|
+
const controller = this.alwaysOnController;
|
|
17203
|
+
this.alwaysOnController = null;
|
|
17204
|
+
await controller.stop().catch(() => {
|
|
17205
|
+
});
|
|
17206
|
+
}
|
|
17207
|
+
if (this.continuousStream) {
|
|
17208
|
+
const cvs = this.continuousStream;
|
|
17209
|
+
this.continuousStream = null;
|
|
17210
|
+
await cvs.stop().catch(() => {
|
|
17211
|
+
});
|
|
17212
|
+
}
|
|
16699
17213
|
await this.stopNativeStream();
|
|
16700
17214
|
const clientIds = Array.from(this.connectedClients);
|
|
16701
17215
|
for (const clientId of clientIds) {
|
|
@@ -17186,6 +17700,30 @@ function buildSetNtpXml(current, patch) {
|
|
|
17186
17700
|
);
|
|
17187
17701
|
}
|
|
17188
17702
|
|
|
17703
|
+
// src/reolink/baichuan/utils/channelEnumeration.ts
|
|
17704
|
+
async function resolveBaichuanChannels(deps) {
|
|
17705
|
+
const fromPush = dedupeSorted(deps.pushChannels);
|
|
17706
|
+
if (fromPush.length > 0) return fromPush;
|
|
17707
|
+
const slots = dedupeSorted(deps.supportChnIds);
|
|
17708
|
+
const candidates = slots.length > 0 ? slots : [0];
|
|
17709
|
+
const probed = await Promise.all(
|
|
17710
|
+
candidates.map(
|
|
17711
|
+
async (channel) => await deps.probe(channel) ? channel : void 0
|
|
17712
|
+
)
|
|
17713
|
+
);
|
|
17714
|
+
return dedupeSorted(
|
|
17715
|
+
probed.filter((c) => c !== void 0)
|
|
17716
|
+
);
|
|
17717
|
+
}
|
|
17718
|
+
function dedupeSorted(values) {
|
|
17719
|
+
const set = /* @__PURE__ */ new Set();
|
|
17720
|
+
for (const v of values) {
|
|
17721
|
+
const n = Number(v);
|
|
17722
|
+
if (Number.isFinite(n) && n >= 0) set.add(n);
|
|
17723
|
+
}
|
|
17724
|
+
return [...set].sort((a, b) => a - b);
|
|
17725
|
+
}
|
|
17726
|
+
|
|
17189
17727
|
// src/reolink/baichuan/utils/dst.ts
|
|
17190
17728
|
init_xml();
|
|
17191
17729
|
var parseNumberSafe3 = (text) => {
|
|
@@ -17369,7 +17907,7 @@ function buildSetSystemGeneralXml(patch) {
|
|
|
17369
17907
|
}
|
|
17370
17908
|
|
|
17371
17909
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
17372
|
-
var
|
|
17910
|
+
var import_jimp2 = require("jimp");
|
|
17373
17911
|
init_ReolinkCgiApi();
|
|
17374
17912
|
init_ReolinkHttpClient();
|
|
17375
17913
|
|
|
@@ -20329,8 +20867,8 @@ var parseSirenStatusListPushXml = (xml) => {
|
|
|
20329
20867
|
};
|
|
20330
20868
|
|
|
20331
20869
|
// src/emailPush/bus.ts
|
|
20332
|
-
var
|
|
20333
|
-
var emitter = new
|
|
20870
|
+
var import_node_events6 = require("events");
|
|
20871
|
+
var emitter = new import_node_events6.EventEmitter();
|
|
20334
20872
|
var cameraResolver = () => void 0;
|
|
20335
20873
|
var lastEventByCamera = /* @__PURE__ */ new Map();
|
|
20336
20874
|
var MAX_GLOBAL_EVENTS = 300;
|
|
@@ -23595,13 +24133,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
23595
24133
|
* @param options.source - Data source for the channel list (default: `"cgi"`):
|
|
23596
24134
|
* - `"cgi"`: Uses HTTP `GetChannelstatus` — returns the channel list immediately,
|
|
23597
24135
|
* no dependency on async push messages. Recommended for first-call discovery.
|
|
23598
|
-
* - `"baichuan"`:
|
|
23599
|
-
*
|
|
23600
|
-
*
|
|
23601
|
-
*
|
|
24136
|
+
* - `"baichuan"`: HTTP-free discovery. Prefers the cmd_id 145 push cache when
|
|
24137
|
+
* populated; otherwise actively probes the channel slots advertised by Support
|
|
24138
|
+
* (`items[].chnID`) via `getInfo`. Use this for hubs with HTTP disabled.
|
|
24139
|
+
*
|
|
24140
|
+
* When the api was constructed with `nativeOnly`, the source is forced to
|
|
24141
|
+
* `"baichuan"` regardless of this option (no HTTP/CGI is ever attempted).
|
|
23602
24142
|
*/
|
|
23603
24143
|
async getNvrChannelsSummary(options) {
|
|
23604
|
-
const source = options?.source ?? "cgi";
|
|
24144
|
+
const source = this.nativeOnly ? "baichuan" : options?.source ?? "cgi";
|
|
24145
|
+
const support = await this.getSupportInfo().catch(() => {
|
|
24146
|
+
this.logger.error?.(
|
|
24147
|
+
"[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
|
|
24148
|
+
);
|
|
24149
|
+
return void 0;
|
|
24150
|
+
});
|
|
23605
24151
|
let channels;
|
|
23606
24152
|
const cgiStatusByChannel = /* @__PURE__ */ new Map();
|
|
23607
24153
|
if (options?.channels?.length) {
|
|
@@ -23631,15 +24177,31 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
23631
24177
|
channels = [];
|
|
23632
24178
|
}
|
|
23633
24179
|
} else {
|
|
23634
|
-
const
|
|
23635
|
-
|
|
24180
|
+
const pushChannels = Array.from(
|
|
24181
|
+
this.getChannelInfoFromPushCache().keys()
|
|
24182
|
+
).map((c) => Number(c)).filter((n) => Number.isFinite(n));
|
|
24183
|
+
const supportChnIds = (support?.items ?? []).map((i) => Number(i.chnID)).filter((n) => Number.isFinite(n));
|
|
24184
|
+
const probeTimeoutMs = options?.timeoutMs ?? 2500;
|
|
24185
|
+
channels = await resolveBaichuanChannels({
|
|
24186
|
+
pushChannels,
|
|
24187
|
+
supportChnIds,
|
|
24188
|
+
probe: async (channel) => {
|
|
24189
|
+
try {
|
|
24190
|
+
await this.getInfo(channel, {
|
|
24191
|
+
timeoutMs: probeTimeoutMs,
|
|
24192
|
+
tags: ["type", "name"]
|
|
24193
|
+
});
|
|
24194
|
+
return true;
|
|
24195
|
+
} catch {
|
|
24196
|
+
return false;
|
|
24197
|
+
}
|
|
24198
|
+
}
|
|
24199
|
+
});
|
|
24200
|
+
this.logger.debug?.(
|
|
24201
|
+
`[ReolinkBaichuanApi] getNvrChannelsSummary: baichuan resolved ${channels.length} channel(s): [${channels.join(", ")}]`
|
|
24202
|
+
);
|
|
23636
24203
|
}
|
|
23637
24204
|
channels = channels.sort((a, b) => a - b);
|
|
23638
|
-
const support = await this.getSupportInfo().catch(() => {
|
|
23639
|
-
this.logger.error?.(
|
|
23640
|
-
"[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
|
|
23641
|
-
);
|
|
23642
|
-
});
|
|
23643
24205
|
const truthyNumberLike = (v) => {
|
|
23644
24206
|
if (typeof v === "number") return v > 0;
|
|
23645
24207
|
if (typeof v === "string") {
|
|
@@ -24209,12 +24771,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
24209
24771
|
let wideImg;
|
|
24210
24772
|
let teleImg;
|
|
24211
24773
|
try {
|
|
24212
|
-
wideImg = await
|
|
24774
|
+
wideImg = await import_jimp2.Jimp.read(wide);
|
|
24213
24775
|
} catch {
|
|
24214
24776
|
return wide;
|
|
24215
24777
|
}
|
|
24216
24778
|
try {
|
|
24217
|
-
teleImg = await
|
|
24779
|
+
teleImg = await import_jimp2.Jimp.read(tele);
|
|
24218
24780
|
} catch {
|
|
24219
24781
|
return wide;
|
|
24220
24782
|
}
|
|
@@ -24248,7 +24810,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
24248
24810
|
});
|
|
24249
24811
|
teleImg.resize({ w: pipW, h: pipH });
|
|
24250
24812
|
wideImg.composite(teleImg, left, top);
|
|
24251
|
-
return await wideImg.getBuffer(
|
|
24813
|
+
return await wideImg.getBuffer(import_jimp2.JimpMime.jpeg, { quality: 80 });
|
|
24252
24814
|
}
|
|
24253
24815
|
const ch = channel !== void 0 ? this.normalizeChannel(channel) : 0;
|
|
24254
24816
|
const variant = options?.variant ?? "default";
|
|
@@ -25206,7 +25768,7 @@ ${xml}`);
|
|
|
25206
25768
|
const chunks = [];
|
|
25207
25769
|
let stderr = "";
|
|
25208
25770
|
let timedOut = false;
|
|
25209
|
-
const ff = (0,
|
|
25771
|
+
const ff = (0, import_node_child_process4.spawn)(params.ffmpegPath, [
|
|
25210
25772
|
"-hide_banner",
|
|
25211
25773
|
"-loglevel",
|
|
25212
25774
|
"error",
|
|
@@ -25291,7 +25853,7 @@ ${xml}`);
|
|
|
25291
25853
|
const chunks = [];
|
|
25292
25854
|
let stderr = "";
|
|
25293
25855
|
let timedOut = false;
|
|
25294
|
-
const ff = (0,
|
|
25856
|
+
const ff = (0, import_node_child_process4.spawn)(ffmpegPath, [
|
|
25295
25857
|
"-hide_banner",
|
|
25296
25858
|
"-loglevel",
|
|
25297
25859
|
"error",
|
|
@@ -25407,7 +25969,7 @@ ${xml}`);
|
|
|
25407
25969
|
ensureEnabled: true
|
|
25408
25970
|
});
|
|
25409
25971
|
await new Promise((resolve, reject) => {
|
|
25410
|
-
const ff = (0,
|
|
25972
|
+
const ff = (0, import_node_child_process4.spawn)(ffmpegPath, [
|
|
25411
25973
|
"-hide_banner",
|
|
25412
25974
|
"-loglevel",
|
|
25413
25975
|
"error",
|
|
@@ -25463,7 +26025,7 @@ ${stderr}`));
|
|
|
25463
26025
|
const atSeconds = Number.isFinite(params.atSeconds) && params.atSeconds >= 0 ? params.atSeconds : 0;
|
|
25464
26026
|
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(params.outputPath), { recursive: true });
|
|
25465
26027
|
await new Promise((resolve, reject) => {
|
|
25466
|
-
const ff = (0,
|
|
26028
|
+
const ff = (0, import_node_child_process4.spawn)(ffmpegPath, [
|
|
25467
26029
|
"-hide_banner",
|
|
25468
26030
|
"-loglevel",
|
|
25469
26031
|
"error",
|
|
@@ -26028,7 +26590,7 @@ ${stderr}`)
|
|
|
26028
26590
|
* Convert a raw video keyframe to JPEG using ffmpeg.
|
|
26029
26591
|
*/
|
|
26030
26592
|
async convertFrameToJpeg(params) {
|
|
26031
|
-
const { spawn:
|
|
26593
|
+
const { spawn: spawn14 } = await import("child_process");
|
|
26032
26594
|
const ffmpeg = params.ffmpegPath ?? "ffmpeg";
|
|
26033
26595
|
const inputFormat = params.videoCodec === "H265" ? "hevc" : "h264";
|
|
26034
26596
|
return new Promise((resolve, reject) => {
|
|
@@ -26050,7 +26612,7 @@ ${stderr}`)
|
|
|
26050
26612
|
"2",
|
|
26051
26613
|
"pipe:1"
|
|
26052
26614
|
];
|
|
26053
|
-
const proc =
|
|
26615
|
+
const proc = spawn14(ffmpeg, args, {
|
|
26054
26616
|
stdio: ["pipe", "pipe", "pipe"]
|
|
26055
26617
|
});
|
|
26056
26618
|
const chunks = [];
|
|
@@ -26193,7 +26755,7 @@ ${stderr}`)
|
|
|
26193
26755
|
* Internal helper to mux video+audio into MP4 using ffmpeg.
|
|
26194
26756
|
*/
|
|
26195
26757
|
async muxToMp4(params) {
|
|
26196
|
-
const { spawn:
|
|
26758
|
+
const { spawn: spawn14 } = await import("child_process");
|
|
26197
26759
|
const { randomUUID: randomUUID3 } = await import("crypto");
|
|
26198
26760
|
const fs7 = await import("fs/promises");
|
|
26199
26761
|
const os2 = await import("os");
|
|
@@ -26245,7 +26807,7 @@ ${stderr}`)
|
|
|
26245
26807
|
outputPath
|
|
26246
26808
|
);
|
|
26247
26809
|
await new Promise((resolve, reject) => {
|
|
26248
|
-
const p =
|
|
26810
|
+
const p = spawn14(ffmpeg, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
26249
26811
|
let stderr = "";
|
|
26250
26812
|
p.stderr.on("data", (d) => {
|
|
26251
26813
|
stderr += d.toString();
|
|
@@ -31232,7 +31794,7 @@ ${scheduleItems}
|
|
|
31232
31794
|
"mjpeg",
|
|
31233
31795
|
"pipe:1"
|
|
31234
31796
|
];
|
|
31235
|
-
const ff = (0,
|
|
31797
|
+
const ff = (0, import_node_child_process4.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
31236
31798
|
const chunks = [];
|
|
31237
31799
|
let stderr = "";
|
|
31238
31800
|
ff.stdout.on("data", (d) => chunks.push(Buffer.from(d)));
|
|
@@ -31356,7 +31918,7 @@ ${scheduleItems}
|
|
|
31356
31918
|
"pipe:1"
|
|
31357
31919
|
];
|
|
31358
31920
|
}
|
|
31359
|
-
ff = (0,
|
|
31921
|
+
ff = (0, import_node_child_process4.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
31360
31922
|
if (!ff.stdin || !ff.stdout || !ff.stderr) {
|
|
31361
31923
|
throw new Error("ffmpeg stdio streams not available");
|
|
31362
31924
|
}
|
|
@@ -31603,7 +32165,7 @@ ${scheduleItems}
|
|
|
31603
32165
|
"mp4",
|
|
31604
32166
|
"pipe:1"
|
|
31605
32167
|
];
|
|
31606
|
-
ff = (0,
|
|
32168
|
+
ff = (0, import_node_child_process4.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
31607
32169
|
if (!ff.stdin || !ff.stdout || !ff.stderr) {
|
|
31608
32170
|
throw new Error("ffmpeg stdio streams not available");
|
|
31609
32171
|
}
|
|
@@ -31812,7 +32374,7 @@ ${scheduleItems}
|
|
|
31812
32374
|
"independent_segments+temp_file",
|
|
31813
32375
|
playlistPath
|
|
31814
32376
|
];
|
|
31815
|
-
ff = (0,
|
|
32377
|
+
ff = (0, import_node_child_process4.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
31816
32378
|
if (!ff.stdin || !ff.stderr) {
|
|
31817
32379
|
throw new Error("ffmpeg stdio streams not available");
|
|
31818
32380
|
}
|
|
@@ -32784,14 +33346,14 @@ function buildHlsRedirectUrl(originalUrl) {
|
|
|
32784
33346
|
}
|
|
32785
33347
|
|
|
32786
33348
|
// src/reolink/discovery.ts
|
|
32787
|
-
var
|
|
33349
|
+
var import_node_child_process5 = require("child_process");
|
|
32788
33350
|
var import_node_crypto4 = require("crypto");
|
|
32789
33351
|
var import_node_dgram2 = __toESM(require("dgram"), 1);
|
|
32790
33352
|
var net3 = __toESM(require("net"), 1);
|
|
32791
33353
|
var import_node_os2 = require("os");
|
|
32792
33354
|
var import_node_util = require("util");
|
|
32793
33355
|
init_ReolinkCgiApi();
|
|
32794
|
-
var execFileAsync = (0, import_node_util.promisify)(
|
|
33356
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process5.execFile);
|
|
32795
33357
|
async function discoverViaUdpDirect(host, options) {
|
|
32796
33358
|
if (!options.enableUdpDiscovery) return [];
|
|
32797
33359
|
const logger = options.logger;
|
|
@@ -33833,7 +34395,7 @@ init_recordingFileName();
|
|
|
33833
34395
|
|
|
33834
34396
|
// src/reolink/baichuan/endpoints-server.ts
|
|
33835
34397
|
var import_node_http = __toESM(require("http"), 1);
|
|
33836
|
-
var
|
|
34398
|
+
var import_node_child_process6 = require("child_process");
|
|
33837
34399
|
function parseIntParam(v, def) {
|
|
33838
34400
|
if (v == null) return def;
|
|
33839
34401
|
const n = Number.parseInt(v, 10);
|
|
@@ -34072,7 +34634,7 @@ function createBaichuanEndpointsServer(opts) {
|
|
|
34072
34634
|
"Cache-Control": "no-cache",
|
|
34073
34635
|
Connection: "close"
|
|
34074
34636
|
});
|
|
34075
|
-
const ff2 = (0,
|
|
34637
|
+
const ff2 = (0, import_node_child_process6.spawn)("ffmpeg", [
|
|
34076
34638
|
"-hide_banner",
|
|
34077
34639
|
"-loglevel",
|
|
34078
34640
|
"error",
|
|
@@ -34105,7 +34667,7 @@ function createBaichuanEndpointsServer(opts) {
|
|
|
34105
34667
|
);
|
|
34106
34668
|
res.setHeader("Cache-Control", "no-cache");
|
|
34107
34669
|
res.setHeader("Connection", "close");
|
|
34108
|
-
const ff = (0,
|
|
34670
|
+
const ff = (0, import_node_child_process6.spawn)("ffmpeg", [
|
|
34109
34671
|
"-hide_banner",
|
|
34110
34672
|
"-loglevel",
|
|
34111
34673
|
"error",
|
|
@@ -34216,7 +34778,7 @@ init_urls();
|
|
|
34216
34778
|
|
|
34217
34779
|
// src/rtsp/server.ts
|
|
34218
34780
|
var import_node_http2 = __toESM(require("http"), 1);
|
|
34219
|
-
var
|
|
34781
|
+
var import_node_child_process7 = require("child_process");
|
|
34220
34782
|
init_urls();
|
|
34221
34783
|
function createRtspProxyServer(opts) {
|
|
34222
34784
|
return import_node_http2.default.createServer((req, res) => {
|
|
@@ -34257,7 +34819,7 @@ function createRtspProxyServer(opts) {
|
|
|
34257
34819
|
Connection: "close"
|
|
34258
34820
|
});
|
|
34259
34821
|
const rtspTransport = opts.rtspTransport ?? "tcp";
|
|
34260
|
-
const ff = (0,
|
|
34822
|
+
const ff = (0, import_node_child_process7.spawn)("ffmpeg", [
|
|
34261
34823
|
"-hide_banner",
|
|
34262
34824
|
"-loglevel",
|
|
34263
34825
|
"error",
|
|
@@ -35129,9 +35691,9 @@ var import_node_net2 = __toESM(require("net"), 1);
|
|
|
35129
35691
|
init_BaichuanVideoStream();
|
|
35130
35692
|
|
|
35131
35693
|
// src/multifocal/compositeStream.ts
|
|
35132
|
-
var
|
|
35694
|
+
var import_node_child_process8 = require("child_process");
|
|
35133
35695
|
var import_node_crypto6 = require("crypto");
|
|
35134
|
-
var
|
|
35696
|
+
var import_node_events7 = require("events");
|
|
35135
35697
|
function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pipHeight, margin) {
|
|
35136
35698
|
const pipW = Math.floor(pipWidth);
|
|
35137
35699
|
const pipH = Math.floor(pipHeight);
|
|
@@ -35159,7 +35721,7 @@ function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pip
|
|
|
35159
35721
|
return { x: m, y: m };
|
|
35160
35722
|
}
|
|
35161
35723
|
}
|
|
35162
|
-
var CompositeStream = class extends
|
|
35724
|
+
var CompositeStream = class extends import_node_events7.EventEmitter {
|
|
35163
35725
|
options;
|
|
35164
35726
|
widerStream = null;
|
|
35165
35727
|
teleStream = null;
|
|
@@ -35484,7 +36046,7 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35484
36046
|
this.logger.log?.(
|
|
35485
36047
|
`[CompositeStream] Starting ffmpeg (rtsp inputs): bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35486
36048
|
);
|
|
35487
|
-
this.ffmpegProcess = (0,
|
|
36049
|
+
this.ffmpegProcess = (0, import_node_child_process8.spawn)(ffmpegBin, ffmpegArgs, {
|
|
35488
36050
|
stdio: ["ignore", "pipe", "pipe"]
|
|
35489
36051
|
});
|
|
35490
36052
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -35614,7 +36176,7 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35614
36176
|
this.logger.log?.(
|
|
35615
36177
|
`[CompositeStream] Starting ffmpeg: bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35616
36178
|
);
|
|
35617
|
-
this.ffmpegProcess = (0,
|
|
36179
|
+
this.ffmpegProcess = (0, import_node_child_process8.spawn)(ffmpegBin, ffmpegArgs, {
|
|
35618
36180
|
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
35619
36181
|
});
|
|
35620
36182
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -36192,7 +36754,8 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36192
36754
|
apisToClose.add(resolvedCompositeApis.widerApi);
|
|
36193
36755
|
if (resolvedCompositeApis?.teleApi)
|
|
36194
36756
|
apisToClose.add(resolvedCompositeApis.teleApi);
|
|
36195
|
-
const
|
|
36757
|
+
const alwaysOnEnabled = Boolean(options.alwaysOn?.enabled) && !isComposite;
|
|
36758
|
+
const uptimeRestartMs = alwaysOnEnabled ? 0 : uptimeRestartMsOpt ?? (isComposite ? 6e4 : 1e4);
|
|
36196
36759
|
const variantSuffix = variant && variant !== "default" ? ` variant=${variant}` : "";
|
|
36197
36760
|
const logPrefix = isComposite ? `[native-rfc4571 composite profile=${profile}${variantSuffix}${requestedId ? ` id=${requestedId}` : ""}]` : `[native-rfc4571 ch=${channel} profile=${profile}${variantSuffix}]`;
|
|
36198
36761
|
const log = (message) => {
|
|
@@ -36216,6 +36779,7 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36216
36779
|
);
|
|
36217
36780
|
let videoStream;
|
|
36218
36781
|
let isCompositeStream = false;
|
|
36782
|
+
let alwaysOnController;
|
|
36219
36783
|
if (isComposite) {
|
|
36220
36784
|
const widerChannel = compositeOptions?.widerChannel ?? 0;
|
|
36221
36785
|
const teleChannel = compositeOptions?.teleChannel ?? 1;
|
|
@@ -36358,7 +36922,7 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36358
36922
|
} else {
|
|
36359
36923
|
streamClient = baseApi.client;
|
|
36360
36924
|
}
|
|
36361
|
-
|
|
36925
|
+
const createLiveStream = async () => new BaichuanVideoStream({
|
|
36362
36926
|
client: streamClient,
|
|
36363
36927
|
api: baseApi,
|
|
36364
36928
|
channel: ch,
|
|
@@ -36366,10 +36930,39 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36366
36930
|
variant,
|
|
36367
36931
|
logger
|
|
36368
36932
|
});
|
|
36369
|
-
|
|
36370
|
-
|
|
36371
|
-
|
|
36372
|
-
|
|
36933
|
+
if (options.alwaysOn?.enabled) {
|
|
36934
|
+
const cvsOpts = {
|
|
36935
|
+
// ContinuousVideoStream owns the lifecycle: it calls createLiveStream
|
|
36936
|
+
// (which returns a started stream) and re-starts it internally on goLive.
|
|
36937
|
+
createLiveStream,
|
|
36938
|
+
logger
|
|
36939
|
+
};
|
|
36940
|
+
if (options.alwaysOn.idleFps !== void 0)
|
|
36941
|
+
cvsOpts.idleFps = options.alwaysOn.idleFps;
|
|
36942
|
+
if (options.alwaysOn.placeholder !== void 0)
|
|
36943
|
+
cvsOpts.placeholder = options.alwaysOn.placeholder;
|
|
36944
|
+
const cvs = new ContinuousVideoStream(cvsOpts);
|
|
36945
|
+
alwaysOnController = new AlwaysOnController({
|
|
36946
|
+
api: baseApi,
|
|
36947
|
+
channel: ch,
|
|
36948
|
+
options: options.alwaysOn,
|
|
36949
|
+
goLive: () => cvs.goLive(),
|
|
36950
|
+
goIdle: () => cvs.goIdle(),
|
|
36951
|
+
logger
|
|
36952
|
+
});
|
|
36953
|
+
await alwaysOnController.start();
|
|
36954
|
+
videoStream = cvs;
|
|
36955
|
+
log(
|
|
36956
|
+
`always-on stream started (ch=${ch} profile=${profile}${deviceId ? ` dedicated=${deviceId}` : ""})`
|
|
36957
|
+
);
|
|
36958
|
+
} else {
|
|
36959
|
+
const live = await createLiveStream();
|
|
36960
|
+
await live.start();
|
|
36961
|
+
videoStream = live;
|
|
36962
|
+
log(
|
|
36963
|
+
`stream started (ch=${ch} profile=${profile}${deviceId ? ` dedicated=${deviceId}` : ""})`
|
|
36964
|
+
);
|
|
36965
|
+
}
|
|
36373
36966
|
}
|
|
36374
36967
|
const waitForKeyframe = async () => {
|
|
36375
36968
|
if (isCompositeStream) {
|
|
@@ -36497,6 +37090,12 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36497
37090
|
try {
|
|
36498
37091
|
keyframe = await waitForKeyframe();
|
|
36499
37092
|
} catch (e) {
|
|
37093
|
+
if (alwaysOnController) {
|
|
37094
|
+
try {
|
|
37095
|
+
await alwaysOnController.stop();
|
|
37096
|
+
} catch {
|
|
37097
|
+
}
|
|
37098
|
+
}
|
|
36500
37099
|
try {
|
|
36501
37100
|
await videoStream.stop();
|
|
36502
37101
|
} catch {
|
|
@@ -36745,12 +37344,13 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36745
37344
|
} catch {
|
|
36746
37345
|
}
|
|
36747
37346
|
muxer = makeMuxer();
|
|
37347
|
+
const restartable = videoStream;
|
|
36748
37348
|
try {
|
|
36749
|
-
await
|
|
37349
|
+
await restartable.stop();
|
|
36750
37350
|
} catch {
|
|
36751
37351
|
}
|
|
36752
37352
|
try {
|
|
36753
|
-
await
|
|
37353
|
+
await restartable.start();
|
|
36754
37354
|
} catch (e) {
|
|
36755
37355
|
restarting = false;
|
|
36756
37356
|
close(e).catch(() => {
|
|
@@ -36771,6 +37371,12 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36771
37371
|
cancelIdleTeardown();
|
|
36772
37372
|
const reasonStr = reason?.message || reason?.toString?.() || reason || "requested";
|
|
36773
37373
|
muxer.close();
|
|
37374
|
+
if (alwaysOnController) {
|
|
37375
|
+
try {
|
|
37376
|
+
await alwaysOnController.stop();
|
|
37377
|
+
} catch {
|
|
37378
|
+
}
|
|
37379
|
+
}
|
|
36774
37380
|
try {
|
|
36775
37381
|
await videoStream.stop();
|
|
36776
37382
|
} catch {
|
|
@@ -37370,7 +37976,7 @@ async function createRfc4571TcpServerForReplay(options) {
|
|
|
37370
37976
|
|
|
37371
37977
|
// src/rfc/replay-http-server.ts
|
|
37372
37978
|
var import_node_http3 = __toESM(require("http"), 1);
|
|
37373
|
-
var
|
|
37979
|
+
var import_node_child_process9 = require("child_process");
|
|
37374
37980
|
var import_node_stream2 = require("stream");
|
|
37375
37981
|
async function createReplayHttpServer(options) {
|
|
37376
37982
|
const {
|
|
@@ -37524,7 +38130,7 @@ async function createReplayHttpServer(options) {
|
|
|
37524
38130
|
"pipe:1"
|
|
37525
38131
|
];
|
|
37526
38132
|
log(`spawning ffmpeg: ${ffmpegPath} ${ffmpegArgs.join(" ")}`);
|
|
37527
|
-
ffmpegProcess = (0,
|
|
38133
|
+
ffmpegProcess = (0, import_node_child_process9.spawn)(ffmpegPath, ffmpegArgs, {
|
|
37528
38134
|
stdio: ["pipe", "pipe", "pipe"]
|
|
37529
38135
|
});
|
|
37530
38136
|
ffmpegProcess.stdout?.pipe(outputStream).pipe(res);
|
|
@@ -37625,7 +38231,7 @@ async function createReplayHttpServer(options) {
|
|
|
37625
38231
|
init_BaichuanVideoStream();
|
|
37626
38232
|
|
|
37627
38233
|
// src/baichuan/stream/Go2rtcTcpServer.ts
|
|
37628
|
-
var
|
|
38234
|
+
var import_node_events8 = require("events");
|
|
37629
38235
|
var net4 = __toESM(require("net"), 1);
|
|
37630
38236
|
init_H264Converter();
|
|
37631
38237
|
init_H265Converter();
|
|
@@ -37737,7 +38343,7 @@ var NativeStreamFanout2 = class {
|
|
|
37737
38343
|
this.pumpPromise = null;
|
|
37738
38344
|
}
|
|
37739
38345
|
};
|
|
37740
|
-
var Go2rtcTcpServer = class _Go2rtcTcpServer extends
|
|
38346
|
+
var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events8.EventEmitter {
|
|
37741
38347
|
api;
|
|
37742
38348
|
channel;
|
|
37743
38349
|
profile;
|
|
@@ -38428,8 +39034,8 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events7.EventEm
|
|
|
38428
39034
|
};
|
|
38429
39035
|
|
|
38430
39036
|
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
38431
|
-
var
|
|
38432
|
-
var
|
|
39037
|
+
var import_node_events9 = require("events");
|
|
39038
|
+
var import_node_child_process10 = require("child_process");
|
|
38433
39039
|
var http4 = __toESM(require("http"), 1);
|
|
38434
39040
|
var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
|
|
38435
39041
|
var NAL_START_CODE_3B3 = Buffer.from([0, 0, 1]);
|
|
@@ -38475,7 +39081,7 @@ function isH264KeyframeFromAnnexB(annexB) {
|
|
|
38475
39081
|
}
|
|
38476
39082
|
return false;
|
|
38477
39083
|
}
|
|
38478
|
-
var BaichuanHttpStreamServer = class extends
|
|
39084
|
+
var BaichuanHttpStreamServer = class extends import_node_events9.EventEmitter {
|
|
38479
39085
|
videoStream;
|
|
38480
39086
|
listenPort;
|
|
38481
39087
|
path;
|
|
@@ -38539,7 +39145,7 @@ var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
|
|
|
38539
39145
|
this.httpServer.on("error", reject);
|
|
38540
39146
|
});
|
|
38541
39147
|
this.logger.info(`[BaichuanHttpStreamServer] Starting ffmpeg for H.264 -> MPEG-TS conversion...`);
|
|
38542
|
-
const ffmpeg = (0,
|
|
39148
|
+
const ffmpeg = (0, import_node_child_process10.spawn)("ffmpeg", [
|
|
38543
39149
|
"-hide_banner",
|
|
38544
39150
|
// ffmpeg warnings often include non-fatal decode messages (e.g. decode_slice_header),
|
|
38545
39151
|
// which we don't want to treat as application errors.
|
|
@@ -38747,15 +39353,15 @@ var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
|
|
|
38747
39353
|
};
|
|
38748
39354
|
|
|
38749
39355
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
38750
|
-
var
|
|
39356
|
+
var import_node_events11 = require("events");
|
|
38751
39357
|
var http5 = __toESM(require("http"), 1);
|
|
38752
39358
|
|
|
38753
39359
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
38754
|
-
var
|
|
38755
|
-
var
|
|
39360
|
+
var import_node_events10 = require("events");
|
|
39361
|
+
var import_node_child_process11 = require("child_process");
|
|
38756
39362
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
38757
39363
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
38758
|
-
var MjpegTransformer = class extends
|
|
39364
|
+
var MjpegTransformer = class extends import_node_events10.EventEmitter {
|
|
38759
39365
|
options;
|
|
38760
39366
|
ffmpeg = null;
|
|
38761
39367
|
started = false;
|
|
@@ -38813,7 +39419,7 @@ var MjpegTransformer = class extends import_node_events9.EventEmitter {
|
|
|
38813
39419
|
"pipe:1"
|
|
38814
39420
|
);
|
|
38815
39421
|
this.log("debug", `Starting FFmpeg with args: ${args.join(" ")}`);
|
|
38816
|
-
this.ffmpeg = (0,
|
|
39422
|
+
this.ffmpeg = (0, import_node_child_process11.spawn)("ffmpeg", args, {
|
|
38817
39423
|
stdio: ["pipe", "pipe", "pipe"]
|
|
38818
39424
|
});
|
|
38819
39425
|
this.ffmpeg.stdout.on("data", (data) => {
|
|
@@ -38954,7 +39560,7 @@ Content-Length: ${frame.length}\r
|
|
|
38954
39560
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
38955
39561
|
init_H264Converter();
|
|
38956
39562
|
init_H265Converter();
|
|
38957
|
-
var BaichuanMjpegServer = class extends
|
|
39563
|
+
var BaichuanMjpegServer = class extends import_node_events11.EventEmitter {
|
|
38958
39564
|
options;
|
|
38959
39565
|
clients = /* @__PURE__ */ new Map();
|
|
38960
39566
|
httpServer = null;
|
|
@@ -39235,14 +39841,14 @@ var BaichuanMjpegServer = class extends import_node_events10.EventEmitter {
|
|
|
39235
39841
|
};
|
|
39236
39842
|
|
|
39237
39843
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
39238
|
-
var
|
|
39844
|
+
var import_node_events13 = require("events");
|
|
39239
39845
|
init_BcMediaAnnexBDecoder();
|
|
39240
39846
|
|
|
39241
39847
|
// src/baichuan/stream/AacToOpusTranscoder.ts
|
|
39242
|
-
var
|
|
39848
|
+
var import_node_child_process12 = require("child_process");
|
|
39243
39849
|
var import_node_dgram3 = require("dgram");
|
|
39244
|
-
var
|
|
39245
|
-
var AacToOpusTranscoder = class extends
|
|
39850
|
+
var import_node_events12 = require("events");
|
|
39851
|
+
var AacToOpusTranscoder = class extends import_node_events12.EventEmitter {
|
|
39246
39852
|
opts;
|
|
39247
39853
|
socket = null;
|
|
39248
39854
|
ffmpeg = null;
|
|
@@ -39319,7 +39925,7 @@ var AacToOpusTranscoder = class extends import_node_events11.EventEmitter {
|
|
|
39319
39925
|
`rtp://127.0.0.1:${this.port}`
|
|
39320
39926
|
];
|
|
39321
39927
|
this.log("info", `spawning ffmpeg with: ${this.opts.ffmpegPath} ${args.join(" ")}`);
|
|
39322
|
-
this.ffmpeg = (0,
|
|
39928
|
+
this.ffmpeg = (0, import_node_child_process12.spawn)(this.opts.ffmpegPath, args, {
|
|
39323
39929
|
stdio: ["pipe", "ignore", "pipe"]
|
|
39324
39930
|
});
|
|
39325
39931
|
this.ffmpeg.on("error", (err) => {
|
|
@@ -39459,7 +40065,7 @@ function getH264NalType(nalUnit) {
|
|
|
39459
40065
|
function getH265NalType2(nalUnit) {
|
|
39460
40066
|
return nalUnit[0] >> 1 & 63;
|
|
39461
40067
|
}
|
|
39462
|
-
var BaichuanWebRTCServer = class extends
|
|
40068
|
+
var BaichuanWebRTCServer = class extends import_node_events13.EventEmitter {
|
|
39463
40069
|
options;
|
|
39464
40070
|
sessions = /* @__PURE__ */ new Map();
|
|
39465
40071
|
sessionIdCounter = 0;
|
|
@@ -40451,12 +41057,12 @@ Error: ${err}`
|
|
|
40451
41057
|
};
|
|
40452
41058
|
|
|
40453
41059
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
40454
|
-
var
|
|
41060
|
+
var import_node_events14 = require("events");
|
|
40455
41061
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
40456
41062
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
40457
41063
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
40458
41064
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
40459
|
-
var
|
|
41065
|
+
var import_node_child_process13 = require("child_process");
|
|
40460
41066
|
init_BcMediaAnnexBDecoder();
|
|
40461
41067
|
init_H264Converter();
|
|
40462
41068
|
init_H265Converter();
|
|
@@ -40531,7 +41137,7 @@ function getNalTypes(codec, annexB) {
|
|
|
40531
41137
|
}
|
|
40532
41138
|
});
|
|
40533
41139
|
}
|
|
40534
|
-
var BaichuanHlsServer = class extends
|
|
41140
|
+
var BaichuanHlsServer = class extends import_node_events14.EventEmitter {
|
|
40535
41141
|
api;
|
|
40536
41142
|
channel;
|
|
40537
41143
|
profile;
|
|
@@ -40933,7 +41539,7 @@ var BaichuanHlsServer = class extends import_node_events13.EventEmitter {
|
|
|
40933
41539
|
this.segmentPattern,
|
|
40934
41540
|
this.playlistPath
|
|
40935
41541
|
);
|
|
40936
|
-
const p = (0,
|
|
41542
|
+
const p = (0, import_node_child_process13.spawn)(this.ffmpegPath, args, {
|
|
40937
41543
|
stdio: ["pipe", "ignore", "pipe"]
|
|
40938
41544
|
});
|
|
40939
41545
|
p.on("error", (err) => {
|
|
@@ -41057,13 +41663,13 @@ async function pingHost(host, timeoutMs = 3e3) {
|
|
|
41057
41663
|
}
|
|
41058
41664
|
return ["-c", "1", "-W", String(Math.max(1, Math.floor(timeoutMs / 1e3))), host];
|
|
41059
41665
|
};
|
|
41060
|
-
const { spawn:
|
|
41666
|
+
const { spawn: spawn14 } = await import("child_process");
|
|
41061
41667
|
for (const bin of pingCandidates) {
|
|
41062
41668
|
const ranOk = await new Promise((resolve) => {
|
|
41063
41669
|
let settled = false;
|
|
41064
41670
|
let child;
|
|
41065
41671
|
try {
|
|
41066
|
-
child =
|
|
41672
|
+
child = spawn14(bin, pingArgs(bin), { stdio: "ignore" });
|
|
41067
41673
|
} catch {
|
|
41068
41674
|
resolve("spawn-failed");
|
|
41069
41675
|
return;
|
|
@@ -41696,10 +42302,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
41696
42302
|
}
|
|
41697
42303
|
|
|
41698
42304
|
// src/multifocal/compositeRtspServer.ts
|
|
41699
|
-
var
|
|
41700
|
-
var
|
|
42305
|
+
var import_node_events15 = require("events");
|
|
42306
|
+
var import_node_child_process14 = require("child_process");
|
|
41701
42307
|
var net5 = __toESM(require("net"), 1);
|
|
41702
|
-
var CompositeRtspServer = class extends
|
|
42308
|
+
var CompositeRtspServer = class extends import_node_events15.EventEmitter {
|
|
41703
42309
|
options;
|
|
41704
42310
|
compositeStream = null;
|
|
41705
42311
|
rtspServer = null;
|
|
@@ -41804,7 +42410,7 @@ var CompositeRtspServer = class extends import_node_events14.EventEmitter {
|
|
|
41804
42410
|
this.logger.log?.(
|
|
41805
42411
|
`[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(" ")}`
|
|
41806
42412
|
);
|
|
41807
|
-
this.ffmpegProcess = (0,
|
|
42413
|
+
this.ffmpegProcess = (0, import_node_child_process14.spawn)("ffmpeg", ffmpegArgs, {
|
|
41808
42414
|
stdio: ["pipe", "pipe", "pipe"]
|
|
41809
42415
|
});
|
|
41810
42416
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -42475,7 +43081,7 @@ var RtspBackchannel = class _RtspBackchannel {
|
|
|
42475
43081
|
};
|
|
42476
43082
|
|
|
42477
43083
|
// src/baichuan/stream/BaichuanRtspBackchannelServer.ts
|
|
42478
|
-
var
|
|
43084
|
+
var import_node_events16 = require("events");
|
|
42479
43085
|
var net6 = __toESM(require("net"), 1);
|
|
42480
43086
|
var crypto3 = __toESM(require("crypto"), 1);
|
|
42481
43087
|
var md5Hex = (s) => crypto3.createHash("md5").update(s).digest("hex");
|
|
@@ -42532,7 +43138,7 @@ function extractPublicEndpoint(url, requestText) {
|
|
|
42532
43138
|
if (hostHeader) return hostHeader;
|
|
42533
43139
|
return null;
|
|
42534
43140
|
}
|
|
42535
|
-
var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends
|
|
43141
|
+
var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends import_node_events16.EventEmitter {
|
|
42536
43142
|
listenHost;
|
|
42537
43143
|
listenPort;
|
|
42538
43144
|
logger;
|
|
@@ -43513,7 +44119,9 @@ function buildInitialStatus(config) {
|
|
|
43513
44119
|
// Annotate the CommonJS export names for ESM import in node:
|
|
43514
44120
|
0 && (module.exports = {
|
|
43515
44121
|
ALL_UDP_DISCOVERY_METHODS,
|
|
44122
|
+
ALWAYS_ON_DEFAULTS,
|
|
43516
44123
|
AesStreamDecryptor,
|
|
44124
|
+
AlwaysOnController,
|
|
43517
44125
|
AutodiscoveryClient,
|
|
43518
44126
|
BC_AES_IV,
|
|
43519
44127
|
BC_CLASS_FILE_DOWNLOAD,
|
|
@@ -43670,6 +44278,7 @@ function buildInitialStatus(config) {
|
|
|
43670
44278
|
BcUdpStream,
|
|
43671
44279
|
CompositeRtspServer,
|
|
43672
44280
|
CompositeStream,
|
|
44281
|
+
ContinuousVideoStream,
|
|
43673
44282
|
DEFAULT_SHELTER_CANVAS,
|
|
43674
44283
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
43675
44284
|
DUAL_LENS_MODELS,
|
|
@@ -43683,6 +44292,7 @@ function buildInitialStatus(config) {
|
|
|
43683
44292
|
MpegTsMuxer,
|
|
43684
44293
|
NVR_HUB_EXACT_TYPES,
|
|
43685
44294
|
NVR_HUB_MODEL_PATTERNS,
|
|
44295
|
+
PlaceholderRenderer,
|
|
43686
44296
|
ReolinkBaichuanApi,
|
|
43687
44297
|
ReolinkCgiApi,
|
|
43688
44298
|
ReolinkHttpClient,
|