@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.0

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.
Files changed (143) hide show
  1. package/README.md +31 -12
  2. package/dist/client/device-registry.d.ts +343 -0
  3. package/dist/client/eufy-mega.d.ts +872 -0
  4. package/dist/client/index.d.ts +1 -6
  5. package/dist/client/map-channels.d.ts +22 -0
  6. package/dist/client/types.d.ts +383 -0
  7. package/dist/core/contracts.d.ts +898 -0
  8. package/dist/core/crypto.d.ts +98 -0
  9. package/dist/core/index.d.ts +9 -7
  10. package/dist/core/logger.d.ts +53 -0
  11. package/dist/core/lz4-block.d.ts +35 -0
  12. package/dist/core/raw-dp-hex.d.ts +32 -0
  13. package/dist/core/raw-dp-writer.d.ts +83 -0
  14. package/dist/core/store.d.ts +43 -0
  15. package/dist/core/types.d.ts +169 -0
  16. package/dist/core/util.d.ts +78 -0
  17. package/dist/index.d.ts +4 -7
  18. package/dist/index.js +25526 -14
  19. package/dist/index.js.map +7 -1
  20. package/dist/model/capabilities/access.d.ts +127 -0
  21. package/dist/model/capabilities/arming.d.ts +201 -0
  22. package/dist/model/capabilities/audio.d.ts +154 -0
  23. package/dist/model/capabilities/battery.d.ts +363 -0
  24. package/dist/model/capabilities/camera.d.ts +564 -0
  25. package/dist/model/capabilities/co.d.ts +40 -0
  26. package/dist/model/capabilities/contact.d.ts +133 -0
  27. package/dist/model/capabilities/doorbell.d.ts +345 -0
  28. package/dist/model/capabilities/dp-catalog.d.ts +38 -0
  29. package/dist/model/capabilities/index.d.ts +561 -0
  30. package/dist/model/capabilities/info.d.ts +28 -0
  31. package/dist/model/capabilities/keypad.d.ts +61 -0
  32. package/dist/model/capabilities/leak.d.ts +43 -0
  33. package/dist/model/capabilities/light.d.ts +174 -0
  34. package/dist/model/capabilities/locate.d.ts +63 -0
  35. package/dist/model/capabilities/lock.d.ts +242 -0
  36. package/dist/model/capabilities/manifest.d.ts +107 -0
  37. package/dist/model/capabilities/members.d.ts +647 -0
  38. package/dist/model/capabilities/motion.d.ts +377 -0
  39. package/dist/model/capabilities/person-detection.d.ts +8 -0
  40. package/dist/model/capabilities/ptz.d.ts +289 -0
  41. package/dist/model/capabilities/rtsp.d.ts +221 -0
  42. package/dist/model/capabilities/siren.d.ts +218 -0
  43. package/dist/model/capabilities/smart-light.d.ts +172 -0
  44. package/dist/model/capabilities/smoke.d.ts +40 -0
  45. package/dist/model/capabilities/snapshot.d.ts +6 -0
  46. package/dist/model/capabilities/storage.d.ts +11 -0
  47. package/dist/model/capabilities/suction.d.ts +104 -0
  48. package/dist/model/capabilities/types.d.ts +484 -0
  49. package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
  50. package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
  51. package/dist/model/capabilities/video.d.ts +6 -0
  52. package/dist/model/classify.d.ts +78 -0
  53. package/dist/model/clean-record-detail.d.ts +65 -0
  54. package/dist/model/clean-records.d.ts +69 -0
  55. package/dist/model/device-family.d.ts +73 -0
  56. package/dist/model/device-types.d.ts +123 -0
  57. package/dist/model/device.d.ts +265 -0
  58. package/dist/model/index.d.ts +29 -4
  59. package/dist/model/infer.d.ts +23 -0
  60. package/dist/model/inspect.d.ts +61 -0
  61. package/dist/model/life-params.d.ts +21 -0
  62. package/dist/model/map-pixels.d.ts +70 -0
  63. package/dist/model/param-dictionary.d.ts +28 -0
  64. package/dist/model/param-namespace.d.ts +21 -0
  65. package/dist/model/proto-read.d.ts +53 -0
  66. package/dist/model/push-events.d.ts +147 -0
  67. package/dist/model/registry.d.ts +54 -0
  68. package/dist/model/types.d.ts +301 -0
  69. package/dist/model/vacuum-map-store.d.ts +92 -0
  70. package/dist/model/vacuum-map.d.ts +286 -0
  71. package/dist/model/vacuum-scenes.d.ts +76 -0
  72. package/dist/model/vacuum-schedules.d.ts +85 -0
  73. package/dist/transport/dp-preset.d.ts +102 -0
  74. package/dist/transport/ff09.d.ts +444 -0
  75. package/dist/transport/ffmpeg.d.ts +86 -0
  76. package/dist/transport/http/decodeImageV1.d.ts +20 -0
  77. package/dist/transport/http/decodeImageV2.d.ts +19 -0
  78. package/dist/transport/http/index.d.ts +5 -0
  79. package/dist/transport/http/light-catalog.d.ts +62 -0
  80. package/dist/transport/http/media-download.d.ts +14 -0
  81. package/dist/transport/http/mega-client.d.ts +514 -0
  82. package/dist/transport/http/phone-model.d.ts +21 -0
  83. package/dist/transport/index.d.ts +10 -7
  84. package/dist/transport/mqtt/app-client-id.d.ts +16 -0
  85. package/dist/transport/mqtt/availability.d.ts +14 -0
  86. package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
  87. package/dist/transport/mqtt/biz-stream.d.ts +98 -0
  88. package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
  89. package/dist/transport/mqtt/clean-codec.d.ts +14 -0
  90. package/dist/transport/mqtt/command-router.d.ts +285 -0
  91. package/dist/transport/mqtt/dp-codec.d.ts +58 -0
  92. package/dist/transport/mqtt/dp-color.d.ts +14 -0
  93. package/dist/transport/mqtt/engine.d.ts +16 -0
  94. package/dist/transport/mqtt/index.d.ts +5 -0
  95. package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
  96. package/dist/transport/mqtt/topics.d.ts +80 -0
  97. package/dist/transport/p2p/adts.d.ts +91 -0
  98. package/dist/transport/p2p/annexb.d.ts +124 -0
  99. package/dist/transport/p2p/codec.d.ts +166 -0
  100. package/dist/transport/p2p/command-router.d.ts +655 -0
  101. package/dist/transport/p2p/commands.d.ts +550 -0
  102. package/dist/transport/p2p/envelope.d.ts +46 -0
  103. package/dist/transport/p2p/fmp4.d.ts +89 -0
  104. package/dist/transport/p2p/fragment-recording.d.ts +33 -0
  105. package/dist/transport/p2p/index.d.ts +13 -0
  106. package/dist/transport/p2p/lan-ip.d.ts +25 -0
  107. package/dist/transport/p2p/live-stream.d.ts +223 -0
  108. package/dist/transport/p2p/live-trace.d.ts +123 -0
  109. package/dist/transport/p2p/media.d.ts +105 -0
  110. package/dist/transport/p2p/p2p-session.d.ts +620 -0
  111. package/dist/transport/p2p/readable-egress.d.ts +27 -0
  112. package/dist/transport/p2p/session-manager.d.ts +154 -0
  113. package/dist/transport/p2p/shared-live-source.d.ts +431 -0
  114. package/dist/transport/p2p/talkback.d.ts +187 -0
  115. package/dist/transport/p2p/video.d.ts +150 -0
  116. package/dist/transport/p2p/write-commands.d.ts +21 -0
  117. package/dist/transport/protobuf.d.ts +5 -0
  118. package/dist/transport/push/fcm.d.ts +23 -0
  119. package/dist/transport/push/index.d.ts +6 -0
  120. package/dist/transport/push/message-tags.d.ts +26 -0
  121. package/dist/transport/push/parser.d.ts +27 -0
  122. package/dist/transport/push/proto.d.ts +11 -0
  123. package/dist/transport/push/push-client.d.ts +64 -0
  124. package/dist/transport/push/store.d.ts +23 -0
  125. package/dist/transport/push/types.d.ts +180 -0
  126. package/dist/transport/raw-dp.d.ts +6 -0
  127. package/dist/transport/stored-image-cache.d.ts +23 -0
  128. package/dist/transport/tuya/account.d.ts +44 -0
  129. package/dist/transport/tuya/client.d.ts +89 -0
  130. package/dist/transport/tuya/command-router.d.ts +79 -0
  131. package/dist/transport/tuya/dp-codec.d.ts +56 -0
  132. package/dist/transport/tuya/index.d.ts +28 -0
  133. package/dist/transport/tuya/request.d.ts +157 -0
  134. package/dist/transport/tuya/sign.d.ts +64 -0
  135. package/package.json +12 -13
  136. package/dist/client/index.js +0 -2
  137. package/dist/client/index.js.map +0 -1
  138. package/dist/core/index.js +0 -2
  139. package/dist/core/index.js.map +0 -1
  140. package/dist/model/index.js +0 -2
  141. package/dist/model/index.js.map +0 -1
  142. package/dist/transport/index.js +0 -2
  143. package/dist/transport/index.js.map +0 -1
@@ -0,0 +1,154 @@
1
+ import { type Logger } from "../../core/logger.js";
2
+ import type { P2PSession } from "./p2p-session.js";
3
+ /** A station's power tier — governs its idle window. */
4
+ export type PowerTier = "wired" | "battery";
5
+ /**
6
+ * A station open was abandoned because its entry was closed or superseded while the factory ran.
7
+ *
8
+ * Distinct from a connect failure: nothing is wrong with the device, the caller's reason to open it
9
+ * simply stopped applying. A speculative caller treats this as a non-event; anyone who asked for the
10
+ * session on a caller's behalf must still surface it.
11
+ */
12
+ export declare class SessionSupersededError extends Error {
13
+ }
14
+ /** Default idle window for a battery station before its session is closed to let the device sleep. */
15
+ export declare const BATTERY_IDLE_MS = 300000;
16
+ /** How long a single control command holds a session warm after dispatch (a burst keeps re-holding). */
17
+ export declare const COMMAND_KEEPALIVE_MS = 15000;
18
+ /**
19
+ * Default window a speculative pre-warm (e.g. after a doorbell ring) holds its user for. Expiring
20
+ * releases that user; it does not close the session — the station's own idle window then runs, so an
21
+ * unattended pre-warm on a battery station costs this plus {@link BATTERY_IDLE_MS}.
22
+ */
23
+ export declare const PREWARM_MS = 28000;
24
+ export interface SessionManagerOpts {
25
+ /** Idle window for battery stations (ms). Default {@link BATTERY_IDLE_MS}. */
26
+ batteryIdleMs?: number;
27
+ /** Keepalive a single command holds after dispatch (ms). Default {@link COMMAND_KEEPALIVE_MS}. */
28
+ commandKeepAliveMs?: number;
29
+ /** Power tier per station serial — injected by the facade (no model import). Default: everything `wired`. */
30
+ poweredFor?: (parentSn: string) => PowerTier;
31
+ /**
32
+ * Called after the manager closes a station on its OWN initiative — an elapsed idle window, or a
33
+ * deferred reset falling due.
34
+ *
35
+ * Those two are the only closes with no caller to follow up: everything riding the session is stale the
36
+ * moment it goes, and only the owner knows what that is. A close a caller asked for is that caller's to
37
+ * clean up after, which is why this does not fire for {@link SessionManager.close},
38
+ * {@link SessionManager.closeAll}, or a superseded open.
39
+ */
40
+ onAutoClose?: (parentSn: string) => void;
41
+ /** Diagnostics sink for the lifecycle transitions (open / idle-arm / detach). Omit for silence. */
42
+ logger?: Logger;
43
+ }
44
+ /**
45
+ * Manages P2P sessions keyed by **parent station serial**. The router builds/wires the actual
46
+ * `P2PSession` (it owns the socket + event fan-out); this decides open/close timing.
47
+ */
48
+ export declare class SessionManager {
49
+ private readonly opts;
50
+ private readonly entries;
51
+ /** Invalidates station factories that finish after {@link closeAll}. */
52
+ private generation;
53
+ private readonly logger;
54
+ constructor(opts?: SessionManagerOpts);
55
+ /** The live session for a station, or `undefined` if not open. */
56
+ get(parentSn: string): P2PSession | undefined;
57
+ /** Serials of stations with a live session. */
58
+ keys(): string[];
59
+ /** A plain `Map<parentSn, P2PSession>` snapshot of the live sessions (for `getSessions()` / tests). */
60
+ liveSessions(): Map<string, P2PSession>;
61
+ /** Get or create the lifecycle entry for a station. */
62
+ private entry;
63
+ /** Register an already-built session for test seeding or an externally assembled connection. */
64
+ register(parentSn: string, session: P2PSession): void;
65
+ /**
66
+ * Ensure a session to `parentSn` is open, building it via `factory` if cold. Concurrent calls for the
67
+ * same cold station share ONE connect (the `connecting` promise); `factory` builds + wires + awaits
68
+ * `connect()` and resolves the connected session.
69
+ */
70
+ acquire(parentSn: string, factory: (register: (session: P2PSession) => void) => Promise<P2PSession>): Promise<P2PSession>;
71
+ /** Add a reason to stay connected; cancels a pending idle-close. */
72
+ retain(parentSn: string): void;
73
+ /**
74
+ * Release a reason; arm the idle-close when the last one goes.
75
+ *
76
+ * A release with nothing retained is REFUSED rather than clamped to zero. Such a release was never
77
+ * earned on this entry, and letting it proceed would either restart a battery station's idle window
78
+ * from scratch or complete a deferred reset a real viewer has not yet earned. Clamping to zero did
79
+ * both silently.
80
+ */
81
+ release(parentSn: string): void;
82
+ /**
83
+ * Hold a session warm for `commandKeepAliveMs` after a control command, then release. A burst of
84
+ * commands each re-holds before the previous release fires, so the session never idles mid-burst.
85
+ */
86
+ bumpCommand(parentSn: string): void;
87
+ /**
88
+ * Retain a station and release it again after `ms` — the primitive behind command-keepalive and event
89
+ * pre-warm, and the only way to hold one open without an attachment to release it.
90
+ *
91
+ * The timer is owned by the entry, so {@link discard} cancels it. That ownership is the point: keyed
92
+ * only by serial, an expiring hold would otherwise outlive the entry it was taken on and release a
93
+ * retain counted by the SUCCESSOR entry — dropping a live viewer's count and arming an idle-detach
94
+ * underneath it.
95
+ */
96
+ hold(parentSn: string, ms: number): void;
97
+ /**
98
+ * Arm the idle-close timer for a station whose retain count just reached zero. A wired station with
99
+ * an infinite window is left persistent (no timer). Any subsequent {@link retain} cancels it.
100
+ */
101
+ private armIdle;
102
+ /**
103
+ * Close a station's session once its idle window elapses with nothing retained, letting the device sleep.
104
+ * Re-checks the count first (activity between the timer firing and now re-arms instead). Dropping the
105
+ * entry here and the session's own `close` → {@link remove} are both idempotent.
106
+ */
107
+ private onIdle;
108
+ /**
109
+ * Close a station the manager itself decided to close, and announce it.
110
+ *
111
+ * The announcement is the whole point: {@link SessionManagerOpts.onAutoClose} is how the owner learns
112
+ * about a teardown it did not request, and so the only way anything riding the session — a lingering
113
+ * live source, a talkback — gets dropped rather than handed out again over a dead connection.
114
+ *
115
+ * It fires the moment the entry is discarded, BEFORE the socket teardown and before any deferred reset
116
+ * settles. That is deliberate on both counts: from the instant the entry is gone a fresh acquisition
117
+ * resolves a new session while a cached source still points at the old one, so announcing later leaves
118
+ * a window in which a viewer can attach to a stale source; and a caller awaiting a reset should find
119
+ * the station's riders already dropped when it resumes.
120
+ *
121
+ * An entry that never carried a session is still torn down, but silently: a pre-warm whose open failed
122
+ * leaves one behind, and announcing it would report a station closed that was never reported open.
123
+ */
124
+ private autoClose;
125
+ /** Drop a station's entry + timer (called from the session's `close` handler). Idempotent. */
126
+ remove(parentSn: string): void;
127
+ /** Close one station now and discard its lifecycle entry. */
128
+ close(parentSn: string): Promise<void>;
129
+ /**
130
+ * Reset once every viewer detaches, ignoring only expiring holds.
131
+ *
132
+ * Every caller that arrives while one is already pending gets the SAME promise: the outcome is a
133
+ * property of the station's teardown, not of who asked, so one deferred per entry is the whole
134
+ * mechanism — and it cannot grow with the number of callers.
135
+ *
136
+ * Both branches close through {@link autoClose}: the caller asked for a recycle, not for the station's
137
+ * live sources to be dropped, so it does not clean up after one — exactly like the idle path.
138
+ */
139
+ resetWhenUnused(parentSn: string): Promise<void>;
140
+ /** Settle a discarded entry's reset callers with the same outcome as its session close. */
141
+ private settleReset;
142
+ /** Close one discarded entry and settle only its own reset callers before preserving any failure. */
143
+ private closeEntry;
144
+ /**
145
+ * Discard one lifecycle entry and return it for bounded close/reset completion.
146
+ *
147
+ * Every timer the entry owns dies with it — the idle window and any hold still counting down. A
148
+ * discarded entry owns no live timer, which is what stops a deferred release from landing on whatever
149
+ * entry next occupies this serial.
150
+ */
151
+ private discard;
152
+ /** Close every session and clear all timers. */
153
+ closeAll(): Promise<void>;
154
+ }
@@ -0,0 +1,431 @@
1
+ import { type Logger } from "../../core/logger.js";
2
+ import { type ParamSets } from "./annexb.js";
3
+ import type { LiveAudioFrame, LiveStreamConsumer, LiveStreamHandle, LiveVideoConfig, LiveVideoFrame } from "../../core/contracts.js";
4
+ /** Lifecycle state of a {@link SharedLiveSource}. */
5
+ export type SharedLiveState = "idle" | "warming" | "live" | "lingering" | "stopped";
6
+ export interface SharedLiveSourceOptions {
7
+ /**
8
+ * Factory that builds a fresh, **un-started** {@link LiveStreamHandle}. Called on every (re)warm so
9
+ * a reconnect rebuilds the stream rather than reusing a dead one. `SharedLiveSource` calls
10
+ * `.start()` itself.
11
+ *
12
+ * `ctx.reassertWanted` answers whether this pull still has anyone attached. A stream that re-asserts a
13
+ * channel to hold it open should consult it, so a pull nothing is watching stops competing for a station
14
+ * that serves one camera at a time.
15
+ */
16
+ makeStream: (ctx: {
17
+ reassertWanted: () => boolean;
18
+ }) => LiveStreamHandle;
19
+ /** No-consumer grace before teardown (default 8000ms). Distinct from the stream's keepalive. */
20
+ lingerMs?: number;
21
+ /** Per-consumer bounded queue depth; overflow → drop-to-keyframe (default 900 ≈ 30s @ 30fps). */
22
+ maxQueue?: number;
23
+ /** Rolling prebuffer window in seconds, 0 = off (default 0). */
24
+ preBufferSeconds?: number;
25
+ /**
26
+ * Warm-up start retry interval (default 2000ms). After warming, if no keyframe has arrived, the source
27
+ * re-issues the start ({@link LiveStreamHandle.nudge}) every interval — self-healing a start that
28
+ * raced the level-2 key negotiation, independent of any caller keepalive.
29
+ */
30
+ warmRetryMs?: number;
31
+ /**
32
+ * Warm-up deadline (default 20000ms). If no keyframe arrives within it, the source emits `error` to
33
+ * consumers ("failed to start") and tears down, so `live()` never hangs silently on a dead start.
34
+ */
35
+ warmTimeoutMs?: number;
36
+ /**
37
+ * Power source, a runtime device fact (`"battery"` incl. solar, or `"wired"`) — NOT a device family
38
+ * trait; the model derives it from the resolved capability set and passes it through. `"wired"`
39
+ * (default) streams unbounded; `"battery"` bounds a continuous stream to {@link batteryBudgetMs}.
40
+ */
41
+ powered?: "wired" | "battery";
42
+ /** Battery/solar continuous-stream budget before the `budget` notice fires (default 45000ms). */
43
+ batteryBudgetMs?: number;
44
+ /** Grace after the budget notice to call `extend()` before the source auto-stops (default 10000ms). */
45
+ budgetGraceMs?: number;
46
+ /** Diagnostics sink. Omit for silence. */
47
+ logger?: Logger;
48
+ /** Prefix label for log lines (e.g. the `parentSn:channel` key), for multi-source disambiguation. */
49
+ label?: string;
50
+ /**
51
+ * Called when the FIRST consumer attaches (0→1). The router uses this to register the source as a
52
+ * "user" of the station's P2P session (so an active stream cancels the session's idle-detach). Paired
53
+ * with {@link onIdle}. Optional — omit if the caller doesn't manage session lifecycle.
54
+ */
55
+ onActive?: () => void;
56
+ /** Called when the LAST consumer detaches (1→0) — the router releases its session user. See {@link onActive}. */
57
+ onIdle?: () => void;
58
+ /**
59
+ * Called when a stream is torn down having **never delivered a keyframe**, AFTER consumers have been told.
60
+ *
61
+ * A source can only rebuild its stream; it holds a factory, not the session that stream rides on. When
62
+ * the session — or the per-device state carried on it — is what has stopped serving this device, every
63
+ * rebuild starts another stream over the same session and dies the same way, so the owner of the session
64
+ * has to hear about it to do anything else.
65
+ *
66
+ * The condition is deliberately "no keyframe ever arrived", not "the warm-up deadline fired". A start can
67
+ * fail without that deadline being reached — an upstream error or stop can arrive first, the battery
68
+ * budget can stop the pull, and a caller that gives up before the deadline cancels it on the way out
69
+ * (`clearWarmWatch`) — and all of those are the same dead start. Enumerating the ways instead of naming
70
+ * the condition is how the case that actually happens gets left out.
71
+ *
72
+ * Not called by {@link SharedLiveSource.dispose}: the owner asked for that one, and it is the very thing
73
+ * an owner does in response to this callback.
74
+ */
75
+ onStartFailed?: () => void;
76
+ /**
77
+ * A media start was abandoned unacknowledged before anything was delivered, so this session is not being
78
+ * heard. The owner is asked for a replacement and calls {@link SharedLiveSource.rewarm} once it has one.
79
+ *
80
+ * Asked at most once per warm-up: further abandonments are the same session saying the same thing.
81
+ */
82
+ onSessionUnreachable?: () => void;
83
+ }
84
+ /**
85
+ * A single consumer of a {@link SharedLiveSource}. A {@link LiveStreamConsumer} (so `live()` can hand it
86
+ * back directly), plus the listener removal and arrival-timed feed the recording and readable egresses use.
87
+ */
88
+ export interface Consumer extends LiveStreamConsumer {
89
+ /** What this consumer holds the pull for. */
90
+ /** Detach a previously registered listener (mirrors {@link LiveStreamHandle.on}). */
91
+ off(event: "video", listener: (frame: LiveVideoFrame) => void): this;
92
+ off(event: "audio", listener: (frame: LiveAudioFrame) => void): this;
93
+ off(event: "start" | "stop", listener: () => void): this;
94
+ off(event: "error", listener: (err: Error) => void): this;
95
+ /** Subscribe to frames carrying the source-captured arrival time used by the prebuffer. */
96
+ onMedia(listener: (item: TimedMediaFrame) => void): this;
97
+ /** True once the source has replayed a cached keyframe to this consumer (no GOP wait on join). */
98
+ readonly primed: boolean;
99
+ /** Leave the source (refcount--). Idempotent. `stop()` is an alias (LiveStreamHandle). */
100
+ detach(): void;
101
+ }
102
+ /**
103
+ * One media frame retained with its transport-arrival time for prebuffer continuity.
104
+ *
105
+ * A video frame carries the coded configuration in force when it ARRIVED, because the item outlives that
106
+ * moment: it is the unit a keyframe-prime replays to a consumer that joined later and the unit a prebuffer
107
+ * drain hands over, and both have to announce the configuration their media was coded under rather than
108
+ * whichever one is current by the time they are delivered.
109
+ */
110
+ export type TimedMediaFrame = {
111
+ kind: "video";
112
+ frame: LiveVideoFrame;
113
+ timestampMs: number;
114
+ config: LiveVideoConfig;
115
+ } | {
116
+ kind: "audio";
117
+ frame: LiveAudioFrame;
118
+ timestampMs: number;
119
+ };
120
+ export declare class SharedLiveSource {
121
+ private readonly opts;
122
+ private stream?;
123
+ private readonly consumers;
124
+ /** No-consumer teardown grace (arm/cancel on the last-detach / re-attach transition). */
125
+ private readonly lingerTimer;
126
+ private _state;
127
+ private disposed;
128
+ /**
129
+ * What the CURRENT stream generation has delivered — replaced wholesale by every {@link warm}, so a new
130
+ * generation cannot inherit a previous one's evidence and no field can be forgotten in the reset.
131
+ *
132
+ * All three are read together to stage a start failure: `keyframe` is what makes a stream live at all,
133
+ * while `video` and `audio` are what separate a source that produced nothing from one whose units were
134
+ * never decodable and one that is answering with sound and no picture — the three stages of
135
+ * {@link LiveStreamStartError}.
136
+ */
137
+ private delivered;
138
+ /** Last keyframe access unit seen — replayed to a joining consumer (keyframe-prime). */
139
+ private lastKeyframe?;
140
+ /** Last parameter sets the stream announced — see {@link parameterSets}. */
141
+ private lastParamSets?;
142
+ /**
143
+ * The geometry the parameter sets in force state, and the sets it was read from.
144
+ *
145
+ * Holding the sets it came from is what keeps the read to one per announcement: `updatedParamSets`
146
+ * returns the SAME object when a frame announces nothing, so identity says the geometry cannot have
147
+ * moved without comparing any bytes.
148
+ */
149
+ private declaredGeometry?;
150
+ private configuredFrom?;
151
+ /** Rolling prebuffer, keyframe-alignable on drain. */
152
+ private ring;
153
+ /** Warm-up start-retry ticker (interval) + single-shot deadline; cleared once the first keyframe arrives. */
154
+ private warmRetryTimer?;
155
+ /** Whether this warm-up has already asked its owner to replace the session. */
156
+ private sessionReplacementAsked;
157
+ /**
158
+ * Whether the pending watch is a REUSE watch, which any frame settles.
159
+ *
160
+ * A cold warm-up needs a keyframe: nothing can be decoded without one. A join already holds the retained
161
+ * keyframe, so what its watch is missing is evidence the stream is still being served — and a delta frame is
162
+ * that evidence. Requiring a keyframe there let the deadline outlive an actively delivering stream whose
163
+ * group of pictures is longer than the window, and the timeout fails EVERY consumer.
164
+ */
165
+ private reuseWatch;
166
+ /** This source's opaque handle for tracing — see {@link SharedLiveSource.trace}. */
167
+ private readonly traceId;
168
+ /**
169
+ * How many re-issues this watch has spent with nothing arriving since it was armed.
170
+ *
171
+ * What a stream delivered BEFORE the current watch is no evidence about now — a reused stream's upstream may
172
+ * have served plenty and since been dropped by the station, and the retained keyframe replayed to a joining
173
+ * consumer says nothing either. Only a frame arriving after the watch was armed does, and {@link delivered}
174
+ * is reset to track exactly that.
175
+ *
176
+ * The first re-issue is therefore a keepalive: a reuse cannot yet know which case it is in, and a keepalive
177
+ * is right where the station is still serving and harmless where it is not. A second one due with nothing
178
+ * arrived is the answer — no bound of its own, the retry's own cadence.
179
+ */
180
+ private fruitlessReissues;
181
+ private readonly warmDeadlineTimer;
182
+ private warmAttempts;
183
+ /** Battery budget timer + post-notice grace timer (battery/solar sources only). */
184
+ private readonly budgetTimer;
185
+ private readonly budgetGraceTimer;
186
+ private readonly lingerMs;
187
+ private readonly maxQueue;
188
+ private readonly preBufferMs;
189
+ private readonly warmRetryMs;
190
+ private readonly warmTimeoutMs;
191
+ private readonly powered;
192
+ private readonly batteryBudgetMs;
193
+ private readonly budgetGraceMs;
194
+ private readonly logger;
195
+ private readonly tag;
196
+ constructor(opts: SharedLiveSourceOptions);
197
+ get state(): SharedLiveState;
198
+ get consumerCount(): number;
199
+ /**
200
+ * The parameter sets (SPS/PPS, plus VPS for H.265) most recently announced on this stream, or
201
+ * `undefined` before any have been seen.
202
+ *
203
+ * A camera commonly sends them ONCE, with the first keyframe of a stream. Every later access unit is
204
+ * then undecodable in isolation, so a consumer that collects a burst — and cannot see frames from
205
+ * before it joined — has no way to recover them. This source watches every frame from stream start,
206
+ * which makes it the only holder of the answer. A caller re-emits them ahead of its collected burst.
207
+ *
208
+ * Cleared when the stream is torn down, so a rebuilt stream never primes a burst with a dead stream's sets.
209
+ */
210
+ get parameterSets(): ParamSets | undefined;
211
+ /**
212
+ * Attach a new consumer. Warms the stream on the first attach (or cancels a pending linger teardown
213
+ * and reuses the warm stream), then replays the cached keyframe so the consumer can decode at once.
214
+ */
215
+ attach(): Consumer;
216
+ /**
217
+ * Attach at the same instant a keyframe-aligned prebuffer snapshot is taken. The returned consumer
218
+ * is not separately keyframe-primed, so replaying `buffered` followed by its live events neither
219
+ * duplicates the newest IDR nor leaves a gap at the handoff.
220
+ */
221
+ attachWithPrebuffer(seconds: number): {
222
+ consumer: Consumer;
223
+ buffered: TimedMediaFrame[];
224
+ };
225
+ private attachConsumer;
226
+ /**
227
+ * Emit a live trace under this source's opaque handle — `pull-N` by order of construction in this process.
228
+ *
229
+ * Not {@link SharedLiveSourceOptions.label}, which is the router's `stationSn:channel` key: that is a serial,
230
+ * and a serial in a retained record survives every redaction a host applies.
231
+ */
232
+ private trace;
233
+ /**
234
+ * Build the underlying stream, wire its frames into the fan-out, and start it.
235
+ *
236
+ * Every warm goes through here, so a source that is rebuilt on a replacement session listens on exactly
237
+ * the events the first attempt did.
238
+ */
239
+ private openStream;
240
+ /**
241
+ * Build + start the underlying stream, wire its frames into the fan-out, and watch the warm-up.
242
+ */
243
+ private warm;
244
+ /**
245
+ * A start was abandoned unacknowledged. Ask for a replacement session where nothing has been delivered yet.
246
+ *
247
+ * The abandonment is roughly twenty byte-identical sends with no reply, against acknowledgement latencies of
248
+ * 4–37 ms awake and 238 ms waking, so it is the session that is not being heard rather than a slow device —
249
+ * `P2PSession` says as much: the camera was never told to stream, so this warm-up can only time out. Where
250
+ * media has already flowed the abandonment means something else and this does nothing.
251
+ *
252
+ * The retry ticker is stopped while a replacement is awaited, because every tick it issues goes to the same
253
+ * unheard session. The DEADLINE is left running: the window belongs to the attempt, not to the session it
254
+ * started on.
255
+ */
256
+ private onStartUnacknowledged;
257
+ /**
258
+ * Warm again on a session the owner has replaced, inside the deadline the first attempt started.
259
+ *
260
+ * The previous stream is dropped rather than stopped through the state machine: it speaks to a session that
261
+ * is gone, and its `stop` would be read as an upstream end. Only a warm-up that asked for a replacement
262
+ * rewarms, so this is inert on a source that is streaming or has already failed.
263
+ */
264
+ rewarm(): void;
265
+ /**
266
+ * Arm the deadline a stream must deliver within, and the ticker that re-issues its start until it does.
267
+ *
268
+ * The deadline is armed before the ticker so that a retry falling on the same instant as the deadline is
269
+ * never issued, which keeps `attempts` on {@link LiveStreamStartError} equal to the number of media starts
270
+ * actually sent. A stream with no `nudge` cannot be retried, so its watch stays at one attempt however long
271
+ * the deadline is.
272
+ */
273
+ private armWarmWatch;
274
+ /**
275
+ * Re-issue this stream's media start, asking for a REAL start while nothing has arrived.
276
+ *
277
+ * On an own-session camera a re-issue is a keepalive once the session believes the channel is started, and
278
+ * that belief outlives a station which acknowledged a start and then served nothing: every later re-issue is
279
+ * then a keepalive holding a stream that was never started. Nothing arriving since this watch was armed, across
280
+ * more than one re-issue, is this source's own evidence that the channel is not being served — see
281
+ * {@link fruitlessReissues} for why one is not enough and why what the stream delivered earlier is not
282
+ * evidence. Once media arrives the keepalive is what is wanted, and an attached camera re-sends a full start
283
+ * either way.
284
+ */
285
+ private reissueStart;
286
+ /**
287
+ * Watch a stream this consumer joined rather than warmed, so a dead one cannot pass for a live one.
288
+ *
289
+ * A reused stream hands a joining consumer the retained keyframe at once, which is evidence about the past:
290
+ * it says the stream WAS being served, not that it still is. A caller commits to media on that frame — a
291
+ * process, a negotiated session — so a stream the station has quietly stopped serving strands it with no
292
+ * deadline, because warming is what arms one and a reuse skips warming by definition.
293
+ *
294
+ * The watch is the warm-up's own, deadline and retry alike, and the first frame to arrive AFTER the join
295
+ * clears it, that being the only frame which says the stream is still being served. A stream still serving
296
+ * clears it long before the deadline fires; one that is not fails its consumers exactly as a cold start that
297
+ * never delivered would.
298
+ *
299
+ * The start is re-issued at once and then on the retry's cadence, because a station that stopped serving a
300
+ * channel when its last consumer left is the very case the retry recovers: waiting the whole window to
301
+ * report what one re-issued start can fix is a timeout where a stream was available.
302
+ *
303
+ * Nothing is armed while a watch is already pending, so several consumers joining one reused stream share the
304
+ * watch the first of them started.
305
+ */
306
+ private watchReusedStream;
307
+ /** Arm the battery budget timer (battery/solar sources) — replaces any pending budget/grace. */
308
+ private armBudget;
309
+ private clearBudget;
310
+ /**
311
+ * Battery budget elapsed: notify consumers (with an {@link StreamBudgetNotice.extend} handle) and arm
312
+ * the grace timer. If no one extends within the grace, auto-stop the pull to protect the battery.
313
+ */
314
+ private onBudgetExpire;
315
+ /** Re-push the battery budget (host called `extend()` from the notice), cancelling the auto-stop. */
316
+ private extendBudget;
317
+ /** Settle a reuse watch on any frame — the join already holds a decodable picture. */
318
+ private settleReuseWatch;
319
+ /** Stop the warm-up retry + deadline (the stream is confirmed live). */
320
+ private clearWarmWatch;
321
+ /**
322
+ * No keyframe within the warm-up window — surface a start failure to consumers, tear down, and report the
323
+ * failed start to the owner (see {@link SharedLiveSourceOptions.onStartFailed}) so it can recycle what
324
+ * this source cannot reach.
325
+ */
326
+ private onWarmTimeout;
327
+ /**
328
+ * The typed failure for a start that produced no keyframe, staged by what the source did deliver: nothing
329
+ * at all, audio without a single video frame, or access units a decoder cannot begin at.
330
+ *
331
+ * Video takes precedence when both arrived: audio alongside video says nothing a caller needs, while video
332
+ * without a keyframe does.
333
+ */
334
+ private startFailure;
335
+ private onVideo;
336
+ /**
337
+ * The coded configuration this frame belongs to: what the parameter sets state, or the frame header's own
338
+ * report where they state nothing readable.
339
+ *
340
+ * The parameter sets are preferred because they define the size a decoder produces while the header only
341
+ * reports it. The fMP4 muxer prefers them for the same reason, though it answers from the sets ONE unit
342
+ * carried rather than from the sets in force, so the two can differ on a keyframe that re-states only a
343
+ * PPS — a muxer is handed frames, not this source's fold.
344
+ *
345
+ * Falling back rather than staying silent is what lets a consumer act on the announcement alone. A set
346
+ * whose geometry cannot be read would otherwise leave it with nothing to rebuild on, which is worse than
347
+ * the header it would have had to diff for itself.
348
+ *
349
+ * Only a keyframe carries parameter sets, so the read costs one parse per announcement: `updatedParamSets`
350
+ * answers with the same object when a frame announces none, and identity settles it from there.
351
+ */
352
+ private configOf;
353
+ private onAudio;
354
+ /**
355
+ * Retain the rolling window, trimmed to the same run a full-window drain asks for.
356
+ *
357
+ * Retention and drain obey one rule, because a ring trimmed tighter than the drain's rule cannot
358
+ * answer it — the media would already be gone. That rule is {@link windowStart}.
359
+ *
360
+ * A keyframe is the only place a run may begin, so it is also the only anchor a time-based trim has. A
361
+ * stream that stops coding them keeps its last decodable run until another keyframe gives the trim
362
+ * somewhere safe to move to: a separate frame-count ceiling would override the configured window, and
363
+ * cutting mid-group would leave retained media no decoder can start from.
364
+ */
365
+ private pushRing;
366
+ /**
367
+ * Drain the rolling prebuffer: a decodable run covering the last `seconds` of retained media, capped
368
+ * at the configured `preBufferSeconds`. Asking for none hands over none. The host decides when to
369
+ * drain (e.g. on a motion event) and where to send it.
370
+ *
371
+ * The run opens on the newest keyframe at or before the window starts, so it covers the whole request
372
+ * and over-delivers by however far back that keyframe sits — one keyframe interval on a steady stream,
373
+ * more where delivery stalled, since retention is timed on arrival and a frame carries no device clock.
374
+ * Beginning inside the window instead would under-deliver by that same distance, which on a short window
375
+ * is most of it, and a decoder allows no third option.
376
+ */
377
+ ringBuffer(seconds: number): LiveVideoFrame[];
378
+ private bufferedMedia;
379
+ /**
380
+ * Where a decodable run covering everything from `cutoff` onwards begins in the ring.
381
+ *
382
+ * The newest keyframe at or before `cutoff` is that place: it is the latest point a decoder can start
383
+ * from and still produce every frame in the window. When the ring reaches no further back than the
384
+ * cutoff, its oldest keyframe is the most of the window that exists. When it holds no keyframe at all,
385
+ * nothing in it is decodable and index `0` reports that to the caller, which checks.
386
+ */
387
+ private windowStart;
388
+ private isKeyframe;
389
+ /**
390
+ * Handle a consumer leaving. When the last one detaches (1→0), release the station-session user via
391
+ * {@link SharedLiveSourceOptions.onIdle} (its own longer idle timer then arms) and arm the stream's
392
+ * linger teardown.
393
+ */
394
+ private onDetach;
395
+ /** Refcount hit zero — arm the linger teardown. A new attach in the window cancels it. */
396
+ private arm;
397
+ /**
398
+ * Stop + drop the underlying stream and clear the prime/ring caches. Rebuildable via attach().
399
+ *
400
+ * The stream reference is dropped BEFORE stopping it, because `stop()` emits `"stop"` synchronously and
401
+ * this source listens for that — so stopping re-enters `teardown` through {@link onUpstreamEnd}. Clearing
402
+ * first makes that re-entry hit the `!this.stream` guard and return, which is what keeps a single
403
+ * teardown from reporting a failed start twice (and, before that report existed, from tearing down twice).
404
+ *
405
+ * `report` is false only for {@link dispose}: the owner asked for that one.
406
+ */
407
+ private teardown;
408
+ /**
409
+ * Underlying stream ended unexpectedly (station max-duration / reconnect): tell consumers.
410
+ *
411
+ * An end before the first keyframe is also a failed start, so those consumers get the typed `error`
412
+ * explaining why nothing played and then the `stop` that closes them — a bare `stop` would look like a
413
+ * normal end of stream to a caller still waiting for its first frame.
414
+ *
415
+ * Teardown runs even if notifying a consumer throws, because what it releases — the upstream stream, the
416
+ * warm-up timers, the ring — belongs to this source and not to the caller whose listener raised. The throw
417
+ * itself still propagates: a listener that raises is the caller's defect to see, not this source's to
418
+ * swallow.
419
+ */
420
+ private onUpstreamEnd;
421
+ /** Underlying stream failed: tell consumers, then tear down regardless (see {@link onUpstreamEnd}). */
422
+ private onUpstreamError;
423
+ /**
424
+ * Permanent shutdown (session close / router closeAll). Consumers get `stop`; no rebuild.
425
+ *
426
+ * Releases the session user when consumers were still attached: {@link SharedLiveSourceOptions.onActive}
427
+ * fired on the 0→1 transition, and this is the 1→0 one, so skipping it would leave the station pinned
428
+ * open for a source that can never serve anyone again.
429
+ */
430
+ dispose(): void;
431
+ }