@apocaliss92/nodelink-js 0.4.26 → 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.
@@ -8114,6 +8114,21 @@ async function* createNativeStream(api, channel, profile, options) {
8114
8114
  let audioSampleRate = null;
8115
8115
  let streamStarted = false;
8116
8116
  let closed = false;
8117
+ const signal = options?.signal;
8118
+ let sleepResolve = null;
8119
+ let sleepTimer = null;
8120
+ const clearSleepTimer = () => {
8121
+ if (sleepTimer) {
8122
+ clearTimeout(sleepTimer);
8123
+ sleepTimer = null;
8124
+ }
8125
+ };
8126
+ const handleAbort = () => {
8127
+ clearSleepTimer();
8128
+ const r = sleepResolve;
8129
+ sleepResolve = null;
8130
+ r?.();
8131
+ };
8117
8132
  const onError = (_error) => {
8118
8133
  closed = true;
8119
8134
  api.logger?.warn?.(
@@ -8211,7 +8226,13 @@ async function* createNativeStream(api, channel, profile, options) {
8211
8226
  }
8212
8227
  });
8213
8228
  streamStarted = true;
8214
- const signal = options?.signal;
8229
+ if (signal) {
8230
+ if (signal.aborted) {
8231
+ closed = true;
8232
+ } else {
8233
+ signal.addEventListener("abort", handleAbort);
8234
+ }
8235
+ }
8215
8236
  while (!closed && !signal?.aborted) {
8216
8237
  if (frameQueue.length > 0) {
8217
8238
  const frame = frameQueue.shift();
@@ -8219,31 +8240,30 @@ async function* createNativeStream(api, channel, profile, options) {
8219
8240
  } else {
8220
8241
  await new Promise((resolve) => {
8221
8242
  frameResolve = resolve;
8222
- const timer = setTimeout(() => {
8243
+ sleepResolve = resolve;
8244
+ sleepTimer = setTimeout(() => {
8245
+ sleepTimer = null;
8246
+ if (sleepResolve === resolve) sleepResolve = null;
8223
8247
  if (frameResolve === resolve) {
8224
8248
  frameResolve = null;
8225
8249
  resolve();
8226
8250
  }
8227
8251
  }, 1e3);
8228
- if (signal) {
8229
- const onAbort = () => {
8230
- clearTimeout(timer);
8231
- if (frameResolve === resolve) frameResolve = null;
8232
- resolve();
8233
- };
8234
- if (signal.aborted) {
8235
- clearTimeout(timer);
8236
- frameResolve = null;
8237
- resolve();
8238
- } else {
8239
- signal.addEventListener("abort", onAbort, { once: true });
8240
- }
8252
+ if (signal?.aborted) {
8253
+ clearSleepTimer();
8254
+ sleepResolve = null;
8255
+ frameResolve = null;
8256
+ resolve();
8241
8257
  }
8242
8258
  });
8259
+ sleepResolve = null;
8260
+ clearSleepTimer();
8243
8261
  }
8244
8262
  }
8245
8263
  } finally {
8246
8264
  closed = true;
8265
+ if (signal) signal.removeEventListener("abort", handleAbort);
8266
+ clearSleepTimer();
8247
8267
  try {
8248
8268
  await videoStream.stop();
8249
8269
  } catch {
@@ -10277,12 +10297,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
10277
10297
  if (frame.videoType === "H264" || frame.videoType === "H265") {
10278
10298
  this.setFlowVideoType(frame.videoType, "native stream");
10279
10299
  }
10280
- this.flow.extractParameterSets(frame.data);
10281
- const { hasParamSets } = this.flow.getFmtp();
10282
- if (hasParamSets) {
10300
+ if (!this.flow.getFmtp().hasParamSets) {
10301
+ this.flow.extractParameterSets(frame.data);
10302
+ }
10303
+ if (this.flow.getFmtp().hasParamSets) {
10283
10304
  this.markFirstFrameReceived();
10284
10305
  }
10285
- const isKeyframe = this.isRawFrameKeyframe(frame);
10306
+ const isKeyframe = typeof frame.isKeyframe === "boolean" ? frame.isKeyframe : this.isRawFrameKeyframe(frame);
10286
10307
  this.prebuffer.push({
10287
10308
  frame: { ...frame, data: Buffer.from(frame.data) },
10288
10309
  time: Date.now(),
@@ -19033,6 +19054,29 @@ var parseSirenStatusListPushXml = (xml) => {
19033
19054
  return out;
19034
19055
  };
19035
19056
 
19057
+ // src/emailPush/bus.ts
19058
+ var import_node_events5 = require("events");
19059
+ var emitter = new import_node_events5.EventEmitter();
19060
+ function onEmailPushEvent(handler) {
19061
+ emitter.on("event", handler);
19062
+ return () => emitter.off("event", handler);
19063
+ }
19064
+ function mapEmailPushInferredType(inferred) {
19065
+ switch (inferred) {
19066
+ case "motion":
19067
+ case "doorbell":
19068
+ case "people":
19069
+ case "vehicle":
19070
+ case "animal":
19071
+ case "face":
19072
+ case "package":
19073
+ return inferred;
19074
+ case "other":
19075
+ default:
19076
+ return "motion";
19077
+ }
19078
+ }
19079
+
19036
19080
  // src/reolink/baichuan/ReolinkBaichuanApi.ts
19037
19081
  var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
19038
19082
  "Reolink Duo PoE",
@@ -19178,7 +19222,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
19178
19222
  * general socket is created, logged in, and all event/push/guard listeners
19179
19223
  * are re-attached automatically.
19180
19224
  *
19181
- * This is a **no-op** when the API is already {@link isReady}.
19225
+ * This is a **no-op** when the API is already ready (see `isReadyState()`).
19182
19226
  *
19183
19227
  * @throws If `close()` was called — the API is permanently closed and a new
19184
19228
  * instance must be created.
@@ -19259,7 +19303,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
19259
19303
  /**
19260
19304
  * Attach event, push, channelInfo, and guard listeners to the current
19261
19305
  * "general" client. Called from the constructor and from
19262
- * {@link reconnectGeneralSocket}.
19306
+ * `reconnectGeneralSocket()`.
19263
19307
  */
19264
19308
  setupGeneralClientListeners() {
19265
19309
  const client = this.client;
@@ -20848,6 +20892,40 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20848
20892
  this.econnresetStormRebootInFlight = void 0;
20849
20893
  });
20850
20894
  }
20895
+ /**
20896
+ * Bind this API instance to the global email-push bus so that incoming
20897
+ * SMTP-delivered motion / AI events for the matching camera surface on
20898
+ * this instance's standard `onSimpleEvent` channel. The consumer keeps
20899
+ * a single subscription (`onSimpleEvent`) and gets both the native
20900
+ * Baichuan push and the email-push transport on the same stream.
20901
+ *
20902
+ * - `cameraId` shorthand: match events with `event.cameraId === cameraId`.
20903
+ * - `match`: arbitrary predicate (e.g. when the consumer uses a
20904
+ * nickname-based mapping or wants to handle multiple recipients).
20905
+ *
20906
+ * Returns an `off()` handle. Safe to call repeatedly — each call
20907
+ * registers its own listener.
20908
+ */
20909
+ subscribeEmailPushEvents(params) {
20910
+ const channel = params.channel ?? 0;
20911
+ const matches = "match" in params ? params.match : (event) => event.cameraId === params.cameraId;
20912
+ const off = onEmailPushEvent((event) => {
20913
+ if (!matches(event)) return;
20914
+ this.dispatchSimpleEvent({
20915
+ type: mapEmailPushInferredType(event.inferredType),
20916
+ channel,
20917
+ timestamp: event.receivedAtMs
20918
+ });
20919
+ if (event.inferredType !== "motion" && event.inferredType !== "doorbell" && event.inferredType !== "other") {
20920
+ this.dispatchSimpleEvent({
20921
+ type: "motion",
20922
+ channel,
20923
+ timestamp: event.receivedAtMs
20924
+ });
20925
+ }
20926
+ });
20927
+ return off;
20928
+ }
20851
20929
  /**
20852
20930
  * Subscribe to minimal high-level events.
20853
20931
  * The API manages Baichuan subscribe/unsubscribe automatically.
@@ -20906,7 +20984,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20906
20984
  * Subscribe to per-frame detection events sourced from the BcMedia
20907
20985
  * `additionalHeader` block on active video streams.
20908
20986
  *
20909
- * Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
20987
+ * Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
20910
20988
  * one event fires for every I-frame / P-frame that carries an overlay block.
20911
20989
  * Coordinates are reported in normalized [0, 1] fractions of the source
20912
20990
  * frame, so the same box renders correctly on mainStream, subStream, and
@@ -20933,7 +21011,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20933
21011
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
20934
21012
  * with class label and confidence) without managing a video stream yourself.
20935
21013
  *
20936
- * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
21014
+ * Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
20937
21015
  * `(channel, profile)` tuple the API ensures the corresponding video stream
20938
21016
  * is running (the pool socket may already be shared with a regular consumer),
20939
21017
  * forwards every box-bearing `additionalHeader` to your callback, and tears
@@ -27777,7 +27855,7 @@ ${xml}`
27777
27855
  * Field meaning per stream:
27778
27856
  * - `audio` — 0/1 toggle
27779
27857
  * - `width`/`height` — resolution in pixels. Must be one of the
27780
- * resolutions returned by {@link getStreamInfoList}.
27858
+ * resolutions returned by `getStreamInfoList()`.
27781
27859
  * - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
27782
27860
  * - `frameRate` — fps. Must match the table from `getStreamInfoList`.
27783
27861
  * - `videoEncType` — `"h264"` or `"h265"`
@@ -29166,10 +29244,12 @@ ${xml}`
29166
29244
  const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
29167
29245
  const attachmentType = params.attachmentType ?? "picture";
29168
29246
  const interval = params.interval ?? 30;
29247
+ const rawUser = params.authUsername ?? recipient;
29248
+ const wireUser = rawUser.includes("@") ? rawUser : `${rawUser}@${domain}`;
29169
29249
  const emailPatch = {
29170
29250
  smtpServer: params.managerHost,
29171
29251
  smtpPort: port,
29172
- userName: params.authUsername ?? recipient,
29252
+ userName: wireUser,
29173
29253
  password: params.authPassword ?? "",
29174
29254
  address1: recipient,
29175
29255
  address2: "",