@mega-yfue/eufy-sdk 0.5.0-beta.82 → 0.5.0-beta.84
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/core/contracts.d.ts +3 -4
- package/dist/index.js +37 -79
- package/dist/index.js.map +2 -2
- package/dist/transport/p2p/command-router.d.ts +1 -1
- package/dist/transport/p2p/media.d.ts +3 -5
- package/dist/transport/p2p/readable-egress.d.ts +1 -2
- package/dist/transport/p2p/shared-live-source.d.ts +15 -42
- package/package.json +1 -1
package/dist/core/contracts.d.ts
CHANGED
|
@@ -680,10 +680,9 @@ export interface LiveStreamConsumer extends LiveStreamHandle {
|
|
|
680
680
|
* consumer receives, so a consumer holding media it has not been told the configuration of is not a state
|
|
681
681
|
* it can reach.
|
|
682
682
|
*
|
|
683
|
-
* Per consumer, against what
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
* would leave both holding an encoder built for media they never received.
|
|
683
|
+
* Per consumer, against what this consumer was last given: a mid-session join begins at its next live
|
|
684
|
+
* keyframe, and a consumer crossing its queue bound resynchronises onto a later IDR. Each receives
|
|
685
|
+
* the configuration of the media it is delivered, even when another consumer has already seen it.
|
|
687
686
|
*
|
|
688
687
|
* Only a consumer announces this, never a bare {@link LiveStreamHandle}: it is read from the parameter
|
|
689
688
|
* sets a shared source watches every frame for, which the raw pull underneath it does not do. That is
|
package/dist/index.js
CHANGED
|
@@ -19128,10 +19128,9 @@ async function openLiveStream(session, opts = {}) {
|
|
|
19128
19128
|
async function captureSnapshotFromShared(source, opts = {}) {
|
|
19129
19129
|
const timeoutMs = opts.timeoutMs ?? 2e4;
|
|
19130
19130
|
const collectMs = opts.collectMs ?? 1500;
|
|
19131
|
-
const skip = opts.skipKeyframes ?? 1;
|
|
19132
19131
|
opts.signal?.throwIfAborted();
|
|
19133
19132
|
const consumer = source.attach();
|
|
19134
|
-
const
|
|
19133
|
+
const skip = opts.skipKeyframes ?? (source.state === "live" ? 0 : 1);
|
|
19135
19134
|
let burst;
|
|
19136
19135
|
try {
|
|
19137
19136
|
burst = await new Promise((resolve, reject) => {
|
|
@@ -19148,8 +19147,7 @@ async function captureSnapshotFromShared(source, opts = {}) {
|
|
|
19148
19147
|
if (fr.keyframe)
|
|
19149
19148
|
keyCount++;
|
|
19150
19149
|
if (!capturing) {
|
|
19151
|
-
|
|
19152
|
-
if (!fr.keyframe || keyCount <= threshold)
|
|
19150
|
+
if (!fr.keyframe || keyCount <= skip)
|
|
19153
19151
|
return;
|
|
19154
19152
|
capturing = true;
|
|
19155
19153
|
codec = fr.codec;
|
|
@@ -19159,7 +19157,7 @@ async function captureSnapshotFromShared(source, opts = {}) {
|
|
|
19159
19157
|
settle = setTimeout(() => {
|
|
19160
19158
|
cleanup();
|
|
19161
19159
|
resolve({ h264: Buffer.concat(bufs), codec, sets });
|
|
19162
|
-
},
|
|
19160
|
+
}, collectMs);
|
|
19163
19161
|
};
|
|
19164
19162
|
const onError = (err) => {
|
|
19165
19163
|
cleanup();
|
|
@@ -19549,30 +19547,24 @@ var ConsumerImpl = class extends EventEmitter4 {
|
|
|
19549
19547
|
queue = [];
|
|
19550
19548
|
paused = false;
|
|
19551
19549
|
detached = false;
|
|
19552
|
-
primed = false;
|
|
19553
19550
|
awaitingKeyframe = false;
|
|
19554
19551
|
/** The last coded configuration announced to THIS consumer — see {@link flush}. */
|
|
19555
19552
|
config;
|
|
19556
|
-
/**
|
|
19557
|
-
|
|
19558
|
-
constructor(onDetach, maxQueue) {
|
|
19553
|
+
/** Re-arm an ordinary join when its first video-bearing subscription starts, including after an async gap. */
|
|
19554
|
+
constructor(onDetach, maxQueue, waitForKeyframe) {
|
|
19559
19555
|
super();
|
|
19560
19556
|
this.onDetach = onDetach;
|
|
19561
19557
|
this.maxQueue = maxQueue;
|
|
19562
|
-
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
/** Stage a keyframe to replay when the first video listener subscribes. */
|
|
19573
|
-
prime(item) {
|
|
19574
|
-
this.primed = true;
|
|
19575
|
-
this.pendingPrime = item;
|
|
19558
|
+
if (waitForKeyframe) {
|
|
19559
|
+
this.on("newListener", (event) => {
|
|
19560
|
+
if (event !== "video" && event !== "media")
|
|
19561
|
+
return;
|
|
19562
|
+
if (this.listenerCount("video") > 0 || this.listenerCount("media") > 0)
|
|
19563
|
+
return;
|
|
19564
|
+
this.awaitingKeyframe = true;
|
|
19565
|
+
this.config = void 0;
|
|
19566
|
+
});
|
|
19567
|
+
}
|
|
19576
19568
|
}
|
|
19577
19569
|
/** Attached-by-construction — `start()` is a no-op so a Consumer satisfies LiveStreamHandle. */
|
|
19578
19570
|
start() {
|
|
@@ -19623,14 +19615,7 @@ var ConsumerImpl = class extends EventEmitter4 {
|
|
|
19623
19615
|
this.flush(q[i]);
|
|
19624
19616
|
}
|
|
19625
19617
|
}
|
|
19626
|
-
/**
|
|
19627
|
-
* Source → consumer video, honouring resync-to-keyframe and the bounded queue.
|
|
19628
|
-
*
|
|
19629
|
-
* Takes the whole item rather than a frame, because a keyframe-prime replays a RETAINED one and has to
|
|
19630
|
-
* arrive by the same route: a primed keyframe is exactly the IDR a resynchronising consumer is waiting
|
|
19631
|
-
* for, so it must clear that wait as a live keyframe does, and it carries the configuration its own media
|
|
19632
|
-
* was coded under. While that wait is unsatisfied a delta frame is dropped — nothing can begin at it.
|
|
19633
|
-
*/
|
|
19618
|
+
/** Source → consumer video, dropping dependent pictures until a subscribed consumer receives a keyframe. */
|
|
19634
19619
|
deliverVideo(item) {
|
|
19635
19620
|
if (this.detached)
|
|
19636
19621
|
return;
|
|
@@ -19675,9 +19660,8 @@ var ConsumerImpl = class extends EventEmitter4 {
|
|
|
19675
19660
|
* about ahead of the frame that carries it.
|
|
19676
19661
|
*
|
|
19677
19662
|
* The single delivery point is where the announcement belongs, because every way media reaches a
|
|
19678
|
-
* consumer passes through here
|
|
19679
|
-
*
|
|
19680
|
-
* saw is what makes a primed join and a post-overflow resynchronisation correct.
|
|
19663
|
+
* consumer passes through here, including a backlog drained by {@link resume}. The comparison is
|
|
19664
|
+
* against what this consumer was last given rather than what the source last saw.
|
|
19681
19665
|
*/
|
|
19682
19666
|
flush(item) {
|
|
19683
19667
|
if (item.kind === "video") {
|
|
@@ -19731,8 +19715,8 @@ var SharedLiveSource = class {
|
|
|
19731
19715
|
* {@link LiveStreamStartError}.
|
|
19732
19716
|
*/
|
|
19733
19717
|
delivered = { keyframe: false, video: false, audio: false };
|
|
19734
|
-
/**
|
|
19735
|
-
|
|
19718
|
+
/** Whether the upstream has delivered a keyframe since its last teardown. */
|
|
19719
|
+
hasKeyframe = false;
|
|
19736
19720
|
/** Last parameter sets the stream announced — see {@link parameterSets}. */
|
|
19737
19721
|
lastParamSets;
|
|
19738
19722
|
/**
|
|
@@ -19750,14 +19734,7 @@ var SharedLiveSource = class {
|
|
|
19750
19734
|
warmRetryTimer;
|
|
19751
19735
|
/** Whether this warm-up has already asked its owner to replace the session. */
|
|
19752
19736
|
sessionReplacementAsked = false;
|
|
19753
|
-
/**
|
|
19754
|
-
* Whether the pending watch is a REUSE watch, which any frame settles.
|
|
19755
|
-
*
|
|
19756
|
-
* A cold warm-up needs a keyframe: nothing can be decoded without one. A join already holds the retained
|
|
19757
|
-
* keyframe, so what its watch is missing is evidence the stream is still being served — and a delta frame is
|
|
19758
|
-
* that evidence. Requiring a keyframe there let the deadline outlive an actively delivering stream whose
|
|
19759
|
-
* group of pictures is longer than the window, and the timeout fails EVERY consumer.
|
|
19760
|
-
*/
|
|
19737
|
+
/** A reuse watch settles on upstream media independently of each consumer's keyframe gate. */
|
|
19761
19738
|
reuseWatch = false;
|
|
19762
19739
|
/** This source's opaque handle for tracing — see {@link SharedLiveSource.trace}. */
|
|
19763
19740
|
traceId;
|
|
@@ -19765,8 +19742,8 @@ var SharedLiveSource = class {
|
|
|
19765
19742
|
* How many re-issues this watch has spent with nothing arriving since it was armed.
|
|
19766
19743
|
*
|
|
19767
19744
|
* What a stream delivered BEFORE the current watch is no evidence about now — a reused stream's upstream may
|
|
19768
|
-
* have served plenty and since been dropped by the station
|
|
19769
|
-
*
|
|
19745
|
+
* have served plenty and since been dropped by the station. Only a frame arriving after the watch was
|
|
19746
|
+
* armed proves current delivery, and {@link delivered}
|
|
19770
19747
|
* is reset to track exactly that.
|
|
19771
19748
|
*
|
|
19772
19749
|
* The first re-issue is therefore a keepalive: a reuse cannot yet know which case it is in, and a keepalive
|
|
@@ -19825,40 +19802,38 @@ var SharedLiveSource = class {
|
|
|
19825
19802
|
}
|
|
19826
19803
|
/**
|
|
19827
19804
|
* Attach a new consumer. Warms the stream on the first attach (or cancels a pending linger teardown
|
|
19828
|
-
* and reuses the warm stream)
|
|
19805
|
+
* and reuses the warm stream). Delivery starts at the next live keyframe so every dependent
|
|
19806
|
+
* picture follows its reference pictures without a gap from a cached keyframe.
|
|
19829
19807
|
*/
|
|
19830
19808
|
attach() {
|
|
19831
19809
|
return this.attachConsumer(true);
|
|
19832
19810
|
}
|
|
19833
19811
|
/**
|
|
19834
19812
|
* Attach at the same instant a keyframe-aligned prebuffer snapshot is taken. The returned consumer
|
|
19835
|
-
*
|
|
19836
|
-
* duplicates the newest IDR nor leaves a gap at the handoff.
|
|
19813
|
+
* continues after `buffered` without waiting for another keyframe or duplicating the newest IDR.
|
|
19837
19814
|
*/
|
|
19838
19815
|
attachWithPrebuffer(seconds) {
|
|
19839
19816
|
const consumer = this.attachConsumer(false);
|
|
19840
19817
|
return { consumer, buffered: this.bufferedMedia(seconds) };
|
|
19841
19818
|
}
|
|
19842
|
-
attachConsumer(
|
|
19819
|
+
attachConsumer(waitForKeyframe) {
|
|
19843
19820
|
if (this.disposed)
|
|
19844
19821
|
throw new Error("SharedLiveSource is disposed");
|
|
19845
19822
|
const wasEmpty = this.consumers.size === 0;
|
|
19846
|
-
const consumer = new ConsumerImpl((c) => this.onDetach(c), this.maxQueue);
|
|
19823
|
+
const consumer = new ConsumerImpl((c) => this.onDetach(c), this.maxQueue, waitForKeyframe);
|
|
19847
19824
|
this.consumers.add(consumer);
|
|
19848
19825
|
if (wasEmpty)
|
|
19849
19826
|
this.opts.onActive?.();
|
|
19850
19827
|
if (this.lingerTimer.pending) {
|
|
19851
19828
|
this.lingerTimer.cancel();
|
|
19852
19829
|
if (this._state === "lingering")
|
|
19853
|
-
this._state = this.
|
|
19830
|
+
this._state = this.hasKeyframe ? "live" : "warming";
|
|
19854
19831
|
this.logger.debug(`${this.tag} re-attach in linger window \u2014 reusing warm stream (consumers=${this.consumers.size})`);
|
|
19855
19832
|
}
|
|
19856
19833
|
if (!this.stream)
|
|
19857
19834
|
this.warm();
|
|
19858
19835
|
else
|
|
19859
19836
|
this.watchReusedStream();
|
|
19860
|
-
if (prime && this.lastKeyframe)
|
|
19861
|
-
consumer.prime(this.lastKeyframe);
|
|
19862
19837
|
return consumer;
|
|
19863
19838
|
}
|
|
19864
19839
|
/**
|
|
@@ -19990,24 +19965,9 @@ var SharedLiveSource = class {
|
|
|
19990
19965
|
current.nudge(!arrivedSinceWatch && this.fruitlessReissues > 1);
|
|
19991
19966
|
}
|
|
19992
19967
|
/**
|
|
19993
|
-
* Watch
|
|
19994
|
-
*
|
|
19995
|
-
*
|
|
19996
|
-
* it says the stream WAS being served, not that it still is. A caller commits to media on that frame — a
|
|
19997
|
-
* process, a negotiated session — so a stream the station has quietly stopped serving strands it with no
|
|
19998
|
-
* deadline, because warming is what arms one and a reuse skips warming by definition.
|
|
19999
|
-
*
|
|
20000
|
-
* The watch is the warm-up's own, deadline and retry alike, and the first frame to arrive AFTER the join
|
|
20001
|
-
* clears it, that being the only frame which says the stream is still being served. A stream still serving
|
|
20002
|
-
* clears it long before the deadline fires; one that is not fails its consumers exactly as a cold start that
|
|
20003
|
-
* never delivered would.
|
|
20004
|
-
*
|
|
20005
|
-
* The start is re-issued at once and then on the retry's cadence, because a station that stopped serving a
|
|
20006
|
-
* channel when its last consumer left is the very case the retry recovers: waiting the whole window to
|
|
20007
|
-
* report what one re-issued start can fix is a timeout where a stream was available.
|
|
20008
|
-
*
|
|
20009
|
-
* Nothing is armed while a watch is already pending, so several consumers joining one reused stream share the
|
|
20010
|
-
* watch the first of them started.
|
|
19968
|
+
* Watch an existing upstream with the warm-up retry and deadline. Any video or audio received after
|
|
19969
|
+
* the watch begins confirms delivery and settles it. Each consumer's keyframe gate remains separate.
|
|
19970
|
+
* Concurrent joins share a pending watch; the first join reissues the start immediately.
|
|
20011
19971
|
*/
|
|
20012
19972
|
watchReusedStream() {
|
|
20013
19973
|
if (this.warmDeadlineTimer.pending || this.warmRetryTimer !== void 0)
|
|
@@ -20056,7 +20016,7 @@ var SharedLiveSource = class {
|
|
|
20056
20016
|
this.clearBudget();
|
|
20057
20017
|
this.budgetTimer.arm(timerMs(ms, this.batteryBudgetMs), () => this.onBudgetExpire());
|
|
20058
20018
|
}
|
|
20059
|
-
/** Settle a reuse watch on any frame
|
|
20019
|
+
/** Settle a reuse watch on any upstream frame without changing consumer keyframe gates. */
|
|
20060
20020
|
settleReuseWatch() {
|
|
20061
20021
|
if (!this.reuseWatch)
|
|
20062
20022
|
return;
|
|
@@ -20118,7 +20078,7 @@ var SharedLiveSource = class {
|
|
|
20118
20078
|
this.lastParamSets = updatedParamSets(frame.data, this.lastParamSets);
|
|
20119
20079
|
const item = { kind: "video", frame, timestampMs: Date.now(), config: this.configOf(frame) };
|
|
20120
20080
|
if (frame.keyframe) {
|
|
20121
|
-
this.
|
|
20081
|
+
this.hasKeyframe = true;
|
|
20122
20082
|
const wasWarming = this.warmRetryTimer !== void 0 || this.warmDeadlineTimer.pending;
|
|
20123
20083
|
this.delivered.keyframe = true;
|
|
20124
20084
|
if (this._state === "warming")
|
|
@@ -20251,7 +20211,7 @@ var SharedLiveSource = class {
|
|
|
20251
20211
|
this.lingerTimer.arm(this.lingerMs, () => this.teardown("stopped"));
|
|
20252
20212
|
}
|
|
20253
20213
|
/**
|
|
20254
|
-
* Stop + drop the underlying stream and clear the
|
|
20214
|
+
* Stop + drop the underlying stream and clear the keyframe/ring caches. Rebuildable via attach().
|
|
20255
20215
|
*
|
|
20256
20216
|
* The stream reference is dropped BEFORE stopping it, because `stop()` emits `"stop"` synchronously and
|
|
20257
20217
|
* this source listens for that — so stopping re-enters `teardown` through {@link onUpstreamEnd}. Clearing
|
|
@@ -20271,7 +20231,7 @@ var SharedLiveSource = class {
|
|
|
20271
20231
|
stream?.stop();
|
|
20272
20232
|
} catch {
|
|
20273
20233
|
}
|
|
20274
|
-
this.
|
|
20234
|
+
this.hasKeyframe = false;
|
|
20275
20235
|
this.lastParamSets = void 0;
|
|
20276
20236
|
this.declaredGeometry = void 0;
|
|
20277
20237
|
this.configuredFrom = void 0;
|
|
@@ -20674,8 +20634,6 @@ function openReadableFromConsumer(consumer, opts = {}) {
|
|
|
20674
20634
|
if (!readable.push(value))
|
|
20675
20635
|
consumer.pause();
|
|
20676
20636
|
});
|
|
20677
|
-
consumer.on("video", () => {
|
|
20678
|
-
});
|
|
20679
20637
|
consumer.on("stop", () => readable.push(null));
|
|
20680
20638
|
consumer.on("error", (err) => readable.destroy(err));
|
|
20681
20639
|
return readable;
|
|
@@ -22187,7 +22145,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22187
22145
|
* A {@link MediaProvider} bound to one serial — resolves the session then calls `p2p/media`.
|
|
22188
22146
|
*
|
|
22189
22147
|
* Every live egress here is a consumer of the SAME shared pull, so N `live()` calls collapse to one
|
|
22190
|
-
* PPCS session and a live snapshot against a warm
|
|
22148
|
+
* PPCS session and a live snapshot against a warm shared source costs no extra pull at all; a
|
|
22191
22149
|
* cold source warms one and waits for a clean keyframe. Each shared egress passes its complete options
|
|
22192
22150
|
* through because any of them may create the source, whose power and retention hints are fixed for
|
|
22193
22151
|
* everyone who joins later. The bounded {@link MediaProvider.record} clip is the exception: it opens its
|