@camstack/addon-pipeline 0.1.0 → 0.1.2

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.cts CHANGED
@@ -1,8 +1,6 @@
1
- export { A as AvailabilityRange, C as CleanupQueueEntry, a as CleanupStatus, D as DataCategory, P as PipelineAddon, R as RecordingCoordinator, b as RecordingDb, c as RecordingEnableConfig, d as RecordingEngineDependencies, e as RecordingEngineV2Dependencies, f as RecordingMode, g as RecordingPolicy, h as RecordingSegment, i as RecordingStorageConfig, j as RecordingThumbnail, S as ScheduleRule, k as SegmentWriterState, l as StorageEstimate, m as StorageUsage, n as StreamEstimate, o as StreamPolicy } from './addon-CwDFZWAb.cjs';
2
- import { IDecoderProvider, IStreamBroker, IScopedLogger, BrokerStatus, IRestreamer, StreamSource, EncodedPacket, Unsubscribe, DecodedFrame, DecodeOptions, DecodedAudioChunk, BrokerStats, DecoderSessionConfig, IDecoderSession, DecoderStats, IStorageLocation, SystemEvent, VideoFrame as VideoFrame$1, IStructuredStorage, IEventBus, AnalysisContext, ServerTrackedDetection, ICamstackAddon, AddonManifest, AddonContext, CapabilityProviderMap } from '@camstack/types';
1
+ export { A as AvailabilityRange, C as CleanupQueueEntry, a as CleanupStatus, D as DataCategory, P as PipelineAddon, R as RecordingCoordinator, b as RecordingDb, c as RecordingEnableConfig, d as RecordingEngineDependencies, e as RecordingEngineV2Dependencies, f as RecordingMode, g as RecordingPolicy, h as RecordingSegment, i as RecordingStorageConfig, j as RecordingThumbnail, S as ScheduleRule, k as SegmentWriterState, l as StorageEstimate, m as StorageUsage, n as StreamEstimate, o as StreamPolicy } from './addon-DK7eQ0PN.cjs';
2
+ import { IDecoderProvider, IStreamBroker, IScopedLogger, BrokerStatus, IRestreamer, StreamSource, EncodedPacket, Unsubscribe, DecodedFrame, DecodeOptions, DecodedAudioChunk, BrokerStats, DecoderSessionConfig, IDecoderSession, DecoderStats, IStorageLocation, SystemEvent, VideoFrame, IStructuredStorage, IEventBus, AnalysisContext, ServerTrackedDetection, ICamstackAddon, AddonManifest, AddonContext, CapabilityProviderMap } from '@camstack/types';
3
3
  export { BrokerStats, BrokerStatus } from '@camstack/types';
4
- import { ChildProcess } from 'node:child_process';
5
- import { EventEmitter } from 'node:events';
6
4
  import 'better-sqlite3';
7
5
 
8
6
  declare class DecoderRegistry {
@@ -231,7 +229,7 @@ declare class EventPersistenceService {
231
229
  /** Save annotated frame (thumbnail + full-res) for an event */
232
230
  saveAnnotatedFrame(eventId: string, result: AnnotatedSnapshotResult): void;
233
231
  /** Save original frame for an event */
234
- saveOriginalFrame(eventId: string, frame: VideoFrame$1): Promise<void>;
232
+ saveOriginalFrame(eventId: string, frame: VideoFrame): Promise<void>;
235
233
  /**
236
234
  * Get all media files for a specific event (from buffer or storage).
237
235
  * Returns immediately — serves from in-memory buffer if not yet flushed.
@@ -549,919 +547,4 @@ declare class BuiltinAnalysisAddon implements ICamstackAddon {
549
547
  isAvailable(): boolean;
550
548
  }
551
549
 
552
- /**
553
- * Core types for media stream servers.
554
- * Protocol-agnostic frame types used across all server implementations.
555
- */
556
- type VideoCodec = "H264" | "H265";
557
- type AudioCodec = "Aac" | "Adpcm" | "Opus" | "Pcmu" | "Pcma";
558
- /**
559
- * A single video frame in Annex-B format (with start codes).
560
- */
561
- interface VideoFrame {
562
- /** Raw video data in Annex-B format (NAL units with 0x00000001 start codes) */
563
- data: Buffer;
564
- /** Video codec type */
565
- codec: VideoCodec;
566
- /** True if this is a keyframe (IDR for H.264, IRAP for H.265) */
567
- isKeyframe: boolean;
568
- /** Presentation timestamp in microseconds (monotonic) */
569
- timestampMicros: number;
570
- /** Optional: frame width in pixels */
571
- width?: number;
572
- /** Optional: frame height in pixels */
573
- height?: number;
574
- }
575
- /**
576
- * A single audio frame.
577
- * For AAC: data should include ADTS headers.
578
- * For other codecs: raw audio samples.
579
- */
580
- interface AudioFrame {
581
- /** Raw audio data */
582
- data: Buffer;
583
- /** Audio codec */
584
- codec: AudioCodec;
585
- /** Sample rate in Hz */
586
- sampleRate: number;
587
- /** Number of channels */
588
- channels: number;
589
- /** Presentation timestamp in microseconds */
590
- timestampMicros: number;
591
- }
592
- /**
593
- * Tagged union for video/audio frames.
594
- */
595
- type MediaFrame = {
596
- type: "video";
597
- frame: VideoFrame;
598
- } | {
599
- type: "audio";
600
- frame: AudioFrame;
601
- };
602
- /**
603
- * Primary frame source interface.
604
- * Yields MediaFrame objects. Returning from the generator signals end of stream.
605
- * Throwing signals an error that should trigger reconnection.
606
- */
607
- type FrameSource = AsyncGenerator<MediaFrame, void, unknown>;
608
- /**
609
- * Factory that creates a new FrameSource.
610
- * Called each time the server needs to start/restart the stream.
611
- * The server is responsible for calling return() to stop the source.
612
- */
613
- type FrameSourceFactory = () => FrameSource;
614
- /**
615
- * Minimal logger interface compatible with console.
616
- */
617
- interface Logger {
618
- log(message?: unknown, ...args: unknown[]): void;
619
- info(message?: unknown, ...args: unknown[]): void;
620
- warn(message?: unknown, ...args: unknown[]): void;
621
- error(message?: unknown, ...args: unknown[]): void;
622
- debug(message?: unknown, ...args: unknown[]): void;
623
- }
624
- /**
625
- * Accept Console or partial Logger implementations.
626
- */
627
- type LoggerLike = Partial<Logger> | Console;
628
- /**
629
- * Wrap a LoggerLike into a full Logger (no-op for missing methods).
630
- */
631
- declare function asLogger(logger?: LoggerLike): Logger;
632
- /**
633
- * Create a logger that discards all output.
634
- */
635
- declare function createNullLogger(): Logger;
636
-
637
- /**
638
- * NAL unit parsing utilities.
639
- * Protocol-agnostic functions for working with Annex-B NAL units.
640
- */
641
-
642
- /**
643
- * Returns true if the buffer starts with an Annex-B start code (0x00000001 or 0x000001).
644
- */
645
- declare function hasStartCodes(data: Buffer): boolean;
646
- /**
647
- * Split Annex-B data into individual NAL unit payloads (without start codes).
648
- * Works for both H.264 and H.265.
649
- */
650
- declare function splitAnnexBToNals(annexB: Buffer): Buffer[];
651
- /**
652
- * Prepend a 4-byte Annex-B start code to a NAL payload.
653
- */
654
- declare function prependStartCode(nal: Buffer): Buffer;
655
- /**
656
- * Join multiple NAL payloads (without start codes) into an Annex-B access unit.
657
- */
658
- declare function joinNalsToAnnexB(...nals: Array<Buffer | null | undefined>): Buffer | undefined;
659
- /**
660
- * Detect the actual video codec from raw NAL data.
661
- * Some cameras report wrong codec (e.g. "H264" but send H.265 data).
662
- * Analyzes the NAL header to determine the real codec.
663
- *
664
- * @param data - Raw video data (either Annex-B or length-prefixed)
665
- * @returns Detected codec type or null if detection fails
666
- */
667
- declare function detectVideoCodecFromNal(data: Buffer): VideoCodec | null;
668
-
669
- /**
670
- * H.264/AVC utilities.
671
- * AVCC to Annex-B conversion, SPS/PPS extraction, keyframe detection, RTP depacketization.
672
- */
673
- /**
674
- * Convert H.264 data from length-prefixed (AVCC) to Annex-B (start codes).
675
- * If already Annex-B, returns as-is.
676
- */
677
- declare function convertH264ToAnnexB(data: Buffer): Buffer;
678
- /**
679
- * Check if an H.264 Annex-B access unit is a keyframe (IDR + SPS + PPS).
680
- */
681
- declare function isH264KeyframeAnnexB(annexB: Buffer): boolean;
682
- /**
683
- * Check if an H.264 Annex-B access unit contains an IDR slice.
684
- */
685
- declare function isH264IdrAccessUnit(annexB: Buffer): boolean;
686
- /**
687
- * Extract SPS and PPS from an H.264 Annex-B access unit.
688
- */
689
- declare function extractH264ParamSets(annexB: Buffer): {
690
- sps?: Buffer;
691
- pps?: Buffer;
692
- profileLevelId?: string;
693
- };
694
- /**
695
- * H.264 RTP depacketizer (RFC 6184).
696
- * Handles single NAL units, STAP-A aggregation, and FU-A/FU-B fragmentation.
697
- * Returns complete NAL units in Annex-B format (with start codes).
698
- */
699
- declare class H264RtpDepacketizer {
700
- private fuNalHeader;
701
- private fuParts;
702
- private static parseRtpPayload;
703
- reset(): void;
704
- push(payload: Buffer): Buffer[];
705
- }
706
-
707
- /**
708
- * H.265/HEVC utilities.
709
- * HVCC to Annex-B conversion, VPS/SPS/PPS extraction, keyframe detection, RTP depacketization.
710
- */
711
- /**
712
- * Convert H.265 data from length-prefixed (HVCC) to Annex-B (start codes).
713
- * If already Annex-B, returns as-is.
714
- */
715
- declare function convertH265ToAnnexB(data: Buffer): Buffer;
716
- /**
717
- * Get H.265 NAL unit type from a NAL payload (without start code).
718
- */
719
- declare function getH265NalType(nalPayload: Buffer): number | null;
720
- /**
721
- * Check if an H.265 NAL unit type is an IRAP (Intra Random Access Point) picture.
722
- * IRAP types: BLA (16-18), IDR (19-20), CRA (21).
723
- */
724
- declare function isH265Irap(nalType: number): boolean;
725
- /**
726
- * Check if an H.265 Annex-B access unit is a keyframe (IRAP + VPS + SPS + PPS).
727
- */
728
- declare function isH265KeyframeAnnexB(annexB: Buffer): boolean;
729
- /**
730
- * Check if an H.265 Annex-B access unit contains an IRAP picture.
731
- */
732
- declare function isH265IrapAccessUnit(annexB: Buffer): boolean;
733
- /**
734
- * Extract VPS, SPS, and PPS from an H.265 Annex-B access unit.
735
- */
736
- declare function extractH265ParamSets(annexB: Buffer): {
737
- vps?: Buffer;
738
- sps?: Buffer;
739
- pps?: Buffer;
740
- };
741
- /**
742
- * H.265 RTP depacketizer (RFC 7798).
743
- * Handles single NAL units, AP aggregation, and FU fragmentation.
744
- * Returns complete NAL units in Annex-B format (with start codes).
745
- */
746
- declare class H265RtpDepacketizer {
747
- private fuParts;
748
- private static parseRtpPayload;
749
- reset(): void;
750
- push(payload: Buffer): Buffer[];
751
- }
752
-
753
- /**
754
- * Stream fan-out utilities.
755
- * Distribute a single source stream to multiple consumers with bounded queues.
756
- */
757
- /**
758
- * Async bounded queue with push/pull interface.
759
- * When the queue is full, the oldest items are dropped (tail-drop).
760
- */
761
- declare class AsyncBoundedQueue<T> {
762
- private readonly maxItems;
763
- private readonly queue;
764
- private waiting;
765
- private closed;
766
- constructor(maxItems: number);
767
- push(item: T): void;
768
- close(): void;
769
- next(): Promise<IteratorResult<T>>;
770
- isClosed(): boolean;
771
- size(): number;
772
- }
773
- interface StreamFanoutOptions<T> {
774
- /** Maximum items per subscriber queue before tail-drop */
775
- maxQueueItems: number;
776
- /** Factory to create the source stream */
777
- createSource: () => AsyncGenerator<T, void, unknown>;
778
- /** Optional callback for each frame (e.g. for extracting metadata) */
779
- onFrame?: (frame: T) => void;
780
- /** Optional error handler */
781
- onError?: (error: unknown) => void;
782
- }
783
- /**
784
- * Fan-out a single async generator source to multiple subscribers.
785
- * The source stream is started when start() is called and frames are
786
- * distributed to all active subscriber queues.
787
- */
788
- declare class StreamFanout<T> {
789
- private readonly opts;
790
- private readonly queues;
791
- private source;
792
- private running;
793
- private pumpPromise;
794
- constructor(opts: StreamFanoutOptions<T>);
795
- /** Start pumping frames from the source to all subscribers. */
796
- start(): void;
797
- /**
798
- * Create a subscriber async generator.
799
- * Returns an async generator that yields frames from the shared source.
800
- * The generator terminates when the source ends or unsubscribe is called.
801
- */
802
- subscribe(id: string): AsyncGenerator<T, void, unknown>;
803
- /** Unsubscribe a specific subscriber. */
804
- unsubscribe(id: string): void;
805
- /** Stop the source and close all subscriber queues. */
806
- stop(): Promise<void>;
807
- /** Returns true if the fan-out is running. */
808
- isRunning(): boolean;
809
- /** Returns the number of active subscribers. */
810
- subscriberCount(): number;
811
- }
812
-
813
- /**
814
- * FFmpeg process lifecycle management.
815
- * Spawn and manage FFmpeg processes with proper cleanup.
816
- */
817
-
818
- interface FfmpegProcessOptions {
819
- /** FFmpeg binary path (default: "ffmpeg") */
820
- ffmpegPath?: string;
821
- /** FFmpeg arguments */
822
- args: string[];
823
- /** Logger instance */
824
- logger?: Logger;
825
- /** Label for log messages */
826
- label?: string;
827
- /** Additional stdio configuration (e.g. ["pipe"] for pipe:3) */
828
- extraStdio?: Array<"pipe" | "ignore" | "inherit">;
829
- /** Callback when process exits */
830
- onExit?: (code: number | null, signal: string | null) => void;
831
- /** Callback for stderr output */
832
- onStderr?: (data: string) => void;
833
- }
834
- /**
835
- * Managed FFmpeg process with proper lifecycle handling.
836
- */
837
- declare class FfmpegProcess {
838
- private readonly options;
839
- private process;
840
- private killed;
841
- private readonly logger;
842
- private readonly label;
843
- constructor(options: FfmpegProcessOptions);
844
- /** Spawn the FFmpeg process. Returns stdin writable stream. */
845
- start(): NodeJS.WritableStream | null;
846
- /** Get a specific stdio stream by fd index (e.g. 3 for pipe:3). */
847
- getStdio(fd: number): NodeJS.ReadableStream | NodeJS.WritableStream | null;
848
- /** Get the underlying ChildProcess. */
849
- getProcess(): ChildProcess | null;
850
- /** Kill the FFmpeg process gracefully (SIGTERM then SIGKILL after timeout). */
851
- kill(timeoutMs?: number): Promise<void>;
852
- /** Check if the process is running. */
853
- isRunning(): boolean;
854
- }
855
-
856
- /**
857
- * Frame source adapters.
858
- * Convert various input patterns (EventEmitter, push callbacks) into FrameSource async generators.
859
- */
860
-
861
- /**
862
- * Convert an EventEmitter-based source into a FrameSource async generator.
863
- *
864
- * @param emitter - The event emitter that fires video/audio frames
865
- * @param videoEvent - Event name for video frames (default: "videoFrame")
866
- * @param audioEvent - Event name for audio frames (default: "audioFrame")
867
- */
868
- declare function fromEventEmitter(emitter: EventEmitter, videoEvent?: string, audioEvent?: string): FrameSource;
869
- /**
870
- * Create a push-based frame source.
871
- * Returns a FrameSource async generator and push functions to deliver frames.
872
- */
873
- declare function fromPushCallback(): {
874
- source: FrameSource;
875
- pushVideo: (frame: VideoFrame) => void;
876
- pushAudio: (frame: AudioFrame) => void;
877
- close: () => void;
878
- };
879
- /**
880
- * Create a FrameSource from a raw async generator of { audio, data, codec, videoType, ... } frames.
881
- * This is the format used by reolink-baichuan-js's createNativeStream().
882
- */
883
- declare function fromNativeStream(native: AsyncGenerator<{
884
- audio: boolean;
885
- data: Buffer;
886
- codec: string | null;
887
- sampleRate: number | null;
888
- microseconds: number | null;
889
- videoType?: string;
890
- isKeyframe?: boolean;
891
- }, void, unknown>): FrameSource;
892
-
893
- /**
894
- * Adaptive FFmpeg source — RTSP→FrameSource pipeline with hot-swappable encoding params.
895
- *
896
- * Spawns ffmpeg to transcode an RTSP source to raw H.264 Annex-B on stdout.
897
- * Supports dynamic bitrate/resolution changes by restarting ffmpeg with new params,
898
- * gating the transition on the next keyframe to avoid visual artifacts.
899
- */
900
-
901
- interface EncodingParams {
902
- /** Max video bitrate in kbps (e.g. 8000 for 8 Mbps). */
903
- maxBitrateKbps: number;
904
- /** Output width (0 = keep source). */
905
- width: number;
906
- /** Output height (0 = keep source). */
907
- height: number;
908
- /** H.264 preset (default: "ultrafast"). */
909
- preset?: string;
910
- }
911
- /** Audio mode: "copy" = passthrough G.711, "opus" = transcode to Opus, "off" = no audio */
912
- type AudioMode = "copy" | "opus" | "off";
913
- interface AdaptiveFfmpegSourceOptions {
914
- /** RTSP source URL. */
915
- rtspUrl: string;
916
- /** Initial encoding parameters. */
917
- initialParams: EncodingParams;
918
- /** Audio mode (default: "copy"). */
919
- audioMode?: AudioMode;
920
- /** FFmpeg binary path (default: "ffmpeg"). */
921
- ffmpegPath?: string;
922
- /** Logger instance. */
923
- logger?: Logger;
924
- /** Label for logging. */
925
- label?: string;
926
- }
927
- declare class AdaptiveFfmpegSource {
928
- private readonly rtspUrl;
929
- private readonly ffmpegPath;
930
- private readonly logger;
931
- private readonly label;
932
- private readonly audioMode;
933
- private currentParams;
934
- private proc;
935
- private audioProc;
936
- private closed;
937
- /** Push callback for the frame source. */
938
- private pushFrame;
939
- private closeSource;
940
- /** The FrameSource async generator. Created once, survives ffmpeg restarts. */
941
- readonly source: FrameSource;
942
- constructor(options: AdaptiveFfmpegSourceOptions);
943
- /** Start the ffmpeg process with current encoding params. */
944
- start(): Promise<void>;
945
- /** Get the current encoding parameters. */
946
- getParams(): Readonly<EncodingParams>;
947
- /**
948
- * Hot-swap encoding parameters.
949
- * Stops the current ffmpeg and starts a new one with updated params.
950
- * The FrameSource continues seamlessly — the new ffmpeg's first keyframe
951
- * is gated internally so consumers see a clean transition.
952
- */
953
- updateParams(params: Partial<EncodingParams>): Promise<void>;
954
- /** Stop the source and kill ffmpeg. */
955
- stop(): Promise<void>;
956
- private spawnFfmpeg;
957
- private killFfmpeg;
958
- }
959
-
960
- /**
961
- * Adaptive quality controller — state machine for bitrate/resolution/source adaptation.
962
- *
963
- * Monitors packet loss, jitter, and RTT from RTCP reports and client-reported stats.
964
- * Makes quality decisions using a three-tier degradation/recovery model:
965
- *
966
- * Level 1 — Reduce bitrate cap (ffmpeg -maxrate)
967
- * Level 2 — Downscale resolution (ffmpeg -vf scale=)
968
- * Level 3 — Switch source stream (main → sub via replaceTrack)
969
- *
970
- * Hysteresis prevents oscillation: degradation requires 2 consecutive bad intervals,
971
- * recovery requires 3 consecutive good intervals.
972
- */
973
-
974
- type QualityTier = "high" | "medium" | "low";
975
- interface QualityProfile {
976
- tier: QualityTier;
977
- /** Encoding params to apply. */
978
- encoding: EncodingParams;
979
- /** Source stream profile ("main" | "sub"). */
980
- sourceProfile: "main" | "sub";
981
- }
982
- interface StreamStats {
983
- /** Packet loss ratio (0.0 – 1.0). */
984
- packetLoss: number;
985
- /** Jitter in milliseconds. */
986
- jitterMs: number;
987
- /** Round-trip time in milliseconds. */
988
- rttMs: number;
989
- /** Timestamp of this measurement. */
990
- timestamp: number;
991
- }
992
- interface AdaptiveControllerOptions {
993
- /** Available quality profiles, ordered from highest to lowest. */
994
- profiles: QualityProfile[];
995
- /** Packet loss threshold to degrade (default: 0.02 = 2%). */
996
- degradeThreshold?: number;
997
- /** Packet loss threshold to recover (default: 0.005 = 0.5%). */
998
- recoverThreshold?: number;
999
- /** Consecutive bad intervals before degradation (default: 2). */
1000
- degradeCount?: number;
1001
- /** Consecutive good intervals before recovery (default: 3). */
1002
- recoverCount?: number;
1003
- /** Callback when quality should change. */
1004
- onQualityChange: (from: QualityProfile, to: QualityProfile) => void | Promise<void>;
1005
- /** Logger. */
1006
- logger?: Logger;
1007
- }
1008
- declare class AdaptiveController {
1009
- private readonly profiles;
1010
- private readonly degradeThreshold;
1011
- private readonly recoverThreshold;
1012
- private readonly degradeCount;
1013
- private readonly recoverCount;
1014
- private readonly onQualityChange;
1015
- private readonly logger;
1016
- private currentIndex;
1017
- private consecutiveBad;
1018
- private consecutiveGood;
1019
- private switching;
1020
- /** Smoothed stats per session (aggregated for decisions). */
1021
- private readonly sessionStats;
1022
- /** Manual override tier (null = auto). */
1023
- private forcedTier;
1024
- constructor(options: AdaptiveControllerOptions);
1025
- /** Get the current quality profile. */
1026
- get currentProfile(): QualityProfile;
1027
- /** Get the current quality tier. */
1028
- get currentTier(): QualityTier;
1029
- /** Get aggregated stats summary. */
1030
- getAggregatedStats(): {
1031
- packetLoss: number;
1032
- jitterMs: number;
1033
- rttMs: number;
1034
- };
1035
- /**
1036
- * Report stats from a session (RTCP or client-reported).
1037
- * Call this periodically (e.g. every 3–5 seconds).
1038
- */
1039
- reportStats(sessionId: string, stats: StreamStats): void;
1040
- /** Remove a session's stats (call on session close). */
1041
- removeSession(sessionId: string): void;
1042
- /** Force a specific quality tier (null = auto). */
1043
- forceQuality(tier: QualityTier | null): void;
1044
- /** Check if auto-adaptation is active (not forced). */
1045
- get isAuto(): boolean;
1046
- private evaluate;
1047
- private degrade;
1048
- private recover;
1049
- private switchTo;
1050
- }
1051
-
1052
- /**
1053
- * Adaptive WebRTC session — extends the base session pattern with:
1054
- * - RTCP Receiver Report monitoring (packet loss, jitter, RTT)
1055
- * - Source replacement (replaceTrack for seamless quality switching)
1056
- * - Stats emission for the AdaptiveController
1057
- *
1058
- * Uses werift (optional peer dependency) for server-side WebRTC.
1059
- */
1060
-
1061
- interface AdaptiveSessionOptions {
1062
- sessionId: string;
1063
- source: FrameSource;
1064
- intercom?: {
1065
- onAudioReceived: (data: Buffer, format: AudioCodec) => void | Promise<void>;
1066
- };
1067
- iceConfig?: {
1068
- stunServers?: string[];
1069
- turnServers?: Array<{
1070
- urls: string;
1071
- username?: string;
1072
- credential?: string;
1073
- }>;
1074
- portRange?: [number, number];
1075
- additionalHostAddresses?: string[];
1076
- };
1077
- /** Callback for RTCP stats (called every ~3s). */
1078
- onStats?: (stats: SessionStats) => void;
1079
- /** Enable verbose frame/RTP logging. */
1080
- debug?: boolean;
1081
- logger: Logger;
1082
- }
1083
- interface SessionStats {
1084
- sessionId: string;
1085
- /** Fraction of packets lost (0.0–1.0). */
1086
- packetLoss: number;
1087
- /** Interarrival jitter in ms. */
1088
- jitterMs: number;
1089
- /** Round-trip time in ms (from RTCP SR/RR). */
1090
- rttMs: number;
1091
- /** Total packets received. */
1092
- packetsReceived: number;
1093
- /** Total packets lost. */
1094
- packetsLost: number;
1095
- /** Timestamp. */
1096
- timestamp: number;
1097
- }
1098
- interface SessionInfo {
1099
- sessionId: string;
1100
- state: "new" | "connecting" | "connected" | "disconnected" | "closed";
1101
- createdAt: number;
1102
- }
1103
- declare class AdaptiveSession {
1104
- private readonly sessionId;
1105
- private source;
1106
- private readonly logger;
1107
- private readonly intercom;
1108
- private readonly iceConfig;
1109
- private readonly onStats;
1110
- readonly debug: boolean;
1111
- private readonly createdAt;
1112
- private state;
1113
- private pc;
1114
- private videoTrack;
1115
- private audioTrack;
1116
- /** Transceiver senders for direct sendRtp (more reliable than track.writeRtp) */
1117
- private videoSender;
1118
- private audioSender;
1119
- private feedAbort;
1120
- private closed;
1121
- private statsTimer;
1122
- /** RTP sequence number counter (must increment per packet). */
1123
- private videoSeqNum;
1124
- private audioSeqNum;
1125
- /** Previous RTCP stats for delta calculation. */
1126
- private prevPacketsReceived;
1127
- private prevPacketsLost;
1128
- constructor(options: AdaptiveSessionOptions);
1129
- /** Build PeerConnection options including H.264 codec config. */
1130
- private buildPcOptions;
1131
- /** Create offer SDP (server → client). */
1132
- createOffer(): Promise<{
1133
- sdp: string;
1134
- type: "offer";
1135
- }>;
1136
- /** Handle WHEP answer: client sends SDP answer, we set remote description and start feeding. */
1137
- handleAnswer(answer: {
1138
- sdp: string;
1139
- type: "answer";
1140
- }): Promise<void>;
1141
- /**
1142
- * Handle WHEP offer: client sends SDP offer, we create answer.
1143
- *
1144
- * Uses the server-creates-offer pattern internally: we create our own offer
1145
- * with sendonly tracks, then use the client's offer codecs to build a
1146
- * compatible answer. This avoids werift transceiver direction issues.
1147
- */
1148
- handleOffer(clientOffer: {
1149
- sdp: string;
1150
- type: "offer";
1151
- }): Promise<{
1152
- sdp: string;
1153
- type: "answer";
1154
- }>;
1155
- /** Add ICE candidate. */
1156
- addIceCandidate(candidate: any): Promise<void>;
1157
- /**
1158
- * Detach the frame source (for connection pooling).
1159
- * The session stays alive (ICE/DTLS connected) but stops feeding frames.
1160
- * Call replaceSource() later to reattach a camera.
1161
- */
1162
- detachSource(): void;
1163
- /** Whether the session has an active feed (vs idle/pooled). */
1164
- get isFeeding(): boolean;
1165
- /**
1166
- * Replace the frame source (for seamless source switching).
1167
- * The new source will take effect at the next keyframe.
1168
- */
1169
- replaceSource(newSource: FrameSource): void;
1170
- getInfo(): SessionInfo;
1171
- close(): Promise<void>;
1172
- private startFeedingFrames;
1173
- /** Build a serialized RTP packet for sender.sendRtp(). */
1174
- private buildRtpBuffer;
1175
- /** Max RTP payload size (MTU 1200 to stay under typical network MTU). */
1176
- private static readonly MAX_RTP_PAYLOAD;
1177
- private rtpPacketsSent;
1178
- private writeVideoNals;
1179
- private writeAudio;
1180
- private startStatsCollection;
1181
- private collectStats;
1182
- }
1183
-
1184
- /**
1185
- * Adaptive RTSP relay — transcodes an RTSP source via ffmpeg and outputs
1186
- * a local RTSP stream that can be registered in go2rtc for WebRTC delivery.
1187
- *
1188
- * This avoids werift's SRTP/DTLS issues by letting go2rtc handle the
1189
- * browser-facing WebRTC connection while we control the transcoding.
1190
- *
1191
- * Pipeline: Camera RTSP → ffmpeg (adaptive params) → RTSP localhost → go2rtc → WHEP → Browser
1192
- */
1193
-
1194
- interface AdaptiveRtspRelayOptions {
1195
- /** RTSP source URL (camera or upstream go2rtc). */
1196
- rtspUrl: string;
1197
- /** Local RTSP output URL (e.g. rtsp://127.0.0.1:8554/adaptive_ingresso_main). */
1198
- rtspOutputUrl: string;
1199
- /** Initial encoding parameters. */
1200
- initialParams: EncodingParams;
1201
- /** FFmpeg binary path (default: "ffmpeg"). */
1202
- ffmpegPath?: string;
1203
- /** Logger. */
1204
- logger?: Logger;
1205
- /** Label for logging. */
1206
- label?: string;
1207
- }
1208
- declare class AdaptiveRtspRelay {
1209
- private readonly rtspUrl;
1210
- private readonly rtspOutputUrl;
1211
- private readonly ffmpegPath;
1212
- private readonly logger;
1213
- private readonly label;
1214
- private currentParams;
1215
- private proc;
1216
- private closed;
1217
- constructor(options: AdaptiveRtspRelayOptions);
1218
- getParams(): Readonly<EncodingParams>;
1219
- /** Start the ffmpeg relay. */
1220
- start(): void;
1221
- /** Hot-swap encoding parameters by restarting ffmpeg. */
1222
- updateParams(params: Partial<EncodingParams>): Promise<void>;
1223
- /** Stop the relay. */
1224
- stop(): Promise<void>;
1225
- /** Check if ffmpeg is running. */
1226
- isRunning(): boolean;
1227
- private spawnFfmpeg;
1228
- private killFfmpeg;
1229
- }
1230
-
1231
- /**
1232
- * SharedSession — single RTCPeerConnection with multiple dynamic video tracks.
1233
- *
1234
- * One connection per client. Tracks are added/removed via data channel messages.
1235
- * Each track corresponds to one camera's ffmpeg source.
1236
- *
1237
- * Protocol (data channel JSON messages):
1238
- *
1239
- * Client → Server:
1240
- * { type: "addTrack", cameraName: string, trackId: string }
1241
- * { type: "removeTrack", trackId: string }
1242
- * { type: "answer", sdp: string }
1243
- *
1244
- * Server → Client:
1245
- * { type: "offer", sdp: string }
1246
- * { type: "trackReady", trackId: string, mid: string }
1247
- * { type: "trackRemoved", trackId: string }
1248
- * { type: "error", message: string }
1249
- */
1250
-
1251
- interface SharedSessionOptions {
1252
- iceConfig?: {
1253
- stunServers?: string[];
1254
- turnServers?: Array<{
1255
- urls: string;
1256
- username?: string;
1257
- credential?: string;
1258
- }>;
1259
- portRange?: [number, number];
1260
- additionalHostAddresses?: string[];
1261
- };
1262
- /** Called when the server needs to attach a camera source for a track. */
1263
- onTrackRequested: (cameraName: string) => FrameSource | null;
1264
- /** Called when a track is removed. */
1265
- onTrackReleased?: (cameraName: string) => void;
1266
- /** Called when intercom audio is received from the client for a camera. */
1267
- onIntercomAudio?: (cameraName: string, data: Buffer) => void;
1268
- logger: Logger;
1269
- }
1270
- declare class SharedSession {
1271
- private readonly options;
1272
- private readonly logger;
1273
- private readonly iceConfig;
1274
- private readonly onTrackRequested;
1275
- private readonly onTrackReleased;
1276
- private pc;
1277
- private dataChannel;
1278
- private readonly activeTracks;
1279
- private closed;
1280
- private negotiating;
1281
- constructor(options: SharedSessionOptions);
1282
- /** Create the initial SDP offer (with data channel, no media tracks yet). */
1283
- createOffer(): Promise<string>;
1284
- /** Handle the client's SDP answer. */
1285
- handleAnswer(sdpAnswer: string): Promise<void>;
1286
- /** Close the shared session and all tracks. */
1287
- close(): Promise<void>;
1288
- private handleDataChannelMessage;
1289
- private handleAddTrack;
1290
- private handleRemoveTrack;
1291
- private handleRenegotiationAnswer;
1292
- private renegotiate;
1293
- private startFeeding;
1294
- private sendDC;
1295
- get isConnected(): boolean;
1296
- get trackCount(): number;
1297
- }
1298
-
1299
- /**
1300
- * Adaptive WebRTC streaming server — multi-camera manager with quality adaptation.
1301
- *
1302
- * For each camera:
1303
- * - Spawns AdaptiveFfmpegSource to transcode RTSP → H.264 with configurable params
1304
- * - Uses StreamFanout to distribute frames to multiple viewer sessions
1305
- * - AdaptiveController monitors stats and adjusts quality per-camera
1306
- *
1307
- * Usage:
1308
- * const server = new AdaptiveStreamServer({ ... });
1309
- * server.addCamera("front_door", { rtspUrl: "rtsp://...", profiles: [...] });
1310
- * const { sessionId, answer } = await server.handleWhepOffer("front_door", sdpOffer);
1311
- */
1312
-
1313
- interface AdaptiveStreamServerOptions {
1314
- /** FFmpeg binary path (default: "ffmpeg"). */
1315
- ffmpegPath?: string;
1316
- /** STUN servers for ICE. */
1317
- stunServers?: string[];
1318
- /** TURN servers for ICE. */
1319
- turnServers?: Array<{
1320
- urls: string;
1321
- username?: string;
1322
- credential?: string;
1323
- }>;
1324
- /** ICE port range. */
1325
- icePortRange?: [number, number];
1326
- /** Additional host IPs to announce. */
1327
- iceAdditionalHostAddresses?: string[];
1328
- /** Logger. */
1329
- logger?: Logger;
1330
- }
1331
- interface CameraConfig {
1332
- /** Primary RTSP source URL (typically main stream). */
1333
- rtspUrl: string;
1334
- /** Secondary RTSP source URL (typically sub stream, for Level 3 switching). */
1335
- subRtspUrl?: string;
1336
- /** Quality profiles ordered from highest to lowest quality. */
1337
- profiles: QualityProfile[];
1338
- /** Audio mode: "copy" (G.711 passthrough), "opus" (transcode), "off" (no audio). Default: "copy". */
1339
- audioMode?: "copy" | "opus" | "off";
1340
- }
1341
- /** Default quality profiles for adaptive streaming. */
1342
- declare function createDefaultProfiles(): QualityProfile[];
1343
- declare class AdaptiveStreamServer extends EventEmitter {
1344
- private readonly ffmpegPath;
1345
- private readonly stunServers;
1346
- private readonly turnServers;
1347
- private readonly icePortRange;
1348
- private readonly iceAdditionalHostAddresses;
1349
- private readonly logger;
1350
- private readonly cameras;
1351
- private readonly sessionCamera;
1352
- private stopped;
1353
- constructor(options?: AdaptiveStreamServerOptions);
1354
- /** Register a camera with adaptive streaming. */
1355
- addCamera(name: string, config: CameraConfig): void;
1356
- /** Remove a camera and close all its sessions. */
1357
- removeCamera(name: string): Promise<void>;
1358
- getCameraNames(): string[];
1359
- /**
1360
- * Create an adaptive session for a camera.
1361
- * Returns a server-generated SDP offer that the client must answer.
1362
- *
1363
- * Flow: createSession() → server offer → client sets remote, creates answer → handleAnswer()
1364
- */
1365
- createSession(cameraName: string): Promise<{
1366
- sessionId: string;
1367
- sdpOffer: string;
1368
- }>;
1369
- /**
1370
- * Handle the client's SDP answer for an adaptive session.
1371
- * Call after createSession() with the client's answer.
1372
- */
1373
- handleAnswer(sessionId: string, sdpAnswer: string): Promise<void>;
1374
- /**
1375
- * Convenience: handleWhepOffer is NOT supported — werift requires server-initiated offers.
1376
- * Use createSession() + handleAnswer() instead.
1377
- */
1378
- handleWhepOffer(_cameraName: string, _sdpOffer: string): Promise<{
1379
- sessionId: string;
1380
- sdpAnswer: string;
1381
- }>;
1382
- /** Pooled sessions: sessionId → true (idle, no camera attached). */
1383
- private readonly pooledSessions;
1384
- /**
1385
- * Create a pooled session (no camera attached yet).
1386
- * The SDP exchange happens, ICE connects, but no ffmpeg is started.
1387
- * Call attachCamera() later to start feeding frames.
1388
- */
1389
- createPooledSession(): Promise<{
1390
- sessionId: string;
1391
- sdpOffer: string;
1392
- }>;
1393
- /**
1394
- * Attach a camera to a pooled session.
1395
- * Starts the ffmpeg transcoder and begins feeding frames.
1396
- */
1397
- attachCamera(sessionId: string, cameraName: string): Promise<void>;
1398
- /**
1399
- * Detach a camera from a session (session returns to pool).
1400
- */
1401
- detachCamera(sessionId: string): Promise<void>;
1402
- /** Check if a session is in the idle pool. */
1403
- isPooledSession(sessionId: string): boolean;
1404
- /** Set debug flag on all sessions for a camera. */
1405
- setDebug(cameraName: string, debug: boolean): number;
1406
- /** Get count of idle pooled sessions. */
1407
- getPoolSize(): number;
1408
- /** Close a specific session. */
1409
- closeSession(sessionId: string): Promise<void>;
1410
- /**
1411
- * Report client-side stats for a session (supplements RTCP monitoring).
1412
- * Call from tRPC route when the client pushes stats.
1413
- */
1414
- reportClientStats(sessionId: string, stats: StreamStats): {
1415
- currentTier: QualityTier;
1416
- currentBitrateKbps: number;
1417
- currentResolution: {
1418
- width: number;
1419
- height: number;
1420
- };
1421
- sourceProfile: "main" | "sub";
1422
- } | null;
1423
- /** Force quality for a camera (null = auto). */
1424
- forceQuality(cameraName: string, tier: QualityTier | null): boolean;
1425
- /** Get current quality info for a camera. */
1426
- getCameraQuality(cameraName: string): {
1427
- tier: QualityTier;
1428
- encoding: EncodingParams;
1429
- isAuto: boolean;
1430
- stats: {
1431
- packetLoss: number;
1432
- jitterMs: number;
1433
- rttMs: number;
1434
- };
1435
- sessionCount: number;
1436
- sourceProfile: "main" | "sub";
1437
- } | null;
1438
- /** Get all sessions. */
1439
- getSessions(cameraName?: string): SessionInfo[];
1440
- getSessionCount(cameraName?: string): number;
1441
- /** Stop all cameras and sessions. */
1442
- stop(): Promise<void>;
1443
- /** Get the currently active fanout for a camera. */
1444
- private getActiveFanout;
1445
- private ensureCameraRunning;
1446
- private scheduleCameraAutoStop;
1447
- /**
1448
- * Handle a quality change from the AdaptiveController.
1449
- * When the sourceProfile changes (main ↔ sub), performs a seamless source
1450
- * switch for all active sessions. When only encoding params change (same
1451
- * sourceProfile), updates ffmpeg params in-place.
1452
- */
1453
- private handleQualityChange;
1454
- /**
1455
- * Switch all active sessions from one source to another (main ↔ sub).
1456
- *
1457
- * Steps:
1458
- * 1. Create/start the target ffmpeg source + fanout
1459
- * 2. For each session: subscribe to new fanout, call replaceSource()
1460
- * 3. Unsubscribe all from old fanout
1461
- * 4. Stop old ffmpeg + fanout (save resources)
1462
- * 5. Update activeSourceProfile
1463
- */
1464
- private switchSource;
1465
- }
1466
-
1467
- export { AdaptiveController, type AdaptiveControllerOptions, AdaptiveFfmpegSource, type AdaptiveFfmpegSourceOptions, AdaptiveRtspRelay, type AdaptiveRtspRelayOptions, AdaptiveSession, type AdaptiveSessionOptions, AdaptiveStreamServer, type AdaptiveStreamServerOptions, type AnnotatedSnapshotResult, AsyncBoundedQueue, type AudioCodec, type AudioFrame, type AudioMode, BuiltinAnalysisAddon, type CameraConfig, type ClipRecognizer, DEFAULT_RETENTION, DecoderRegistry, type EncodingParams, type EventBufferStatus, type EventPersistenceDeps, EventPersistenceService, FfmpegDecoderProvider, FfmpegDecoderSession, FfmpegProcess, type FfmpegProcessOptions, FrameDropper, type FrameSource, type FrameSourceFactory, type GlobalIdentity, H264RtpDepacketizer, H265RtpDepacketizer, type IAnalysisDataPersistence, type KnownFaceEntry, type KnownFacesDeps, KnownFacesService, type LoggerLike, type MediaFrame, type ObjectSnapshotResult, type PersistableEvent, type QualityProfile, type QualityTier, type RetentionConfig, type RetentionDeps, type RetentionReport, RetentionService, type SessionInfo, type SessionStats, type SessionTrack, type SessionTrackerDeps, SessionTrackerService, SharedSession, type SharedSessionOptions, StreamBroker, StreamBrokerManager, StreamFanout, type StreamFanoutOptions, StreamPipeServer, type StreamStats, type TrackCaptureConfig, type TrackMediaFile, type TrackMediaType, type TrackPosition, type TrackSnapshot, type TrackTrail, type TrackTrailDeps, TrackTrailService, type VideoCodec, type Logger as WebRtcLogger, type VideoFrame as WebRtcVideoFrame, asLogger, convertH264ToAnnexB, convertH265ToAnnexB, cosineSimilarity, createDefaultProfiles, createNullLogger, detectVideoCodecFromNal, extractH264ParamSets, extractH265ParamSets, fromEventEmitter, fromNativeStream, fromPushCallback, getH265NalType, hasStartCodes, isH264IdrAccessUnit, isH264KeyframeAnnexB, isH265Irap, isH265IrapAccessUnit, isH265KeyframeAnnexB, joinNalsToAnnexB, prependStartCode, splitAnnexBToNals };
550
+ export { type AnnotatedSnapshotResult, BuiltinAnalysisAddon, type ClipRecognizer, DEFAULT_RETENTION, DecoderRegistry, type EventBufferStatus, type EventPersistenceDeps, EventPersistenceService, FfmpegDecoderProvider, FfmpegDecoderSession, FrameDropper, type GlobalIdentity, type IAnalysisDataPersistence, type KnownFaceEntry, type KnownFacesDeps, KnownFacesService, type ObjectSnapshotResult, type PersistableEvent, type RetentionConfig, type RetentionDeps, type RetentionReport, RetentionService, type SessionTrack, type SessionTrackerDeps, SessionTrackerService, StreamBroker, StreamBrokerManager, StreamPipeServer, type TrackCaptureConfig, type TrackMediaFile, type TrackMediaType, type TrackPosition, type TrackSnapshot, type TrackTrail, type TrackTrailDeps, TrackTrailService, cosineSimilarity };