@apocaliss92/nodelink-js 0.4.24 → 0.4.28
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 +4 -2
- package/dist/{chunk-JYHK2ZSH.js → chunk-NQ7D5TLR.js} +241 -27
- package/dist/chunk-NQ7D5TLR.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +201 -26
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +569 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -6
- package/dist/index.d.ts +269 -5
- package/dist/index.js +290 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-JYHK2ZSH.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -3293,10 +3293,10 @@ function buildRtspPath(channel, stream) {
|
|
|
3293
3293
|
}
|
|
3294
3294
|
function buildRtspUrl(params) {
|
|
3295
3295
|
const port = params.port ?? 554;
|
|
3296
|
-
const
|
|
3296
|
+
const path7 = buildRtspPath(params.channel, params.stream);
|
|
3297
3297
|
const user = encodeURIComponent(params.username);
|
|
3298
3298
|
const pass = encodeURIComponent(params.password);
|
|
3299
|
-
return `rtsp://${user}:${pass}@${params.host}:${port}${
|
|
3299
|
+
return `rtsp://${user}:${pass}@${params.host}:${port}${path7}`;
|
|
3300
3300
|
}
|
|
3301
3301
|
var init_urls = __esm({
|
|
3302
3302
|
"src/rtsp/urls.ts"() {
|
|
@@ -4845,8 +4845,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
4845
4845
|
for (const app of rtmpApps) {
|
|
4846
4846
|
for (const streamName of streams) {
|
|
4847
4847
|
const streamType = streamName.includes("sub") || streamName === "sub" || streamName === "mobile" ? 1 : 0;
|
|
4848
|
-
const
|
|
4849
|
-
const u = new URL(`rtmp://${params.host}:1935${
|
|
4848
|
+
const path7 = `/${app}/channel${params.channel}_${streamName}.bcs`;
|
|
4849
|
+
const u = new URL(`rtmp://${params.host}:1935${path7}`);
|
|
4850
4850
|
u.searchParams.set("channel", params.channel.toString());
|
|
4851
4851
|
u.searchParams.set("stream", streamType.toString());
|
|
4852
4852
|
u.searchParams.set("user", params.username);
|
|
@@ -8334,6 +8334,7 @@ __export(index_exports, {
|
|
|
8334
8334
|
ReolinkCgiApi: () => ReolinkCgiApi,
|
|
8335
8335
|
ReolinkHttpClient: () => ReolinkHttpClient,
|
|
8336
8336
|
Rfc4571Muxer: () => Rfc4571Muxer,
|
|
8337
|
+
_resetEmailPushBusForTests: () => _resetEmailPushBusForTests,
|
|
8337
8338
|
abilitiesHasAny: () => abilitiesHasAny,
|
|
8338
8339
|
aesDecrypt: () => aesDecrypt,
|
|
8339
8340
|
aesEncrypt: () => aesEncrypt,
|
|
@@ -8379,6 +8380,7 @@ __export(index_exports, {
|
|
|
8379
8380
|
createBaichuanEndpointsServer: () => createBaichuanEndpointsServer,
|
|
8380
8381
|
createDebugGateLogger: () => createDebugGateLogger,
|
|
8381
8382
|
createDiagnosticsBundle: () => createDiagnosticsBundle,
|
|
8383
|
+
createEmailPushServer: () => createEmailPushServer,
|
|
8382
8384
|
createLogger: () => createLogger,
|
|
8383
8385
|
createMjpegBoundary: () => createMjpegBoundary,
|
|
8384
8386
|
createNativeStream: () => createNativeStream,
|
|
@@ -8406,6 +8408,7 @@ __export(index_exports, {
|
|
|
8406
8408
|
discoverViaTcpPortScan: () => discoverViaTcpPortScan,
|
|
8407
8409
|
discoverViaUdpBroadcast: () => discoverViaUdpBroadcast,
|
|
8408
8410
|
discoverViaUdpDirect: () => discoverViaUdpDirect,
|
|
8411
|
+
emitEmailPushEvent: () => emitEmailPushEvent,
|
|
8409
8412
|
encodeHeader: () => encodeHeader,
|
|
8410
8413
|
encodeMotionScopeBitmap: () => encodeMotionScopeBitmap,
|
|
8411
8414
|
encodeMotionSensitivityListXml: () => encodeMotionSensitivityListXml,
|
|
@@ -8422,10 +8425,14 @@ __export(index_exports, {
|
|
|
8422
8425
|
flattenAbilitiesForChannel: () => flattenAbilitiesForChannel,
|
|
8423
8426
|
formatMjpegFrame: () => formatMjpegFrame,
|
|
8424
8427
|
fullCoverageScope: () => fullCoverageScope,
|
|
8428
|
+
getCameraEmailAddress: () => getCameraEmailAddress,
|
|
8425
8429
|
getConstructedVideoStreamOptions: () => getConstructedVideoStreamOptions,
|
|
8430
|
+
getEmailPushCameraResolver: () => getEmailPushCameraResolver,
|
|
8426
8431
|
getGlobalLogger: () => getGlobalLogger,
|
|
8427
8432
|
getH265NalType: () => getH265NalType,
|
|
8433
|
+
getLastEmailPushEvent: () => getLastEmailPushEvent,
|
|
8428
8434
|
getMjpegContentType: () => getMjpegContentType,
|
|
8435
|
+
getRecentEmailPushEvents: () => getRecentEmailPushEvents,
|
|
8429
8436
|
getSupportItemForChannel: () => getSupportItemForChannel,
|
|
8430
8437
|
getVideoStream: () => getVideoStream,
|
|
8431
8438
|
getVideoclipClientInfo: () => getVideoclipClientInfo,
|
|
@@ -8440,12 +8447,15 @@ __export(index_exports, {
|
|
|
8440
8447
|
isTcpFailureThatShouldFallbackToUdp: () => isTcpFailureThatShouldFallbackToUdp,
|
|
8441
8448
|
isValidH264AnnexBAccessUnit: () => isValidH264AnnexBAccessUnit,
|
|
8442
8449
|
isValidH265AnnexBAccessUnit: () => isValidH265AnnexBAccessUnit,
|
|
8450
|
+
loadEmailPushTls: () => loadEmailPushTls,
|
|
8451
|
+
mapEmailPushInferredType: () => mapEmailPushInferredType,
|
|
8443
8452
|
maskUid: () => maskUid,
|
|
8444
8453
|
md5HexUpper: () => md5HexUpper,
|
|
8445
8454
|
md5StrModern: () => md5StrModern,
|
|
8446
8455
|
normalizeDayNightMode: () => normalizeDayNightMode,
|
|
8447
8456
|
normalizeOpenClose: () => normalizeOpenClose,
|
|
8448
8457
|
normalizeUid: () => normalizeUid,
|
|
8458
|
+
onEmailPushEvent: () => onEmailPushEvent,
|
|
8449
8459
|
packetizeAacAdtsFrame: () => packetizeAacAdtsFrame,
|
|
8450
8460
|
packetizeAacRawFrame: () => packetizeAacRawFrame,
|
|
8451
8461
|
packetizeH264: () => packetizeH264,
|
|
@@ -8463,6 +8473,7 @@ __export(index_exports, {
|
|
|
8463
8473
|
runMultifocalDiagnosticsConsecutively: () => runMultifocalDiagnosticsConsecutively,
|
|
8464
8474
|
sampleStreams: () => sampleStreams,
|
|
8465
8475
|
sanitizeFixtureData: () => sanitizeFixtureData,
|
|
8476
|
+
setEmailPushCameraResolver: () => setEmailPushCameraResolver,
|
|
8466
8477
|
setGlobalLogger: () => setGlobalLogger,
|
|
8467
8478
|
splitAnnexBToNalPayloads: () => splitAnnexBToNalPayloads,
|
|
8468
8479
|
splitAnnexBToNals: () => splitAnnexBToNals,
|
|
@@ -13646,6 +13657,21 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13646
13657
|
let audioSampleRate = null;
|
|
13647
13658
|
let streamStarted = false;
|
|
13648
13659
|
let closed = false;
|
|
13660
|
+
const signal = options?.signal;
|
|
13661
|
+
let sleepResolve = null;
|
|
13662
|
+
let sleepTimer = null;
|
|
13663
|
+
const clearSleepTimer = () => {
|
|
13664
|
+
if (sleepTimer) {
|
|
13665
|
+
clearTimeout(sleepTimer);
|
|
13666
|
+
sleepTimer = null;
|
|
13667
|
+
}
|
|
13668
|
+
};
|
|
13669
|
+
const handleAbort = () => {
|
|
13670
|
+
clearSleepTimer();
|
|
13671
|
+
const r = sleepResolve;
|
|
13672
|
+
sleepResolve = null;
|
|
13673
|
+
r?.();
|
|
13674
|
+
};
|
|
13649
13675
|
const onError = (_error) => {
|
|
13650
13676
|
closed = true;
|
|
13651
13677
|
api.logger?.warn?.(
|
|
@@ -13743,7 +13769,13 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13743
13769
|
}
|
|
13744
13770
|
});
|
|
13745
13771
|
streamStarted = true;
|
|
13746
|
-
|
|
13772
|
+
if (signal) {
|
|
13773
|
+
if (signal.aborted) {
|
|
13774
|
+
closed = true;
|
|
13775
|
+
} else {
|
|
13776
|
+
signal.addEventListener("abort", handleAbort);
|
|
13777
|
+
}
|
|
13778
|
+
}
|
|
13747
13779
|
while (!closed && !signal?.aborted) {
|
|
13748
13780
|
if (frameQueue.length > 0) {
|
|
13749
13781
|
const frame = frameQueue.shift();
|
|
@@ -13751,31 +13783,30 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13751
13783
|
} else {
|
|
13752
13784
|
await new Promise((resolve) => {
|
|
13753
13785
|
frameResolve = resolve;
|
|
13754
|
-
|
|
13786
|
+
sleepResolve = resolve;
|
|
13787
|
+
sleepTimer = setTimeout(() => {
|
|
13788
|
+
sleepTimer = null;
|
|
13789
|
+
if (sleepResolve === resolve) sleepResolve = null;
|
|
13755
13790
|
if (frameResolve === resolve) {
|
|
13756
13791
|
frameResolve = null;
|
|
13757
13792
|
resolve();
|
|
13758
13793
|
}
|
|
13759
13794
|
}, 1e3);
|
|
13760
|
-
if (signal) {
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
};
|
|
13766
|
-
if (signal.aborted) {
|
|
13767
|
-
clearTimeout(timer);
|
|
13768
|
-
frameResolve = null;
|
|
13769
|
-
resolve();
|
|
13770
|
-
} else {
|
|
13771
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
13772
|
-
}
|
|
13795
|
+
if (signal?.aborted) {
|
|
13796
|
+
clearSleepTimer();
|
|
13797
|
+
sleepResolve = null;
|
|
13798
|
+
frameResolve = null;
|
|
13799
|
+
resolve();
|
|
13773
13800
|
}
|
|
13774
13801
|
});
|
|
13802
|
+
sleepResolve = null;
|
|
13803
|
+
clearSleepTimer();
|
|
13775
13804
|
}
|
|
13776
13805
|
}
|
|
13777
13806
|
} finally {
|
|
13778
13807
|
closed = true;
|
|
13808
|
+
if (signal) signal.removeEventListener("abort", handleAbort);
|
|
13809
|
+
clearSleepTimer();
|
|
13779
13810
|
try {
|
|
13780
13811
|
await videoStream.stop();
|
|
13781
13812
|
} catch {
|
|
@@ -15809,12 +15840,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
15809
15840
|
if (frame.videoType === "H264" || frame.videoType === "H265") {
|
|
15810
15841
|
this.setFlowVideoType(frame.videoType, "native stream");
|
|
15811
15842
|
}
|
|
15812
|
-
this.flow.
|
|
15813
|
-
|
|
15814
|
-
|
|
15843
|
+
if (!this.flow.getFmtp().hasParamSets) {
|
|
15844
|
+
this.flow.extractParameterSets(frame.data);
|
|
15845
|
+
}
|
|
15846
|
+
if (this.flow.getFmtp().hasParamSets) {
|
|
15815
15847
|
this.markFirstFrameReceived();
|
|
15816
15848
|
}
|
|
15817
|
-
const isKeyframe = this.isRawFrameKeyframe(frame);
|
|
15849
|
+
const isKeyframe = typeof frame.isKeyframe === "boolean" ? frame.isKeyframe : this.isRawFrameKeyframe(frame);
|
|
15818
15850
|
this.prebuffer.push({
|
|
15819
15851
|
frame: { ...frame, data: Buffer.from(frame.data) },
|
|
15820
15852
|
time: Date.now(),
|
|
@@ -19635,6 +19667,93 @@ var applyFloodlightSettingsToXml = (xml, settings) => {
|
|
|
19635
19667
|
return modifiedXml;
|
|
19636
19668
|
};
|
|
19637
19669
|
|
|
19670
|
+
// src/reolink/baichuan/utils/whiteLedStatusPush.ts
|
|
19671
|
+
var parseFloodlightStatusListPushXml = (xml) => {
|
|
19672
|
+
const out = [];
|
|
19673
|
+
const re = /<channel>\s*(\d+)\s*<\/channel>[\s\S]*?<status>\s*(\d+)\s*<\/status>/gi;
|
|
19674
|
+
let m;
|
|
19675
|
+
while ((m = re.exec(xml)) !== null) {
|
|
19676
|
+
const channel = Number.parseInt(m[1] ?? "", 10);
|
|
19677
|
+
const status = Number.parseInt(m[2] ?? "", 10);
|
|
19678
|
+
if (!Number.isFinite(channel) || !Number.isFinite(status)) continue;
|
|
19679
|
+
out.push({ channel, status });
|
|
19680
|
+
}
|
|
19681
|
+
return out;
|
|
19682
|
+
};
|
|
19683
|
+
|
|
19684
|
+
// src/reolink/baichuan/utils/sirenStatusPush.ts
|
|
19685
|
+
var parseSirenStatusListPushXml = (xml) => {
|
|
19686
|
+
const out = [];
|
|
19687
|
+
const re = /<(?:channelId|channel)>\s*(\d+)\s*<\/(?:channelId|channel)>[\s\S]*?<status>\s*(\d+)\s*<\/status>(?:[\s\S]*?<playing>\s*(\d+)\s*<\/playing>)?/gi;
|
|
19688
|
+
let m;
|
|
19689
|
+
while ((m = re.exec(xml)) !== null) {
|
|
19690
|
+
const channel = Number.parseInt(m[1] ?? "", 10);
|
|
19691
|
+
const status = Number.parseInt(m[2] ?? "", 10);
|
|
19692
|
+
if (!Number.isFinite(channel) || !Number.isFinite(status)) continue;
|
|
19693
|
+
const entry = { channel, status };
|
|
19694
|
+
if (m[3] !== void 0) {
|
|
19695
|
+
const playing = Number.parseInt(m[3], 10);
|
|
19696
|
+
if (Number.isFinite(playing)) entry.playing = playing;
|
|
19697
|
+
}
|
|
19698
|
+
out.push(entry);
|
|
19699
|
+
}
|
|
19700
|
+
return out;
|
|
19701
|
+
};
|
|
19702
|
+
|
|
19703
|
+
// src/emailPush/bus.ts
|
|
19704
|
+
var import_node_events5 = require("events");
|
|
19705
|
+
var emitter = new import_node_events5.EventEmitter();
|
|
19706
|
+
var cameraResolver = () => void 0;
|
|
19707
|
+
var lastEventByCamera = /* @__PURE__ */ new Map();
|
|
19708
|
+
var MAX_GLOBAL_EVENTS = 300;
|
|
19709
|
+
var globalRecentEvents = [];
|
|
19710
|
+
function setEmailPushCameraResolver(resolver) {
|
|
19711
|
+
cameraResolver = resolver;
|
|
19712
|
+
}
|
|
19713
|
+
function getEmailPushCameraResolver() {
|
|
19714
|
+
return cameraResolver;
|
|
19715
|
+
}
|
|
19716
|
+
function onEmailPushEvent(handler) {
|
|
19717
|
+
emitter.on("event", handler);
|
|
19718
|
+
return () => emitter.off("event", handler);
|
|
19719
|
+
}
|
|
19720
|
+
function emitEmailPushEvent(event) {
|
|
19721
|
+
lastEventByCamera.set(event.cameraId, event);
|
|
19722
|
+
globalRecentEvents.unshift(event);
|
|
19723
|
+
if (globalRecentEvents.length > MAX_GLOBAL_EVENTS) {
|
|
19724
|
+
globalRecentEvents.length = MAX_GLOBAL_EVENTS;
|
|
19725
|
+
}
|
|
19726
|
+
emitter.emit("event", event);
|
|
19727
|
+
}
|
|
19728
|
+
function getLastEmailPushEvent(cameraId) {
|
|
19729
|
+
return lastEventByCamera.get(cameraId);
|
|
19730
|
+
}
|
|
19731
|
+
function getRecentEmailPushEvents(limit = MAX_GLOBAL_EVENTS) {
|
|
19732
|
+
const clamped = Math.max(0, Math.min(limit, MAX_GLOBAL_EVENTS));
|
|
19733
|
+
return globalRecentEvents.slice(0, clamped);
|
|
19734
|
+
}
|
|
19735
|
+
function mapEmailPushInferredType(inferred) {
|
|
19736
|
+
switch (inferred) {
|
|
19737
|
+
case "motion":
|
|
19738
|
+
case "doorbell":
|
|
19739
|
+
case "people":
|
|
19740
|
+
case "vehicle":
|
|
19741
|
+
case "animal":
|
|
19742
|
+
case "face":
|
|
19743
|
+
case "package":
|
|
19744
|
+
return inferred;
|
|
19745
|
+
case "other":
|
|
19746
|
+
default:
|
|
19747
|
+
return "motion";
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
function _resetEmailPushBusForTests() {
|
|
19751
|
+
emitter.removeAllListeners();
|
|
19752
|
+
cameraResolver = () => void 0;
|
|
19753
|
+
lastEventByCamera.clear();
|
|
19754
|
+
globalRecentEvents.length = 0;
|
|
19755
|
+
}
|
|
19756
|
+
|
|
19638
19757
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
19639
19758
|
var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
|
|
19640
19759
|
"Reolink Duo PoE",
|
|
@@ -19780,7 +19899,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19780
19899
|
* general socket is created, logged in, and all event/push/guard listeners
|
|
19781
19900
|
* are re-attached automatically.
|
|
19782
19901
|
*
|
|
19783
|
-
* This is a **no-op** when the API is already
|
|
19902
|
+
* This is a **no-op** when the API is already ready (see `isReadyState()`).
|
|
19784
19903
|
*
|
|
19785
19904
|
* @throws If `close()` was called — the API is permanently closed and a new
|
|
19786
19905
|
* instance must be created.
|
|
@@ -19861,7 +19980,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19861
19980
|
/**
|
|
19862
19981
|
* Attach event, push, channelInfo, and guard listeners to the current
|
|
19863
19982
|
* "general" client. Called from the constructor and from
|
|
19864
|
-
*
|
|
19983
|
+
* `reconnectGeneralSocket()`.
|
|
19865
19984
|
*/
|
|
19866
19985
|
setupGeneralClientListeners() {
|
|
19867
19986
|
const client = this.client;
|
|
@@ -19913,7 +20032,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19913
20032
|
});
|
|
19914
20033
|
client.on("push", (frame) => {
|
|
19915
20034
|
const cmdId = frame.header.cmdId;
|
|
19916
|
-
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
20035
|
+
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST && cmdId !== BC_CMD_ID_FLOODLIGHT_STATUS_LIST && cmdId !== BC_CMD_ID_GET_AUDIO_ALARM) {
|
|
19917
20036
|
return;
|
|
19918
20037
|
}
|
|
19919
20038
|
try {
|
|
@@ -21450,6 +21569,40 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21450
21569
|
this.econnresetStormRebootInFlight = void 0;
|
|
21451
21570
|
});
|
|
21452
21571
|
}
|
|
21572
|
+
/**
|
|
21573
|
+
* Bind this API instance to the global email-push bus so that incoming
|
|
21574
|
+
* SMTP-delivered motion / AI events for the matching camera surface on
|
|
21575
|
+
* this instance's standard `onSimpleEvent` channel. The consumer keeps
|
|
21576
|
+
* a single subscription (`onSimpleEvent`) and gets both the native
|
|
21577
|
+
* Baichuan push and the email-push transport on the same stream.
|
|
21578
|
+
*
|
|
21579
|
+
* - `cameraId` shorthand: match events with `event.cameraId === cameraId`.
|
|
21580
|
+
* - `match`: arbitrary predicate (e.g. when the consumer uses a
|
|
21581
|
+
* nickname-based mapping or wants to handle multiple recipients).
|
|
21582
|
+
*
|
|
21583
|
+
* Returns an `off()` handle. Safe to call repeatedly — each call
|
|
21584
|
+
* registers its own listener.
|
|
21585
|
+
*/
|
|
21586
|
+
subscribeEmailPushEvents(params) {
|
|
21587
|
+
const channel = params.channel ?? 0;
|
|
21588
|
+
const matches = "match" in params ? params.match : (event) => event.cameraId === params.cameraId;
|
|
21589
|
+
const off = onEmailPushEvent((event) => {
|
|
21590
|
+
if (!matches(event)) return;
|
|
21591
|
+
this.dispatchSimpleEvent({
|
|
21592
|
+
type: mapEmailPushInferredType(event.inferredType),
|
|
21593
|
+
channel,
|
|
21594
|
+
timestamp: event.receivedAtMs
|
|
21595
|
+
});
|
|
21596
|
+
if (event.inferredType !== "motion" && event.inferredType !== "doorbell" && event.inferredType !== "other") {
|
|
21597
|
+
this.dispatchSimpleEvent({
|
|
21598
|
+
type: "motion",
|
|
21599
|
+
channel,
|
|
21600
|
+
timestamp: event.receivedAtMs
|
|
21601
|
+
});
|
|
21602
|
+
}
|
|
21603
|
+
});
|
|
21604
|
+
return off;
|
|
21605
|
+
}
|
|
21453
21606
|
/**
|
|
21454
21607
|
* Subscribe to minimal high-level events.
|
|
21455
21608
|
* The API manages Baichuan subscribe/unsubscribe automatically.
|
|
@@ -21508,7 +21661,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21508
21661
|
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
21509
21662
|
* `additionalHeader` block on active video streams.
|
|
21510
21663
|
*
|
|
21511
|
-
* Mirrors
|
|
21664
|
+
* Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
|
|
21512
21665
|
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
21513
21666
|
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
21514
21667
|
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
@@ -21535,7 +21688,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21535
21688
|
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
21536
21689
|
* with class label and confidence) without managing a video stream yourself.
|
|
21537
21690
|
*
|
|
21538
|
-
* Mirrors
|
|
21691
|
+
* Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
|
|
21539
21692
|
* `(channel, profile)` tuple the API ensures the corresponding video stream
|
|
21540
21693
|
* is running (the pool socket may already be shared with a regular consumer),
|
|
21541
21694
|
* forwards every box-bearing `additionalHeader` to your callback, and tears
|
|
@@ -25355,24 +25508,24 @@ ${stderr}`)
|
|
|
25355
25508
|
async muxToMp4(params) {
|
|
25356
25509
|
const { spawn: spawn13 } = await import("child_process");
|
|
25357
25510
|
const { randomUUID: randomUUID3 } = await import("crypto");
|
|
25358
|
-
const
|
|
25511
|
+
const fs7 = await import("fs/promises");
|
|
25359
25512
|
const os2 = await import("os");
|
|
25360
|
-
const
|
|
25513
|
+
const path7 = await import("path");
|
|
25361
25514
|
const ffmpeg = params.ffmpegPath ?? "ffmpeg";
|
|
25362
25515
|
const tmpDir = os2.tmpdir();
|
|
25363
25516
|
const id = randomUUID3();
|
|
25364
25517
|
const videoFormat = params.videoCodec === "H265" ? "hevc" : "h264";
|
|
25365
|
-
const videoPath =
|
|
25366
|
-
const outputPath =
|
|
25518
|
+
const videoPath = path7.join(tmpDir, `reolink-${id}.${videoFormat}`);
|
|
25519
|
+
const outputPath = path7.join(tmpDir, `reolink-${id}.mp4`);
|
|
25367
25520
|
let audioPath = null;
|
|
25368
25521
|
if (params.audioData && params.audioData.length > 0 && params.audioCodec) {
|
|
25369
25522
|
const audioExt = params.audioCodec === "Aac" ? "aac" : "raw";
|
|
25370
|
-
audioPath =
|
|
25523
|
+
audioPath = path7.join(tmpDir, `reolink-${id}.${audioExt}`);
|
|
25371
25524
|
}
|
|
25372
25525
|
try {
|
|
25373
|
-
await
|
|
25526
|
+
await fs7.writeFile(videoPath, params.videoData);
|
|
25374
25527
|
if (audioPath && params.audioData) {
|
|
25375
|
-
await
|
|
25528
|
+
await fs7.writeFile(audioPath, params.audioData);
|
|
25376
25529
|
}
|
|
25377
25530
|
const args = ["-hide_banner", "-loglevel", "error", "-y"];
|
|
25378
25531
|
if (params.fps > 0) {
|
|
@@ -25425,13 +25578,13 @@ ${stderr}`)
|
|
|
25425
25578
|
}
|
|
25426
25579
|
});
|
|
25427
25580
|
});
|
|
25428
|
-
return await
|
|
25581
|
+
return await fs7.readFile(outputPath);
|
|
25429
25582
|
} finally {
|
|
25430
|
-
await
|
|
25583
|
+
await fs7.unlink(videoPath).catch(() => {
|
|
25431
25584
|
});
|
|
25432
|
-
if (audioPath) await
|
|
25585
|
+
if (audioPath) await fs7.unlink(audioPath).catch(() => {
|
|
25433
25586
|
});
|
|
25434
|
-
await
|
|
25587
|
+
await fs7.unlink(outputPath).catch(() => {
|
|
25435
25588
|
});
|
|
25436
25589
|
}
|
|
25437
25590
|
}
|
|
@@ -28379,7 +28532,7 @@ ${xml}`
|
|
|
28379
28532
|
* Field meaning per stream:
|
|
28380
28533
|
* - `audio` — 0/1 toggle
|
|
28381
28534
|
* - `width`/`height` — resolution in pixels. Must be one of the
|
|
28382
|
-
* resolutions returned by
|
|
28535
|
+
* resolutions returned by `getStreamInfoList()`.
|
|
28383
28536
|
* - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
|
|
28384
28537
|
* - `frameRate` — fps. Must match the table from `getStreamInfoList`.
|
|
28385
28538
|
* - `videoEncType` — `"h264"` or `"h265"`
|
|
@@ -28954,6 +29107,33 @@ ${xml}`
|
|
|
28954
29107
|
};
|
|
28955
29108
|
return;
|
|
28956
29109
|
}
|
|
29110
|
+
if (cmdId === BC_CMD_ID_FLOODLIGHT_STATUS_LIST) {
|
|
29111
|
+
const entries = parseFloodlightStatusListPushXml(xml);
|
|
29112
|
+
if (entries.length === 0) return;
|
|
29113
|
+
for (const entry of entries) {
|
|
29114
|
+
const channel = normalizePushChannel(entry.channel) ?? channelFromHeader;
|
|
29115
|
+
getEntry(channel).floodlightStatus = {
|
|
29116
|
+
updatedAtMs: now,
|
|
29117
|
+
value: { status: entry.status === 1 }
|
|
29118
|
+
};
|
|
29119
|
+
}
|
|
29120
|
+
return;
|
|
29121
|
+
}
|
|
29122
|
+
if (cmdId === BC_CMD_ID_GET_AUDIO_ALARM) {
|
|
29123
|
+
const entries = parseSirenStatusListPushXml(xml);
|
|
29124
|
+
if (entries.length === 0) return;
|
|
29125
|
+
for (const entry of entries) {
|
|
29126
|
+
const channel = normalizePushChannel(entry.channel) ?? channelFromHeader;
|
|
29127
|
+
getEntry(channel).sirenStatus = {
|
|
29128
|
+
updatedAtMs: now,
|
|
29129
|
+
value: {
|
|
29130
|
+
status: entry.status === 1,
|
|
29131
|
+
...entry.playing !== void 0 ? { playing: entry.playing === 1 } : {}
|
|
29132
|
+
}
|
|
29133
|
+
};
|
|
29134
|
+
}
|
|
29135
|
+
return;
|
|
29136
|
+
}
|
|
28957
29137
|
}
|
|
28958
29138
|
/** Read-only snapshot of cached settings pushes (cmd_id 78/79/464/484/623/723). */
|
|
28959
29139
|
getSettingsPushCacheSnapshot() {
|
|
@@ -28986,6 +29166,18 @@ ${xml}`
|
|
|
28986
29166
|
...entry.coordinatePointList,
|
|
28987
29167
|
value: { ...entry.coordinatePointList.value }
|
|
28988
29168
|
}
|
|
29169
|
+
} : {},
|
|
29170
|
+
...entry.floodlightStatus ? {
|
|
29171
|
+
floodlightStatus: {
|
|
29172
|
+
...entry.floodlightStatus,
|
|
29173
|
+
value: { ...entry.floodlightStatus.value }
|
|
29174
|
+
}
|
|
29175
|
+
} : {},
|
|
29176
|
+
...entry.sirenStatus ? {
|
|
29177
|
+
sirenStatus: {
|
|
29178
|
+
...entry.sirenStatus,
|
|
29179
|
+
value: { ...entry.sirenStatus.value }
|
|
29180
|
+
}
|
|
28989
29181
|
} : {}
|
|
28990
29182
|
});
|
|
28991
29183
|
}
|
|
@@ -29009,6 +29201,29 @@ ${xml}`
|
|
|
29009
29201
|
getCoordinatePointListFromPushCache(channel = 0) {
|
|
29010
29202
|
return this.settingsPushCache.get(channel)?.coordinatePointList;
|
|
29011
29203
|
}
|
|
29204
|
+
/**
|
|
29205
|
+
* Last cmd_id 291 (FloodlightStatusList) push observed for the channel.
|
|
29206
|
+
* The camera emits this whenever the floodlight transitions on/off,
|
|
29207
|
+
* including the auto-off after the FloodlightManual duration. This is
|
|
29208
|
+
* the only reliable source for the current manual state because cmd 289
|
|
29209
|
+
* only returns the FloodlightTask config.
|
|
29210
|
+
*
|
|
29211
|
+
* Returns undefined when no push has been received yet.
|
|
29212
|
+
*/
|
|
29213
|
+
getCachedFloodlightStatus(channel = 0) {
|
|
29214
|
+
return this.settingsPushCache.get(channel)?.floodlightStatus;
|
|
29215
|
+
}
|
|
29216
|
+
/**
|
|
29217
|
+
* Last cmd_id 547 (SirenStatusList) push observed for the channel.
|
|
29218
|
+
* Captures the actual on/off transitions including the firmware's
|
|
29219
|
+
* built-in auto-off after the siren playback duration expires —
|
|
29220
|
+
* polling cmd 547 alone can race that auto-off.
|
|
29221
|
+
*
|
|
29222
|
+
* Returns undefined when no push has been received yet.
|
|
29223
|
+
*/
|
|
29224
|
+
getCachedSirenStatus(channel = 0) {
|
|
29225
|
+
return this.settingsPushCache.get(channel)?.sirenStatus;
|
|
29226
|
+
}
|
|
29012
29227
|
// --------------------
|
|
29013
29228
|
// PCAP-derived settings getters (typed wrappers)
|
|
29014
29229
|
// --------------------
|
|
@@ -29706,10 +29921,12 @@ ${xml}`
|
|
|
29706
29921
|
const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
|
|
29707
29922
|
const attachmentType = params.attachmentType ?? "picture";
|
|
29708
29923
|
const interval = params.interval ?? 30;
|
|
29924
|
+
const rawUser = params.authUsername ?? recipient;
|
|
29925
|
+
const wireUser = rawUser.includes("@") ? rawUser : `${rawUser}@${domain}`;
|
|
29709
29926
|
const emailPatch = {
|
|
29710
29927
|
smtpServer: params.managerHost,
|
|
29711
29928
|
smtpPort: port,
|
|
29712
|
-
userName:
|
|
29929
|
+
userName: wireUser,
|
|
29713
29930
|
password: params.authPassword ?? "",
|
|
29714
29931
|
address1: recipient,
|
|
29715
29932
|
address2: "",
|
|
@@ -30721,16 +30938,16 @@ ${scheduleItems}
|
|
|
30721
30938
|
const logger = params.logger ?? this.logger;
|
|
30722
30939
|
const hlsSegmentDuration = params.hlsSegmentDuration ?? 4;
|
|
30723
30940
|
const os2 = await import("os");
|
|
30724
|
-
const
|
|
30725
|
-
const
|
|
30941
|
+
const path7 = await import("path");
|
|
30942
|
+
const fs7 = await import("fs/promises");
|
|
30726
30943
|
const crypto3 = await import("crypto");
|
|
30727
|
-
const tempDir =
|
|
30944
|
+
const tempDir = path7.join(
|
|
30728
30945
|
os2.tmpdir(),
|
|
30729
30946
|
`reolink-hls-${crypto3.randomBytes(8).toString("hex")}`
|
|
30730
30947
|
);
|
|
30731
|
-
await
|
|
30732
|
-
const playlistPath =
|
|
30733
|
-
const segmentPattern =
|
|
30948
|
+
await fs7.mkdir(tempDir, { recursive: true });
|
|
30949
|
+
const playlistPath = path7.join(tempDir, "playlist.m3u8");
|
|
30950
|
+
const segmentPattern = path7.join(tempDir, "segment_%03d.ts");
|
|
30734
30951
|
const parsed = parseRecordingFileName(params.fileName);
|
|
30735
30952
|
const durationMs = parsed?.durationMs ?? 3e5;
|
|
30736
30953
|
const fps = parsed?.framerate && parsed.framerate > 0 ? parsed.framerate : 15;
|
|
@@ -30767,13 +30984,13 @@ ${scheduleItems}
|
|
|
30767
30984
|
const segments = /* @__PURE__ */ new Map();
|
|
30768
30985
|
const startSegmentWatcher = () => {
|
|
30769
30986
|
if (segmentWatcher || !readyResolve) return;
|
|
30770
|
-
const firstSegmentPath =
|
|
30987
|
+
const firstSegmentPath = path7.join(tempDir, "segment_000.ts");
|
|
30771
30988
|
let checkCount = 0;
|
|
30772
30989
|
const maxChecks = Math.ceil((hlsSegmentDuration + 2) * 10);
|
|
30773
30990
|
segmentWatcher = setInterval(async () => {
|
|
30774
30991
|
checkCount++;
|
|
30775
30992
|
try {
|
|
30776
|
-
const stats = await
|
|
30993
|
+
const stats = await fs7.stat(firstSegmentPath);
|
|
30777
30994
|
if (stats.size > 256) {
|
|
30778
30995
|
if (segmentWatcher) {
|
|
30779
30996
|
clearInterval(segmentWatcher);
|
|
@@ -30915,12 +31132,12 @@ ${scheduleItems}
|
|
|
30915
31132
|
]);
|
|
30916
31133
|
setTimeout(async () => {
|
|
30917
31134
|
try {
|
|
30918
|
-
const files = await
|
|
31135
|
+
const files = await fs7.readdir(tempDir);
|
|
30919
31136
|
for (const file of files) {
|
|
30920
|
-
await
|
|
31137
|
+
await fs7.unlink(path7.join(tempDir, file)).catch(() => {
|
|
30921
31138
|
});
|
|
30922
31139
|
}
|
|
30923
|
-
await
|
|
31140
|
+
await fs7.rmdir(tempDir).catch(() => {
|
|
30924
31141
|
});
|
|
30925
31142
|
} catch {
|
|
30926
31143
|
}
|
|
@@ -30996,7 +31213,7 @@ ${scheduleItems}
|
|
|
30996
31213
|
}
|
|
30997
31214
|
try {
|
|
30998
31215
|
const { readFileSync } = require("fs");
|
|
30999
|
-
const segmentPath =
|
|
31216
|
+
const segmentPath = path7.join(tempDir, name);
|
|
31000
31217
|
const data = readFileSync(segmentPath);
|
|
31001
31218
|
segments.set(name, data);
|
|
31002
31219
|
return data;
|
|
@@ -34169,7 +34386,7 @@ init_BaichuanVideoStream();
|
|
|
34169
34386
|
// src/multifocal/compositeStream.ts
|
|
34170
34387
|
var import_node_child_process7 = require("child_process");
|
|
34171
34388
|
var import_node_crypto5 = require("crypto");
|
|
34172
|
-
var
|
|
34389
|
+
var import_node_events6 = require("events");
|
|
34173
34390
|
function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pipHeight, margin) {
|
|
34174
34391
|
const pipW = Math.floor(pipWidth);
|
|
34175
34392
|
const pipH = Math.floor(pipHeight);
|
|
@@ -34197,7 +34414,7 @@ function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pip
|
|
|
34197
34414
|
return { x: m, y: m };
|
|
34198
34415
|
}
|
|
34199
34416
|
}
|
|
34200
|
-
var CompositeStream = class extends
|
|
34417
|
+
var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
34201
34418
|
options;
|
|
34202
34419
|
widerStream = null;
|
|
34203
34420
|
teleStream = null;
|
|
@@ -36620,7 +36837,7 @@ async function createReplayHttpServer(options) {
|
|
|
36620
36837
|
init_BaichuanVideoStream();
|
|
36621
36838
|
|
|
36622
36839
|
// src/baichuan/stream/Go2rtcTcpServer.ts
|
|
36623
|
-
var
|
|
36840
|
+
var import_node_events7 = require("events");
|
|
36624
36841
|
var net4 = __toESM(require("net"), 1);
|
|
36625
36842
|
init_H264Converter();
|
|
36626
36843
|
init_H265Converter();
|
|
@@ -36732,7 +36949,7 @@ var NativeStreamFanout2 = class {
|
|
|
36732
36949
|
this.pumpPromise = null;
|
|
36733
36950
|
}
|
|
36734
36951
|
};
|
|
36735
|
-
var Go2rtcTcpServer = class _Go2rtcTcpServer extends
|
|
36952
|
+
var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events7.EventEmitter {
|
|
36736
36953
|
api;
|
|
36737
36954
|
channel;
|
|
36738
36955
|
profile;
|
|
@@ -37423,7 +37640,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events6.EventEm
|
|
|
37423
37640
|
};
|
|
37424
37641
|
|
|
37425
37642
|
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
37426
|
-
var
|
|
37643
|
+
var import_node_events8 = require("events");
|
|
37427
37644
|
var import_node_child_process9 = require("child_process");
|
|
37428
37645
|
var http4 = __toESM(require("http"), 1);
|
|
37429
37646
|
var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
|
|
@@ -37470,7 +37687,7 @@ function isH264KeyframeFromAnnexB(annexB) {
|
|
|
37470
37687
|
}
|
|
37471
37688
|
return false;
|
|
37472
37689
|
}
|
|
37473
|
-
var BaichuanHttpStreamServer = class extends
|
|
37690
|
+
var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
|
|
37474
37691
|
videoStream;
|
|
37475
37692
|
listenPort;
|
|
37476
37693
|
path;
|
|
@@ -37742,15 +37959,15 @@ var BaichuanHttpStreamServer = class extends import_node_events7.EventEmitter {
|
|
|
37742
37959
|
};
|
|
37743
37960
|
|
|
37744
37961
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
37745
|
-
var
|
|
37962
|
+
var import_node_events10 = require("events");
|
|
37746
37963
|
var http5 = __toESM(require("http"), 1);
|
|
37747
37964
|
|
|
37748
37965
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
37749
|
-
var
|
|
37966
|
+
var import_node_events9 = require("events");
|
|
37750
37967
|
var import_node_child_process10 = require("child_process");
|
|
37751
37968
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
37752
37969
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
37753
|
-
var MjpegTransformer = class extends
|
|
37970
|
+
var MjpegTransformer = class extends import_node_events9.EventEmitter {
|
|
37754
37971
|
options;
|
|
37755
37972
|
ffmpeg = null;
|
|
37756
37973
|
started = false;
|
|
@@ -37949,7 +38166,7 @@ Content-Length: ${frame.length}\r
|
|
|
37949
38166
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
37950
38167
|
init_H264Converter();
|
|
37951
38168
|
init_H265Converter();
|
|
37952
|
-
var BaichuanMjpegServer = class extends
|
|
38169
|
+
var BaichuanMjpegServer = class extends import_node_events10.EventEmitter {
|
|
37953
38170
|
options;
|
|
37954
38171
|
clients = /* @__PURE__ */ new Map();
|
|
37955
38172
|
httpServer = null;
|
|
@@ -37971,9 +38188,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
37971
38188
|
this.started = true;
|
|
37972
38189
|
const port = this.options.port ?? 8080;
|
|
37973
38190
|
const host = this.options.host ?? "0.0.0.0";
|
|
37974
|
-
const
|
|
38191
|
+
const path7 = this.options.path ?? "/mjpeg";
|
|
37975
38192
|
this.httpServer = http5.createServer((req, res) => {
|
|
37976
|
-
this.handleRequest(req, res,
|
|
38193
|
+
this.handleRequest(req, res, path7);
|
|
37977
38194
|
});
|
|
37978
38195
|
return new Promise((resolve, reject) => {
|
|
37979
38196
|
this.httpServer.on("error", (err) => {
|
|
@@ -37983,9 +38200,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
37983
38200
|
this.httpServer.listen(port, host, () => {
|
|
37984
38201
|
this.log(
|
|
37985
38202
|
"info",
|
|
37986
|
-
`MJPEG server started on http://${host}:${port}${
|
|
38203
|
+
`MJPEG server started on http://${host}:${port}${path7}`
|
|
37987
38204
|
);
|
|
37988
|
-
this.emit("started", { host, port, path:
|
|
38205
|
+
this.emit("started", { host, port, path: path7 });
|
|
37989
38206
|
resolve();
|
|
37990
38207
|
});
|
|
37991
38208
|
});
|
|
@@ -38230,14 +38447,14 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
38230
38447
|
};
|
|
38231
38448
|
|
|
38232
38449
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
38233
|
-
var
|
|
38450
|
+
var import_node_events12 = require("events");
|
|
38234
38451
|
init_BcMediaAnnexBDecoder();
|
|
38235
38452
|
|
|
38236
38453
|
// src/baichuan/stream/AacToOpusTranscoder.ts
|
|
38237
38454
|
var import_node_child_process11 = require("child_process");
|
|
38238
38455
|
var import_node_dgram3 = require("dgram");
|
|
38239
|
-
var
|
|
38240
|
-
var AacToOpusTranscoder = class extends
|
|
38456
|
+
var import_node_events11 = require("events");
|
|
38457
|
+
var AacToOpusTranscoder = class extends import_node_events11.EventEmitter {
|
|
38241
38458
|
opts;
|
|
38242
38459
|
socket = null;
|
|
38243
38460
|
ffmpeg = null;
|
|
@@ -38454,7 +38671,7 @@ function getH264NalType(nalUnit) {
|
|
|
38454
38671
|
function getH265NalType2(nalUnit) {
|
|
38455
38672
|
return nalUnit[0] >> 1 & 63;
|
|
38456
38673
|
}
|
|
38457
|
-
var BaichuanWebRTCServer = class extends
|
|
38674
|
+
var BaichuanWebRTCServer = class extends import_node_events12.EventEmitter {
|
|
38458
38675
|
options;
|
|
38459
38676
|
sessions = /* @__PURE__ */ new Map();
|
|
38460
38677
|
sessionIdCounter = 0;
|
|
@@ -39446,7 +39663,7 @@ Error: ${err}`
|
|
|
39446
39663
|
};
|
|
39447
39664
|
|
|
39448
39665
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
39449
|
-
var
|
|
39666
|
+
var import_node_events13 = require("events");
|
|
39450
39667
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
39451
39668
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
39452
39669
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
@@ -39526,7 +39743,7 @@ function getNalTypes(codec, annexB) {
|
|
|
39526
39743
|
}
|
|
39527
39744
|
});
|
|
39528
39745
|
}
|
|
39529
|
-
var BaichuanHlsServer = class extends
|
|
39746
|
+
var BaichuanHlsServer = class extends import_node_events13.EventEmitter {
|
|
39530
39747
|
api;
|
|
39531
39748
|
channel;
|
|
39532
39749
|
profile;
|
|
@@ -40550,10 +40767,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40550
40767
|
}
|
|
40551
40768
|
|
|
40552
40769
|
// src/multifocal/compositeRtspServer.ts
|
|
40553
|
-
var
|
|
40770
|
+
var import_node_events14 = require("events");
|
|
40554
40771
|
var import_node_child_process13 = require("child_process");
|
|
40555
40772
|
var net5 = __toESM(require("net"), 1);
|
|
40556
|
-
var CompositeRtspServer = class extends
|
|
40773
|
+
var CompositeRtspServer = class extends import_node_events14.EventEmitter {
|
|
40557
40774
|
options;
|
|
40558
40775
|
compositeStream = null;
|
|
40559
40776
|
rtspServer = null;
|
|
@@ -40846,6 +41063,272 @@ function base64DecodeToBytes(b64) {
|
|
|
40846
41063
|
}
|
|
40847
41064
|
return out.subarray(0, outIdx);
|
|
40848
41065
|
}
|
|
41066
|
+
|
|
41067
|
+
// src/emailPush/server.ts
|
|
41068
|
+
var import_smtp_server = require("smtp-server");
|
|
41069
|
+
var import_mailparser = require("mailparser");
|
|
41070
|
+
|
|
41071
|
+
// src/emailPush/tls.ts
|
|
41072
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
41073
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
41074
|
+
async function loadEmailPushTls(params) {
|
|
41075
|
+
const certPath = import_node_path4.default.join(params.dir, "cert.pem");
|
|
41076
|
+
const keyPath = import_node_path4.default.join(params.dir, "key.pem");
|
|
41077
|
+
const warn = params.warn ?? ((m) => console.warn(`[email-push-tls] ${m}`));
|
|
41078
|
+
if (!import_node_fs2.default.existsSync(certPath) || !import_node_fs2.default.existsSync(keyPath)) {
|
|
41079
|
+
warn(
|
|
41080
|
+
`Email-push TLS requested but ${certPath} or ${keyPath} is missing. Place a PEM cert+key under that directory to enable STARTTLS, or disable TLS in the consumer config to silence this warning.`
|
|
41081
|
+
);
|
|
41082
|
+
return void 0;
|
|
41083
|
+
}
|
|
41084
|
+
return {
|
|
41085
|
+
cert: import_node_fs2.default.readFileSync(certPath),
|
|
41086
|
+
key: import_node_fs2.default.readFileSync(keyPath)
|
|
41087
|
+
};
|
|
41088
|
+
}
|
|
41089
|
+
|
|
41090
|
+
// src/emailPush/server.ts
|
|
41091
|
+
async function defaultLoadTls(dir, warn) {
|
|
41092
|
+
return loadEmailPushTls({ dir, warn });
|
|
41093
|
+
}
|
|
41094
|
+
function classifyMessage(parsed) {
|
|
41095
|
+
const haystack = `${parsed.subject ?? ""} ${parsed.text ?? ""}`.toLowerCase();
|
|
41096
|
+
if (/person|people|human/.test(haystack)) return "people";
|
|
41097
|
+
if (/vehicle|car|truck/.test(haystack)) return "vehicle";
|
|
41098
|
+
if (/dog[_\s-]?cat|pet|animal/.test(haystack)) return "animal";
|
|
41099
|
+
if (/face/.test(haystack)) return "face";
|
|
41100
|
+
if (/package|parcel/.test(haystack)) return "package";
|
|
41101
|
+
if (/doorbell|ring(?:ing)?\s+button/.test(haystack)) return "doorbell";
|
|
41102
|
+
if (/motion|alarm|alert|detect/.test(haystack)) return "motion";
|
|
41103
|
+
return "other";
|
|
41104
|
+
}
|
|
41105
|
+
function getCameraEmailAddress(cameraId, domain) {
|
|
41106
|
+
return `cam-${cameraId}@${domain}`;
|
|
41107
|
+
}
|
|
41108
|
+
function createEmailPushServer(params) {
|
|
41109
|
+
const log = {
|
|
41110
|
+
debug: params.logger?.debug ?? (() => {
|
|
41111
|
+
}),
|
|
41112
|
+
info: params.logger?.info ?? (() => {
|
|
41113
|
+
}),
|
|
41114
|
+
warn: params.logger?.warn ?? (() => {
|
|
41115
|
+
}),
|
|
41116
|
+
error: params.logger?.error ?? (() => {
|
|
41117
|
+
})
|
|
41118
|
+
};
|
|
41119
|
+
let config = params.config;
|
|
41120
|
+
let server;
|
|
41121
|
+
let status = buildInitialStatus(config);
|
|
41122
|
+
function parseRecipient(rcpt) {
|
|
41123
|
+
const [local, domain] = rcpt.toLowerCase().split("@");
|
|
41124
|
+
if (!local || !domain) return void 0;
|
|
41125
|
+
return { local, domain };
|
|
41126
|
+
}
|
|
41127
|
+
function resolveCameraIdFromRecipient(rcpt) {
|
|
41128
|
+
const parsed = parseRecipient(rcpt);
|
|
41129
|
+
if (!parsed) return void 0;
|
|
41130
|
+
if (parsed.domain !== config.domain.toLowerCase()) return void 0;
|
|
41131
|
+
const match = parsed.local.match(/^cam-(.+)$/);
|
|
41132
|
+
if (!match || !match[1]) return void 0;
|
|
41133
|
+
return params.cameraResolver(match[1]);
|
|
41134
|
+
}
|
|
41135
|
+
async function handleIncomingMessage(cameraId, recipient, raw) {
|
|
41136
|
+
let parsed;
|
|
41137
|
+
try {
|
|
41138
|
+
parsed = await (0, import_mailparser.simpleParser)(raw);
|
|
41139
|
+
} catch (err) {
|
|
41140
|
+
log.warn(
|
|
41141
|
+
`Failed to parse mail for ${cameraId}: ${err instanceof Error ? err.message : err}`
|
|
41142
|
+
);
|
|
41143
|
+
status.messagesRejected++;
|
|
41144
|
+
return;
|
|
41145
|
+
}
|
|
41146
|
+
const receivedAtMs = Date.now();
|
|
41147
|
+
const event = {
|
|
41148
|
+
cameraId,
|
|
41149
|
+
recipient,
|
|
41150
|
+
inferredType: classifyMessage(parsed),
|
|
41151
|
+
receivedAtMs,
|
|
41152
|
+
subject: parsed.subject ?? "",
|
|
41153
|
+
from: typeof parsed.from === "object" && parsed.from !== null && "text" in parsed.from ? String(parsed.from.text) : "",
|
|
41154
|
+
bodyExcerpt: (parsed.text ?? "").slice(0, 500)
|
|
41155
|
+
};
|
|
41156
|
+
status.messagesAccepted++;
|
|
41157
|
+
log.info(
|
|
41158
|
+
`Email push for camera=${cameraId} type=${event.inferredType} subject="${event.subject.slice(0, 80)}"`
|
|
41159
|
+
);
|
|
41160
|
+
emitEmailPushEvent(event);
|
|
41161
|
+
}
|
|
41162
|
+
async function start() {
|
|
41163
|
+
if (server) {
|
|
41164
|
+
log.debug("startEmailPushServer called but server already running");
|
|
41165
|
+
return;
|
|
41166
|
+
}
|
|
41167
|
+
const tlsOptions = config.tls ? await (params.loadTls ?? defaultLoadTls)(
|
|
41168
|
+
config.tlsDir ?? "./email-push-tls",
|
|
41169
|
+
(m) => log.warn(m)
|
|
41170
|
+
) : void 0;
|
|
41171
|
+
status = buildInitialStatus(config);
|
|
41172
|
+
const next = new import_smtp_server.SMTPServer({
|
|
41173
|
+
authOptional: !config.requireAuth,
|
|
41174
|
+
disabledCommands: config.requireAuth ? [] : ["AUTH"],
|
|
41175
|
+
allowInsecureAuth: !config.tls,
|
|
41176
|
+
size: config.maxMessageBytes,
|
|
41177
|
+
logger: {
|
|
41178
|
+
info: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
41179
|
+
debug: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
41180
|
+
error: (...args) => log.warn(`[smtp] ${args.map((a) => String(a)).join(" ")}`)
|
|
41181
|
+
},
|
|
41182
|
+
...tlsOptions ? { secure: false, ...tlsOptions } : {},
|
|
41183
|
+
onConnect(session, callback) {
|
|
41184
|
+
log.info(
|
|
41185
|
+
`SMTP connect from ${session.remoteAddress} (sessionId=${session.id})`
|
|
41186
|
+
);
|
|
41187
|
+
callback();
|
|
41188
|
+
},
|
|
41189
|
+
onClose(session) {
|
|
41190
|
+
log.debug(
|
|
41191
|
+
`SMTP close ${session.remoteAddress} (sessionId=${session.id})`
|
|
41192
|
+
);
|
|
41193
|
+
},
|
|
41194
|
+
onMailFrom(address, session, callback) {
|
|
41195
|
+
log.info(
|
|
41196
|
+
`SMTP MAIL FROM ${address.address} (sessionId=${session.id})`
|
|
41197
|
+
);
|
|
41198
|
+
callback();
|
|
41199
|
+
},
|
|
41200
|
+
onAuth(auth, session, callback) {
|
|
41201
|
+
const expectedUser = config.authUsername;
|
|
41202
|
+
const expectedPass = config.authPassword;
|
|
41203
|
+
if (!expectedUser || !expectedPass) {
|
|
41204
|
+
log.warn(
|
|
41205
|
+
`SMTP AUTH rejected from ${session.remoteAddress} (sessionId=${session.id}): server has no credentials configured`
|
|
41206
|
+
);
|
|
41207
|
+
return callback(new Error("Email push auth not configured"));
|
|
41208
|
+
}
|
|
41209
|
+
const stripDomain = (u) => {
|
|
41210
|
+
const at = u.lastIndexOf("@");
|
|
41211
|
+
if (at < 0) return u;
|
|
41212
|
+
const local = u.slice(0, at);
|
|
41213
|
+
const domain = u.slice(at + 1).toLowerCase();
|
|
41214
|
+
return domain === config.domain.toLowerCase() ? local : u;
|
|
41215
|
+
};
|
|
41216
|
+
const triedUserNorm = stripDomain(auth.username ?? "");
|
|
41217
|
+
const expectedUserNorm = stripDomain(expectedUser);
|
|
41218
|
+
if (triedUserNorm === expectedUserNorm && auth.password === expectedPass) {
|
|
41219
|
+
log.info(
|
|
41220
|
+
`SMTP AUTH ok method=${auth.method} user=${auth.username} (sessionId=${session.id})`
|
|
41221
|
+
);
|
|
41222
|
+
return callback(null, { user: auth.username });
|
|
41223
|
+
}
|
|
41224
|
+
log.warn(
|
|
41225
|
+
`SMTP AUTH FAIL method=${auth.method} from=${session.remoteAddress} triedUser="${auth.username}" expectedUser="${expectedUser}" triedPasswordLen=${auth.password?.length ?? 0} (sessionId=${session.id})`
|
|
41226
|
+
);
|
|
41227
|
+
return callback(new Error("Invalid username or password"));
|
|
41228
|
+
},
|
|
41229
|
+
onRcptTo(address, _session, callback) {
|
|
41230
|
+
const cameraId = resolveCameraIdFromRecipient(address.address);
|
|
41231
|
+
if (!cameraId) {
|
|
41232
|
+
status.messagesRejected++;
|
|
41233
|
+
return callback(
|
|
41234
|
+
new Error(
|
|
41235
|
+
`Unknown recipient ${address.address} (not registered)`
|
|
41236
|
+
)
|
|
41237
|
+
);
|
|
41238
|
+
}
|
|
41239
|
+
callback();
|
|
41240
|
+
},
|
|
41241
|
+
onData(stream, session, callback) {
|
|
41242
|
+
const chunks = [];
|
|
41243
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
41244
|
+
stream.on("end", () => {
|
|
41245
|
+
const recipients = session.envelope.rcptTo?.map((r) => r.address) ?? [];
|
|
41246
|
+
const buffer = Buffer.concat(chunks);
|
|
41247
|
+
const matched = recipients.map((r) => ({
|
|
41248
|
+
recipient: r,
|
|
41249
|
+
cameraId: resolveCameraIdFromRecipient(r)
|
|
41250
|
+
})).filter(
|
|
41251
|
+
(m) => Boolean(m.cameraId)
|
|
41252
|
+
);
|
|
41253
|
+
if (matched.length === 0) {
|
|
41254
|
+
status.messagesRejected++;
|
|
41255
|
+
return callback(new Error("No recognised recipients"));
|
|
41256
|
+
}
|
|
41257
|
+
Promise.all(
|
|
41258
|
+
matched.map(
|
|
41259
|
+
(m) => handleIncomingMessage(m.cameraId, m.recipient, buffer)
|
|
41260
|
+
)
|
|
41261
|
+
).then(() => callback()).catch((err) => {
|
|
41262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41263
|
+
log.error(`Email push pipeline error: ${msg}`);
|
|
41264
|
+
status.lastErrorMessage = msg;
|
|
41265
|
+
callback(new Error(msg));
|
|
41266
|
+
});
|
|
41267
|
+
});
|
|
41268
|
+
stream.on("error", (err) => {
|
|
41269
|
+
log.warn(`SMTP stream error: ${err.message}`);
|
|
41270
|
+
callback(err);
|
|
41271
|
+
});
|
|
41272
|
+
}
|
|
41273
|
+
});
|
|
41274
|
+
next.on("error", (err) => {
|
|
41275
|
+
status.lastErrorMessage = err.message;
|
|
41276
|
+
log.error(`Email push server error: ${err.message}`);
|
|
41277
|
+
});
|
|
41278
|
+
await new Promise((resolve, reject) => {
|
|
41279
|
+
next.listen(config.port, config.bindHost, () => {
|
|
41280
|
+
status.running = true;
|
|
41281
|
+
status.startedAtMs = Date.now();
|
|
41282
|
+
log.info(
|
|
41283
|
+
`Email push SMTP listening on ${config.bindHost}:${config.port} (domain=${config.domain}, auth=${config.requireAuth}, tls=${config.tls})`
|
|
41284
|
+
);
|
|
41285
|
+
resolve();
|
|
41286
|
+
});
|
|
41287
|
+
next.once("error", reject);
|
|
41288
|
+
});
|
|
41289
|
+
server = next;
|
|
41290
|
+
}
|
|
41291
|
+
async function stop() {
|
|
41292
|
+
if (!server) return;
|
|
41293
|
+
const active = server;
|
|
41294
|
+
server = void 0;
|
|
41295
|
+
await new Promise((resolve) => {
|
|
41296
|
+
active.close(() => resolve());
|
|
41297
|
+
});
|
|
41298
|
+
status.running = false;
|
|
41299
|
+
log.info("Email push SMTP server stopped");
|
|
41300
|
+
}
|
|
41301
|
+
async function restart() {
|
|
41302
|
+
await stop();
|
|
41303
|
+
await start();
|
|
41304
|
+
}
|
|
41305
|
+
return {
|
|
41306
|
+
start,
|
|
41307
|
+
stop,
|
|
41308
|
+
restart,
|
|
41309
|
+
getStatus() {
|
|
41310
|
+
return { ...status };
|
|
41311
|
+
},
|
|
41312
|
+
updateConfig(next) {
|
|
41313
|
+
config = next;
|
|
41314
|
+
}
|
|
41315
|
+
};
|
|
41316
|
+
}
|
|
41317
|
+
function buildInitialStatus(config) {
|
|
41318
|
+
return {
|
|
41319
|
+
enabled: true,
|
|
41320
|
+
running: false,
|
|
41321
|
+
port: config.port,
|
|
41322
|
+
bindHost: config.bindHost,
|
|
41323
|
+
domain: config.domain,
|
|
41324
|
+
requireAuth: config.requireAuth,
|
|
41325
|
+
tls: config.tls,
|
|
41326
|
+
messagesAccepted: 0,
|
|
41327
|
+
messagesRejected: 0,
|
|
41328
|
+
startedAtMs: void 0,
|
|
41329
|
+
lastErrorMessage: void 0
|
|
41330
|
+
};
|
|
41331
|
+
}
|
|
40849
41332
|
// Annotate the CommonJS export names for ESM import in node:
|
|
40850
41333
|
0 && (module.exports = {
|
|
40851
41334
|
AesStreamDecryptor,
|
|
@@ -41019,6 +41502,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41019
41502
|
ReolinkCgiApi,
|
|
41020
41503
|
ReolinkHttpClient,
|
|
41021
41504
|
Rfc4571Muxer,
|
|
41505
|
+
_resetEmailPushBusForTests,
|
|
41022
41506
|
abilitiesHasAny,
|
|
41023
41507
|
aesDecrypt,
|
|
41024
41508
|
aesEncrypt,
|
|
@@ -41064,6 +41548,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41064
41548
|
createBaichuanEndpointsServer,
|
|
41065
41549
|
createDebugGateLogger,
|
|
41066
41550
|
createDiagnosticsBundle,
|
|
41551
|
+
createEmailPushServer,
|
|
41067
41552
|
createLogger,
|
|
41068
41553
|
createMjpegBoundary,
|
|
41069
41554
|
createNativeStream,
|
|
@@ -41091,6 +41576,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41091
41576
|
discoverViaTcpPortScan,
|
|
41092
41577
|
discoverViaUdpBroadcast,
|
|
41093
41578
|
discoverViaUdpDirect,
|
|
41579
|
+
emitEmailPushEvent,
|
|
41094
41580
|
encodeHeader,
|
|
41095
41581
|
encodeMotionScopeBitmap,
|
|
41096
41582
|
encodeMotionSensitivityListXml,
|
|
@@ -41107,10 +41593,14 @@ function base64DecodeToBytes(b64) {
|
|
|
41107
41593
|
flattenAbilitiesForChannel,
|
|
41108
41594
|
formatMjpegFrame,
|
|
41109
41595
|
fullCoverageScope,
|
|
41596
|
+
getCameraEmailAddress,
|
|
41110
41597
|
getConstructedVideoStreamOptions,
|
|
41598
|
+
getEmailPushCameraResolver,
|
|
41111
41599
|
getGlobalLogger,
|
|
41112
41600
|
getH265NalType,
|
|
41601
|
+
getLastEmailPushEvent,
|
|
41113
41602
|
getMjpegContentType,
|
|
41603
|
+
getRecentEmailPushEvents,
|
|
41114
41604
|
getSupportItemForChannel,
|
|
41115
41605
|
getVideoStream,
|
|
41116
41606
|
getVideoclipClientInfo,
|
|
@@ -41125,12 +41615,15 @@ function base64DecodeToBytes(b64) {
|
|
|
41125
41615
|
isTcpFailureThatShouldFallbackToUdp,
|
|
41126
41616
|
isValidH264AnnexBAccessUnit,
|
|
41127
41617
|
isValidH265AnnexBAccessUnit,
|
|
41618
|
+
loadEmailPushTls,
|
|
41619
|
+
mapEmailPushInferredType,
|
|
41128
41620
|
maskUid,
|
|
41129
41621
|
md5HexUpper,
|
|
41130
41622
|
md5StrModern,
|
|
41131
41623
|
normalizeDayNightMode,
|
|
41132
41624
|
normalizeOpenClose,
|
|
41133
41625
|
normalizeUid,
|
|
41626
|
+
onEmailPushEvent,
|
|
41134
41627
|
packetizeAacAdtsFrame,
|
|
41135
41628
|
packetizeAacRawFrame,
|
|
41136
41629
|
packetizeH264,
|
|
@@ -41148,6 +41641,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41148
41641
|
runMultifocalDiagnosticsConsecutively,
|
|
41149
41642
|
sampleStreams,
|
|
41150
41643
|
sanitizeFixtureData,
|
|
41644
|
+
setEmailPushCameraResolver,
|
|
41151
41645
|
setGlobalLogger,
|
|
41152
41646
|
splitAnnexBToNalPayloads,
|
|
41153
41647
|
splitAnnexBToNals,
|