@apocaliss92/nodelink-js 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,67 +1,20 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
1
+ import {
2
+ BC_CLASS_MODERN_24,
3
+ BC_CLASS_MODERN_24_ALT,
4
+ BC_CMD_ID_GET_WHITE_LED,
5
+ BC_CMD_ID_VIDEO,
6
+ BaichuanVideoStream,
7
+ getH265NalType,
8
+ recordingsTraceLog,
9
+ splitAnnexBToNalPayloads,
10
+ splitAnnexBToNalPayloads2
11
+ } from "./chunk-W2ANCJVM.js";
7
12
 
8
13
  // src/debug/DiagnosticsTools.ts
9
- import * as fs4 from "fs";
10
- import * as path4 from "path";
14
+ import * as fs2 from "fs";
15
+ import * as path2 from "path";
11
16
  import { spawn } from "child_process";
12
17
 
13
- // src/debug/DebugConfig.ts
14
- import * as fs from "fs";
15
- import * as path from "path";
16
- function normalizeDebugOptions(opts) {
17
- const general = opts?.general === true;
18
- const debugRtsp = opts?.debugRtsp === true;
19
- const traceNativeStream = opts?.traceNativeStream === true;
20
- const traceRecordings = opts?.traceRecordings === true;
21
- const traceTalk = opts?.traceTalk === true;
22
- const traceEvents = opts?.traceEvents === true;
23
- const dumpEnabled = opts?.dump?.enabled === true;
24
- const dumpDir = opts?.dump?.dir && opts.dump.dir.trim() || path.join(process.cwd(), "test", "frames-debug");
25
- const dumpBcMedia = opts?.dump?.bcmedia ?? dumpEnabled;
26
- const dumpNals = opts?.dump?.nals ?? dumpEnabled;
27
- return {
28
- general,
29
- debugRtsp,
30
- traceNativeStream,
31
- traceRecordings,
32
- traceTalk,
33
- traceEvents,
34
- dumpEnabled,
35
- dumpDir,
36
- dumpBcMedia,
37
- dumpNals
38
- };
39
- }
40
- function recordingsTraceLog(cfg, logger, tag, message) {
41
- if (!cfg?.traceRecordings || !logger) return;
42
- logger.log(`[${tag}] ${message}`);
43
- }
44
- function ensureDumpDir(cfg) {
45
- if (!cfg.dumpEnabled) return;
46
- fs.mkdirSync(cfg.dumpDir, { recursive: true });
47
- }
48
- function debugLog(cfg, logger, tag, message) {
49
- if (!cfg.general) return;
50
- logger.debug(`[${tag}] ${message}`);
51
- }
52
- function traceLog(cfg, logger, tag, message) {
53
- if (!cfg.traceNativeStream) return;
54
- logger.debug(`[${tag}] ${message}`);
55
- }
56
- function talkTraceLog(cfg, logger, tag, message) {
57
- if (!cfg.traceTalk) return;
58
- logger.debug(`[${tag}] ${message}`);
59
- }
60
- function eventTraceLog(cfg, logger, tag, message) {
61
- if (!cfg.traceEvents) return;
62
- logger.debug(`[${tag}] ${message}`);
63
- }
64
-
65
18
  // src/reolink/baichuan/recordingFileName.ts
66
19
  var FLAGS_CAM_V2 = {
67
20
  resolution_index: [0, 7],
@@ -2339,18 +2292,18 @@ var ReolinkCgiApi = class _ReolinkCgiApi {
2339
2292
  };
2340
2293
 
2341
2294
  // src/debug/DiagnosticsTools.ts
2342
- import { join as join5 } from "path";
2295
+ import { join as join3 } from "path";
2343
2296
 
2344
2297
  // src/debug/zip.ts
2345
- import * as fs2 from "fs";
2346
- import * as path2 from "path";
2298
+ import * as fs from "fs";
2299
+ import * as path from "path";
2347
2300
  import yazl from "yazl";
2348
2301
  async function walkDir(rootDir, currentDir) {
2349
- const entries = await fs2.promises.readdir(currentDir, { withFileTypes: true });
2302
+ const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
2350
2303
  const out = [];
2351
2304
  for (const ent of entries) {
2352
- const absPath = path2.join(currentDir, ent.name);
2353
- const relPath = path2.relative(rootDir, absPath).replace(/\\/g, "/");
2305
+ const absPath = path.join(currentDir, ent.name);
2306
+ const relPath = path.relative(rootDir, absPath).replace(/\\/g, "/");
2354
2307
  if (ent.isDirectory()) {
2355
2308
  out.push({ absPath, relPath, isDir: true });
2356
2309
  out.push(...await walkDir(rootDir, absPath));
@@ -2365,7 +2318,7 @@ async function walkDir(rootDir, currentDir) {
2365
2318
  }
2366
2319
  async function zipDirectory(params) {
2367
2320
  const { sourceDir, zipPath } = params;
2368
- await fs2.promises.mkdir(path2.dirname(zipPath), { recursive: true });
2321
+ await fs.promises.mkdir(path.dirname(zipPath), { recursive: true });
2369
2322
  const zipFile = new yazl.ZipFile();
2370
2323
  const entries = await walkDir(sourceDir, sourceDir);
2371
2324
  const dirs = entries.filter((e) => e.isDir);
@@ -2377,7 +2330,7 @@ async function zipDirectory(params) {
2377
2330
  for (const f of files) {
2378
2331
  zipFile.addFile(f.absPath, f.relPath);
2379
2332
  }
2380
- const out = fs2.createWriteStream(zipPath);
2333
+ const out = fs.createWriteStream(zipPath);
2381
2334
  await new Promise((resolve, reject) => {
2382
2335
  out.on("error", reject);
2383
2336
  zipFile.outputStream.on("error", reject);
@@ -2387,2414 +2340,6 @@ async function zipDirectory(params) {
2387
2340
  });
2388
2341
  }
2389
2342
 
2390
- // src/baichuan/stream/BaichuanVideoStream.ts
2391
- import { EventEmitter } from "events";
2392
- import * as fs3 from "fs";
2393
- import * as path3 from "path";
2394
-
2395
- // src/protocol/crypto.ts
2396
- import { createCipheriv, createDecipheriv, createHash } from "crypto";
2397
-
2398
- // src/protocol/constants.ts
2399
- var BC_TCP_DEFAULT_PORT = 9e3;
2400
- var BC_MAGIC = Buffer.from([240, 222, 188, 10]);
2401
- var BC_MAGIC_REV = Buffer.from([160, 203, 237, 15]);
2402
- var BC_XML_KEY = Uint8Array.from([
2403
- 31,
2404
- 45,
2405
- 60,
2406
- 75,
2407
- 90,
2408
- 105,
2409
- 120,
2410
- 255
2411
- ]);
2412
- var BC_AES_IV = Buffer.from("0123456789abcdef", "utf8");
2413
- var BC_CLASS_LEGACY = 25876;
2414
- var BC_CLASS_MODERN_20 = 26132;
2415
- var BC_CLASS_MODERN_24 = 25620;
2416
- var BC_CLASS_MODERN_24_ALT = 0;
2417
- var BC_CLASS_FILE_DOWNLOAD = 25730;
2418
- function bcHeaderHasPayloadOffset(messageClass) {
2419
- return messageClass === BC_CLASS_MODERN_24 || messageClass === BC_CLASS_MODERN_24_ALT || messageClass === BC_CLASS_FILE_DOWNLOAD;
2420
- }
2421
- var BC_CMD_ID_LOGIN = 1;
2422
- var BC_CMD_ID_LOGOUT = 2;
2423
- var BC_CMD_ID_VIDEO = 3;
2424
- var BC_CMD_ID_VIDEO_STOP = 4;
2425
- var BC_CMD_ID_FILE_INFO_LIST_REPLAY = 5;
2426
- var BC_CMD_ID_FILE_INFO_LIST_STOP = 7;
2427
- var BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO = 8;
2428
- var BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD = 13;
2429
- var BC_CMD_ID_FILE_INFO_LIST_OPEN = 14;
2430
- var BC_CMD_ID_FILE_INFO_LIST_GET = 15;
2431
- var BC_CMD_ID_FILE_INFO_LIST_CLOSE = 16;
2432
- var BC_CMD_ID_FIND_REC_VIDEO_OPEN = 272;
2433
- var BC_CMD_ID_FIND_REC_VIDEO_GET = 273;
2434
- var BC_CMD_ID_FIND_REC_VIDEO_CLOSE = 274;
2435
- var BC_CMD_ID_COVER_PREVIEW = 298;
2436
- var BC_CMD_ID_COVER_STANDALONE_458 = 458;
2437
- var BC_CMD_ID_COVER_STANDALONE_459 = 459;
2438
- var BC_CMD_ID_COVER_STANDALONE_460 = 460;
2439
- var BC_CMD_ID_COVER_STANDALONE_461 = 461;
2440
- var BC_CMD_ID_COVER_STANDALONE_462 = 462;
2441
- var BC_CMD_ID_COVER_RESPONSE = 138;
2442
- var BC_CMD_ID_TALK_ABILITY = 10;
2443
- var BC_CMD_ID_TALK_RESET = 11;
2444
- var BC_CMD_ID_TALK_CONFIG = 201;
2445
- var BC_CMD_ID_TALK = 202;
2446
- var BC_CMD_ID_PTZ_CONTROL = 18;
2447
- var BC_CMD_ID_PTZ_CONTROL_PRESET = 19;
2448
- var BC_CMD_ID_GET_PTZ_PRESET = 190;
2449
- var BC_CMD_ID_GET_PTZ_POSITION = 433;
2450
- var BC_CMD_ID_GET_ZOOM_FOCUS = 294;
2451
- var BC_CMD_ID_SET_ZOOM_FOCUS = 295;
2452
- var BC_CMD_ID_GET_BATTERY_INFO_LIST = 252;
2453
- var BC_CMD_ID_GET_BATTERY_INFO = 253;
2454
- var BC_CMD_ID_UDP_KEEP_ALIVE = 234;
2455
- var BC_CMD_ID_GET_PIR_INFO = 212;
2456
- var BC_CMD_ID_SET_PIR_INFO = 213;
2457
- var BC_CMD_ID_GET_MOTION_ALARM = 46;
2458
- var BC_CMD_ID_SET_MOTION_ALARM = 47;
2459
- var BC_CMD_ID_ALARM_EVENT_LIST = 33;
2460
- var BC_CMD_ID_GET_AI_ALARM = 342;
2461
- var BC_CMD_ID_SET_AI_ALARM = 343;
2462
- var BC_CMD_ID_GET_AUDIO_ALARM = 547;
2463
- var BC_CMD_ID_AUDIO_ALARM_PLAY = 263;
2464
- var BC_CMD_ID_GET_WHITE_LED = 289;
2465
- var BC_CMD_ID_SET_WHITE_LED_STATE = 288;
2466
- var BC_CMD_ID_SET_WHITE_LED_TASK = 290;
2467
- var BC_CMD_ID_FLOODLIGHT_STATUS_LIST = 291;
2468
- var BC_CMD_ID_ABILITY_INFO = 151;
2469
- var BC_CMD_ID_SUPPORT = 199;
2470
- var BC_CMD_ID_PING = 93;
2471
- var BC_CMD_ID_CHANNEL_INFO_ALL = 145;
2472
- var BC_CMD_ID_GET_OSD_DATETIME = 44;
2473
- var BC_CMD_ID_GET_RECORD_CFG = 54;
2474
- var BC_CMD_ID_GET_ABILITY_SUPPORT = 58;
2475
- var BC_CMD_ID_GET_FTP_TASK = 70;
2476
- var BC_CMD_ID_GET_RECORD = 81;
2477
- var BC_CMD_ID_GET_HDD_INFO_LIST = 102;
2478
- var BC_CMD_ID_GET_WIFI_SIGNAL = 115;
2479
- var BC_CMD_ID_GET_WIFI = 116;
2480
- var BC_CMD_ID_GET_ONLINE_USER_LIST = 120;
2481
- var BC_CMD_ID_GET_DAY_RECORDS = 142;
2482
- var BC_CMD_ID_GET_STREAM_INFO_LIST = 146;
2483
- var BC_CMD_ID_GET_LED_STATE = 208;
2484
- var BC_CMD_ID_GET_EMAIL_TASK = 217;
2485
- var BC_CMD_ID_GET_AUDIO_TASK = 232;
2486
- var BC_CMD_ID_GET_AUDIO_CFG = 264;
2487
- var BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD = 296;
2488
- var BC_CMD_ID_GET_TIMELAPSE_CFG = 319;
2489
- var BC_CMD_ID_GET_AI_DENOISE = 439;
2490
- var BC_CMD_ID_GET_KIT_AP_CFG = 481;
2491
- var BC_CMD_ID_GET_REC_ENC_CFG = 507;
2492
- var BC_CMD_ID_GET_ACCESS_USER_LIST = 511;
2493
- var BC_CMD_ID_GET_SLEEP_STATE = 574;
2494
- var BC_CMD_ID_GET_VIDEO_INPUT = 26;
2495
- var BC_CMD_ID_GET_SYSTEM_GENERAL = 104;
2496
- var BC_CMD_ID_GET_SUPPORT = 199;
2497
- var BC_CMD_ID_GET_AI_CFG = 299;
2498
- var BC_CMD_ID_SET_AI_CFG = 300;
2499
- var BC_CMD_ID_GET_SIREN_STATUS = 547;
2500
- var BC_CMD_ID_SET_AUDIO_TASK = 231;
2501
- var BC_CMD_ID_SET_VIDEO_INPUT = 25;
2502
- var BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD = 297;
2503
- var BC_CMD_ID_GET_ENC = 56;
2504
- var BC_CMD_ID_SET_ENC = 57;
2505
- var BC_CMD_ID_GET_PRIVACY_MASK = 52;
2506
- var BC_CMD_ID_SET_PRIVACY_MASK = 53;
2507
- var BC_CMD_ID_SET_AI_DENOISE = 440;
2508
- var BC_CMD_ID_SET_LED_STATE = 209;
2509
- var BC_CMD_ID_SET_AUDIO_CFG = 265;
2510
- var BC_CMD_ID_SET_RECORD = 82;
2511
- var BC_CMD_ID_SET_RECORD_CFG = 55;
2512
- var BC_CMD_ID_SET_EMAIL_TASK = 216;
2513
- var BC_CMD_ID_GET_PUSH_TASK = 219;
2514
- var BC_CMD_ID_SET_PUSH_TASK = 218;
2515
- var BC_CMD_ID_GET_AUTO_FOCUS = 224;
2516
- var BC_CMD_ID_SET_AUTO_FOCUS = 225;
2517
- var BC_CMD_ID_CMD_123 = 123;
2518
- var BC_CMD_ID_CMD_209 = 209;
2519
- var BC_CMD_ID_CMD_265 = 265;
2520
- var BC_CMD_ID_CMD_440 = 440;
2521
- var BC_CMD_ID_PUSH_VIDEO_INPUT = 78;
2522
- var BC_CMD_ID_PUSH_SERIAL = 79;
2523
- var BC_CMD_ID_PUSH_NET_INFO = 464;
2524
- var BC_CMD_ID_PUSH_DINGDONG_LIST = 484;
2525
- var BC_CMD_ID_PUSH_SLEEP_STATUS = 623;
2526
- var BC_CMD_ID_PUSH_COORDINATE_POINT_LIST = 723;
2527
- var BC_CMD_ID_DING_DONG_CTRL = 483;
2528
- var BC_CMD_ID_GET_DING_DONG_LIST = 484;
2529
- var BC_CMD_ID_DING_DONG_OPT = 485;
2530
- var BC_CMD_ID_GET_DING_DONG_CFG = 486;
2531
- var BC_CMD_ID_SET_DING_DONG_CFG = 487;
2532
- var BC_CMD_ID_QUICK_REPLY_PLAY = 349;
2533
- var BC_CMD_ID_GET_DING_DONG_SILENT = 609;
2534
- var BC_CMD_ID_SET_DING_DONG_SILENT = 610;
2535
-
2536
- // src/protocol/crypto.ts
2537
- function md5HexUpper(input) {
2538
- return createHash("md5").update(input, "utf8").digest("hex").toUpperCase();
2539
- }
2540
- function md5StrModern(input) {
2541
- return md5HexUpper(input).slice(0, 31);
2542
- }
2543
- function deriveAesKey(nonce, password) {
2544
- const keyStr = md5StrModern(`${nonce}-${password}`).slice(0, 16);
2545
- return Buffer.from(keyStr, "utf8");
2546
- }
2547
- function bcEncrypt(buf, offset) {
2548
- const off = offset & 255;
2549
- const out = Buffer.allocUnsafe(buf.length);
2550
- for (let i = 0; i < buf.length; i++) {
2551
- const key = BC_XML_KEY[(off + i) % BC_XML_KEY.length];
2552
- out[i] = buf[i] ^ key ^ off;
2553
- }
2554
- return out;
2555
- }
2556
- function bcDecrypt(buf, offset) {
2557
- return bcEncrypt(buf, offset);
2558
- }
2559
- function aesEncrypt(buf, key) {
2560
- if (buf.length === 0) return Buffer.alloc(0);
2561
- const cipher = createCipheriv("aes-128-cfb", key, BC_AES_IV);
2562
- cipher.setAutoPadding(false);
2563
- return Buffer.concat([cipher.update(buf), cipher.final()]);
2564
- }
2565
- function aesDecrypt(buf, key) {
2566
- if (buf.length === 0) return Buffer.alloc(0);
2567
- const decipher = createDecipheriv("aes-128-cfb", key, BC_AES_IV);
2568
- decipher.setAutoPadding(false);
2569
- return Buffer.concat([decipher.update(buf), decipher.final()]);
2570
- }
2571
- var AesStreamDecryptor = class {
2572
- decipher = null;
2573
- key;
2574
- constructor(key) {
2575
- this.key = key;
2576
- }
2577
- /**
2578
- * Reset the cipher state (start decrypting with fresh IV).
2579
- * Call this when a new BcMedia packet is detected.
2580
- */
2581
- reset() {
2582
- this.decipher = createDecipheriv("aes-128-cfb", this.key, BC_AES_IV);
2583
- this.decipher.setAutoPadding(false);
2584
- }
2585
- /**
2586
- * Decrypt a chunk using the current cipher state.
2587
- * Automatically resets if no cipher exists.
2588
- *
2589
- * @param buf - Encrypted buffer to decrypt
2590
- * @returns Decrypted buffer
2591
- */
2592
- update(buf) {
2593
- if (buf.length === 0) return Buffer.alloc(0);
2594
- if (!this.decipher) this.reset();
2595
- return this.decipher.update(buf);
2596
- }
2597
- /**
2598
- * Check if the decryptor has been initialized.
2599
- */
2600
- isInitialized() {
2601
- return this.decipher !== null;
2602
- }
2603
- };
2604
-
2605
- // src/baichuan/stream/BcMediaParser.ts
2606
- var MAGIC_INFO_V1 = 825241649;
2607
- var MAGIC_INFO_V2 = 842018865;
2608
- var MAGIC_IFRAME_START = 1667510320;
2609
- var MAGIC_IFRAME_END = 1667510329;
2610
- var MAGIC_PFRAME_START = 1667510576;
2611
- var MAGIC_PFRAME_END = 1667510585;
2612
- var MAGIC_AAC = 1651979568;
2613
- var MAGIC_ADPCM = 1651978544;
2614
- var PAD_SIZE = 8;
2615
- function parseBcMedia(buf) {
2616
- if (buf.length < 4) return null;
2617
- const magic = buf.readUInt32LE(0);
2618
- if (magic === MAGIC_INFO_V1) {
2619
- return parseInfoV1(buf);
2620
- } else if (magic === MAGIC_INFO_V2) {
2621
- return parseInfoV2(buf);
2622
- } else if (magic >= MAGIC_IFRAME_START && magic <= MAGIC_IFRAME_END) {
2623
- return parseIframe(buf);
2624
- } else if (magic >= MAGIC_PFRAME_START && magic <= MAGIC_PFRAME_END) {
2625
- return parsePframe(buf);
2626
- } else if (magic === MAGIC_AAC) {
2627
- return parseAac(buf);
2628
- } else if (magic === MAGIC_ADPCM) {
2629
- return parseAdpcm(buf);
2630
- }
2631
- return null;
2632
- }
2633
- function parseInfoV1(buf) {
2634
- if (buf.length < 32) return null;
2635
- const headerSize = buf.readUInt32LE(4);
2636
- if (headerSize !== 32) return null;
2637
- const media = {
2638
- type: "InfoV1",
2639
- videoWidth: buf.readUInt32LE(8),
2640
- videoHeight: buf.readUInt32LE(12),
2641
- fps: buf.readUInt8(17),
2642
- startYear: buf.readUInt8(18),
2643
- startMonth: buf.readUInt8(19),
2644
- startDay: buf.readUInt8(20),
2645
- startHour: buf.readUInt8(21),
2646
- startMin: buf.readUInt8(22),
2647
- startSeconds: buf.readUInt8(23),
2648
- endYear: buf.readUInt8(24),
2649
- endMonth: buf.readUInt8(25),
2650
- endDay: buf.readUInt8(26),
2651
- endHour: buf.readUInt8(27),
2652
- endMin: buf.readUInt8(28),
2653
- endSeconds: buf.readUInt8(29)
2654
- };
2655
- return { media, consumed: 32 };
2656
- }
2657
- function parseInfoV2(buf) {
2658
- if (buf.length < 32) return null;
2659
- const headerSize = buf.readUInt32LE(4);
2660
- if (headerSize !== 32) return null;
2661
- const media = {
2662
- type: "InfoV2",
2663
- videoWidth: buf.readUInt32LE(8),
2664
- videoHeight: buf.readUInt32LE(12),
2665
- fps: buf.readUInt8(17),
2666
- startYear: buf.readUInt8(18),
2667
- startMonth: buf.readUInt8(19),
2668
- startDay: buf.readUInt8(20),
2669
- startHour: buf.readUInt8(21),
2670
- startMin: buf.readUInt8(22),
2671
- startSeconds: buf.readUInt8(23),
2672
- endYear: buf.readUInt8(24),
2673
- endMonth: buf.readUInt8(25),
2674
- endDay: buf.readUInt8(26),
2675
- endHour: buf.readUInt8(27),
2676
- endMin: buf.readUInt8(28),
2677
- endSeconds: buf.readUInt8(29)
2678
- };
2679
- return { media, consumed: 32 };
2680
- }
2681
- function parseIframe(buf) {
2682
- if (buf.length < 20) return null;
2683
- const videoTypeStr = buf.toString("utf8", 4, 8);
2684
- if (videoTypeStr !== "H264" && videoTypeStr !== "H265") return null;
2685
- const videoType = videoTypeStr;
2686
- const payloadSize = buf.readUInt32LE(8);
2687
- const additionalHeaderSize = buf.readUInt32LE(12);
2688
- const microseconds = buf.readUInt32LE(16);
2689
- let offset = 20;
2690
- const unknown = buf.readUInt32LE(offset);
2691
- offset += 4;
2692
- let time;
2693
- if (buf.length < offset + additionalHeaderSize) return null;
2694
- const additionalHeader = buf.subarray(offset, offset + additionalHeaderSize);
2695
- if (additionalHeaderSize >= 4) {
2696
- time = additionalHeader.readUInt32LE(0);
2697
- }
2698
- offset += additionalHeaderSize;
2699
- if (buf.length < offset + payloadSize) return null;
2700
- const data = buf.subarray(offset, offset + payloadSize);
2701
- offset += payloadSize;
2702
- const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
2703
- if (buf.length < offset + padSize) return null;
2704
- offset += padSize;
2705
- const media = {
2706
- type: "Iframe",
2707
- videoType,
2708
- microseconds,
2709
- ...time !== void 0 ? { time } : {},
2710
- additionalHeader,
2711
- additionalHeaderSize,
2712
- unknown,
2713
- data
2714
- };
2715
- return { media, consumed: offset };
2716
- }
2717
- function parsePframe(buf) {
2718
- if (buf.length < 20) return null;
2719
- const videoTypeStr = buf.toString("utf8", 4, 8);
2720
- if (videoTypeStr !== "H264" && videoTypeStr !== "H265") return null;
2721
- const videoType = videoTypeStr;
2722
- const payloadSize = buf.readUInt32LE(8);
2723
- const additionalHeaderSize = buf.readUInt32LE(12);
2724
- const microseconds = buf.readUInt32LE(16);
2725
- let offset = 20;
2726
- const unknown = buf.readUInt32LE(offset);
2727
- offset += 4;
2728
- if (buf.length < offset + additionalHeaderSize) return null;
2729
- const additionalHeader = buf.subarray(offset, offset + additionalHeaderSize);
2730
- offset += additionalHeaderSize;
2731
- if (buf.length < offset + payloadSize) return null;
2732
- const data = buf.subarray(offset, offset + payloadSize);
2733
- offset += payloadSize;
2734
- const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
2735
- if (buf.length < offset + padSize) return null;
2736
- offset += padSize;
2737
- const media = {
2738
- type: "Pframe",
2739
- videoType,
2740
- microseconds,
2741
- additionalHeader,
2742
- additionalHeaderSize,
2743
- unknown,
2744
- data
2745
- };
2746
- return { media, consumed: offset };
2747
- }
2748
- function parseAac(buf) {
2749
- if (buf.length < 12) return null;
2750
- const payloadSize = buf.readUInt16LE(4);
2751
- const payloadSizeB = buf.readUInt16LE(6);
2752
- if (payloadSize !== payloadSizeB) return null;
2753
- const headerLen = 8;
2754
- const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
2755
- const totalLen = headerLen + payloadSize + padSize;
2756
- if (buf.length < totalLen) return null;
2757
- const data = buf.subarray(headerLen, headerLen + payloadSize);
2758
- const media = {
2759
- type: "Aac",
2760
- data
2761
- };
2762
- return { media, consumed: totalLen };
2763
- }
2764
- function parseAdpcm(buf) {
2765
- if (buf.length < 12) return null;
2766
- const payloadSize = buf.readUInt16LE(4);
2767
- const payloadSizeB = buf.readUInt16LE(6);
2768
- if (payloadSize !== payloadSizeB) return null;
2769
- const magicData = buf.readUInt16LE(8);
2770
- if (magicData !== 256) return null;
2771
- const halfBlockSize = buf.readUInt16LE(10);
2772
- void halfBlockSize;
2773
- const subHeaderSize = 4;
2774
- if (payloadSize < subHeaderSize) return null;
2775
- const blockSize = payloadSize - subHeaderSize;
2776
- const headerLen = 12;
2777
- const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
2778
- const totalLen = headerLen + blockSize + padSize;
2779
- if (buf.length < totalLen) return null;
2780
- const data = buf.subarray(headerLen, headerLen + blockSize);
2781
- const media = {
2782
- type: "Adpcm",
2783
- data
2784
- };
2785
- return { media, consumed: totalLen };
2786
- }
2787
-
2788
- // src/baichuan/stream/BcMediaCodec.ts
2789
- var BcMediaCodec = class {
2790
- buffer = Buffer.alloc(0);
2791
- strict;
2792
- amountSkipped = 0;
2793
- logger;
2794
- constructor(strict = false, logger) {
2795
- this.strict = strict;
2796
- this.logger = logger;
2797
- }
2798
- /**
2799
- * Push data into the codec buffer and try to parse complete BcMedia packets.
2800
- * Returns an array of complete BcMedia packets found.
2801
- *
2802
- * @param chunk - New data chunk to add to buffer
2803
- * @returns Array of complete BcMedia packets (empty if none complete yet)
2804
- */
2805
- decode(chunk) {
2806
- this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
2807
- const results = [];
2808
- while (this.buffer.length >= 4) {
2809
- const result = parseBcMedia(this.buffer);
2810
- if (result) {
2811
- if (this.amountSkipped > 0) {
2812
- if (this.strict) {
2813
- this.logger?.warn(`[BcMediaCodec] Recovered stream after skipping ${this.amountSkipped} bytes`);
2814
- } else {
2815
- this.logger?.warn(`[BcMediaCodec] Recovered stream after skipping ${this.amountSkipped} bytes`);
2816
- }
2817
- this.amountSkipped = 0;
2818
- }
2819
- results.push(result.media);
2820
- this.buffer = this.buffer.subarray(result.consumed);
2821
- } else {
2822
- const isKnownMagic = (magic2) => {
2823
- const isInfoV1 = magic2 === 825241649;
2824
- const isInfoV2 = magic2 === 842018865;
2825
- const isIFrame = magic2 >= 1667510320 && magic2 <= 1667510329;
2826
- const isPFrame = magic2 >= 1667510576 && magic2 <= 1667510585;
2827
- const isAac = magic2 === 1651979568;
2828
- const isAdpcm = magic2 === 1651978544;
2829
- return isInfoV1 || isInfoV2 || isIFrame || isPFrame || isAac || isAdpcm;
2830
- };
2831
- const magic = this.buffer.readUInt32LE(0);
2832
- const startsWithKnownMagic = isKnownMagic(magic);
2833
- if (startsWithKnownMagic) {
2834
- break;
2835
- }
2836
- if (this.strict) {
2837
- throw new Error(`[BcMediaCodec] Invalid data in stream (no valid magic at buffer start, len=${this.buffer.length})`);
2838
- }
2839
- if (this.amountSkipped === 0) {
2840
- this.logger?.warn(`[BcMediaCodec] Error in stream, attempting to recover...`);
2841
- }
2842
- let next = -1;
2843
- for (const off of [528, 1056, 1584]) {
2844
- if (this.buffer.length >= off + 4 && isKnownMagic(this.buffer.readUInt32LE(off))) {
2845
- next = off;
2846
- break;
2847
- }
2848
- }
2849
- if (next < 0) {
2850
- for (let i = 1; i <= this.buffer.length - 4; i++) {
2851
- if (isKnownMagic(this.buffer.readUInt32LE(i))) {
2852
- next = i;
2853
- break;
2854
- }
2855
- }
2856
- }
2857
- if (next > 0) {
2858
- this.amountSkipped += next;
2859
- this.buffer = this.buffer.subarray(next);
2860
- continue;
2861
- }
2862
- if (this.buffer.length > 3) {
2863
- const keep = 3;
2864
- this.amountSkipped += this.buffer.length - keep;
2865
- this.buffer = this.buffer.subarray(this.buffer.length - keep);
2866
- }
2867
- break;
2868
- }
2869
- }
2870
- return results;
2871
- }
2872
- /**
2873
- * Get remaining buffer (for debugging)
2874
- */
2875
- getRemainingBuffer() {
2876
- return this.buffer;
2877
- }
2878
- /**
2879
- * Clear the buffer (useful for resetting the codec)
2880
- */
2881
- clear() {
2882
- this.buffer = Buffer.alloc(0);
2883
- this.amountSkipped = 0;
2884
- }
2885
- };
2886
-
2887
- // src/baichuan/stream/H264Converter.ts
2888
- var NAL_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
2889
- var NAL_START_CODE_3B = Buffer.from([0, 0, 1]);
2890
- function hasStartCodes(data) {
2891
- if (data.length < 4) return false;
2892
- if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;
2893
- if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;
2894
- return false;
2895
- }
2896
- function tryConvertWithLengthReader(data, readLen) {
2897
- const result = [];
2898
- let offset = 0;
2899
- let nalCount = 0;
2900
- while (offset < data.length) {
2901
- if (offset + 4 > data.length) return null;
2902
- const nalLength = readLen(data, offset);
2903
- offset += 4;
2904
- if (nalLength <= 0) return null;
2905
- if (nalLength > data.length - offset) return null;
2906
- result.push(NAL_START_CODE_4B);
2907
- result.push(data.subarray(offset, offset + nalLength));
2908
- offset += nalLength;
2909
- nalCount++;
2910
- }
2911
- if (nalCount === 0) return null;
2912
- return Buffer.concat(result);
2913
- }
2914
- function tryConvertWithLengthReader16(data, readLen) {
2915
- const result = [];
2916
- let offset = 0;
2917
- let nalCount = 0;
2918
- while (offset < data.length) {
2919
- if (offset + 2 > data.length) return null;
2920
- const nalLength = readLen(data, offset);
2921
- offset += 2;
2922
- if (nalLength <= 0) return null;
2923
- if (nalLength > data.length - offset) return null;
2924
- result.push(NAL_START_CODE_4B);
2925
- result.push(data.subarray(offset, offset + nalLength));
2926
- offset += nalLength;
2927
- nalCount++;
2928
- }
2929
- if (nalCount === 0) return null;
2930
- return Buffer.concat(result);
2931
- }
2932
- function tryConvertWithLengthReader24(data, endian) {
2933
- const result = [];
2934
- let offset = 0;
2935
- let nalCount = 0;
2936
- const readLen24 = (buf, at) => {
2937
- if (at + 3 > buf.length) return 0;
2938
- const b0 = buf[at];
2939
- const b1 = buf[at + 1];
2940
- const b2 = buf[at + 2];
2941
- return endian === "be" ? (b0 << 16 | b1 << 8 | b2) >>> 0 : (b2 << 16 | b1 << 8 | b0) >>> 0;
2942
- };
2943
- while (offset < data.length) {
2944
- if (offset + 3 > data.length) return null;
2945
- const nalLength = readLen24(data, offset);
2946
- offset += 3;
2947
- if (nalLength <= 0) return null;
2948
- if (nalLength > data.length - offset) return null;
2949
- result.push(NAL_START_CODE_4B);
2950
- result.push(data.subarray(offset, offset + nalLength));
2951
- offset += nalLength;
2952
- nalCount++;
2953
- }
2954
- if (nalCount === 0) return null;
2955
- return Buffer.concat(result);
2956
- }
2957
- function looksLikeSingleH264Nal(nalPayload) {
2958
- if (nalPayload.length < 1) return false;
2959
- const b0 = nalPayload[0];
2960
- if (b0 === void 0) return false;
2961
- if ((b0 & 128) !== 0) return false;
2962
- const nalType = b0 & 31;
2963
- return nalType >= 1 && nalType <= 23;
2964
- }
2965
- function depacketizeRtpAggregationToAnnexB(payload) {
2966
- if (payload.length < 1) return null;
2967
- const nalHeader = payload[0];
2968
- const nalType = nalHeader & 31;
2969
- const out = [];
2970
- const pushNal = (nal) => {
2971
- if (nal.length === 0) return;
2972
- out.push(NAL_START_CODE_4B, nal);
2973
- };
2974
- if (nalType === 24) {
2975
- let off = 1;
2976
- while (off + 2 <= payload.length) {
2977
- const size = payload.readUInt16BE(off);
2978
- off += 2;
2979
- if (size <= 0 || off + size > payload.length) return null;
2980
- pushNal(payload.subarray(off, off + size));
2981
- off += size;
2982
- }
2983
- return out.length ? Buffer.concat(out) : null;
2984
- }
2985
- if (nalType === 25) {
2986
- let off = 1 + 2;
2987
- if (off > payload.length) return null;
2988
- while (off + 2 <= payload.length) {
2989
- const size = payload.readUInt16BE(off);
2990
- off += 2;
2991
- if (size <= 0 || off + size > payload.length) return null;
2992
- pushNal(payload.subarray(off, off + size));
2993
- off += size;
2994
- }
2995
- return out.length ? Buffer.concat(out) : null;
2996
- }
2997
- if (nalType === 26) {
2998
- let off = 1 + 2;
2999
- if (off > payload.length) return null;
3000
- while (off + 2 <= payload.length) {
3001
- const size = payload.readUInt16BE(off);
3002
- off += 2;
3003
- if (off + 1 + 2 > payload.length) return null;
3004
- off += 1;
3005
- off += 2;
3006
- if (size <= 0 || off + size > payload.length) return null;
3007
- pushNal(payload.subarray(off, off + size));
3008
- off += size;
3009
- }
3010
- return out.length ? Buffer.concat(out) : null;
3011
- }
3012
- if (nalType === 27) {
3013
- let off = 1 + 2;
3014
- if (off > payload.length) return null;
3015
- while (off + 2 <= payload.length) {
3016
- const size = payload.readUInt16BE(off);
3017
- off += 2;
3018
- if (off + 1 + 3 > payload.length) return null;
3019
- off += 1;
3020
- off += 3;
3021
- if (size <= 0 || off + size > payload.length) return null;
3022
- pushNal(payload.subarray(off, off + size));
3023
- off += size;
3024
- }
3025
- return out.length ? Buffer.concat(out) : null;
3026
- }
3027
- return null;
3028
- }
3029
- function convertToAnnexB(data) {
3030
- if (hasStartCodes(data)) {
3031
- return data;
3032
- }
3033
- const sc4 = Buffer.from([0, 0, 0, 1]);
3034
- const sc3 = Buffer.from([0, 0, 1]);
3035
- const maxScan = Math.min(64, data.length);
3036
- const idx4 = data.subarray(0, maxScan).indexOf(sc4);
3037
- if (idx4 > 0) return data.subarray(idx4);
3038
- const idx3 = data.subarray(0, maxScan).indexOf(sc3);
3039
- if (idx3 > 0) return data.subarray(idx3);
3040
- const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));
3041
- if (be) return be;
3042
- const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));
3043
- if (le) return le;
3044
- const be24 = tryConvertWithLengthReader24(data, "be");
3045
- if (be24) return be24;
3046
- const le24 = tryConvertWithLengthReader24(data, "le");
3047
- if (le24) return le24;
3048
- const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));
3049
- if (be16) return be16;
3050
- const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));
3051
- if (le16) return le16;
3052
- const agg = depacketizeRtpAggregationToAnnexB(data);
3053
- if (agg) return agg;
3054
- if (looksLikeSingleH264Nal(data)) {
3055
- return Buffer.concat([NAL_START_CODE_4B, data]);
3056
- }
3057
- return data;
3058
- }
3059
- function splitAnnexBToNalPayloads(annexB) {
3060
- const starts = [];
3061
- for (let i = 0; i < annexB.length - 3; i++) {
3062
- if (annexB[i] === 0 && annexB[i + 1] === 0) {
3063
- if (annexB[i + 2] === 1) {
3064
- starts.push({ idx: i, len: 3 });
3065
- i += 2;
3066
- } else if (annexB[i + 2] === 0 && annexB[i + 3] === 1) {
3067
- starts.push({ idx: i, len: 4 });
3068
- i += 3;
3069
- }
3070
- }
3071
- }
3072
- if (starts.length === 0) return [];
3073
- const out = [];
3074
- for (let s = 0; s < starts.length; s++) {
3075
- const st = starts[s];
3076
- const start = st.idx + st.len;
3077
- const end = starts[s + 1] ? starts[s + 1].idx : annexB.length;
3078
- if (end > start) out.push(annexB.subarray(start, end));
3079
- }
3080
- return out;
3081
- }
3082
- function isValidH264AnnexBAccessUnit(annexB) {
3083
- if (!hasStartCodes(annexB)) return false;
3084
- const nals = splitAnnexBToNalPayloads(annexB);
3085
- if (nals.length === 0) return false;
3086
- for (const nal of nals) {
3087
- if (nal.length < 1) return false;
3088
- const b0 = nal[0];
3089
- if (b0 === void 0) return false;
3090
- if ((b0 & 128) !== 0) return false;
3091
- const nalType = b0 & 31;
3092
- if (nalType === 0 || nalType >= 24) return false;
3093
- }
3094
- return true;
3095
- }
3096
- function isH264KeyframeAnnexB(annexB) {
3097
- const nals = splitAnnexBToNalPayloads(annexB);
3098
- let hasSps = false;
3099
- let hasPps = false;
3100
- let hasIdr = false;
3101
- for (const nal of nals) {
3102
- const t = (nal[0] ?? 0) & 31;
3103
- if (t === 7) hasSps = true;
3104
- if (t === 8) hasPps = true;
3105
- if (t === 5) hasIdr = true;
3106
- }
3107
- return hasIdr && hasSps && hasPps;
3108
- }
3109
- var H264RtpDepacketizer = class _H264RtpDepacketizer {
3110
- fuNalHeader = null;
3111
- fuParts = [];
3112
- static parseRtpPayload(packet) {
3113
- if (!packet || packet.length < 12) return null;
3114
- const version = packet[0] >> 6 & 3;
3115
- if (version !== 2) return null;
3116
- const padding = (packet[0] & 32) !== 0;
3117
- const extension = (packet[0] & 16) !== 0;
3118
- const csrcCount = packet[0] & 15;
3119
- let offset = 12 + csrcCount * 4;
3120
- if (offset > packet.length) return null;
3121
- if (extension) {
3122
- if (offset + 4 > packet.length) return null;
3123
- const extLenWords = packet.readUInt16BE(offset + 2);
3124
- offset += 4 + extLenWords * 4;
3125
- if (offset > packet.length) return null;
3126
- }
3127
- let end = packet.length;
3128
- if (padding) {
3129
- const padLen = packet[packet.length - 1];
3130
- if (padLen <= 0 || padLen > packet.length) return null;
3131
- end = packet.length - padLen;
3132
- if (end < offset) return null;
3133
- }
3134
- if (end <= offset) return null;
3135
- return packet.subarray(offset, end);
3136
- }
3137
- reset() {
3138
- this.fuNalHeader = null;
3139
- this.fuParts = [];
3140
- }
3141
- push(payload) {
3142
- if (payload.length === 0) return [];
3143
- const rtpPayload = _H264RtpDepacketizer.parseRtpPayload(payload);
3144
- if (rtpPayload) payload = rtpPayload;
3145
- if (hasStartCodes(payload)) return [payload];
3146
- const b0 = payload[0];
3147
- if ((b0 & 128) !== 0) return [];
3148
- const nalType = b0 & 31;
3149
- if (nalType >= 1 && nalType <= 23) {
3150
- return [Buffer.concat([NAL_START_CODE_4B, payload])];
3151
- }
3152
- if (nalType === 24) {
3153
- if (payload.length < 1 + 2) return [];
3154
- let off = 1;
3155
- const out = [];
3156
- while (off + 2 <= payload.length) {
3157
- const size = payload.readUInt16BE(off);
3158
- off += 2;
3159
- if (size <= 0 || off + size > payload.length) return [];
3160
- const nal = payload.subarray(off, off + size);
3161
- off += size;
3162
- if (nal.length < 1) return [];
3163
- if ((nal[0] & 128) !== 0) return [];
3164
- const t = nal[0] & 31;
3165
- if (t === 0 || t >= 24) return [];
3166
- out.push(Buffer.concat([NAL_START_CODE_4B, nal]));
3167
- }
3168
- return out;
3169
- }
3170
- if (nalType === 28 || nalType === 29) {
3171
- if (payload.length < 2) return [];
3172
- const fuIndicator = payload[0];
3173
- const fuHeader = payload[1];
3174
- const start = (fuHeader & 128) !== 0;
3175
- const end = (fuHeader & 64) !== 0;
3176
- const origType = fuHeader & 31;
3177
- const reconstructedHeader = fuIndicator & 224 | origType;
3178
- let off = 2;
3179
- if (nalType === 29) {
3180
- if (payload.length < off + 2) return [];
3181
- off += 2;
3182
- }
3183
- const frag = payload.subarray(off);
3184
- if (start) {
3185
- this.fuNalHeader = reconstructedHeader;
3186
- this.fuParts = [frag];
3187
- } else if (this.fuNalHeader != null) {
3188
- this.fuParts.push(frag);
3189
- } else {
3190
- return [];
3191
- }
3192
- if (end && this.fuNalHeader != null) {
3193
- const nal = Buffer.concat([Buffer.from([this.fuNalHeader]), ...this.fuParts]);
3194
- this.reset();
3195
- return [Buffer.concat([NAL_START_CODE_4B, nal])];
3196
- }
3197
- return [];
3198
- }
3199
- return [];
3200
- }
3201
- };
3202
- function convertToLengthPrefixed(data) {
3203
- const result = [];
3204
- let offset = 0;
3205
- while (offset < data.length) {
3206
- let startCodeOffset = -1;
3207
- let startCodeLength = 0;
3208
- if (offset + 4 <= data.length && data.subarray(offset, offset + 4).equals(NAL_START_CODE_4B)) {
3209
- startCodeOffset = offset;
3210
- startCodeLength = 4;
3211
- } else if (offset + 3 <= data.length && data.subarray(offset, offset + 3).equals(NAL_START_CODE_3B)) {
3212
- startCodeOffset = offset;
3213
- startCodeLength = 3;
3214
- }
3215
- if (startCodeOffset === -1) {
3216
- result.push(data.subarray(offset));
3217
- break;
3218
- }
3219
- offset = startCodeOffset + startCodeLength;
3220
- let nextStartCode = -1;
3221
- let nextStartCodeLength = 0;
3222
- for (let i = offset; i < data.length - 3; i++) {
3223
- if (i + 4 <= data.length && data.subarray(i, i + 4).equals(NAL_START_CODE_4B)) {
3224
- nextStartCode = i;
3225
- nextStartCodeLength = 4;
3226
- break;
3227
- }
3228
- if (i + 3 <= data.length && data.subarray(i, i + 3).equals(NAL_START_CODE_3B)) {
3229
- nextStartCode = i;
3230
- nextStartCodeLength = 3;
3231
- break;
3232
- }
3233
- }
3234
- const nalEnd = nextStartCode !== -1 ? nextStartCode : data.length;
3235
- const nalData = data.subarray(offset, nalEnd);
3236
- const lengthBuf = Buffer.alloc(4);
3237
- lengthBuf.writeUInt32BE(nalData.length, 0);
3238
- result.push(lengthBuf);
3239
- result.push(nalData);
3240
- offset = nalEnd;
3241
- }
3242
- return Buffer.concat(result);
3243
- }
3244
-
3245
- // src/baichuan/stream/H265Converter.ts
3246
- var NAL_START_CODE_4B2 = Buffer.from([0, 0, 0, 1]);
3247
- var NAL_START_CODE_3B2 = Buffer.from([0, 0, 1]);
3248
- function hasStartCodes2(data) {
3249
- if (data.length < 4) return false;
3250
- if (data.subarray(0, 4).equals(NAL_START_CODE_4B2)) return true;
3251
- if (data.subarray(0, 3).equals(NAL_START_CODE_3B2)) return true;
3252
- return false;
3253
- }
3254
- function tryConvertWithLengthReader2(data, readLen) {
3255
- const result = [];
3256
- let offset = 0;
3257
- let nalCount = 0;
3258
- while (offset < data.length) {
3259
- if (offset + 4 > data.length) return null;
3260
- const nalLength = readLen(data, offset);
3261
- offset += 4;
3262
- if (nalLength <= 0) return null;
3263
- if (nalLength > data.length - offset) return null;
3264
- result.push(NAL_START_CODE_4B2);
3265
- result.push(data.subarray(offset, offset + nalLength));
3266
- offset += nalLength;
3267
- nalCount++;
3268
- }
3269
- if (nalCount === 0) return null;
3270
- return Buffer.concat(result);
3271
- }
3272
- function tryConvertWithLengthReader162(data, readLen) {
3273
- const result = [];
3274
- let offset = 0;
3275
- let nalCount = 0;
3276
- while (offset < data.length) {
3277
- if (offset + 2 > data.length) return null;
3278
- const nalLength = readLen(data, offset);
3279
- offset += 2;
3280
- if (nalLength <= 0) return null;
3281
- if (nalLength > data.length - offset) return null;
3282
- result.push(NAL_START_CODE_4B2);
3283
- result.push(data.subarray(offset, offset + nalLength));
3284
- offset += nalLength;
3285
- nalCount++;
3286
- }
3287
- if (nalCount === 0) return null;
3288
- return Buffer.concat(result);
3289
- }
3290
- function tryConvertWithLengthReader242(data, endian) {
3291
- const result = [];
3292
- let offset = 0;
3293
- let nalCount = 0;
3294
- const readLen24 = (buf, at) => {
3295
- if (at + 3 > buf.length) return 0;
3296
- const b0 = buf[at];
3297
- const b1 = buf[at + 1];
3298
- const b2 = buf[at + 2];
3299
- return endian === "be" ? (b0 << 16 | b1 << 8 | b2) >>> 0 : (b2 << 16 | b1 << 8 | b0) >>> 0;
3300
- };
3301
- while (offset < data.length) {
3302
- if (offset + 3 > data.length) return null;
3303
- const nalLength = readLen24(data, offset);
3304
- offset += 3;
3305
- if (nalLength <= 0) return null;
3306
- if (nalLength > data.length - offset) return null;
3307
- result.push(NAL_START_CODE_4B2);
3308
- result.push(data.subarray(offset, offset + nalLength));
3309
- offset += nalLength;
3310
- nalCount++;
3311
- }
3312
- if (nalCount === 0) return null;
3313
- return Buffer.concat(result);
3314
- }
3315
- function looksLikeSingleH265Nal(nalPayload) {
3316
- if (nalPayload.length < 2) return false;
3317
- const b0 = nalPayload[0];
3318
- const b1 = nalPayload[1];
3319
- if (b0 === void 0 || b1 === void 0) return false;
3320
- if ((b0 & 128) !== 0) return false;
3321
- const nalType = b0 >> 1 & 63;
3322
- return nalType <= 40;
3323
- }
3324
- function convertToAnnexB2(data) {
3325
- if (hasStartCodes2(data)) {
3326
- return data;
3327
- }
3328
- const sc4 = Buffer.from([0, 0, 0, 1]);
3329
- const sc3 = Buffer.from([0, 0, 1]);
3330
- const maxScan = Math.min(64, data.length);
3331
- const idx4 = data.subarray(0, maxScan).indexOf(sc4);
3332
- if (idx4 > 0) return data.subarray(idx4);
3333
- const idx3 = data.subarray(0, maxScan).indexOf(sc3);
3334
- if (idx3 > 0) return data.subarray(idx3);
3335
- const be = tryConvertWithLengthReader2(data, (b, o) => b.readUInt32BE(o));
3336
- if (be) return be;
3337
- const le = tryConvertWithLengthReader2(data, (b, o) => b.readUInt32LE(o));
3338
- if (le) return le;
3339
- const be24 = tryConvertWithLengthReader242(data, "be");
3340
- if (be24) return be24;
3341
- const le24 = tryConvertWithLengthReader242(data, "le");
3342
- if (le24) return le24;
3343
- const be16 = tryConvertWithLengthReader162(data, (b, o) => b.readUInt16BE(o));
3344
- if (be16) return be16;
3345
- const le16 = tryConvertWithLengthReader162(data, (b, o) => b.readUInt16LE(o));
3346
- if (le16) return le16;
3347
- if (looksLikeSingleH265Nal(data)) {
3348
- return Buffer.concat([NAL_START_CODE_4B2, data]);
3349
- }
3350
- return data;
3351
- }
3352
- var H265RtpDepacketizer = class _H265RtpDepacketizer {
3353
- fuParts = null;
3354
- reset() {
3355
- this.fuParts = null;
3356
- }
3357
- static parseRtpPayload(packet) {
3358
- if (!packet || packet.length < 12) return null;
3359
- const version = packet[0] >> 6 & 3;
3360
- if (version !== 2) return null;
3361
- const padding = (packet[0] & 32) !== 0;
3362
- const extension = (packet[0] & 16) !== 0;
3363
- const csrcCount = packet[0] & 15;
3364
- let offset = 12 + csrcCount * 4;
3365
- if (offset > packet.length) return null;
3366
- if (extension) {
3367
- if (offset + 4 > packet.length) return null;
3368
- const extLenWords = packet.readUInt16BE(offset + 2);
3369
- offset += 4 + extLenWords * 4;
3370
- if (offset > packet.length) return null;
3371
- }
3372
- let end = packet.length;
3373
- if (padding) {
3374
- const padLen = packet[packet.length - 1];
3375
- if (padLen <= 0 || padLen > packet.length) return null;
3376
- end = packet.length - padLen;
3377
- if (end < offset) return null;
3378
- }
3379
- if (end <= offset) return null;
3380
- return packet.subarray(offset, end);
3381
- }
3382
- push(payload) {
3383
- if (!payload || payload.length < 2) return [];
3384
- const rtpPayload = _H265RtpDepacketizer.parseRtpPayload(payload);
3385
- if (rtpPayload) payload = rtpPayload;
3386
- const h0 = payload[0];
3387
- const h1 = payload[1];
3388
- if ((h0 & 128) !== 0) return [];
3389
- const nalType = h0 >> 1 & 63;
3390
- if (nalType === 48) {
3391
- let off = 2;
3392
- const out = [];
3393
- while (off + 2 <= payload.length) {
3394
- const size = payload.readUInt16BE(off);
3395
- off += 2;
3396
- if (size <= 0 || off + size > payload.length) return [];
3397
- const nal = payload.subarray(off, off + size);
3398
- off += size;
3399
- if (nal.length) out.push(NAL_START_CODE_4B2, nal);
3400
- }
3401
- return out.length ? [Buffer.concat(out)] : [];
3402
- }
3403
- if (nalType === 49) {
3404
- if (payload.length < 3) return [];
3405
- const fuHeader = payload[2];
3406
- const start = (fuHeader & 128) !== 0;
3407
- const end = (fuHeader & 64) !== 0;
3408
- const origType = fuHeader & 63;
3409
- const orig0 = h0 & 129 | (origType & 63) << 1;
3410
- const orig1 = h1;
3411
- const frag = payload.subarray(3);
3412
- if (start) {
3413
- this.fuParts = [NAL_START_CODE_4B2, Buffer.from([orig0, orig1]), frag];
3414
- } else {
3415
- if (!this.fuParts) return [];
3416
- this.fuParts.push(frag);
3417
- }
3418
- if (end) {
3419
- if (!this.fuParts) return [];
3420
- const out = Buffer.concat(this.fuParts);
3421
- this.fuParts = null;
3422
- return [out];
3423
- }
3424
- return [];
3425
- }
3426
- return [Buffer.concat([NAL_START_CODE_4B2, payload])];
3427
- }
3428
- };
3429
- function splitAnnexBToNalPayloads2(annexB) {
3430
- const starts = [];
3431
- for (let i = 0; i < annexB.length - 3; i++) {
3432
- if (annexB[i] === 0 && annexB[i + 1] === 0) {
3433
- if (annexB[i + 2] === 1) {
3434
- starts.push({ idx: i, len: 3 });
3435
- i += 2;
3436
- } else if (annexB[i + 2] === 0 && annexB[i + 3] === 1) {
3437
- starts.push({ idx: i, len: 4 });
3438
- i += 3;
3439
- }
3440
- }
3441
- }
3442
- if (starts.length === 0) return [];
3443
- const out = [];
3444
- for (let s = 0; s < starts.length; s++) {
3445
- const st = starts[s];
3446
- const start = st.idx + st.len;
3447
- const end = starts[s + 1] ? starts[s + 1].idx : annexB.length;
3448
- if (end > start) out.push(annexB.subarray(start, end));
3449
- }
3450
- return out;
3451
- }
3452
- function getH265NalType(nalPayload) {
3453
- if (nalPayload.length < 1) return null;
3454
- const b0 = nalPayload[0];
3455
- if (b0 === void 0) return null;
3456
- if ((b0 & 128) !== 0) return null;
3457
- return b0 >> 1 & 63;
3458
- }
3459
- function isH265Irap(nalType) {
3460
- return nalType >= 16 && nalType <= 23;
3461
- }
3462
- function isValidH265AnnexBAccessUnit(annexB) {
3463
- if (!hasStartCodes2(annexB)) return false;
3464
- const nals = splitAnnexBToNalPayloads2(annexB);
3465
- if (nals.length === 0) return false;
3466
- for (const nal of nals) {
3467
- if (nal.length < 2) return false;
3468
- const b0 = nal[0];
3469
- if (b0 === void 0) return false;
3470
- if ((b0 & 128) !== 0) return false;
3471
- const nalType = getH265NalType(nal);
3472
- if (nalType === null) return false;
3473
- if (nalType > 40) return false;
3474
- }
3475
- return true;
3476
- }
3477
- function isH265KeyframeAnnexB(annexB) {
3478
- const nals = splitAnnexBToNalPayloads2(annexB);
3479
- let hasVps = false;
3480
- let hasSps = false;
3481
- let hasPps = false;
3482
- let hasIrap = false;
3483
- for (const nal of nals) {
3484
- const nalType = getH265NalType(nal);
3485
- if (nalType === null) continue;
3486
- if (nalType === 32) hasVps = true;
3487
- if (nalType === 33) hasSps = true;
3488
- if (nalType === 34) hasPps = true;
3489
- if (isH265Irap(nalType)) hasIrap = true;
3490
- }
3491
- return hasIrap && hasVps && hasSps && hasPps;
3492
- }
3493
- function extractVpsFromAnnexB(annexB) {
3494
- const nals = splitAnnexBToNalPayloads2(annexB);
3495
- for (const nal of nals) {
3496
- const nalType = getH265NalType(nal);
3497
- if (nalType === 32) {
3498
- return nal;
3499
- }
3500
- }
3501
- return null;
3502
- }
3503
- function extractSpsFromAnnexB(annexB) {
3504
- const nals = splitAnnexBToNalPayloads2(annexB);
3505
- for (const nal of nals) {
3506
- const nalType = getH265NalType(nal);
3507
- if (nalType === 33) {
3508
- return nal;
3509
- }
3510
- }
3511
- return null;
3512
- }
3513
- function extractPpsFromAnnexB(annexB) {
3514
- const nals = splitAnnexBToNalPayloads2(annexB);
3515
- for (const nal of nals) {
3516
- const nalType = getH265NalType(nal);
3517
- if (nalType === 34) {
3518
- return nal;
3519
- }
3520
- }
3521
- return null;
3522
- }
3523
-
3524
- // src/baichuan/stream/BcMediaAnnexBDecoder.ts
3525
- var ANNEXB_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
3526
- function detectVideoCodecFromNal(data) {
3527
- if (!data || data.length < 5) return null;
3528
- let nalStart = -1;
3529
- for (let i = 0; i < Math.min(data.length - 4, 100); i++) {
3530
- if (data[i] === 0 && data[i + 1] === 0) {
3531
- if (data[i + 2] === 0 && data[i + 3] === 1) {
3532
- nalStart = i + 4;
3533
- break;
3534
- }
3535
- if (data[i + 2] === 1) {
3536
- nalStart = i + 3;
3537
- break;
3538
- }
3539
- }
3540
- }
3541
- if (nalStart < 0 && data.length >= 5) {
3542
- const len = data.readUInt32BE(0);
3543
- if (len > 0 && len < data.length - 4) {
3544
- nalStart = 4;
3545
- }
3546
- }
3547
- if (nalStart < 0 || nalStart >= data.length) return null;
3548
- const nalByte = data[nalStart];
3549
- if (nalByte === void 0) return null;
3550
- const forbiddenBit264 = nalByte >> 7 & 1;
3551
- const h264Type = nalByte & 31;
3552
- if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {
3553
- if (h264Type === 7 || h264Type === 8) {
3554
- return "H264";
3555
- }
3556
- if (h264Type === 5) {
3557
- return "H264";
3558
- }
3559
- if (h264Type === 1) {
3560
- const nalRefIdc = nalByte >> 5 & 3;
3561
- if (nalRefIdc >= 1) {
3562
- return "H264";
3563
- }
3564
- }
3565
- }
3566
- if (nalStart + 1 < data.length) {
3567
- const nalByte2 = data[nalStart + 1];
3568
- if (nalByte2 !== void 0) {
3569
- const forbiddenBit = nalByte >> 7 & 1;
3570
- const hevcType = nalByte >> 1 & 63;
3571
- const temporalId = nalByte2 & 7;
3572
- if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {
3573
- if (hevcType === 32 || hevcType === 33 || hevcType === 34) {
3574
- return "H265";
3575
- }
3576
- if (hevcType === 19 || hevcType === 20 || hevcType === 21) {
3577
- return "H265";
3578
- }
3579
- if (hevcType <= 1 && nalByte <= 3) {
3580
- return "H265";
3581
- }
3582
- }
3583
- }
3584
- }
3585
- return null;
3586
- }
3587
- var BcMediaAnnexBDecoder = class {
3588
- codec;
3589
- logger;
3590
- onVideoAccessUnit;
3591
- onAudioFrame;
3592
- stats = {
3593
- bytesIn: 0,
3594
- bytesOut: 0,
3595
- audioBytesOut: 0,
3596
- packets: 0,
3597
- videoPackets: 0,
3598
- audioPackets: 0,
3599
- aacPackets: 0,
3600
- adpcmPackets: 0,
3601
- keyframes: 0,
3602
- videoType: null,
3603
- audioType: null,
3604
- infos: [],
3605
- recoveredSkips: 0
3606
- };
3607
- lastH264Sps = null;
3608
- lastH264Pps = null;
3609
- lastH265Vps = null;
3610
- lastH265Sps = null;
3611
- lastH265Pps = null;
3612
- constructor(params) {
3613
- this.logger = params?.logger;
3614
- this.onVideoAccessUnit = params?.onVideoAccessUnit;
3615
- this.onAudioFrame = params?.onAudioFrame;
3616
- this.codec = new BcMediaCodec(params?.strict ?? false, this.logger);
3617
- }
3618
- getStats() {
3619
- return { ...this.stats, infos: [...this.stats.infos] };
3620
- }
3621
- /**
3622
- * Push arbitrary bytes from a Baichuan/BcMedia transport into the decoder.
3623
- * Emits complete Annex-B access units via callback.
3624
- */
3625
- push(chunk) {
3626
- if (!chunk || chunk.length === 0) return;
3627
- this.stats.bytesIn += chunk.length;
3628
- const packets = this.codec.decode(chunk);
3629
- this.stats.packets += packets.length;
3630
- for (const media of packets) {
3631
- this.handleMedia(media);
3632
- }
3633
- }
3634
- handleMedia(media) {
3635
- if (media.type === "InfoV1" || media.type === "InfoV2") {
3636
- const info = media;
3637
- this.stats.infos.push({
3638
- type: media.type,
3639
- videoWidth: info.videoWidth,
3640
- videoHeight: info.videoHeight,
3641
- fps: info.fps,
3642
- startYear: info.startYear,
3643
- startMonth: info.startMonth,
3644
- startDay: info.startDay,
3645
- startHour: info.startHour,
3646
- startMin: info.startMin,
3647
- startSeconds: info.startSeconds,
3648
- endYear: info.endYear,
3649
- endMonth: info.endMonth,
3650
- endDay: info.endDay,
3651
- endHour: info.endHour,
3652
- endMin: info.endMin,
3653
- endSeconds: info.endSeconds
3654
- });
3655
- return;
3656
- }
3657
- if (media.type === "Aac" || media.type === "Adpcm") {
3658
- const audioMedia = media;
3659
- this.stats.audioPackets++;
3660
- if (media.type === "Aac") {
3661
- this.stats.aacPackets++;
3662
- } else {
3663
- this.stats.adpcmPackets++;
3664
- }
3665
- if (this.stats.audioType == null) {
3666
- this.stats.audioType = media.type;
3667
- }
3668
- if (this.onAudioFrame) {
3669
- this.stats.audioBytesOut += audioMedia.data.length;
3670
- this.onAudioFrame({
3671
- audioType: media.type,
3672
- data: audioMedia.data
3673
- });
3674
- }
3675
- return;
3676
- }
3677
- if (media.type !== "Iframe" && media.type !== "Pframe") return;
3678
- this.stats.videoPackets++;
3679
- const microseconds = media.microseconds;
3680
- const raw = media.data;
3681
- let videoType = media.videoType;
3682
- const detectedType = detectVideoCodecFromNal(raw);
3683
- if (detectedType != null && detectedType !== videoType) {
3684
- this.logger?.debug?.(
3685
- `[BcMediaAnnexBDecoder] Codec mismatch: reported ${videoType}, detected ${detectedType}`
3686
- );
3687
- videoType = detectedType;
3688
- }
3689
- if (this.stats.videoType == null) this.stats.videoType = videoType;
3690
- let annexB = videoType === "H265" ? convertToAnnexB2(raw) : convertToAnnexB(raw);
3691
- const isKeyframe = media.type === "Iframe" || (videoType === "H265" ? isH265KeyframeAnnexB(annexB) : isH264KeyframeAnnexB(annexB));
3692
- if (videoType === "H264") {
3693
- const nals = splitAnnexBToNalPayloads(annexB);
3694
- for (const nal of nals) {
3695
- const t = (nal[0] ?? 0) & 31;
3696
- if (t === 7) this.lastH264Sps = nal;
3697
- if (t === 8) this.lastH264Pps = nal;
3698
- }
3699
- if (isKeyframe) {
3700
- const hasSps = nals.some((nal) => ((nal[0] ?? 0) & 31) === 7);
3701
- const hasPps = nals.some((nal) => ((nal[0] ?? 0) & 31) === 8);
3702
- const toPrepend = [];
3703
- if (!hasSps && this.lastH264Sps)
3704
- toPrepend.push(ANNEXB_START_CODE_4B, this.lastH264Sps);
3705
- if (!hasPps && this.lastH264Pps)
3706
- toPrepend.push(ANNEXB_START_CODE_4B, this.lastH264Pps);
3707
- if (toPrepend.length > 0)
3708
- annexB = Buffer.concat([...toPrepend, annexB]);
3709
- }
3710
- } else {
3711
- const vps = extractVpsFromAnnexB(annexB);
3712
- const sps = extractSpsFromAnnexB(annexB);
3713
- const pps = extractPpsFromAnnexB(annexB);
3714
- if (vps) this.lastH265Vps = vps;
3715
- if (sps) this.lastH265Sps = sps;
3716
- if (pps) this.lastH265Pps = pps;
3717
- if (isKeyframe) {
3718
- const hasVps = vps != null;
3719
- const hasSps = sps != null;
3720
- const hasPps = pps != null;
3721
- const toPrepend = [];
3722
- if (!hasVps && this.lastH265Vps)
3723
- toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Vps);
3724
- if (!hasSps && this.lastH265Sps)
3725
- toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Sps);
3726
- if (!hasPps && this.lastH265Pps)
3727
- toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Pps);
3728
- if (toPrepend.length > 0)
3729
- annexB = Buffer.concat([...toPrepend, annexB]);
3730
- }
3731
- }
3732
- if (isKeyframe) this.stats.keyframes++;
3733
- this.stats.bytesOut += annexB.length;
3734
- this.onVideoAccessUnit?.({ videoType, annexB, microseconds, isKeyframe });
3735
- }
3736
- };
3737
-
3738
- // src/baichuan/stream/BaichuanVideoStream.ts
3739
- var NAL_START_CODE_4B3 = Buffer.from([0, 0, 0, 1]);
3740
- var WATCHDOG_TICK_MS = 1e3;
3741
- var WATCHDOG_MAX_RESTARTS_PER_MINUTE = 3;
3742
- var AsyncFsQueue = class {
3743
- running = false;
3744
- queue = [];
3745
- maxQueueSize;
3746
- constructor(maxQueueSize) {
3747
- this.maxQueueSize = maxQueueSize;
3748
- }
3749
- enqueue(task) {
3750
- if (this.queue.length >= this.maxQueueSize) return;
3751
- this.queue.push(task);
3752
- this.pump();
3753
- }
3754
- pump() {
3755
- if (this.running) return;
3756
- this.running = true;
3757
- void this.run();
3758
- }
3759
- async run() {
3760
- try {
3761
- while (this.queue.length > 0) {
3762
- const task = this.queue.shift();
3763
- if (!task) continue;
3764
- try {
3765
- await task();
3766
- } catch {
3767
- }
3768
- }
3769
- } finally {
3770
- this.running = false;
3771
- }
3772
- }
3773
- };
3774
- function removeEmulationPreventionBytes(rbsp) {
3775
- const out = [];
3776
- for (let i = 0; i < rbsp.length; i++) {
3777
- if (i >= 2 && rbsp[i] === 3 && rbsp[i - 1] === 0 && rbsp[i - 2] === 0) {
3778
- continue;
3779
- }
3780
- out.push(rbsp[i]);
3781
- }
3782
- return Buffer.from(out);
3783
- }
3784
- var BitReader = class {
3785
- b;
3786
- bitPos = 0;
3787
- constructor(buf) {
3788
- this.b = buf;
3789
- }
3790
- readBits(n) {
3791
- if (n <= 0) return 0;
3792
- let v = 0;
3793
- for (let i = 0; i < n; i++) {
3794
- const bytePos = this.bitPos >> 3;
3795
- if (bytePos >= this.b.length) return null;
3796
- const bitInByte = 7 - (this.bitPos & 7);
3797
- const bit = this.b[bytePos] >> bitInByte & 1;
3798
- v = v << 1 | bit;
3799
- this.bitPos++;
3800
- }
3801
- return v >>> 0;
3802
- }
3803
- readUE() {
3804
- let zeros = 0;
3805
- while (true) {
3806
- const bit = this.readBits(1);
3807
- if (bit == null) return null;
3808
- if (bit === 0) zeros++;
3809
- else break;
3810
- if (zeros > 31) return null;
3811
- }
3812
- const rest = zeros ? this.readBits(zeros) : 0;
3813
- if (rest == null) return null;
3814
- return (1 << zeros) - 1 + rest >>> 0;
3815
- }
3816
- };
3817
- function parseSpsIdFromNal(nalPayload) {
3818
- if (((nalPayload[0] ?? 0) & 31) !== 7) return null;
3819
- const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
3820
- const r = new BitReader(rbsp);
3821
- if (r.readBits(8) == null) return null;
3822
- if (r.readBits(8) == null) return null;
3823
- if (r.readBits(8) == null) return null;
3824
- return r.readUE();
3825
- }
3826
- function parsePpsIdsFromNal(nalPayload) {
3827
- if (((nalPayload[0] ?? 0) & 31) !== 8) return null;
3828
- const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
3829
- const r = new BitReader(rbsp);
3830
- const ppsId = r.readUE();
3831
- const spsId = r.readUE();
3832
- if (ppsId == null || spsId == null) return null;
3833
- return { ppsId, spsId };
3834
- }
3835
- function parseSlicePpsIdFromNal(nalPayload) {
3836
- const t = (nalPayload[0] ?? 0) & 31;
3837
- if (t !== 1 && t !== 5) return null;
3838
- const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
3839
- const r = new BitReader(rbsp);
3840
- if (r.readUE() == null) return null;
3841
- if (r.readUE() == null) return null;
3842
- return r.readUE();
3843
- }
3844
- var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
3845
- client;
3846
- api;
3847
- channel;
3848
- profile;
3849
- variant;
3850
- logger;
3851
- active = false;
3852
- videoFrameHandler;
3853
- expectedStreamTypes;
3854
- activeMsgNum;
3855
- cmdId;
3856
- acceptAnyStreamType;
3857
- lockedChannelId;
3858
- bcMediaCodec;
3859
- debugH264LogsLeft;
3860
- debugSavedSamples;
3861
- warnedNonAnnexBOnce = false;
3862
- // "RTP-like" depacketizer (some models encapsulate NAL units in FU-A/STAP)
3863
- depacketizer = new H264RtpDepacketizer();
3864
- depacketizerH265 = new H265RtpDepacketizer();
3865
- dumpChunkIdx = 0;
3866
- dumpNalLines = 0;
3867
- dumpIo = new AsyncFsQueue(200);
3868
- spsById = /* @__PURE__ */ new Map();
3869
- // NAL payload (without start code) - H.264
3870
- ppsById = /* @__PURE__ */ new Map();
3871
- // NAL payload + mapping - H.264
3872
- lastSps = null;
3873
- // H.264
3874
- lastPps = null;
3875
- // H.264
3876
- lastPrependedPpsId = null;
3877
- // H.264
3878
- // H.265 parameter sets
3879
- lastVps = null;
3880
- // H.265 VPS
3881
- lastSpsH265 = null;
3882
- // H.265 SPS
3883
- lastPpsH265 = null;
3884
- // H.265 PPS
3885
- lastPrependedParamSetsH265 = false;
3886
- // Track if we've prepended H.265 param sets
3887
- // Stateful AES decryptor for fragmented BcMedia packets (full_aes mode)
3888
- // In CFB mode, continuation frames must use the cipher state from previous frames.
3889
- aesStreamDecryptor = null;
3890
- /**
3891
- * Pending startup error stashed when emitSafeError is called before any
3892
- * "error" listener is registered (e.g. camera returns 400 during start()).
3893
- * The rfc4571-server's waitForKeyframe can consume this immediately instead
3894
- * of waiting for the full keyframe timeout.
3895
- */
3896
- _pendingStartupError;
3897
- /** Consume and clear any pending startup error. */
3898
- consumePendingStartupError() {
3899
- const err = this._pendingStartupError;
3900
- this._pendingStartupError = void 0;
3901
- return err;
3902
- }
3903
- emitSafeError(err) {
3904
- if (!this.active) {
3905
- this.logger?.warn?.(
3906
- `[BaichuanVideoStream] Suppressed error after stop: ${err.message}`
3907
- );
3908
- return;
3909
- }
3910
- if (this.listenerCount("error") === 0) {
3911
- this.logger?.warn?.(
3912
- `[BaichuanVideoStream] Unhandled stream error: ${err.message}`
3913
- );
3914
- this._pendingStartupError = err;
3915
- return;
3916
- }
3917
- this.emit("error", err);
3918
- }
3919
- lastMediaAtMs = 0;
3920
- watchdogTimer;
3921
- restarting = false;
3922
- restartWindowStartMs = 0;
3923
- restartCountInWindow = 0;
3924
- idleRestartMs;
3925
- // Note: reassembly happens at the BcMediaCodec transport level, so we do not
3926
- // accumulate frames here.
3927
- static scoreBcMediaLike(b) {
3928
- if (b.length < 4) return { score: -1, first: -1 };
3929
- const maxScan = Math.min(64 * 1024, b.length - 4);
3930
- let count = 0;
3931
- let first = -1;
3932
- for (let i = 0; i <= maxScan; i++) {
3933
- const magic = b.readUInt32LE(i);
3934
- const isInfoV1 = magic === 825241649;
3935
- const isInfoV2 = magic === 842018865;
3936
- const isIFrame = magic >= 1667510320 && magic <= 1667510329;
3937
- const isPFrame = magic >= 1667510576 && magic <= 1667510585;
3938
- const isAac = magic === 1651979568;
3939
- const isAdpcm = magic === 1651978544;
3940
- if (isInfoV1 || isInfoV2 || isIFrame || isPFrame || isAac || isAdpcm) {
3941
- count++;
3942
- if (first < 0) first = i;
3943
- if (count > 32 && first === 0) break;
3944
- }
3945
- }
3946
- return { score: count * 1e3 - (first < 0 ? 5e4 : first), first };
3947
- }
3948
- chooseDecryptedOrRawCandidate(params) {
3949
- const { raw, enc, channelId, allowResync, encryptLen } = params;
3950
- if (encryptLen !== void 0 && encryptLen > 0 && encryptLen < raw.length) {
3951
- const encryptedPart = raw.subarray(0, encryptLen);
3952
- const clearPart = raw.subarray(encryptLen);
3953
- const decryptedPart = this.client.tryDecryptBinary(
3954
- encryptedPart,
3955
- channelId,
3956
- enc
3957
- );
3958
- const chosen2 = Buffer.concat([decryptedPart, clearPart]);
3959
- if (!allowResync) return chosen2;
3960
- const best2 = _BaichuanVideoStream.scoreBcMediaLike(chosen2);
3961
- return best2.first > 0 ? chosen2.subarray(best2.first) : chosen2;
3962
- }
3963
- if (enc.kind === "full_aes") {
3964
- const key = enc.key;
3965
- const isReplayMode = this.cmdId === 5;
3966
- const freshDecrypted = aesDecrypt(raw, key);
3967
- const freshScore = _BaichuanVideoStream.scoreBcMediaLike(freshDecrypted);
3968
- const rawScore2 = _BaichuanVideoStream.scoreBcMediaLike(raw);
3969
- const startsWithMagic = freshScore.first === 0 && freshScore.score > 0;
3970
- if (!isReplayMode) {
3971
- const chosen3 = freshScore.score > rawScore2.score ? freshDecrypted : raw;
3972
- if (!allowResync) return chosen3;
3973
- const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
3974
- return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
3975
- }
3976
- if (startsWithMagic && freshDecrypted.length >= 24) {
3977
- const magic = freshDecrypted.readUInt32LE(0);
3978
- const isIFrame = magic >= 1667510320 && magic <= 1667510329;
3979
- const isPFrame = magic >= 1667510576 && magic <= 1667510585;
3980
- if ((isIFrame || isPFrame) && freshDecrypted.length >= 24) {
3981
- const additionalHeaderSize = freshDecrypted.readUInt32LE(12);
3982
- const headerLen = 24 + additionalHeaderSize;
3983
- if (headerLen > 0 && headerLen < raw.length) {
3984
- const rawPayloadStart = raw.subarray(headerLen, headerLen + 4);
3985
- const hasRawStartCode = rawPayloadStart.length >= 4 && rawPayloadStart[0] === 0 && rawPayloadStart[1] === 0 && (rawPayloadStart[2] === 1 || rawPayloadStart[2] === 0 && rawPayloadStart[3] === 1);
3986
- if (hasRawStartCode) {
3987
- const headerDecrypted = aesDecrypt(
3988
- raw.subarray(0, headerLen),
3989
- key
3990
- );
3991
- const clearPayload = raw.subarray(headerLen);
3992
- const chosen3 = Buffer.concat([headerDecrypted, clearPayload]);
3993
- if (!allowResync) return chosen3;
3994
- const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
3995
- return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
3996
- }
3997
- }
3998
- }
3999
- const IFRAME_ENCRYPT_BOUNDARY = 1024;
4000
- if (raw.length > IFRAME_ENCRYPT_BOUNDARY) {
4001
- const encryptedPart = raw.subarray(0, IFRAME_ENCRYPT_BOUNDARY);
4002
- const clearPart = raw.subarray(IFRAME_ENCRYPT_BOUNDARY);
4003
- const decryptedPart = aesDecrypt(encryptedPart, key);
4004
- const chosen3 = Buffer.concat([decryptedPart, clearPart]);
4005
- if (!allowResync) return chosen3;
4006
- const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
4007
- return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
4008
- }
4009
- {
4010
- const chosen3 = freshDecrypted;
4011
- if (!allowResync) return chosen3;
4012
- const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
4013
- return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
4014
- }
4015
- }
4016
- if (rawScore2.first === 0 && rawScore2.score > freshScore.score) {
4017
- const chosen3 = raw;
4018
- if (!allowResync) return chosen3;
4019
- const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
4020
- return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
4021
- }
4022
- if (this.aesStreamDecryptor && this.aesStreamDecryptor.isInitialized()) {
4023
- const statefulDecrypted = this.aesStreamDecryptor.update(raw);
4024
- return statefulDecrypted;
4025
- }
4026
- const chosen2 = freshScore.score > rawScore2.score ? freshDecrypted : raw;
4027
- if (!allowResync) return chosen2;
4028
- const best2 = _BaichuanVideoStream.scoreBcMediaLike(chosen2);
4029
- return best2.first > 0 ? chosen2.subarray(best2.first) : chosen2;
4030
- }
4031
- const rawScore = _BaichuanVideoStream.scoreBcMediaLike(raw);
4032
- const dec = this.client.enc.kind === "aes" || this.client.enc.kind === "bc" ? this.client.tryDecryptBinary(raw, channelId, enc) : raw;
4033
- const decScore = _BaichuanVideoStream.scoreBcMediaLike(dec);
4034
- const chosen = decScore.score > rawScore.score ? dec : raw;
4035
- if (!allowResync) return chosen;
4036
- const best = _BaichuanVideoStream.scoreBcMediaLike(chosen);
4037
- return best.first > 0 ? chosen.subarray(best.first) : chosen;
4038
- }
4039
- constructor(options) {
4040
- super();
4041
- this.client = options.client;
4042
- this.api = options.api;
4043
- this.channel = options.channel;
4044
- this.profile = options.profile;
4045
- this.variant = options.variant ?? "default";
4046
- this.logger = options.logger;
4047
- this.cmdId = options.cmdId ?? 3;
4048
- this.acceptAnyStreamType = options.acceptAnyStreamType ?? false;
4049
- this.expectedStreamTypes = this.profile === "sub" ? /* @__PURE__ */ new Set([1, 3]) : /* @__PURE__ */ new Set([0, 2]);
4050
- if (this.variant === "telephoto") this.expectedStreamTypes.add(0);
4051
- this.bcMediaCodec = new BcMediaCodec(false, this.logger);
4052
- const dbg = this.client.getDebugConfig();
4053
- this.debugH264LogsLeft = dbg.traceNativeStream ? 200 : 0;
4054
- this.debugSavedSamples = false;
4055
- this.dumpChunkIdx = 0;
4056
- this.spsById = /* @__PURE__ */ new Map();
4057
- this.ppsById = /* @__PURE__ */ new Map();
4058
- this.lastSps = null;
4059
- this.lastPps = null;
4060
- this.lastPrependedPpsId = null;
4061
- this.lastVps = null;
4062
- this.lastSpsH265 = null;
4063
- this.lastPpsH265 = null;
4064
- this.lastPrependedParamSetsH265 = false;
4065
- if (options.msgNum !== void 0) {
4066
- this.activeMsgNum = options.msgNum;
4067
- }
4068
- const transport = this.client.getTransport?.();
4069
- this.idleRestartMs = transport === "udp" ? 6e4 : 3e4;
4070
- }
4071
- noteMediaActivity() {
4072
- this.lastMediaAtMs = Date.now();
4073
- }
4074
- startWatchdog() {
4075
- if (!this.api) return;
4076
- if (this.watchdogTimer) return;
4077
- this.restartWindowStartMs = Date.now();
4078
- this.restartCountInWindow = 0;
4079
- this.watchdogTimer = setInterval(() => {
4080
- void this.watchdogTick();
4081
- }, WATCHDOG_TICK_MS);
4082
- }
4083
- stopWatchdog() {
4084
- if (this.watchdogTimer) clearInterval(this.watchdogTimer);
4085
- this.watchdogTimer = void 0;
4086
- }
4087
- async watchdogTick() {
4088
- if (!this.active) return;
4089
- if (!this.api) return;
4090
- if (this.restarting) return;
4091
- if (this.lastMediaAtMs <= 0) return;
4092
- const now = Date.now();
4093
- const idleMs = now - this.lastMediaAtMs;
4094
- if (idleMs < this.idleRestartMs) return;
4095
- if (now - this.restartWindowStartMs > 6e4) {
4096
- this.restartWindowStartMs = now;
4097
- this.restartCountInWindow = 0;
4098
- }
4099
- if (this.restartCountInWindow >= WATCHDOG_MAX_RESTARTS_PER_MINUTE) {
4100
- this.logger?.warn(
4101
- `[BaichuanVideoStream] Watchdog: idle for ${idleMs}ms, but restart budget exceeded (${WATCHDOG_MAX_RESTARTS_PER_MINUTE}/min); leaving stream as-is`
4102
- );
4103
- this.lastMediaAtMs = now;
4104
- return;
4105
- }
4106
- this.restartCountInWindow++;
4107
- void this.restartNativeStream({ reason: `idle ${idleMs}ms` });
4108
- }
4109
- async restartNativeStream(params) {
4110
- if (!this.api) return;
4111
- if (!this.active) return;
4112
- if (this.restarting) return;
4113
- this.restarting = true;
4114
- try {
4115
- const transport = this.client.getTransport?.() ?? "unknown";
4116
- const msgNum = this.activeMsgNum ?? "unknown";
4117
- this.logger?.warn(
4118
- `[BaichuanVideoStream] Watchdog restarting native stream (channel=${this.channel} profile=${this.profile} expectedStreamTypes=[${[
4119
- ...this.expectedStreamTypes
4120
- ].join(
4121
- ","
4122
- )}] msgNum=${msgNum} transport=${transport} reason=${params.reason})`
4123
- );
4124
- this.depacketizer.reset();
4125
- this.depacketizerH265.reset();
4126
- this.bcMediaCodec.clear();
4127
- this.activeMsgNum = void 0;
4128
- this.lockedChannelId = void 0;
4129
- this.lastPrependedPpsId = null;
4130
- this.lastPrependedParamSetsH265 = false;
4131
- try {
4132
- await this.api.stopVideoStream(this.channel, this.profile, {
4133
- variant: this.variant,
4134
- client: this.client
4135
- });
4136
- } catch {
4137
- }
4138
- await this.api.startVideoStream(this.channel, this.profile, {
4139
- variant: this.variant,
4140
- client: this.client
4141
- });
4142
- try {
4143
- const getMsgNum = this.api.getActiveVideoMsgNumWithVariant;
4144
- const v = typeof getMsgNum === "function" ? getMsgNum(this.channel, this.profile, this.variant) : void 0;
4145
- if (v !== void 0) this.activeMsgNum = v;
4146
- } catch {
4147
- }
4148
- this.lastMediaAtMs = Date.now();
4149
- } catch (error) {
4150
- const err = error instanceof Error ? error : new Error(String(error));
4151
- this.emitSafeError(err);
4152
- } finally {
4153
- this.restarting = false;
4154
- }
4155
- }
4156
- /**
4157
- * Start video stream.
4158
- * Listens to `push` events and processes cmd_id=3 frames carrying BcMedia packets.
4159
- */
4160
- async start() {
4161
- if (this.active) {
4162
- throw new Error("Video stream already active");
4163
- }
4164
- this.depacketizer.reset();
4165
- this.depacketizerH265.reset();
4166
- let totalFramesReceived = 0;
4167
- let totalMediaPackets = 0;
4168
- this.videoFrameHandler = (frame) => {
4169
- if (frame.header.cmdId !== this.cmdId) return;
4170
- if (this.activeMsgNum !== void 0 && frame.header.msgNum !== this.activeMsgNum) {
4171
- const allowMsgNum0Fallback = this.acceptAnyStreamType && frame.header.msgNum === 0;
4172
- if (!allowMsgNum0Fallback) {
4173
- const frameCount = this._msgNumMismatchCount = (this._msgNumMismatchCount || 0) + 1;
4174
- if (frameCount <= 5 && this.client.getDebugConfig().general) {
4175
- this.logger?.log(
4176
- `[BaichuanVideoStream] Frame msgNum mismatch: received=${frame.header.msgNum}, expected=${this.activeMsgNum}, channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
4177
- );
4178
- }
4179
- return;
4180
- }
4181
- }
4182
- if (!this.acceptAnyStreamType && !this.expectedStreamTypes.has(frame.header.streamType)) {
4183
- const frameCount = this._streamTypeMismatchCount = (this._streamTypeMismatchCount || 0) + 1;
4184
- if (frameCount <= 5 && this.client.getDebugConfig().general) {
4185
- this.logger?.log(
4186
- `[BaichuanVideoStream] Frame streamType mismatch: received=${frame.header.streamType}, expectedAny=[${[
4187
- ...this.expectedStreamTypes
4188
- ].join(
4189
- ","
4190
- )}], channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
4191
- );
4192
- }
4193
- return;
4194
- }
4195
- if (this.lockedChannelId === void 0) {
4196
- this.lockedChannelId = frame.header.channelId;
4197
- } else if (frame.header.channelId !== this.lockedChannelId) {
4198
- const frameCount = this._channelIdMismatchCount = (this._channelIdMismatchCount || 0) + 1;
4199
- if (frameCount <= 5) {
4200
- this.logger?.warn(
4201
- `[BaichuanVideoStream] Frame channelId mismatch: received=${frame.header.channelId}, locked=${this.lockedChannelId}, streamType=${frame.header.streamType}, msgNum=${frame.header.msgNum}, activeMsgNum=${this.activeMsgNum ?? "unknown"}, channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
4202
- );
4203
- }
4204
- return;
4205
- }
4206
- totalFramesReceived++;
4207
- const dbg = this.client.getDebugConfig();
4208
- const rtspDebug = dbg.debugRtsp;
4209
- if (totalFramesReceived === 1) {
4210
- if (rtspDebug) {
4211
- this.logger?.log(
4212
- `[BaichuanVideoStream] First cmd_id=${this.cmdId} frame received (bodyLen: ${frame.body.length}, channelId: ${frame.header.channelId})`
4213
- );
4214
- }
4215
- }
4216
- if (totalFramesReceived % 10 === 0 || totalFramesReceived <= 5) {
4217
- if (rtspDebug) {
4218
- this.logger?.log(
4219
- `[BaichuanVideoStream] Received ${totalFramesReceived} Baichuan frames (cmd_id=${this.cmdId})`
4220
- );
4221
- }
4222
- }
4223
- const enc = this.client.enc;
4224
- const rawCandidate = frame.payload.length > 0 ? frame.payload : frame.body;
4225
- let dataToParse = rawCandidate;
4226
- if (frame.payload.length === 0) {
4227
- let searchStart = 0;
4228
- const extensionEnd = rawCandidate.indexOf(Buffer.from("</Extension>"));
4229
- const bodyEnd = rawCandidate.indexOf(Buffer.from("</body>"));
4230
- if (extensionEnd !== -1)
4231
- searchStart = extensionEnd + Buffer.from("</Extension>").length;
4232
- else if (bodyEnd !== -1)
4233
- searchStart = bodyEnd + Buffer.from("</body>").length;
4234
- dataToParse = rawCandidate.subarray(searchStart);
4235
- }
4236
- let encryptLen;
4237
- if (frame.extension && frame.extension.length > 0) {
4238
- try {
4239
- const extDec = this.client.tryDecryptXml(
4240
- frame.extension,
4241
- frame.header.channelId,
4242
- enc
4243
- );
4244
- const encryptLenMatch = extDec.match(
4245
- /<encryptLen>(\d+)<\/encryptLen>/i
4246
- );
4247
- if (encryptLenMatch && encryptLenMatch[1]) {
4248
- encryptLen = parseInt(encryptLenMatch[1], 10);
4249
- }
4250
- } catch {
4251
- }
4252
- }
4253
- const dataAfterXml = this.chooseDecryptedOrRawCandidate({
4254
- raw: dataToParse,
4255
- enc,
4256
- channelId: frame.header.channelId,
4257
- // Some NVR/Hub streams appear to include non-media bytes even when payloadOffset is present.
4258
- // Allow a one-time resync at startup to avoid delaying the first keyframe.
4259
- allowResync: frame.payload.length === 0 || totalFramesReceived <= 10 && totalMediaPackets === 0,
4260
- ...encryptLen !== void 0 ? { encryptLen } : {}
4261
- });
4262
- if (this.bcMediaCodec.getRemainingBuffer().length === 0 && dataAfterXml.length <= 600) {
4263
- const s = _BaichuanVideoStream.scoreBcMediaLike(dataAfterXml);
4264
- if (s.first < 0) {
4265
- return;
4266
- }
4267
- }
4268
- if (totalFramesReceived === 1) {
4269
- if (rtspDebug) {
4270
- this.logger?.log(
4271
- `[BaichuanVideoStream] Data after XML: ${dataAfterXml.length} bytes, first 32 bytes: ${dataAfterXml.subarray(0, Math.min(32, dataAfterXml.length)).toString("hex")}`
4272
- );
4273
- }
4274
- }
4275
- if (dbg.dumpEnabled) ensureDumpDir(dbg);
4276
- if (dbg.dumpBcMedia && this.dumpChunkIdx < 200) {
4277
- const outDir = dbg.dumpDir;
4278
- const idx = String(this.dumpChunkIdx).padStart(4, "0");
4279
- const chunkPath = path3.join(outDir, `bcmedia_chunk_${idx}.bin`);
4280
- const infoPath = path3.join(outDir, "bcmedia_info.json");
4281
- const chunk = Buffer.from(dataAfterXml);
4282
- const writeInfo = this.dumpChunkIdx === 0;
4283
- const infoJson = writeInfo ? JSON.stringify(
4284
- {
4285
- note: "Chunks fed into the BcMedia decoder (after XML stripping/alignment).",
4286
- profile: this.profile,
4287
- channel: this.channel,
4288
- encKind: this.client.enc.kind
4289
- },
4290
- null,
4291
- 2
4292
- ) : "";
4293
- this.dumpIo.enqueue(async () => {
4294
- await fs3.promises.writeFile(chunkPath, chunk);
4295
- if (writeInfo) {
4296
- await fs3.promises.writeFile(infoPath, infoJson);
4297
- }
4298
- });
4299
- this.dumpChunkIdx++;
4300
- }
4301
- const mediaPackets = this.bcMediaCodec.decode(dataAfterXml);
4302
- totalMediaPackets += mediaPackets.length;
4303
- const packetTypes = /* @__PURE__ */ new Map();
4304
- for (const pkt of mediaPackets) {
4305
- packetTypes.set(pkt.type, (packetTypes.get(pkt.type) || 0) + 1);
4306
- }
4307
- let videoFramesEmitted = 0;
4308
- let audioFramesEmitted = 0;
4309
- for (const media of mediaPackets) {
4310
- const maybeCacheParamSets = (annexB, source, videoType) => {
4311
- if (videoType === "H264") {
4312
- const nals = splitAnnexBToNalPayloads(annexB);
4313
- for (const nal of nals) {
4314
- const t = (nal[0] ?? 0) & 31;
4315
- if (t === 7) {
4316
- const id = parseSpsIdFromNal(nal);
4317
- if (id != null) {
4318
- if (!isPlausibleH264Sps(nal)) continue;
4319
- this.spsById.set(id, nal);
4320
- if (dbg.traceNativeStream) {
4321
- this.logger?.warn(
4322
- `[BaichuanVideoStream] Cached H.264 SPS id=${id} len=${nal.length}`
4323
- );
4324
- }
4325
- }
4326
- if (isPlausibleH264Sps(nal)) this.lastSps = nal;
4327
- }
4328
- if (t === 8) {
4329
- const ids = parsePpsIdsFromNal(nal);
4330
- if (ids) {
4331
- const sps = this.spsById.get(ids.spsId);
4332
- if (sps && !isPlausibleH264Sps(sps)) continue;
4333
- this.ppsById.set(ids.ppsId, { nal, spsId: ids.spsId });
4334
- if (dbg.traceNativeStream) {
4335
- this.logger?.warn(
4336
- `[BaichuanVideoStream] Cached H.264 PPS id=${ids.ppsId} (spsId=${ids.spsId}) len=${nal.length}`
4337
- );
4338
- }
4339
- }
4340
- this.lastPps = nal;
4341
- }
4342
- }
4343
- } else if (videoType === "H265") {
4344
- const vps = extractVpsFromAnnexB(annexB);
4345
- if (vps) {
4346
- this.lastVps = vps;
4347
- if (dbg.traceNativeStream) {
4348
- this.logger?.warn(
4349
- `[BaichuanVideoStream] Cached H.265 VPS len=${vps.length}`
4350
- );
4351
- }
4352
- }
4353
- const sps = extractSpsFromAnnexB(annexB);
4354
- if (sps) {
4355
- this.lastSpsH265 = sps;
4356
- if (dbg.traceNativeStream) {
4357
- this.logger?.warn(
4358
- `[BaichuanVideoStream] Cached H.265 SPS len=${sps.length}`
4359
- );
4360
- }
4361
- }
4362
- const pps = extractPpsFromAnnexB(annexB);
4363
- if (pps) {
4364
- this.lastPpsH265 = pps;
4365
- if (dbg.traceNativeStream) {
4366
- this.logger?.warn(
4367
- `[BaichuanVideoStream] Cached H.265 PPS len=${pps.length}`
4368
- );
4369
- }
4370
- }
4371
- }
4372
- };
4373
- const prependParamSetsIfNeeded = (annexB, videoType, isPframe = false) => {
4374
- if (videoType === "H264") {
4375
- const nals = splitAnnexBToNalPayloads(annexB);
4376
- if (nals.length === 0) return annexB;
4377
- const types = nals.map((n) => (n[0] ?? 0) & 31);
4378
- const hasVcl = types.some(
4379
- (t) => t === 1 || t === 5 || t === 19 || t === 20
4380
- );
4381
- if (isPframe && !hasVcl) {
4382
- if (dbg.traceNativeStream) {
4383
- this.logger?.warn(
4384
- `[BaichuanVideoStream] Dropping P-frame without VCL (only param sets): types=${types.join(",")}`
4385
- );
4386
- }
4387
- return Buffer.alloc(0);
4388
- }
4389
- if (types.includes(7) && types.includes(8)) {
4390
- let ppsIdFromSlice = null;
4391
- for (const nal of nals) {
4392
- const t = (nal[0] ?? 0) & 31;
4393
- if (t === 1 || t === 5) {
4394
- ppsIdFromSlice = parseSlicePpsIdFromNal(nal);
4395
- break;
4396
- }
4397
- }
4398
- if (ppsIdFromSlice != null && ppsIdFromSlice <= 255) {
4399
- this.lastPrependedPpsId = ppsIdFromSlice;
4400
- } else {
4401
- this.lastPrependedPpsId = -1;
4402
- }
4403
- return annexB;
4404
- }
4405
- if (!hasVcl) return annexB;
4406
- let ppsId = null;
4407
- for (const nal of nals) {
4408
- const t = (nal[0] ?? 0) & 31;
4409
- if (t === 1 || t === 5) {
4410
- ppsId = parseSlicePpsIdFromNal(nal);
4411
- break;
4412
- }
4413
- }
4414
- if (dbg.traceNativeStream) {
4415
- this.logger?.warn(
4416
- `[BaichuanVideoStream] Slice references ppsId=${ppsId ?? "?"} lastPrepended=${this.lastPrependedPpsId ?? "?"}`
4417
- );
4418
- }
4419
- if (ppsId == null || ppsId > 255) {
4420
- if (this.lastPrependedPpsId != null) return annexB;
4421
- if (!this.lastSps || !this.lastPps) return annexB;
4422
- this.lastPrependedPpsId = -1;
4423
- return Buffer.concat([
4424
- NAL_START_CODE_4B3,
4425
- this.lastSps,
4426
- NAL_START_CODE_4B3,
4427
- this.lastPps,
4428
- annexB
4429
- ]);
4430
- }
4431
- if (this.lastPrependedPpsId === ppsId) return annexB;
4432
- const pps = this.ppsById.get(ppsId);
4433
- if (pps) {
4434
- const sps = this.spsById.get(pps.spsId);
4435
- if (sps) {
4436
- this.lastPrependedPpsId = ppsId;
4437
- return Buffer.concat([
4438
- NAL_START_CODE_4B3,
4439
- sps,
4440
- NAL_START_CODE_4B3,
4441
- pps.nal,
4442
- annexB
4443
- ]);
4444
- }
4445
- }
4446
- return Buffer.alloc(0);
4447
- } else if (videoType === "H265") {
4448
- const nals = splitAnnexBToNalPayloads2(annexB);
4449
- if (nals.length === 0) return annexB;
4450
- const types = nals.map((n) => getH265NalType(n)).filter((t) => t !== null);
4451
- const hasVcl = types.some(
4452
- (t) => t >= 0 && t <= 9 || t >= 16 && t <= 23
4453
- );
4454
- if (isPframe && !hasVcl) {
4455
- if (dbg.traceNativeStream) {
4456
- this.logger?.warn(
4457
- `[BaichuanVideoStream] Dropping H.265 P-frame without VCL (only param sets): types=${types.join(",")}`
4458
- );
4459
- }
4460
- return Buffer.alloc(0);
4461
- }
4462
- if (types.includes(32) && types.includes(33) && types.includes(34))
4463
- return annexB;
4464
- if (!hasVcl) return annexB;
4465
- if (this.lastPrependedParamSetsH265) return annexB;
4466
- if (!this.lastVps || !this.lastSpsH265 || !this.lastPpsH265)
4467
- return annexB;
4468
- this.lastPrependedParamSetsH265 = true;
4469
- if (dbg.traceNativeStream) {
4470
- this.logger?.warn(
4471
- `[BaichuanVideoStream] Prepending H.265 VPS/SPS/PPS to frame`
4472
- );
4473
- }
4474
- return Buffer.concat([
4475
- NAL_START_CODE_4B3,
4476
- this.lastVps,
4477
- NAL_START_CODE_4B3,
4478
- this.lastSpsH265,
4479
- NAL_START_CODE_4B3,
4480
- this.lastPpsH265,
4481
- annexB
4482
- ]);
4483
- }
4484
- return annexB;
4485
- };
4486
- const dumpNalSummary = (annexB, label, microseconds) => {
4487
- if (!dbg.dumpNals) return;
4488
- try {
4489
- if (dbg.dumpEnabled) ensureDumpDir(dbg);
4490
- const outDir = dbg.dumpDir;
4491
- const nals = splitAnnexBToNalPayloads(annexB);
4492
- const types = nals.map((n) => (n[0] ?? 0) & 31);
4493
- let slicePpsId = null;
4494
- const spsIds = [];
4495
- const ppsIds = [];
4496
- for (const nal of nals) {
4497
- const t = (nal[0] ?? 0) & 31;
4498
- if ((t === 1 || t === 5) && slicePpsId == null) {
4499
- slicePpsId = parseSlicePpsIdFromNal(nal);
4500
- }
4501
- if (t === 7) {
4502
- const id = parseSpsIdFromNal(nal);
4503
- if (id != null) spsIds.push(id);
4504
- }
4505
- if (t === 8) {
4506
- const ids = parsePpsIdsFromNal(nal);
4507
- if (ids) ppsIds.push(ids.ppsId);
4508
- }
4509
- }
4510
- if (this.dumpNalLines >= 2e4) return;
4511
- const line = JSON.stringify({
4512
- label,
4513
- microseconds,
4514
- len: annexB.length,
4515
- nalTypes: types,
4516
- slicePpsId,
4517
- auSpsIds: spsIds,
4518
- auPpsIds: ppsIds,
4519
- cachedSps: this.spsById.size,
4520
- cachedPps: this.ppsById.size,
4521
- lastPrependedPpsId: this.lastPrependedPpsId
4522
- }) + "\n";
4523
- this.dumpNalLines++;
4524
- const outPath = path3.join(outDir, "nal_dump.ndjson");
4525
- this.dumpIo.enqueue(async () => {
4526
- await fs3.promises.appendFile(outPath, line);
4527
- });
4528
- } catch {
4529
- }
4530
- };
4531
- const isPlausibleH264Sps = (nal) => {
4532
- if (nal.length < 4) return false;
4533
- if (((nal[0] ?? 0) & 31) !== 7) return false;
4534
- const profileIdc = nal[1] ?? 0;
4535
- const levelIdc = nal[3] ?? 0;
4536
- const knownProfiles = /* @__PURE__ */ new Set([66, 77, 88, 100, 110, 122, 244]);
4537
- if (!knownProfiles.has(profileIdc)) return false;
4538
- if (levelIdc === 0 || levelIdc > 255) return false;
4539
- return true;
4540
- };
4541
- if (media.type === "Iframe") {
4542
- let videoType = media.videoType;
4543
- const detectedCodec = detectVideoCodecFromNal(media.data);
4544
- if (detectedCodec && detectedCodec !== videoType) {
4545
- if (dbg.traceNativeStream) {
4546
- this.logger?.warn(
4547
- `[BaichuanVideoStream] Codec mismatch in Iframe: header says ${videoType}, NAL says ${detectedCodec} - using ${detectedCodec}`
4548
- );
4549
- }
4550
- videoType = detectedCodec;
4551
- }
4552
- const annexBData = videoType === "H265" ? convertToAnnexB2(media.data) : convertToAnnexB(media.data);
4553
- const isKeyframe = true;
4554
- maybeCacheParamSets(annexBData, "Iframe", videoType);
4555
- const outAnnex = prependParamSetsIfNeeded(annexBData, videoType);
4556
- if (outAnnex.length === 0) {
4557
- if (dbg.traceNativeStream) {
4558
- this.logger?.warn(
4559
- `[BaichuanVideoStream] Iframe DROPPED: outAnnex is empty`
4560
- );
4561
- }
4562
- continue;
4563
- }
4564
- dumpNalSummary(outAnnex, "Iframe", media.microseconds);
4565
- if (videoType === "H264") {
4566
- if (!isValidH264AnnexBAccessUnit(outAnnex) || !isH264KeyframeAnnexB(outAnnex)) {
4567
- if (dbg.traceNativeStream) {
4568
- this.logger?.warn(
4569
- `[BaichuanVideoStream] Dropping invalid H.264 Iframe (Annex-B) len=${outAnnex.length}`
4570
- );
4571
- }
4572
- continue;
4573
- }
4574
- } else if (videoType === "H265") {
4575
- if (!isValidH265AnnexBAccessUnit(outAnnex)) {
4576
- if (dbg.traceNativeStream) {
4577
- this.logger?.warn(
4578
- `[BaichuanVideoStream] Dropping invalid H.265 Iframe (Annex-B) len=${outAnnex.length} first16=${outAnnex.subarray(0, 16).toString("hex")}`
4579
- );
4580
- }
4581
- continue;
4582
- }
4583
- if (!isH265KeyframeAnnexB(outAnnex)) {
4584
- if (dbg.traceNativeStream) {
4585
- this.logger?.warn(
4586
- `[BaichuanVideoStream] H.265 Iframe missing VPS/SPS/PPS or IRAP, but continuing len=${outAnnex.length}`
4587
- );
4588
- }
4589
- }
4590
- }
4591
- if (dbg.traceNativeStream && !this.debugSavedSamples) {
4592
- try {
4593
- const outDir = dbg.dumpDir;
4594
- fs3.mkdirSync(outDir, { recursive: true });
4595
- if (media.type === "Iframe" && hasStartCodes(annexBData)) {
4596
- fs3.writeFileSync(
4597
- path3.join(outDir, "iframe_annexb.bin"),
4598
- annexBData
4599
- );
4600
- }
4601
- } catch {
4602
- }
4603
- }
4604
- if (!this.warnedNonAnnexBOnce && !hasStartCodes(annexBData)) {
4605
- this.warnedNonAnnexBOnce = true;
4606
- const b = media.data;
4607
- const head = b.subarray(0, Math.min(24, b.length)).toString("hex");
4608
- const headAnnex = annexBData.subarray(0, Math.min(24, annexBData.length)).toString("hex");
4609
- this.logger?.warn(
4610
- `[BaichuanVideoStream] WARNING: non-AnnexB frame after conversion (${media.type} ${media.videoType}) len=${b.length} head=${head} convertedLen=${annexBData.length} convertedHead=${headAnnex}`
4611
- );
4612
- }
4613
- this.emit("videoFrame", outAnnex);
4614
- this.emit("videoAccessUnit", {
4615
- data: outAnnex,
4616
- isKeyframe,
4617
- videoType,
4618
- // Use the detected/corrected videoType, not media.videoType
4619
- microseconds: media.microseconds,
4620
- ...media.type === "Iframe" && "time" in media ? media.time !== void 0 ? { time: media.time } : {} : {}
4621
- });
4622
- videoFramesEmitted++;
4623
- if (totalFramesReceived <= 5 || videoFramesEmitted <= 5) {
4624
- const sc = hasStartCodes(annexBData) ? "yes" : "no";
4625
- if (rtspDebug) {
4626
- this.logger?.log(
4627
- `[BaichuanVideoStream] Emitted ${media.type} (${media.videoType}) ${media.data.length} bytes -> ${annexBData.length} bytes (Annex-B, startCode:${sc})`
4628
- );
4629
- }
4630
- }
4631
- } else if (media.type === "Pframe") {
4632
- const chunk = media.data;
4633
- let videoType = media.videoType;
4634
- const detectedCodec = detectVideoCodecFromNal(chunk);
4635
- if (detectedCodec && detectedCodec !== videoType) {
4636
- videoType = detectedCodec;
4637
- }
4638
- const annexBOrRaw = hasStartCodes(chunk) ? chunk : videoType === "H265" ? convertToAnnexB2(chunk) : convertToAnnexB(chunk);
4639
- const parts = hasStartCodes(annexBOrRaw) ? [annexBOrRaw] : videoType === "H265" ? this.depacketizerH265.push(chunk) : this.depacketizer.push(chunk);
4640
- if (parts.length === 0) {
4641
- continue;
4642
- }
4643
- for (const p of parts) {
4644
- maybeCacheParamSets(p, "Pframe", videoType);
4645
- const outP0 = prependParamSetsIfNeeded(p, videoType, true);
4646
- if (outP0.length === 0) continue;
4647
- const outP = outP0;
4648
- dumpNalSummary(outP, "Pframe", media.microseconds);
4649
- const isValid = videoType === "H265" ? isValidH265AnnexBAccessUnit(outP) : isValidH264AnnexBAccessUnit(outP);
4650
- if (!isValid) {
4651
- if (dbg.traceNativeStream && this.debugH264LogsLeft > 0) {
4652
- this.debugH264LogsLeft--;
4653
- const head = outP.subarray(0, Math.min(24, outP.length)).toString("hex");
4654
- this.logger?.warn(
4655
- `[BaichuanVideoStream] Dropping invalid Pframe (${videoType}): len=${outP.length} head=${head}`
4656
- );
4657
- }
4658
- continue;
4659
- }
4660
- this.emit("videoFrame", outP);
4661
- this.emit("videoAccessUnit", {
4662
- data: outP,
4663
- isKeyframe: false,
4664
- videoType,
4665
- microseconds: media.microseconds
4666
- });
4667
- videoFramesEmitted++;
4668
- }
4669
- }
4670
- if (media.type === "Aac" || media.type === "Adpcm") {
4671
- audioFramesEmitted++;
4672
- this.emit("audioFrame", media.data);
4673
- }
4674
- if (media.type === "InfoV1" || media.type === "InfoV2") {
4675
- }
4676
- }
4677
- if (totalFramesReceived <= 10 || totalFramesReceived % 20 === 0 && (videoFramesEmitted > 0 || audioFramesEmitted > 0)) {
4678
- if (rtspDebug) {
4679
- this.logger?.log(
4680
- `[BaichuanVideoStream] Frame #${totalFramesReceived}: emitted ${videoFramesEmitted} video frames, ${audioFramesEmitted} audio frames`
4681
- );
4682
- }
4683
- }
4684
- if (videoFramesEmitted > 0 || audioFramesEmitted > 0) {
4685
- this.noteMediaActivity();
4686
- }
4687
- if (videoFramesEmitted > 0 && (totalFramesReceived <= 10 || totalFramesReceived % 50 === 0)) {
4688
- let totalVideoFrames = 0;
4689
- }
4690
- };
4691
- this.client.on("push", this.videoFrameHandler);
4692
- this.active = true;
4693
- this.startWatchdog();
4694
- this.lastMediaAtMs = Date.now();
4695
- if (this.api) {
4696
- try {
4697
- if (this.variant === "default") {
4698
- try {
4699
- await this.api.stopVideoStream(this.channel, this.profile, {
4700
- variant: "default",
4701
- client: this.client
4702
- });
4703
- } catch {
4704
- }
4705
- } else {
4706
- try {
4707
- await this.api.stopVideoStream(this.channel, this.profile, {
4708
- variant: this.variant,
4709
- client: this.client
4710
- });
4711
- this.logger?.log(
4712
- `[BaichuanVideoStream] Successfully stopped existing variant stream: ${this.variant}`
4713
- );
4714
- } catch (e) {
4715
- this.logger?.log(
4716
- `[BaichuanVideoStream] Error stopping variant stream ${this.variant} (may not exist): ${e instanceof Error ? e.message : String(e)}`
4717
- );
4718
- }
4719
- }
4720
- await new Promise((resolve) => setTimeout(resolve, 100));
4721
- const startPromise = this.api.startVideoStream(
4722
- this.channel,
4723
- this.profile,
4724
- { variant: this.variant, client: this.client }
4725
- );
4726
- const updateActiveMsgNum = () => {
4727
- try {
4728
- const getMsgNum = this.api.getActiveVideoMsgNumWithVariant;
4729
- const v = typeof getMsgNum === "function" ? getMsgNum(this.channel, this.profile, this.variant) : void 0;
4730
- if (v !== void 0) this.activeMsgNum = v;
4731
- } catch {
4732
- }
4733
- };
4734
- updateActiveMsgNum();
4735
- if (this.client.getTransport?.() === "udp") {
4736
- await startPromise;
4737
- } else {
4738
- await Promise.race([
4739
- startPromise,
4740
- new Promise((resolve) => setTimeout(resolve, 400))
4741
- ]);
4742
- }
4743
- updateActiveMsgNum();
4744
- void startPromise.then(() => updateActiveMsgNum()).catch((e) => {
4745
- const err = e instanceof Error ? e : new Error(String(e));
4746
- this.emitSafeError(err);
4747
- });
4748
- } catch (error) {
4749
- const err = error instanceof Error ? error : new Error(String(error));
4750
- if (this.client.getTransport?.() === "udp") {
4751
- this.stopWatchdog();
4752
- this.active = false;
4753
- if (this.videoFrameHandler)
4754
- this.client.off("push", this.videoFrameHandler);
4755
- this.videoFrameHandler = void 0;
4756
- throw err;
4757
- }
4758
- this.emitSafeError(err);
4759
- }
4760
- }
4761
- }
4762
- // isVideoFrame, isAudioFrame, and extractVideoData are no longer needed
4763
- // BcMediaParser handles all frame parsing and extraction
4764
- /**
4765
- * Stop video stream.
4766
- */
4767
- async stop() {
4768
- if (!this.active) return;
4769
- this.stopWatchdog();
4770
- this.depacketizer.reset();
4771
- this.depacketizerH265.reset();
4772
- if (this.videoFrameHandler) {
4773
- this.client.removeListener("push", this.videoFrameHandler);
4774
- }
4775
- this.videoFrameHandler = void 0;
4776
- this.bcMediaCodec.clear();
4777
- this.activeMsgNum = void 0;
4778
- if (this.api) {
4779
- try {
4780
- await this.api.stopVideoStream(this.channel, this.profile, {
4781
- variant: this.variant,
4782
- client: this.client
4783
- });
4784
- } catch (error) {
4785
- this.emitSafeError(
4786
- error instanceof Error ? error : new Error(String(error))
4787
- );
4788
- }
4789
- }
4790
- this.active = false;
4791
- this.emit("close");
4792
- }
4793
- isActive() {
4794
- return this.active;
4795
- }
4796
- };
4797
-
4798
2343
  // src/rtsp/urls.ts
4799
2344
  function buildRtspPath(channel, stream) {
4800
2345
  const ch = String(channel + 1).padStart(2, "0");
@@ -4810,10 +2355,10 @@ function buildRtspPath(channel, stream) {
4810
2355
  }
4811
2356
  function buildRtspUrl(params) {
4812
2357
  const port = params.port ?? 554;
4813
- const path5 = buildRtspPath(params.channel, params.stream);
2358
+ const path3 = buildRtspPath(params.channel, params.stream);
4814
2359
  const user = encodeURIComponent(params.username);
4815
2360
  const pass = encodeURIComponent(params.password);
4816
- return `rtsp://${user}:${pass}@${params.host}:${port}${path5}`;
2361
+ return `rtsp://${user}:${pass}@${params.host}:${port}${path3}`;
4817
2362
  }
4818
2363
 
4819
2364
  // src/protocol/xml.ts
@@ -5046,6 +2591,15 @@ function applyXmlTagPatch(xml, tag, value) {
5046
2591
  const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
5047
2592
  return xml.replace(re, `<${tag}>${v}</${tag}>`);
5048
2593
  }
2594
+ function upsertXmlTag(xml, tag, value) {
2595
+ if (value === void 0) return xml;
2596
+ const v = typeof value === "boolean" ? value ? 1 : 0 : value;
2597
+ const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
2598
+ if (re.test(xml)) {
2599
+ return xml.replace(re, `<${tag}>${v}</${tag}>`);
2600
+ }
2601
+ return `${xml}<${tag}>${v}</${tag}>`;
2602
+ }
5049
2603
  function patchNestedTag(xml, parent, child, value) {
5050
2604
  if (value === void 0) return xml;
5051
2605
  const v = typeof value === "boolean" ? value ? 1 : 0 : value;
@@ -5061,6 +2615,15 @@ function applyStreamPatch(xml, streamTag, patch) {
5061
2615
  );
5062
2616
  return xml.replace(re, (_match, open, body, close) => {
5063
2617
  let next = body;
2618
+ if (patch.audio !== void 0) {
2619
+ next = applyXmlTagPatch(next, "audio", patch.audio);
2620
+ }
2621
+ if (patch.width !== void 0) {
2622
+ next = applyXmlTagPatch(next, "width", patch.width);
2623
+ }
2624
+ if (patch.height !== void 0) {
2625
+ next = applyXmlTagPatch(next, "height", patch.height);
2626
+ }
5064
2627
  if (patch.bitRate !== void 0) {
5065
2628
  next = applyXmlTagPatch(next, "bitRate", patch.bitRate);
5066
2629
  }
@@ -5072,6 +2635,23 @@ function applyStreamPatch(xml, streamTag, patch) {
5072
2635
  const intVal = patch.videoEncType === "h265" ? 1 : 0;
5073
2636
  next = applyXmlTagPatch(next, "videoEncType", intVal);
5074
2637
  }
2638
+ if (patch.encoderType !== void 0) {
2639
+ next = upsertXmlTag(next, "encoderType", patch.encoderType);
2640
+ }
2641
+ if (patch.encoderProfile !== void 0) {
2642
+ next = upsertXmlTag(next, "encoderProfile", patch.encoderProfile);
2643
+ }
2644
+ if (patch.gop !== void 0) {
2645
+ const gopBlockRe = /(<gop[^>]*>)([\s\S]*?)(<\/gop>)/;
2646
+ if (gopBlockRe.test(next)) {
2647
+ next = next.replace(
2648
+ gopBlockRe,
2649
+ (_m, gOpen, gBody, gClose) => `${gOpen}${applyXmlTagPatch(gBody, "cur", patch.gop)}${gClose}`
2650
+ );
2651
+ } else {
2652
+ next = `${next}<gop><cur>${patch.gop}</cur></gop>`;
2653
+ }
2654
+ }
5075
2655
  return `${open}${next}${close}`;
5076
2656
  });
5077
2657
  }
@@ -5112,19 +2692,19 @@ async function tryCall(fn) {
5112
2692
  }
5113
2693
  }
5114
2694
  function mkdirp(dir) {
5115
- fs4.mkdirSync(dir, { recursive: true });
2695
+ fs2.mkdirSync(dir, { recursive: true });
5116
2696
  }
5117
2697
  function writeJson(filePath, obj) {
5118
- mkdirp(path4.dirname(filePath));
5119
- fs4.writeFileSync(filePath, JSON.stringify(obj, null, 2));
2698
+ mkdirp(path2.dirname(filePath));
2699
+ fs2.writeFileSync(filePath, JSON.stringify(obj, null, 2));
5120
2700
  }
5121
2701
  function writeText(filePath, text) {
5122
- mkdirp(path4.dirname(filePath));
5123
- fs4.writeFileSync(filePath, text);
2702
+ mkdirp(path2.dirname(filePath));
2703
+ fs2.writeFileSync(filePath, text);
5124
2704
  }
5125
2705
  function appendNdjson(filePath, obj) {
5126
- mkdirp(path4.dirname(filePath));
5127
- fs4.appendFileSync(filePath, JSON.stringify(obj) + "\n");
2706
+ mkdirp(path2.dirname(filePath));
2707
+ fs2.appendFileSync(filePath, JSON.stringify(obj) + "\n");
5128
2708
  }
5129
2709
  function sleepMs(ms) {
5130
2710
  return new Promise((r) => setTimeout(r, ms));
@@ -5234,7 +2814,7 @@ async function createDiagnosticsBundle(params) {
5234
2814
  cgi,
5235
2815
  ...params.extra ? { extra: params.extra } : {}
5236
2816
  };
5237
- const diagnosticsPath = path4.join(outDir, "diagnostics.json");
2817
+ const diagnosticsPath = path2.join(outDir, "diagnostics.json");
5238
2818
  writeJson(diagnosticsPath, diagnostics);
5239
2819
  return { outDir, diagnosticsPath };
5240
2820
  }
@@ -5248,8 +2828,8 @@ function sanitizeFfmpegError(error) {
5248
2828
  }
5249
2829
  function spawnFfmpeg(args, logPath) {
5250
2830
  return new Promise((resolve) => {
5251
- mkdirp(path4.dirname(logPath));
5252
- const logStream = fs4.createWriteStream(logPath, { flags: "a" });
2831
+ mkdirp(path2.dirname(logPath));
2832
+ const logStream = fs2.createWriteStream(logPath, { flags: "a" });
5253
2833
  const p = spawn("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
5254
2834
  p.on("error", (e) => {
5255
2835
  logStream.write(
@@ -5279,8 +2859,8 @@ ${stderr.slice(-4e3)}`;
5279
2859
  }
5280
2860
  function spawnFfprobeJson(args, logPath) {
5281
2861
  return new Promise((resolve) => {
5282
- mkdirp(path4.dirname(logPath));
5283
- const logStream = fs4.createWriteStream(logPath, { flags: "a" });
2862
+ mkdirp(path2.dirname(logPath));
2863
+ const logStream = fs2.createWriteStream(logPath, { flags: "a" });
5284
2864
  const p = spawn("ffprobe", args, { stdio: ["ignore", "pipe", "pipe"] });
5285
2865
  p.on("error", (e) => {
5286
2866
  const msg = e instanceof Error ? e.message : String(e);
@@ -5498,7 +3078,7 @@ async function sampleStreams(opts) {
5498
3078
  snapshotIntervalSeconds,
5499
3079
  selection: opts.selection
5500
3080
  });
5501
- const eventsPath = path4.join(outDir, "events.ndjson");
3081
+ const eventsPath = path2.join(outDir, "events.ndjson");
5502
3082
  appendNdjson(eventsPath, {
5503
3083
  t: Date.now(),
5504
3084
  type: "sampling_start",
@@ -5510,7 +3090,7 @@ async function sampleStreams(opts) {
5510
3090
  for (const profile of selectedProfiles) {
5511
3091
  for (const kind of opts.selection.kinds) {
5512
3092
  const tag = `${kind}_${profile}`;
5513
- const baseDir = path4.join(outDir, tag);
3093
+ const baseDir = path2.join(outDir, tag);
5514
3094
  mkdirp(baseDir);
5515
3095
  appendNdjson(eventsPath, {
5516
3096
  t: Date.now(),
@@ -5537,8 +3117,8 @@ async function sampleStreams(opts) {
5537
3117
  });
5538
3118
  continue;
5539
3119
  }
5540
- const rawDir = path4.join(baseDir, "raw_frames");
5541
- const snapsDir = path4.join(baseDir, "snapshots");
3120
+ const rawDir = path2.join(baseDir, "raw_frames");
3121
+ const snapsDir = path2.join(baseDir, "snapshots");
5542
3122
  mkdirp(rawDir);
5543
3123
  mkdirp(snapsDir);
5544
3124
  const maxRawFrames = opts.limits?.maxNativeRawFrames ?? 400;
@@ -5557,9 +3137,9 @@ async function sampleStreams(opts) {
5557
3137
  rawFrames++;
5558
3138
  rawBytes += payload.length;
5559
3139
  const idx = String(rawFrames).padStart(6, "0");
5560
- const binPath = path4.join(rawDir, `frame_${idx}.bin`);
3140
+ const binPath = path2.join(rawDir, `frame_${idx}.bin`);
5561
3141
  try {
5562
- fs4.writeFileSync(binPath, payload);
3142
+ fs2.writeFileSync(binPath, payload);
5563
3143
  } catch {
5564
3144
  }
5565
3145
  appendNdjson(eventsPath, {
@@ -5581,12 +3161,12 @@ async function sampleStreams(opts) {
5581
3161
  channel,
5582
3162
  profile
5583
3163
  });
5584
- const clipBase = path4.join(baseDir, `clip_${nowIsoCompact()}`);
3164
+ const clipBase = path2.join(baseDir, `clip_${nowIsoCompact()}`);
5585
3165
  const clipAnnexBPath = clipBase + ".annexb";
5586
3166
  const clipAudioPath = clipBase + ".audio.bin";
5587
3167
  const clipInfoPath = clipBase + ".json";
5588
- const videoOut = fs4.createWriteStream(clipAnnexBPath, { flags: "w" });
5589
- const audioOut = fs4.createWriteStream(clipAudioPath, { flags: "w" });
3168
+ const videoOut = fs2.createWriteStream(clipAnnexBPath, { flags: "w" });
3169
+ const audioOut = fs2.createWriteStream(clipAudioPath, { flags: "w" });
5590
3170
  let firstVideoType;
5591
3171
  let firstKeyframeAtMs = null;
5592
3172
  let videoAUs = 0;
@@ -5620,12 +3200,12 @@ async function sampleStreams(opts) {
5620
3200
  if (u.isKeyframe && now - lastSnapshotAtMs >= snapshotIntervalSeconds * 1e3) {
5621
3201
  lastSnapshotAtMs = now;
5622
3202
  const snapId = nowIsoCompact();
5623
- const snapAnnex = path4.join(
3203
+ const snapAnnex = path2.join(
5624
3204
  snapsDir,
5625
3205
  `snap_${snapId}.${u.videoType === "H265" ? "h265" : "h264"}`
5626
3206
  );
5627
3207
  try {
5628
- fs4.writeFileSync(snapAnnex, u.data);
3208
+ fs2.writeFileSync(snapAnnex, u.data);
5629
3209
  appendNdjson(eventsPath, {
5630
3210
  t: Date.now(),
5631
3211
  type: "native_snapshot_saved",
@@ -5633,12 +3213,12 @@ async function sampleStreams(opts) {
5633
3213
  profile,
5634
3214
  path: snapAnnex
5635
3215
  });
5636
- const snapJpeg = path4.join(snapsDir, `snap_${snapId}.jpg`);
3216
+ const snapJpeg = path2.join(snapsDir, `snap_${snapId}.jpg`);
5637
3217
  void tryJpegFromAnnexB({
5638
3218
  videoType: u.videoType,
5639
3219
  snapshotAnnexBPath: snapAnnex,
5640
3220
  outputJpegPath: snapJpeg,
5641
- logPath: path4.join(baseDir, "ffmpeg_snapshot.log")
3221
+ logPath: path2.join(baseDir, "ffmpeg_snapshot.log")
5642
3222
  });
5643
3223
  } catch {
5644
3224
  }
@@ -5742,11 +3322,11 @@ async function sampleStreams(opts) {
5742
3322
  stream: profile,
5743
3323
  ...opts.rtsp.port != null ? { port: opts.rtsp.port } : {}
5744
3324
  });
5745
- const mp4Path = path4.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
5746
- const logPath = path4.join(baseDir, "ffmpeg_record.log");
5747
- const snapsDir = path4.join(baseDir, "snapshots");
3325
+ const mp4Path = path2.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
3326
+ const logPath = path2.join(baseDir, "ffmpeg_record.log");
3327
+ const snapsDir = path2.join(baseDir, "snapshots");
5748
3328
  mkdirp(snapsDir);
5749
- const snapsPattern = path4.join(snapsDir, "snap_%05d.jpg");
3329
+ const snapsPattern = path2.join(snapsDir, "snap_%05d.jpg");
5750
3330
  appendNdjson(eventsPath, {
5751
3331
  t: Date.now(),
5752
3332
  type: "rtsp_url",
@@ -5768,7 +3348,7 @@ async function sampleStreams(opts) {
5768
3348
  snapshotsPattern: snapsPattern,
5769
3349
  durationSeconds: opts.durationSeconds,
5770
3350
  snapshotIntervalSeconds,
5771
- logPath: path4.join(baseDir, "ffmpeg_snapshots.log")
3351
+ logPath: path2.join(baseDir, "ffmpeg_snapshots.log")
5772
3352
  })
5773
3353
  ]);
5774
3354
  appendNdjson(eventsPath, {
@@ -5807,11 +3387,11 @@ async function sampleStreams(opts) {
5807
3387
  });
5808
3388
  continue;
5809
3389
  }
5810
- const mp4Path = path4.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
5811
- const logPath = path4.join(baseDir, "ffmpeg_record.log");
5812
- const snapsDir = path4.join(baseDir, "snapshots");
3390
+ const mp4Path = path2.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
3391
+ const logPath = path2.join(baseDir, "ffmpeg_record.log");
3392
+ const snapsDir = path2.join(baseDir, "snapshots");
5813
3393
  mkdirp(snapsDir);
5814
- const snapsPattern = path4.join(snapsDir, "snap_%05d.jpg");
3394
+ const snapsPattern = path2.join(snapsDir, "snap_%05d.jpg");
5815
3395
  appendNdjson(eventsPath, {
5816
3396
  t: Date.now(),
5817
3397
  type: "rtmp_url",
@@ -5833,7 +3413,7 @@ async function sampleStreams(opts) {
5833
3413
  snapshotsPattern: snapsPattern,
5834
3414
  durationSeconds: opts.durationSeconds,
5835
3415
  snapshotIntervalSeconds,
5836
- logPath: path4.join(baseDir, "ffmpeg_snapshots.log")
3416
+ logPath: path2.join(baseDir, "ffmpeg_snapshots.log")
5837
3417
  })
5838
3418
  ]);
5839
3419
  appendNdjson(eventsPath, {
@@ -6547,8 +4127,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6547
4127
  else console.log(msg);
6548
4128
  };
6549
4129
  const runDir = params.outDir;
6550
- const streamsDir = join5(runDir, "streams");
6551
- const logsDir = join5(runDir, "logs");
4130
+ const streamsDir = join3(runDir, "streams");
4131
+ const logsDir = join3(runDir, "logs");
6552
4132
  mkdirp(runDir);
6553
4133
  mkdirp(streamsDir);
6554
4134
  mkdirp(logsDir);
@@ -6620,8 +4200,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6620
4200
  for (const app of rtmpApps) {
6621
4201
  for (const streamName of streams) {
6622
4202
  const streamType = streamName.includes("sub") || streamName === "sub" || streamName === "mobile" ? 1 : 0;
6623
- const path5 = `/${app}/channel${params.channel}_${streamName}.bcs`;
6624
- const u = new URL(`rtmp://${params.host}:1935${path5}`);
4203
+ const path3 = `/${app}/channel${params.channel}_${streamName}.bcs`;
4204
+ const u = new URL(`rtmp://${params.host}:1935${path3}`);
6625
4205
  u.searchParams.set("channel", params.channel.toString());
6626
4206
  u.searchParams.set("stream", streamType.toString());
6627
4207
  u.searchParams.set("user", params.username);
@@ -6646,9 +4226,9 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6646
4226
  /[^a-zA-Z0-9._-]+/g,
6647
4227
  "_"
6648
4228
  );
6649
- const outPath = join5(streamsDir, `${baseName}.mkv`);
6650
- const probeLog = join5(logsDir, `${baseName}.ffprobe.log`);
6651
- const recLog = join5(logsDir, `${baseName}.ffmpeg.log`);
4229
+ const outPath = join3(streamsDir, `${baseName}.mkv`);
4230
+ const probeLog = join3(logsDir, `${baseName}.ffprobe.log`);
4231
+ const recLog = join3(logsDir, `${baseName}.ffmpeg.log`);
6652
4232
  const probe = await probeVideoInfo({
6653
4233
  url: urlWithAuth,
6654
4234
  kind: "rtsp",
@@ -6706,9 +4286,9 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6706
4286
  /[^a-zA-Z0-9._-]+/g,
6707
4287
  "_"
6708
4288
  );
6709
- const outPath = join5(streamsDir, `${baseName}.mkv`);
6710
- const probeLog = join5(logsDir, `${baseName}.ffprobe.log`);
6711
- const recLog = join5(logsDir, `${baseName}.ffmpeg.log`);
4289
+ const outPath = join3(streamsDir, `${baseName}.mkv`);
4290
+ const probeLog = join3(logsDir, `${baseName}.ffprobe.log`);
4291
+ const recLog = join3(logsDir, `${baseName}.ffmpeg.log`);
6712
4292
  const probe = await probeVideoInfo({
6713
4293
  url: cand.url,
6714
4294
  kind: "rtmp",
@@ -6804,7 +4384,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6804
4384
  /[^a-zA-Z0-9._-]+/g,
6805
4385
  "_"
6806
4386
  );
6807
- const baseDir = join5(
4387
+ const baseDir = join3(
6808
4388
  streamsDir,
6809
4389
  "native",
6810
4390
  mode,
@@ -6812,12 +4392,12 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6812
4392
  profile,
6813
4393
  variant
6814
4394
  );
6815
- const rawDir = join5(baseDir, "raw_frames");
6816
- const snapsDir = join5(baseDir, "snapshots");
6817
- const logsBase = join5(logsDir, baseName);
4395
+ const rawDir = join3(baseDir, "raw_frames");
4396
+ const snapsDir = join3(baseDir, "snapshots");
4397
+ const logsBase = join3(logsDir, baseName);
6818
4398
  mkdirp(rawDir);
6819
4399
  mkdirp(snapsDir);
6820
- const eventsPath = join5(baseDir, "events.ndjson");
4400
+ const eventsPath = join3(baseDir, "events.ndjson");
6821
4401
  appendNdjson(eventsPath, {
6822
4402
  t: Date.now(),
6823
4403
  type: "native_begin",
@@ -6851,15 +4431,15 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6851
4431
  rawFrames++;
6852
4432
  rawBytes += payload.length;
6853
4433
  const idx = String(rawFrames).padStart(6, "0");
6854
- const binPath = join5(rawDir, `frame_${idx}.bin`);
4434
+ const binPath = join3(rawDir, `frame_${idx}.bin`);
6855
4435
  try {
6856
- fs4.writeFileSync(binPath, payload);
4436
+ fs2.writeFileSync(binPath, payload);
6857
4437
  } catch {
6858
4438
  }
6859
4439
  if (rawFrames === 1) {
6860
4440
  try {
6861
4441
  writeJson(
6862
- join5(baseDir, "first_frame_header.json"),
4442
+ join3(baseDir, "first_frame_header.json"),
6863
4443
  frame.header
6864
4444
  );
6865
4445
  } catch {
@@ -6875,13 +4455,13 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6875
4455
  payloadLen: frame.payload?.length ?? 0
6876
4456
  });
6877
4457
  };
6878
- const clipBase = join5(baseDir, `clip_${nowIsoCompact()}`);
4458
+ const clipBase = join3(baseDir, `clip_${nowIsoCompact()}`);
6879
4459
  const clipAnnexBPath = clipBase + ".annexb";
6880
4460
  const clipAudioPath = clipBase + ".audio.bin";
6881
4461
  const clipInfoPath = clipBase + ".json";
6882
4462
  const ffmpegMuxLog = logsBase + ".ffmpeg_mux.log";
6883
- const videoOut = fs4.createWriteStream(clipAnnexBPath, { flags: "w" });
6884
- const audioOut = fs4.createWriteStream(clipAudioPath, { flags: "w" });
4463
+ const videoOut = fs2.createWriteStream(clipAnnexBPath, { flags: "w" });
4464
+ const audioOut = fs2.createWriteStream(clipAudioPath, { flags: "w" });
6885
4465
  let firstVideoType;
6886
4466
  let firstKeyframeAtMs = null;
6887
4467
  let startedAtMs = null;
@@ -6923,24 +4503,24 @@ async function runMultifocalDiagnosticsConsecutively(params) {
6923
4503
  if (u.isKeyframe && now - lastSnapshotAtMs >= 2e3) {
6924
4504
  lastSnapshotAtMs = now;
6925
4505
  const snapId = nowIsoCompact();
6926
- const snapAnnex = join5(
4506
+ const snapAnnex = join3(
6927
4507
  snapsDir,
6928
4508
  `snap_${snapId}.${u.videoType === "H265" ? "h265" : "h264"}`
6929
4509
  );
6930
4510
  try {
6931
- fs4.writeFileSync(snapAnnex, u.data);
4511
+ fs2.writeFileSync(snapAnnex, u.data);
6932
4512
  appendNdjson(eventsPath, {
6933
4513
  t: Date.now(),
6934
4514
  type: "native_snapshot_saved",
6935
4515
  id,
6936
4516
  path: snapAnnex
6937
4517
  });
6938
- const snapJpeg = join5(snapsDir, `snap_${snapId}.jpg`);
4518
+ const snapJpeg = join3(snapsDir, `snap_${snapId}.jpg`);
6939
4519
  void tryJpegFromAnnexB({
6940
4520
  videoType: u.videoType,
6941
4521
  snapshotAnnexBPath: snapAnnex,
6942
4522
  outputJpegPath: snapJpeg,
6943
- logPath: join5(baseDir, "ffmpeg_snapshot.log")
4523
+ logPath: join3(baseDir, "ffmpeg_snapshot.log")
6944
4524
  });
6945
4525
  } catch {
6946
4526
  }
@@ -7232,7 +4812,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
7232
4812
  lockedMsgNum,
7233
4813
  annexbPath: clipAnnexBPath,
7234
4814
  audioPath: clipAudioPath,
7235
- mkvPath: fs4.existsSync(mkvPath) ? mkvPath : void 0
4815
+ mkvPath: fs2.existsSync(mkvPath) ? mkvPath : void 0
7236
4816
  };
7237
4817
  if (info.mkvPath) {
7238
4818
  const p = await spawnFfprobeJson(
@@ -7304,7 +4884,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
7304
4884
  streamsDir,
7305
4885
  okStreams: okIds
7306
4886
  });
7307
- const resultsPath = join5(runDir, "multifocal_diagnostics.json");
4887
+ const resultsPath = join3(runDir, "multifocal_diagnostics.json");
7308
4888
  writeJson(resultsPath, results);
7309
4889
  return { runDir, resultsPath, streamsDir };
7310
4890
  }
@@ -7322,7 +4902,7 @@ async function runAllDiagnosticsConsecutively(params) {
7322
4902
  };
7323
4903
  const baseOutDir = params.outDir;
7324
4904
  const runDirName = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7325
- const runDir = join5(baseOutDir, runDirName);
4905
+ const runDir = join3(baseOutDir, runDirName);
7326
4906
  log("[Diagnostics] starting run", {
7327
4907
  outDir: baseOutDir,
7328
4908
  runDir,
@@ -7353,7 +4933,7 @@ async function runAllDiagnosticsConsecutively(params) {
7353
4933
  runDir: diagnosticsRes.outDir,
7354
4934
  cgiEnabled
7355
4935
  });
7356
- const streamsDir = join5(runDir, "streams");
4936
+ const streamsDir = join3(runDir, "streams");
7357
4937
  const rtspEnabled = params.rtsp === true || typeof params.rtsp === "object" && params.rtsp != null;
7358
4938
  const rtspCfg = rtspEnabled ? {
7359
4939
  host: (typeof params.rtsp === "object" ? params.rtsp.host : void 0) ?? host,
@@ -7374,7 +4954,7 @@ async function runAllDiagnosticsConsecutively(params) {
7374
4954
  ...logger ? { logger } : {}
7375
4955
  });
7376
4956
  log("[Diagnostics] stream sampling completed", { streamsDir });
7377
- const zipPath = join5(baseOutDir, `${runDirName}.zip`);
4957
+ const zipPath = join3(baseOutDir, `${runDirName}.zip`);
7378
4958
  log("[Diagnostics] creating zip bundle", { zipPath });
7379
4959
  await zipDirectory({ sourceDir: runDir, zipPath });
7380
4960
  log("[Diagnostics] zip bundle created", { zipPath });
@@ -7524,122 +5104,122 @@ async function captureModelFixtures(params) {
7524
5104
  const info = await capture(
7525
5105
  "getInfo",
7526
5106
  () => api.getInfo(channel),
7527
- (v) => writeJsonSafe(path4.join(outDir, "device-info.json"), v)
5107
+ (v) => writeJsonSafe(path2.join(outDir, "device-info.json"), v)
7528
5108
  );
7529
5109
  const support = await capture(
7530
5110
  "getSupportInfo",
7531
5111
  () => api.getSupportInfo(),
7532
- (v) => writeJsonSafe(path4.join(outDir, "support-info.json"), v)
5112
+ (v) => writeJsonSafe(path2.join(outDir, "support-info.json"), v)
7533
5113
  );
7534
5114
  const abilities = await capture(
7535
5115
  "getAbilityInfo",
7536
5116
  () => api.getAbilityInfo(),
7537
- (v) => writeJsonSafe(path4.join(outDir, "ability-info.json"), v)
5117
+ (v) => writeJsonSafe(path2.join(outDir, "ability-info.json"), v)
7538
5118
  );
7539
5119
  await capture(
7540
5120
  "getDeviceCapabilities",
7541
5121
  () => api.getDeviceCapabilities(channel),
7542
- (v) => writeJsonSafe(path4.join(outDir, "capabilities.json"), v)
5122
+ (v) => writeJsonSafe(path2.join(outDir, "capabilities.json"), v)
7543
5123
  );
7544
5124
  await capture("cmd289-WhiteLed", () => api.sendXml({
7545
5125
  cmdId: BC_CMD_ID_GET_WHITE_LED,
7546
5126
  channel,
7547
5127
  timeoutMs: 3e3
7548
- }), (v) => writeTextSafe(path4.join(outDir, "cmd289-white-led.xml"), v));
5128
+ }), (v) => writeTextSafe(path2.join(outDir, "cmd289-white-led.xml"), v));
7549
5129
  await capture(
7550
5130
  "getStreamMetadata",
7551
5131
  () => api.getStreamMetadata(channel),
7552
- (v) => writeJsonSafe(path4.join(outDir, "stream-metadata.json"), v)
5132
+ (v) => writeJsonSafe(path2.join(outDir, "stream-metadata.json"), v)
7553
5133
  );
7554
5134
  await capture(
7555
5135
  "getEncXml",
7556
5136
  () => api.getEncXml(channel),
7557
- (v) => writeTextSafe(path4.join(outDir, "enc-config.xml"), v)
5137
+ (v) => writeTextSafe(path2.join(outDir, "enc-config.xml"), v)
7558
5138
  );
7559
5139
  await capture(
7560
5140
  "getPorts",
7561
5141
  () => api.getPorts(),
7562
- (v) => writeJsonSafe(path4.join(outDir, "ports.json"), v)
5142
+ (v) => writeJsonSafe(path2.join(outDir, "ports.json"), v)
7563
5143
  );
7564
5144
  await capture(
7565
5145
  "getTalkAbility",
7566
5146
  () => api.getTalkAbility(channel),
7567
- (v) => writeJsonSafe(path4.join(outDir, "talk-ability.json"), v)
5147
+ (v) => writeJsonSafe(path2.join(outDir, "talk-ability.json"), v)
7568
5148
  );
7569
5149
  await capture(
7570
5150
  "getTwoWayAudioConfig",
7571
5151
  () => api.getTwoWayAudioConfig(channel),
7572
- (v) => writeJsonSafe(path4.join(outDir, "two-way-audio-config.json"), v)
5152
+ (v) => writeJsonSafe(path2.join(outDir, "two-way-audio-config.json"), v)
7573
5153
  );
7574
5154
  await capture(
7575
5155
  "getAiState",
7576
5156
  () => api.getAiState(channel),
7577
- (v) => writeJsonSafe(path4.join(outDir, "ai-state.json"), v)
5157
+ (v) => writeJsonSafe(path2.join(outDir, "ai-state.json"), v)
7578
5158
  );
7579
5159
  await capture(
7580
5160
  "getAiCfg",
7581
5161
  () => api.getAiCfg(channel),
7582
- (v) => writeJsonSafe(path4.join(outDir, "ai-cfg.json"), v)
5162
+ (v) => writeJsonSafe(path2.join(outDir, "ai-cfg.json"), v)
7583
5163
  );
7584
5164
  await capture(
7585
5165
  "getOsd",
7586
5166
  () => api.getOsd(channel),
7587
- (v) => writeJsonSafe(path4.join(outDir, "osd.json"), v)
5167
+ (v) => writeJsonSafe(path2.join(outDir, "osd.json"), v)
7588
5168
  );
7589
5169
  await capture(
7590
5170
  "getMotionAlarm",
7591
5171
  () => api.getMotionAlarm(channel),
7592
- (v) => writeJsonSafe(path4.join(outDir, "motion-alarm.json"), v)
5172
+ (v) => writeJsonSafe(path2.join(outDir, "motion-alarm.json"), v)
7593
5173
  );
7594
5174
  await capture(
7595
5175
  "getRecordCfg",
7596
5176
  () => api.getRecordCfg(channel),
7597
- (v) => writeJsonSafe(path4.join(outDir, "record-cfg.json"), v)
5177
+ (v) => writeJsonSafe(path2.join(outDir, "record-cfg.json"), v)
7598
5178
  );
7599
5179
  await capture(
7600
5180
  "getVideoInput",
7601
5181
  () => api.getVideoInput(channel),
7602
- (v) => writeJsonSafe(path4.join(outDir, "video-input.json"), v)
5182
+ (v) => writeJsonSafe(path2.join(outDir, "video-input.json"), v)
7603
5183
  );
7604
5184
  await capture(
7605
5185
  "getPtzPresets",
7606
5186
  () => api.getPtzPresets(channel),
7607
- (v) => writeJsonSafe(path4.join(outDir, "ptz-presets.json"), v)
5187
+ (v) => writeJsonSafe(path2.join(outDir, "ptz-presets.json"), v)
7608
5188
  );
7609
5189
  await capture(
7610
5190
  "getNetworkInfo",
7611
5191
  () => api.getNetworkInfo(),
7612
- (v) => writeJsonSafe(path4.join(outDir, "network-info.json"), v)
5192
+ (v) => writeJsonSafe(path2.join(outDir, "network-info.json"), v)
7613
5193
  );
7614
5194
  await capture(
7615
5195
  "getSystemGeneral",
7616
5196
  () => api.getSystemGeneral(),
7617
- (v) => writeJsonSafe(path4.join(outDir, "system-general.json"), v)
5197
+ (v) => writeJsonSafe(path2.join(outDir, "system-general.json"), v)
7618
5198
  );
7619
5199
  await capture(
7620
5200
  "getWifiSignal",
7621
5201
  () => api.getWifiSignal(channel),
7622
- (v) => writeJsonSafe(path4.join(outDir, "wifi-signal.json"), v)
5202
+ (v) => writeJsonSafe(path2.join(outDir, "wifi-signal.json"), v)
7623
5203
  );
7624
5204
  await capture(
7625
5205
  "getWhiteLedState",
7626
5206
  () => api.getWhiteLedState(channel),
7627
- (v) => writeJsonSafe(path4.join(outDir, "white-led-state.json"), v)
5207
+ (v) => writeJsonSafe(path2.join(outDir, "white-led-state.json"), v)
7628
5208
  );
7629
5209
  await capture(
7630
5210
  "getFloodlightOnMotion",
7631
5211
  () => api.getFloodlightOnMotion(channel),
7632
- (v) => writeJsonSafe(path4.join(outDir, "floodlight-on-motion.json"), v)
5212
+ (v) => writeJsonSafe(path2.join(outDir, "floodlight-on-motion.json"), v)
7633
5213
  );
7634
5214
  await capture(
7635
5215
  "buildVideoStreamOptions",
7636
5216
  () => api.buildVideoStreamOptions({ channel }),
7637
- (v) => writeJsonSafe(path4.join(outDir, "video-stream-options.json"), v)
5217
+ (v) => writeJsonSafe(path2.join(outDir, "video-stream-options.json"), v)
7638
5218
  );
7639
5219
  await capture(
7640
5220
  "getDualLensChannelInfo",
7641
5221
  () => api.getDualLensChannelInfo(channel),
7642
- (v) => writeJsonSafe(path4.join(outDir, "dual-lens-info.json"), v)
5222
+ (v) => writeJsonSafe(path2.join(outDir, "dual-lens-info.json"), v)
7643
5223
  );
7644
5224
  if (!params.skipStreamCombinationTest) await capture("streamCombinationTest", async () => {
7645
5225
  let dualLensInfo;
@@ -7781,12 +5361,12 @@ async function captureModelFixtures(params) {
7781
5361
  unexpected: unexpected.map((r) => `${r.pair[0]}+${r.pair[1]}: got ${r.ok ? "OK" : "FAIL"}, expected ${r.expectedOk ? "OK" : "FAIL"}`)
7782
5362
  }
7783
5363
  };
7784
- }, (v) => writeJsonSafe(path4.join(outDir, "stream-combination-test.json"), v));
5364
+ }, (v) => writeJsonSafe(path2.join(outDir, "stream-combination-test.json"), v));
7785
5365
  const total = Object.keys(calls).length;
7786
5366
  const ok = Object.values(calls).filter((c) => c.ok).length;
7787
5367
  const failed = total - ok;
7788
5368
  const summary = { total, ok, failed, errors };
7789
- writeJsonSafe(path4.join(outDir, "_summary.json"), {
5369
+ writeJsonSafe(path2.join(outDir, "_summary.json"), {
7790
5370
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
7791
5371
  model: info?.type ?? "unknown",
7792
5372
  itemNo: info?.itemNo ?? "unknown",
@@ -7812,140 +5392,6 @@ async function captureModelFixtures(params) {
7812
5392
  }
7813
5393
 
7814
5394
  export {
7815
- __require,
7816
- BC_TCP_DEFAULT_PORT,
7817
- BC_MAGIC,
7818
- BC_MAGIC_REV,
7819
- BC_XML_KEY,
7820
- BC_AES_IV,
7821
- BC_CLASS_LEGACY,
7822
- BC_CLASS_MODERN_20,
7823
- BC_CLASS_MODERN_24,
7824
- BC_CLASS_MODERN_24_ALT,
7825
- BC_CLASS_FILE_DOWNLOAD,
7826
- bcHeaderHasPayloadOffset,
7827
- BC_CMD_ID_LOGIN,
7828
- BC_CMD_ID_LOGOUT,
7829
- BC_CMD_ID_VIDEO,
7830
- BC_CMD_ID_VIDEO_STOP,
7831
- BC_CMD_ID_FILE_INFO_LIST_REPLAY,
7832
- BC_CMD_ID_FILE_INFO_LIST_STOP,
7833
- BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO,
7834
- BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD,
7835
- BC_CMD_ID_FILE_INFO_LIST_OPEN,
7836
- BC_CMD_ID_FILE_INFO_LIST_GET,
7837
- BC_CMD_ID_FILE_INFO_LIST_CLOSE,
7838
- BC_CMD_ID_FIND_REC_VIDEO_OPEN,
7839
- BC_CMD_ID_FIND_REC_VIDEO_GET,
7840
- BC_CMD_ID_FIND_REC_VIDEO_CLOSE,
7841
- BC_CMD_ID_COVER_PREVIEW,
7842
- BC_CMD_ID_COVER_STANDALONE_458,
7843
- BC_CMD_ID_COVER_STANDALONE_459,
7844
- BC_CMD_ID_COVER_STANDALONE_460,
7845
- BC_CMD_ID_COVER_STANDALONE_461,
7846
- BC_CMD_ID_COVER_STANDALONE_462,
7847
- BC_CMD_ID_COVER_RESPONSE,
7848
- BC_CMD_ID_TALK_ABILITY,
7849
- BC_CMD_ID_TALK_RESET,
7850
- BC_CMD_ID_TALK_CONFIG,
7851
- BC_CMD_ID_TALK,
7852
- BC_CMD_ID_PTZ_CONTROL,
7853
- BC_CMD_ID_PTZ_CONTROL_PRESET,
7854
- BC_CMD_ID_GET_PTZ_PRESET,
7855
- BC_CMD_ID_GET_PTZ_POSITION,
7856
- BC_CMD_ID_GET_ZOOM_FOCUS,
7857
- BC_CMD_ID_SET_ZOOM_FOCUS,
7858
- BC_CMD_ID_GET_BATTERY_INFO_LIST,
7859
- BC_CMD_ID_GET_BATTERY_INFO,
7860
- BC_CMD_ID_UDP_KEEP_ALIVE,
7861
- BC_CMD_ID_GET_PIR_INFO,
7862
- BC_CMD_ID_SET_PIR_INFO,
7863
- BC_CMD_ID_GET_MOTION_ALARM,
7864
- BC_CMD_ID_SET_MOTION_ALARM,
7865
- BC_CMD_ID_ALARM_EVENT_LIST,
7866
- BC_CMD_ID_GET_AI_ALARM,
7867
- BC_CMD_ID_SET_AI_ALARM,
7868
- BC_CMD_ID_GET_AUDIO_ALARM,
7869
- BC_CMD_ID_AUDIO_ALARM_PLAY,
7870
- BC_CMD_ID_GET_WHITE_LED,
7871
- BC_CMD_ID_SET_WHITE_LED_STATE,
7872
- BC_CMD_ID_SET_WHITE_LED_TASK,
7873
- BC_CMD_ID_FLOODLIGHT_STATUS_LIST,
7874
- BC_CMD_ID_ABILITY_INFO,
7875
- BC_CMD_ID_SUPPORT,
7876
- BC_CMD_ID_PING,
7877
- BC_CMD_ID_CHANNEL_INFO_ALL,
7878
- BC_CMD_ID_GET_OSD_DATETIME,
7879
- BC_CMD_ID_GET_RECORD_CFG,
7880
- BC_CMD_ID_GET_ABILITY_SUPPORT,
7881
- BC_CMD_ID_GET_FTP_TASK,
7882
- BC_CMD_ID_GET_RECORD,
7883
- BC_CMD_ID_GET_HDD_INFO_LIST,
7884
- BC_CMD_ID_GET_WIFI_SIGNAL,
7885
- BC_CMD_ID_GET_WIFI,
7886
- BC_CMD_ID_GET_ONLINE_USER_LIST,
7887
- BC_CMD_ID_GET_DAY_RECORDS,
7888
- BC_CMD_ID_GET_STREAM_INFO_LIST,
7889
- BC_CMD_ID_GET_LED_STATE,
7890
- BC_CMD_ID_GET_EMAIL_TASK,
7891
- BC_CMD_ID_GET_AUDIO_TASK,
7892
- BC_CMD_ID_GET_AUDIO_CFG,
7893
- BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
7894
- BC_CMD_ID_GET_TIMELAPSE_CFG,
7895
- BC_CMD_ID_GET_AI_DENOISE,
7896
- BC_CMD_ID_GET_KIT_AP_CFG,
7897
- BC_CMD_ID_GET_REC_ENC_CFG,
7898
- BC_CMD_ID_GET_ACCESS_USER_LIST,
7899
- BC_CMD_ID_GET_SLEEP_STATE,
7900
- BC_CMD_ID_GET_VIDEO_INPUT,
7901
- BC_CMD_ID_GET_SYSTEM_GENERAL,
7902
- BC_CMD_ID_GET_SUPPORT,
7903
- BC_CMD_ID_GET_AI_CFG,
7904
- BC_CMD_ID_SET_AI_CFG,
7905
- BC_CMD_ID_GET_SIREN_STATUS,
7906
- BC_CMD_ID_SET_AUDIO_TASK,
7907
- BC_CMD_ID_SET_VIDEO_INPUT,
7908
- BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
7909
- BC_CMD_ID_GET_ENC,
7910
- BC_CMD_ID_SET_ENC,
7911
- BC_CMD_ID_GET_PRIVACY_MASK,
7912
- BC_CMD_ID_SET_PRIVACY_MASK,
7913
- BC_CMD_ID_SET_AI_DENOISE,
7914
- BC_CMD_ID_SET_LED_STATE,
7915
- BC_CMD_ID_SET_AUDIO_CFG,
7916
- BC_CMD_ID_SET_RECORD,
7917
- BC_CMD_ID_SET_RECORD_CFG,
7918
- BC_CMD_ID_SET_EMAIL_TASK,
7919
- BC_CMD_ID_GET_PUSH_TASK,
7920
- BC_CMD_ID_SET_PUSH_TASK,
7921
- BC_CMD_ID_GET_AUTO_FOCUS,
7922
- BC_CMD_ID_SET_AUTO_FOCUS,
7923
- BC_CMD_ID_CMD_123,
7924
- BC_CMD_ID_CMD_209,
7925
- BC_CMD_ID_CMD_265,
7926
- BC_CMD_ID_CMD_440,
7927
- BC_CMD_ID_PUSH_VIDEO_INPUT,
7928
- BC_CMD_ID_PUSH_SERIAL,
7929
- BC_CMD_ID_PUSH_NET_INFO,
7930
- BC_CMD_ID_PUSH_DINGDONG_LIST,
7931
- BC_CMD_ID_PUSH_SLEEP_STATUS,
7932
- BC_CMD_ID_PUSH_COORDINATE_POINT_LIST,
7933
- BC_CMD_ID_DING_DONG_CTRL,
7934
- BC_CMD_ID_GET_DING_DONG_LIST,
7935
- BC_CMD_ID_DING_DONG_OPT,
7936
- BC_CMD_ID_GET_DING_DONG_CFG,
7937
- BC_CMD_ID_SET_DING_DONG_CFG,
7938
- BC_CMD_ID_QUICK_REPLY_PLAY,
7939
- BC_CMD_ID_GET_DING_DONG_SILENT,
7940
- BC_CMD_ID_SET_DING_DONG_SILENT,
7941
- md5HexUpper,
7942
- md5StrModern,
7943
- deriveAesKey,
7944
- bcEncrypt,
7945
- bcDecrypt,
7946
- aesEncrypt,
7947
- aesDecrypt,
7948
- AesStreamDecryptor,
7949
5395
  xmlEscape,
7950
5396
  buildLoginXml,
7951
5397
  buildLogoutXml,
@@ -7966,42 +5412,14 @@ export {
7966
5412
  buildWhiteLedStateXml,
7967
5413
  ensureXmlHeader,
7968
5414
  applyXmlTagPatch,
5415
+ upsertXmlTag,
7969
5416
  patchNestedTag,
7970
5417
  applyStreamPatch,
7971
5418
  normalizeDayNightMode,
7972
5419
  normalizeOpenClose,
7973
5420
  buildAbilityInfoExtensionXml,
7974
- normalizeDebugOptions,
7975
- recordingsTraceLog,
7976
- debugLog,
7977
- traceLog,
7978
- talkTraceLog,
7979
- eventTraceLog,
7980
5421
  ReolinkHttpClient,
7981
5422
  zipDirectory,
7982
- parseBcMedia,
7983
- BcMediaCodec,
7984
- hasStartCodes,
7985
- convertToAnnexB,
7986
- splitAnnexBToNalPayloads,
7987
- isValidH264AnnexBAccessUnit,
7988
- isH264KeyframeAnnexB,
7989
- H264RtpDepacketizer,
7990
- convertToLengthPrefixed,
7991
- hasStartCodes2,
7992
- convertToAnnexB2,
7993
- H265RtpDepacketizer,
7994
- splitAnnexBToNalPayloads2,
7995
- getH265NalType,
7996
- isH265Irap,
7997
- isValidH265AnnexBAccessUnit,
7998
- isH265KeyframeAnnexB,
7999
- extractVpsFromAnnexB,
8000
- extractSpsFromAnnexB,
8001
- extractPpsFromAnnexB,
8002
- detectVideoCodecFromNal,
8003
- BcMediaAnnexBDecoder,
8004
- BaichuanVideoStream,
8005
5423
  buildRtspPath,
8006
5424
  buildRtspUrl,
8007
5425
  collectNativeDiagnostics,
@@ -8020,4 +5438,4 @@ export {
8020
5438
  parseRecordingFileName,
8021
5439
  ReolinkCgiApi
8022
5440
  };
8023
- //# sourceMappingURL=chunk-EDLMKBG2.js.map
5441
+ //# sourceMappingURL=chunk-IJG45AOT.js.map