@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.
package/dist/index.d.ts CHANGED
@@ -180,15 +180,27 @@ export declare type AiTypesCacheEntry = {
180
180
  export declare type AnyBuffer = Buffer<ArrayBufferLike>;
181
181
 
182
182
  /**
183
- * Patch one or more fields inside an `<Enc>` stream block
184
- * (`<mainStream>` or `<subStream>`). Used by `setEnc` —
185
- * Reolink emits both blocks in the same document so a per-block scope
186
- * is needed to avoid clobbering the wrong stream.
187
- */
188
- export declare function applyStreamPatch(xml: string, streamTag: "mainStream" | "subStream", patch: {
183
+ * Patch fields inside a `<Compression>` stream block (`<mainStream>`,
184
+ * `<subStream>`, or `<thirdStream>`). Used by `setEnc` — Reolink emits all
185
+ * three blocks in the same document so a per-block scope is needed to avoid
186
+ * clobbering the wrong stream.
187
+ *
188
+ * The mapping `videoEncType` codec uses the convention seen on the wire:
189
+ * 0 = H.264
190
+ * 1 = H.265
191
+ * Both `<frame>` (camera-side preferred) and `<frameRate>` (older reolink_aio
192
+ * naming) are patched defensively — no-op if only one is present.
193
+ */
194
+ export declare function applyStreamPatch(xml: string, streamTag: "mainStream" | "subStream" | "thirdStream", patch: {
195
+ audio?: 0 | 1;
196
+ width?: number;
197
+ height?: number;
189
198
  bitRate?: number;
190
199
  frameRate?: number;
191
200
  videoEncType?: "h264" | "h265";
201
+ encoderType?: "vbr" | "cbr";
202
+ encoderProfile?: "high" | "main" | "baseline";
203
+ gop?: number;
192
204
  } | undefined): string;
193
205
 
194
206
  /**
@@ -464,6 +476,23 @@ export declare class BaichuanClient extends EventEmitter<{
464
476
  * even if the current client instance is idle/disconnected.
465
477
  */
466
478
  private static readonly streamingRegistry;
479
+ /**
480
+ * Per-device set of live BaichuanClient instances.
481
+ *
482
+ * Why: when a streaming client unsubscribes (e.g. RTSP grace timer expires
483
+ * and SocketPool tears the streaming socket down), the global streaming
484
+ * registry decrements but the GENERAL client of the same device has no
485
+ * way of knowing — its idle-disconnect timer was last evaluated while
486
+ * `isDeviceStreamingActive()` was still true (because the streaming socket
487
+ * was still alive) and wasn't rescheduled. Without this registry the
488
+ * general socket stays connected, the 60-second session-guard timer keeps
489
+ * sending getOnlineUserList() to the camera, and a battery camera ends up
490
+ * waking up every minute (issue #18).
491
+ *
492
+ * On streamingRegistry decrement-to-zero we walk this set and kick every
493
+ * sibling's idle-disconnect timer so it can re-evaluate eligibility.
494
+ */
495
+ private static readonly deviceClients;
467
496
  /**
468
497
  * Per-host D2C_DISC backoff state that persists across client instance recreation.
469
498
  *
@@ -552,6 +581,10 @@ export declare class BaichuanClient extends EventEmitter<{
552
581
  private streamTraceStats;
553
582
  private rxCmdTraceStats;
554
583
  private readonly alarmEventState;
584
+ /** Whether this instance is currently in BaichuanClient.deviceClients. */
585
+ private registeredInDeviceClients;
586
+ private registerInDeviceClients;
587
+ private unregisterFromDeviceClients;
555
588
  constructor(options: BaichuanClientOptions);
556
589
  private newSocketSessionId;
557
590
  private logFixed;
@@ -1701,6 +1734,50 @@ export declare type BaichuanStreamInfoList = {
1701
1734
 
1702
1735
  export declare type BaichuanTransport = "tcp" | "udp";
1703
1736
 
1737
+ /**
1738
+ * Snapshot of `<VersionInfo>` returned by Baichuan cmd_id=80
1739
+ * (`BC_CMD_ID_GET_VERSION_INFO`) — the same payload the Reolink mobile app
1740
+ * displays in "About this device". Distinct from `getSystemGeneral`
1741
+ * (cmd_104) which holds time/locale.
1742
+ *
1743
+ * All fields optional because firmware variants and models include
1744
+ * different subsets. Empty XML elements (e.g. `<cc3200Version></cc3200Version>`)
1745
+ * come through as the empty string so callers can distinguish "absent"
1746
+ * (undefined) from "explicitly empty" ("").
1747
+ */
1748
+ export declare interface BaichuanVersionInfo {
1749
+ /** User-assigned name (matches the OSD camera name). */
1750
+ name?: string;
1751
+ /** Model code, e.g. `"E1 Zoom"`, `"RLC-810A"`. */
1752
+ type?: string;
1753
+ /** Hardware serial number. */
1754
+ serialNumber?: string;
1755
+ /** Build identifier, typically a date string like `"build 2503281992"`. */
1756
+ buildDay?: string;
1757
+ /** Internal hardware revision, e.g. `"IPC_NT14NA48MPSD6"`. */
1758
+ hardwareVersion?: string;
1759
+ /** Config-format version Reolink uses to identify the schema the device speaks. */
1760
+ cfgVersion?: string;
1761
+ /** Firmware version, e.g. `"v3.2.0.4741_2503281992"`. */
1762
+ firmwareVersion?: string;
1763
+ /** Extended hardware detail (often hardwareVersion + region/sku suffix). */
1764
+ detail?: string;
1765
+ /** IE-Client identifier (legacy plug-in tag, usually `"IEClient"`). */
1766
+ IEClient?: string;
1767
+ /** Legacy MCU firmware version (present on some Reolink wireless models). */
1768
+ cc3200Version?: string;
1769
+ /** Signal-processor firmware version (rarely populated). */
1770
+ spVersion?: string;
1771
+ /** File extension Reolink expects for firmware updates (`"pak"`). */
1772
+ pakSuffix?: string;
1773
+ /** Reolink item / SKU number, e.g. `"E340"`. */
1774
+ itemNo?: string;
1775
+ /** AI-model bundle version (the on-device people/vehicle/animal detector). */
1776
+ aiVersion?: string;
1777
+ /** Diagnostic helper string the app sends to support, e.g. `"blackPointsLevel=0"`. */
1778
+ helpVersion?: string;
1779
+ }
1780
+
1704
1781
  export declare type BaichuanVideoInputPush = {
1705
1782
  channelId?: number;
1706
1783
  bright?: number;
@@ -1737,6 +1814,22 @@ export declare class BaichuanVideoStream extends EventEmitter<{
1737
1814
  time?: number;
1738
1815
  }
1739
1816
  ];
1817
+ /**
1818
+ * Raw BcMedia `additionalHeader` block emitted for every I-frame and P-frame.
1819
+ * Carries Reolink's per-frame side-channel metadata (detection bounding boxes).
1820
+ * `frameWidth`/`frameHeight` come from the most recent BcMedia InfoV1/V2 packet
1821
+ * so consumers can normalize pixel coordinates against the actual stream size.
1822
+ */
1823
+ additionalHeader: [
1824
+ {
1825
+ raw: Buffer;
1826
+ frameType: "Iframe" | "Pframe";
1827
+ videoType: "H264" | "H265";
1828
+ microseconds: number;
1829
+ frameWidth?: number;
1830
+ frameHeight?: number;
1831
+ }
1832
+ ];
1740
1833
  audioFrame: [Buffer];
1741
1834
  error: [Error];
1742
1835
  close: [];
@@ -1755,6 +1848,13 @@ export declare class BaichuanVideoStream extends EventEmitter<{
1755
1848
  private readonly acceptAnyStreamType;
1756
1849
  private lockedChannelId;
1757
1850
  private bcMediaCodec;
1851
+ /**
1852
+ * Diagnostic-only accessor for the BcMedia codec. Used by tools that need to
1853
+ * inspect unknown chunks (for example to discover undocumented audio
1854
+ * sub-packets the parser currently skips). Not part of the supported public
1855
+ * surface — do not rely on it in application code.
1856
+ */
1857
+ get _bcMediaCodec(): BcMediaCodec;
1758
1858
  private debugH264LogsLeft;
1759
1859
  private debugSavedSamples;
1760
1860
  private warnedNonAnnexBOnce;
@@ -1773,6 +1873,9 @@ export declare class BaichuanVideoStream extends EventEmitter<{
1773
1873
  private lastPpsH265;
1774
1874
  private lastPrependedParamSetsH265;
1775
1875
  private aesStreamDecryptor;
1876
+ private latestFrameWidth;
1877
+ private latestFrameHeight;
1878
+ private detectionTeardown;
1776
1879
  /**
1777
1880
  * Pending startup error stashed when emitSafeError is called before any
1778
1881
  * "error" listener is registered (e.g. camera returns 400 during start()).
@@ -1902,6 +2005,14 @@ export declare class BaichuanWebRTCServer extends EventEmitter {
1902
2005
  * H.265 → DataChannel with raw Annex-B frames (decoded by WebCodecs in browser)
1903
2006
  */
1904
2007
  private pumpFramesToWebRTC;
2008
+ /**
2009
+ * Lazily start the AAC → Opus transcoder for `session` and wire it to the
2010
+ * audio RTP track. ffmpeg writes RTP-formatted Opus packets back to a UDP
2011
+ * loopback the transcoder owns; we strip the RTP header and rewrap the
2012
+ * Opus payload with our audioTrack's SSRC so the browser receives a
2013
+ * coherent stream.
2014
+ */
2015
+ private ensureAudioTranscoder;
1905
2016
  /**
1906
2017
  * Send H.264 frame via RTP media track
1907
2018
  * Returns the number of RTP packets sent
@@ -1960,6 +2071,12 @@ export declare interface BaichuanWebRTCServerOptions {
1960
2071
  iceAdditionalHostAddresses?: string[];
1961
2072
  /** Force relay-only (TURN) if needed */
1962
2073
  iceTransportPolicy?: "all" | "relay";
2074
+ /**
2075
+ * Path to ffmpeg used for AAC → Opus audio transcoding. Defaults to
2076
+ * "ffmpeg" on PATH. Pass an empty string to disable audio (the audio track
2077
+ * will still appear in the SDP for SDP-stability, but stay silent).
2078
+ */
2079
+ ffmpegPath?: string;
1963
2080
  /** Logger callback */
1964
2081
  logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
1965
2082
  }
@@ -2155,6 +2272,8 @@ export declare const BC_CMD_ID_GET_SYSTEM_GENERAL = 104;
2155
2272
 
2156
2273
  export declare const BC_CMD_ID_GET_TIMELAPSE_CFG = 319;
2157
2274
 
2275
+ export declare const BC_CMD_ID_GET_VERSION_INFO = 80;
2276
+
2158
2277
  export declare const BC_CMD_ID_GET_VIDEO_INPUT = 26;
2159
2278
 
2160
2279
  export declare const BC_CMD_ID_GET_WHITE_LED = 289;
@@ -2222,6 +2341,8 @@ export declare const BC_CMD_ID_SET_LED_STATE = 209;
2222
2341
 
2223
2342
  export declare const BC_CMD_ID_SET_MOTION_ALARM = 47;
2224
2343
 
2344
+ export declare const BC_CMD_ID_SET_OSD_DATETIME = 45;
2345
+
2225
2346
  export declare const BC_CMD_ID_SET_PIR_INFO = 213;
2226
2347
 
2227
2348
  export declare const BC_CMD_ID_SET_PRIVACY_MASK = 53;
@@ -2373,7 +2494,10 @@ export declare class BcMediaCodec {
2373
2494
  private strict;
2374
2495
  private amountSkipped;
2375
2496
  private logger;
2497
+ private onUnknownChunk;
2376
2498
  constructor(strict?: boolean, logger?: Logger);
2499
+ /** Register a listener that fires for every unknown chunk before recovery. */
2500
+ setUnknownChunkListener(listener: UnknownChunkListener | undefined): void;
2377
2501
  /**
2378
2502
  * Push data into the codec buffer and try to parse complete BcMedia packets.
2379
2503
  * Returns an array of complete BcMedia packets found.
@@ -4046,6 +4170,23 @@ export declare function decodeHeader(buf: AnyBuffer): {
4046
4170
  messageKey: number;
4047
4171
  };
4048
4172
 
4173
+ /**
4174
+ * Decode the base64 `valueTable` from a `<scope>` (or `<area>`) into a flat
4175
+ * boolean grid. Bytes are packed MSB-first: bit 7 of byte 0 is cell (0,0).
4176
+ *
4177
+ * The camera ships TWO pairs of dimensions: `<scope><columns>×<rows>`
4178
+ * (bitmap size — typically 96×64) and `<width>×<height>` in the parent
4179
+ * block (the effective motion grid — typically smaller, e.g. 60×33 on
4180
+ * E1 Zoom). The user-editable region matches `width × height`, not the
4181
+ * full bitmap; bits past that are camera-side padding that stays 0.
4182
+ *
4183
+ * When `width`/`height` are omitted we treat the whole bitmap as the
4184
+ * active region (back-compat).
4185
+ *
4186
+ * Throws if the base64 contains too few bytes for `columns*rows` bits.
4187
+ */
4188
+ export declare function decodeMotionScopeBitmap(valueTable: string, columns: number, rows: number, width?: number, height?: number): MotionZoneScope;
4189
+
4049
4190
  /**
4050
4191
  * AES key derivation used by Reolink:
4051
4192
  * keyString = md5_str_modern(`${nonce}-${password}`).slice(0,16)
@@ -4463,6 +4604,49 @@ export declare function encodeHeader(h: Omit<BaichuanHeader, "magic"> & {
4463
4604
  magic?: AnyBuffer;
4464
4605
  }): AnyBuffer;
4465
4606
 
4607
+ /**
4608
+ * Encode a `width × height` boolean grid back into the camera's
4609
+ * `columns × rows` `valueTable`. Bits outside the active region stay 0,
4610
+ * matching what the camera ships on the way down.
4611
+ *
4612
+ * `scope.cells.length` must equal `scope.width * scope.height`.
4613
+ */
4614
+ export declare function encodeMotionScopeBitmap(scope: MotionZoneScope): string;
4615
+
4616
+ /**
4617
+ * Reply from `getEncOptions` — the set of allowable values for `setEnc`,
4618
+ * derived from `getStreamInfoList`. Use this to validate user input or
4619
+ * populate UI selectors.
4620
+ */
4621
+ export declare interface EncOptions {
4622
+ channel: number;
4623
+ mainStream?: EncStreamOptions;
4624
+ subStream?: EncStreamOptions;
4625
+ thirdStream?: EncStreamOptions;
4626
+ }
4627
+
4628
+ /**
4629
+ * One allowable resolution from a `getEncOptions` reply.
4630
+ * `videoEncTypeList` enumerates the codecs supported at this resolution
4631
+ * (mapped to `"h264"`/`"h265"`).
4632
+ */
4633
+ export declare interface EncResolutionOption {
4634
+ width: number;
4635
+ height: number;
4636
+ /** Codecs available at this resolution. */
4637
+ videoEncTypes: Array<"h264" | "h265">;
4638
+ /** Camera-default framerate at this resolution, if reported. */
4639
+ defaultFramerate?: number;
4640
+ /** Camera-default bitrate (kbps) at this resolution, if reported. */
4641
+ defaultBitrate?: number;
4642
+ /** Camera-default GOP at this resolution, if reported. */
4643
+ defaultGop?: number;
4644
+ /** Allowed framerate values. */
4645
+ framerateOptions: number[];
4646
+ /** Allowed bitrate values (kbps). */
4647
+ bitrateOptions: number[];
4648
+ }
4649
+
4466
4650
  export declare type EncryptionProtocol = {
4467
4651
  kind: "none";
4468
4652
  } | {
@@ -4475,6 +4659,40 @@ export declare type EncryptionProtocol = {
4475
4659
  key: Buffer;
4476
4660
  };
4477
4661
 
4662
+ /**
4663
+ * Allowable values for a single stream profile (mainStream / subStream / thirdStream).
4664
+ * Aggregated from `getStreamInfoList` (cmd_146) so consumers can populate UI
4665
+ * pickers without re-implementing the parsing logic.
4666
+ */
4667
+ export declare interface EncStreamOptions {
4668
+ /** Stream profile (one of `mainStream` / `subStream` / `thirdStream`). */
4669
+ type: string;
4670
+ /** Each entry is a `{width, height}` paired with its allowed values. */
4671
+ resolutions: EncResolutionOption[];
4672
+ /** Encoder rate-control modes Reolink exposes in the app. */
4673
+ encoderTypes: Array<"vbr" | "cbr">;
4674
+ /** Encoder profiles Reolink exposes in the app. */
4675
+ encoderProfiles: Array<"high" | "main" | "baseline">;
4676
+ }
4677
+
4678
+ /**
4679
+ * Patch payload accepted by `setEnc` for a single stream block
4680
+ * (`mainStream` / `subStream` / `thirdStream`). All fields optional —
4681
+ * unspecified ones are preserved from the device's current config.
4682
+ */
4683
+ export declare interface EncStreamPatch {
4684
+ audio?: 0 | 1;
4685
+ width?: number;
4686
+ height?: number;
4687
+ bitRate?: number;
4688
+ frameRate?: number;
4689
+ videoEncType?: "h264" | "h265";
4690
+ encoderType?: "vbr" | "cbr";
4691
+ encoderProfile?: "high" | "main" | "baseline";
4692
+ /** Keyframe interval in seconds — patches `<gop><cur>`. */
4693
+ gop?: number;
4694
+ }
4695
+
4478
4696
  /**
4479
4697
  * Prepend the XML declaration if the body doesn't already start with
4480
4698
  * one. Reolink rejects payloads without it on most setX commands.
@@ -4585,6 +4803,13 @@ export declare interface FtpTaskConfig {
4585
4803
  };
4586
4804
  }
4587
4805
 
4806
+ /**
4807
+ * Convenience: build an "everything enabled" grid of the given dimensions.
4808
+ * Useful when the camera response has no `<valueTable>` and we want to
4809
+ * start the user off with a clean slate.
4810
+ */
4811
+ export declare function fullCoverageScope(columns: number, rows: number, width?: number, height?: number): MotionZoneScope;
4812
+
4588
4813
  /**
4589
4814
  * Get constructed video stream options for all available profiles.
4590
4815
  *
@@ -4733,6 +4958,9 @@ export declare class Go2rtcTcpServer extends EventEmitter<{
4733
4958
  private resolvedPort;
4734
4959
  private nativeFanout;
4735
4960
  private nativeStreamActive;
4961
+ private nativeStreamStopping;
4962
+ private nativeStreamRetryTimer;
4963
+ private nativeStreamRetryDelayMs;
4736
4964
  private dedicatedSessionRelease;
4737
4965
  private detectedVideoType;
4738
4966
  private connectedClients;
@@ -4794,6 +5022,21 @@ export declare class Go2rtcTcpServer extends EventEmitter<{
4794
5022
  * Returns null when the buffer is not a valid ADTS frame.
4795
5023
  */
4796
5024
  private static parseAdtsSamplingInfo;
5025
+ /**
5026
+ * Schedule another startNativeStream() attempt after the given delay.
5027
+ * Idempotent: a no-op if a retry is already scheduled, the server is no
5028
+ * longer active, or an explicit stop is in progress. Implements unbounded
5029
+ * exponential backoff (5s → 60s) so a camera that stays unreachable for
5030
+ * minutes (e.g. nightly maintenance reboot) eventually recovers without
5031
+ * manual intervention — see issue #16.
5032
+ */
5033
+ private scheduleNativeStreamRetry;
5034
+ /**
5035
+ * Cancel any pending retry timer and reset the backoff. Called on explicit
5036
+ * stop and on first-frame-received so the next failure starts the backoff
5037
+ * window from scratch.
5038
+ */
5039
+ private clearNativeStreamRetry;
4797
5040
  private startNativeStream;
4798
5041
  private stopNativeStream;
4799
5042
  private startStreamHealthMonitor;
@@ -5463,6 +5706,37 @@ export declare interface MotionEvent {
5463
5706
  source?: "md" | "pir" | "unknown";
5464
5707
  }
5465
5708
 
5709
+ /**
5710
+ * Motion-detection zone grid helpers.
5711
+ *
5712
+ * Reolink's GetMdAlarm response (cmd_id=46) carries the active detection
5713
+ * region as a base64-encoded bitmap inside `<scope><valueTable>...</valueTable></scope>`.
5714
+ * The bitmap has one bit per grid cell:
5715
+ *
5716
+ * <scope>
5717
+ * <columns>96</columns>
5718
+ * <rows>64</rows>
5719
+ * <valueTable>{base64 of columns*rows bits, packed MSB-first per byte}</valueTable>
5720
+ * </scope>
5721
+ *
5722
+ * The same shape is reused by AI detection (`<AiDetectCfg><area>...</area>`)
5723
+ * — only the column/row counts differ across firmwares. Use the helpers
5724
+ * here to round-trip between the camera's base64 string and a flat boolean
5725
+ * grid the UI can render and edit.
5726
+ */
5727
+ export declare interface MotionZoneScope {
5728
+ /** Active region width (effective grid columns, `<width>` in MD). */
5729
+ width: number;
5730
+ /** Active region height (effective grid rows, `<height>` in MD). */
5731
+ height: number;
5732
+ /** Bitmap columns reported by `<scope><columns>`. */
5733
+ columns: number;
5734
+ /** Bitmap rows reported by `<scope><rows>`. */
5735
+ rows: number;
5736
+ /** Flat `width × height` array, row-major. `true` = cell included. */
5737
+ cells: boolean[];
5738
+ }
5739
+
5466
5740
  /**
5467
5741
  * Stateful MPEG-TS muxer. Each instance has its own continuity counters —
5468
5742
  * create one per output stream (or per client connection for prebuffer replay).
@@ -5996,6 +6270,12 @@ export declare class ReolinkBaichuanApi {
5996
6270
  private sessionGuardIntervalTimer;
5997
6271
  private readonly simpleEventListeners;
5998
6272
  private simpleEventSubscribed;
6273
+ private readonly detectionEventListeners;
6274
+ private readonly detectionEventStreamHooks;
6275
+ private readonly objectDetectionListeners;
6276
+ private objectDetectionStream;
6277
+ private objectDetectionStreamStartInFlight;
6278
+ private objectDetectionInternalListener;
5999
6279
  private simpleEventSubscribeInFlight;
6000
6280
  private simpleEventUnsubscribeInFlight;
6001
6281
  private simpleEventResubscribeTimer;
@@ -6413,6 +6693,73 @@ export declare class ReolinkBaichuanApi {
6413
6693
  * When the last listener is removed, the API unsubscribes from Baichuan events.
6414
6694
  */
6415
6695
  offSimpleEvent(callback?: (event: ReolinkSimpleEvent) => void | Promise<void>): Promise<void>;
6696
+ /**
6697
+ * Subscribe to per-frame detection events sourced from the BcMedia
6698
+ * `additionalHeader` block on active video streams.
6699
+ *
6700
+ * Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
6701
+ * one event fires for every I-frame / P-frame that carries an overlay block.
6702
+ * Coordinates are reported in normalized [0, 1] fractions of the source
6703
+ * frame, so the same box renders correctly on mainStream, subStream, and
6704
+ * externStream.
6705
+ *
6706
+ * Unlike `onSimpleEvent`, no Baichuan subscribe command is involved — events
6707
+ * only flow while a video stream is open. The library hooks every
6708
+ * `BaichuanVideoStream` created via this API for the listener's lifetime.
6709
+ */
6710
+ onDetection(callback: (event: ReolinkDetectionEvent) => void | Promise<void>): void;
6711
+ /**
6712
+ * Remove a single detection callback, or all of them if `callback` is omitted.
6713
+ */
6714
+ offDetection(callback?: (event: ReolinkDetectionEvent) => void | Promise<void>): void;
6715
+ /**
6716
+ * Subscribe to AI object detections (people / vehicle / animal / face boxes
6717
+ * with class label and confidence) without managing a video stream yourself.
6718
+ *
6719
+ * Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
6720
+ * substream behind the scenes on the first listener, forwards every box-bearing
6721
+ * `additionalHeader` to your callback, and tears the stream down when the last
6722
+ * listener unsubscribes. The substream is the lightest profile (typically
6723
+ * 640×360) so the additional bandwidth/CPU overhead is minimal.
6724
+ *
6725
+ * Each event carries normalized `[0, 1]` box coordinates, a class label, and
6726
+ * a confidence score — render-ready without further conversion.
6727
+ */
6728
+ onObjectDetections(callback: (event: ReolinkDetectionEvent) => void | Promise<void>): Promise<void>;
6729
+ /**
6730
+ * Remove one detection callback, or all of them if `callback` is omitted.
6731
+ * When the last listener is removed the auto-managed substream is closed.
6732
+ */
6733
+ offObjectDetections(callback?: (event: ReolinkDetectionEvent) => void | Promise<void>): Promise<void>;
6734
+ private ensureObjectDetectionStream;
6735
+ private tearDownObjectDetectionStream;
6736
+ /**
6737
+ * Internal: invoked by BaichuanVideoStream when it starts so the API can hook
6738
+ * its `additionalHeader` event. Returns a teardown function the stream calls
6739
+ * on stop. Not intended for direct use by consumers.
6740
+ */
6741
+ _registerVideoStreamForDetection(stream: {
6742
+ on: (event: "additionalHeader", listener: (info: {
6743
+ raw: Buffer;
6744
+ frameType: "Iframe" | "Pframe";
6745
+ videoType: "H264" | "H265";
6746
+ microseconds: number;
6747
+ frameWidth?: number;
6748
+ frameHeight?: number;
6749
+ }) => void) => void;
6750
+ off: (event: "additionalHeader", listener: (info: {
6751
+ raw: Buffer;
6752
+ frameType: "Iframe" | "Pframe";
6753
+ videoType: "H264" | "H265";
6754
+ microseconds: number;
6755
+ frameWidth?: number;
6756
+ frameHeight?: number;
6757
+ }) => void) => void;
6758
+ }, context: {
6759
+ channel: number;
6760
+ profile: "main" | "sub" | "ext";
6761
+ }): () => void;
6762
+ private dispatchDetectionEvent;
6416
6763
  private startSimpleEventResubscribeTimer;
6417
6764
  private stopSimpleEventResubscribeTimer;
6418
6765
  /**
@@ -6568,6 +6915,55 @@ export declare class ReolinkBaichuanApi {
6568
6915
  port: "rtsp" | "rtmp" | "onvif" | "http" | "https";
6569
6916
  enable: boolean;
6570
6917
  }): Promise<void>;
6918
+ /**
6919
+ * Full port-config setter (cmd_id 36). Patches one or more of the six
6920
+ * service ports the camera serves — Server (Baichuan), HTTP, HTTPS,
6921
+ * RTSP, RTMP, ONVIF. Each entry takes an optional `port` (number) and
6922
+ * `enable` (boolean); fields the caller doesn't pass are left alone.
6923
+ *
6924
+ * Sends one block per port that has any field set, then issues a
6925
+ * single cmd_36 with the merged body. The camera accepts multiple
6926
+ * `<XxxPort>` siblings in the same payload.
6927
+ *
6928
+ * Wire format observed on E1 Zoom:
6929
+ *
6930
+ * <body>
6931
+ * <RtspPort version="1.1">
6932
+ * <rtspPort>554</rtspPort>
6933
+ * <enable>1</enable>
6934
+ * </RtspPort>
6935
+ * <HttpsPort version="1.1">
6936
+ * <enable>0</enable>
6937
+ * </HttpsPort>
6938
+ * ...
6939
+ * </body>
6940
+ */
6941
+ setPortConfig(patch: {
6942
+ server?: {
6943
+ port?: number;
6944
+ enable?: boolean;
6945
+ };
6946
+ http?: {
6947
+ port?: number;
6948
+ enable?: boolean;
6949
+ };
6950
+ https?: {
6951
+ port?: number;
6952
+ enable?: boolean;
6953
+ };
6954
+ rtsp?: {
6955
+ port?: number;
6956
+ enable?: boolean;
6957
+ };
6958
+ rtmp?: {
6959
+ port?: number;
6960
+ enable?: boolean;
6961
+ };
6962
+ onvif?: {
6963
+ port?: number;
6964
+ enable?: boolean;
6965
+ };
6966
+ }): Promise<void>;
6571
6967
  /** GetDevInfo via Baichuan: host cmd_id 80, channel cmd_id 318 */
6572
6968
  getInfo(channel?: number, options?: {
6573
6969
  timeoutMs?: number;
@@ -7358,6 +7754,15 @@ export declare class ReolinkBaichuanApi {
7358
7754
  */
7359
7755
  setPtzPreset(presetId: number, name: string, channel?: number): Promise<void>;
7360
7756
  setPtzPreset(channel: number, presetId: number, name: string): Promise<void>;
7757
+ /**
7758
+ * Recall (move to) a saved PTZ preset.
7759
+ *
7760
+ * cmd_id 19 (PTZ_CONTROL_PRESET) with command="toPos". The camera moves
7761
+ * the head at its own default preset-recall speed; we don't expose
7762
+ * speed here because most firmwares ignore the field on toPos.
7763
+ */
7764
+ gotoPtzPreset(presetId: number, channel?: number): Promise<void>;
7765
+ gotoPtzPreset(channel: number, presetId: number): Promise<void>;
7361
7766
  /**
7362
7767
  * Best-effort delete/disable a PTZ preset.
7363
7768
  *
@@ -7532,6 +7937,39 @@ export declare class ReolinkBaichuanApi {
7532
7937
  */
7533
7938
  setMotionDetection(enabled: boolean, sensitivity?: number, channel?: number): Promise<void>;
7534
7939
  setMotionDetection(channel: number, enabled: boolean, sensitivity?: number): Promise<void>;
7940
+ /**
7941
+ * Set motion alarm with full control, including the detection-zone grid.
7942
+ *
7943
+ * Wire format observed on E1 Zoom (cmd_id=47 SetMdAlarm body):
7944
+ *
7945
+ * <MD version="1.1">
7946
+ * <channelId>0</channelId>
7947
+ * <enable>1</enable>
7948
+ * <usepir>0</usepir>
7949
+ * <width>60</width> <height>33</height>
7950
+ * <scope>
7951
+ * <columns>96</columns> <rows>64</rows>
7952
+ * <valueTable>{base64 6144-bit bitmap}</valueTable>
7953
+ * </scope>
7954
+ * ... other camera-specific fields ...
7955
+ * </MD>
7956
+ *
7957
+ * We do a read-modify-write of the GET response so any camera-specific
7958
+ * extension fields are preserved untouched. Pass `valueTable` to update
7959
+ * the detection zone — see `encodeMotionScopeBitmap` for the bitmap layout.
7960
+ *
7961
+ * @param channel - 0-based channel
7962
+ * @param enabled - toggle motion detection on/off (optional)
7963
+ * @param sensitivity - 0-50, higher = more sensitive (optional)
7964
+ * @param valueTable - base64-encoded grid bitmap; size must match
7965
+ * `<scope><columns>×<rows></scope>` from the GET (optional)
7966
+ */
7967
+ setMotionAlarmFull(opts: {
7968
+ channel?: number;
7969
+ enabled?: boolean;
7970
+ sensitivity?: number;
7971
+ valueTable?: string;
7972
+ }): Promise<void>;
7535
7973
  /**
7536
7974
  * Set AI detection settings via Baichuan.
7537
7975
  * cmd_id: 343 (SetAiAlarm)
@@ -7822,25 +8260,30 @@ export declare class ReolinkBaichuanApi {
7822
8260
  }): Promise<EncConfig>;
7823
8261
  /**
7824
8262
  * SetEnc via Baichuan (cmdId=57). Read-modify-write — preserves
7825
- * unspecified fields. Mirrors reolink_aio's `SetEnc`.
8263
+ * unspecified fields. Mirrors reolink_aio's `SetEnc` plus the additional
8264
+ * `width`/`height`/`encoderType`/`encoderProfile`/`gop`/`thirdStream`
8265
+ * fields observed in the official mobile app (see `pcap/resolution.pcapng`).
8266
+ *
8267
+ * Field meaning per stream:
8268
+ * - `audio` — 0/1 toggle
8269
+ * - `width`/`height` — resolution in pixels. Must be one of the
8270
+ * resolutions returned by {@link getStreamInfoList}.
8271
+ * - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
8272
+ * - `frameRate` — fps. Must match the table from `getStreamInfoList`.
8273
+ * - `videoEncType` — `"h264"` or `"h265"`
8274
+ * - `encoderType` — `"vbr"` or `"cbr"`
8275
+ * - `encoderProfile` — `"high"`, `"main"`, or `"baseline"`
8276
+ * - `gop` — keyframe interval in seconds (sets `<gop><cur>`)
7826
8277
  *
7827
8278
  * @param channel - Channel number (0-based)
7828
- * @param patch - Fields to update on `mainStream` and/or `subStream`,
7829
- * plus a top-level `audio` toggle (0/1). Pass only what you want
7830
- * to change.
8279
+ * @param patch - Fields to update. Pass only the fields you want to change;
8280
+ * everything else is preserved from the device's current configuration.
7831
8281
  */
7832
8282
  setEnc(channel: number, patch: {
7833
8283
  audio?: 0 | 1;
7834
- mainStream?: {
7835
- bitRate?: number;
7836
- frameRate?: number;
7837
- videoEncType?: "h264" | "h265";
7838
- };
7839
- subStream?: {
7840
- bitRate?: number;
7841
- frameRate?: number;
7842
- videoEncType?: "h264" | "h265";
7843
- };
8284
+ mainStream?: EncStreamPatch;
8285
+ subStream?: EncStreamPatch;
8286
+ thirdStream?: EncStreamPatch;
7844
8287
  }, options?: {
7845
8288
  timeoutMs?: number;
7846
8289
  }): Promise<void>;
@@ -8100,6 +8543,35 @@ export declare class ReolinkBaichuanApi {
8100
8543
  getCoordinatePointListFromPushCache(channel?: number): BaichuanCachedPush<BaichuanCoordinatePointListPush> | undefined;
8101
8544
  private isNvrLikeDevice;
8102
8545
  private sendPcapDerivedSettingsGetXml;
8546
+ /**
8547
+ * Update the OSD timestamp + channel-name overlay via cmd_id=45
8548
+ * (SetOsdDatetime). The schema is the same `<body><OsdDatetime>` +
8549
+ * `<OsdChannelName>` block returned by `getOsdDatetime` — we
8550
+ * read-modify-write so any extension fields the camera sent are
8551
+ * preserved.
8552
+ *
8553
+ * Position is in **camera pixel coordinates** (e.g. (1,1) for top-left,
8554
+ * not preset strings). Set `enable=0` to hide the overlay; the camera
8555
+ * keeps the stored position so re-enabling later restores it.
8556
+ */
8557
+ setOsdDatetime(channel: number, patch: {
8558
+ datetime?: {
8559
+ enable?: boolean | 0 | 1;
8560
+ topLeftX?: number;
8561
+ topLeftY?: number;
8562
+ language?: string;
8563
+ };
8564
+ channelName?: {
8565
+ name?: string;
8566
+ enable?: boolean | 0 | 1;
8567
+ topLeftX?: number;
8568
+ topLeftY?: number;
8569
+ enWatermark?: boolean | 0 | 1;
8570
+ enBgcolor?: boolean | 0 | 1;
8571
+ };
8572
+ }, options?: {
8573
+ timeoutMs?: number;
8574
+ }): Promise<void>;
8103
8575
  getOsdDatetime(channel: number, options?: {
8104
8576
  timeoutMs?: number;
8105
8577
  }): Promise<BaichuanGetOsdDatetimeResult>;
@@ -8118,6 +8590,36 @@ export declare class ReolinkBaichuanApi {
8118
8590
  getStreamInfoList(channel: number, options?: {
8119
8591
  timeoutMs?: number;
8120
8592
  }): Promise<BaichuanStreamInfoList>;
8593
+ /**
8594
+ * Return the set of values `setEnc` will accept on each stream of `channel`.
8595
+ * Aggregates `getStreamInfoList` (cmd_146) into a UI-friendly shape:
8596
+ * per-stream resolutions with their allowed codecs/framerates/bitrates plus
8597
+ * the enumerated encoder modes/profiles Reolink exposes.
8598
+ *
8599
+ * Useful for populating selectors and validating user input before calling
8600
+ * `setEnc` — picking an unsupported combination causes the camera to reject
8601
+ * the SET_ENC command (responseCode != 200).
8602
+ */
8603
+ getEncOptions(channel: number, options?: {
8604
+ timeoutMs?: number;
8605
+ }): Promise<EncOptions>;
8606
+ /**
8607
+ * Read the camera's `<VersionInfo>` block (cmd_id=80). Returns the
8608
+ * friendly name, model code (e.g. `"E1 Zoom"`), serial number, firmware
8609
+ * version, hardware revision, build day, AI model bundle version, etc.
8610
+ *
8611
+ * This is the same info the Reolink mobile app shows in "About this
8612
+ * device" — distinct from `getSystemGeneral` (cmd_104) which carries
8613
+ * time/locale.
8614
+ *
8615
+ * No channel parameter: this command is device-global on NVRs/Hubs and
8616
+ * camera-global on standalone cameras. Pass an explicit channel via the
8617
+ * underlying `sendXml` only if a specific firmware demands it (none we've
8618
+ * tested do).
8619
+ */
8620
+ getVersionInfo(options?: {
8621
+ timeoutMs?: number;
8622
+ }): Promise<BaichuanVersionInfo>;
8121
8623
  getLedState(channel: number, options?: {
8122
8624
  timeoutMs?: number;
8123
8625
  }): Promise<BaichuanLedState>;
@@ -9353,6 +9855,64 @@ export declare interface ReolinkDayNightNotification {
9353
9855
  timestamp?: number;
9354
9856
  }
9355
9857
 
9858
+ /**
9859
+ * A single detection bounding box in normalized [0, 1] coordinates relative to
9860
+ * the source video frame. Using fractions instead of pixels keeps the same box
9861
+ * valid across mainStream / subStream / externStream output and across firmware
9862
+ * resolution changes.
9863
+ */
9864
+ export declare interface ReolinkDetectionBox {
9865
+ /** Left edge in [0, 1] (0 = left, 1 = right). */
9866
+ x: number;
9867
+ /** Top edge in [0, 1] (0 = top, 1 = bottom). */
9868
+ y: number;
9869
+ /** Width in [0, 1]. */
9870
+ width: number;
9871
+ /** Height in [0, 1]. */
9872
+ height: number;
9873
+ /** AI class label if the camera reports one (e.g. "person", "vehicle"). */
9874
+ label?: string;
9875
+ /** Confidence in [0, 1] if exposed by the camera. */
9876
+ confidence?: number;
9877
+ }
9878
+
9879
+ /**
9880
+ * Diagnostic state describing how much of the BcMedia additionalHeader the
9881
+ * decoder was able to interpret. Useful for consumers iterating on the format.
9882
+ */
9883
+ export declare type ReolinkDetectionDecodeState =
9884
+ /** Header marker was missing or malformed. */
9885
+ "invalid-marker"
9886
+ /** Baseline 128-byte header — camera reports no overlay metadata. */
9887
+ | "no-overlay"
9888
+ /** Overlay block present, but coordinates have not been decoded yet. */
9889
+ | "overlay-undecoded"
9890
+ /** Overlay block decoded successfully. */
9891
+ | "overlay-decoded";
9892
+
9893
+ /**
9894
+ * High-level "detection" event emitted alongside every video frame that carries
9895
+ * a non-empty BcMedia additionalHeader. Mirrors `ReolinkSimpleEvent` but is
9896
+ * sourced from the streaming side-channel rather than from cmd_id 33 push events.
9897
+ */
9898
+ export declare interface ReolinkDetectionEvent {
9899
+ channel: number;
9900
+ /** Microseconds timestamp from the BcMedia video frame. */
9901
+ microseconds: number;
9902
+ /** Stream profile that produced the underlying frame. */
9903
+ profile: "main" | "sub" | "ext";
9904
+ /** Boxes in [0, 1] fractional coordinates. */
9905
+ boxes: ReolinkDetectionBox[];
9906
+ /** Source frame width (from BcMedia InfoV1/V2) if known. */
9907
+ frameWidth?: number;
9908
+ /** Source frame height (from BcMedia InfoV1/V2) if known. */
9909
+ frameHeight?: number;
9910
+ /** Decoder diagnostic state. */
9911
+ decodeState: ReolinkDetectionDecodeState;
9912
+ /** Raw additionalHeader bytes — kept for downstream decoder work. */
9913
+ rawHeader: Buffer;
9914
+ }
9915
+
9356
9916
  export declare interface ReolinkDeviceInfo {
9357
9917
  type?: string;
9358
9918
  hardwareVersion?: string;
@@ -10224,6 +10784,27 @@ export declare interface TwoWayAudioConfig {
10224
10784
  mode?: "mixAudioStream" | string;
10225
10785
  }
10226
10786
 
10787
+ /**
10788
+ * Optional listener invoked whenever the codec encounters a 4-byte sequence
10789
+ * at the buffer head that does NOT match any known BcMedia magic and is about
10790
+ * to be skipped as "recovery". Use it to discover undocumented sub-packet
10791
+ * shapes (e.g. AI overlay metadata) without altering the codec's behaviour.
10792
+ */
10793
+ export declare type UnknownChunkListener = (info: {
10794
+ magic: number;
10795
+ /** Up to 256 bytes starting at the unknown chunk's first byte. */
10796
+ preview: Buffer;
10797
+ /** Number of bytes the codec is about to skip before resyncing. */
10798
+ skipped: number;
10799
+ }) => void;
10800
+
10801
+ /**
10802
+ * Like {@link applyXmlTagPatch} but inserts the tag at the end of the block
10803
+ * when it is not already present. Required for fields the camera omits on
10804
+ * GET responses but expects on SET (e.g. `<encoderType>` on `setEnc`).
10805
+ */
10806
+ export declare function upsertXmlTag(xml: string, tag: string, value: string | number | boolean | undefined): string;
10807
+
10227
10808
  /**
10228
10809
  * Client information extracted from HTTP request headers.
10229
10810
  * Used to determine optimal video delivery format.