@camstack/addon-pipeline 1.2.222 → 1.2.224
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/audio-analyzer/index.js +53 -14
- package/dist/audio-analyzer/index.mjs +53 -14
- package/dist/pipeline-runner/index.js +61 -45
- package/dist/pipeline-runner/index.mjs +61 -45
- package/dist/recorder/index.js +88 -25
- package/dist/recorder/index.mjs +88 -25
- package/dist/stream-broker/index.js +93 -51
- package/dist/stream-broker/index.mjs +93 -51
- package/package.json +1 -1
package/dist/recorder/index.mjs
CHANGED
|
@@ -7770,6 +7770,7 @@ function buildRecordingProvider(deps) {
|
|
|
7770
7770
|
},
|
|
7771
7771
|
setDeviceConfig: async ({ deviceId, config }) => {
|
|
7772
7772
|
const saved = await saveDeviceConfig(deps.configStore, deviceId, config);
|
|
7773
|
+
deps.onConfigSaved?.(deviceId, saved);
|
|
7773
7774
|
if (deps.onConfigChanged) try {
|
|
7774
7775
|
await deps.onConfigChanged(deviceId);
|
|
7775
7776
|
} catch (err) {
|
|
@@ -10956,29 +10957,75 @@ Object.freeze({
|
|
|
10956
10957
|
"turn-provider": ["getTurnServers"],
|
|
10957
10958
|
"user-passkeys": ["listPasskeys"]
|
|
10958
10959
|
});
|
|
10959
|
-
//#endregion
|
|
10960
|
-
//#region src/recorder/addon/last-known-config.ts
|
|
10961
10960
|
/**
|
|
10962
|
-
*
|
|
10961
|
+
* Build the recorder's config mirror.
|
|
10963
10962
|
*
|
|
10964
|
-
*
|
|
10965
|
-
* call site
|
|
10966
|
-
*
|
|
10967
|
-
*
|
|
10963
|
+
* `load` is the fallible store read (`loadDeviceConfig(configStore, id)`), and
|
|
10964
|
+
* every existing call site keeps its signature: the controller is handed
|
|
10965
|
+
* {@link RecordingConfigMirror.read} and cannot tell the difference, which is
|
|
10966
|
+
* the point — the trigger post-buffer is the path that got CAUGHT writing a
|
|
10967
|
+
* wrong number, but it is one of six reads and the other five are the volume.
|
|
10968
10968
|
*/
|
|
10969
|
-
function
|
|
10969
|
+
function createRecordingConfigMirror(deps) {
|
|
10970
10970
|
const now = deps.now ?? (() => Date.now());
|
|
10971
|
-
const
|
|
10972
|
-
|
|
10971
|
+
const freshMs = deps.freshMs ?? 3e4;
|
|
10972
|
+
const reportIntervalMs = deps.reportIntervalMs ?? 6e4;
|
|
10973
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
10974
|
+
const window = {
|
|
10975
|
+
served: 0,
|
|
10976
|
+
storeReads: 0,
|
|
10977
|
+
fallbacks: 0,
|
|
10978
|
+
startedAt: now()
|
|
10979
|
+
};
|
|
10980
|
+
/**
|
|
10981
|
+
* One line per window, fleet-level (no `deviceId`: this aggregates every
|
|
10982
|
+
* camera, and the per-camera lines are the fallback WARNs above).
|
|
10983
|
+
*
|
|
10984
|
+
* It exists because a branch that ACCEPTS work and produces no store traffic
|
|
10985
|
+
* is indistinguishable from a branch nobody calls: without this line, a
|
|
10986
|
+
* mirror that silently stopped mirroring and a mirror doing its job both look
|
|
10987
|
+
* like silence, and the only visible difference is 1 800 calls a minute on a
|
|
10988
|
+
* runner nobody is watching.
|
|
10989
|
+
*/
|
|
10990
|
+
const reportIfDue = (atMs) => {
|
|
10991
|
+
const windowMs = atMs - window.startedAt;
|
|
10992
|
+
if (windowMs < reportIntervalMs) return;
|
|
10993
|
+
const calls = window.served + window.storeReads;
|
|
10994
|
+
window.startedAt = atMs;
|
|
10995
|
+
const served = window.served;
|
|
10996
|
+
const storeReads = window.storeReads;
|
|
10997
|
+
const fallbacks = window.fallbacks;
|
|
10998
|
+
window.served = 0;
|
|
10999
|
+
window.storeReads = 0;
|
|
11000
|
+
window.fallbacks = 0;
|
|
11001
|
+
if (calls === 0) return;
|
|
11002
|
+
deps.logger.info("recorder: config mirror window", { meta: {
|
|
11003
|
+
served,
|
|
11004
|
+
storeReads,
|
|
11005
|
+
fallbacks,
|
|
11006
|
+
devices: mirror.size,
|
|
11007
|
+
windowMs,
|
|
11008
|
+
freshMs
|
|
11009
|
+
} });
|
|
11010
|
+
};
|
|
11011
|
+
const read = async (deviceId) => {
|
|
11012
|
+
const atMs = now();
|
|
11013
|
+
reportIfDue(atMs);
|
|
11014
|
+
const held = mirror.get(deviceId);
|
|
11015
|
+
if (held !== void 0 && atMs - held.atMs < freshMs) {
|
|
11016
|
+
window.served += 1;
|
|
11017
|
+
return held.config;
|
|
11018
|
+
}
|
|
11019
|
+
window.storeReads += 1;
|
|
10973
11020
|
try {
|
|
10974
11021
|
const config = await deps.load(deviceId);
|
|
10975
|
-
|
|
11022
|
+
mirror.set(deviceId, {
|
|
10976
11023
|
config,
|
|
10977
11024
|
atMs: now()
|
|
10978
11025
|
});
|
|
10979
11026
|
return config;
|
|
10980
11027
|
} catch (err) {
|
|
10981
|
-
const cached =
|
|
11028
|
+
const cached = mirror.get(deviceId);
|
|
10982
11029
|
if (cached === void 0) {
|
|
10983
11030
|
deps.logger.warn("recorder: config read failed and NOTHING is cached for this camera — no last-known-good to serve", {
|
|
10984
11031
|
tags: { deviceId },
|
|
@@ -10986,17 +11033,29 @@ function withLastKnownConfig(deps) {
|
|
|
10986
11033
|
});
|
|
10987
11034
|
throw err;
|
|
10988
11035
|
}
|
|
10989
|
-
|
|
11036
|
+
window.fallbacks += 1;
|
|
10990
11037
|
deps.logger.warn("recorder: config read failed — serving the LAST-KNOWN-GOOD config (not the shipped default)", {
|
|
10991
11038
|
tags: { deviceId },
|
|
10992
11039
|
meta: {
|
|
10993
|
-
ageMs,
|
|
11040
|
+
ageMs: now() - cached.atMs,
|
|
10994
11041
|
error: errMsg(err)
|
|
10995
11042
|
}
|
|
10996
11043
|
});
|
|
10997
11044
|
return cached.config;
|
|
10998
11045
|
}
|
|
10999
11046
|
};
|
|
11047
|
+
return {
|
|
11048
|
+
read,
|
|
11049
|
+
put(deviceId, config) {
|
|
11050
|
+
mirror.set(deviceId, {
|
|
11051
|
+
config,
|
|
11052
|
+
atMs: now()
|
|
11053
|
+
});
|
|
11054
|
+
},
|
|
11055
|
+
invalidate(deviceId) {
|
|
11056
|
+
mirror.delete(deviceId);
|
|
11057
|
+
}
|
|
11058
|
+
};
|
|
11000
11059
|
}
|
|
11001
11060
|
//#endregion
|
|
11002
11061
|
//#region src/recorder/addon/ledger-walk-reconcile.ts
|
|
@@ -17452,21 +17511,22 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
17452
17511
|
/** Continuous + events band write-path controller (ffmpeg writers + watchers). */
|
|
17453
17512
|
controller = null;
|
|
17454
17513
|
/**
|
|
17455
|
-
* The controller's config read
|
|
17514
|
+
* The controller's config read — this addon's mirror of the rows it owns
|
|
17515
|
+
* (`config-mirror.ts`, D466), with the last-known-good fallback.
|
|
17456
17516
|
*
|
|
17457
|
-
* Built ONCE and memoized, because the
|
|
17458
|
-
*
|
|
17459
|
-
* to
|
|
17460
|
-
*
|
|
17461
|
-
* `undefined` at that point.
|
|
17517
|
+
* Built ONCE and memoized, because the map lives INSIDE it: rebuilding it per
|
|
17518
|
+
* call would throw away both the freshness the trigger path is served from
|
|
17519
|
+
* and the last value a failed read falls back to. Lazy rather than a field
|
|
17520
|
+
* initializer — a field initializer runs before the constructor assigns
|
|
17521
|
+
* `this.ctx`, so `this.ctx.logger` would be `undefined` at that point.
|
|
17462
17522
|
*/
|
|
17463
|
-
|
|
17464
|
-
get
|
|
17465
|
-
this.
|
|
17523
|
+
mirroredConfigRead = null;
|
|
17524
|
+
get configMirror() {
|
|
17525
|
+
this.mirroredConfigRead ??= createRecordingConfigMirror({
|
|
17466
17526
|
load: (deviceId) => loadDeviceConfig(this.configStore(), deviceId),
|
|
17467
17527
|
logger: this.ctx.logger
|
|
17468
17528
|
});
|
|
17469
|
-
return this.
|
|
17529
|
+
return this.mirroredConfigRead;
|
|
17470
17530
|
}
|
|
17471
17531
|
/** Unsubscribe handle for the EventCapture motion subscription. */
|
|
17472
17532
|
eventUnsub = null;
|
|
@@ -17714,7 +17774,7 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
17714
17774
|
nodeId: this.nodeId,
|
|
17715
17775
|
ownerNodeId: ingestOwner.ownerNodeId,
|
|
17716
17776
|
segmentStore: this.segmentStore,
|
|
17717
|
-
loadConfig: this.
|
|
17777
|
+
loadConfig: this.configMirror.read,
|
|
17718
17778
|
enabledDeviceIds: async () => [...(await readDeviceConfigs(this.configStore())).keys()],
|
|
17719
17779
|
/**
|
|
17720
17780
|
* The DURABLE half of the trigger path (D8/D11). `pipelineAnalytics` rows
|
|
@@ -17853,6 +17913,9 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
17853
17913
|
calendar: this.calendar,
|
|
17854
17914
|
mfraFor: (deviceId, profile, startMs) => this.mfraTables.get(deviceId, profile, startMs),
|
|
17855
17915
|
hydrateWindow: (deviceId, fromMs, toMs, verifyClosedHours) => hydrateWindowFromStorage(this.index, deviceId, fromMs, toMs, this.resolvedLocations, this.ctx.logger, this.segmentHours ?? void 0, { verifyClosedHours: verifyClosedHours === true }),
|
|
17916
|
+
onConfigSaved: (deviceId, config) => {
|
|
17917
|
+
this.configMirror.put(deviceId, config);
|
|
17918
|
+
},
|
|
17856
17919
|
onConfigChanged: async (deviceId) => {
|
|
17857
17920
|
await this.refreshSensorTriggerBindings();
|
|
17858
17921
|
await (this.controller?.onConfigChanged(deviceId) ?? Promise.resolve());
|
|
@@ -9409,42 +9409,14 @@ function extractQuoted(header, key) {
|
|
|
9409
9409
|
const unquotedRegex = new RegExp(`${key}=([^,\\s]+)`, "i");
|
|
9410
9410
|
return header.match(unquotedRegex)?.[1];
|
|
9411
9411
|
}
|
|
9412
|
-
//#endregion
|
|
9413
|
-
//#region src/stream-broker/stream-broker/audio-chunk-plane.ts
|
|
9414
|
-
/**
|
|
9415
|
-
* `AudioChunkPlane` — the broker's serialisable decoded audio-chunk surface
|
|
9416
|
-
* (Phase 5 / D9).
|
|
9417
|
-
*
|
|
9418
|
-
* This is the poll-based replacement for `StreamBroker.onDecodedAudioChunk`'s
|
|
9419
|
-
* live-object callback path. A live callback cannot cross a process boundary;
|
|
9420
|
-
* once the `pipeline` group is dissolved (Task 8) the audio consumer
|
|
9421
|
-
* (`pipeline-orchestrator`) runs in a different process from the broker, so
|
|
9422
|
-
* audio delivery must go over tRPC like the video frame plane.
|
|
9423
|
-
*
|
|
9424
|
-
* ## Why no shared memory
|
|
9425
|
-
*
|
|
9426
|
-
* Unlike video frames (~6 MB at 1080p RGB), a decoded audio chunk is tiny — a
|
|
9427
|
-
* ~500ms `AudioCodecSession` window is a few KB of Float32 PCM. Tiny payloads
|
|
9428
|
-
* serialise over tRPC cheaply, so audio ships its bytes INLINE on the RPC
|
|
9429
|
-
* wire; there is no shm ring.
|
|
9430
|
-
*
|
|
9431
|
-
* ## FIFO / no-drop queue policy
|
|
9432
|
-
*
|
|
9433
|
-
* Audio wants FIFO arrival order with no latest-wins drop: a missing chunk is
|
|
9434
|
-
* an audible gap and breaks the audio analyzer's fixed-length analysis
|
|
9435
|
-
* window. Each subscription therefore owns a generously-sized FIFO queue
|
|
9436
|
-
* (`AUDIO_QUEUE_CAPACITY`). Chunks arrive roughly every
|
|
9437
|
-
* `AudioCodecSession`-window (~500ms), so the queue buffers tens of seconds
|
|
9438
|
-
* of audio — a healthy poll loop never fills it. The queue only drops its
|
|
9439
|
-
* OLDEST chunk if a truly stalled consumer lets it overflow (a bounded safety
|
|
9440
|
-
* valve, not the steady-state behaviour).
|
|
9441
|
-
*/
|
|
9442
9412
|
/**
|
|
9443
|
-
*
|
|
9444
|
-
*
|
|
9445
|
-
*
|
|
9413
|
+
* Re-state a standing overflow at most this often (D465). The line fires when a
|
|
9414
|
+
* subscription STARTS overwriting, and again every 30 s while it still is — a
|
|
9415
|
+
* per-chunk warn is ~25 writes/s per camera of synchronous winston on the
|
|
9416
|
+
* broker's event loop, which is the cost the `pullPcm` warn coalescing exists
|
|
9417
|
+
* to avoid.
|
|
9446
9418
|
*/
|
|
9447
|
-
var
|
|
9419
|
+
var OVERFLOW_RELOG_MS = 3e4;
|
|
9448
9420
|
/**
|
|
9449
9421
|
* How long a subscription may go without a `pull` before the plane releases it
|
|
9450
9422
|
* (D461).
|
|
@@ -9515,10 +9487,13 @@ var AudioChunkPlane = class {
|
|
|
9515
9487
|
id: subscriptionId,
|
|
9516
9488
|
tag: input?.tag ?? "unknown",
|
|
9517
9489
|
subscribedAt: Date.now(),
|
|
9518
|
-
queue: new require_dist.RingBuffer(
|
|
9490
|
+
queue: new require_dist.RingBuffer(64),
|
|
9519
9491
|
accept: new Set(input?.accept ?? []),
|
|
9520
9492
|
servedKey: null,
|
|
9521
9493
|
chunksDelivered: 0,
|
|
9494
|
+
chunksDropped: 0,
|
|
9495
|
+
overflowing: false,
|
|
9496
|
+
lastOverflowLogAt: 0,
|
|
9522
9497
|
lastPulledAt: Date.now()
|
|
9523
9498
|
};
|
|
9524
9499
|
this.subscriptions.set(subscriptionId, subscription);
|
|
@@ -9570,6 +9545,7 @@ var AudioChunkPlane = class {
|
|
|
9570
9545
|
leaseMs: AUDIO_SUBSCRIPTION_IDLE_MS,
|
|
9571
9546
|
queued: subscription.queue.size,
|
|
9572
9547
|
chunksDelivered: subscription.chunksDelivered,
|
|
9548
|
+
chunksDropped: subscription.chunksDropped,
|
|
9573
9549
|
hint: "the consumer process died without detaching — its subscription was holding this broker in demand"
|
|
9574
9550
|
} });
|
|
9575
9551
|
}
|
|
@@ -9605,13 +9581,47 @@ var AudioChunkPlane = class {
|
|
|
9605
9581
|
for (const subscription of this.subscriptions.values()) {
|
|
9606
9582
|
if (sourceFormat === "f32le" || subscription.accept.has(sourceFormat)) {
|
|
9607
9583
|
this.noteServed(subscription, sourceFormat, sourceFormat);
|
|
9608
|
-
|
|
9584
|
+
this.enqueue(subscription, chunk);
|
|
9609
9585
|
continue;
|
|
9610
9586
|
}
|
|
9611
9587
|
expanded ??= expandChunk(chunk, sourceFormat);
|
|
9612
9588
|
this.noteServed(subscription, sourceFormat, "f32le");
|
|
9613
|
-
|
|
9589
|
+
this.enqueue(subscription, expanded);
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
/**
|
|
9593
|
+
* Push one chunk into a subscription's FIFO and NAME the loss when the FIFO
|
|
9594
|
+
* had no room (D465).
|
|
9595
|
+
*
|
|
9596
|
+
* `RingBuffer.push` overwrites its oldest entry. Every docblock on this path
|
|
9597
|
+
* called that "a bounded safety valve, not the steady-state behaviour" and
|
|
9598
|
+
* nothing ever said when the valve opened, so a consumer polling too slowly
|
|
9599
|
+
* for its producer lost audio permanently and in silence — the exact shape
|
|
9600
|
+
* CLAUDE.md forbids. The realistic cause is a cadence chosen for the
|
|
9601
|
+
* BOUNDARY rather than for the producer's chunk rate: 500 ms of a ~42 ms AAC
|
|
9602
|
+
* chunk is ~12 chunks against a batch that used to drain 8.
|
|
9603
|
+
*/
|
|
9604
|
+
enqueue(subscription, chunk) {
|
|
9605
|
+
const wasFull = subscription.queue.size >= 64;
|
|
9606
|
+
subscription.queue.push(chunk);
|
|
9607
|
+
if (!wasFull) {
|
|
9608
|
+
subscription.overflowing = false;
|
|
9609
|
+
return;
|
|
9614
9610
|
}
|
|
9611
|
+
subscription.chunksDropped += 1;
|
|
9612
|
+
const now = Date.now();
|
|
9613
|
+
const isNewEpisode = !subscription.overflowing;
|
|
9614
|
+
subscription.overflowing = true;
|
|
9615
|
+
if (!isNewEpisode && now - subscription.lastOverflowLogAt < OVERFLOW_RELOG_MS) return;
|
|
9616
|
+
subscription.lastOverflowLogAt = now;
|
|
9617
|
+
this.logger?.warn("audio-chunk subscription OVERFLOW — oldest audio overwritten", { meta: {
|
|
9618
|
+
subscriptionId: subscription.id,
|
|
9619
|
+
tag: subscription.tag,
|
|
9620
|
+
capacity: 64,
|
|
9621
|
+
chunksDropped: subscription.chunksDropped,
|
|
9622
|
+
chunksDelivered: subscription.chunksDelivered,
|
|
9623
|
+
hint: "the consumer is pulling slower than this camera produces — its poll cadence or drain batch is wrong for the producer, not the boundary"
|
|
9624
|
+
} });
|
|
9615
9625
|
}
|
|
9616
9626
|
/**
|
|
9617
9627
|
* Run the idle-lease sweep on a cadence rather than on every chunk. A quarter
|
|
@@ -9669,10 +9679,6 @@ var AudioChunkPlane = class {
|
|
|
9669
9679
|
this.subscriptions.clear();
|
|
9670
9680
|
}
|
|
9671
9681
|
};
|
|
9672
|
-
//#endregion
|
|
9673
|
-
//#region src/stream-broker/stream-broker/audio-codec-session.ts
|
|
9674
|
-
var POLL_INTERVAL_MS$1 = 40;
|
|
9675
|
-
var MAX_PCM_PER_POLL = 16;
|
|
9676
9682
|
var RTP_HEADER_LENGTH = 12;
|
|
9677
9683
|
var WARN_THROTTLE_MS$1 = 5e3;
|
|
9678
9684
|
var SESSION_NOT_FOUND_RE$1 = /decode session\b.*\bnot found\b/;
|
|
@@ -9684,19 +9690,23 @@ var AudioCodecSession = class {
|
|
|
9684
9690
|
cfg;
|
|
9685
9691
|
emit;
|
|
9686
9692
|
logger;
|
|
9693
|
+
realtimeDemand;
|
|
9687
9694
|
sessionId = null;
|
|
9688
9695
|
sessionNodeId = null;
|
|
9689
9696
|
creating = null;
|
|
9690
9697
|
pollTimer = null;
|
|
9698
|
+
/** The cadence the loop last armed, so a change can be NAMED once (D465). */
|
|
9699
|
+
pollIntervalMs = null;
|
|
9691
9700
|
closed = false;
|
|
9692
9701
|
packetsIn = 0;
|
|
9693
9702
|
packetsDropped = 0;
|
|
9694
9703
|
warnState = /* @__PURE__ */ new Map();
|
|
9695
|
-
constructor(api, cfg, emit, logger) {
|
|
9704
|
+
constructor(api, cfg, emit, logger, realtimeDemand) {
|
|
9696
9705
|
this.api = api;
|
|
9697
9706
|
this.cfg = cfg;
|
|
9698
9707
|
this.emit = emit;
|
|
9699
9708
|
this.logger = logger;
|
|
9709
|
+
this.realtimeDemand = realtimeDemand;
|
|
9700
9710
|
}
|
|
9701
9711
|
processPacket(rtpData) {
|
|
9702
9712
|
if (this.closed) return;
|
|
@@ -9764,7 +9774,7 @@ var AudioCodecSession = class {
|
|
|
9764
9774
|
if (this.creating) await this.creating;
|
|
9765
9775
|
if (this.closed) return;
|
|
9766
9776
|
if (this.pollTimer) {
|
|
9767
|
-
|
|
9777
|
+
clearTimeout(this.pollTimer);
|
|
9768
9778
|
this.pollTimer = null;
|
|
9769
9779
|
}
|
|
9770
9780
|
if (!this.sessionId) return;
|
|
@@ -9786,7 +9796,7 @@ var AudioCodecSession = class {
|
|
|
9786
9796
|
if (this.closed) return;
|
|
9787
9797
|
this.closed = true;
|
|
9788
9798
|
if (this.pollTimer) {
|
|
9789
|
-
|
|
9799
|
+
clearTimeout(this.pollTimer);
|
|
9790
9800
|
this.pollTimer = null;
|
|
9791
9801
|
}
|
|
9792
9802
|
if (this.sessionId) {
|
|
@@ -9852,11 +9862,43 @@ var AudioCodecSession = class {
|
|
|
9852
9862
|
})();
|
|
9853
9863
|
return this.creating;
|
|
9854
9864
|
}
|
|
9865
|
+
/**
|
|
9866
|
+
* The cadence this tick should use. Re-read every tick rather than latched at
|
|
9867
|
+
* arm time: a WebRTC session attaching mid-stream must be served at
|
|
9868
|
+
* {@link REALTIME_POLL_INTERVAL_MS}, and it costs at most one idle interval
|
|
9869
|
+
* (400 ms) to notice — against an ICE/DTLS handshake measured in seconds.
|
|
9870
|
+
*/
|
|
9871
|
+
currentPollIntervalMs() {
|
|
9872
|
+
if (!this.realtimeDemand) return 40;
|
|
9873
|
+
return this.realtimeDemand() ? 40 : 400;
|
|
9874
|
+
}
|
|
9855
9875
|
startPollLoop() {
|
|
9856
9876
|
if (this.pollTimer || this.closed) return;
|
|
9857
|
-
this.
|
|
9858
|
-
|
|
9859
|
-
|
|
9877
|
+
this.scheduleNextPoll();
|
|
9878
|
+
}
|
|
9879
|
+
/**
|
|
9880
|
+
* Self-scheduling tick. `setInterval` cannot change period, and the period is
|
|
9881
|
+
* now a property of who is listening (D465), so the loop re-arms itself.
|
|
9882
|
+
*/
|
|
9883
|
+
scheduleNextPoll() {
|
|
9884
|
+
if (this.closed || !this.sessionId) return;
|
|
9885
|
+
const intervalMs = this.currentPollIntervalMs();
|
|
9886
|
+
if (intervalMs !== this.pollIntervalMs) {
|
|
9887
|
+
const previous = this.pollIntervalMs;
|
|
9888
|
+
this.pollIntervalMs = intervalMs;
|
|
9889
|
+
this.logger?.info("audio-codec: poll cadence", { meta: {
|
|
9890
|
+
intervalMs,
|
|
9891
|
+
previousMs: previous,
|
|
9892
|
+
reason: intervalMs === 40 ? "realtime-consumer" : "plane-only",
|
|
9893
|
+
maxPcmPerPoll: 32
|
|
9894
|
+
} });
|
|
9895
|
+
}
|
|
9896
|
+
this.pollTimer = setTimeout(() => {
|
|
9897
|
+
this.pollTimer = null;
|
|
9898
|
+
this.pollOnce().finally(() => {
|
|
9899
|
+
this.scheduleNextPoll();
|
|
9900
|
+
});
|
|
9901
|
+
}, intervalMs);
|
|
9860
9902
|
if (typeof this.pollTimer.unref === "function") this.pollTimer.unref();
|
|
9861
9903
|
}
|
|
9862
9904
|
async pollOnce() {
|
|
@@ -9867,7 +9909,7 @@ var AudioCodecSession = class {
|
|
|
9867
9909
|
const pcms = await this.api.pullPcm({
|
|
9868
9910
|
sessionId: sid,
|
|
9869
9911
|
...nodeId ? { nodeId } : {},
|
|
9870
|
-
maxCount:
|
|
9912
|
+
maxCount: 32
|
|
9871
9913
|
});
|
|
9872
9914
|
for (const pcm of pcms) this.appendPcm(pcm);
|
|
9873
9915
|
} catch (err) {
|
|
@@ -9911,7 +9953,7 @@ var AudioCodecSession = class {
|
|
|
9911
9953
|
this.sessionId = null;
|
|
9912
9954
|
this.sessionNodeId = null;
|
|
9913
9955
|
if (this.pollTimer) {
|
|
9914
|
-
|
|
9956
|
+
clearTimeout(this.pollTimer);
|
|
9915
9957
|
this.pollTimer = null;
|
|
9916
9958
|
}
|
|
9917
9959
|
this.warnThrottled("audio-codec: session reaped upstream — will re-create on next packet", `staleSessionId=${staleSid}`);
|
|
@@ -12619,7 +12661,7 @@ var StreamBroker = class StreamBroker {
|
|
|
12619
12661
|
this.logger?.debug("audio-codec: prior session close failed", { meta: { error: require_dist.errMsg(err) } });
|
|
12620
12662
|
});
|
|
12621
12663
|
}
|
|
12622
|
-
this.audioCodecSession = new AudioCodecSession(this.audioCodecApi, cfg, this.buildAudioCodecEmitCallback(), this.logger?.child("audio-codec"));
|
|
12664
|
+
this.audioCodecSession = new AudioCodecSession(this.audioCodecApi, cfg, this.buildAudioCodecEmitCallback(), this.logger?.child("audio-codec"), () => this.encodedCallbacks.size > 0);
|
|
12623
12665
|
this.logger?.info("push-audio: decode session armed", { meta: {
|
|
12624
12666
|
codec: cfg.codec,
|
|
12625
12667
|
sourceSampleRate: cfg.sourceSampleRate,
|
|
@@ -13825,7 +13867,7 @@ var StreamBroker = class StreamBroker {
|
|
|
13825
13867
|
this.logger?.debug("audio-codec: prior RTSP session close failed", { meta: { error: require_dist.errMsg(err) } });
|
|
13826
13868
|
});
|
|
13827
13869
|
}
|
|
13828
|
-
this.audioCodecSession = new AudioCodecSession(this.audioCodecApi, capCfg, this.buildAudioCodecEmitCallback(), this.logger?.child("audio-codec"));
|
|
13870
|
+
this.audioCodecSession = new AudioCodecSession(this.audioCodecApi, capCfg, this.buildAudioCodecEmitCallback(), this.logger?.child("audio-codec"), () => this.encodedCallbacks.size > 0);
|
|
13829
13871
|
this.logger?.info("audio-codec: decode session armed", { meta: {
|
|
13830
13872
|
codec: capCfg.codec,
|
|
13831
13873
|
sourceSampleRate: capCfg.sourceSampleRate,
|