@apocaliss92/nodelink-js 0.4.11 → 0.4.13

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.js CHANGED
@@ -45,7 +45,54 @@ import {
45
45
  parseSupportXml,
46
46
  setGlobalLogger,
47
47
  xmlIndicatesFloodlight
48
- } from "./chunk-HGQ53FB3.js";
48
+ } from "./chunk-R5AJV73A.js";
49
+ import {
50
+ ReolinkCgiApi,
51
+ ReolinkHttpClient,
52
+ applyStreamPatch,
53
+ applyXmlTagPatch,
54
+ buildAbilityInfoExtensionXml,
55
+ buildBinaryExtensionXml,
56
+ buildChannelExtensionXml,
57
+ buildFloodlightManualXml,
58
+ buildLoginXml,
59
+ buildLogoutXml,
60
+ buildPreviewStopXml,
61
+ buildPreviewStopXmlV11,
62
+ buildPreviewXml,
63
+ buildPreviewXmlV11,
64
+ buildPtzControlXml,
65
+ buildPtzPresetXml,
66
+ buildPtzPresetXmlV2,
67
+ buildRtspPath,
68
+ buildRtspUrl,
69
+ buildSirenManualXml,
70
+ buildSirenTimesXml,
71
+ buildStartZoomFocusXml,
72
+ buildWhiteLedStateXml,
73
+ captureModelFixtures,
74
+ collectCgiDiagnostics,
75
+ collectMultifocalDiagnostics,
76
+ collectNativeDiagnostics,
77
+ collectNvrDiagnostics,
78
+ computeExpectedStreamCompatibility,
79
+ createDiagnosticsBundle,
80
+ ensureXmlHeader,
81
+ getXmlText,
82
+ normalizeDayNightMode,
83
+ normalizeOpenClose,
84
+ parseRecordingFileName,
85
+ patchNestedTag,
86
+ printNvrDiagnostics,
87
+ runAllDiagnosticsConsecutively,
88
+ runMultifocalDiagnosticsConsecutively,
89
+ sampleStreams,
90
+ sanitizeFixtureData,
91
+ testChannelStreams,
92
+ upsertXmlTag,
93
+ xmlEscape,
94
+ zipDirectory
95
+ } from "./chunk-2JNXKT3C.js";
49
96
  import {
50
97
  AesStreamDecryptor,
51
98
  BC_AES_IV,
@@ -91,6 +138,7 @@ import {
91
138
  BC_CMD_ID_GET_AUDIO_CFG,
92
139
  BC_CMD_ID_GET_AUDIO_TASK,
93
140
  BC_CMD_ID_GET_AUTO_FOCUS,
141
+ BC_CMD_ID_GET_AUTO_REBOOT,
94
142
  BC_CMD_ID_GET_BATTERY_INFO,
95
143
  BC_CMD_ID_GET_BATTERY_INFO_LIST,
96
144
  BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
@@ -98,6 +146,8 @@ import {
98
146
  BC_CMD_ID_GET_DING_DONG_CFG,
99
147
  BC_CMD_ID_GET_DING_DONG_LIST,
100
148
  BC_CMD_ID_GET_DING_DONG_SILENT,
149
+ BC_CMD_ID_GET_DST,
150
+ BC_CMD_ID_GET_EMAIL,
101
151
  BC_CMD_ID_GET_EMAIL_TASK,
102
152
  BC_CMD_ID_GET_ENC,
103
153
  BC_CMD_ID_GET_FTP_TASK,
@@ -105,6 +155,7 @@ import {
105
155
  BC_CMD_ID_GET_KIT_AP_CFG,
106
156
  BC_CMD_ID_GET_LED_STATE,
107
157
  BC_CMD_ID_GET_MOTION_ALARM,
158
+ BC_CMD_ID_GET_NTP,
108
159
  BC_CMD_ID_GET_ONLINE_USER_LIST,
109
160
  BC_CMD_ID_GET_OSD_DATETIME,
110
161
  BC_CMD_ID_GET_PIR_INFO,
@@ -121,6 +172,7 @@ import {
121
172
  BC_CMD_ID_GET_SUPPORT,
122
173
  BC_CMD_ID_GET_SYSTEM_GENERAL,
123
174
  BC_CMD_ID_GET_TIMELAPSE_CFG,
175
+ BC_CMD_ID_GET_VERSION_INFO,
124
176
  BC_CMD_ID_GET_VIDEO_INPUT,
125
177
  BC_CMD_ID_GET_WHITE_LED,
126
178
  BC_CMD_ID_GET_WIFI,
@@ -144,18 +196,24 @@ import {
144
196
  BC_CMD_ID_SET_AUDIO_CFG,
145
197
  BC_CMD_ID_SET_AUDIO_TASK,
146
198
  BC_CMD_ID_SET_AUTO_FOCUS,
199
+ BC_CMD_ID_SET_AUTO_REBOOT,
147
200
  BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
148
201
  BC_CMD_ID_SET_DING_DONG_CFG,
149
202
  BC_CMD_ID_SET_DING_DONG_SILENT,
203
+ BC_CMD_ID_SET_DST,
204
+ BC_CMD_ID_SET_EMAIL,
150
205
  BC_CMD_ID_SET_EMAIL_TASK,
151
206
  BC_CMD_ID_SET_ENC,
152
207
  BC_CMD_ID_SET_LED_STATE,
153
208
  BC_CMD_ID_SET_MOTION_ALARM,
209
+ BC_CMD_ID_SET_NTP,
210
+ BC_CMD_ID_SET_OSD_DATETIME,
154
211
  BC_CMD_ID_SET_PIR_INFO,
155
212
  BC_CMD_ID_SET_PRIVACY_MASK,
156
213
  BC_CMD_ID_SET_PUSH_TASK,
157
214
  BC_CMD_ID_SET_RECORD,
158
215
  BC_CMD_ID_SET_RECORD_CFG,
216
+ BC_CMD_ID_SET_SYSTEM_GENERAL,
159
217
  BC_CMD_ID_SET_VIDEO_INPUT,
160
218
  BC_CMD_ID_SET_WHITE_LED_STATE,
161
219
  BC_CMD_ID_SET_WHITE_LED_TASK,
@@ -165,6 +223,7 @@ import {
165
223
  BC_CMD_ID_TALK_ABILITY,
166
224
  BC_CMD_ID_TALK_CONFIG,
167
225
  BC_CMD_ID_TALK_RESET,
226
+ BC_CMD_ID_TEST_EMAIL,
168
227
  BC_CMD_ID_UDP_KEEP_ALIVE,
169
228
  BC_CMD_ID_VIDEO,
170
229
  BC_CMD_ID_VIDEO_STOP,
@@ -177,52 +236,20 @@ import {
177
236
  BcMediaCodec,
178
237
  H264RtpDepacketizer,
179
238
  H265RtpDepacketizer,
180
- ReolinkCgiApi,
181
- ReolinkHttpClient,
182
239
  aesDecrypt,
183
240
  aesEncrypt,
184
- applyStreamPatch,
185
- applyXmlTagPatch,
186
241
  bcDecrypt,
187
242
  bcEncrypt,
188
243
  bcHeaderHasPayloadOffset,
189
- buildAbilityInfoExtensionXml,
190
- buildBinaryExtensionXml,
191
- buildChannelExtensionXml,
192
- buildFloodlightManualXml,
193
- buildLoginXml,
194
- buildLogoutXml,
195
- buildPreviewStopXml,
196
- buildPreviewStopXmlV11,
197
- buildPreviewXml,
198
- buildPreviewXmlV11,
199
- buildPtzControlXml,
200
- buildPtzPresetXml,
201
- buildPtzPresetXmlV2,
202
- buildRtspPath,
203
- buildRtspUrl,
204
- buildSirenManualXml,
205
- buildSirenTimesXml,
206
- buildStartZoomFocusXml,
207
- buildWhiteLedStateXml,
208
- captureModelFixtures,
209
- collectCgiDiagnostics,
210
- collectMultifocalDiagnostics,
211
- collectNativeDiagnostics,
212
- collectNvrDiagnostics,
213
- computeExpectedStreamCompatibility,
214
244
  convertToAnnexB,
215
245
  convertToAnnexB2,
216
246
  convertToLengthPrefixed,
217
- createDiagnosticsBundle,
218
247
  deriveAesKey,
219
248
  detectVideoCodecFromNal,
220
- ensureXmlHeader,
221
249
  extractPpsFromAnnexB,
222
250
  extractSpsFromAnnexB,
223
251
  extractVpsFromAnnexB,
224
252
  getH265NalType,
225
- getXmlText,
226
253
  hasStartCodes,
227
254
  hasStartCodes2,
228
255
  isH264KeyframeAnnexB,
@@ -232,22 +259,10 @@ import {
232
259
  isValidH265AnnexBAccessUnit,
233
260
  md5HexUpper,
234
261
  md5StrModern,
235
- normalizeDayNightMode,
236
- normalizeOpenClose,
237
262
  parseBcMedia,
238
- parseRecordingFileName,
239
- patchNestedTag,
240
- printNvrDiagnostics,
241
- runAllDiagnosticsConsecutively,
242
- runMultifocalDiagnosticsConsecutively,
243
- sampleStreams,
244
- sanitizeFixtureData,
245
263
  splitAnnexBToNalPayloads,
246
- splitAnnexBToNalPayloads2,
247
- testChannelStreams,
248
- xmlEscape,
249
- zipDirectory
250
- } from "./chunk-EDLMKBG2.js";
264
+ splitAnnexBToNalPayloads2
265
+ } from "./chunk-C57QV7IL.js";
251
266
 
252
267
  // src/reolink/baichuan/HlsSessionManager.ts
253
268
  var withTimeout = async (p, ms, label) => {
@@ -4757,6 +4772,17 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4757
4772
  // Native stream
4758
4773
  nativeFanout = null;
4759
4774
  nativeStreamActive = false;
4775
+ // Set only by stopNativeStream() (explicit teardown) so the fanout's onEnd
4776
+ // callback can short-circuit cleanup/restart logic. NOT set by the inactivity-
4777
+ // timeout force-restart path — that flow wants onEnd to run and decide
4778
+ // whether to restart based on prestartStream / connected clients.
4779
+ nativeStreamStopping = false;
4780
+ // Pending retry timer for the unbounded auto-restart loop. When a stream
4781
+ // start fails transiently (camera in maintenance reboot, idle-disconnect
4782
+ // race, etc.) we keep trying with exponential backoff until either the
4783
+ // server is stopped or a frame finally arrives.
4784
+ nativeStreamRetryTimer;
4785
+ nativeStreamRetryDelayMs = 0;
4760
4786
  dedicatedSessionRelease;
4761
4787
  detectedVideoType;
4762
4788
  // Client tracking
@@ -4825,6 +4851,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4825
4851
  if (!this.active) return;
4826
4852
  this.active = false;
4827
4853
  clearTimeout(this.stopGraceTimer);
4854
+ this.clearNativeStreamRetry();
4828
4855
  this.stopStreamHealthMonitor();
4829
4856
  for (const [id, sock] of this.clientSockets) {
4830
4857
  sock.destroy();
@@ -5153,6 +5180,46 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5153
5180
  // -----------------------------------------------------------------------
5154
5181
  // Native stream management
5155
5182
  // -----------------------------------------------------------------------
5183
+ /**
5184
+ * Schedule another startNativeStream() attempt after the given delay.
5185
+ * Idempotent: a no-op if a retry is already scheduled, the server is no
5186
+ * longer active, or an explicit stop is in progress. Implements unbounded
5187
+ * exponential backoff (5s → 60s) so a camera that stays unreachable for
5188
+ * minutes (e.g. nightly maintenance reboot) eventually recovers without
5189
+ * manual intervention — see issue #16.
5190
+ */
5191
+ scheduleNativeStreamRetry(reason) {
5192
+ if (!this.active) return;
5193
+ if (this.nativeStreamStopping) return;
5194
+ if (this.nativeStreamRetryTimer) return;
5195
+ const delay = this.nativeStreamRetryDelayMs > 0 ? this.nativeStreamRetryDelayMs : 5e3;
5196
+ this.logger.info?.(
5197
+ `[Go2rtcTcpServer] scheduling native stream retry in ${(delay / 1e3).toFixed(0)}s (reason=${reason})`
5198
+ );
5199
+ this.nativeStreamRetryTimer = setTimeout(() => {
5200
+ this.nativeStreamRetryTimer = void 0;
5201
+ if (!this.active) return;
5202
+ if (this.nativeStreamStopping) return;
5203
+ this.startNativeStream().catch((err) => {
5204
+ this.logger.warn?.(
5205
+ `[Go2rtcTcpServer] retry of startNativeStream threw: ${err instanceof Error ? err.message : err}`
5206
+ );
5207
+ });
5208
+ }, delay);
5209
+ this.nativeStreamRetryDelayMs = Math.min(delay * 2, 6e4);
5210
+ }
5211
+ /**
5212
+ * Cancel any pending retry timer and reset the backoff. Called on explicit
5213
+ * stop and on first-frame-received so the next failure starts the backoff
5214
+ * window from scratch.
5215
+ */
5216
+ clearNativeStreamRetry() {
5217
+ if (this.nativeStreamRetryTimer) {
5218
+ clearTimeout(this.nativeStreamRetryTimer);
5219
+ this.nativeStreamRetryTimer = void 0;
5220
+ }
5221
+ this.nativeStreamRetryDelayMs = 0;
5222
+ }
5156
5223
  async startNativeStream() {
5157
5224
  if (this.nativeStreamActive) return;
5158
5225
  if (!this.api.isReady) {
@@ -5169,8 +5236,9 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5169
5236
  await this.api.ensureConnected();
5170
5237
  } catch (e) {
5171
5238
  this.logger.warn?.(
5172
- `[Go2rtcTcpServer] ensureConnected failed, aborting stream start: ${e}`
5239
+ `[Go2rtcTcpServer] ensureConnected failed: ${e}`
5173
5240
  );
5241
+ this.scheduleNativeStreamRetry("ensureConnected failed");
5174
5242
  return;
5175
5243
  }
5176
5244
  }
@@ -5200,6 +5268,9 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5200
5268
  ...dedicatedClient ? { client: dedicatedClient } : {}
5201
5269
  }),
5202
5270
  onFrame: (frame) => {
5271
+ if (!hadFrames) {
5272
+ this.clearNativeStreamRetry();
5273
+ }
5203
5274
  hadFrames = true;
5204
5275
  this.lastFrameAt = Date.now();
5205
5276
  this.totalFramesReceived++;
@@ -5243,7 +5314,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5243
5314
  this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);
5244
5315
  },
5245
5316
  onEnd: () => {
5246
- if (!this.nativeStreamActive) return;
5317
+ if (this.nativeStreamStopping) return;
5247
5318
  this.nativeStreamActive = false;
5248
5319
  this.nativeFanout = null;
5249
5320
  this.stopStreamHealthMonitor();
@@ -5285,18 +5356,24 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5285
5356
  this.startStreamHealthMonitor();
5286
5357
  }
5287
5358
  async stopNativeStream() {
5359
+ this.nativeStreamStopping = true;
5288
5360
  this.nativeStreamActive = false;
5361
+ this.clearNativeStreamRetry();
5289
5362
  this.stopStreamHealthMonitor();
5290
5363
  const fanout = this.nativeFanout;
5291
5364
  this.nativeFanout = null;
5292
- if (fanout) {
5293
- await fanout.stop();
5294
- }
5295
- this.prebuffer = [];
5296
- if (this.dedicatedSessionRelease) {
5297
- await this.dedicatedSessionRelease().catch(() => {
5298
- });
5299
- this.dedicatedSessionRelease = void 0;
5365
+ try {
5366
+ if (fanout) {
5367
+ await fanout.stop();
5368
+ }
5369
+ this.prebuffer = [];
5370
+ if (this.dedicatedSessionRelease) {
5371
+ await this.dedicatedSessionRelease().catch(() => {
5372
+ });
5373
+ this.dedicatedSessionRelease = void 0;
5374
+ }
5375
+ } finally {
5376
+ this.nativeStreamStopping = false;
5300
5377
  }
5301
5378
  }
5302
5379
  // -----------------------------------------------------------------------
@@ -5486,10 +5563,15 @@ var BaichuanHttpStreamServer = class extends EventEmitter3 {
5486
5563
  // Force a known frame rate on raw H.264 input so the muxer gets valid PTS/DTS.
5487
5564
  "-r",
5488
5565
  String(this.inputFps),
5566
+ // `+genpts` generates uniform PTS from `-r` for raw Annex-B input. We
5567
+ // deliberately do NOT pass `-use_wallclock_as_timestamps 1`: that
5568
+ // overrides the generated PTS with the host wallclock at frame ARRIVAL
5569
+ // time, and the network stream is bursty so the resulting PTS is
5570
+ // uneven. With `-r` forcing a target rate downstream, ffmpeg then
5571
+ // drops/duplicates frames to match — visible as the periodic stutter
5572
+ // reported on local-restreamer recordings (issue #11).
5489
5573
  "-fflags",
5490
5574
  "+genpts",
5491
- "-use_wallclock_as_timestamps",
5492
- "1",
5493
5575
  "-f",
5494
5576
  "h264",
5495
5577
  // Input format (H.264 Annex-B)
@@ -6166,7 +6248,194 @@ var BaichuanMjpegServer = class extends EventEmitter5 {
6166
6248
  };
6167
6249
 
6168
6250
  // src/baichuan/stream/BaichuanWebRTCServer.ts
6251
+ import { EventEmitter as EventEmitter7 } from "events";
6252
+
6253
+ // src/baichuan/stream/AacToOpusTranscoder.ts
6254
+ import { spawn as spawn7 } from "child_process";
6255
+ import { createSocket } from "dgram";
6169
6256
  import { EventEmitter as EventEmitter6 } from "events";
6257
+ var AacToOpusTranscoder = class extends EventEmitter6 {
6258
+ opts;
6259
+ socket = null;
6260
+ ffmpeg = null;
6261
+ port = 0;
6262
+ starting = null;
6263
+ stopped = false;
6264
+ constructor(options = {}) {
6265
+ super();
6266
+ this.opts = {
6267
+ ffmpegPath: options.ffmpegPath ?? "ffmpeg",
6268
+ opusSampleRate: options.opusSampleRate ?? 48e3,
6269
+ opusChannels: options.opusChannels ?? 2,
6270
+ opusFrameMs: options.opusFrameMs ?? 20,
6271
+ opusBitrate: options.opusBitrate ?? 64e3,
6272
+ ...options.logger !== void 0 ? { logger: options.logger } : {}
6273
+ };
6274
+ }
6275
+ log(level, message) {
6276
+ this.opts.logger?.(level, `[AacToOpusTranscoder] ${message}`);
6277
+ }
6278
+ /**
6279
+ * Allocate the UDP loopback socket and spawn ffmpeg. Must be awaited before
6280
+ * the first call to `feedAac`.
6281
+ */
6282
+ async start() {
6283
+ if (this.starting) return this.starting;
6284
+ this.starting = this._start();
6285
+ return this.starting;
6286
+ }
6287
+ async _start() {
6288
+ if (this.stopped) throw new Error("transcoder stopped");
6289
+ this.socket = createSocket("udp4");
6290
+ await new Promise((resolve, reject) => {
6291
+ this.socket.once("error", reject);
6292
+ this.socket.bind({ address: "127.0.0.1", port: 0 }, () => {
6293
+ this.socket.removeListener("error", reject);
6294
+ this.port = this.socket.address().port;
6295
+ resolve();
6296
+ });
6297
+ });
6298
+ this.log("info", `UDP loopback bound on 127.0.0.1:${this.port}`);
6299
+ this.socket.on("message", (rtpPacket) => this.handleRtp(rtpPacket));
6300
+ this.socket.on("error", (err) => {
6301
+ this.log("error", `socket error: ${err.message}`);
6302
+ this.emit("error", err);
6303
+ });
6304
+ const args = [
6305
+ "-loglevel",
6306
+ "warning",
6307
+ "-fflags",
6308
+ "nobuffer",
6309
+ "-f",
6310
+ "aac",
6311
+ "-i",
6312
+ "pipe:0",
6313
+ "-c:a",
6314
+ "libopus",
6315
+ "-ar",
6316
+ String(this.opts.opusSampleRate),
6317
+ "-ac",
6318
+ String(this.opts.opusChannels),
6319
+ "-application",
6320
+ "audio",
6321
+ "-frame_duration",
6322
+ String(this.opts.opusFrameMs),
6323
+ "-b:a",
6324
+ String(this.opts.opusBitrate),
6325
+ // Important: disable VBR so the output rate matches the configured
6326
+ // bitrate consistently — easier on the browser jitter buffer.
6327
+ "-vbr",
6328
+ "off",
6329
+ "-f",
6330
+ "rtp",
6331
+ `rtp://127.0.0.1:${this.port}`
6332
+ ];
6333
+ this.log("info", `spawning ffmpeg with: ${this.opts.ffmpegPath} ${args.join(" ")}`);
6334
+ this.ffmpeg = spawn7(this.opts.ffmpegPath, args, {
6335
+ stdio: ["pipe", "ignore", "pipe"]
6336
+ });
6337
+ this.ffmpeg.on("error", (err) => {
6338
+ this.log("error", `ffmpeg spawn error: ${err.message}`);
6339
+ this.emit("error", err);
6340
+ });
6341
+ this.ffmpeg.stderr?.on("data", (chunk) => {
6342
+ this.log("debug", `ffmpeg stderr: ${chunk.toString().trim()}`);
6343
+ });
6344
+ this.ffmpeg.on("exit", (code) => {
6345
+ this.log(code === 0 ? "info" : "warn", `ffmpeg exited code=${code}`);
6346
+ this.emit("exit", code);
6347
+ });
6348
+ }
6349
+ /**
6350
+ * Feed one or more concatenated ADTS AAC frames to ffmpeg. Returns the
6351
+ * number of bytes written. Safe to call before a frame is fully buffered.
6352
+ */
6353
+ feedAac(buf) {
6354
+ if (this.stopped) return false;
6355
+ if (!this.ffmpeg?.stdin || !this.ffmpeg.stdin.writable) {
6356
+ this.log("debug", "drop AAC frame \u2014 ffmpeg stdin not writable");
6357
+ return false;
6358
+ }
6359
+ return this.ffmpeg.stdin.write(buf);
6360
+ }
6361
+ /**
6362
+ * Close stdin (ffmpeg exits cleanly on EOF) and tear down the UDP socket.
6363
+ */
6364
+ async stop() {
6365
+ if (this.stopped) return;
6366
+ this.stopped = true;
6367
+ try {
6368
+ this.ffmpeg?.stdin?.end();
6369
+ } catch {
6370
+ }
6371
+ if (this.ffmpeg && this.ffmpeg.exitCode === null) {
6372
+ await new Promise((resolve) => {
6373
+ const t = setTimeout(() => {
6374
+ this.ffmpeg?.kill("SIGKILL");
6375
+ resolve();
6376
+ }, 500);
6377
+ this.ffmpeg?.once("exit", () => {
6378
+ clearTimeout(t);
6379
+ resolve();
6380
+ });
6381
+ });
6382
+ }
6383
+ this.ffmpeg = null;
6384
+ if (this.socket) {
6385
+ try {
6386
+ this.socket.close();
6387
+ } catch {
6388
+ }
6389
+ this.socket = null;
6390
+ }
6391
+ this.log("info", "stopped");
6392
+ }
6393
+ /**
6394
+ * Parse an RTP packet ffmpeg emitted on the loopback socket and surface
6395
+ * its Opus payload. RTP header layout (RFC 3550):
6396
+ *
6397
+ * 0 1 2 3
6398
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
6399
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6400
+ * |V=2|P|X| CC |M| PT | sequence number |
6401
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6402
+ * | timestamp |
6403
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6404
+ * | synchronization source (SSRC) identifier |
6405
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6406
+ *
6407
+ * We honor the CC, X (extension) and P (padding) bits to compute the
6408
+ * payload offset / length correctly even if ffmpeg ever emits those.
6409
+ */
6410
+ handleRtp(packet) {
6411
+ if (packet.length < 12) return;
6412
+ const b0 = packet[0];
6413
+ const b1 = packet[1];
6414
+ const version = b0 >> 6;
6415
+ if (version !== 2) return;
6416
+ const padding = (b0 & 32) !== 0;
6417
+ const extension = (b0 & 16) !== 0;
6418
+ const csrcCount = b0 & 15;
6419
+ const marker = (b1 & 128) !== 0;
6420
+ let offset = 12 + csrcCount * 4;
6421
+ if (extension) {
6422
+ if (packet.length < offset + 4) return;
6423
+ const extLen = packet.readUInt16BE(offset + 2);
6424
+ offset += 4 + extLen * 4;
6425
+ }
6426
+ let end = packet.length;
6427
+ if (padding) {
6428
+ const pad = packet[packet.length - 1];
6429
+ end -= pad;
6430
+ }
6431
+ if (offset >= end) return;
6432
+ const timestamp = packet.readUInt32BE(4);
6433
+ const payload = Buffer.from(packet.subarray(offset, end));
6434
+ this.emit("packet", { payload, timestamp, marker });
6435
+ }
6436
+ };
6437
+
6438
+ // src/baichuan/stream/BaichuanWebRTCServer.ts
6170
6439
  function parseAnnexBNalUnits(annexB) {
6171
6440
  const nalUnits = [];
6172
6441
  let offset = 0;
@@ -6201,7 +6470,7 @@ function getH264NalType(nalUnit) {
6201
6470
  function getH265NalType2(nalUnit) {
6202
6471
  return nalUnit[0] >> 1 & 63;
6203
6472
  }
6204
- var BaichuanWebRTCServer = class extends EventEmitter6 {
6473
+ var BaichuanWebRTCServer = class extends EventEmitter7 {
6205
6474
  options;
6206
6475
  sessions = /* @__PURE__ */ new Map();
6207
6476
  sessionIdCounter = 0;
@@ -6439,6 +6708,14 @@ Error: ${err}`
6439
6708
  }
6440
6709
  session.dataChannel = null;
6441
6710
  }
6711
+ if (session.audioTranscoder) {
6712
+ try {
6713
+ await session.audioTranscoder.stop();
6714
+ } catch (err) {
6715
+ this.log("debug", `Error stopping audio transcoder: ${err}`);
6716
+ }
6717
+ session.audioTranscoder = null;
6718
+ }
6442
6719
  if (session.cleanup) {
6443
6720
  session.cleanup();
6444
6721
  }
@@ -6565,6 +6842,17 @@ Error: ${err}`
6565
6842
  }
6566
6843
  if (frame.audio) {
6567
6844
  session.stats.audioFrames++;
6845
+ if (session.stats.audioFrames === 1) {
6846
+ const head = frame.data && frame.data.length > 0 ? frame.data.subarray(0, Math.min(8, frame.data.length)).toString("hex") : "(empty)";
6847
+ this.log(
6848
+ "info",
6849
+ `First audio frame for ${session.id}: codec=${frame.codec ?? "?"} bytes=${frame.data?.length ?? 0} head=${head}`
6850
+ );
6851
+ }
6852
+ if (this.options.ffmpegPath !== "" && frame.data && frame.data.length > 0) {
6853
+ await this.ensureAudioTranscoder(session, werift);
6854
+ session.audioTranscoder?.feedAac(frame.data);
6855
+ }
6568
6856
  } else {
6569
6857
  if (frame.data) {
6570
6858
  if (!session.videoCodec && frame.videoType) {
@@ -6635,7 +6923,7 @@ Error: ${err}`
6635
6923
  if (now - lastLogTime >= 5e3) {
6636
6924
  this.log(
6637
6925
  "debug",
6638
- `WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB`
6926
+ `WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} video frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB | audio frames=${session.stats.audioFrames}`
6639
6927
  );
6640
6928
  lastLogTime = now;
6641
6929
  packetsSentSinceLastLog = 0;
@@ -6651,6 +6939,77 @@ Error: ${err}`
6651
6939
  }
6652
6940
  this.log("info", `Native stream ended for session ${session.id}`);
6653
6941
  }
6942
+ /**
6943
+ * Lazily start the AAC → Opus transcoder for `session` and wire it to the
6944
+ * audio RTP track. ffmpeg writes RTP-formatted Opus packets back to a UDP
6945
+ * loopback the transcoder owns; we strip the RTP header and rewrap the
6946
+ * Opus payload with our audioTrack's SSRC so the browser receives a
6947
+ * coherent stream.
6948
+ */
6949
+ async ensureAudioTranscoder(session, werift) {
6950
+ if (session.audioTranscoder !== void 0) return;
6951
+ const { RtpPacket, RtpHeader } = werift;
6952
+ const ssrc = session.audioTrack?.ssrc ?? Math.floor(Math.random() * 4294967295);
6953
+ const transcoder = new AacToOpusTranscoder({
6954
+ ...this.options.ffmpegPath ? { ffmpegPath: this.options.ffmpegPath } : {},
6955
+ logger: (level, msg) => this.log(level, msg)
6956
+ });
6957
+ session.audioRtpSequence = Math.floor(Math.random() * 65535);
6958
+ session.audioRtpTimestampBase = 0;
6959
+ transcoder.on("packet", ({ payload, timestamp, marker }) => {
6960
+ try {
6961
+ if (session.audioRtpTimestampBase === void 0 || session.audioRtpTimestampBase === 0) {
6962
+ session.audioRtpTimestampBase = timestamp;
6963
+ }
6964
+ const localTs = timestamp - session.audioRtpTimestampBase >>> 0;
6965
+ const seq = session.audioRtpSequence;
6966
+ session.audioRtpSequence = seq + 1 & 65535;
6967
+ const header = new RtpHeader({
6968
+ version: 2,
6969
+ padding: false,
6970
+ extension: false,
6971
+ marker,
6972
+ // Werift assigns 111 to Opus by default in the receiver SDP, but it
6973
+ // also accepts other PTs. Use 111 for compatibility with the offer
6974
+ // we generated earlier.
6975
+ payloadType: 111,
6976
+ sequenceNumber: seq,
6977
+ timestamp: localTs,
6978
+ ssrc
6979
+ });
6980
+ const rtp = new RtpPacket(header, payload);
6981
+ session.audioTrack?.writeRtp(rtp);
6982
+ if (!session.audioStartedLogged) {
6983
+ session.audioStartedLogged = true;
6984
+ this.log(
6985
+ "info",
6986
+ `Audio RTP started for ${session.id} (PT=111, ssrc=${ssrc})`
6987
+ );
6988
+ }
6989
+ } catch (err) {
6990
+ this.log(
6991
+ "warn",
6992
+ `audio writeRtp failed for ${session.id}: ${err.message}`
6993
+ );
6994
+ }
6995
+ });
6996
+ transcoder.on("error", (err) => {
6997
+ this.log("error", `audio transcoder error for ${session.id}: ${err.message}`);
6998
+ });
6999
+ transcoder.on("exit", (code) => {
7000
+ this.log("info", `audio transcoder exited (${code}) for ${session.id}`);
7001
+ });
7002
+ try {
7003
+ await transcoder.start();
7004
+ session.audioTranscoder = transcoder;
7005
+ } catch (err) {
7006
+ this.log(
7007
+ "error",
7008
+ `failed to start audio transcoder for ${session.id}: ${err.message}`
7009
+ );
7010
+ session.audioTranscoder = null;
7011
+ }
7012
+ }
6654
7013
  /**
6655
7014
  * Send H.264 frame via RTP media track
6656
7015
  * Returns the number of RTP packets sent
@@ -6938,21 +7297,21 @@ Error: ${err}`
6938
7297
  `Sending ${codec} frame ${frameNumber}: ${packet.length} bytes, keyframe=${isKeyframe}`
6939
7298
  );
6940
7299
  }
6941
- const MAX_CHUNK_SIZE = 16e3;
7300
+ const CHUNK_HEADER_LEN = 4;
7301
+ const MAX_PAYLOAD_PER_CHUNK = 16e3 - CHUNK_HEADER_LEN;
6942
7302
  try {
6943
- if (packet.length <= MAX_CHUNK_SIZE) {
6944
- session.videoDataChannel.send(packet);
6945
- } else {
6946
- const totalChunks = Math.ceil(packet.length / MAX_CHUNK_SIZE);
6947
- for (let i = 0; i < totalChunks; i++) {
6948
- const start = i * MAX_CHUNK_SIZE;
6949
- const end = Math.min(start + MAX_CHUNK_SIZE, packet.length);
6950
- const chunk = packet.subarray(start, end);
6951
- const chunkHeader = Buffer.alloc(2);
6952
- chunkHeader.writeUInt8(i, 0);
6953
- chunkHeader.writeUInt8(totalChunks, 1);
6954
- session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));
6955
- }
7303
+ const totalChunks = Math.max(
7304
+ 1,
7305
+ Math.ceil(packet.length / MAX_PAYLOAD_PER_CHUNK)
7306
+ );
7307
+ for (let i = 0; i < totalChunks; i++) {
7308
+ const start = i * MAX_PAYLOAD_PER_CHUNK;
7309
+ const end = Math.min(start + MAX_PAYLOAD_PER_CHUNK, packet.length);
7310
+ const chunk = packet.subarray(start, end);
7311
+ const chunkHeader = Buffer.alloc(CHUNK_HEADER_LEN);
7312
+ chunkHeader.writeUInt16BE(i, 0);
7313
+ chunkHeader.writeUInt16BE(totalChunks, 2);
7314
+ session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));
6956
7315
  }
6957
7316
  return true;
6958
7317
  } catch (err) {
@@ -7103,12 +7462,12 @@ Error: ${err}`
7103
7462
  };
7104
7463
 
7105
7464
  // src/baichuan/stream/BaichuanHlsServer.ts
7106
- import { EventEmitter as EventEmitter7 } from "events";
7465
+ import { EventEmitter as EventEmitter8 } from "events";
7107
7466
  import fs from "fs";
7108
7467
  import fsp from "fs/promises";
7109
7468
  import os from "os";
7110
7469
  import path from "path";
7111
- import { spawn as spawn7 } from "child_process";
7470
+ import { spawn as spawn8 } from "child_process";
7112
7471
  function parseAnnexBNalUnits2(data) {
7113
7472
  const units = [];
7114
7473
  const len = data.length;
@@ -7180,7 +7539,7 @@ function getNalTypes(codec, annexB) {
7180
7539
  }
7181
7540
  });
7182
7541
  }
7183
- var BaichuanHlsServer = class extends EventEmitter7 {
7542
+ var BaichuanHlsServer = class extends EventEmitter8 {
7184
7543
  api;
7185
7544
  channel;
7186
7545
  profile;
@@ -7537,10 +7896,17 @@ var BaichuanHlsServer = class extends EventEmitter7 {
7537
7896
  "-hide_banner",
7538
7897
  "-loglevel",
7539
7898
  "warning",
7899
+ // `+genpts` makes ffmpeg generate uniform PTS from the declared `-r`
7900
+ // when the raw H.264/H.265 input has none. We deliberately do NOT use
7901
+ // `-use_wallclock_as_timestamps 1` here: it replaces the generated
7902
+ // PTS with the host wallclock at FRAME ARRIVAL time, and because the
7903
+ // camera ships frames in bursty network reads, the resulting PTS
7904
+ // sequence is uneven. With `-r 25` (or anything else) forcing a
7905
+ // target rate downstream, ffmpeg then drops/duplicates frames to
7906
+ // match — visible as the periodic stutter / pulsing reported on
7907
+ // local-restreamer recordings (issue #11).
7540
7908
  "-fflags",
7541
7909
  "+genpts",
7542
- "-use_wallclock_as_timestamps",
7543
- "1",
7544
7910
  "-r",
7545
7911
  "25",
7546
7912
  "-f",
@@ -7575,7 +7941,7 @@ var BaichuanHlsServer = class extends EventEmitter7 {
7575
7941
  this.segmentPattern,
7576
7942
  this.playlistPath
7577
7943
  );
7578
- const p = spawn7(this.ffmpegPath, args, {
7944
+ const p = spawn8(this.ffmpegPath, args, {
7579
7945
  stdio: ["pipe", "ignore", "pipe"]
7580
7946
  });
7581
7947
  p.on("error", (err) => {
@@ -7598,10 +7964,10 @@ var BaichuanHlsServer = class extends EventEmitter7 {
7598
7964
  };
7599
7965
 
7600
7966
  // src/multifocal/compositeRtspServer.ts
7601
- import { EventEmitter as EventEmitter8 } from "events";
7602
- import { spawn as spawn8 } from "child_process";
7967
+ import { EventEmitter as EventEmitter9 } from "events";
7968
+ import { spawn as spawn9 } from "child_process";
7603
7969
  import * as net2 from "net";
7604
- var CompositeRtspServer = class extends EventEmitter8 {
7970
+ var CompositeRtspServer = class extends EventEmitter9 {
7605
7971
  options;
7606
7972
  compositeStream = null;
7607
7973
  rtspServer = null;
@@ -7706,7 +8072,7 @@ var CompositeRtspServer = class extends EventEmitter8 {
7706
8072
  this.logger.log?.(
7707
8073
  `[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(" ")}`
7708
8074
  );
7709
- this.ffmpegProcess = spawn8("ffmpeg", ffmpegArgs, {
8075
+ this.ffmpegProcess = spawn9("ffmpeg", ffmpegArgs, {
7710
8076
  stdio: ["pipe", "pipe", "pipe"]
7711
8077
  });
7712
8078
  this.ffmpegProcess.on("error", (error) => {
@@ -7814,6 +8180,86 @@ var CompositeRtspServer = class extends EventEmitter8 {
7814
8180
  return this.connectedClients.size;
7815
8181
  }
7816
8182
  };
8183
+
8184
+ // src/reolink/baichuan/utils/motionZone.ts
8185
+ function decodeMotionScopeBitmap(valueTable, columns, rows, width = columns, height = rows) {
8186
+ const trimmed = valueTable.trim().replace(/[^A-Za-z0-9+/=]/g, "");
8187
+ const bytes = base64DecodeToBytes(trimmed);
8188
+ const totalBits = columns * rows;
8189
+ if (bytes.length * 8 < totalBits) {
8190
+ throw new Error(
8191
+ `valueTable too short: have ${bytes.length * 8} bits, need ${totalBits}`
8192
+ );
8193
+ }
8194
+ const w = Math.min(width, columns);
8195
+ const h = Math.min(height, rows);
8196
+ const cells = new Array(w * h);
8197
+ for (let r = 0; r < h; r++) {
8198
+ for (let c = 0; c < w; c++) {
8199
+ const bitIndex = r * columns + c;
8200
+ const byteIdx = bitIndex >> 3;
8201
+ const bitIdx = 7 - (bitIndex & 7);
8202
+ cells[r * w + c] = (bytes[byteIdx] ?? 0) >> bitIdx & 1 ? true : false;
8203
+ }
8204
+ }
8205
+ return { width: w, height: h, columns, rows, cells };
8206
+ }
8207
+ function encodeMotionScopeBitmap(scope) {
8208
+ const bytes = new Uint8Array(Math.ceil(scope.columns * scope.rows / 8));
8209
+ for (let r = 0; r < scope.height; r++) {
8210
+ for (let c = 0; c < scope.width; c++) {
8211
+ const on = scope.cells[r * scope.width + c];
8212
+ if (!on) continue;
8213
+ const bitIndex = r * scope.columns + c;
8214
+ const byteIdx = bitIndex >> 3;
8215
+ const bitIdx = 7 - (bitIndex & 7);
8216
+ bytes[byteIdx] = (bytes[byteIdx] ?? 0) | 1 << bitIdx;
8217
+ }
8218
+ }
8219
+ return base64EncodeBytes(bytes);
8220
+ }
8221
+ function fullCoverageScope(columns, rows, width = columns, height = rows) {
8222
+ return {
8223
+ width,
8224
+ height,
8225
+ columns,
8226
+ rows,
8227
+ cells: new Array(width * height).fill(true)
8228
+ };
8229
+ }
8230
+ var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8231
+ function base64EncodeBytes(bytes) {
8232
+ let out = "";
8233
+ for (let i = 0; i < bytes.length; i += 3) {
8234
+ const b0 = bytes[i] ?? 0;
8235
+ const b1 = bytes[i + 1] ?? 0;
8236
+ const b2 = bytes[i + 2] ?? 0;
8237
+ out += B64_ALPHABET[b0 >> 2];
8238
+ out += B64_ALPHABET[(b0 & 3) << 4 | b1 >> 4];
8239
+ out += i + 1 < bytes.length ? B64_ALPHABET[(b1 & 15) << 2 | b2 >> 6] : "=";
8240
+ out += i + 2 < bytes.length ? B64_ALPHABET[b2 & 63] : "=";
8241
+ }
8242
+ return out;
8243
+ }
8244
+ function base64DecodeToBytes(b64) {
8245
+ const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
8246
+ const stripped = padded.replace(/=+$/, "");
8247
+ const out = new Uint8Array(Math.floor(stripped.length * 6 / 8));
8248
+ let bits = 0;
8249
+ let value = 0;
8250
+ let outIdx = 0;
8251
+ for (const ch of stripped) {
8252
+ const idx = B64_ALPHABET.indexOf(ch);
8253
+ if (idx < 0) continue;
8254
+ value = value << 6 | idx;
8255
+ bits += 6;
8256
+ if (bits >= 8) {
8257
+ bits -= 8;
8258
+ out[outIdx++] = value >> bits & 255;
8259
+ }
8260
+ }
8261
+ return out.subarray(0, outIdx);
8262
+ }
7817
8263
  export {
7818
8264
  AesStreamDecryptor,
7819
8265
  AutodiscoveryClient,
@@ -7860,6 +8306,7 @@ export {
7860
8306
  BC_CMD_ID_GET_AUDIO_CFG,
7861
8307
  BC_CMD_ID_GET_AUDIO_TASK,
7862
8308
  BC_CMD_ID_GET_AUTO_FOCUS,
8309
+ BC_CMD_ID_GET_AUTO_REBOOT,
7863
8310
  BC_CMD_ID_GET_BATTERY_INFO,
7864
8311
  BC_CMD_ID_GET_BATTERY_INFO_LIST,
7865
8312
  BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
@@ -7867,6 +8314,8 @@ export {
7867
8314
  BC_CMD_ID_GET_DING_DONG_CFG,
7868
8315
  BC_CMD_ID_GET_DING_DONG_LIST,
7869
8316
  BC_CMD_ID_GET_DING_DONG_SILENT,
8317
+ BC_CMD_ID_GET_DST,
8318
+ BC_CMD_ID_GET_EMAIL,
7870
8319
  BC_CMD_ID_GET_EMAIL_TASK,
7871
8320
  BC_CMD_ID_GET_ENC,
7872
8321
  BC_CMD_ID_GET_FTP_TASK,
@@ -7874,6 +8323,7 @@ export {
7874
8323
  BC_CMD_ID_GET_KIT_AP_CFG,
7875
8324
  BC_CMD_ID_GET_LED_STATE,
7876
8325
  BC_CMD_ID_GET_MOTION_ALARM,
8326
+ BC_CMD_ID_GET_NTP,
7877
8327
  BC_CMD_ID_GET_ONLINE_USER_LIST,
7878
8328
  BC_CMD_ID_GET_OSD_DATETIME,
7879
8329
  BC_CMD_ID_GET_PIR_INFO,
@@ -7890,6 +8340,7 @@ export {
7890
8340
  BC_CMD_ID_GET_SUPPORT,
7891
8341
  BC_CMD_ID_GET_SYSTEM_GENERAL,
7892
8342
  BC_CMD_ID_GET_TIMELAPSE_CFG,
8343
+ BC_CMD_ID_GET_VERSION_INFO,
7893
8344
  BC_CMD_ID_GET_VIDEO_INPUT,
7894
8345
  BC_CMD_ID_GET_WHITE_LED,
7895
8346
  BC_CMD_ID_GET_WIFI,
@@ -7913,18 +8364,24 @@ export {
7913
8364
  BC_CMD_ID_SET_AUDIO_CFG,
7914
8365
  BC_CMD_ID_SET_AUDIO_TASK,
7915
8366
  BC_CMD_ID_SET_AUTO_FOCUS,
8367
+ BC_CMD_ID_SET_AUTO_REBOOT,
7916
8368
  BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
7917
8369
  BC_CMD_ID_SET_DING_DONG_CFG,
7918
8370
  BC_CMD_ID_SET_DING_DONG_SILENT,
8371
+ BC_CMD_ID_SET_DST,
8372
+ BC_CMD_ID_SET_EMAIL,
7919
8373
  BC_CMD_ID_SET_EMAIL_TASK,
7920
8374
  BC_CMD_ID_SET_ENC,
7921
8375
  BC_CMD_ID_SET_LED_STATE,
7922
8376
  BC_CMD_ID_SET_MOTION_ALARM,
8377
+ BC_CMD_ID_SET_NTP,
8378
+ BC_CMD_ID_SET_OSD_DATETIME,
7923
8379
  BC_CMD_ID_SET_PIR_INFO,
7924
8380
  BC_CMD_ID_SET_PRIVACY_MASK,
7925
8381
  BC_CMD_ID_SET_PUSH_TASK,
7926
8382
  BC_CMD_ID_SET_RECORD,
7927
8383
  BC_CMD_ID_SET_RECORD_CFG,
8384
+ BC_CMD_ID_SET_SYSTEM_GENERAL,
7928
8385
  BC_CMD_ID_SET_VIDEO_INPUT,
7929
8386
  BC_CMD_ID_SET_WHITE_LED_STATE,
7930
8387
  BC_CMD_ID_SET_WHITE_LED_TASK,
@@ -7934,6 +8391,7 @@ export {
7934
8391
  BC_CMD_ID_TALK_ABILITY,
7935
8392
  BC_CMD_ID_TALK_CONFIG,
7936
8393
  BC_CMD_ID_TALK_RESET,
8394
+ BC_CMD_ID_TEST_EMAIL,
7937
8395
  BC_CMD_ID_UDP_KEEP_ALIVE,
7938
8396
  BC_CMD_ID_VIDEO,
7939
8397
  BC_CMD_ID_VIDEO_STOP,
@@ -8028,6 +8486,7 @@ export {
8028
8486
  decideSleepInferenceTransition,
8029
8487
  decideVideoclipTranscodeMode,
8030
8488
  decodeHeader,
8489
+ decodeMotionScopeBitmap,
8031
8490
  deriveAesKey,
8032
8491
  detectIosClient,
8033
8492
  detectVideoCodecFromNal,
@@ -8040,6 +8499,7 @@ export {
8040
8499
  discoverViaUdpBroadcast,
8041
8500
  discoverViaUdpDirect,
8042
8501
  encodeHeader,
8502
+ encodeMotionScopeBitmap,
8043
8503
  ensureXmlHeader,
8044
8504
  extractH264ParamSetsFromAccessUnit,
8045
8505
  extractH265ParamSetsFromAccessUnit,
@@ -8048,6 +8508,7 @@ export {
8048
8508
  extractVpsFromAnnexB,
8049
8509
  flattenAbilitiesForChannel,
8050
8510
  formatMjpegFrame,
8511
+ fullCoverageScope,
8051
8512
  getConstructedVideoStreamOptions,
8052
8513
  getGlobalLogger,
8053
8514
  getH265NalType,
@@ -8091,6 +8552,7 @@ export {
8091
8552
  splitAnnexBToNals,
8092
8553
  splitAnnexBToNalPayloads2 as splitH265AnnexBToNalPayloads,
8093
8554
  testChannelStreams,
8555
+ upsertXmlTag,
8094
8556
  xmlEscape,
8095
8557
  xmlIndicatesFloodlight,
8096
8558
  zipDirectory