@apocaliss92/nodelink-js 0.1.7 → 0.1.9

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
@@ -919,6 +919,84 @@ export declare type BaichuanHeader = {
919
919
  payloadOffset?: number;
920
920
  };
921
921
 
922
+ export declare class BaichuanHlsServer extends EventEmitter {
923
+ private readonly api;
924
+ private readonly channel;
925
+ private readonly profile;
926
+ private readonly variant;
927
+ private readonly segmentDuration;
928
+ private readonly playlistSize;
929
+ private readonly ffmpegPath;
930
+ private readonly log;
931
+ private outputDir;
932
+ private createdTempDir;
933
+ private playlistPath;
934
+ private segmentPattern;
935
+ private state;
936
+ private codec;
937
+ private framesReceived;
938
+ private ffmpeg;
939
+ private nativeStream;
940
+ private pumpPromise;
941
+ private startedAt;
942
+ private lastError;
943
+ constructor(options: BaichuanHlsServerOptions);
944
+ /**
945
+ * Start HLS streaming
946
+ */
947
+ start(): Promise<void>;
948
+ /**
949
+ * Stop HLS streaming
950
+ */
951
+ stop(): Promise<void>;
952
+ /**
953
+ * Get current status
954
+ */
955
+ getStatus(): HlsServerStatus;
956
+ /**
957
+ * Get playlist file path
958
+ */
959
+ getPlaylistPath(): string | null;
960
+ /**
961
+ * Get output directory
962
+ */
963
+ getOutputDir(): string | null;
964
+ /**
965
+ * Check if playlist file exists
966
+ */
967
+ waitForPlaylist(timeoutMs?: number): Promise<boolean>;
968
+ /**
969
+ * Read an HLS asset (playlist or segment)
970
+ */
971
+ readAsset(assetName: string): Promise<{
972
+ data: Buffer;
973
+ contentType: string;
974
+ } | null>;
975
+ private pumpNativeToFfmpeg;
976
+ private spawnFfmpeg;
977
+ }
978
+
979
+ export declare interface BaichuanHlsServerOptions {
980
+ /** API instance (required) */
981
+ api: ReolinkBaichuanApi;
982
+ /** Channel number (required) */
983
+ channel: number;
984
+ /** Stream profile (required) */
985
+ profile: StreamProfile;
986
+ /** Native-only: TrackMix tele/autotrack variants */
987
+ variant?: NativeVideoStreamVariant;
988
+ /** Output directory for HLS segments. If not provided, a temp directory will be created. */
989
+ outputDir?: string;
990
+ /** HLS segment duration in seconds (default: 2) */
991
+ segmentDuration?: number;
992
+ /** Number of segments to keep in playlist (default: 5) */
993
+ playlistSize?: number;
994
+ /** ffmpeg binary path (default: "ffmpeg") */
995
+ ffmpegPath?: string;
996
+ /** Logger callback */
997
+ logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
998
+ }
999
+
922
1000
  /**
923
1001
  * BaichuanHttpStreamServer - HTTP server that serves a Baichuan video stream as MPEG-TS.
924
1002
  *
@@ -980,6 +1058,106 @@ export declare type BaichuanLedState = {
980
1058
  lightState?: string;
981
1059
  };
982
1060
 
1061
+ /**
1062
+ * BaichuanMjpegServer - MJPEG HTTP server for Baichuan video streams
1063
+ *
1064
+ * Events:
1065
+ * - 'started': Server started
1066
+ * - 'stopped': Server stopped
1067
+ * - 'client-connected': Client connected
1068
+ * - 'client-disconnected': Client disconnected
1069
+ * - 'error': Error occurred
1070
+ */
1071
+ export declare class BaichuanMjpegServer extends EventEmitter {
1072
+ private readonly options;
1073
+ private readonly clients;
1074
+ private httpServer;
1075
+ private transformer;
1076
+ private nativeStream;
1077
+ private streamPump;
1078
+ private detectedCodec;
1079
+ private started;
1080
+ private clientIdCounter;
1081
+ constructor(options: BaichuanMjpegServerOptions);
1082
+ /**
1083
+ * Start the MJPEG server
1084
+ */
1085
+ start(): Promise<void>;
1086
+ /**
1087
+ * Stop the MJPEG server
1088
+ */
1089
+ stop(): Promise<void>;
1090
+ /**
1091
+ * Handle HTTP request
1092
+ */
1093
+ private handleRequest;
1094
+ /**
1095
+ * Handle new MJPEG client
1096
+ */
1097
+ private handleMjpegClient;
1098
+ /**
1099
+ * Start the native video stream and MJPEG transformer
1100
+ */
1101
+ private startStream;
1102
+ /**
1103
+ * Pump native stream and feed to transformer
1104
+ */
1105
+ private pumpStream;
1106
+ /**
1107
+ * Initialize MJPEG transformer once codec is detected
1108
+ */
1109
+ private initTransformer;
1110
+ /**
1111
+ * Broadcast JPEG frame to all connected clients
1112
+ */
1113
+ private broadcastFrame;
1114
+ /**
1115
+ * Stop the stream and transformer
1116
+ */
1117
+ private stopStream;
1118
+ /**
1119
+ * Get current number of connected clients
1120
+ */
1121
+ getClientCount(): number;
1122
+ /**
1123
+ * Get server status
1124
+ */
1125
+ getStatus(): {
1126
+ running: boolean;
1127
+ clients: number;
1128
+ codec: string | null;
1129
+ frames: number;
1130
+ };
1131
+ private log;
1132
+ }
1133
+
1134
+ export declare interface BaichuanMjpegServerOptions {
1135
+ /** API instance (required) */
1136
+ api: ReolinkBaichuanApi;
1137
+ /** Channel number (required) */
1138
+ channel: number;
1139
+ /** Stream profile (required) */
1140
+ profile: StreamProfile;
1141
+ /** Native-only: TrackMix tele/autotrack variants */
1142
+ variant?: NativeVideoStreamVariant;
1143
+ /** HTTP server port (default: 8080) */
1144
+ port?: number;
1145
+ /** HTTP server host (default: "0.0.0.0") */
1146
+ host?: string;
1147
+ /** URL path for MJPEG stream (default: "/mjpeg") */
1148
+ path?: string;
1149
+ /** JPEG quality (1-31, lower is better, default: 5) */
1150
+ quality?: number;
1151
+ /** Output width (optional) */
1152
+ width?: number;
1153
+ /** Output height (optional) */
1154
+ height?: number;
1155
+ /** Max FPS (optional) */
1156
+ maxFps?: number;
1157
+ /** Logger callback */
1158
+ logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
1159
+ }
1160
+
983
1161
  export declare type BaichuanNetInfoPush = {
984
1162
  netType?: string;
985
1163
  signal?: number;
@@ -1376,6 +1554,133 @@ export declare interface BaichuanVideoStreamOptions {
1376
1554
  acceptAnyStreamType?: boolean;
1377
1555
  }
1378
1556
 
1557
+ /**
1558
+ * BaichuanWebRTCServer - WebRTC server for Baichuan video streams
1559
+ *
1560
+ * Events:
1561
+ * - 'session-created': New session created
1562
+ * - 'session-connected': Session connected (ICE complete)
1563
+ * - 'session-closed': Session closed
1564
+ * - 'intercom-started': Intercom started for session
1565
+ * - 'intercom-stopped': Intercom stopped for session
1566
+ * - 'error': Error occurred
1567
+ */
1568
+ export declare class BaichuanWebRTCServer extends EventEmitter {
1569
+ private readonly options;
1570
+ private readonly sessions;
1571
+ private sessionIdCounter;
1572
+ private weriftModule;
1573
+ constructor(options: BaichuanWebRTCServerOptions);
1574
+ /**
1575
+ * Initialize werift module (lazy load to avoid requiring it if not used)
1576
+ */
1577
+ private loadWerift;
1578
+ /**
1579
+ * Create a new WebRTC session
1580
+ * Returns a session ID and SDP offer to send to the browser
1581
+ */
1582
+ createSession(): Promise<{
1583
+ sessionId: string;
1584
+ offer: WebRTCOffer;
1585
+ }>;
1586
+ /**
1587
+ * Handle WebRTC answer from browser and start streaming
1588
+ */
1589
+ handleAnswer(sessionId: string, answer: WebRTCAnswer): Promise<void>;
1590
+ /**
1591
+ * Add ICE candidate from browser
1592
+ */
1593
+ addIceCandidate(sessionId: string, candidate: WebRTCIceCandidate): Promise<void>;
1594
+ /**
1595
+ * Close a WebRTC session
1596
+ */
1597
+ closeSession(sessionId: string): Promise<void>;
1598
+ /**
1599
+ * Get information about all active sessions
1600
+ */
1601
+ getSessions(): WebRTCSessionInfo[];
1602
+ /**
1603
+ * Get information about a specific session
1604
+ */
1605
+ getSession(sessionId: string): WebRTCSessionInfo | null;
1606
+ /**
1607
+ * Close all sessions and stop the server
1608
+ */
1609
+ stop(): Promise<void>;
1610
+ /**
1611
+ * Get the number of active sessions
1612
+ */
1613
+ get sessionCount(): number;
1614
+ /**
1615
+ * Wait for ICE gathering to complete
1616
+ */
1617
+ private waitForIceGathering;
1618
+ /**
1619
+ * Start native Baichuan stream and pump frames to WebRTC
1620
+ */
1621
+ private startNativeStream;
1622
+ /**
1623
+ * Pump frames from native stream to WebRTC tracks
1624
+ * H.264 → RTP media track (standard WebRTC)
1625
+ * H.265 → DataChannel with raw Annex-B frames (decoded by WebCodecs in browser)
1626
+ */
1627
+ private pumpFramesToWebRTC;
1628
+ /**
1629
+ * Send H.264 frame via RTP media track
1630
+ * Returns the number of RTP packets sent
1631
+ */
1632
+ private sendH264Frame;
1633
+ /**
1634
+ * Send video frame via DataChannel (works for both H.264 and H.265)
1635
+ * Format: 12-byte header + Annex-B data
1636
+ * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]
1637
+ * Flags: 0x01 = H.265, 0x02 = H.264
1638
+ */
1639
+ private sendVideoFrameViaDataChannel;
1640
+ /**
1641
+ * Send H.265 frame via DataChannel
1642
+ * Format: 12-byte header + Annex-B data
1643
+ * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]
1644
+ */
1645
+ private sendH265Frame;
1646
+ /**
1647
+ * Create RTP packets for H.264 NAL unit
1648
+ * Handles single NAL, STAP-A aggregation, and FU-A fragmentation
1649
+ */
1650
+ private createH264RtpPackets;
1651
+ /**
1652
+ * Start intercom (two-way audio)
1653
+ */
1654
+ private startIntercom;
1655
+ /**
1656
+ * Log helper
1657
+ */
1658
+ private log;
1659
+ }
1660
+
1661
+ export declare interface BaichuanWebRTCServerOptions {
1662
+ /** API instance (required) */
1663
+ api: ReolinkBaichuanApi;
1664
+ /** Channel number (required) */
1665
+ channel: number;
1666
+ /** Stream profile (required) */
1667
+ profile: StreamProfile;
1668
+ /** Native-only: TrackMix tele/autotrack variants */
1669
+ variant?: NativeVideoStreamVariant;
1670
+ /** Enable two-way audio intercom (default: false) */
1671
+ enableIntercom?: boolean;
1672
+ /** STUN servers for ICE (default: Google STUN) */
1673
+ stunServers?: string[];
1674
+ /** TURN servers for NAT traversal (optional) */
1675
+ turnServers?: Array<{
1676
+ urls: string;
1677
+ username?: string;
1678
+ credential?: string;
1679
+ }>;
1680
+ /** Logger callback */
1681
+ logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
1682
+ }
1683
+
1379
1684
  export declare type BaichuanWifi = {
1380
1685
  protocol?: number;
1381
1686
  mode?: string;
@@ -1959,6 +2264,14 @@ export declare function buildChannelExtensionXml(channelId: number | string | un
1959
2264
  */
1960
2265
  export declare function buildFloodlightManualXml(channelId: number, status: number, durationSeconds?: number): string;
1961
2266
 
2267
+ /**
2268
+ * Build the HLS redirect URL from the original request URL.
2269
+ *
2270
+ * @param originalUrl - The original request URL
2271
+ * @returns The URL with ?hls=playlist.m3u8 appended
2272
+ */
2273
+ export declare function buildHlsRedirectUrl(originalUrl: string): string;
2274
+
1962
2275
  export declare function buildLoginXml(userNameHash: string, passwordHash: string): string;
1963
2276
 
1964
2277
  /**
@@ -2791,6 +3104,25 @@ export declare function createLogger(options?: {
2791
3104
  tag?: string;
2792
3105
  }): Logger_2;
2793
3106
 
3107
+ /**
3108
+ * Create an MJPEG HTTP response handler
3109
+ *
3110
+ * Usage:
3111
+ * ```ts
3112
+ * const transformer = new MjpegTransformer({ codec: "h264" });
3113
+ * transformer.start();
3114
+ *
3115
+ * // In your stream handler:
3116
+ * stream.on("accessUnit", (au) => transformer.push(au.data, au.timestamp));
3117
+ *
3118
+ * // HTTP handler:
3119
+ * app.get("/mjpeg", (req, res) => {
3120
+ * serveMjpeg(res, transformer);
3121
+ * });
3122
+ * ```
3123
+ */
3124
+ export declare function createMjpegBoundary(): string;
3125
+
2794
3126
  /**
2795
3127
  * Stream frame data for rebroadcast.
2796
3128
  * Similar to Wyze forkAndStream() implementation.
@@ -2923,6 +3255,22 @@ export declare type DebugOptions = {
2923
3255
  };
2924
3256
  };
2925
3257
 
3258
+ /**
3259
+ * Determine if H.265 should be transcoded to H.264 based on client capabilities.
3260
+ *
3261
+ * Decision logic:
3262
+ * - iOS devices (Safari): Need transcoding (no native H.265 in <video> without HLS)
3263
+ * - macOS Safari: Supports H.265 natively
3264
+ * - Chrome/Edge: Limited H.265 support, safer to transcode
3265
+ * - Firefox: No H.265 support, needs transcoding
3266
+ * - Android: Variable support, transcode for safety
3267
+ *
3268
+ * @param headers - HTTP request headers
3269
+ * @param forceMode - Optional override: "passthrough" or "transcode-h264"
3270
+ * @returns Decision with mode, reason, and client info
3271
+ */
3272
+ export declare function decideVideoclipTranscodeMode(headers: Record<string, string | string[] | undefined>, forceMode?: VideoclipTranscodeMode): VideoclipModeDecision;
3273
+
2926
3274
  export declare function decodeHeader(buf: AnyBuffer): {
2927
3275
  header: BaichuanHeader;
2928
3276
  headerLen: number;
@@ -2936,6 +3284,18 @@ export declare function decodeHeader(buf: AnyBuffer): {
2936
3284
  */
2937
3285
  export declare function deriveAesKey(nonce: string, password: string): Buffer;
2938
3286
 
3287
+ /**
3288
+ * Detect if the request is from an iOS device that needs HLS.
3289
+ *
3290
+ * @param userAgent - The User-Agent header from the request
3291
+ * @returns Object with iOS detection results
3292
+ */
3293
+ export declare function detectIosClient(userAgent: string | undefined): {
3294
+ isIos: boolean;
3295
+ isIosInstalledApp: boolean;
3296
+ needsHls: boolean;
3297
+ };
3298
+
2939
3299
  /**
2940
3300
  * Detect the actual video codec from raw NAL data.
2941
3301
  * Some cameras report wrong codec (e.g. "H264" but send H.265 data).
@@ -3350,6 +3710,8 @@ declare interface FloodlightTaskState {
3350
3710
  detectType?: string;
3351
3711
  }
3352
3712
 
3713
+ export declare function formatMjpegFrame(frame: Buffer, boundary: string): Buffer;
3714
+
3353
3715
  /**
3354
3716
  * FTP task configuration.
3355
3717
  */
@@ -3378,6 +3740,8 @@ export declare function getGlobalLogger(): Logger_2;
3378
3740
  */
3379
3741
  export declare function getH265NalType(nalPayload: Buffer): number | null;
3380
3742
 
3743
+ export declare function getMjpegContentType(boundary: string): string;
3744
+
3381
3745
  /**
3382
3746
  * Result of getRecordingVideo() - a fully muxed MP4 with stats.
3383
3747
  */
@@ -3416,6 +3780,11 @@ export declare interface GetRecordingVideoStats {
3416
3780
  hasAudio: boolean;
3417
3781
  }
3418
3782
 
3783
+ /**
3784
+ * Extract client info from HTTP request headers.
3785
+ */
3786
+ export declare function getVideoclipClientInfo(headers: Record<string, string | string[] | undefined>): VideoclipClientInfo;
3787
+
3419
3788
  /**
3420
3789
  * Parameters for getVideoclips() recording search.
3421
3790
  */
@@ -3539,6 +3908,145 @@ export declare interface HddInfoListConfig {
3539
3908
  };
3540
3909
  }
3541
3910
 
3911
+ export declare type HlsCodec = "h264" | "h265";
3912
+
3913
+ /**
3914
+ * HTTP response result.
3915
+ */
3916
+ export declare interface HlsHttpResponse {
3917
+ /** HTTP status code */
3918
+ statusCode: number;
3919
+ /** Response headers */
3920
+ headers: Record<string, string>;
3921
+ /** Response body (string for playlist, Buffer for segment) */
3922
+ body: string | Buffer;
3923
+ }
3924
+
3925
+ export declare interface HlsServerStatus {
3926
+ state: "idle" | "starting" | "running" | "stopping" | "stopped" | "error";
3927
+ codec: HlsCodec | null;
3928
+ framesReceived: number;
3929
+ ffmpegRunning: boolean;
3930
+ playlistPath: string | null;
3931
+ outputDir: string | null;
3932
+ startedAt: Date | null;
3933
+ error: string | null;
3934
+ }
3935
+
3936
+ /**
3937
+ * HLS session returned by createRecordingReplayHlsSession.
3938
+ */
3939
+ export declare interface HlsSession {
3940
+ /** Get the current HLS playlist content (.m3u8) */
3941
+ getPlaylist: () => string;
3942
+ /** Get a segment file by name */
3943
+ getSegment: (name: string) => Buffer | undefined;
3944
+ /** List all available segment names */
3945
+ listSegments: () => string[];
3946
+ /** Wait for the HLS session to be ready */
3947
+ waitForReady: () => Promise<void>;
3948
+ /** Stop the HLS session and cleanup */
3949
+ stop: () => Promise<void>;
3950
+ /** Path to the temporary directory */
3951
+ tempDir: string;
3952
+ }
3953
+
3954
+ /**
3955
+ * Manages HLS sessions with caching, TTL, and HTTP response generation.
3956
+ */
3957
+ export declare class HlsSessionManager {
3958
+ private readonly api;
3959
+ private sessions;
3960
+ private readonly logger;
3961
+ private readonly sessionTtlMs;
3962
+ private cleanupTimer;
3963
+ private creationLocks;
3964
+ constructor(api: ReolinkBaichuanApi, options?: HlsSessionManagerOptions);
3965
+ /**
3966
+ * Handle an HLS request and return the HTTP response.
3967
+ *
3968
+ * @param params - Request parameters
3969
+ * @returns HTTP response ready to be sent
3970
+ */
3971
+ handleRequest(params: {
3972
+ /** Unique session key (e.g., `${deviceId}:${fileId}`) */
3973
+ sessionKey: string;
3974
+ /** HLS path: "playlist.m3u8" or segment name like "segment_001.ts" */
3975
+ hlsPath: string;
3976
+ /** Full request URL for rewriting playlist URLs */
3977
+ requestUrl: string;
3978
+ /** Function to create session params if session doesn't exist */
3979
+ createSession: () => Promise<HlsSessionParams> | HlsSessionParams;
3980
+ /**
3981
+ * Optional prefix used to ensure only one active HLS session per logical client.
3982
+ * When a new session is created, any other sessions whose keys start with this
3983
+ * prefix will be stopped. This prevents replay/ffmpeg queue starvation when
3984
+ * clients quickly switch clips.
3985
+ */
3986
+ exclusiveKeyPrefix?: string;
3987
+ }): Promise<HlsHttpResponse>;
3988
+ private withCreationLock;
3989
+ /**
3990
+ * Check if a session exists for the given key.
3991
+ */
3992
+ hasSession(sessionKey: string): boolean;
3993
+ /**
3994
+ * Stop a specific session.
3995
+ */
3996
+ stopSession(sessionKey: string): Promise<void>;
3997
+ /**
3998
+ * Stop all sessions and cleanup.
3999
+ */
4000
+ stopAll(): Promise<void>;
4001
+ /**
4002
+ * Get the number of active sessions.
4003
+ */
4004
+ get sessionCount(): number;
4005
+ /**
4006
+ * Serve the HLS playlist with rewritten segment URLs.
4007
+ */
4008
+ private servePlaylist;
4009
+ /**
4010
+ * Serve an HLS segment.
4011
+ */
4012
+ private serveSegment;
4013
+ /**
4014
+ * Cleanup expired sessions.
4015
+ */
4016
+ private cleanupExpiredSessions;
4017
+ private stopOtherSessionsWithPrefix;
4018
+ }
4019
+
4020
+ /**
4021
+ * Options for HlsSessionManager constructor.
4022
+ */
4023
+ export declare interface HlsSessionManagerOptions {
4024
+ /** Logger instance */
4025
+ logger?: Logger_2;
4026
+ /** Session TTL in milliseconds (default: 5 minutes) */
4027
+ sessionTtlMs?: number;
4028
+ /** Cleanup interval in milliseconds (default: 30 seconds) */
4029
+ cleanupIntervalMs?: number;
4030
+ }
4031
+
4032
+ /**
4033
+ * Parameters for creating a new HLS session.
4034
+ */
4035
+ export declare interface HlsSessionParams {
4036
+ /** Channel number */
4037
+ channel: number;
4038
+ /** Recording file name/path */
4039
+ fileName: string;
4040
+ /** Whether this is an NVR recording */
4041
+ isNvr?: boolean;
4042
+ /** External device ID for dedicated socket */
4043
+ deviceId?: string;
4044
+ /** Transcode H.265 to H.264 */
4045
+ transcodeH265ToH264?: boolean;
4046
+ /** HLS segment duration in seconds */
4047
+ hlsSegmentDuration?: number;
4048
+ }
4049
+
3542
4050
  /**
3543
4051
  * Intercom - Two-way audio support for Reolink cameras via Baichuan protocol.
3544
4052
  *
@@ -3651,6 +4159,9 @@ declare interface Logger_2 {
3651
4159
  child?(tag: string): Logger_2;
3652
4160
  }
3653
4161
 
4162
+ /** Logger callback type for MjpegTransformer */
4163
+ export declare type LoggerCallback = (level: "debug" | "info" | "warn" | "error", message: string) => void;
4164
+
3654
4165
  declare type LoggerLike = Partial<Logger_2> | Console;
3655
4166
 
3656
4167
  export declare type LoginResponseValue = {
@@ -3696,6 +4207,74 @@ export declare interface MediaStream {
3696
4207
  };
3697
4208
  }
3698
4209
 
4210
+ export declare interface MjpegFrame {
4211
+ /** JPEG data */
4212
+ data: Buffer;
4213
+ /** Timestamp in microseconds */
4214
+ timestamp: number;
4215
+ }
4216
+
4217
+ /**
4218
+ * MjpegTransformer - Transforms H.264/H.265 access units to JPEG frames
4219
+ *
4220
+ * Events:
4221
+ * - 'frame': Emitted when a JPEG frame is ready (MjpegFrame)
4222
+ * - 'error': Emitted on error
4223
+ * - 'close': Emitted when transformer is closed
4224
+ */
4225
+ export declare class MjpegTransformer extends EventEmitter {
4226
+ private readonly options;
4227
+ private ffmpeg;
4228
+ private started;
4229
+ private closed;
4230
+ private jpegBuffer;
4231
+ private frameCount;
4232
+ private lastTimestamp;
4233
+ constructor(options: MjpegTransformerOptions);
4234
+ /**
4235
+ * Start the transformer (spawns FFmpeg process)
4236
+ */
4237
+ start(): void;
4238
+ /**
4239
+ * Push an H.264/H.265 access unit (Annex-B format with start codes)
4240
+ */
4241
+ push(accessUnit: Buffer, timestamp?: number): void;
4242
+ /**
4243
+ * Handle JPEG data from FFmpeg stdout
4244
+ * FFmpeg outputs complete JPEG images, each starting with SOI (0xFFD8)
4245
+ * and ending with EOI (0xFFD9)
4246
+ */
4247
+ private handleJpegData;
4248
+ /**
4249
+ * Stop the transformer
4250
+ */
4251
+ stop(): Promise<void>;
4252
+ /**
4253
+ * Get frame count
4254
+ */
4255
+ getFrameCount(): number;
4256
+ /**
4257
+ * Check if running
4258
+ */
4259
+ isRunning(): boolean;
4260
+ private log;
4261
+ }
4262
+
4263
+ export declare interface MjpegTransformerOptions {
4264
+ /** Video codec (required) */
4265
+ codec: "h264" | "h265";
4266
+ /** JPEG quality (1-31, lower is better, default: 5) */
4267
+ quality?: number | undefined;
4268
+ /** Output width (optional, maintains aspect ratio if only one dimension set) */
4269
+ width?: number | undefined;
4270
+ /** Output height (optional, maintains aspect ratio if only one dimension set) */
4271
+ height?: number | undefined;
4272
+ /** Frame rate limit (optional, e.g., 10 for max 10 fps) */
4273
+ maxFps?: number | undefined;
4274
+ /** Logger callback */
4275
+ logger?: LoggerCallback | undefined;
4276
+ }
4277
+
3699
4278
  /**
3700
4279
  * Motion alarm configuration (getMotionAlarm response).
3701
4280
  * cmdId=46 (GetMdAlarm)
@@ -3831,6 +4410,8 @@ export declare interface ParsedRecordingFileName {
3831
4410
  durationMs: number;
3832
4411
  /** Frame rate extracted from filename hex flags (if available) */
3833
4412
  framerate?: number;
4413
+ /** File size in bytes extracted from filename (last hex field before extension) */
4414
+ sizeBytes?: number;
3834
4415
  flags?: RecordingVodFlags;
3835
4416
  rawFlags?: Record<string, number>;
3836
4417
  animalTypeRaw?: string;
@@ -4055,6 +4636,8 @@ export declare class ReolinkBaichuanApi {
4055
4636
  * Value: client, refCount, createdAt
4056
4637
  */
4057
4638
  private readonly dedicatedClients;
4639
+ /** Keep replay dedicated sockets warm briefly to reduce clip switch latency. */
4640
+ private static readonly REPLAY_DEDICATED_KEEPALIVE_MS;
4058
4641
  /**
4059
4642
  * Get a summary of currently active dedicated sessions.
4060
4643
  * Useful for debugging/logging to see how many sockets are open.
@@ -4176,6 +4759,17 @@ export declare class ReolinkBaichuanApi {
4176
4759
  * This ensures clean teardown at the end of each clip.
4177
4760
  */
4178
4761
  private releaseDedicatedClient;
4762
+ /**
4763
+ * Force-close a dedicated client if it exists.
4764
+ * This is called BEFORE entering the queue to immediately terminate any existing stream
4765
+ * for the same sessionKey. The existing stream will receive an error, release its queue slot,
4766
+ * and the new request can then proceed.
4767
+ *
4768
+ * @param sessionKey - The session key to force-close (e.g., `replay:${deviceId}`)
4769
+ * @param logger - Optional logger
4770
+ * @returns true if a client was closed, false if no client existed
4771
+ */
4772
+ private forceCloseDedicatedClient;
4179
4773
  /**
4180
4774
  * Create a dedicated Baichuan client session for streaming.
4181
4775
  * This is useful for consumers that need isolated socket connections per stream.
@@ -5417,11 +6011,13 @@ export declare class ReolinkBaichuanApi {
5417
6011
  * @param settings - Floodlight settings to apply
5418
6012
  *
5419
6013
  * @example
6014
+ * ```typescript
5420
6015
  * await api.setFloodlightSettings(0, {
5421
6016
  * duration: 300, // 5 minutes
5422
6017
  * detectType: 'people,vehicle',
5423
6018
  * brightness: 80,
5424
6019
  * });
6020
+ * ```
5425
6021
  */
5426
6022
  setFloodlightSettings(channel: number | undefined, settings: {
5427
6023
  duration?: number;
@@ -6024,6 +6620,20 @@ export declare class ReolinkBaichuanApi {
6024
6620
  * Recommended: pass a unique identifier per logical device/player instance.
6025
6621
  */
6026
6622
  deviceId?: string;
6623
+ /**
6624
+ * Transcode H.265/HEVC to H.264/AVC for compatibility with clients that don't support H.265.
6625
+ * When true and the source is H.265, ffmpeg will transcode to H.264 using libx264.
6626
+ * This increases CPU usage but ensures playback on iOS Safari, older browsers, etc.
6627
+ * Default: false (passthrough/copy).
6628
+ */
6629
+ transcodeH265ToH264?: boolean;
6630
+ /**
6631
+ * Use MPEG-TS muxer to preserve frame timestamps (PTS).
6632
+ * When true, frames are muxed into MPEG-TS before being passed to ffmpeg.
6633
+ * This can help with variable framerate streams but may cause issues with some decoders.
6634
+ * Default: true (MPEG-TS muxing for proper timestamp alignment).
6635
+ */
6636
+ useMpegTsMuxer?: boolean;
6027
6637
  }): Promise<{
6028
6638
  mp4: Readable;
6029
6639
  stop: () => Promise<void>;
@@ -6067,6 +6677,98 @@ export declare class ReolinkBaichuanApi {
6067
6677
  mp4: Readable;
6068
6678
  stop: () => Promise<void>;
6069
6679
  }>;
6680
+ /**
6681
+ * Create an HLS (HTTP Live Streaming) session for a recording.
6682
+ *
6683
+ * This method creates HLS segments on-the-fly from a recording replay stream.
6684
+ * HLS is required for iOS devices (Safari, Home app) which don't support
6685
+ * fragmented MP4 streaming well and require Range request support.
6686
+ *
6687
+ * The session writes HLS segments (.ts files) and playlist (.m3u8) to a
6688
+ * temporary directory. You must serve these files via HTTP to the client.
6689
+ *
6690
+ * @example
6691
+ * ```ts
6692
+ * const session = await api.createRecordingReplayHlsSession({
6693
+ * channel: 0,
6694
+ * fileName: "/mnt/sda/Mp4Record/2026-01-25/RecS03.mp4",
6695
+ * });
6696
+ *
6697
+ * // Serve playlist
6698
+ * app.get('/clip.m3u8', (req, res) => {
6699
+ * res.type('application/vnd.apple.mpegurl');
6700
+ * res.send(session.getPlaylist());
6701
+ * });
6702
+ *
6703
+ * // Serve segments
6704
+ * app.get('/segment/:name', (req, res) => {
6705
+ * const data = session.getSegment(req.params.name);
6706
+ * if (data) {
6707
+ * res.type('video/mp2t');
6708
+ * res.send(data);
6709
+ * } else {
6710
+ * res.status(404).end();
6711
+ * }
6712
+ * });
6713
+ *
6714
+ * // Cleanup when done
6715
+ * await session.stop();
6716
+ * ```
6717
+ */
6718
+ createRecordingReplayHlsSession(params: {
6719
+ /** Channel number (0-based). Required. */
6720
+ channel: number;
6721
+ /** Full path to the recording file. Required. */
6722
+ fileName: string;
6723
+ /**
6724
+ * Force NVR mode (uses id-based XML with UID) or standalone mode (name-based XML).
6725
+ * If not specified, the library will detect based on device channel count.
6726
+ */
6727
+ isNvr?: boolean;
6728
+ /** Optional logger override. If not provided, uses the API's logger. */
6729
+ logger?: Logger;
6730
+ /**
6731
+ * External identifier for the dedicated socket session.
6732
+ * When provided, a dedicated BaichuanClient is created/reused for this deviceId.
6733
+ */
6734
+ deviceId?: string;
6735
+ /**
6736
+ * Transcode H.265/HEVC to H.264/AVC for compatibility.
6737
+ * Default: false (passthrough).
6738
+ */
6739
+ transcodeH265ToH264?: boolean;
6740
+ /**
6741
+ * HLS segment duration in seconds. Default: 4.
6742
+ */
6743
+ hlsSegmentDuration?: number;
6744
+ }): Promise<{
6745
+ /**
6746
+ * Get the current HLS playlist content (.m3u8).
6747
+ * Call this to serve the playlist to the client.
6748
+ */
6749
+ getPlaylist: () => string;
6750
+ /**
6751
+ * Get a segment file by name.
6752
+ * Returns undefined if the segment doesn't exist yet.
6753
+ */
6754
+ getSegment: (name: string) => Buffer | undefined;
6755
+ /**
6756
+ * List all available segment names.
6757
+ */
6758
+ listSegments: () => string[];
6759
+ /**
6760
+ * Wait for the HLS session to be ready (at least one segment available).
6761
+ */
6762
+ waitForReady: () => Promise<void>;
6763
+ /**
6764
+ * Stop the HLS session and cleanup.
6765
+ */
6766
+ stop: () => Promise<void>;
6767
+ /**
6768
+ * Path to the temporary directory containing HLS files.
6769
+ */
6770
+ tempDir: string;
6771
+ }>;
6070
6772
  /**
6071
6773
  * List recordings from a standalone camera.
6072
6774
  *
@@ -7547,6 +8249,28 @@ export declare interface TwoWayAudioConfig {
7547
8249
  mode?: "mixAudioStream" | string;
7548
8250
  }
7549
8251
 
8252
+ /**
8253
+ * Client information extracted from HTTP request headers.
8254
+ * Used to determine optimal video delivery format.
8255
+ */
8256
+ export declare type VideoclipClientInfo = {
8257
+ userAgent: string | undefined;
8258
+ accept: string | undefined;
8259
+ range: string | undefined;
8260
+ secChUa: string | undefined;
8261
+ secChUaMobile: string | undefined;
8262
+ secChUaPlatform: string | undefined;
8263
+ };
8264
+
8265
+ /**
8266
+ * Result of videoclip mode decision.
8267
+ */
8268
+ export declare type VideoclipModeDecision = {
8269
+ mode: VideoclipTranscodeMode;
8270
+ reason: string;
8271
+ clientInfo: VideoclipClientInfo;
8272
+ };
8273
+
7550
8274
  export declare type VideoclipThumbnailResult = {
7551
8275
  /** Raw I-frame data (H.264 or H.265) */
7552
8276
  frame: Buffer;
@@ -7560,6 +8284,13 @@ export declare type VideoclipThumbnailResult = {
7560
8284
  streamInfo: PlaybackSnapshotStreamInfo;
7561
8285
  };
7562
8286
 
8287
+ /**
8288
+ * Videoclip delivery mode.
8289
+ * - `passthrough`: Copy codec as-is (H.264 or H.265)
8290
+ * - `transcode-h264`: Transcode H.265 to H.264 for compatibility
8291
+ */
8292
+ export declare type VideoclipTranscodeMode = "passthrough" | "transcode-h264";
8293
+
7563
8294
  export declare type VideoCodec = "H.264" | "H.265" | "MJPEG" | "MPEG4" | string;
7564
8295
 
7565
8296
  /**
@@ -7643,7 +8374,8 @@ export declare type VodFile = {
7643
8374
  sec: number;
7644
8375
  };
7645
8376
  name: string;
7646
- size: number;
8377
+ /** File size in bytes - API may return as string or number */
8378
+ size: number | string;
7647
8379
  };
7648
8380
 
7649
8381
  export declare type VodSearchResponse = ReolinkCmdResponseExt<VodSearchResult> & {
@@ -7684,6 +8416,34 @@ export declare type WakeUpOptions = {
7684
8416
  reconnect?: boolean;
7685
8417
  };
7686
8418
 
8419
+ export declare interface WebRTCAnswer {
8420
+ sdp: string;
8421
+ type: "answer";
8422
+ }
8423
+
8424
+ export declare interface WebRTCIceCandidate {
8425
+ candidate: string;
8426
+ sdpMid?: string;
8427
+ sdpMLineIndex?: number;
8428
+ }
8429
+
8430
+ export declare interface WebRTCOffer {
8431
+ sdp: string;
8432
+ type: "offer";
8433
+ }
8434
+
8435
+ export declare interface WebRTCSessionInfo {
8436
+ id: string;
8437
+ state: "connecting" | "connected" | "disconnected" | "failed";
8438
+ createdAt: Date;
8439
+ stats: {
8440
+ videoFrames: number;
8441
+ audioFrames: number;
8442
+ bytesSent: number;
8443
+ intercomBytesSent: number;
8444
+ };
8445
+ }
8446
+
7687
8447
  /**
7688
8448
  * White LED state configuration.
7689
8449
  */