@mega-yfue/eufy-sdk 0.5.0-beta.73 → 0.5.0-beta.74

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.
@@ -456,8 +456,7 @@ export declare class EufyMega extends EventEmitter {
456
456
  private rtcRouteFor;
457
457
  /**
458
458
  * The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
459
- * `device_channel` resolves back to it on the station; `undefined` otherwise, including a camera behind
460
- * a T8N00, whose route carries commands only.
459
+ * `device_channel` resolves back to it on the station; `undefined` otherwise, unless an NVR camera has its own dedicated media call.
461
460
  */
462
461
  private rtcMediaFor;
463
462
  /**
package/dist/index.js CHANGED
@@ -3655,6 +3655,7 @@ var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
3655
3655
  var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
3656
3656
  var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
3657
3657
  var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
3658
+ var isNvrMediaCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
3658
3659
  var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
3659
3660
  var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
3660
3661
  var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
@@ -18698,16 +18699,18 @@ function h265Geometry(sps) {
18698
18699
  };
18699
18700
  }
18700
18701
 
18701
- // dist/transport/p2p/live-stream.js
18702
- var CMD_VIDEO_FRAME2 = 1300;
18702
+ // dist/transport/p2p/audio-frame.js
18703
18703
  var CMD_AUDIO_FRAME2 = 1301;
18704
- var VIDEO_HEADER_LEN = 22;
18705
18704
  var AUDIO_HEADER_LEN = 16;
18706
18705
  var AUDIO_TYPE_OFFSET = 5;
18706
+ var AUDIO_CODECS = { 0: "aac-lc", 2: "g711a", 7: "aac-eld" };
18707
+
18708
+ // dist/transport/p2p/live-stream.js
18709
+ var CMD_VIDEO_FRAME2 = 1300;
18710
+ var VIDEO_HEADER_LEN = 22;
18707
18711
  var SC4 = Buffer.from([0, 0, 0, 1]);
18708
18712
  var SC3 = Buffer.from([0, 0, 1]);
18709
18713
  var AAC_ELD_AUDIO_SPECIFIC_CONFIG = Buffer.from("f8f03000", "hex");
18710
- var AUDIO_CODECS = { 0: "aac-lc", 2: "g711a", 7: "aac-eld" };
18711
18714
  var MAX_TRACED_DECODE_FAILURES = 3;
18712
18715
  var DEFAULT_KEEPALIVE_MS = 3e3;
18713
18716
  var LiveStream = class extends EventEmitter3 {
@@ -23423,6 +23426,8 @@ var RtcLiveStream = class extends EventEmitter7 {
23423
23426
  opts;
23424
23427
  running = false;
23425
23428
  startSegment = -1;
23429
+ onConnected = () => this.sendStart();
23430
+ onError = (error) => this.fail(error);
23426
23431
  keepalive;
23427
23432
  width = 0;
23428
23433
  height = 0;
@@ -23439,11 +23444,24 @@ var RtcLiveStream = class extends EventEmitter7 {
23439
23444
  start() {
23440
23445
  if (this.running)
23441
23446
  return this;
23442
- const { session, seg, accountId } = this.opts;
23447
+ const { session } = this.opts;
23443
23448
  this.running = true;
23449
+ this.startSegment = -1;
23444
23450
  session.on("mediaData", this.onMedia);
23445
23451
  session.on("commandData", this.onCommand);
23446
23452
  session.on("close", this.onClose);
23453
+ if (this.opts.nvr) {
23454
+ session.on("connected", this.onConnected);
23455
+ session.on("error", this.onError);
23456
+ session.connect().catch(this.onError);
23457
+ } else
23458
+ this.sendStart();
23459
+ return this;
23460
+ }
23461
+ sendStart() {
23462
+ if (!this.running || this.startSegment !== -1)
23463
+ return;
23464
+ const { session, seg, accountId, nvr } = this.opts;
23447
23465
  this.startSegment = seg.next();
23448
23466
  const sent2 = session.sendCommand(buildPortalPacket({
23449
23467
  commandId: PORTAL_CMD_SET_PAYLOAD,
@@ -23459,7 +23477,7 @@ var RtcLiveStream = class extends EventEmitter7 {
23459
23477
  camera_type: 0,
23460
23478
  key: "",
23461
23479
  msg_id: 116,
23462
- audio_chn: -1,
23480
+ audio_chn: nvr ? this.opts.channel : -1,
23463
23481
  streamtype: 2,
23464
23482
  stitch_mode: 1,
23465
23483
  chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
@@ -23471,7 +23489,7 @@ var RtcLiveStream = class extends EventEmitter7 {
23471
23489
  }));
23472
23490
  if (!sent2) {
23473
23491
  queueMicrotask(() => this.fail(new Error(`rtc live ${this.tag}: command channel not open`)));
23474
- return this;
23492
+ return;
23475
23493
  }
23476
23494
  this.keepalive = setInterval(() => {
23477
23495
  if (!session.sendRaw(PORTAL_KEEPALIVE))
@@ -23479,20 +23497,22 @@ var RtcLiveStream = class extends EventEmitter7 {
23479
23497
  }, PORTAL_KEEPALIVE_MS);
23480
23498
  this.keepalive.unref?.();
23481
23499
  this.opts.logger?.debug?.(`[rtc] ${this.tag} live start sent`);
23482
- return this;
23483
23500
  }
23484
23501
  stop() {
23485
23502
  if (!this.running)
23486
23503
  return;
23487
23504
  this.teardown();
23488
23505
  const { session, seg, accountId } = this.opts;
23489
- session.sendCommand(buildPortalPacket({
23490
- commandId: PORTAL_CMD_SET_PAYLOAD,
23491
- channel: PORTAL_STATION_CHANNEL,
23492
- segment: seg.next(),
23493
- isResponse: LIVE_STREAM_ID,
23494
- payload: { account_id: accountId, cmd: LIVE_STOP, payload: {} }
23495
- }));
23506
+ if (this.startSegment !== -1)
23507
+ session.sendCommand(buildPortalPacket({
23508
+ commandId: PORTAL_CMD_SET_PAYLOAD,
23509
+ channel: PORTAL_STATION_CHANNEL,
23510
+ segment: seg.next(),
23511
+ isResponse: LIVE_STREAM_ID,
23512
+ payload: { account_id: accountId, cmd: LIVE_STOP, payload: {} }
23513
+ }));
23514
+ if (this.opts.nvr)
23515
+ session.close();
23496
23516
  this.emit("stop");
23497
23517
  }
23498
23518
  teardown() {
@@ -23504,11 +23524,15 @@ var RtcLiveStream = class extends EventEmitter7 {
23504
23524
  session.off("mediaData", this.onMedia);
23505
23525
  session.off("commandData", this.onCommand);
23506
23526
  session.off("close", this.onClose);
23527
+ session.off("connected", this.onConnected);
23528
+ session.off("error", this.onError);
23507
23529
  }
23508
23530
  fail(err) {
23509
23531
  if (!this.running)
23510
23532
  return;
23511
23533
  this.teardown();
23534
+ if (this.opts.nvr)
23535
+ this.opts.session.close();
23512
23536
  this.emit("error", err);
23513
23537
  }
23514
23538
  /** The start's ACK: a non-zero result code ends the pull. */
@@ -23521,7 +23545,20 @@ var RtcLiveStream = class extends EventEmitter7 {
23521
23545
  }
23522
23546
  onMediaFrame(frame) {
23523
23547
  const h = parsePortalHeader(frame);
23524
- if (!h || h.commandId !== LIVE_MEDIA || h.channel !== LIVE_MEDIA_CHANNEL)
23548
+ if (!h || h.channel !== LIVE_MEDIA_CHANNEL)
23549
+ return;
23550
+ if (h.commandId === CMD_AUDIO_FRAME2 && this.opts.nvr) {
23551
+ if (h.paramLength !== frame.length - PORTAL_HEADER_LENGTH || h.paramLength <= AUDIO_HEADER_LEN)
23552
+ return;
23553
+ const body = frame.subarray(PORTAL_HEADER_LENGTH);
23554
+ const codec = AUDIO_CODECS[body[AUDIO_TYPE_OFFSET]];
23555
+ if (body.readUInt32LE(0) !== body.length - AUDIO_HEADER_LEN || codec !== "aac-lc" && codec !== "g711a")
23556
+ return;
23557
+ const audio = { codec, data: body.subarray(AUDIO_HEADER_LEN) };
23558
+ this.emit("audio", audio);
23559
+ return;
23560
+ }
23561
+ if (h.commandId !== LIVE_MEDIA)
23525
23562
  return;
23526
23563
  if (h.paramLength > frame.length - PORTAL_HEADER_LENGTH)
23527
23564
  return;
@@ -24782,38 +24819,54 @@ var RtcCommandRouter = class {
24782
24819
  * session. Recording has no wire here and is refused.
24783
24820
  */
24784
24821
  trackMediaProviderFor(route) {
24785
- const sn = route.stationSn;
24786
- const sourceFor = (hints = {}) => {
24787
- let source = this.callSources.get(sn);
24822
+ return this.callMediaProviderFor(route, route.stationSn, (session) => new RtcCallLiveStream(session, this.deps.logger), `record is not available for ${route.stationSn} over a portal call`, 45e3);
24823
+ }
24824
+ /** NVR media uses a dedicated call per camera, independent of station commands. */
24825
+ nvrMediaProviderFor(route) {
24826
+ return this.callMediaProviderFor(route, route.cameraSn, (session, accountId) => new RtcLiveStream({
24827
+ session,
24828
+ seg: new SegmentCounter(),
24829
+ stationSn: route.stationSn,
24830
+ channel: route.channel,
24831
+ accountId,
24832
+ logger: this.deps.logger,
24833
+ nvr: true
24834
+ }), "record is not available over an NVR portal call");
24835
+ }
24836
+ /** Share a dedicated-call source and its consumer operations under one route key. */
24837
+ callMediaProviderFor(route, key, makeStream, recordError, warmTimeoutMs) {
24838
+ const sourceFor = (opts = {}) => {
24839
+ opts.signal?.throwIfAborted();
24840
+ let source = this.callSources.get(key);
24788
24841
  if (!source) {
24789
24842
  source = new SharedLiveSource({
24790
24843
  makeStream: () => {
24791
24844
  const identity = this.deps.identity();
24792
24845
  if (!identity)
24793
- throw new Error(`rtc: not logged in, cannot call ${sn}`);
24794
- const session = this.newSession(route, route.adminUserId || identity.userId, identity);
24795
- return new RtcCallLiveStream(session, this.deps.logger);
24846
+ throw new Error(`rtc: not logged in, cannot call ${route.stationSn}`);
24847
+ const accountId = route.adminUserId || identity.userId;
24848
+ return makeStream(this.newSession(route, accountId, identity), accountId);
24796
24849
  },
24797
- label: sn,
24850
+ label: key,
24798
24851
  logger: this.deps.logger,
24799
- powered: hints.powered,
24800
- preBufferSeconds: hints.preBufferSeconds,
24801
- warmTimeoutMs: 45e3
24852
+ powered: opts.powered,
24853
+ preBufferSeconds: opts.preBufferSeconds,
24854
+ warmTimeoutMs
24802
24855
  });
24803
- this.callSources.set(sn, source);
24856
+ this.callSources.set(key, source);
24804
24857
  }
24805
24858
  return source;
24806
24859
  };
24807
24860
  return {
24808
24861
  live: async (opts) => attachUnlessAborted(sourceFor(opts), opts?.signal),
24809
24862
  openReadable: async (opts) => openReadableFromConsumer(attachUnlessAborted(sourceFor(opts), opts?.signal), opts),
24810
- snapshotLive: (opts) => captureSnapshotFromShared(sourceFor(opts), {
24863
+ snapshotLive: async (opts) => captureSnapshotFromShared(sourceFor(opts), {
24811
24864
  ...opts,
24812
24865
  logger: this.deps.logger,
24813
24866
  ffmpegPath: this.deps.ffmpegPath
24814
24867
  }),
24815
24868
  record: async () => {
24816
- throw new Error(`record is not available for ${sn} over a portal call`);
24869
+ throw new Error(recordError);
24817
24870
  }
24818
24871
  };
24819
24872
  }
@@ -29446,19 +29499,24 @@ var EufyMega = class extends EventEmitter14 {
29446
29499
  }
29447
29500
  /**
29448
29501
  * The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
29449
- * `device_channel` resolves back to it on the station; `undefined` otherwise, including a camera behind
29450
- * a T8N00, whose route carries commands only.
29502
+ * `device_channel` resolves back to it on the station; `undefined` otherwise, unless an NVR camera has its own dedicated media call.
29451
29503
  */
29452
29504
  rtcMediaFor(sn) {
29453
29505
  const route = this.rtcRouteFor(sn);
29454
29506
  if (route?.mediaTrack)
29455
29507
  return this.rtc.trackMediaProviderFor(route);
29456
- if (!route?.attached || route.signalingMode === "call")
29508
+ if (!route?.attached)
29457
29509
  return void 0;
29458
- const raw = this.registry.list().find((d) => d.sn === sn)?.raw ?? {};
29510
+ const camera = this.registry.list().find((d) => d.sn === sn);
29511
+ const raw = camera?.raw ?? {};
29459
29512
  const channel = typeof raw.device_channel === "number" ? raw.device_channel : void 0;
29460
29513
  if (channel === void 0 || this.registry.serialForFrame(route.stationSn, channel) !== sn)
29461
29514
  return void 0;
29515
+ if (route.signalingMode === "call") {
29516
+ if (!camera || !isNvrMediaCamera(camera))
29517
+ return void 0;
29518
+ return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel });
29519
+ }
29462
29520
  return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
29463
29521
  }
29464
29522
  /**