@mega-yfue/eufy-sdk 0.5.0-beta.5 → 0.5.0-beta.7

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.js CHANGED
@@ -15699,8 +15699,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15699
15699
  return this.connected;
15700
15700
  }
15701
15701
  /**
15702
- * Ask the OS for {@link RECEIVE_BUFFER_BYTES} on a bound socket, and warn once per session when it grants
15703
- * less or refuses.
15702
+ * Ask the OS for {@link RECEIVE_BUFFER_BYTES} on a bound socket, and log once per session when it grants
15703
+ * less (debug) or refuses (warn).
15704
15704
  *
15705
15705
  * The request is made here rather than through `createSocket`'s `recvBufferSize`: Node applies that option
15706
15706
  * inside the bind callback, where a refusal is thrown out of reach of this session and ends the process.
@@ -15716,7 +15716,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15716
15716
  if (granted >= RECEIVE_BUFFER_BYTES || this.receiveBufferReported)
15717
15717
  return;
15718
15718
  this.receiveBufferReported = true;
15719
- this.logger.warn(`[p2p] ${this.cfg.stationSn} UDP receive buffer below the ${RECEIVE_BUFFER_BYTES} bytes requested` + (granted > 0 ? ` (granted ${granted})` : " (request refused)") + `; live video can lose keyframes. Raise the OS limit (net.core.rmem_max on Linux, kern.ipc.maxsockbuf on BSD) to at least ${RECEIVE_BUFFER_BYTES}.`);
15719
+ const [level, outcome] = granted > 0 ? ["debug", ` (granted ${granted})`] : ["warn", " (request refused)"];
15720
+ this.logger[level](`[p2p] ${this.cfg.stationSn} UDP receive buffer below the ${RECEIVE_BUFFER_BYTES} bytes requested` + outcome + `; live video can lose keyframes. Raise the OS limit (net.core.rmem_max on Linux, kern.ipc.maxsockbuf on BSD) to at least ${RECEIVE_BUFFER_BYTES}.`);
15720
15721
  }
15721
15722
  /** Open the socket and start the lookup → hole-punch handshake. */
15722
15723
  async connect() {
@@ -16809,11 +16810,13 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16809
16810
  if (advance > SEQUENCE_LOOKBACK) {
16810
16811
  if (65536 - advance <= STALE_RETRANSMIT_DEPTH)
16811
16812
  return;
16812
- if (this.pendingByDataType.has(dataType) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16813
+ if ((dataType === P2PDataType.VIDEO || this.pendingByDataType.has(dataType)) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16813
16814
  this.trace({ phase: "sequence-restart", dataType });
16814
16815
  this.clearReorderTimer(dataType);
16815
16816
  this.reorderByType.delete(dataType);
16816
16817
  this.pendingByDataType.delete(dataType);
16818
+ if (dataType === P2PDataType.VIDEO)
16819
+ this.emit("videoGap");
16817
16820
  this.lastSeqByType.set(dataType, seqNo);
16818
16821
  this.reassemble(dataType, msg.subarray(8));
16819
16822
  return;
@@ -16879,9 +16882,11 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16879
16882
  */
16880
16883
  abandonHole(dataType) {
16881
16884
  const held = this.reorderByType.get(dataType).held;
16882
- if (this.pendingByDataType.has(dataType) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16885
+ if ((dataType === P2PDataType.VIDEO || this.pendingByDataType.has(dataType)) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16883
16886
  this.trace({ phase: "datagram-gap", dataType });
16884
16887
  this.pendingByDataType.delete(dataType);
16888
+ if (dataType === P2PDataType.VIDEO)
16889
+ this.emit("videoGap");
16885
16890
  const last = this.lastSeqByType.get(dataType);
16886
16891
  const earliest = [...held.keys()].sort((a, b) => (a - last & 65535) - (b - last & 65535))[0];
16887
16892
  this.lastSeqByType.set(dataType, earliest - 1 & 65535);
@@ -17518,7 +17523,8 @@ var AccessUnitAssembler = class {
17518
17523
  * `decode` extracts the payload of a frame, whatever the stream's encryption: it is called for EVERY
17519
17524
  * frame, including a continuation, because a continuation carries its own wrapped key ahead of its share
17520
17525
  * of the payload (measured: 129 bytes beyond what its header declares, exactly as a unit's first frame
17521
- * carries).
17526
+ * carries). A continuation without its matching open unit has no Annex-B start code and cannot be emitted
17527
+ * as a complete unit, even if its repeated header says keyframe.
17522
17528
  */
17523
17529
  push(payload, decode) {
17524
17530
  const header = parseVideoFrameHeader(payload);
@@ -17538,6 +17544,8 @@ var AccessUnitAssembler = class {
17538
17544
  return [unitOf(open2.header, Buffer.concat(open2.chunks))];
17539
17545
  }
17540
17546
  this.discard();
17547
+ if (!beginsAccessUnit(body))
17548
+ return [];
17541
17549
  if (full) {
17542
17550
  this.open = { header, chunks: [body], carried: body.length };
17543
17551
  return [];
@@ -17891,6 +17899,12 @@ var LiveStream = class extends EventEmitter3 {
17891
17899
  lastCodec = "h264";
17892
17900
  /** Rebuilds an access unit the station split across several frames — see {@link AccessUnitAssembler}. */
17893
17901
  units = new AccessUnitAssembler((drop) => this.reportDroppedUnit(drop));
17902
+ /** Whether a missing video datagram or incomplete unit invalidated the current reference chain. */
17903
+ awaitingKeyframeAfterGap = false;
17904
+ videoGapHandler = () => {
17905
+ this.units.discard();
17906
+ this.awaitingKeyframeAfterGap = true;
17907
+ };
17894
17908
  /** The channel inbound media must be tagged with, once {@link acceptsMedia} trusts the station's tag. */
17895
17909
  mediaChannel;
17896
17910
  tracedFirstVideoCommand = false;
@@ -17933,6 +17947,7 @@ var LiveStream = class extends EventEmitter3 {
17933
17947
  return this;
17934
17948
  this.listening = true;
17935
17949
  this.session.on("data", this.handler);
17950
+ this.session.on("videoGap", this.videoGapHandler);
17936
17951
  this.session.on("liveStartUnacknowledged", this.unackedHandler);
17937
17952
  this.sendStart();
17938
17953
  const keepAliveMs = this.opts.keepAliveMs ?? DEFAULT_KEEPALIVE_MS;
@@ -18030,6 +18045,8 @@ var LiveStream = class extends EventEmitter3 {
18030
18045
  return;
18031
18046
  this.listening = false;
18032
18047
  this.session.off("data", this.handler);
18048
+ this.session.off("videoGap", this.videoGapHandler);
18049
+ this.awaitingKeyframeAfterGap = false;
18033
18050
  this.session.off("liveStartUnacknowledged", this.unackedHandler);
18034
18051
  if (this.kaTimer)
18035
18052
  clearInterval(this.kaTimer);
@@ -18065,6 +18082,11 @@ var LiveStream = class extends EventEmitter3 {
18065
18082
  try {
18066
18083
  if (f.commandId === CMD_VIDEO_FRAME2) {
18067
18084
  for (const unit of this.units.push(f.data, (payload) => this.annexbOf(payload, f.signCode))) {
18085
+ if (this.awaitingKeyframeAfterGap) {
18086
+ if (!unit.keyframe)
18087
+ continue;
18088
+ this.awaitingKeyframeAfterGap = false;
18089
+ }
18068
18090
  if (!this.tracedFirstVideoUnit) {
18069
18091
  this.tracedFirstVideoUnit = true;
18070
18092
  this.trace({ phase: "first-video-unit", keyframe: unit.keyframe });
@@ -18159,6 +18181,7 @@ var LiveStream = class extends EventEmitter3 {
18159
18181
  * otherwise silent in both directions: no frame reaches a consumer, and nothing states why.
18160
18182
  */
18161
18183
  reportDroppedUnit(drop) {
18184
+ this.awaitingKeyframeAfterGap = true;
18162
18185
  const message = `[live] dropped an incomplete access unit (${drop.carried} bytes in ${drop.chunks} frame(s), tail never arrived, ${drop.count} so far)`;
18163
18186
  if (drop.count === 1)
18164
18187
  this.logger.warn(message);