@camstack/addon-pipeline 1.1.31 → 1.1.33
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 +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/audio-codec-ffmpeg/index.js +118 -3
- package/dist/audio-codec-ffmpeg/index.mjs +118 -3
- package/dist/decoder-ffmpeg/index.js +74 -9
- package/dist/decoder-ffmpeg/index.mjs +74 -9
- package/dist/decoder-nodeav/index.js +218 -16
- package/dist/decoder-nodeav/index.mjs +218 -16
- package/dist/detection-pipeline/index.js +34 -3
- package/dist/detection-pipeline/index.mjs +34 -3
- package/dist/{dist-Cwc0TUQr.js → dist-BrIE85BG.js} +38 -1
- package/dist/{dist-DjuGmyG9.mjs → dist-CfSORkv2.mjs} +38 -1
- package/dist/{ffmpeg-args-C5GPp8Cw.mjs → ffmpeg-args-CpeRWwL4.mjs} +7 -4
- package/dist/{ffmpeg-args-D6h1edXK.js → ffmpeg-args-Dl9mLcOC.js} +7 -4
- package/dist/{frame-handle-plane-Bkxz-TTD.mjs → frame-handle-plane-C05rq9Zs.mjs} +108 -17
- package/dist/{frame-handle-plane-DQNCTrpC.js → frame-handle-plane-zMju9vmk.js} +108 -17
- package/dist/{frame-ring-sink-8LLV-cvH.js → frame-ring-sink-B-DxHelU.js} +175 -22
- package/dist/{frame-ring-sink-ClEWjiRU.mjs → frame-ring-sink-B_WjDX0x.mjs} +164 -23
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +86 -16
- package/dist/pipeline-runner/index.mjs +85 -17
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DPFAbcUo.mjs} +3 -3
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BsrrdOBU.mjs} +1 -1
- package/dist/stream-broker/{hostInit-B_b3PIZB.mjs → hostInit-C8slrxw1.mjs} +3 -3
- package/dist/stream-broker/index.js +165 -27
- package/dist/stream-broker/index.mjs +165 -27
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js → MaskShapeCanvas-DI4BY7W2-WUVfFzT-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DTeuDGCU.js → MotionZonesSettings-NcxxQN8r-CF7FXXLs.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-CirtxO3e.js → PrivacyMaskSettings-APgPLF7p-qQCpVob3.js} +1 -1
- package/embed-dist/assets/{index-CpUy8OIE.js → index-BNhisVUZ.js} +4 -4
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
- package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/dist/constants-CAqYVigN.js +0 -6615
- package/dist/constants-oYgW36Ty.mjs +0 -3587
- package/dist/dist-D3Ytt-_Y.js +0 -41111
- package/dist/dist-DluJuxV5.mjs +0 -37976
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-DxM2DSns.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_frame_handle_plane = require("../frame-handle-plane-
|
|
8
|
-
const require_ffmpeg_args = require("../ffmpeg-args-
|
|
6
|
+
const require_dist = require("../dist-BrIE85BG.js");
|
|
7
|
+
const require_frame_handle_plane = require("../frame-handle-plane-zMju9vmk.js");
|
|
8
|
+
const require_ffmpeg_args = require("../ffmpeg-args-Dl9mLcOC.js");
|
|
9
9
|
const require_frame_dropper = require("../frame-dropper-DKLM6pMz.js");
|
|
10
10
|
let node_crypto = require("node:crypto");
|
|
11
11
|
node_crypto = require_model_download_service_C_IHWnXx.__toESM(node_crypto);
|
|
@@ -3949,14 +3949,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
3949
3949
|
const frame = mediaFrame.frame;
|
|
3950
3950
|
if (!this.audioSender) continue;
|
|
3951
3951
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
3952
|
-
if (this.debug) this.logger.info("audio frame in (direct-feed)", { meta: {
|
|
3953
|
-
phase: "session",
|
|
3954
|
-
sessionId: this.sessionId,
|
|
3955
|
-
timestampMicros: frame.timestampMicros,
|
|
3956
|
-
dataLen: frame.data.length,
|
|
3957
|
-
computedRtpTs: rtpTs,
|
|
3958
|
-
codec: frame.codec
|
|
3959
|
-
} });
|
|
3960
3952
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
3961
3953
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
3962
3954
|
}
|
|
@@ -4076,14 +4068,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
4076
4068
|
if (mediaFrame.type === "audio") {
|
|
4077
4069
|
const frame = mediaFrame.frame;
|
|
4078
4070
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
4079
|
-
if (this.debug) this.logger.info("audio frame in (transcode passthrough)", { meta: {
|
|
4080
|
-
phase: "session",
|
|
4081
|
-
sessionId: this.sessionId,
|
|
4082
|
-
timestampMicros: frame.timestampMicros,
|
|
4083
|
-
dataLen: frame.data.length,
|
|
4084
|
-
computedRtpTs: rtpTs,
|
|
4085
|
-
codec: frame.codec
|
|
4086
|
-
} });
|
|
4087
4071
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
4088
4072
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
4089
4073
|
}
|
|
@@ -8788,6 +8772,77 @@ var EncodedRingBuffer = class {
|
|
|
8788
8772
|
}
|
|
8789
8773
|
};
|
|
8790
8774
|
//#endregion
|
|
8775
|
+
//#region src/stream-broker/stream-broker/annexb-param-sets.ts
|
|
8776
|
+
/**
|
|
8777
|
+
* Annex-B parameter-set detection for the decoder-priming path.
|
|
8778
|
+
*
|
|
8779
|
+
* The frame-handle-plane decoder (`ffmpeg -f h264|hevc -i pipe:0`) initialises
|
|
8780
|
+
* only once it sees the codec parameter sets (H.264 SPS+PPS / H.265 SPS+PPS,
|
|
8781
|
+
* plus VPS) in its Annex-B input. Most cameras carry them in-band with each
|
|
8782
|
+
* IDR; some (Reolink high-profile native streams) ship them ONLY in the SDP.
|
|
8783
|
+
* The broker harvests those SDP param sets (`sdpParameterSets`) but must not
|
|
8784
|
+
* blindly re-inject them ahead of every keyframe — that would duplicate the
|
|
8785
|
+
* sets for well-behaved cameras. This scan lets the broker inject only when a
|
|
8786
|
+
* keyframe's own NAL stream is missing them.
|
|
8787
|
+
*
|
|
8788
|
+
* Pure byte scanning: no allocation beyond the walk; safe on any Annex-B blob.
|
|
8789
|
+
*/
|
|
8790
|
+
/** Required parameter-set NAL types per codec (SPS, PPS). */
|
|
8791
|
+
var H264_SPS = 7;
|
|
8792
|
+
var H264_PPS = 8;
|
|
8793
|
+
var H265_SPS = 33;
|
|
8794
|
+
var H265_PPS = 34;
|
|
8795
|
+
function isH265Codec(codec) {
|
|
8796
|
+
const c = codec.toLowerCase();
|
|
8797
|
+
return c.includes("265") || c.includes("hevc");
|
|
8798
|
+
}
|
|
8799
|
+
/**
|
|
8800
|
+
* True when `data` already carries BOTH the SPS and the PPS the decoder needs
|
|
8801
|
+
* to initialise (per `codec`). Walks Annex-B NAL units by their 3- or 4-byte
|
|
8802
|
+
* start codes and reads each NAL header type. Returns as soon as both are seen.
|
|
8803
|
+
*/
|
|
8804
|
+
function annexBHasParameterSets(data, codec) {
|
|
8805
|
+
const h265 = isH265Codec(codec);
|
|
8806
|
+
const spsType = h265 ? H265_SPS : H264_SPS;
|
|
8807
|
+
const ppsType = h265 ? H265_PPS : H264_PPS;
|
|
8808
|
+
let hasSps = false;
|
|
8809
|
+
let hasPps = false;
|
|
8810
|
+
const len = data.length;
|
|
8811
|
+
let i = 0;
|
|
8812
|
+
while (i + 3 < len) {
|
|
8813
|
+
if (data[i] === 0 && data[i + 1] === 0) {
|
|
8814
|
+
let nalStart = -1;
|
|
8815
|
+
if (data[i + 2] === 1) nalStart = i + 3;
|
|
8816
|
+
else if (data[i + 2] === 0 && data[i + 3] === 1) nalStart = i + 4;
|
|
8817
|
+
if (nalStart >= 0 && nalStart < len) {
|
|
8818
|
+
const headerByte = data[nalStart];
|
|
8819
|
+
const nalType = h265 ? headerByte >> 1 & 63 : headerByte & 31;
|
|
8820
|
+
if (nalType === spsType) hasSps = true;
|
|
8821
|
+
else if (nalType === ppsType) hasPps = true;
|
|
8822
|
+
if (hasSps && hasPps) return true;
|
|
8823
|
+
i = nalStart + 1;
|
|
8824
|
+
continue;
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
i += 1;
|
|
8828
|
+
}
|
|
8829
|
+
return hasSps && hasPps;
|
|
8830
|
+
}
|
|
8831
|
+
/**
|
|
8832
|
+
* Concatenate raw parameter-set NAL payloads into a single Annex-B buffer
|
|
8833
|
+
* (each prefixed with a 4-byte start code) — the shape the decoder's stdin
|
|
8834
|
+
* expects. Mirrors the existing SDP-param-set replay for encoded subscribers.
|
|
8835
|
+
*/
|
|
8836
|
+
function concatParameterSetsAnnexB(parameterSets) {
|
|
8837
|
+
const startCode = Buffer.from([
|
|
8838
|
+
0,
|
|
8839
|
+
0,
|
|
8840
|
+
0,
|
|
8841
|
+
1
|
|
8842
|
+
]);
|
|
8843
|
+
return Buffer.concat(parameterSets.flatMap((ps) => [startCode, ps]));
|
|
8844
|
+
}
|
|
8845
|
+
//#endregion
|
|
8791
8846
|
//#region src/stream-broker/stream-broker/g711-mulaw.ts
|
|
8792
8847
|
/**
|
|
8793
8848
|
* G.711 µ-law encoder — converts 16-bit signed PCM samples to µ-law (ITU-T G.711).
|
|
@@ -9008,6 +9063,8 @@ var StreamBroker = class StreamBroker {
|
|
|
9008
9063
|
* falls back to the source passed to `start()`.
|
|
9009
9064
|
*/
|
|
9010
9065
|
sourceProvider = null;
|
|
9066
|
+
/** Resolver for this broker's own RTSP restream URL — see `setRestreamUrlResolver`. */
|
|
9067
|
+
restreamUrlResolver = null;
|
|
9011
9068
|
/**
|
|
9012
9069
|
* Manager-supplied notifier that asks the publishing addon to
|
|
9013
9070
|
* refresh its loopback transport (e.g. re-run
|
|
@@ -9141,6 +9198,17 @@ var StreamBroker = class StreamBroker {
|
|
|
9141
9198
|
pipeServer;
|
|
9142
9199
|
rtspRestreamer;
|
|
9143
9200
|
preBuffer;
|
|
9201
|
+
/**
|
|
9202
|
+
* Keyframe-aligned GOP of the EXACT packets fed to the decoder plane (i.e.
|
|
9203
|
+
* param-set prepended). Replayed to a freshly-armed shm decoder session so it
|
|
9204
|
+
* initialises on a keyframe (SPS/PPS) instead of the next live P-frame — the
|
|
9205
|
+
* decoder cold-start fix for push-mode sessions. Populated for ALL source
|
|
9206
|
+
* types (unlike `preBuffer`, which push-only sources fill for the WebRTC
|
|
9207
|
+
* late-join path): an rfc4571 Reolink source has an EMPTY `preBuffer`, so this
|
|
9208
|
+
* is the only GOP available to seed its decoder. `getPackets()` returns just
|
|
9209
|
+
* the current GOP (from the last keyframe), so a modest retention suffices.
|
|
9210
|
+
*/
|
|
9211
|
+
decoderSeedBuffer;
|
|
9144
9212
|
encodedCallbacks = /* @__PURE__ */ new Map();
|
|
9145
9213
|
/**
|
|
9146
9214
|
* Source-RTP video subscribers — receive raw on-wire RTP bytes
|
|
@@ -9251,6 +9319,13 @@ var StreamBroker = class StreamBroker {
|
|
|
9251
9319
|
static DEFAULT_PRE_BUFFER_SEC = 10;
|
|
9252
9320
|
/** Maximum allowed pre-buffer duration in seconds */
|
|
9253
9321
|
static MAX_PRE_BUFFER_SEC = 30;
|
|
9322
|
+
/**
|
|
9323
|
+
* Retention for the decoder-seed GOP ring. Only ever needs to hold the
|
|
9324
|
+
* current GOP (a fresh keyframe evicts the previous GOP on read), so a few
|
|
9325
|
+
* seconds covers every realistic keyframe interval; cameras with a GOP longer
|
|
9326
|
+
* than this fall back to the plane's live-keyframe bootstrap.
|
|
9327
|
+
*/
|
|
9328
|
+
static DECODER_SEED_BUFFER_SEC = 6;
|
|
9254
9329
|
constructor(deviceId, decoderApi, logger, audioCodecApi, localNodeId) {
|
|
9255
9330
|
this.deviceId = deviceId;
|
|
9256
9331
|
this.decoderApi = decoderApi;
|
|
@@ -9261,6 +9336,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9261
9336
|
this.rtspRestreamer = new RtspRestreamer(deviceId);
|
|
9262
9337
|
if (logger) this.rtspRestreamer.setLogger(logger.child("rtsp"));
|
|
9263
9338
|
this.preBuffer = new EncodedRingBuffer(StreamBroker.DEFAULT_PRE_BUFFER_SEC);
|
|
9339
|
+
this.decoderSeedBuffer = new EncodedRingBuffer(StreamBroker.DECODER_SEED_BUFFER_SEC);
|
|
9264
9340
|
this.audioChunkPlane = new AudioChunkPlane(logger?.child("audio-chunk-plane"));
|
|
9265
9341
|
this.rtspRestreamer.onSessionCountChanged(() => this.checkDemand());
|
|
9266
9342
|
this.pipeServer.onClientCountChanged(() => this.checkDemand());
|
|
@@ -9298,6 +9374,22 @@ var StreamBroker = class StreamBroker {
|
|
|
9298
9374
|
this.restreamers = restreamers;
|
|
9299
9375
|
}
|
|
9300
9376
|
/**
|
|
9377
|
+
* Inject a manager-owned resolver for this broker's OWN RTSP restream URL
|
|
9378
|
+
* (`rtsp://127.0.0.1:<port>/<token>[/muted]`). When the source is an RTP
|
|
9379
|
+
* source ({@link isRtpSource}), the frame-handle decoder decodes via PULL
|
|
9380
|
+
* mode from this restream instead of the broker's hand-depacketized AnnexB
|
|
9381
|
+
* push feed — ffmpeg's own RTSP/RTP demuxer does the FU-A reassembly, which
|
|
9382
|
+
* is robust where the AnnexB depacketizer chokes on heavily-fragmented 4K
|
|
9383
|
+
* NALs (Reolink rfc4571 produced 0 decoded frames on the push path; the same
|
|
9384
|
+
* stream decodes at full fps through the RTSP restream). Mirrors Scrypted's
|
|
9385
|
+
* rebroadcast (prebuffer-mixin) which passes RTP through to the consumer's
|
|
9386
|
+
* demuxer. Returns `null` before a restream token exists → falls back to the
|
|
9387
|
+
* push path.
|
|
9388
|
+
*/
|
|
9389
|
+
setRestreamUrlResolver(resolver) {
|
|
9390
|
+
this.restreamUrlResolver = resolver;
|
|
9391
|
+
}
|
|
9392
|
+
/**
|
|
9301
9393
|
* Inject the resolved `ffmpeg` binary path used by the "ffmpeg parser"
|
|
9302
9394
|
* ingress ({@link startFfmpegSourceReader}). Threaded from the manager's
|
|
9303
9395
|
* system-config (`ffmpeg.binaryPath`). Absent = bare `'ffmpeg'` (PATH lookup).
|
|
@@ -9451,6 +9543,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9451
9543
|
await this.pipeServer.stop();
|
|
9452
9544
|
this.rtspRestreamer.destroy();
|
|
9453
9545
|
this.preBuffer.clear();
|
|
9546
|
+
this.decoderSeedBuffer.clear();
|
|
9454
9547
|
this.pendingParamNals = [];
|
|
9455
9548
|
this.firstKeyframeReceived = false;
|
|
9456
9549
|
this.encodedCallbacks.clear();
|
|
@@ -9547,6 +9640,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9547
9640
|
this.placeholderTimer = void 0;
|
|
9548
9641
|
}
|
|
9549
9642
|
this.preBuffer.clear();
|
|
9643
|
+
this.decoderSeedBuffer.clear();
|
|
9550
9644
|
this.firstKeyframeReceived = false;
|
|
9551
9645
|
this.pendingParamNals = [];
|
|
9552
9646
|
this.rtspRestreamer.resetStreamState();
|
|
@@ -9680,7 +9774,46 @@ var StreamBroker = class StreamBroker {
|
|
|
9680
9774
|
const streamId = `${this.deviceId}/video`;
|
|
9681
9775
|
restreamer.pushPacket(streamId, packet);
|
|
9682
9776
|
}
|
|
9683
|
-
if (packet.type === "video" && this.firstKeyframeReceived)
|
|
9777
|
+
if (packet.type === "video" && this.firstKeyframeReceived) {
|
|
9778
|
+
const decoderPacket = this.decoderPacketWithParamSets(packet);
|
|
9779
|
+
this.decoderSeedBuffer.push(decoderPacket);
|
|
9780
|
+
this.frameHandlePlane?.pushPacket(decoderPacket);
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
/**
|
|
9784
|
+
* Return the packet to feed the decoder plane, prepending the SDP-harvested
|
|
9785
|
+
* SPS/PPS to a keyframe that lacks them in-band.
|
|
9786
|
+
*
|
|
9787
|
+
* The plane seeds a freshly-armed decoder session with the SINGLE keyframe
|
|
9788
|
+
* packet that triggered the arm (`armPendingSessions`), so the parameter sets
|
|
9789
|
+
* MUST travel inside that same packet — a separate param-set packet pushed
|
|
9790
|
+
* just before it reaches only already-live sessions and is lost on the
|
|
9791
|
+
* arming one. Unlike the encoded-subscriber path (`emitSdpParamSetsTo`), the
|
|
9792
|
+
* decoder plane is otherwise never handed `sdpParameterSets`. Cameras that
|
|
9793
|
+
* ship parameter sets ONLY in the SDP (Reolink high-profile native streams)
|
|
9794
|
+
* therefore starve the `ffmpeg -i pipe:0` decoder of SPS/PPS, which loops
|
|
9795
|
+
* `non-existing PPS → no frame` and produces zero decoded frames (verified
|
|
9796
|
+
* live on device 592: WebRTC worked, detection decoded nothing). Only
|
|
9797
|
+
* keyframes actually missing the sets are rewritten — well-behaved cameras
|
|
9798
|
+
* are returned untouched — and the check runs per keyframe so a decoder
|
|
9799
|
+
* restart mid-stream re-primes on the next IDR. The returned object is a
|
|
9800
|
+
* fresh copy; the original `packet` fanned out to the other consumers above
|
|
9801
|
+
* (WebRTC, restreamers, pre-buffer) is never mutated.
|
|
9802
|
+
*/
|
|
9803
|
+
decoderPacketWithParamSets(packet) {
|
|
9804
|
+
if (!packet.keyframe) return packet;
|
|
9805
|
+
const paramSets = this.sdpParameterSets;
|
|
9806
|
+
if (!paramSets || paramSets.length === 0) return packet;
|
|
9807
|
+
if (annexBHasParameterSets(packet.data, packet.codec)) return packet;
|
|
9808
|
+
if (this.streamingDebug) this.logger?.info("Prepending SDP param sets to decoder keyframe (in-band SPS/PPS absent)", { meta: {
|
|
9809
|
+
codec: packet.codec,
|
|
9810
|
+
psCount: paramSets.length,
|
|
9811
|
+
keyframeBytes: packet.data.length
|
|
9812
|
+
} });
|
|
9813
|
+
return {
|
|
9814
|
+
...packet,
|
|
9815
|
+
data: Buffer.concat([concatParameterSetsAnnexB(paramSets), packet.data])
|
|
9816
|
+
};
|
|
9684
9817
|
}
|
|
9685
9818
|
onEncodedData(callback, attribution = UNKNOWN_ATTRIBUTION) {
|
|
9686
9819
|
this.subscriberIdCounter += 1;
|
|
@@ -10021,11 +10154,12 @@ var StreamBroker = class StreamBroker {
|
|
|
10021
10154
|
if (!codec || !this.source) return null;
|
|
10022
10155
|
const slash = this.deviceId.indexOf("/");
|
|
10023
10156
|
const numericDeviceId = slash >= 0 ? Number.parseInt(this.deviceId.slice(0, slash), 10) : Number.parseInt(this.deviceId, 10);
|
|
10157
|
+
const pullModeUrl = this.isRtpSource() ? this.restreamUrlResolver?.() ?? null : null;
|
|
10024
10158
|
return {
|
|
10025
10159
|
codec,
|
|
10026
10160
|
...Number.isFinite(numericDeviceId) ? { numericDeviceId } : {},
|
|
10027
10161
|
tag: `broker:${this.deviceId}`,
|
|
10028
|
-
pullModeUrl
|
|
10162
|
+
pullModeUrl
|
|
10029
10163
|
};
|
|
10030
10164
|
}
|
|
10031
10165
|
/** Lazily construct the frame-handle plane, returning `null` with no decoder. */
|
|
@@ -10039,7 +10173,8 @@ var StreamBroker = class StreamBroker {
|
|
|
10039
10173
|
decoderApi: this.decoderApi,
|
|
10040
10174
|
logger: this.logger?.child("frame-handle-plane"),
|
|
10041
10175
|
resolveStreamInfo: () => this.resolveFrameHandleStreamInfo(),
|
|
10042
|
-
localNodeId: this.localNodeId
|
|
10176
|
+
localNodeId: this.localNodeId,
|
|
10177
|
+
getSeedPackets: () => this.decoderSeedBuffer.getPackets()
|
|
10043
10178
|
});
|
|
10044
10179
|
return this.frameHandlePlane;
|
|
10045
10180
|
}
|
|
@@ -12433,7 +12568,7 @@ var StreamBrokerManager = class {
|
|
|
12433
12568
|
const existing = this.upsertSourceCameraStream(deviceId, input);
|
|
12434
12569
|
const urlRefreshed = existing !== void 0 && input.url !== void 0 && existing.url !== input.url;
|
|
12435
12570
|
const wasAlreadyAssigned = (PUSH_KINDS.has(kind) ? findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId) : null) !== null && existing !== void 0;
|
|
12436
|
-
await this.reconcileAutoAssignment(deviceId, "publish");
|
|
12571
|
+
await this.reconcileAutoAssignment(deviceId, "publish", existing === void 0);
|
|
12437
12572
|
if (wasAlreadyAssigned) {
|
|
12438
12573
|
const finalProfile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12439
12574
|
if (finalProfile !== null) this.emitCamStreamDemand(deviceId, camStreamId, finalProfile);
|
|
@@ -12481,7 +12616,8 @@ var StreamBrokerManager = class {
|
|
|
12481
12616
|
const urlBefore = /* @__PURE__ */ new Map();
|
|
12482
12617
|
for (const d of sources) urlBefore.set(d.camStreamId, streamMapBefore?.get(d.camStreamId)?.url);
|
|
12483
12618
|
for (const d of sources) this.upsertSourceCameraStream(deviceId, d);
|
|
12484
|
-
|
|
12619
|
+
const anyNewStream = sources.some((d) => streamMapBefore?.get(d.camStreamId) === void 0);
|
|
12620
|
+
await this.reconcileAutoAssignment(deviceId, "publish", anyNewStream);
|
|
12485
12621
|
for (const camStreamId of reEmitDemandFor) {
|
|
12486
12622
|
const profile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12487
12623
|
if (profile !== null) this.emitCamStreamDemand(deviceId, camStreamId, profile);
|
|
@@ -13975,14 +14111,15 @@ var StreamBrokerManager = class {
|
|
|
13975
14111
|
* assignment). When the device's assignment is operator-locked
|
|
13976
14112
|
* (`auto === false`) the existing map is re-applied unchanged.
|
|
13977
14113
|
*/
|
|
13978
|
-
async reconcileAutoAssignment(deviceId, reason) {
|
|
14114
|
+
async reconcileAutoAssignment(deviceId, reason, streamAdded = false) {
|
|
13979
14115
|
const current = this.assignments.get(deviceId);
|
|
13980
14116
|
const streams = [...this.cameraStreams.get(deviceId)?.values() ?? []].toSorted((a, b) => a.order - b.order);
|
|
13981
14117
|
const isAuto = current?.auto ?? true;
|
|
13982
14118
|
const newMap = !current || isAuto && this.wouldChangeAutoMap(current.map, streams) ? this.computeInitialAssignment(streams) : current.map;
|
|
13983
14119
|
await this.applyAssignmentUpdate(deviceId, newMap, {
|
|
13984
14120
|
auto: isAuto,
|
|
13985
|
-
reason
|
|
14121
|
+
reason,
|
|
14122
|
+
streamAdded
|
|
13986
14123
|
});
|
|
13987
14124
|
}
|
|
13988
14125
|
wouldChangeAutoMap(current, streams) {
|
|
@@ -14027,7 +14164,7 @@ var StreamBrokerManager = class {
|
|
|
14027
14164
|
if (anyChange || current.auto !== nextAssignment.auto) {
|
|
14028
14165
|
this.persistProfileMap();
|
|
14029
14166
|
this.emitProfileSlotsChanged(deviceId);
|
|
14030
|
-
} else if (opts.reason === "publish") this.emitProfileSlotsChanged(deviceId);
|
|
14167
|
+
} else if (opts.reason === "publish" && opts.streamAdded) this.emitProfileSlotsChanged(deviceId);
|
|
14031
14168
|
this.syncProfileRestreamers(deviceId);
|
|
14032
14169
|
}
|
|
14033
14170
|
countPushConsumers(map, deviceId) {
|
|
@@ -14218,6 +14355,7 @@ var StreamBrokerManager = class {
|
|
|
14218
14355
|
const source = this.buildSourceFromCamStream(cam);
|
|
14219
14356
|
const broker = new StreamBroker(brokerId, this.buildDecoderCapApi(deviceId), brokerLogger, buildAudioCodecCapApi(this.api), this.localNodeId);
|
|
14220
14357
|
broker.setRestreamers(this.restreamers);
|
|
14358
|
+
broker.setRestreamUrlResolver(() => this.rtspProvider.getEntry(brokerId)?.mutedUrl ?? null);
|
|
14221
14359
|
broker.setFfmpegBinaryPath(this.ffmpegConfig.binaryPath);
|
|
14222
14360
|
this.applyPreBufferConfig(broker, deviceId, camStreamId, cam);
|
|
14223
14361
|
if (this.deviceOverrides.get(deviceId)?.streamingDebug) broker.setStreamingDebug(true);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as errMsg, G as EventCategory, H as CAM_PROFILE_ORDER, J as makeProfileBrokerId, K as createEvent, L as streamBrokerCapability, O as maskUrlCredentials, U as DeviceFeature, V as BaseAddon, W as DeviceType, Y as makeSourceBrokerId, Z as parseProfileBrokerId, at as literal, ct as record, et as _enum, h as addonWidgetsSourceCapability, j as nodePin, l as EncodeProfileSchema, lt as string, nt as boolean, ot as number, p as RingBuffer, rt as discriminatedUnion, st as object, tt as array, ut as union, y as cameraStreamsCapability, z as webrtcSessionCapability } from "../dist-
|
|
3
|
-
import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-
|
|
2
|
+
import { B as errMsg, G as EventCategory, H as CAM_PROFILE_ORDER, J as makeProfileBrokerId, K as createEvent, L as streamBrokerCapability, O as maskUrlCredentials, U as DeviceFeature, V as BaseAddon, W as DeviceType, Y as makeSourceBrokerId, Z as parseProfileBrokerId, at as literal, ct as record, et as _enum, h as addonWidgetsSourceCapability, j as nodePin, l as EncodeProfileSchema, lt as string, nt as boolean, ot as number, p as RingBuffer, rt as discriminatedUnion, st as object, tt as array, ut as union, y as cameraStreamsCapability, z as webrtcSessionCapability } from "../dist-CfSORkv2.mjs";
|
|
3
|
+
import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-C05rq9Zs.mjs";
|
|
4
4
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
|
-
import { f as rankDecodeHwAccels, g as logBannerArgs, h as audioEncoderArgs } from "../ffmpeg-args-
|
|
5
|
+
import { f as rankDecodeHwAccels, g as logBannerArgs, h as audioEncoderArgs } from "../ffmpeg-args-CpeRWwL4.mjs";
|
|
6
6
|
import { t as FrameDropper } from "../frame-dropper-AjheBGMG.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import * as crypto$1 from "node:crypto";
|
|
@@ -3945,14 +3945,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
3945
3945
|
const frame = mediaFrame.frame;
|
|
3946
3946
|
if (!this.audioSender) continue;
|
|
3947
3947
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
3948
|
-
if (this.debug) this.logger.info("audio frame in (direct-feed)", { meta: {
|
|
3949
|
-
phase: "session",
|
|
3950
|
-
sessionId: this.sessionId,
|
|
3951
|
-
timestampMicros: frame.timestampMicros,
|
|
3952
|
-
dataLen: frame.data.length,
|
|
3953
|
-
computedRtpTs: rtpTs,
|
|
3954
|
-
codec: frame.codec
|
|
3955
|
-
} });
|
|
3956
3948
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
3957
3949
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
3958
3950
|
}
|
|
@@ -4072,14 +4064,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
4072
4064
|
if (mediaFrame.type === "audio") {
|
|
4073
4065
|
const frame = mediaFrame.frame;
|
|
4074
4066
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
4075
|
-
if (this.debug) this.logger.info("audio frame in (transcode passthrough)", { meta: {
|
|
4076
|
-
phase: "session",
|
|
4077
|
-
sessionId: this.sessionId,
|
|
4078
|
-
timestampMicros: frame.timestampMicros,
|
|
4079
|
-
dataLen: frame.data.length,
|
|
4080
|
-
computedRtpTs: rtpTs,
|
|
4081
|
-
codec: frame.codec
|
|
4082
|
-
} });
|
|
4083
4067
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
4084
4068
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
4085
4069
|
}
|
|
@@ -8784,6 +8768,77 @@ var EncodedRingBuffer = class {
|
|
|
8784
8768
|
}
|
|
8785
8769
|
};
|
|
8786
8770
|
//#endregion
|
|
8771
|
+
//#region src/stream-broker/stream-broker/annexb-param-sets.ts
|
|
8772
|
+
/**
|
|
8773
|
+
* Annex-B parameter-set detection for the decoder-priming path.
|
|
8774
|
+
*
|
|
8775
|
+
* The frame-handle-plane decoder (`ffmpeg -f h264|hevc -i pipe:0`) initialises
|
|
8776
|
+
* only once it sees the codec parameter sets (H.264 SPS+PPS / H.265 SPS+PPS,
|
|
8777
|
+
* plus VPS) in its Annex-B input. Most cameras carry them in-band with each
|
|
8778
|
+
* IDR; some (Reolink high-profile native streams) ship them ONLY in the SDP.
|
|
8779
|
+
* The broker harvests those SDP param sets (`sdpParameterSets`) but must not
|
|
8780
|
+
* blindly re-inject them ahead of every keyframe — that would duplicate the
|
|
8781
|
+
* sets for well-behaved cameras. This scan lets the broker inject only when a
|
|
8782
|
+
* keyframe's own NAL stream is missing them.
|
|
8783
|
+
*
|
|
8784
|
+
* Pure byte scanning: no allocation beyond the walk; safe on any Annex-B blob.
|
|
8785
|
+
*/
|
|
8786
|
+
/** Required parameter-set NAL types per codec (SPS, PPS). */
|
|
8787
|
+
var H264_SPS = 7;
|
|
8788
|
+
var H264_PPS = 8;
|
|
8789
|
+
var H265_SPS = 33;
|
|
8790
|
+
var H265_PPS = 34;
|
|
8791
|
+
function isH265Codec(codec) {
|
|
8792
|
+
const c = codec.toLowerCase();
|
|
8793
|
+
return c.includes("265") || c.includes("hevc");
|
|
8794
|
+
}
|
|
8795
|
+
/**
|
|
8796
|
+
* True when `data` already carries BOTH the SPS and the PPS the decoder needs
|
|
8797
|
+
* to initialise (per `codec`). Walks Annex-B NAL units by their 3- or 4-byte
|
|
8798
|
+
* start codes and reads each NAL header type. Returns as soon as both are seen.
|
|
8799
|
+
*/
|
|
8800
|
+
function annexBHasParameterSets(data, codec) {
|
|
8801
|
+
const h265 = isH265Codec(codec);
|
|
8802
|
+
const spsType = h265 ? H265_SPS : H264_SPS;
|
|
8803
|
+
const ppsType = h265 ? H265_PPS : H264_PPS;
|
|
8804
|
+
let hasSps = false;
|
|
8805
|
+
let hasPps = false;
|
|
8806
|
+
const len = data.length;
|
|
8807
|
+
let i = 0;
|
|
8808
|
+
while (i + 3 < len) {
|
|
8809
|
+
if (data[i] === 0 && data[i + 1] === 0) {
|
|
8810
|
+
let nalStart = -1;
|
|
8811
|
+
if (data[i + 2] === 1) nalStart = i + 3;
|
|
8812
|
+
else if (data[i + 2] === 0 && data[i + 3] === 1) nalStart = i + 4;
|
|
8813
|
+
if (nalStart >= 0 && nalStart < len) {
|
|
8814
|
+
const headerByte = data[nalStart];
|
|
8815
|
+
const nalType = h265 ? headerByte >> 1 & 63 : headerByte & 31;
|
|
8816
|
+
if (nalType === spsType) hasSps = true;
|
|
8817
|
+
else if (nalType === ppsType) hasPps = true;
|
|
8818
|
+
if (hasSps && hasPps) return true;
|
|
8819
|
+
i = nalStart + 1;
|
|
8820
|
+
continue;
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
i += 1;
|
|
8824
|
+
}
|
|
8825
|
+
return hasSps && hasPps;
|
|
8826
|
+
}
|
|
8827
|
+
/**
|
|
8828
|
+
* Concatenate raw parameter-set NAL payloads into a single Annex-B buffer
|
|
8829
|
+
* (each prefixed with a 4-byte start code) — the shape the decoder's stdin
|
|
8830
|
+
* expects. Mirrors the existing SDP-param-set replay for encoded subscribers.
|
|
8831
|
+
*/
|
|
8832
|
+
function concatParameterSetsAnnexB(parameterSets) {
|
|
8833
|
+
const startCode = Buffer.from([
|
|
8834
|
+
0,
|
|
8835
|
+
0,
|
|
8836
|
+
0,
|
|
8837
|
+
1
|
|
8838
|
+
]);
|
|
8839
|
+
return Buffer.concat(parameterSets.flatMap((ps) => [startCode, ps]));
|
|
8840
|
+
}
|
|
8841
|
+
//#endregion
|
|
8787
8842
|
//#region src/stream-broker/stream-broker/g711-mulaw.ts
|
|
8788
8843
|
/**
|
|
8789
8844
|
* G.711 µ-law encoder — converts 16-bit signed PCM samples to µ-law (ITU-T G.711).
|
|
@@ -9004,6 +9059,8 @@ var StreamBroker = class StreamBroker {
|
|
|
9004
9059
|
* falls back to the source passed to `start()`.
|
|
9005
9060
|
*/
|
|
9006
9061
|
sourceProvider = null;
|
|
9062
|
+
/** Resolver for this broker's own RTSP restream URL — see `setRestreamUrlResolver`. */
|
|
9063
|
+
restreamUrlResolver = null;
|
|
9007
9064
|
/**
|
|
9008
9065
|
* Manager-supplied notifier that asks the publishing addon to
|
|
9009
9066
|
* refresh its loopback transport (e.g. re-run
|
|
@@ -9137,6 +9194,17 @@ var StreamBroker = class StreamBroker {
|
|
|
9137
9194
|
pipeServer;
|
|
9138
9195
|
rtspRestreamer;
|
|
9139
9196
|
preBuffer;
|
|
9197
|
+
/**
|
|
9198
|
+
* Keyframe-aligned GOP of the EXACT packets fed to the decoder plane (i.e.
|
|
9199
|
+
* param-set prepended). Replayed to a freshly-armed shm decoder session so it
|
|
9200
|
+
* initialises on a keyframe (SPS/PPS) instead of the next live P-frame — the
|
|
9201
|
+
* decoder cold-start fix for push-mode sessions. Populated for ALL source
|
|
9202
|
+
* types (unlike `preBuffer`, which push-only sources fill for the WebRTC
|
|
9203
|
+
* late-join path): an rfc4571 Reolink source has an EMPTY `preBuffer`, so this
|
|
9204
|
+
* is the only GOP available to seed its decoder. `getPackets()` returns just
|
|
9205
|
+
* the current GOP (from the last keyframe), so a modest retention suffices.
|
|
9206
|
+
*/
|
|
9207
|
+
decoderSeedBuffer;
|
|
9140
9208
|
encodedCallbacks = /* @__PURE__ */ new Map();
|
|
9141
9209
|
/**
|
|
9142
9210
|
* Source-RTP video subscribers — receive raw on-wire RTP bytes
|
|
@@ -9247,6 +9315,13 @@ var StreamBroker = class StreamBroker {
|
|
|
9247
9315
|
static DEFAULT_PRE_BUFFER_SEC = 10;
|
|
9248
9316
|
/** Maximum allowed pre-buffer duration in seconds */
|
|
9249
9317
|
static MAX_PRE_BUFFER_SEC = 30;
|
|
9318
|
+
/**
|
|
9319
|
+
* Retention for the decoder-seed GOP ring. Only ever needs to hold the
|
|
9320
|
+
* current GOP (a fresh keyframe evicts the previous GOP on read), so a few
|
|
9321
|
+
* seconds covers every realistic keyframe interval; cameras with a GOP longer
|
|
9322
|
+
* than this fall back to the plane's live-keyframe bootstrap.
|
|
9323
|
+
*/
|
|
9324
|
+
static DECODER_SEED_BUFFER_SEC = 6;
|
|
9250
9325
|
constructor(deviceId, decoderApi, logger, audioCodecApi, localNodeId) {
|
|
9251
9326
|
this.deviceId = deviceId;
|
|
9252
9327
|
this.decoderApi = decoderApi;
|
|
@@ -9257,6 +9332,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9257
9332
|
this.rtspRestreamer = new RtspRestreamer(deviceId);
|
|
9258
9333
|
if (logger) this.rtspRestreamer.setLogger(logger.child("rtsp"));
|
|
9259
9334
|
this.preBuffer = new EncodedRingBuffer(StreamBroker.DEFAULT_PRE_BUFFER_SEC);
|
|
9335
|
+
this.decoderSeedBuffer = new EncodedRingBuffer(StreamBroker.DECODER_SEED_BUFFER_SEC);
|
|
9260
9336
|
this.audioChunkPlane = new AudioChunkPlane(logger?.child("audio-chunk-plane"));
|
|
9261
9337
|
this.rtspRestreamer.onSessionCountChanged(() => this.checkDemand());
|
|
9262
9338
|
this.pipeServer.onClientCountChanged(() => this.checkDemand());
|
|
@@ -9294,6 +9370,22 @@ var StreamBroker = class StreamBroker {
|
|
|
9294
9370
|
this.restreamers = restreamers;
|
|
9295
9371
|
}
|
|
9296
9372
|
/**
|
|
9373
|
+
* Inject a manager-owned resolver for this broker's OWN RTSP restream URL
|
|
9374
|
+
* (`rtsp://127.0.0.1:<port>/<token>[/muted]`). When the source is an RTP
|
|
9375
|
+
* source ({@link isRtpSource}), the frame-handle decoder decodes via PULL
|
|
9376
|
+
* mode from this restream instead of the broker's hand-depacketized AnnexB
|
|
9377
|
+
* push feed — ffmpeg's own RTSP/RTP demuxer does the FU-A reassembly, which
|
|
9378
|
+
* is robust where the AnnexB depacketizer chokes on heavily-fragmented 4K
|
|
9379
|
+
* NALs (Reolink rfc4571 produced 0 decoded frames on the push path; the same
|
|
9380
|
+
* stream decodes at full fps through the RTSP restream). Mirrors Scrypted's
|
|
9381
|
+
* rebroadcast (prebuffer-mixin) which passes RTP through to the consumer's
|
|
9382
|
+
* demuxer. Returns `null` before a restream token exists → falls back to the
|
|
9383
|
+
* push path.
|
|
9384
|
+
*/
|
|
9385
|
+
setRestreamUrlResolver(resolver) {
|
|
9386
|
+
this.restreamUrlResolver = resolver;
|
|
9387
|
+
}
|
|
9388
|
+
/**
|
|
9297
9389
|
* Inject the resolved `ffmpeg` binary path used by the "ffmpeg parser"
|
|
9298
9390
|
* ingress ({@link startFfmpegSourceReader}). Threaded from the manager's
|
|
9299
9391
|
* system-config (`ffmpeg.binaryPath`). Absent = bare `'ffmpeg'` (PATH lookup).
|
|
@@ -9447,6 +9539,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9447
9539
|
await this.pipeServer.stop();
|
|
9448
9540
|
this.rtspRestreamer.destroy();
|
|
9449
9541
|
this.preBuffer.clear();
|
|
9542
|
+
this.decoderSeedBuffer.clear();
|
|
9450
9543
|
this.pendingParamNals = [];
|
|
9451
9544
|
this.firstKeyframeReceived = false;
|
|
9452
9545
|
this.encodedCallbacks.clear();
|
|
@@ -9543,6 +9636,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9543
9636
|
this.placeholderTimer = void 0;
|
|
9544
9637
|
}
|
|
9545
9638
|
this.preBuffer.clear();
|
|
9639
|
+
this.decoderSeedBuffer.clear();
|
|
9546
9640
|
this.firstKeyframeReceived = false;
|
|
9547
9641
|
this.pendingParamNals = [];
|
|
9548
9642
|
this.rtspRestreamer.resetStreamState();
|
|
@@ -9676,7 +9770,46 @@ var StreamBroker = class StreamBroker {
|
|
|
9676
9770
|
const streamId = `${this.deviceId}/video`;
|
|
9677
9771
|
restreamer.pushPacket(streamId, packet);
|
|
9678
9772
|
}
|
|
9679
|
-
if (packet.type === "video" && this.firstKeyframeReceived)
|
|
9773
|
+
if (packet.type === "video" && this.firstKeyframeReceived) {
|
|
9774
|
+
const decoderPacket = this.decoderPacketWithParamSets(packet);
|
|
9775
|
+
this.decoderSeedBuffer.push(decoderPacket);
|
|
9776
|
+
this.frameHandlePlane?.pushPacket(decoderPacket);
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
/**
|
|
9780
|
+
* Return the packet to feed the decoder plane, prepending the SDP-harvested
|
|
9781
|
+
* SPS/PPS to a keyframe that lacks them in-band.
|
|
9782
|
+
*
|
|
9783
|
+
* The plane seeds a freshly-armed decoder session with the SINGLE keyframe
|
|
9784
|
+
* packet that triggered the arm (`armPendingSessions`), so the parameter sets
|
|
9785
|
+
* MUST travel inside that same packet — a separate param-set packet pushed
|
|
9786
|
+
* just before it reaches only already-live sessions and is lost on the
|
|
9787
|
+
* arming one. Unlike the encoded-subscriber path (`emitSdpParamSetsTo`), the
|
|
9788
|
+
* decoder plane is otherwise never handed `sdpParameterSets`. Cameras that
|
|
9789
|
+
* ship parameter sets ONLY in the SDP (Reolink high-profile native streams)
|
|
9790
|
+
* therefore starve the `ffmpeg -i pipe:0` decoder of SPS/PPS, which loops
|
|
9791
|
+
* `non-existing PPS → no frame` and produces zero decoded frames (verified
|
|
9792
|
+
* live on device 592: WebRTC worked, detection decoded nothing). Only
|
|
9793
|
+
* keyframes actually missing the sets are rewritten — well-behaved cameras
|
|
9794
|
+
* are returned untouched — and the check runs per keyframe so a decoder
|
|
9795
|
+
* restart mid-stream re-primes on the next IDR. The returned object is a
|
|
9796
|
+
* fresh copy; the original `packet` fanned out to the other consumers above
|
|
9797
|
+
* (WebRTC, restreamers, pre-buffer) is never mutated.
|
|
9798
|
+
*/
|
|
9799
|
+
decoderPacketWithParamSets(packet) {
|
|
9800
|
+
if (!packet.keyframe) return packet;
|
|
9801
|
+
const paramSets = this.sdpParameterSets;
|
|
9802
|
+
if (!paramSets || paramSets.length === 0) return packet;
|
|
9803
|
+
if (annexBHasParameterSets(packet.data, packet.codec)) return packet;
|
|
9804
|
+
if (this.streamingDebug) this.logger?.info("Prepending SDP param sets to decoder keyframe (in-band SPS/PPS absent)", { meta: {
|
|
9805
|
+
codec: packet.codec,
|
|
9806
|
+
psCount: paramSets.length,
|
|
9807
|
+
keyframeBytes: packet.data.length
|
|
9808
|
+
} });
|
|
9809
|
+
return {
|
|
9810
|
+
...packet,
|
|
9811
|
+
data: Buffer.concat([concatParameterSetsAnnexB(paramSets), packet.data])
|
|
9812
|
+
};
|
|
9680
9813
|
}
|
|
9681
9814
|
onEncodedData(callback, attribution = UNKNOWN_ATTRIBUTION) {
|
|
9682
9815
|
this.subscriberIdCounter += 1;
|
|
@@ -10017,11 +10150,12 @@ var StreamBroker = class StreamBroker {
|
|
|
10017
10150
|
if (!codec || !this.source) return null;
|
|
10018
10151
|
const slash = this.deviceId.indexOf("/");
|
|
10019
10152
|
const numericDeviceId = slash >= 0 ? Number.parseInt(this.deviceId.slice(0, slash), 10) : Number.parseInt(this.deviceId, 10);
|
|
10153
|
+
const pullModeUrl = this.isRtpSource() ? this.restreamUrlResolver?.() ?? null : null;
|
|
10020
10154
|
return {
|
|
10021
10155
|
codec,
|
|
10022
10156
|
...Number.isFinite(numericDeviceId) ? { numericDeviceId } : {},
|
|
10023
10157
|
tag: `broker:${this.deviceId}`,
|
|
10024
|
-
pullModeUrl
|
|
10158
|
+
pullModeUrl
|
|
10025
10159
|
};
|
|
10026
10160
|
}
|
|
10027
10161
|
/** Lazily construct the frame-handle plane, returning `null` with no decoder. */
|
|
@@ -10035,7 +10169,8 @@ var StreamBroker = class StreamBroker {
|
|
|
10035
10169
|
decoderApi: this.decoderApi,
|
|
10036
10170
|
logger: this.logger?.child("frame-handle-plane"),
|
|
10037
10171
|
resolveStreamInfo: () => this.resolveFrameHandleStreamInfo(),
|
|
10038
|
-
localNodeId: this.localNodeId
|
|
10172
|
+
localNodeId: this.localNodeId,
|
|
10173
|
+
getSeedPackets: () => this.decoderSeedBuffer.getPackets()
|
|
10039
10174
|
});
|
|
10040
10175
|
return this.frameHandlePlane;
|
|
10041
10176
|
}
|
|
@@ -12429,7 +12564,7 @@ var StreamBrokerManager = class {
|
|
|
12429
12564
|
const existing = this.upsertSourceCameraStream(deviceId, input);
|
|
12430
12565
|
const urlRefreshed = existing !== void 0 && input.url !== void 0 && existing.url !== input.url;
|
|
12431
12566
|
const wasAlreadyAssigned = (PUSH_KINDS.has(kind) ? findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId) : null) !== null && existing !== void 0;
|
|
12432
|
-
await this.reconcileAutoAssignment(deviceId, "publish");
|
|
12567
|
+
await this.reconcileAutoAssignment(deviceId, "publish", existing === void 0);
|
|
12433
12568
|
if (wasAlreadyAssigned) {
|
|
12434
12569
|
const finalProfile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12435
12570
|
if (finalProfile !== null) this.emitCamStreamDemand(deviceId, camStreamId, finalProfile);
|
|
@@ -12477,7 +12612,8 @@ var StreamBrokerManager = class {
|
|
|
12477
12612
|
const urlBefore = /* @__PURE__ */ new Map();
|
|
12478
12613
|
for (const d of sources) urlBefore.set(d.camStreamId, streamMapBefore?.get(d.camStreamId)?.url);
|
|
12479
12614
|
for (const d of sources) this.upsertSourceCameraStream(deviceId, d);
|
|
12480
|
-
|
|
12615
|
+
const anyNewStream = sources.some((d) => streamMapBefore?.get(d.camStreamId) === void 0);
|
|
12616
|
+
await this.reconcileAutoAssignment(deviceId, "publish", anyNewStream);
|
|
12481
12617
|
for (const camStreamId of reEmitDemandFor) {
|
|
12482
12618
|
const profile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12483
12619
|
if (profile !== null) this.emitCamStreamDemand(deviceId, camStreamId, profile);
|
|
@@ -13971,14 +14107,15 @@ var StreamBrokerManager = class {
|
|
|
13971
14107
|
* assignment). When the device's assignment is operator-locked
|
|
13972
14108
|
* (`auto === false`) the existing map is re-applied unchanged.
|
|
13973
14109
|
*/
|
|
13974
|
-
async reconcileAutoAssignment(deviceId, reason) {
|
|
14110
|
+
async reconcileAutoAssignment(deviceId, reason, streamAdded = false) {
|
|
13975
14111
|
const current = this.assignments.get(deviceId);
|
|
13976
14112
|
const streams = [...this.cameraStreams.get(deviceId)?.values() ?? []].toSorted((a, b) => a.order - b.order);
|
|
13977
14113
|
const isAuto = current?.auto ?? true;
|
|
13978
14114
|
const newMap = !current || isAuto && this.wouldChangeAutoMap(current.map, streams) ? this.computeInitialAssignment(streams) : current.map;
|
|
13979
14115
|
await this.applyAssignmentUpdate(deviceId, newMap, {
|
|
13980
14116
|
auto: isAuto,
|
|
13981
|
-
reason
|
|
14117
|
+
reason,
|
|
14118
|
+
streamAdded
|
|
13982
14119
|
});
|
|
13983
14120
|
}
|
|
13984
14121
|
wouldChangeAutoMap(current, streams) {
|
|
@@ -14023,7 +14160,7 @@ var StreamBrokerManager = class {
|
|
|
14023
14160
|
if (anyChange || current.auto !== nextAssignment.auto) {
|
|
14024
14161
|
this.persistProfileMap();
|
|
14025
14162
|
this.emitProfileSlotsChanged(deviceId);
|
|
14026
|
-
} else if (opts.reason === "publish") this.emitProfileSlotsChanged(deviceId);
|
|
14163
|
+
} else if (opts.reason === "publish" && opts.streamAdded) this.emitProfileSlotsChanged(deviceId);
|
|
14027
14164
|
this.syncProfileRestreamers(deviceId);
|
|
14028
14165
|
}
|
|
14029
14166
|
countPushConsumers(map, deviceId) {
|
|
@@ -14214,6 +14351,7 @@ var StreamBrokerManager = class {
|
|
|
14214
14351
|
const source = this.buildSourceFromCamStream(cam);
|
|
14215
14352
|
const broker = new StreamBroker(brokerId, this.buildDecoderCapApi(deviceId), brokerLogger, buildAudioCodecCapApi(this.api), this.localNodeId);
|
|
14216
14353
|
broker.setRestreamers(this.restreamers);
|
|
14354
|
+
broker.setRestreamUrlResolver(() => this.rtspProvider.getEntry(brokerId)?.mutedUrl ?? null);
|
|
14217
14355
|
broker.setFfmpegBinaryPath(this.ffmpegConfig.binaryPath);
|
|
14218
14356
|
this.applyPreBufferConfig(broker, deviceId, camStreamId, cam);
|
|
14219
14357
|
if (this.deviceOverrides.get(deviceId)?.streamingDebug) broker.setStreamingDebug(true);
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DPFAbcUo.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|