@mega-yfue/eufy-sdk 0.5.0-beta.82 → 0.5.0-beta.83

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.
@@ -262,7 +262,7 @@ export declare class P2PCommandRouter {
262
262
  * A {@link MediaProvider} bound to one serial — resolves the session then calls `p2p/media`.
263
263
  *
264
264
  * Every live egress here is a consumer of the SAME shared pull, so N `live()` calls collapse to one
265
- * PPCS session and a live snapshot against a warm, keyframe-primed source costs no extra pull at all; a
265
+ * PPCS session and a live snapshot against a warm shared source costs no extra pull at all; a
266
266
  * cold source warms one and waits for a clean keyframe. Each shared egress passes its complete options
267
267
  * through because any of them may create the source, whose power and retention hints are fixed for
268
268
  * everyone who joins later. The bounded {@link MediaProvider.record} clip is the exception: it opens its
@@ -24,11 +24,9 @@ import type { SharedLiveSource } from "./shared-live-source.js";
24
24
  export declare function openLiveStream(session: P2PSession, opts?: LiveStreamOptions): Promise<LiveStream>;
25
25
  /**
26
26
  * **Live snapshot off a SHARED source** (V6) — snapshot as just another consumer of the shared live
27
- * pull. If the source is already warm and has a cached keyframe (V2 keyframe-prime), the joining
28
- * consumer receives that IDR immediately and we decode it with **no extra pull** — a snapshot while
29
- * someone else watches costs nothing on the wire. Otherwise we warm the source and wait for a clean
30
- * keyframe (the first IDR after a cold start is frequently partial, so skip it by default). Requires
31
- * `ffmpeg` for the Annex-B → JPEG decode.
27
+ * pull. A warm source reuses its upstream session; collection starts from live keyframes rather than
28
+ * a retained picture. A live source captures its next keyframe by default; a cold source skips the
29
+ * first keyframe because its first IDR is frequently partial. Requires `ffmpeg` for the Annex-B → JPEG decode.
32
30
  *
33
31
  * The returned dimensions are the ENCODED IMAGE's own, read back out of it — never the frame header's.
34
32
  * The header states the stream's geometry when the capture started, and a stream that reconfigures
@@ -185,6 +185,10 @@ export declare class P2PSession extends EventEmitter {
185
185
  * change (the level-2 key arrived after an initial level-1 start), otherwise sends the 1139 nudge.
186
186
  */
187
187
  private readonly liveStartedChannels;
188
+ /** The `cipher_id` this connection's `CMD_GATEWAYINFO` reply named, once its negotiation has read it. */
189
+ private gatewayCipherId?;
190
+ /** The `n` of the last `[0x01, n]` header this connection sent in the clear — see {@link nextClearCommandBytes}. */
191
+ private clearCommandCount;
188
192
  private readonly unackedLiveStarts;
189
193
  private liveStartRetransmitTimer?;
190
194
  private readonly logger;
@@ -396,6 +400,9 @@ export declare class P2PSession extends EventEmitter {
396
400
  * `video_type` is 12, and `transaction` is the millisecond timestamp as a string. The device answers a
397
401
  * start carrying these values with a keyframe; `encryptkey` is the modulus it RSA-wraps each keyframe's
398
402
  * AES media key with (unwrapped by {@link decodeVideoFrame}).
403
+ *
404
+ * With `clearMsgId`, the body of a start sent in the clear: `{accountId, encryptkey, msg_id, streamtype: 1}`
405
+ * only, `msg_id` being `clearMsgId`, which changes from one start to the next.
399
406
  */
400
407
  private startLiveJson;
401
408
  /**
@@ -403,8 +410,20 @@ export declare class P2PSession extends EventEmitter {
403
410
  * (GCM, signCode 8, frame type 10) when the session has a negotiated key, else level-1 (AES-128-ECB,
404
411
  * signCode 1, frame type 11) — chosen by the session key, not the device family. The camera RSA-wraps
405
412
  * each keyframe's AES media key with the `encryptkey` modulus (unwrapped by {@link decodeVideoFrame}).
413
+ * A session that {@link sendsInTheClear} sends it unencrypted instead: signCode 0, frame type 0, header
414
+ * bytes `[0x01, n]` from {@link nextClearCommandBytes}, and `n` as the body's `msg_id`.
406
415
  */
407
416
  private sendStartLiveOwnSession;
417
+ /**
418
+ * Whether this session's own-session start and stop go in the clear: its `CMD_GATEWAYINFO` reply named
419
+ * `cipher_id` 0.
420
+ */
421
+ private sendsInTheClear;
422
+ /**
423
+ * Header bytes 10-11 of this connection's next command sent in the clear: `[0x01, n]`, with `n` counting
424
+ * those commands from 1 and never 0. A retransmit repeats its `n`, and the device's reply echoes it.
425
+ */
426
+ private nextClearCommandBytes;
408
427
  /**
409
428
  * Repeat unacknowledged own-session live starts until the device takes one. Runs only while a start is
410
429
  * outstanding.
@@ -475,6 +494,8 @@ export declare class P2PSession extends EventEmitter {
475
494
  * `uint32`, the same 4-byte body as the attached talkback frames. The app sends this frame on closing
476
495
  * an own-session camera's live view; the camera stops streaming on it within a second, and keeps
477
496
  * streaming through the 1350-wrapped form.
497
+ * - own-session on a session that {@link sendsInTheClear}: the same 4-byte body unencrypted, signCode 0,
498
+ * header bytes from {@link nextClearCommandBytes}.
478
499
  * - without a level-2 key: the bare 1004 command.
479
500
  */
480
501
  stopLiveMedia(channel?: number, accountId?: string, homeBaseAttached?: boolean): void;
@@ -25,7 +25,6 @@ export interface ReadableEgressOptions {
25
25
  /**
26
26
  * Wrap a shared-source {@link Consumer} in a fresh Readable. The consumer is detached when the
27
27
  * Readable is destroyed/ended, so callers own the lifetime by owning the stream.
28
- * Frames arrive through onMedia; an empty video listener releases the keyframe-prime
29
- * because ConsumerImpl replays it when the first video listener is added.
28
+ * Frames arrive through onMedia, preserving their source-arrival metadata.
30
29
  */
31
30
  export declare function openReadableFromConsumer(consumer: Consumer, opts?: ReadableEgressOptions): Readable;
@@ -96,7 +96,6 @@ export interface SharedLiveSourceOptions {
96
96
  * back directly), plus the listener removal and arrival-timed feed the recording and readable egresses use.
97
97
  */
98
98
  export interface Consumer extends LiveStreamConsumer {
99
- /** What this consumer holds the pull for. */
100
99
  /** Detach a previously registered listener (mirrors {@link LiveStreamHandle.on}). */
101
100
  off(event: "video", listener: (frame: LiveVideoFrame) => void): this;
102
101
  off(event: "audio", listener: (frame: LiveAudioFrame) => void): this;
@@ -104,18 +103,14 @@ export interface Consumer extends LiveStreamConsumer {
104
103
  off(event: "error", listener: (err: Error) => void): this;
105
104
  /** Subscribe to frames carrying the source-captured arrival time used by the prebuffer. */
106
105
  onMedia(listener: (item: TimedMediaFrame) => void): this;
107
- /** True once the source has replayed a cached keyframe to this consumer (no GOP wait on join). */
108
- readonly primed: boolean;
109
106
  /** Leave the source (refcount--). Idempotent. `stop()` is an alias (LiveStreamHandle). */
110
107
  detach(): void;
111
108
  }
112
109
  /**
113
110
  * One media frame retained with its transport-arrival time for prebuffer continuity.
114
111
  *
115
- * A video frame carries the coded configuration in force when it ARRIVED, because the item outlives that
116
- * moment: it is the unit a keyframe-prime replays to a consumer that joined later and the unit a prebuffer
117
- * drain hands over, and both have to announce the configuration their media was coded under rather than
118
- * whichever one is current by the time they are delivered.
112
+ * A video frame carries the coded configuration in force when it arrived, so a prebuffer drain
113
+ * announces the configuration its retained media was coded under.
119
114
  */
120
115
  export type TimedMediaFrame = {
121
116
  kind: "video";
@@ -145,8 +140,8 @@ export declare class SharedLiveSource {
145
140
  * {@link LiveStreamStartError}.
146
141
  */
147
142
  private delivered;
148
- /** Last keyframe access unit seen — replayed to a joining consumer (keyframe-prime). */
149
- private lastKeyframe?;
143
+ /** Whether the upstream has delivered a keyframe since its last teardown. */
144
+ private hasKeyframe;
150
145
  /** Last parameter sets the stream announced — see {@link parameterSets}. */
151
146
  private lastParamSets?;
152
147
  /**
@@ -164,14 +159,7 @@ export declare class SharedLiveSource {
164
159
  private warmRetryTimer?;
165
160
  /** Whether this warm-up has already asked its owner to replace the session. */
166
161
  private sessionReplacementAsked;
167
- /**
168
- * Whether the pending watch is a REUSE watch, which any frame settles.
169
- *
170
- * A cold warm-up needs a keyframe: nothing can be decoded without one. A join already holds the retained
171
- * keyframe, so what its watch is missing is evidence the stream is still being served — and a delta frame is
172
- * that evidence. Requiring a keyframe there let the deadline outlive an actively delivering stream whose
173
- * group of pictures is longer than the window, and the timeout fails EVERY consumer.
174
- */
162
+ /** A reuse watch settles on upstream media independently of each consumer's keyframe gate. */
175
163
  private reuseWatch;
176
164
  /** This source's opaque handle for tracing — see {@link SharedLiveSource.trace}. */
177
165
  private readonly traceId;
@@ -179,8 +167,8 @@ export declare class SharedLiveSource {
179
167
  * How many re-issues this watch has spent with nothing arriving since it was armed.
180
168
  *
181
169
  * What a stream delivered BEFORE the current watch is no evidence about now — a reused stream's upstream may
182
- * have served plenty and since been dropped by the station, and the retained keyframe replayed to a joining
183
- * consumer says nothing either. Only a frame arriving after the watch was armed does, and {@link delivered}
170
+ * have served plenty and since been dropped by the station. Only a frame arriving after the watch was
171
+ * armed proves current delivery, and {@link delivered}
184
172
  * is reset to track exactly that.
185
173
  *
186
174
  * The first re-issue is therefore a keepalive: a reuse cannot yet know which case it is in, and a keepalive
@@ -220,13 +208,13 @@ export declare class SharedLiveSource {
220
208
  get parameterSets(): ParamSets | undefined;
221
209
  /**
222
210
  * Attach a new consumer. Warms the stream on the first attach (or cancels a pending linger teardown
223
- * and reuses the warm stream), then replays the cached keyframe so the consumer can decode at once.
211
+ * and reuses the warm stream). Delivery starts at the next live keyframe so every dependent
212
+ * picture follows its reference pictures without a gap from a cached keyframe.
224
213
  */
225
214
  attach(): Consumer;
226
215
  /**
227
216
  * Attach at the same instant a keyframe-aligned prebuffer snapshot is taken. The returned consumer
228
- * is not separately keyframe-primed, so replaying `buffered` followed by its live events neither
229
- * duplicates the newest IDR nor leaves a gap at the handoff.
217
+ * continues after `buffered` without waiting for another keyframe or duplicating the newest IDR.
230
218
  */
231
219
  attachWithPrebuffer(seconds: number): {
232
220
  consumer: Consumer;
@@ -298,24 +286,9 @@ export declare class SharedLiveSource {
298
286
  */
299
287
  private reissueStart;
300
288
  /**
301
- * Watch a stream this consumer joined rather than warmed, so a dead one cannot pass for a live one.
302
- *
303
- * A reused stream hands a joining consumer the retained keyframe at once, which is evidence about the past:
304
- * it says the stream WAS being served, not that it still is. A caller commits to media on that frame — a
305
- * process, a negotiated session — so a stream the station has quietly stopped serving strands it with no
306
- * deadline, because warming is what arms one and a reuse skips warming by definition.
307
- *
308
- * The watch is the warm-up's own, deadline and retry alike, and the first frame to arrive AFTER the join
309
- * clears it, that being the only frame which says the stream is still being served. A stream still serving
310
- * clears it long before the deadline fires; one that is not fails its consumers exactly as a cold start that
311
- * never delivered would.
312
- *
313
- * The start is re-issued at once and then on the retry's cadence, because a station that stopped serving a
314
- * channel when its last consumer left is the very case the retry recovers: waiting the whole window to
315
- * report what one re-issued start can fix is a timeout where a stream was available.
316
- *
317
- * Nothing is armed while a watch is already pending, so several consumers joining one reused stream share the
318
- * watch the first of them started.
289
+ * Watch an existing upstream with the warm-up retry and deadline. Any video or audio received after
290
+ * the watch begins confirms delivery and settles it. Each consumer's keyframe gate remains separate.
291
+ * Concurrent joins share a pending watch; the first join reissues the start immediately.
319
292
  */
320
293
  private watchReusedStream;
321
294
  /** Arm the battery budget timer (battery/solar sources) — replaces any pending budget/grace. */
@@ -328,7 +301,7 @@ export declare class SharedLiveSource {
328
301
  private onBudgetExpire;
329
302
  /** Re-push the battery budget (host called `extend()` from the notice), cancelling the auto-stop. */
330
303
  private extendBudget;
331
- /** Settle a reuse watch on any frame — the join already holds a decodable picture. */
304
+ /** Settle a reuse watch on any upstream frame without changing consumer keyframe gates. */
332
305
  private settleReuseWatch;
333
306
  /** Stop the warm-up retry + deadline (the stream is confirmed live). */
334
307
  private clearWarmWatch;
@@ -410,7 +383,7 @@ export declare class SharedLiveSource {
410
383
  /** Refcount hit zero — arm the linger teardown. A new attach in the window cancels it. */
411
384
  private arm;
412
385
  /**
413
- * Stop + drop the underlying stream and clear the prime/ring caches. Rebuildable via attach().
386
+ * Stop + drop the underlying stream and clear the keyframe/ring caches. Rebuildable via attach().
414
387
  *
415
388
  * The stream reference is dropped BEFORE stopping it, because `stop()` emits `"stop"` synchronously and
416
389
  * this source listens for that — so stopping re-enters `teardown` through {@link onUpstreamEnd}. Clearing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.5.0-beta.82",
3
+ "version": "0.5.0-beta.83",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",