@apocaliss92/nodelink-js 0.1.7 → 0.1.8

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
@@ -980,6 +980,106 @@ export declare type BaichuanLedState = {
980
980
  lightState?: string;
981
981
  };
982
982
 
983
+ /**
984
+ * BaichuanMjpegServer - MJPEG HTTP server for Baichuan video streams
985
+ *
986
+ * Events:
987
+ * - 'started': Server started
988
+ * - 'stopped': Server stopped
989
+ * - 'client-connected': Client connected
990
+ * - 'client-disconnected': Client disconnected
991
+ * - 'error': Error occurred
992
+ */
993
+ export declare class BaichuanMjpegServer extends EventEmitter {
994
+ private readonly options;
995
+ private readonly clients;
996
+ private httpServer;
997
+ private transformer;
998
+ private nativeStream;
999
+ private streamPump;
1000
+ private detectedCodec;
1001
+ private started;
1002
+ private clientIdCounter;
1003
+ constructor(options: BaichuanMjpegServerOptions);
1004
+ /**
1005
+ * Start the MJPEG server
1006
+ */
1007
+ start(): Promise<void>;
1008
+ /**
1009
+ * Stop the MJPEG server
1010
+ */
1011
+ stop(): Promise<void>;
1012
+ /**
1013
+ * Handle HTTP request
1014
+ */
1015
+ private handleRequest;
1016
+ /**
1017
+ * Handle new MJPEG client
1018
+ */
1019
+ private handleMjpegClient;
1020
+ /**
1021
+ * Start the native video stream and MJPEG transformer
1022
+ */
1023
+ private startStream;
1024
+ /**
1025
+ * Pump native stream and feed to transformer
1026
+ */
1027
+ private pumpStream;
1028
+ /**
1029
+ * Initialize MJPEG transformer once codec is detected
1030
+ */
1031
+ private initTransformer;
1032
+ /**
1033
+ * Broadcast JPEG frame to all connected clients
1034
+ */
1035
+ private broadcastFrame;
1036
+ /**
1037
+ * Stop the stream and transformer
1038
+ */
1039
+ private stopStream;
1040
+ /**
1041
+ * Get current number of connected clients
1042
+ */
1043
+ getClientCount(): number;
1044
+ /**
1045
+ * Get server status
1046
+ */
1047
+ getStatus(): {
1048
+ running: boolean;
1049
+ clients: number;
1050
+ codec: string | null;
1051
+ frames: number;
1052
+ };
1053
+ private log;
1054
+ }
1055
+
1056
+ export declare interface BaichuanMjpegServerOptions {
1057
+ /** API instance (required) */
1058
+ api: ReolinkBaichuanApi;
1059
+ /** Channel number (required) */
1060
+ channel: number;
1061
+ /** Stream profile (required) */
1062
+ profile: StreamProfile;
1063
+ /** Native-only: TrackMix tele/autotrack variants */
1064
+ variant?: NativeVideoStreamVariant;
1065
+ /** HTTP server port (default: 8080) */
1066
+ port?: number;
1067
+ /** HTTP server host (default: "0.0.0.0") */
1068
+ host?: string;
1069
+ /** URL path for MJPEG stream (default: "/mjpeg") */
1070
+ path?: string;
1071
+ /** JPEG quality (1-31, lower is better, default: 5) */
1072
+ quality?: number;
1073
+ /** Output width (optional) */
1074
+ width?: number;
1075
+ /** Output height (optional) */
1076
+ height?: number;
1077
+ /** Max FPS (optional) */
1078
+ maxFps?: number;
1079
+ /** Logger callback */
1080
+ logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
1081
+ }
1082
+
983
1083
  export declare type BaichuanNetInfoPush = {
984
1084
  netType?: string;
985
1085
  signal?: number;
@@ -1376,6 +1476,125 @@ export declare interface BaichuanVideoStreamOptions {
1376
1476
  acceptAnyStreamType?: boolean;
1377
1477
  }
1378
1478
 
1479
+ /**
1480
+ * BaichuanWebRTCServer - WebRTC server for Baichuan video streams
1481
+ *
1482
+ * Events:
1483
+ * - 'session-created': New session created
1484
+ * - 'session-connected': Session connected (ICE complete)
1485
+ * - 'session-closed': Session closed
1486
+ * - 'intercom-started': Intercom started for session
1487
+ * - 'intercom-stopped': Intercom stopped for session
1488
+ * - 'error': Error occurred
1489
+ */
1490
+ export declare class BaichuanWebRTCServer extends EventEmitter {
1491
+ private readonly options;
1492
+ private readonly sessions;
1493
+ private sessionIdCounter;
1494
+ private weriftModule;
1495
+ constructor(options: BaichuanWebRTCServerOptions);
1496
+ /**
1497
+ * Initialize werift module (lazy load to avoid requiring it if not used)
1498
+ */
1499
+ private loadWerift;
1500
+ /**
1501
+ * Create a new WebRTC session
1502
+ * Returns a session ID and SDP offer to send to the browser
1503
+ */
1504
+ createSession(): Promise<{
1505
+ sessionId: string;
1506
+ offer: WebRTCOffer;
1507
+ }>;
1508
+ /**
1509
+ * Handle WebRTC answer from browser and start streaming
1510
+ */
1511
+ handleAnswer(sessionId: string, answer: WebRTCAnswer): Promise<void>;
1512
+ /**
1513
+ * Add ICE candidate from browser
1514
+ */
1515
+ addIceCandidate(sessionId: string, candidate: WebRTCIceCandidate): Promise<void>;
1516
+ /**
1517
+ * Close a WebRTC session
1518
+ */
1519
+ closeSession(sessionId: string): Promise<void>;
1520
+ /**
1521
+ * Get information about all active sessions
1522
+ */
1523
+ getSessions(): WebRTCSessionInfo[];
1524
+ /**
1525
+ * Get information about a specific session
1526
+ */
1527
+ getSession(sessionId: string): WebRTCSessionInfo | null;
1528
+ /**
1529
+ * Close all sessions and stop the server
1530
+ */
1531
+ stop(): Promise<void>;
1532
+ /**
1533
+ * Get the number of active sessions
1534
+ */
1535
+ get sessionCount(): number;
1536
+ /**
1537
+ * Wait for ICE gathering to complete
1538
+ */
1539
+ private waitForIceGathering;
1540
+ /**
1541
+ * Start native Baichuan stream and pump frames to WebRTC
1542
+ */
1543
+ private startNativeStream;
1544
+ /**
1545
+ * Pump frames from native stream to WebRTC tracks
1546
+ * H.264 → RTP media track (standard WebRTC)
1547
+ * H.265 → DataChannel with raw Annex-B frames (decoded by WebCodecs in browser)
1548
+ */
1549
+ private pumpFramesToWebRTC;
1550
+ /**
1551
+ * Send H.264 frame via RTP media track
1552
+ */
1553
+ private sendH264Frame;
1554
+ /**
1555
+ * Send H.265 frame via DataChannel
1556
+ * Format: 12-byte header + Annex-B data
1557
+ * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]
1558
+ */
1559
+ private sendH265Frame;
1560
+ /**
1561
+ * Create RTP packets for H.264 NAL unit
1562
+ * Handles single NAL, STAP-A aggregation, and FU-A fragmentation
1563
+ */
1564
+ private createH264RtpPackets;
1565
+ /**
1566
+ * Start intercom (two-way audio)
1567
+ */
1568
+ private startIntercom;
1569
+ /**
1570
+ * Log helper
1571
+ */
1572
+ private log;
1573
+ }
1574
+
1575
+ export declare interface BaichuanWebRTCServerOptions {
1576
+ /** API instance (required) */
1577
+ api: ReolinkBaichuanApi;
1578
+ /** Channel number (required) */
1579
+ channel: number;
1580
+ /** Stream profile (required) */
1581
+ profile: StreamProfile;
1582
+ /** Native-only: TrackMix tele/autotrack variants */
1583
+ variant?: NativeVideoStreamVariant;
1584
+ /** Enable two-way audio intercom (default: false) */
1585
+ enableIntercom?: boolean;
1586
+ /** STUN servers for ICE (default: Google STUN) */
1587
+ stunServers?: string[];
1588
+ /** TURN servers for NAT traversal (optional) */
1589
+ turnServers?: Array<{
1590
+ urls: string;
1591
+ username?: string;
1592
+ credential?: string;
1593
+ }>;
1594
+ /** Logger callback */
1595
+ logger?: (level: "debug" | "info" | "warn" | "error", message: string) => void;
1596
+ }
1597
+
1379
1598
  export declare type BaichuanWifi = {
1380
1599
  protocol?: number;
1381
1600
  mode?: string;
@@ -2791,6 +3010,25 @@ export declare function createLogger(options?: {
2791
3010
  tag?: string;
2792
3011
  }): Logger_2;
2793
3012
 
3013
+ /**
3014
+ * Create an MJPEG HTTP response handler
3015
+ *
3016
+ * Usage:
3017
+ * ```ts
3018
+ * const transformer = new MjpegTransformer({ codec: "h264" });
3019
+ * transformer.start();
3020
+ *
3021
+ * // In your stream handler:
3022
+ * stream.on("accessUnit", (au) => transformer.push(au.data, au.timestamp));
3023
+ *
3024
+ * // HTTP handler:
3025
+ * app.get("/mjpeg", (req, res) => {
3026
+ * serveMjpeg(res, transformer);
3027
+ * });
3028
+ * ```
3029
+ */
3030
+ export declare function createMjpegBoundary(): string;
3031
+
2794
3032
  /**
2795
3033
  * Stream frame data for rebroadcast.
2796
3034
  * Similar to Wyze forkAndStream() implementation.
@@ -2923,6 +3161,22 @@ export declare type DebugOptions = {
2923
3161
  };
2924
3162
  };
2925
3163
 
3164
+ /**
3165
+ * Determine if H.265 should be transcoded to H.264 based on client capabilities.
3166
+ *
3167
+ * Decision logic:
3168
+ * - iOS devices (Safari): Need transcoding (no native H.265 in <video> without HLS)
3169
+ * - macOS Safari: Supports H.265 natively
3170
+ * - Chrome/Edge: Limited H.265 support, safer to transcode
3171
+ * - Firefox: No H.265 support, needs transcoding
3172
+ * - Android: Variable support, transcode for safety
3173
+ *
3174
+ * @param headers HTTP request headers
3175
+ * @param forceMode Optional override: "passthrough" or "transcode-h264"
3176
+ * @returns Decision with mode, reason, and client info
3177
+ */
3178
+ export declare function decideVideoclipTranscodeMode(headers: Record<string, string | string[] | undefined>, forceMode?: VideoclipTranscodeMode): VideoclipModeDecision;
3179
+
2926
3180
  export declare function decodeHeader(buf: AnyBuffer): {
2927
3181
  header: BaichuanHeader;
2928
3182
  headerLen: number;
@@ -3350,6 +3604,8 @@ declare interface FloodlightTaskState {
3350
3604
  detectType?: string;
3351
3605
  }
3352
3606
 
3607
+ export declare function formatMjpegFrame(frame: Buffer, boundary: string): Buffer;
3608
+
3353
3609
  /**
3354
3610
  * FTP task configuration.
3355
3611
  */
@@ -3378,6 +3634,8 @@ export declare function getGlobalLogger(): Logger_2;
3378
3634
  */
3379
3635
  export declare function getH265NalType(nalPayload: Buffer): number | null;
3380
3636
 
3637
+ export declare function getMjpegContentType(boundary: string): string;
3638
+
3381
3639
  /**
3382
3640
  * Result of getRecordingVideo() - a fully muxed MP4 with stats.
3383
3641
  */
@@ -3416,6 +3674,11 @@ export declare interface GetRecordingVideoStats {
3416
3674
  hasAudio: boolean;
3417
3675
  }
3418
3676
 
3677
+ /**
3678
+ * Extract client info from HTTP request headers.
3679
+ */
3680
+ export declare function getVideoclipClientInfo(headers: Record<string, string | string[] | undefined>): VideoclipClientInfo;
3681
+
3419
3682
  /**
3420
3683
  * Parameters for getVideoclips() recording search.
3421
3684
  */
@@ -3651,6 +3914,9 @@ declare interface Logger_2 {
3651
3914
  child?(tag: string): Logger_2;
3652
3915
  }
3653
3916
 
3917
+ /** Logger callback type for MjpegTransformer */
3918
+ export declare type LoggerCallback = (level: "debug" | "info" | "warn" | "error", message: string) => void;
3919
+
3654
3920
  declare type LoggerLike = Partial<Logger_2> | Console;
3655
3921
 
3656
3922
  export declare type LoginResponseValue = {
@@ -3696,6 +3962,74 @@ export declare interface MediaStream {
3696
3962
  };
3697
3963
  }
3698
3964
 
3965
+ export declare interface MjpegFrame {
3966
+ /** JPEG data */
3967
+ data: Buffer;
3968
+ /** Timestamp in microseconds */
3969
+ timestamp: number;
3970
+ }
3971
+
3972
+ /**
3973
+ * MjpegTransformer - Transforms H.264/H.265 access units to JPEG frames
3974
+ *
3975
+ * Events:
3976
+ * - 'frame': Emitted when a JPEG frame is ready (MjpegFrame)
3977
+ * - 'error': Emitted on error
3978
+ * - 'close': Emitted when transformer is closed
3979
+ */
3980
+ export declare class MjpegTransformer extends EventEmitter {
3981
+ private readonly options;
3982
+ private ffmpeg;
3983
+ private started;
3984
+ private closed;
3985
+ private jpegBuffer;
3986
+ private frameCount;
3987
+ private lastTimestamp;
3988
+ constructor(options: MjpegTransformerOptions);
3989
+ /**
3990
+ * Start the transformer (spawns FFmpeg process)
3991
+ */
3992
+ start(): void;
3993
+ /**
3994
+ * Push an H.264/H.265 access unit (Annex-B format with start codes)
3995
+ */
3996
+ push(accessUnit: Buffer, timestamp?: number): void;
3997
+ /**
3998
+ * Handle JPEG data from FFmpeg stdout
3999
+ * FFmpeg outputs complete JPEG images, each starting with SOI (0xFFD8)
4000
+ * and ending with EOI (0xFFD9)
4001
+ */
4002
+ private handleJpegData;
4003
+ /**
4004
+ * Stop the transformer
4005
+ */
4006
+ stop(): Promise<void>;
4007
+ /**
4008
+ * Get frame count
4009
+ */
4010
+ getFrameCount(): number;
4011
+ /**
4012
+ * Check if running
4013
+ */
4014
+ isRunning(): boolean;
4015
+ private log;
4016
+ }
4017
+
4018
+ export declare interface MjpegTransformerOptions {
4019
+ /** Video codec (required) */
4020
+ codec: "h264" | "h265";
4021
+ /** JPEG quality (1-31, lower is better, default: 5) */
4022
+ quality?: number | undefined;
4023
+ /** Output width (optional, maintains aspect ratio if only one dimension set) */
4024
+ width?: number | undefined;
4025
+ /** Output height (optional, maintains aspect ratio if only one dimension set) */
4026
+ height?: number | undefined;
4027
+ /** Frame rate limit (optional, e.g., 10 for max 10 fps) */
4028
+ maxFps?: number | undefined;
4029
+ /** Logger callback */
4030
+ logger?: LoggerCallback | undefined;
4031
+ }
4032
+
3699
4033
  /**
3700
4034
  * Motion alarm configuration (getMotionAlarm response).
3701
4035
  * cmdId=46 (GetMdAlarm)
@@ -6024,6 +6358,13 @@ export declare class ReolinkBaichuanApi {
6024
6358
  * Recommended: pass a unique identifier per logical device/player instance.
6025
6359
  */
6026
6360
  deviceId?: string;
6361
+ /**
6362
+ * Transcode H.265/HEVC to H.264/AVC for compatibility with clients that don't support H.265.
6363
+ * When true and the source is H.265, ffmpeg will transcode to H.264 using libx264.
6364
+ * This increases CPU usage but ensures playback on iOS Safari, older browsers, etc.
6365
+ * Default: false (passthrough/copy).
6366
+ */
6367
+ transcodeH265ToH264?: boolean;
6027
6368
  }): Promise<{
6028
6369
  mp4: Readable;
6029
6370
  stop: () => Promise<void>;
@@ -7547,6 +7888,28 @@ export declare interface TwoWayAudioConfig {
7547
7888
  mode?: "mixAudioStream" | string;
7548
7889
  }
7549
7890
 
7891
+ /**
7892
+ * Client information extracted from HTTP request headers.
7893
+ * Used to determine optimal video delivery format.
7894
+ */
7895
+ export declare type VideoclipClientInfo = {
7896
+ userAgent: string | undefined;
7897
+ accept: string | undefined;
7898
+ range: string | undefined;
7899
+ secChUa: string | undefined;
7900
+ secChUaMobile: string | undefined;
7901
+ secChUaPlatform: string | undefined;
7902
+ };
7903
+
7904
+ /**
7905
+ * Result of videoclip mode decision.
7906
+ */
7907
+ export declare type VideoclipModeDecision = {
7908
+ mode: VideoclipTranscodeMode;
7909
+ reason: string;
7910
+ clientInfo: VideoclipClientInfo;
7911
+ };
7912
+
7550
7913
  export declare type VideoclipThumbnailResult = {
7551
7914
  /** Raw I-frame data (H.264 or H.265) */
7552
7915
  frame: Buffer;
@@ -7560,6 +7923,13 @@ export declare type VideoclipThumbnailResult = {
7560
7923
  streamInfo: PlaybackSnapshotStreamInfo;
7561
7924
  };
7562
7925
 
7926
+ /**
7927
+ * Videoclip delivery mode.
7928
+ * - `passthrough`: Copy codec as-is (H.264 or H.265)
7929
+ * - `transcode-h264`: Transcode H.265 to H.264 for compatibility
7930
+ */
7931
+ export declare type VideoclipTranscodeMode = "passthrough" | "transcode-h264";
7932
+
7563
7933
  export declare type VideoCodec = "H.264" | "H.265" | "MJPEG" | "MPEG4" | string;
7564
7934
 
7565
7935
  /**
@@ -7684,6 +8054,34 @@ export declare type WakeUpOptions = {
7684
8054
  reconnect?: boolean;
7685
8055
  };
7686
8056
 
8057
+ export declare interface WebRTCAnswer {
8058
+ sdp: string;
8059
+ type: "answer";
8060
+ }
8061
+
8062
+ export declare interface WebRTCIceCandidate {
8063
+ candidate: string;
8064
+ sdpMid?: string;
8065
+ sdpMLineIndex?: number;
8066
+ }
8067
+
8068
+ export declare interface WebRTCOffer {
8069
+ sdp: string;
8070
+ type: "offer";
8071
+ }
8072
+
8073
+ export declare interface WebRTCSessionInfo {
8074
+ id: string;
8075
+ state: "connecting" | "connected" | "disconnected" | "failed";
8076
+ createdAt: Date;
8077
+ stats: {
8078
+ videoFrames: number;
8079
+ audioFrames: number;
8080
+ bytesSent: number;
8081
+ intercomBytesSent: number;
8082
+ };
8083
+ }
8084
+
7687
8085
  /**
7688
8086
  * White LED state configuration.
7689
8087
  */