@apocaliss92/nodelink-js 0.4.26 → 0.4.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 path6 = buildRtspPath(params.channel, params.stream);
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}${path6}`;
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 path6 = `/${app}/channel${params.channel}_${streamName}.bcs`;
4849
- const u = new URL(`rtmp://${params.host}:1935${path6}`);
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
- const signal = options?.signal;
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
- const timer = setTimeout(() => {
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
- const onAbort = () => {
13762
- clearTimeout(timer);
13763
- if (frameResolve === resolve) frameResolve = null;
13764
- resolve();
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.extractParameterSets(frame.data);
15813
- const { hasParamSets: hasParamSets2 } = this.flow.getFmtp();
15814
- if (hasParamSets2) {
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(),
@@ -19668,6 +19700,60 @@ var parseSirenStatusListPushXml = (xml) => {
19668
19700
  return out;
19669
19701
  };
19670
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
+
19671
19757
  // src/reolink/baichuan/ReolinkBaichuanApi.ts
19672
19758
  var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
19673
19759
  "Reolink Duo PoE",
@@ -19813,7 +19899,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
19813
19899
  * general socket is created, logged in, and all event/push/guard listeners
19814
19900
  * are re-attached automatically.
19815
19901
  *
19816
- * This is a **no-op** when the API is already {@link isReady}.
19902
+ * This is a **no-op** when the API is already ready (see `isReadyState()`).
19817
19903
  *
19818
19904
  * @throws If `close()` was called — the API is permanently closed and a new
19819
19905
  * instance must be created.
@@ -19894,7 +19980,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
19894
19980
  /**
19895
19981
  * Attach event, push, channelInfo, and guard listeners to the current
19896
19982
  * "general" client. Called from the constructor and from
19897
- * {@link reconnectGeneralSocket}.
19983
+ * `reconnectGeneralSocket()`.
19898
19984
  */
19899
19985
  setupGeneralClientListeners() {
19900
19986
  const client = this.client;
@@ -21483,6 +21569,40 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21483
21569
  this.econnresetStormRebootInFlight = void 0;
21484
21570
  });
21485
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
+ }
21486
21606
  /**
21487
21607
  * Subscribe to minimal high-level events.
21488
21608
  * The API manages Baichuan subscribe/unsubscribe automatically.
@@ -21541,7 +21661,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21541
21661
  * Subscribe to per-frame detection events sourced from the BcMedia
21542
21662
  * `additionalHeader` block on active video streams.
21543
21663
  *
21544
- * Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
21664
+ * Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
21545
21665
  * one event fires for every I-frame / P-frame that carries an overlay block.
21546
21666
  * Coordinates are reported in normalized [0, 1] fractions of the source
21547
21667
  * frame, so the same box renders correctly on mainStream, subStream, and
@@ -21568,7 +21688,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21568
21688
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
21569
21689
  * with class label and confidence) without managing a video stream yourself.
21570
21690
  *
21571
- * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
21691
+ * Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
21572
21692
  * `(channel, profile)` tuple the API ensures the corresponding video stream
21573
21693
  * is running (the pool socket may already be shared with a regular consumer),
21574
21694
  * forwards every box-bearing `additionalHeader` to your callback, and tears
@@ -25388,24 +25508,24 @@ ${stderr}`)
25388
25508
  async muxToMp4(params) {
25389
25509
  const { spawn: spawn13 } = await import("child_process");
25390
25510
  const { randomUUID: randomUUID3 } = await import("crypto");
25391
- const fs6 = await import("fs/promises");
25511
+ const fs7 = await import("fs/promises");
25392
25512
  const os2 = await import("os");
25393
- const path6 = await import("path");
25513
+ const path7 = await import("path");
25394
25514
  const ffmpeg = params.ffmpegPath ?? "ffmpeg";
25395
25515
  const tmpDir = os2.tmpdir();
25396
25516
  const id = randomUUID3();
25397
25517
  const videoFormat = params.videoCodec === "H265" ? "hevc" : "h264";
25398
- const videoPath = path6.join(tmpDir, `reolink-${id}.${videoFormat}`);
25399
- const outputPath = path6.join(tmpDir, `reolink-${id}.mp4`);
25518
+ const videoPath = path7.join(tmpDir, `reolink-${id}.${videoFormat}`);
25519
+ const outputPath = path7.join(tmpDir, `reolink-${id}.mp4`);
25400
25520
  let audioPath = null;
25401
25521
  if (params.audioData && params.audioData.length > 0 && params.audioCodec) {
25402
25522
  const audioExt = params.audioCodec === "Aac" ? "aac" : "raw";
25403
- audioPath = path6.join(tmpDir, `reolink-${id}.${audioExt}`);
25523
+ audioPath = path7.join(tmpDir, `reolink-${id}.${audioExt}`);
25404
25524
  }
25405
25525
  try {
25406
- await fs6.writeFile(videoPath, params.videoData);
25526
+ await fs7.writeFile(videoPath, params.videoData);
25407
25527
  if (audioPath && params.audioData) {
25408
- await fs6.writeFile(audioPath, params.audioData);
25528
+ await fs7.writeFile(audioPath, params.audioData);
25409
25529
  }
25410
25530
  const args = ["-hide_banner", "-loglevel", "error", "-y"];
25411
25531
  if (params.fps > 0) {
@@ -25458,13 +25578,13 @@ ${stderr}`)
25458
25578
  }
25459
25579
  });
25460
25580
  });
25461
- return await fs6.readFile(outputPath);
25581
+ return await fs7.readFile(outputPath);
25462
25582
  } finally {
25463
- await fs6.unlink(videoPath).catch(() => {
25583
+ await fs7.unlink(videoPath).catch(() => {
25464
25584
  });
25465
- if (audioPath) await fs6.unlink(audioPath).catch(() => {
25585
+ if (audioPath) await fs7.unlink(audioPath).catch(() => {
25466
25586
  });
25467
- await fs6.unlink(outputPath).catch(() => {
25587
+ await fs7.unlink(outputPath).catch(() => {
25468
25588
  });
25469
25589
  }
25470
25590
  }
@@ -28412,7 +28532,7 @@ ${xml}`
28412
28532
  * Field meaning per stream:
28413
28533
  * - `audio` — 0/1 toggle
28414
28534
  * - `width`/`height` — resolution in pixels. Must be one of the
28415
- * resolutions returned by {@link getStreamInfoList}.
28535
+ * resolutions returned by `getStreamInfoList()`.
28416
28536
  * - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
28417
28537
  * - `frameRate` — fps. Must match the table from `getStreamInfoList`.
28418
28538
  * - `videoEncType` — `"h264"` or `"h265"`
@@ -29801,10 +29921,12 @@ ${xml}`
29801
29921
  const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
29802
29922
  const attachmentType = params.attachmentType ?? "picture";
29803
29923
  const interval = params.interval ?? 30;
29924
+ const rawUser = params.authUsername ?? recipient;
29925
+ const wireUser = rawUser.includes("@") ? rawUser : `${rawUser}@${domain}`;
29804
29926
  const emailPatch = {
29805
29927
  smtpServer: params.managerHost,
29806
29928
  smtpPort: port,
29807
- userName: params.authUsername ?? recipient,
29929
+ userName: wireUser,
29808
29930
  password: params.authPassword ?? "",
29809
29931
  address1: recipient,
29810
29932
  address2: "",
@@ -30816,16 +30938,16 @@ ${scheduleItems}
30816
30938
  const logger = params.logger ?? this.logger;
30817
30939
  const hlsSegmentDuration = params.hlsSegmentDuration ?? 4;
30818
30940
  const os2 = await import("os");
30819
- const path6 = await import("path");
30820
- const fs6 = await import("fs/promises");
30941
+ const path7 = await import("path");
30942
+ const fs7 = await import("fs/promises");
30821
30943
  const crypto3 = await import("crypto");
30822
- const tempDir = path6.join(
30944
+ const tempDir = path7.join(
30823
30945
  os2.tmpdir(),
30824
30946
  `reolink-hls-${crypto3.randomBytes(8).toString("hex")}`
30825
30947
  );
30826
- await fs6.mkdir(tempDir, { recursive: true });
30827
- const playlistPath = path6.join(tempDir, "playlist.m3u8");
30828
- const segmentPattern = path6.join(tempDir, "segment_%03d.ts");
30948
+ await fs7.mkdir(tempDir, { recursive: true });
30949
+ const playlistPath = path7.join(tempDir, "playlist.m3u8");
30950
+ const segmentPattern = path7.join(tempDir, "segment_%03d.ts");
30829
30951
  const parsed = parseRecordingFileName(params.fileName);
30830
30952
  const durationMs = parsed?.durationMs ?? 3e5;
30831
30953
  const fps = parsed?.framerate && parsed.framerate > 0 ? parsed.framerate : 15;
@@ -30862,13 +30984,13 @@ ${scheduleItems}
30862
30984
  const segments = /* @__PURE__ */ new Map();
30863
30985
  const startSegmentWatcher = () => {
30864
30986
  if (segmentWatcher || !readyResolve) return;
30865
- const firstSegmentPath = path6.join(tempDir, "segment_000.ts");
30987
+ const firstSegmentPath = path7.join(tempDir, "segment_000.ts");
30866
30988
  let checkCount = 0;
30867
30989
  const maxChecks = Math.ceil((hlsSegmentDuration + 2) * 10);
30868
30990
  segmentWatcher = setInterval(async () => {
30869
30991
  checkCount++;
30870
30992
  try {
30871
- const stats = await fs6.stat(firstSegmentPath);
30993
+ const stats = await fs7.stat(firstSegmentPath);
30872
30994
  if (stats.size > 256) {
30873
30995
  if (segmentWatcher) {
30874
30996
  clearInterval(segmentWatcher);
@@ -31010,12 +31132,12 @@ ${scheduleItems}
31010
31132
  ]);
31011
31133
  setTimeout(async () => {
31012
31134
  try {
31013
- const files = await fs6.readdir(tempDir);
31135
+ const files = await fs7.readdir(tempDir);
31014
31136
  for (const file of files) {
31015
- await fs6.unlink(path6.join(tempDir, file)).catch(() => {
31137
+ await fs7.unlink(path7.join(tempDir, file)).catch(() => {
31016
31138
  });
31017
31139
  }
31018
- await fs6.rmdir(tempDir).catch(() => {
31140
+ await fs7.rmdir(tempDir).catch(() => {
31019
31141
  });
31020
31142
  } catch {
31021
31143
  }
@@ -31091,7 +31213,7 @@ ${scheduleItems}
31091
31213
  }
31092
31214
  try {
31093
31215
  const { readFileSync } = require("fs");
31094
- const segmentPath = path6.join(tempDir, name);
31216
+ const segmentPath = path7.join(tempDir, name);
31095
31217
  const data = readFileSync(segmentPath);
31096
31218
  segments.set(name, data);
31097
31219
  return data;
@@ -34264,7 +34386,7 @@ init_BaichuanVideoStream();
34264
34386
  // src/multifocal/compositeStream.ts
34265
34387
  var import_node_child_process7 = require("child_process");
34266
34388
  var import_node_crypto5 = require("crypto");
34267
- var import_node_events5 = require("events");
34389
+ var import_node_events6 = require("events");
34268
34390
  function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pipHeight, margin) {
34269
34391
  const pipW = Math.floor(pipWidth);
34270
34392
  const pipH = Math.floor(pipHeight);
@@ -34292,7 +34414,7 @@ function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pip
34292
34414
  return { x: m, y: m };
34293
34415
  }
34294
34416
  }
34295
- var CompositeStream = class extends import_node_events5.EventEmitter {
34417
+ var CompositeStream = class extends import_node_events6.EventEmitter {
34296
34418
  options;
34297
34419
  widerStream = null;
34298
34420
  teleStream = null;
@@ -36715,7 +36837,7 @@ async function createReplayHttpServer(options) {
36715
36837
  init_BaichuanVideoStream();
36716
36838
 
36717
36839
  // src/baichuan/stream/Go2rtcTcpServer.ts
36718
- var import_node_events6 = require("events");
36840
+ var import_node_events7 = require("events");
36719
36841
  var net4 = __toESM(require("net"), 1);
36720
36842
  init_H264Converter();
36721
36843
  init_H265Converter();
@@ -36827,7 +36949,7 @@ var NativeStreamFanout2 = class {
36827
36949
  this.pumpPromise = null;
36828
36950
  }
36829
36951
  };
36830
- var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events6.EventEmitter {
36952
+ var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events7.EventEmitter {
36831
36953
  api;
36832
36954
  channel;
36833
36955
  profile;
@@ -37518,7 +37640,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events6.EventEm
37518
37640
  };
37519
37641
 
37520
37642
  // src/baichuan/stream/BaichuanHttpStreamServer.ts
37521
- var import_node_events7 = require("events");
37643
+ var import_node_events8 = require("events");
37522
37644
  var import_node_child_process9 = require("child_process");
37523
37645
  var http4 = __toESM(require("http"), 1);
37524
37646
  var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
@@ -37565,7 +37687,7 @@ function isH264KeyframeFromAnnexB(annexB) {
37565
37687
  }
37566
37688
  return false;
37567
37689
  }
37568
- var BaichuanHttpStreamServer = class extends import_node_events7.EventEmitter {
37690
+ var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
37569
37691
  videoStream;
37570
37692
  listenPort;
37571
37693
  path;
@@ -37837,15 +37959,15 @@ var BaichuanHttpStreamServer = class extends import_node_events7.EventEmitter {
37837
37959
  };
37838
37960
 
37839
37961
  // src/baichuan/stream/BaichuanMjpegServer.ts
37840
- var import_node_events9 = require("events");
37962
+ var import_node_events10 = require("events");
37841
37963
  var http5 = __toESM(require("http"), 1);
37842
37964
 
37843
37965
  // src/baichuan/stream/MjpegTransformer.ts
37844
- var import_node_events8 = require("events");
37966
+ var import_node_events9 = require("events");
37845
37967
  var import_node_child_process10 = require("child_process");
37846
37968
  var JPEG_SOI = Buffer.from([255, 216]);
37847
37969
  var JPEG_EOI = Buffer.from([255, 217]);
37848
- var MjpegTransformer = class extends import_node_events8.EventEmitter {
37970
+ var MjpegTransformer = class extends import_node_events9.EventEmitter {
37849
37971
  options;
37850
37972
  ffmpeg = null;
37851
37973
  started = false;
@@ -38044,7 +38166,7 @@ Content-Length: ${frame.length}\r
38044
38166
  // src/baichuan/stream/BaichuanMjpegServer.ts
38045
38167
  init_H264Converter();
38046
38168
  init_H265Converter();
38047
- var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
38169
+ var BaichuanMjpegServer = class extends import_node_events10.EventEmitter {
38048
38170
  options;
38049
38171
  clients = /* @__PURE__ */ new Map();
38050
38172
  httpServer = null;
@@ -38066,9 +38188,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
38066
38188
  this.started = true;
38067
38189
  const port = this.options.port ?? 8080;
38068
38190
  const host = this.options.host ?? "0.0.0.0";
38069
- const path6 = this.options.path ?? "/mjpeg";
38191
+ const path7 = this.options.path ?? "/mjpeg";
38070
38192
  this.httpServer = http5.createServer((req, res) => {
38071
- this.handleRequest(req, res, path6);
38193
+ this.handleRequest(req, res, path7);
38072
38194
  });
38073
38195
  return new Promise((resolve, reject) => {
38074
38196
  this.httpServer.on("error", (err) => {
@@ -38078,9 +38200,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
38078
38200
  this.httpServer.listen(port, host, () => {
38079
38201
  this.log(
38080
38202
  "info",
38081
- `MJPEG server started on http://${host}:${port}${path6}`
38203
+ `MJPEG server started on http://${host}:${port}${path7}`
38082
38204
  );
38083
- this.emit("started", { host, port, path: path6 });
38205
+ this.emit("started", { host, port, path: path7 });
38084
38206
  resolve();
38085
38207
  });
38086
38208
  });
@@ -38325,14 +38447,14 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
38325
38447
  };
38326
38448
 
38327
38449
  // src/baichuan/stream/BaichuanWebRTCServer.ts
38328
- var import_node_events11 = require("events");
38450
+ var import_node_events12 = require("events");
38329
38451
  init_BcMediaAnnexBDecoder();
38330
38452
 
38331
38453
  // src/baichuan/stream/AacToOpusTranscoder.ts
38332
38454
  var import_node_child_process11 = require("child_process");
38333
38455
  var import_node_dgram3 = require("dgram");
38334
- var import_node_events10 = require("events");
38335
- var AacToOpusTranscoder = class extends import_node_events10.EventEmitter {
38456
+ var import_node_events11 = require("events");
38457
+ var AacToOpusTranscoder = class extends import_node_events11.EventEmitter {
38336
38458
  opts;
38337
38459
  socket = null;
38338
38460
  ffmpeg = null;
@@ -38549,7 +38671,7 @@ function getH264NalType(nalUnit) {
38549
38671
  function getH265NalType2(nalUnit) {
38550
38672
  return nalUnit[0] >> 1 & 63;
38551
38673
  }
38552
- var BaichuanWebRTCServer = class extends import_node_events11.EventEmitter {
38674
+ var BaichuanWebRTCServer = class extends import_node_events12.EventEmitter {
38553
38675
  options;
38554
38676
  sessions = /* @__PURE__ */ new Map();
38555
38677
  sessionIdCounter = 0;
@@ -39541,7 +39663,7 @@ Error: ${err}`
39541
39663
  };
39542
39664
 
39543
39665
  // src/baichuan/stream/BaichuanHlsServer.ts
39544
- var import_node_events12 = require("events");
39666
+ var import_node_events13 = require("events");
39545
39667
  var import_node_fs = __toESM(require("fs"), 1);
39546
39668
  var import_promises3 = __toESM(require("fs/promises"), 1);
39547
39669
  var import_node_os3 = __toESM(require("os"), 1);
@@ -39621,7 +39743,7 @@ function getNalTypes(codec, annexB) {
39621
39743
  }
39622
39744
  });
39623
39745
  }
39624
- var BaichuanHlsServer = class extends import_node_events12.EventEmitter {
39746
+ var BaichuanHlsServer = class extends import_node_events13.EventEmitter {
39625
39747
  api;
39626
39748
  channel;
39627
39749
  profile;
@@ -40645,10 +40767,10 @@ async function autoDetectDeviceType(inputs) {
40645
40767
  }
40646
40768
 
40647
40769
  // src/multifocal/compositeRtspServer.ts
40648
- var import_node_events13 = require("events");
40770
+ var import_node_events14 = require("events");
40649
40771
  var import_node_child_process13 = require("child_process");
40650
40772
  var net5 = __toESM(require("net"), 1);
40651
- var CompositeRtspServer = class extends import_node_events13.EventEmitter {
40773
+ var CompositeRtspServer = class extends import_node_events14.EventEmitter {
40652
40774
  options;
40653
40775
  compositeStream = null;
40654
40776
  rtspServer = null;
@@ -40941,6 +41063,272 @@ function base64DecodeToBytes(b64) {
40941
41063
  }
40942
41064
  return out.subarray(0, outIdx);
40943
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
+ }
40944
41332
  // Annotate the CommonJS export names for ESM import in node:
40945
41333
  0 && (module.exports = {
40946
41334
  AesStreamDecryptor,
@@ -41114,6 +41502,7 @@ function base64DecodeToBytes(b64) {
41114
41502
  ReolinkCgiApi,
41115
41503
  ReolinkHttpClient,
41116
41504
  Rfc4571Muxer,
41505
+ _resetEmailPushBusForTests,
41117
41506
  abilitiesHasAny,
41118
41507
  aesDecrypt,
41119
41508
  aesEncrypt,
@@ -41159,6 +41548,7 @@ function base64DecodeToBytes(b64) {
41159
41548
  createBaichuanEndpointsServer,
41160
41549
  createDebugGateLogger,
41161
41550
  createDiagnosticsBundle,
41551
+ createEmailPushServer,
41162
41552
  createLogger,
41163
41553
  createMjpegBoundary,
41164
41554
  createNativeStream,
@@ -41186,6 +41576,7 @@ function base64DecodeToBytes(b64) {
41186
41576
  discoverViaTcpPortScan,
41187
41577
  discoverViaUdpBroadcast,
41188
41578
  discoverViaUdpDirect,
41579
+ emitEmailPushEvent,
41189
41580
  encodeHeader,
41190
41581
  encodeMotionScopeBitmap,
41191
41582
  encodeMotionSensitivityListXml,
@@ -41202,10 +41593,14 @@ function base64DecodeToBytes(b64) {
41202
41593
  flattenAbilitiesForChannel,
41203
41594
  formatMjpegFrame,
41204
41595
  fullCoverageScope,
41596
+ getCameraEmailAddress,
41205
41597
  getConstructedVideoStreamOptions,
41598
+ getEmailPushCameraResolver,
41206
41599
  getGlobalLogger,
41207
41600
  getH265NalType,
41601
+ getLastEmailPushEvent,
41208
41602
  getMjpegContentType,
41603
+ getRecentEmailPushEvents,
41209
41604
  getSupportItemForChannel,
41210
41605
  getVideoStream,
41211
41606
  getVideoclipClientInfo,
@@ -41220,12 +41615,15 @@ function base64DecodeToBytes(b64) {
41220
41615
  isTcpFailureThatShouldFallbackToUdp,
41221
41616
  isValidH264AnnexBAccessUnit,
41222
41617
  isValidH265AnnexBAccessUnit,
41618
+ loadEmailPushTls,
41619
+ mapEmailPushInferredType,
41223
41620
  maskUid,
41224
41621
  md5HexUpper,
41225
41622
  md5StrModern,
41226
41623
  normalizeDayNightMode,
41227
41624
  normalizeOpenClose,
41228
41625
  normalizeUid,
41626
+ onEmailPushEvent,
41229
41627
  packetizeAacAdtsFrame,
41230
41628
  packetizeAacRawFrame,
41231
41629
  packetizeH264,
@@ -41243,6 +41641,7 @@ function base64DecodeToBytes(b64) {
41243
41641
  runMultifocalDiagnosticsConsecutively,
41244
41642
  sampleStreams,
41245
41643
  sanitizeFixtureData,
41644
+ setEmailPushCameraResolver,
41246
41645
  setGlobalLogger,
41247
41646
  splitAnnexBToNalPayloads,
41248
41647
  splitAnnexBToNals,