@camstack/addon-pipeline 1.1.20 → 1.1.21

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.
@@ -1,24 +1,3 @@
1
- //#region src/stream-broker/stream-broker/frame-dropper.ts
2
- var FrameDropper = class {
3
- intervalMs;
4
- lastPassedAt = -Infinity;
5
- constructor(maxFps) {
6
- this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
7
- }
8
- shouldKeep() {
9
- if (this.intervalMs === 0) return true;
10
- const now = Date.now();
11
- if (now - this.lastPassedAt >= this.intervalMs) {
12
- this.lastPassedAt = now;
13
- return true;
14
- }
15
- return false;
16
- }
17
- setMaxFps(maxFps) {
18
- this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
19
- }
20
- };
21
- //#endregion
22
1
  //#region src/stream-broker/stream-broker/ffmpeg-args-common.ts
23
2
  var AUDIO_ENCODER_BY_CODEC = {
24
3
  opus: "libopus",
@@ -48,4 +27,25 @@ function logBannerArgs(level) {
48
27
  ];
49
28
  }
50
29
  //#endregion
51
- export { logBannerArgs as n, FrameDropper as r, audioEncoderArgs as t };
30
+ //#region src/stream-broker/stream-broker/frame-dropper.ts
31
+ var FrameDropper = class {
32
+ intervalMs;
33
+ lastPassedAt = -Infinity;
34
+ constructor(maxFps) {
35
+ this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
36
+ }
37
+ shouldKeep() {
38
+ if (this.intervalMs === 0) return true;
39
+ const now = Date.now();
40
+ if (now - this.lastPassedAt >= this.intervalMs) {
41
+ this.lastPassedAt = now;
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ setMaxFps(maxFps) {
47
+ this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
48
+ }
49
+ };
50
+ //#endregion
51
+ export { audioEncoderArgs as n, logBannerArgs as r, FrameDropper as t };
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
6
6
  const require_dist = require("../dist-BiP1gPeY.js");
7
- const require_ffmpeg_args_common = require("../ffmpeg-args-common-CASoNt42.js");
7
+ const require_frame_dropper = require("../frame-dropper-7RTo_YyG.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
10
10
  let node_path = require("node:path");
@@ -352,6 +352,11 @@ var FrameHandlePlane = class {
352
352
  localNodeId;
353
353
  subscriptions = /* @__PURE__ */ new Map();
354
354
  sessions = /* @__PURE__ */ new Map();
355
+ /** In-flight `ensureSession` creation per format. Coalesces concurrent
356
+ * same-format callers onto ONE decoder session — without it two callers
357
+ * both pass the `sessions.get` check, both `createSession` (two ffmpeg),
358
+ * and the second registration orphans the first with no `destroySession`. */
359
+ sessionCreating = /* @__PURE__ */ new Map();
355
360
  disposed = false;
356
361
  /** Re-entrancy guard for `armPendingSessions` — collapses the per-packet
357
362
  * `pushPacket` calls into a single in-flight deferred-arm pass. */
@@ -626,15 +631,29 @@ var FrameHandlePlane = class {
626
631
  existing.subscriberIds.add(subscriptionId);
627
632
  return;
628
633
  }
629
- const subscriberIds = new Set([subscriptionId]);
630
- for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
631
- const session = {
632
- format,
633
- proxy: null,
634
- nodeId: null,
635
- subscriberIds
636
- };
637
- if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
634
+ const inflight = this.sessionCreating.get(format);
635
+ if (inflight) {
636
+ await inflight;
637
+ this.sessions.get(format)?.subscriberIds.add(subscriptionId);
638
+ return;
639
+ }
640
+ const creation = (async () => {
641
+ const subscriberIds = new Set([subscriptionId]);
642
+ for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
643
+ const session = {
644
+ format,
645
+ proxy: null,
646
+ nodeId: null,
647
+ subscriberIds
648
+ };
649
+ if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
650
+ })();
651
+ this.sessionCreating.set(format, creation);
652
+ try {
653
+ await creation;
654
+ } finally {
655
+ this.sessionCreating.delete(format);
656
+ }
638
657
  }
639
658
  /**
640
659
  * Create the `frameSink: 'shm'` decoder for a `FormatSession` and start
@@ -6502,6 +6521,7 @@ var RtspListenServer = class RtspListenServer {
6502
6521
  releaseOnce();
6503
6522
  }, isMuted);
6504
6523
  restreamer.addSession(session);
6524
+ releaseOnce();
6505
6525
  socket.unshift(Buffer.from(requestBuffer, "ascii"));
6506
6526
  }
6507
6527
  /**
@@ -6704,23 +6724,23 @@ function pickVideoEncoder(target, encoders, hwAccel) {
6704
6724
  /** Audio encoder args for a transcode target (preset values over the shared low-level builder). */
6705
6725
  function pickAudioEncoderArgs(audio) {
6706
6726
  switch (audio) {
6707
- case "aac": return require_ffmpeg_args_common.audioEncoderArgs("aac", {
6727
+ case "aac": return require_frame_dropper.audioEncoderArgs("aac", {
6708
6728
  bitrateKbps: 128,
6709
6729
  sampleRateHz: 48e3,
6710
6730
  channels: 2
6711
6731
  });
6712
- case "opus": return require_ffmpeg_args_common.audioEncoderArgs("opus", {
6732
+ case "opus": return require_frame_dropper.audioEncoderArgs("opus", {
6713
6733
  bitrateKbps: 64,
6714
6734
  sampleRateHz: 48e3,
6715
6735
  channels: 2
6716
6736
  });
6717
- case "pcmu": return require_ffmpeg_args_common.audioEncoderArgs("pcmu", {
6737
+ case "pcmu": return require_frame_dropper.audioEncoderArgs("pcmu", {
6718
6738
  sampleRateHz: 8e3,
6719
6739
  channels: 1
6720
6740
  });
6721
- case "copy": return require_ffmpeg_args_common.audioEncoderArgs("copy");
6741
+ case "copy": return require_frame_dropper.audioEncoderArgs("copy");
6722
6742
  case "none": return ["-an"];
6723
- default: return require_ffmpeg_args_common.audioEncoderArgs("aac", { bitrateKbps: 128 });
6743
+ default: return require_frame_dropper.audioEncoderArgs("aac", { bitrateKbps: 128 });
6724
6744
  }
6725
6745
  }
6726
6746
  /**
@@ -6785,7 +6805,7 @@ function buildFfmpegArgs$1(inv) {
6785
6805
  const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
6786
6806
  const audioArgs = inv.sink.kind === "stdout" && (inv.sink.container === "h264" || inv.sink.container === "hevc") ? ["-an"] : pickAudioEncoderArgs(inv.audio);
6787
6807
  return [
6788
- ...require_ffmpeg_args_common.logBannerArgs("warning"),
6808
+ ...require_frame_dropper.logBannerArgs("warning"),
6789
6809
  ...decodeArgs,
6790
6810
  "-rtsp_transport",
6791
6811
  "tcp",
@@ -11586,7 +11606,7 @@ function planAudioSilencePadPackets(input) {
11586
11606
  * audio plane honours the profile literally.
11587
11607
  */
11588
11608
  function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl) {
11589
- const args = [...require_ffmpeg_args_common.logBannerArgs("error")];
11609
+ const args = [...require_frame_dropper.logBannerArgs("error")];
11590
11610
  if (profile.inputArgs?.length) args.push(...profile.inputArgs);
11591
11611
  if (decodeHwAccel && decodeHwAccel !== "none") args.push("-hwaccel", decodeHwAccel);
11592
11612
  args.push("-fflags", "+discardcorrupt", "-rtsp_transport", "tcp", "-i", sourceUrl);
@@ -11608,7 +11628,7 @@ function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl
11608
11628
  if (profile.audio === "passthrough") args.push("-an");
11609
11629
  else {
11610
11630
  const a = profile.audio;
11611
- args.push(...require_ffmpeg_args_common.audioEncoderArgs(a.codec, {
11631
+ args.push(...require_frame_dropper.audioEncoderArgs(a.codec, {
11612
11632
  bitrateKbps: a.bitrateKbps,
11613
11633
  sampleRateHz: a.sampleRateHz,
11614
11634
  channels: a.channels
@@ -16927,7 +16947,7 @@ function codecToInputFormat(codec) {
16927
16947
  function buildFfmpegArgs(config) {
16928
16948
  const inputFormat = codecToInputFormat(config.codec);
16929
16949
  const args = [
16930
- ...require_ffmpeg_args_common.logBannerArgs("error"),
16950
+ ...require_frame_dropper.logBannerArgs("error"),
16931
16951
  "-fflags",
16932
16952
  "+nobuffer+flush_packets",
16933
16953
  "-flags",
@@ -16964,7 +16984,7 @@ var FfmpegDecoderSession = class {
16964
16984
  constructor(config, logger = noopLogger) {
16965
16985
  this.config = { ...config };
16966
16986
  this.logger = logger;
16967
- this.frameDropper = new require_ffmpeg_args_common.FrameDropper(config.maxFps);
16987
+ this.frameDropper = new require_frame_dropper.FrameDropper(config.maxFps);
16968
16988
  this.spawnFfmpeg();
16969
16989
  }
16970
16990
  spawnFfmpeg() {
@@ -1,7 +1,7 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
2
  import { A as nodePin, B as BaseAddon, D as maskUrlCredentials, G as asJsonObject, H as DeviceFeature, I as streamBrokerCapability, Q as parseProfileBrokerId, R as webrtcSessionCapability, U as DeviceType, V as CAM_PROFILE_ORDER, W as EventCategory, X as makeSourceBrokerId, Y as makeProfileBrokerId, c as EncodeProfileSchema, ct as object, dt as union, f as RingBuffer, it as discriminatedUnion, lt as record, m as addonWidgetsSourceCapability, ot as literal, q as createEvent, rt as boolean, st as number, tt as _enum, ut as string, v as cameraStreamsCapability, z as errMsg } from "../dist-tzhTTRq4.mjs";
3
3
  import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
4
- import { n as logBannerArgs, r as FrameDropper, t as audioEncoderArgs } from "../ffmpeg-args-common-c3p-nWtU.mjs";
4
+ import { n as audioEncoderArgs, r as logBannerArgs, t as FrameDropper } from "../frame-dropper-CwkBTPGV.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import * as fs from "node:fs";
7
7
  import * as path$1 from "node:path";
@@ -348,6 +348,11 @@ var FrameHandlePlane = class {
348
348
  localNodeId;
349
349
  subscriptions = /* @__PURE__ */ new Map();
350
350
  sessions = /* @__PURE__ */ new Map();
351
+ /** In-flight `ensureSession` creation per format. Coalesces concurrent
352
+ * same-format callers onto ONE decoder session — without it two callers
353
+ * both pass the `sessions.get` check, both `createSession` (two ffmpeg),
354
+ * and the second registration orphans the first with no `destroySession`. */
355
+ sessionCreating = /* @__PURE__ */ new Map();
351
356
  disposed = false;
352
357
  /** Re-entrancy guard for `armPendingSessions` — collapses the per-packet
353
358
  * `pushPacket` calls into a single in-flight deferred-arm pass. */
@@ -622,15 +627,29 @@ var FrameHandlePlane = class {
622
627
  existing.subscriberIds.add(subscriptionId);
623
628
  return;
624
629
  }
625
- const subscriberIds = new Set([subscriptionId]);
626
- for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
627
- const session = {
628
- format,
629
- proxy: null,
630
- nodeId: null,
631
- subscriberIds
632
- };
633
- if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
630
+ const inflight = this.sessionCreating.get(format);
631
+ if (inflight) {
632
+ await inflight;
633
+ this.sessions.get(format)?.subscriberIds.add(subscriptionId);
634
+ return;
635
+ }
636
+ const creation = (async () => {
637
+ const subscriberIds = new Set([subscriptionId]);
638
+ for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
639
+ const session = {
640
+ format,
641
+ proxy: null,
642
+ nodeId: null,
643
+ subscriberIds
644
+ };
645
+ if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
646
+ })();
647
+ this.sessionCreating.set(format, creation);
648
+ try {
649
+ await creation;
650
+ } finally {
651
+ this.sessionCreating.delete(format);
652
+ }
634
653
  }
635
654
  /**
636
655
  * Create the `frameSink: 'shm'` decoder for a `FormatSession` and start
@@ -6498,6 +6517,7 @@ var RtspListenServer = class RtspListenServer {
6498
6517
  releaseOnce();
6499
6518
  }, isMuted);
6500
6519
  restreamer.addSession(session);
6520
+ releaseOnce();
6501
6521
  socket.unshift(Buffer.from(requestBuffer, "ascii"));
6502
6522
  }
6503
6523
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.1.20",
3
+ "version": "1.1.21",
4
4
  "description": "CamStack Pipeline bundle — runner, detection, motion, decoders, audio + stream broker. Multi-entry npm package shipping 7 addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",
@@ -1,24 +1,3 @@
1
- //#region src/stream-broker/stream-broker/frame-dropper.ts
2
- var FrameDropper = class {
3
- intervalMs;
4
- lastPassedAt = -Infinity;
5
- constructor(maxFps) {
6
- this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
7
- }
8
- shouldKeep() {
9
- if (this.intervalMs === 0) return true;
10
- const now = Date.now();
11
- if (now - this.lastPassedAt >= this.intervalMs) {
12
- this.lastPassedAt = now;
13
- return true;
14
- }
15
- return false;
16
- }
17
- setMaxFps(maxFps) {
18
- this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
19
- }
20
- };
21
- //#endregion
22
1
  //#region src/stream-broker/stream-broker/ffmpeg-args-common.ts
23
2
  var AUDIO_ENCODER_BY_CODEC = {
24
3
  opus: "libopus",
@@ -48,6 +27,27 @@ function logBannerArgs(level) {
48
27
  ];
49
28
  }
50
29
  //#endregion
30
+ //#region src/stream-broker/stream-broker/frame-dropper.ts
31
+ var FrameDropper = class {
32
+ intervalMs;
33
+ lastPassedAt = -Infinity;
34
+ constructor(maxFps) {
35
+ this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
36
+ }
37
+ shouldKeep() {
38
+ if (this.intervalMs === 0) return true;
39
+ const now = Date.now();
40
+ if (now - this.lastPassedAt >= this.intervalMs) {
41
+ this.lastPassedAt = now;
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ setMaxFps(maxFps) {
47
+ this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
48
+ }
49
+ };
50
+ //#endregion
51
51
  Object.defineProperty(exports, "FrameDropper", {
52
52
  enumerable: true,
53
53
  get: function() {