@apocaliss92/nodelink-js 0.6.5 → 0.6.7

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
@@ -8431,7 +8431,6 @@ __export(index_exports, {
8431
8431
  DUAL_LENS_DUAL_MOTION_MODELS: () => DUAL_LENS_DUAL_MOTION_MODELS,
8432
8432
  DUAL_LENS_MODELS: () => DUAL_LENS_MODELS,
8433
8433
  DUAL_LENS_SINGLE_MOTION_MODELS: () => DUAL_LENS_SINGLE_MOTION_MODELS,
8434
- Go2rtcTcpServer: () => Go2rtcTcpServer,
8435
8434
  H264RtpDepacketizer: () => H264RtpDepacketizer,
8436
8435
  H265RtpDepacketizer: () => H265RtpDepacketizer,
8437
8436
  HlsSessionManager: () => HlsSessionManager,
@@ -14898,6 +14897,38 @@ function createRtspFlow(transport, videoType) {
14898
14897
  return new H265Flow(transport);
14899
14898
  }
14900
14899
 
14900
+ // src/baichuan/stream/rtpVideoTimestamp.ts
14901
+ var U32 = 4294967296;
14902
+ var DEFAULT_VIDEO_CLOCK_RATE = 9e4;
14903
+ function deriveRtpVideoTimestamp(state, frameMicroseconds, clockRate = DEFAULT_VIDEO_CLOCK_RATE) {
14904
+ if (frameMicroseconds === null || frameMicroseconds === void 0 || !Number.isFinite(frameMicroseconds)) {
14905
+ return { timestamp: state.timestamp, state };
14906
+ }
14907
+ const curRaw = frameMicroseconds >>> 0;
14908
+ if (state.baseUnwrappedUs === void 0) {
14909
+ return {
14910
+ timestamp: state.timestamp,
14911
+ state: {
14912
+ ...state,
14913
+ unwrappedUs: curRaw,
14914
+ lastRawUs: curRaw,
14915
+ baseUnwrappedUs: curRaw,
14916
+ baseTimestamp: state.timestamp
14917
+ }
14918
+ };
14919
+ }
14920
+ const lastRaw = state.lastRawUs ?? curRaw;
14921
+ let forwardDelta = curRaw - lastRaw >>> 0;
14922
+ if (forwardDelta > U32 / 2) forwardDelta = 0;
14923
+ const unwrappedUs = (state.unwrappedUs ?? curRaw) + forwardDelta;
14924
+ const deltaUs = unwrappedUs - state.baseUnwrappedUs;
14925
+ const timestamp = state.baseTimestamp + Math.round(deltaUs * clockRate / 1e6) >>> 0;
14926
+ return {
14927
+ timestamp,
14928
+ state: { ...state, unwrappedUs, lastRawUs: curRaw, timestamp }
14929
+ };
14930
+ }
14931
+
14901
14932
  // src/baichuan/stream/BaichuanRtspServer.ts
14902
14933
  init_H264Converter();
14903
14934
  init_H265Converter();
@@ -15472,7 +15503,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events5.E
15472
15503
  let useTcpInterleaved = false;
15473
15504
  let clientUdpSocket = null;
15474
15505
  let clientUdpSocketAudio = null;
15506
+ let cleanedUp = false;
15475
15507
  const cleanup = () => {
15508
+ if (cleanedUp) return;
15509
+ cleanedUp = true;
15476
15510
  const sessionDurationMs = Date.now() - connectTime;
15477
15511
  const res = this.clientResources.get(clientId);
15478
15512
  const framesSent = res?.framesSent ?? 0;
@@ -16220,22 +16254,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events5.E
16220
16254
  resources.rtpVideoTimestamp = 0;
16221
16255
  if (resources.rtpVideoBaseTimestamp === void 0)
16222
16256
  resources.rtpVideoBaseTimestamp = resources.rtpVideoTimestamp;
16223
- if (resources.rtpVideoBaseMicroseconds === void 0) {
16257
+ const { timestamp, state } = deriveRtpVideoTimestamp(
16258
+ {
16259
+ timestamp: resources.rtpVideoTimestamp,
16260
+ baseTimestamp: resources.rtpVideoBaseTimestamp,
16261
+ unwrappedUs: resources.rtpVideoUnwrappedUs,
16262
+ lastRawUs: resources.rtpVideoLastRawUs,
16263
+ baseUnwrappedUs: resources.rtpVideoBaseUnwrappedUs
16264
+ },
16265
+ frameMicroseconds,
16266
+ videoClockRate
16267
+ );
16268
+ resources.rtpVideoTimestamp = timestamp;
16269
+ resources.rtpVideoBaseTimestamp = state.baseTimestamp;
16270
+ resources.rtpVideoUnwrappedUs = state.unwrappedUs;
16271
+ resources.rtpVideoLastRawUs = state.lastRawUs;
16272
+ resources.rtpVideoBaseUnwrappedUs = state.baseUnwrappedUs;
16273
+ resources.rtpVideoLastTimestamp = timestamp;
16274
+ if (resources.rtpVideoBaseMicroseconds === void 0)
16224
16275
  resources.rtpVideoBaseMicroseconds = frameMicroseconds >>> 0;
16225
- resources.rtpVideoLastTimestamp = resources.rtpVideoTimestamp;
16226
- return;
16227
- }
16228
- const baseUs = resources.rtpVideoBaseMicroseconds >>> 0;
16229
- const curUs = frameMicroseconds >>> 0;
16230
- const deltaUs = curUs - baseUs >>> 0;
16231
- const baseTs = (resources.rtpVideoBaseTimestamp ?? 0) >>> 0;
16232
- let ts = baseTs + Math.round(deltaUs * videoClockRate / 1e6) >>> 0;
16233
- const last = resources.rtpVideoLastTimestamp;
16234
- if (last !== void 0 && ts <= last >>> 0) {
16235
- ts = (last >>> 0) + 1 >>> 0;
16236
- }
16237
- resources.rtpVideoTimestamp = ts;
16238
- resources.rtpVideoLastTimestamp = ts;
16239
16276
  };
16240
16277
  const sendVideoAccessUnit = (videoType, accessUnitAnnexB, advanceTimestamp = true) => {
16241
16278
  const nals = _BaichuanRtspServer.splitAnnexBNals(accessUnitAnnexB);
@@ -17700,6 +17737,30 @@ function buildSetNtpXml(current, patch) {
17700
17737
  );
17701
17738
  }
17702
17739
 
17740
+ // src/reolink/baichuan/utils/channelEnumeration.ts
17741
+ async function resolveBaichuanChannels(deps) {
17742
+ const fromPush = dedupeSorted(deps.pushChannels);
17743
+ if (fromPush.length > 0) return fromPush;
17744
+ const slots = dedupeSorted(deps.supportChnIds);
17745
+ const candidates = slots.length > 0 ? slots : [0];
17746
+ const probed = await Promise.all(
17747
+ candidates.map(
17748
+ async (channel) => await deps.probe(channel) ? channel : void 0
17749
+ )
17750
+ );
17751
+ return dedupeSorted(
17752
+ probed.filter((c) => c !== void 0)
17753
+ );
17754
+ }
17755
+ function dedupeSorted(values) {
17756
+ const set = /* @__PURE__ */ new Set();
17757
+ for (const v of values) {
17758
+ const n = Number(v);
17759
+ if (Number.isFinite(n) && n >= 0) set.add(n);
17760
+ }
17761
+ return [...set].sort((a, b) => a - b);
17762
+ }
17763
+
17703
17764
  // src/reolink/baichuan/utils/dst.ts
17704
17765
  init_xml();
17705
17766
  var parseNumberSafe3 = (text) => {
@@ -24109,13 +24170,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
24109
24170
  * @param options.source - Data source for the channel list (default: `"cgi"`):
24110
24171
  * - `"cgi"`: Uses HTTP `GetChannelstatus` — returns the channel list immediately,
24111
24172
  * no dependency on async push messages. Recommended for first-call discovery.
24112
- * - `"baichuan"`: Uses the cmd_id 145 push cache populated when the NVR sends channel
24113
- * info after login + event subscription. This push is *asynchronous*: if it has not
24114
- * arrived yet, the result will have zero channels. Callers must retry (nvr.ts does this
24115
- * with a 1-second loop). Note: explicitly requesting cmd_id 145 is not supported.
24173
+ * - `"baichuan"`: HTTP-free discovery. Prefers the cmd_id 145 push cache when
24174
+ * populated; otherwise actively probes the channel slots advertised by Support
24175
+ * (`items[].chnID`) via `getInfo`. Use this for hubs with HTTP disabled.
24176
+ *
24177
+ * When the api was constructed with `nativeOnly`, the source is forced to
24178
+ * `"baichuan"` regardless of this option (no HTTP/CGI is ever attempted).
24116
24179
  */
24117
24180
  async getNvrChannelsSummary(options) {
24118
- const source = options?.source ?? "cgi";
24181
+ const source = this.nativeOnly ? "baichuan" : options?.source ?? "cgi";
24182
+ const support = await this.getSupportInfo().catch(() => {
24183
+ this.logger.error?.(
24184
+ "[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
24185
+ );
24186
+ return void 0;
24187
+ });
24119
24188
  let channels;
24120
24189
  const cgiStatusByChannel = /* @__PURE__ */ new Map();
24121
24190
  if (options?.channels?.length) {
@@ -24145,15 +24214,31 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
24145
24214
  channels = [];
24146
24215
  }
24147
24216
  } else {
24148
- const pushInfo2 = this.getChannelInfoFromPushCache();
24149
- channels = Array.from(pushInfo2.keys()).map((c) => Number(c)).filter((n) => Number.isFinite(n));
24217
+ const pushChannels = Array.from(
24218
+ this.getChannelInfoFromPushCache().keys()
24219
+ ).map((c) => Number(c)).filter((n) => Number.isFinite(n));
24220
+ const supportChnIds = (support?.items ?? []).map((i) => Number(i.chnID)).filter((n) => Number.isFinite(n));
24221
+ const probeTimeoutMs = options?.timeoutMs ?? 2500;
24222
+ channels = await resolveBaichuanChannels({
24223
+ pushChannels,
24224
+ supportChnIds,
24225
+ probe: async (channel) => {
24226
+ try {
24227
+ await this.getInfo(channel, {
24228
+ timeoutMs: probeTimeoutMs,
24229
+ tags: ["type", "name"]
24230
+ });
24231
+ return true;
24232
+ } catch {
24233
+ return false;
24234
+ }
24235
+ }
24236
+ });
24237
+ this.logger.debug?.(
24238
+ `[ReolinkBaichuanApi] getNvrChannelsSummary: baichuan resolved ${channels.length} channel(s): [${channels.join(", ")}]`
24239
+ );
24150
24240
  }
24151
24241
  channels = channels.sort((a, b) => a - b);
24152
- const support = await this.getSupportInfo().catch(() => {
24153
- this.logger.error?.(
24154
- "[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
24155
- );
24156
- });
24157
24242
  const truthyNumberLike = (v) => {
24158
24243
  if (typeof v === "number") return v > 0;
24159
24244
  if (typeof v === "string") {
@@ -38182,811 +38267,8 @@ async function createReplayHttpServer(options) {
38182
38267
  // src/index.ts
38183
38268
  init_BaichuanVideoStream();
38184
38269
 
38185
- // src/baichuan/stream/Go2rtcTcpServer.ts
38186
- var import_node_events8 = require("events");
38187
- var net4 = __toESM(require("net"), 1);
38188
- init_H264Converter();
38189
- init_H265Converter();
38190
- var AsyncBoundedQueue2 = class {
38191
- maxItems;
38192
- queue = [];
38193
- waiting;
38194
- closed = false;
38195
- constructor(maxItems) {
38196
- this.maxItems = Math.max(1, maxItems | 0);
38197
- }
38198
- push(item) {
38199
- if (this.closed) return;
38200
- if (this.waiting) {
38201
- const { resolve } = this.waiting;
38202
- this.waiting = void 0;
38203
- resolve({ value: item, done: false });
38204
- return;
38205
- }
38206
- this.queue.push(item);
38207
- if (this.queue.length > this.maxItems) {
38208
- this.queue.splice(0, this.queue.length - this.maxItems);
38209
- }
38210
- }
38211
- close() {
38212
- if (this.closed) return;
38213
- this.closed = true;
38214
- if (this.waiting) {
38215
- const { resolve } = this.waiting;
38216
- this.waiting = void 0;
38217
- resolve({ value: void 0, done: true });
38218
- }
38219
- }
38220
- async next() {
38221
- if (this.closed) return { value: void 0, done: true };
38222
- const item = this.queue.shift();
38223
- if (item !== void 0) return { value: item, done: false };
38224
- return await new Promise((resolve) => {
38225
- this.waiting = { resolve };
38226
- });
38227
- }
38228
- };
38229
- var NativeStreamFanout2 = class {
38230
- opts;
38231
- queues = /* @__PURE__ */ new Map();
38232
- source = null;
38233
- running = false;
38234
- pumpPromise = null;
38235
- constructor(opts) {
38236
- this.opts = opts;
38237
- }
38238
- start() {
38239
- if (this.running) return;
38240
- this.running = true;
38241
- this.source = this.opts.createSource();
38242
- this.pumpPromise = (async () => {
38243
- try {
38244
- for await (const frame of this.source) {
38245
- try {
38246
- this.opts.onFrame?.(frame);
38247
- } catch {
38248
- }
38249
- for (const q of this.queues.values()) {
38250
- q.push(frame);
38251
- }
38252
- }
38253
- } catch (e) {
38254
- this.opts.onError?.(e);
38255
- } finally {
38256
- for (const q of this.queues.values()) q.close();
38257
- this.queues.clear();
38258
- this.running = false;
38259
- this.opts.onEnd?.();
38260
- }
38261
- })();
38262
- }
38263
- subscribe(id) {
38264
- const q = new AsyncBoundedQueue2(this.opts.maxQueueItems);
38265
- this.queues.set(id, q);
38266
- const self = this;
38267
- return (async function* () {
38268
- try {
38269
- while (true) {
38270
- const r = await q.next();
38271
- if (r.done) return;
38272
- yield r.value;
38273
- }
38274
- } finally {
38275
- q.close();
38276
- self.queues.delete(id);
38277
- }
38278
- })();
38279
- }
38280
- async stop() {
38281
- if (!this.running) return;
38282
- this.running = false;
38283
- const src = this.source;
38284
- this.source = null;
38285
- for (const q of this.queues.values()) q.close();
38286
- this.queues.clear();
38287
- try {
38288
- await src?.return(void 0);
38289
- } catch {
38290
- }
38291
- try {
38292
- await this.pumpPromise;
38293
- } catch {
38294
- }
38295
- this.pumpPromise = null;
38296
- }
38297
- };
38298
- var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events8.EventEmitter {
38299
- api;
38300
- channel;
38301
- profile;
38302
- variant;
38303
- listenHost;
38304
- listenPort;
38305
- logger;
38306
- deviceId;
38307
- gracePeriodMs;
38308
- prebufferMaxMs;
38309
- maxBufferBytes;
38310
- streamTimeoutMs;
38311
- prestartStream;
38312
- active = false;
38313
- server;
38314
- resolvedPort;
38315
- // Native stream
38316
- nativeFanout = null;
38317
- nativeStreamActive = false;
38318
- // Set only by stopNativeStream() (explicit teardown) so the fanout's onEnd
38319
- // callback can short-circuit cleanup/restart logic. NOT set by the inactivity-
38320
- // timeout force-restart path — that flow wants onEnd to run and decide
38321
- // whether to restart based on prestartStream / connected clients.
38322
- nativeStreamStopping = false;
38323
- // Pending retry timer for the unbounded auto-restart loop. When a stream
38324
- // start fails transiently (camera in maintenance reboot, idle-disconnect
38325
- // race, etc.) we keep trying with exponential backoff until either the
38326
- // server is stopped or a frame finally arrives.
38327
- nativeStreamRetryTimer;
38328
- nativeStreamRetryDelayMs = 0;
38329
- dedicatedSessionRelease;
38330
- detectedVideoType;
38331
- // Client tracking
38332
- connectedClients = /* @__PURE__ */ new Set();
38333
- clientSockets = /* @__PURE__ */ new Map();
38334
- stopGraceTimer;
38335
- // Stream health monitoring
38336
- lastFrameAt = 0;
38337
- streamHealthTimer;
38338
- totalFramesReceived = 0;
38339
- totalVideoFramesWritten = 0;
38340
- // Prebuffer
38341
- prebuffer = [];
38342
- // Audio metadata — populated on first valid ADTS AAC frame.
38343
- // Exposed via getAudioInfo() for the stream-diagnostics feature.
38344
- audioInfo = null;
38345
- constructor(options) {
38346
- super();
38347
- this.api = options.api;
38348
- this.channel = options.channel;
38349
- this.profile = options.profile;
38350
- this.variant = options.variant ?? "default";
38351
- this.listenHost = options.listenHost ?? "127.0.0.1";
38352
- this.listenPort = options.listenPort ?? 0;
38353
- this.logger = options.logger ?? console;
38354
- this.deviceId = options.deviceId;
38355
- this.gracePeriodMs = options.gracePeriodMs ?? 3e4;
38356
- this.prebufferMaxMs = options.prebufferMs ?? 3e3;
38357
- this.maxBufferBytes = options.maxBufferBytes ?? 1e8;
38358
- this.streamTimeoutMs = options.streamTimeoutMs ?? 15e3;
38359
- this.prestartStream = options.prestartStream ?? true;
38360
- }
38361
- // -----------------------------------------------------------------------
38362
- // Public API
38363
- // -----------------------------------------------------------------------
38364
- /** Start listening. Resolves once the TCP server is bound. */
38365
- async start() {
38366
- if (this.active) return;
38367
- this.active = true;
38368
- this.server = net4.createServer((socket) => this.handleClient(socket));
38369
- this.server.on("error", (err) => {
38370
- this.logger.error?.(`[Go2rtcTcpServer] server error: ${err.message}`);
38371
- this.emit("error", err);
38372
- });
38373
- await new Promise((resolve, reject) => {
38374
- this.server.listen(this.listenPort, this.listenHost, () => {
38375
- const addr = this.server.address();
38376
- this.resolvedPort = addr.port;
38377
- this.logger.info?.(
38378
- `[Go2rtcTcpServer] listening on ${addr.address}:${addr.port} channel=${this.channel} profile=${this.profile}`
38379
- );
38380
- this.emit("listening", { host: addr.address, port: addr.port });
38381
- resolve();
38382
- });
38383
- this.server.once("error", reject);
38384
- });
38385
- if (this.prestartStream) {
38386
- this.logger.info?.(
38387
- `[Go2rtcTcpServer] pre-starting native stream channel=${this.channel} profile=${this.profile}`
38388
- );
38389
- this.startNativeStream();
38390
- }
38391
- }
38392
- /** Stop the server and all active streams. */
38393
- async stop() {
38394
- if (!this.active) return;
38395
- this.active = false;
38396
- clearTimeout(this.stopGraceTimer);
38397
- this.clearNativeStreamRetry();
38398
- this.stopStreamHealthMonitor();
38399
- for (const [id, sock] of this.clientSockets) {
38400
- sock.destroy();
38401
- this.connectedClients.delete(id);
38402
- }
38403
- this.clientSockets.clear();
38404
- await this.stopNativeStream();
38405
- if (this.server) {
38406
- await new Promise((resolve) => {
38407
- this.server.close(() => resolve());
38408
- });
38409
- this.server = void 0;
38410
- }
38411
- this.prebuffer = [];
38412
- this.resolvedPort = void 0;
38413
- this.emit("close");
38414
- }
38415
- /** The actual port the server is listening on (available after start()). */
38416
- get port() {
38417
- return this.resolvedPort;
38418
- }
38419
- /** The go2rtc-compatible source URL. */
38420
- get go2rtcSourceUrl() {
38421
- if (this.resolvedPort == null) return void 0;
38422
- return `tcp://127.0.0.1:${this.resolvedPort}`;
38423
- }
38424
- /** Number of currently connected clients. */
38425
- get clientCount() {
38426
- return this.connectedClients.size;
38427
- }
38428
- // -----------------------------------------------------------------------
38429
- // Diagnostic subscription API (implements DiagnosticStreamServer)
38430
- //
38431
- // Matches the shape of BaichuanRtspServer's diagnostic API so the
38432
- // stream-diagnostic feature in the Manager app can drive either backend
38433
- // with identical code.
38434
- // -----------------------------------------------------------------------
38435
- /**
38436
- * Subscribe to the raw native stream for diagnostic purposes.
38437
- * The subscriber receives the same frames the MPEG-TS muxer consumes
38438
- * (pre-muxing). Counts as a "consumer" so the native stream is kept alive
38439
- * for the lifetime of the subscription. If the stream is not already
38440
- * running (battery camera, prestart=false), this starts it.
38441
- */
38442
- async subscribeDiagnostic(id) {
38443
- this.connectedClients.add(`diag:${id}`);
38444
- if (!this.nativeStreamActive) {
38445
- await this.startNativeStream();
38446
- }
38447
- if (!this.nativeFanout) {
38448
- this.connectedClients.delete(`diag:${id}`);
38449
- throw new Error(
38450
- "Go2rtcTcpServer: native stream failed to start \u2014 cannot subscribe diagnostic"
38451
- );
38452
- }
38453
- return this.nativeFanout.subscribe(`diag:${id}`);
38454
- }
38455
- /** Unsubscribe a diagnostic session and release its consumer slot. */
38456
- unsubscribeDiagnostic(id) {
38457
- this.removeClient(`diag:${id}`, "diagnostic unsubscribe");
38458
- }
38459
- /**
38460
- * Returns ADTS AAC audio metadata detected from the native stream, or
38461
- * null if no audio frame has been observed yet (e.g. video-only cameras
38462
- * or before the first audio packet arrives).
38463
- */
38464
- getAudioInfo() {
38465
- return this.audioInfo;
38466
- }
38467
- // -----------------------------------------------------------------------
38468
- // Client handling
38469
- // -----------------------------------------------------------------------
38470
- handleClient(socket) {
38471
- const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
38472
- socket.setNoDelay(true);
38473
- this.connectedClients.add(clientId);
38474
- this.clientSockets.set(clientId, socket);
38475
- this.logger.info?.(
38476
- `[Go2rtcTcpServer] client connected id=${clientId} total=${this.connectedClients.size}`
38477
- );
38478
- this.emit("client", clientId);
38479
- if (this.stopGraceTimer) {
38480
- clearTimeout(this.stopGraceTimer);
38481
- this.stopGraceTimer = void 0;
38482
- }
38483
- if (!this.nativeStreamActive) {
38484
- this.startNativeStream();
38485
- }
38486
- this.feedClient(clientId, socket).catch((err) => {
38487
- this.logger.warn?.(
38488
- `[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`
38489
- );
38490
- });
38491
- const cleanup = (reason) => {
38492
- this.removeClient(clientId, reason);
38493
- socket.destroy();
38494
- };
38495
- socket.on("error", (err) => cleanup(`error: ${err.message}`));
38496
- socket.on("close", (hadError) => cleanup(hadError ? "close (with error)" : "close (clean)"));
38497
- }
38498
- async feedClient(clientId, socket) {
38499
- const fanoutDeadline = Date.now() + 3e4;
38500
- while (this.active && !this.nativeFanout) {
38501
- if (socket.destroyed) return;
38502
- if (Date.now() > fanoutDeadline) {
38503
- this.logger.warn?.(
38504
- `[Go2rtcTcpServer] fanout not ready after 30s, dropping client ${clientId}`
38505
- );
38506
- return;
38507
- }
38508
- await new Promise((r) => setTimeout(r, 100));
38509
- }
38510
- if (!this.active || !this.nativeFanout) return;
38511
- const subscription = this.nativeFanout.subscribe(clientId);
38512
- let muxer = null;
38513
- const prebufferSnap = this.prebuffer.slice();
38514
- let lastIdrIdx = -1;
38515
- for (let i = prebufferSnap.length - 1; i >= 0; i--) {
38516
- if (prebufferSnap[i].isKeyframe) {
38517
- lastIdrIdx = i;
38518
- break;
38519
- }
38520
- }
38521
- if (lastIdrIdx >= 0) {
38522
- const replay = prebufferSnap.slice(lastIdrIdx);
38523
- this.logger.info?.(
38524
- `[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`
38525
- );
38526
- if (!muxer) {
38527
- muxer = new MpegTsMuxer({
38528
- videoType: this.detectedVideoType ?? "H264",
38529
- includeAudio: true
38530
- });
38531
- }
38532
- for (const entry of replay) {
38533
- if (socket.destroyed) return;
38534
- let ts;
38535
- if (!entry.audio) {
38536
- ts = muxer.muxVideo(entry.data, entry.pts, entry.isKeyframe);
38537
- } else {
38538
- ts = muxer.muxAudio(entry.data, entry.pts);
38539
- }
38540
- if (ts.length > 0) socket.write(ts);
38541
- }
38542
- }
38543
- let seenKeyframe = lastIdrIdx >= 0;
38544
- let liveFrameCount = 0;
38545
- let liveVideoWritten = 0;
38546
- let lastLogAt = Date.now();
38547
- try {
38548
- this.logger.info?.(
38549
- `[Go2rtcTcpServer] entering live loop client=${clientId} seenKeyframe=${seenKeyframe}`
38550
- );
38551
- for await (const frame of subscription) {
38552
- if (socket.destroyed || !this.active) {
38553
- this.logger.info?.(
38554
- `[Go2rtcTcpServer] live loop exit client=${clientId} destroyed=${socket.destroyed} active=${this.active}`
38555
- );
38556
- break;
38557
- }
38558
- liveFrameCount++;
38559
- if (frame.audio) {
38560
- if (muxer) {
38561
- const pts2 = frame.microseconds ?? Date.now() * 1e3;
38562
- const ts2 = muxer.muxAudio(frame.data, pts2);
38563
- if (ts2.length > 0) socket.write(ts2);
38564
- }
38565
- continue;
38566
- }
38567
- const annexB = this.convertVideoFrame(frame);
38568
- if (!annexB) continue;
38569
- const isKf = this.isAnnexBKeyframe(annexB, frame.videoType);
38570
- if (!seenKeyframe) {
38571
- if (!isKf) continue;
38572
- seenKeyframe = true;
38573
- this.logger.info?.(
38574
- `[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`
38575
- );
38576
- if (!muxer) {
38577
- muxer = new MpegTsMuxer({
38578
- videoType: frame.videoType ?? this.detectedVideoType ?? "H264",
38579
- includeAudio: true
38580
- });
38581
- }
38582
- }
38583
- const pts = frame.microseconds ?? Date.now() * 1e3;
38584
- const ts = muxer.muxVideo(annexB, pts, isKf);
38585
- socket.write(ts);
38586
- liveVideoWritten++;
38587
- this.totalVideoFramesWritten++;
38588
- if (Date.now() - lastLogAt > 1e4) {
38589
- this.logger.info?.(
38590
- `[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`
38591
- );
38592
- lastLogAt = Date.now();
38593
- }
38594
- if (socket.writableLength > this.maxBufferBytes) {
38595
- this.logger.warn?.(
38596
- `[Go2rtcTcpServer] buffer overflow (${socket.writableLength} bytes), dropping client ${clientId}`
38597
- );
38598
- socket.destroy();
38599
- break;
38600
- }
38601
- }
38602
- this.logger.info?.(
38603
- `[Go2rtcTcpServer] live loop ended naturally client=${clientId} received=${liveFrameCount} written=${liveVideoWritten}`
38604
- );
38605
- } finally {
38606
- await subscription.return(void 0).catch(() => {
38607
- });
38608
- }
38609
- }
38610
- // -----------------------------------------------------------------------
38611
- // Frame conversion
38612
- // -----------------------------------------------------------------------
38613
- /**
38614
- * Convert a native video frame to Annex-B.
38615
- * Returns null for audio frames (handled separately by muxAudio).
38616
- */
38617
- convertVideoFrame(frame) {
38618
- if (frame.audio) return null;
38619
- if (frame.data.length === 0) return null;
38620
- try {
38621
- if (frame.videoType === "H264") {
38622
- return convertToAnnexB(frame.data);
38623
- }
38624
- if (frame.videoType === "H265") {
38625
- return convertToAnnexB2(frame.data);
38626
- }
38627
- } catch {
38628
- }
38629
- return frame.data;
38630
- }
38631
- /** Check if an Annex-B buffer contains a keyframe (IDR for H.264, IRAP for H.265). */
38632
- isAnnexBKeyframe(annexB, videoType) {
38633
- try {
38634
- if (videoType === "H264") {
38635
- const nals = _Go2rtcTcpServer.splitAnnexBNals(annexB);
38636
- return nals.some((n) => n.length >= 1 && (n[0] & 31) === 5);
38637
- }
38638
- if (videoType === "H265") {
38639
- const nals = splitAnnexBToNalPayloads2(annexB);
38640
- return nals.some(
38641
- (n) => n.length >= 2 && isH265Irap(n[0] >> 1 & 63)
38642
- );
38643
- }
38644
- } catch {
38645
- }
38646
- return false;
38647
- }
38648
- /** Split Annex-B byte stream into individual NAL units. */
38649
- static splitAnnexBNals(buf) {
38650
- const nals = [];
38651
- let i = 0;
38652
- while (i < buf.length) {
38653
- if (i + 2 < buf.length && buf[i] === 0 && buf[i + 1] === 0) {
38654
- let scLen;
38655
- if (buf[i + 2] === 1) {
38656
- scLen = 3;
38657
- } else if (i + 3 < buf.length && buf[i + 2] === 0 && buf[i + 3] === 1) {
38658
- scLen = 4;
38659
- } else {
38660
- i++;
38661
- continue;
38662
- }
38663
- const nalStart = i + scLen;
38664
- let nalEnd = buf.length;
38665
- for (let j = nalStart; j < buf.length - 2; j++) {
38666
- if (buf[j] === 0 && buf[j + 1] === 0 && (buf[j + 2] === 1 || j + 3 < buf.length && buf[j + 2] === 0 && buf[j + 3] === 1)) {
38667
- nalEnd = j;
38668
- break;
38669
- }
38670
- }
38671
- if (nalEnd > nalStart) {
38672
- nals.push(buf.subarray(nalStart, nalEnd));
38673
- }
38674
- i = nalEnd;
38675
- } else {
38676
- i++;
38677
- }
38678
- }
38679
- return nals;
38680
- }
38681
- // -----------------------------------------------------------------------
38682
- // ADTS AAC parsing (used for audio metadata exposed via getAudioInfo)
38683
- // -----------------------------------------------------------------------
38684
- /** True if `b` starts with an ADTS AAC syncword (0xFFF). */
38685
- static isAdtsAacFrame(b) {
38686
- return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
38687
- }
38688
- /**
38689
- * Parse an ADTS header into {sampleRate, channels, AudioSpecificConfig hex}.
38690
- * Returns null when the buffer is not a valid ADTS frame.
38691
- */
38692
- static parseAdtsSamplingInfo(b) {
38693
- if (b.length < 7) return null;
38694
- if (!_Go2rtcTcpServer.isAdtsAacFrame(b)) return null;
38695
- const samplingIndex = b[2] >> 2 & 15;
38696
- const sampleRates = [
38697
- 96e3,
38698
- 88200,
38699
- 64e3,
38700
- 48e3,
38701
- 44100,
38702
- 32e3,
38703
- 24e3,
38704
- 22050,
38705
- 16e3,
38706
- 12e3,
38707
- 11025,
38708
- 8e3,
38709
- 7350
38710
- ];
38711
- const sampleRate = sampleRates[samplingIndex] ?? null;
38712
- if (!sampleRate) return null;
38713
- const channelConfig = (b[2] & 1) << 2 | b[3] >> 6 & 3;
38714
- const channels = channelConfig === 0 ? 1 : channelConfig;
38715
- const profile = b[2] >> 6 & 3;
38716
- const audioObjectType = profile + 1;
38717
- const asc = audioObjectType << 11 | samplingIndex << 7 | channelConfig << 3;
38718
- const configHex = Buffer.from([asc >> 8 & 255, asc & 255]).toString(
38719
- "hex"
38720
- );
38721
- return { sampleRate, channels, configHex };
38722
- }
38723
- // -----------------------------------------------------------------------
38724
- // Native stream management
38725
- // -----------------------------------------------------------------------
38726
- /**
38727
- * Schedule another startNativeStream() attempt after the given delay.
38728
- * Idempotent: a no-op if a retry is already scheduled, the server is no
38729
- * longer active, or an explicit stop is in progress. Implements unbounded
38730
- * exponential backoff (5s → 60s) so a camera that stays unreachable for
38731
- * minutes (e.g. nightly maintenance reboot) eventually recovers without
38732
- * manual intervention — see issue #16.
38733
- */
38734
- scheduleNativeStreamRetry(reason) {
38735
- if (!this.active) return;
38736
- if (this.nativeStreamStopping) return;
38737
- if (this.nativeStreamRetryTimer) return;
38738
- const delay = this.nativeStreamRetryDelayMs > 0 ? this.nativeStreamRetryDelayMs : 5e3;
38739
- this.logger.info?.(
38740
- `[Go2rtcTcpServer] scheduling native stream retry in ${(delay / 1e3).toFixed(0)}s (reason=${reason})`
38741
- );
38742
- this.nativeStreamRetryTimer = setTimeout(() => {
38743
- this.nativeStreamRetryTimer = void 0;
38744
- if (!this.active) return;
38745
- if (this.nativeStreamStopping) return;
38746
- this.startNativeStream().catch((err) => {
38747
- this.logger.warn?.(
38748
- `[Go2rtcTcpServer] retry of startNativeStream threw: ${err instanceof Error ? err.message : err}`
38749
- );
38750
- });
38751
- }, delay);
38752
- this.nativeStreamRetryDelayMs = Math.min(delay * 2, 6e4);
38753
- }
38754
- /**
38755
- * Cancel any pending retry timer and reset the backoff. Called on explicit
38756
- * stop and on first-frame-received so the next failure starts the backoff
38757
- * window from scratch.
38758
- */
38759
- clearNativeStreamRetry() {
38760
- if (this.nativeStreamRetryTimer) {
38761
- clearTimeout(this.nativeStreamRetryTimer);
38762
- this.nativeStreamRetryTimer = void 0;
38763
- }
38764
- this.nativeStreamRetryDelayMs = 0;
38765
- }
38766
- async startNativeStream() {
38767
- if (this.nativeStreamActive) return;
38768
- if (!this.api.isReady) {
38769
- if (this.api.isClosed) {
38770
- this.logger.warn?.(
38771
- `[Go2rtcTcpServer] API has been explicitly closed \u2014 stream cannot start`
38772
- );
38773
- return;
38774
- }
38775
- try {
38776
- this.logger.info?.(
38777
- `[Go2rtcTcpServer] API not ready (idle disconnect?), calling ensureConnected`
38778
- );
38779
- await this.api.ensureConnected();
38780
- } catch (e) {
38781
- this.logger.warn?.(
38782
- `[Go2rtcTcpServer] ensureConnected failed: ${e}`
38783
- );
38784
- this.scheduleNativeStreamRetry("ensureConnected failed");
38785
- return;
38786
- }
38787
- }
38788
- this.nativeStreamActive = true;
38789
- let dedicatedClient;
38790
- if (this.deviceId) {
38791
- try {
38792
- const session = await this.api.createDedicatedSession(
38793
- `live:${this.deviceId}:ch${this.channel}:${this.profile}`
38794
- );
38795
- dedicatedClient = session.client;
38796
- this.dedicatedSessionRelease = session.release;
38797
- } catch (e) {
38798
- this.logger.warn?.(
38799
- `[Go2rtcTcpServer] failed to acquire dedicated session, using shared socket: ${e}`
38800
- );
38801
- }
38802
- }
38803
- this.logger.info?.(
38804
- `[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`
38805
- );
38806
- let hadFrames = false;
38807
- this.nativeFanout = new NativeStreamFanout2({
38808
- maxQueueItems: 200,
38809
- createSource: () => createNativeStream(this.api, this.channel, this.profile, {
38810
- variant: this.variant,
38811
- ...dedicatedClient ? { client: dedicatedClient } : {}
38812
- }),
38813
- onFrame: (frame) => {
38814
- if (!hadFrames) {
38815
- this.clearNativeStreamRetry();
38816
- }
38817
- hadFrames = true;
38818
- this.lastFrameAt = Date.now();
38819
- this.totalFramesReceived++;
38820
- if (!frame.audio && (frame.videoType === "H264" || frame.videoType === "H265")) {
38821
- this.detectedVideoType = frame.videoType;
38822
- }
38823
- let prebufData;
38824
- let isKeyframe;
38825
- if (frame.audio) {
38826
- if (frame.data.length === 0) return;
38827
- if (!this.audioInfo) {
38828
- const parsed = _Go2rtcTcpServer.parseAdtsSamplingInfo(frame.data);
38829
- if (parsed) {
38830
- this.audioInfo = { codec: "aac-adts", ...parsed };
38831
- }
38832
- }
38833
- prebufData = frame.data;
38834
- isKeyframe = false;
38835
- } else {
38836
- const annexB = this.convertVideoFrame(frame);
38837
- if (!annexB || annexB.length === 0) return;
38838
- prebufData = annexB;
38839
- isKeyframe = this.isAnnexBKeyframe(annexB, frame.videoType);
38840
- }
38841
- const pts = frame.microseconds ?? Date.now() * 1e3;
38842
- this.prebuffer.push({
38843
- data: Buffer.from(prebufData),
38844
- time: Date.now(),
38845
- isKeyframe,
38846
- audio: frame.audio,
38847
- pts
38848
- });
38849
- const cutoff = Date.now() - this.prebufferMaxMs;
38850
- let trimIdx = 0;
38851
- while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx].time < cutoff) {
38852
- trimIdx++;
38853
- }
38854
- if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);
38855
- },
38856
- onError: (error) => {
38857
- this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);
38858
- },
38859
- onEnd: () => {
38860
- if (this.nativeStreamStopping) return;
38861
- this.nativeStreamActive = false;
38862
- this.nativeFanout = null;
38863
- this.stopStreamHealthMonitor();
38864
- const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
38865
- const diagnosis = silenceMs > this.streamTimeoutMs ? "camera stopped sending frames" : silenceMs >= 0 ? "stream source closed" : "no frames were ever received";
38866
- this.logger.warn?.(
38867
- `[Go2rtcTcpServer] native stream ended diagnosis="${diagnosis}" lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1e3).toFixed(1)}s ago` : "never"} totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`
38868
- );
38869
- if (this.dedicatedSessionRelease) {
38870
- this.dedicatedSessionRelease().catch(() => {
38871
- });
38872
- this.dedicatedSessionRelease = void 0;
38873
- }
38874
- if (!this.prestartStream) {
38875
- this.logger.info?.(
38876
- `[Go2rtcTcpServer] battery native stream ended hadFrames=${hadFrames} channel=${this.channel} profile=${this.profile} \u2014 dropping ${this.connectedClients.size} client(s) to prevent wake loop`
38877
- );
38878
- for (const [, sock] of this.clientSockets) {
38879
- sock.destroy();
38880
- }
38881
- } else if (this.active) {
38882
- if (typeof this.api.isStreamProfileRejected === "function" && this.api.isStreamProfileRejected(this.channel, this.profile)) {
38883
- this.logger.warn?.(
38884
- `[Go2rtcTcpServer] profile rejected by device channel=${this.channel} profile=${this.profile} \u2014 not restarting`
38885
- );
38886
- for (const [, sock] of this.clientSockets) {
38887
- sock.destroy();
38888
- }
38889
- return;
38890
- }
38891
- this.logger.info?.(
38892
- `[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
38893
- );
38894
- this.startNativeStream();
38895
- }
38896
- }
38897
- });
38898
- this.nativeFanout.start();
38899
- this.startStreamHealthMonitor();
38900
- }
38901
- async stopNativeStream() {
38902
- this.nativeStreamStopping = true;
38903
- this.nativeStreamActive = false;
38904
- this.clearNativeStreamRetry();
38905
- this.stopStreamHealthMonitor();
38906
- const fanout = this.nativeFanout;
38907
- this.nativeFanout = null;
38908
- try {
38909
- if (fanout) {
38910
- await fanout.stop();
38911
- }
38912
- this.prebuffer = [];
38913
- if (this.dedicatedSessionRelease) {
38914
- await this.dedicatedSessionRelease().catch(() => {
38915
- });
38916
- this.dedicatedSessionRelease = void 0;
38917
- }
38918
- } finally {
38919
- this.nativeStreamStopping = false;
38920
- }
38921
- }
38922
- // -----------------------------------------------------------------------
38923
- // Stream health monitoring
38924
- // -----------------------------------------------------------------------
38925
- startStreamHealthMonitor() {
38926
- this.stopStreamHealthMonitor();
38927
- if (this.streamTimeoutMs <= 0) return;
38928
- this.lastFrameAt = Date.now();
38929
- this.streamHealthTimer = setInterval(() => {
38930
- if (!this.nativeStreamActive || !this.active) {
38931
- this.stopStreamHealthMonitor();
38932
- return;
38933
- }
38934
- const silenceMs = Date.now() - this.lastFrameAt;
38935
- if (silenceMs > this.streamTimeoutMs) {
38936
- this.logger.warn?.(
38937
- `[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1e3).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} \u2014 forcing stream restart`
38938
- );
38939
- this.stopStreamHealthMonitor();
38940
- const fanout = this.nativeFanout;
38941
- if (fanout) {
38942
- this.nativeStreamActive = false;
38943
- this.nativeFanout = null;
38944
- fanout.stop().catch(() => {
38945
- });
38946
- }
38947
- }
38948
- }, Math.min(this.streamTimeoutMs / 2, 5e3));
38949
- }
38950
- stopStreamHealthMonitor() {
38951
- if (this.streamHealthTimer) {
38952
- clearInterval(this.streamHealthTimer);
38953
- this.streamHealthTimer = void 0;
38954
- }
38955
- }
38956
- // -----------------------------------------------------------------------
38957
- // Client lifecycle
38958
- // -----------------------------------------------------------------------
38959
- removeClient(clientId, reason) {
38960
- if (!this.connectedClients.has(clientId)) return;
38961
- this.connectedClients.delete(clientId);
38962
- this.clientSockets.delete(clientId);
38963
- const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
38964
- const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1e3).toFixed(1)}s ago` : "";
38965
- this.logger.info?.(
38966
- `[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? "unknown"} remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`
38967
- );
38968
- this.emit("clientDisconnected", clientId);
38969
- if (this.connectedClients.size === 0 && !this.prestartStream) {
38970
- this.scheduleStop();
38971
- }
38972
- }
38973
- scheduleStop() {
38974
- if (this.stopGraceTimer) return;
38975
- this.logger.info?.(
38976
- `[Go2rtcTcpServer] no clients, scheduling stream stop in ${this.gracePeriodMs}ms`
38977
- );
38978
- this.stopGraceTimer = setTimeout(async () => {
38979
- this.stopGraceTimer = void 0;
38980
- if (this.connectedClients.size === 0 && this.nativeStreamActive) {
38981
- this.logger.info?.("[Go2rtcTcpServer] grace period expired, stopping native stream");
38982
- await this.stopNativeStream();
38983
- }
38984
- }, this.gracePeriodMs);
38985
- }
38986
- };
38987
-
38988
38270
  // src/baichuan/stream/BaichuanHttpStreamServer.ts
38989
- var import_node_events9 = require("events");
38271
+ var import_node_events8 = require("events");
38990
38272
  var import_node_child_process10 = require("child_process");
38991
38273
  var http4 = __toESM(require("http"), 1);
38992
38274
  var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
@@ -39033,7 +38315,7 @@ function isH264KeyframeFromAnnexB(annexB) {
39033
38315
  }
39034
38316
  return false;
39035
38317
  }
39036
- var BaichuanHttpStreamServer = class extends import_node_events9.EventEmitter {
38318
+ var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
39037
38319
  videoStream;
39038
38320
  listenPort;
39039
38321
  path;
@@ -39305,15 +38587,15 @@ var BaichuanHttpStreamServer = class extends import_node_events9.EventEmitter {
39305
38587
  };
39306
38588
 
39307
38589
  // src/baichuan/stream/BaichuanMjpegServer.ts
39308
- var import_node_events11 = require("events");
38590
+ var import_node_events10 = require("events");
39309
38591
  var http5 = __toESM(require("http"), 1);
39310
38592
 
39311
38593
  // src/baichuan/stream/MjpegTransformer.ts
39312
- var import_node_events10 = require("events");
38594
+ var import_node_events9 = require("events");
39313
38595
  var import_node_child_process11 = require("child_process");
39314
38596
  var JPEG_SOI = Buffer.from([255, 216]);
39315
38597
  var JPEG_EOI = Buffer.from([255, 217]);
39316
- var MjpegTransformer = class extends import_node_events10.EventEmitter {
38598
+ var MjpegTransformer = class extends import_node_events9.EventEmitter {
39317
38599
  options;
39318
38600
  ffmpeg = null;
39319
38601
  started = false;
@@ -39512,7 +38794,7 @@ Content-Length: ${frame.length}\r
39512
38794
  // src/baichuan/stream/BaichuanMjpegServer.ts
39513
38795
  init_H264Converter();
39514
38796
  init_H265Converter();
39515
- var BaichuanMjpegServer = class extends import_node_events11.EventEmitter {
38797
+ var BaichuanMjpegServer = class extends import_node_events10.EventEmitter {
39516
38798
  options;
39517
38799
  clients = /* @__PURE__ */ new Map();
39518
38800
  httpServer = null;
@@ -39793,14 +39075,14 @@ var BaichuanMjpegServer = class extends import_node_events11.EventEmitter {
39793
39075
  };
39794
39076
 
39795
39077
  // src/baichuan/stream/BaichuanWebRTCServer.ts
39796
- var import_node_events13 = require("events");
39078
+ var import_node_events12 = require("events");
39797
39079
  init_BcMediaAnnexBDecoder();
39798
39080
 
39799
39081
  // src/baichuan/stream/AacToOpusTranscoder.ts
39800
39082
  var import_node_child_process12 = require("child_process");
39801
39083
  var import_node_dgram3 = require("dgram");
39802
- var import_node_events12 = require("events");
39803
- var AacToOpusTranscoder = class extends import_node_events12.EventEmitter {
39084
+ var import_node_events11 = require("events");
39085
+ var AacToOpusTranscoder = class extends import_node_events11.EventEmitter {
39804
39086
  opts;
39805
39087
  socket = null;
39806
39088
  ffmpeg = null;
@@ -40017,7 +39299,7 @@ function getH264NalType(nalUnit) {
40017
39299
  function getH265NalType2(nalUnit) {
40018
39300
  return nalUnit[0] >> 1 & 63;
40019
39301
  }
40020
- var BaichuanWebRTCServer = class extends import_node_events13.EventEmitter {
39302
+ var BaichuanWebRTCServer = class extends import_node_events12.EventEmitter {
40021
39303
  options;
40022
39304
  sessions = /* @__PURE__ */ new Map();
40023
39305
  sessionIdCounter = 0;
@@ -41009,7 +40291,7 @@ Error: ${err}`
41009
40291
  };
41010
40292
 
41011
40293
  // src/baichuan/stream/BaichuanHlsServer.ts
41012
- var import_node_events14 = require("events");
40294
+ var import_node_events13 = require("events");
41013
40295
  var import_node_fs = __toESM(require("fs"), 1);
41014
40296
  var import_promises3 = __toESM(require("fs/promises"), 1);
41015
40297
  var import_node_os3 = __toESM(require("os"), 1);
@@ -41089,7 +40371,7 @@ function getNalTypes(codec, annexB) {
41089
40371
  }
41090
40372
  });
41091
40373
  }
41092
- var BaichuanHlsServer = class extends import_node_events14.EventEmitter {
40374
+ var BaichuanHlsServer = class extends import_node_events13.EventEmitter {
41093
40375
  api;
41094
40376
  channel;
41095
40377
  profile;
@@ -41658,10 +40940,10 @@ async function pingHost(host, timeoutMs = 3e3) {
41658
40940
  return false;
41659
40941
  }
41660
40942
  async function tcpReachabilityProbe(host, port, timeoutMs) {
41661
- const net7 = await import("net");
40943
+ const net6 = await import("net");
41662
40944
  return new Promise((resolve) => {
41663
40945
  let settled = false;
41664
- const socket = new net7.Socket();
40946
+ const socket = new net6.Socket();
41665
40947
  const timer = setTimeout(() => {
41666
40948
  if (settled) return;
41667
40949
  settled = true;
@@ -42254,10 +41536,10 @@ async function autoDetectDeviceType(inputs) {
42254
41536
  }
42255
41537
 
42256
41538
  // src/multifocal/compositeRtspServer.ts
42257
- var import_node_events15 = require("events");
41539
+ var import_node_events14 = require("events");
42258
41540
  var import_node_child_process14 = require("child_process");
42259
- var net5 = __toESM(require("net"), 1);
42260
- var CompositeRtspServer = class extends import_node_events15.EventEmitter {
41541
+ var net4 = __toESM(require("net"), 1);
41542
+ var CompositeRtspServer = class extends import_node_events14.EventEmitter {
42261
41543
  options;
42262
41544
  compositeStream = null;
42263
41545
  rtspServer = null;
@@ -42323,7 +41605,7 @@ var CompositeRtspServer = class extends import_node_events15.EventEmitter {
42323
41605
  const width = widerStreamInfo?.width ?? 1920;
42324
41606
  const height = widerStreamInfo?.height ?? 1080;
42325
41607
  const fps = widerStreamInfo?.frameRate ?? 25;
42326
- this.rtspServer = net5.createServer((socket) => {
41608
+ this.rtspServer = net4.createServer((socket) => {
42327
41609
  this.handleRtspConnection(socket);
42328
41610
  });
42329
41611
  await new Promise((resolve, reject) => {
@@ -43033,8 +42315,8 @@ var RtspBackchannel = class _RtspBackchannel {
43033
42315
  };
43034
42316
 
43035
42317
  // src/baichuan/stream/BaichuanRtspBackchannelServer.ts
43036
- var import_node_events16 = require("events");
43037
- var net6 = __toESM(require("net"), 1);
42318
+ var import_node_events15 = require("events");
42319
+ var net5 = __toESM(require("net"), 1);
43038
42320
  var crypto3 = __toESM(require("crypto"), 1);
43039
42321
  var md5Hex = (s) => crypto3.createHash("md5").update(s).digest("hex");
43040
42322
  var RTCP_KEEPALIVE_INTERVAL_MS = 1e4;
@@ -43090,7 +42372,7 @@ function extractPublicEndpoint(url, requestText) {
43090
42372
  if (hostHeader) return hostHeader;
43091
42373
  return null;
43092
42374
  }
43093
- var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends import_node_events16.EventEmitter {
42375
+ var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends import_node_events15.EventEmitter {
43094
42376
  listenHost;
43095
42377
  listenPort;
43096
42378
  logger;
@@ -43207,7 +42489,7 @@ var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends
43207
42489
  async start() {
43208
42490
  if (this.server) return;
43209
42491
  await new Promise((resolve, reject) => {
43210
- const server = net6.createServer((socket) => this.handleConnection(socket));
42492
+ const server = net5.createServer((socket) => this.handleConnection(socket));
43211
42493
  const onError = (err) => {
43212
42494
  server.removeListener("error", onError);
43213
42495
  reject(err);
@@ -44235,7 +43517,6 @@ function buildInitialStatus(config) {
44235
43517
  DUAL_LENS_DUAL_MOTION_MODELS,
44236
43518
  DUAL_LENS_MODELS,
44237
43519
  DUAL_LENS_SINGLE_MOTION_MODELS,
44238
- Go2rtcTcpServer,
44239
43520
  H264RtpDepacketizer,
44240
43521
  H265RtpDepacketizer,
44241
43522
  HlsSessionManager,