@mega-yfue/eufy-sdk 0.5.0-beta.72 → 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.
- package/dist/client/eufy-mega.d.ts +1 -2
- package/dist/index.js +107 -38
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/motion.d.ts +12 -2
- package/dist/model/device-family.d.ts +2 -0
- package/dist/transport/p2p/audio-frame.d.ts +14 -0
- package/dist/transport/rtc/command-router.d.ts +4 -0
- package/dist/transport/rtc/live.d.ts +9 -4
- package/package.json +1 -1
|
@@ -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,
|
|
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";
|
|
@@ -4235,6 +4236,10 @@ function decodeAiDetectType(value) {
|
|
|
4235
4236
|
pet: !!(value & AiDetectType.pet)
|
|
4236
4237
|
};
|
|
4237
4238
|
}
|
|
4239
|
+
function decodeAiDetectTypeParam(raw) {
|
|
4240
|
+
const value = typeof raw === "object" && raw !== null ? raw.ai_detect_type : raw;
|
|
4241
|
+
return typeof value === "number" && Number.isInteger(value) ? value : void 0;
|
|
4242
|
+
}
|
|
4238
4243
|
function snoozePayload(seconds) {
|
|
4239
4244
|
if (seconds <= 0)
|
|
4240
4245
|
return { snooze_time: 0 };
|
|
@@ -4331,11 +4336,15 @@ var MOTION_MEMBERS = {
|
|
|
4331
4336
|
* 1298 holds the detailed AI-type BITMASK (live-observed: `0x30000` enabled-base | type bits, e.g. a
|
|
4332
4337
|
* T8425 reads `0x3000f`). Cams also report 1299 (`hbAiDetectType`) but that is a separate, simpler
|
|
4333
4338
|
* value (1) — NOT this bitmask, so 1298 is the read/write id.
|
|
4339
|
+
*
|
|
4340
|
+
* The value arrives in two shapes, both read by the app's 1298 parser: a bare bitmask, or a JSON
|
|
4341
|
+
* object carrying the same bitmask as `ai_detect_type` (see {@link decodeAiDetectTypeParam}).
|
|
4334
4342
|
*/
|
|
4335
4343
|
aiDetectType: {
|
|
4336
4344
|
param: MOTION_CMD.AI_DETECT_TYPE,
|
|
4337
|
-
type: "
|
|
4338
|
-
|
|
4345
|
+
type: "string",
|
|
4346
|
+
decode: decodeAiDetectTypeParam,
|
|
4347
|
+
decodedKind: "bitfield",
|
|
4339
4348
|
provenance: "verified",
|
|
4340
4349
|
description: "AI detection type bitmask \u2014 which classes trigger detection. \u2705 Bits decoded live + confirmed vs the app: 0x30000 = enabled base, bit0 = human recognition, bit1 = human detection, bit2 = vehicle, bit3 = pet (see AiDetectType / encodeAiDetectType). \u2705 WRITE HW-verified live on T8124 (1350 SET_PAYLOAD, {ai_detect_type, channel}): each write landed byte-exact + read back (0x30003 \u2192 0x8 \u2192 0x3000b).",
|
|
4341
4350
|
write: (v, ctx) => {
|
|
@@ -12336,10 +12345,13 @@ var SECURITY_PARAMS = {
|
|
|
12336
12345
|
type: "string",
|
|
12337
12346
|
provenance: "apk"
|
|
12338
12347
|
},
|
|
12348
|
+
// A camera answers a bare bitmask; a camera behind an NVR-class hub answers a JSON object carrying
|
|
12349
|
+
// it as `ai_detect_type` beside `ai_switch`, `ai_notification`, `ai_use_global` and `channel`.
|
|
12339
12350
|
1298: {
|
|
12340
12351
|
name: "aiDetectType",
|
|
12341
|
-
type: "
|
|
12342
|
-
provenance: "apk"
|
|
12352
|
+
type: "string",
|
|
12353
|
+
provenance: "apk",
|
|
12354
|
+
encoding: "json"
|
|
12343
12355
|
},
|
|
12344
12356
|
// Cameras and a HomeBase 3 answer a bare number; a HomeBase S1 Pro (T9000) answers a JSON envelope
|
|
12345
12357
|
// (`{"ai_switch":1,"smart_detection":…,"devlist":[…]}`).
|
|
@@ -13219,7 +13231,7 @@ var CLEAN_PARAMS = {
|
|
|
13219
13231
|
},
|
|
13220
13232
|
155: {
|
|
13221
13233
|
name: "direction",
|
|
13222
|
-
type: "
|
|
13234
|
+
type: "string",
|
|
13223
13235
|
provenance: "mega"
|
|
13224
13236
|
},
|
|
13225
13237
|
156: {
|
|
@@ -18687,16 +18699,18 @@ function h265Geometry(sps) {
|
|
|
18687
18699
|
};
|
|
18688
18700
|
}
|
|
18689
18701
|
|
|
18690
|
-
// dist/transport/p2p/
|
|
18691
|
-
var CMD_VIDEO_FRAME2 = 1300;
|
|
18702
|
+
// dist/transport/p2p/audio-frame.js
|
|
18692
18703
|
var CMD_AUDIO_FRAME2 = 1301;
|
|
18693
|
-
var VIDEO_HEADER_LEN = 22;
|
|
18694
18704
|
var AUDIO_HEADER_LEN = 16;
|
|
18695
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;
|
|
18696
18711
|
var SC4 = Buffer.from([0, 0, 0, 1]);
|
|
18697
18712
|
var SC3 = Buffer.from([0, 0, 1]);
|
|
18698
18713
|
var AAC_ELD_AUDIO_SPECIFIC_CONFIG = Buffer.from("f8f03000", "hex");
|
|
18699
|
-
var AUDIO_CODECS = { 0: "aac-lc", 2: "g711a", 7: "aac-eld" };
|
|
18700
18714
|
var MAX_TRACED_DECODE_FAILURES = 3;
|
|
18701
18715
|
var DEFAULT_KEEPALIVE_MS = 3e3;
|
|
18702
18716
|
var LiveStream = class extends EventEmitter3 {
|
|
@@ -23412,6 +23426,8 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23412
23426
|
opts;
|
|
23413
23427
|
running = false;
|
|
23414
23428
|
startSegment = -1;
|
|
23429
|
+
onConnected = () => this.sendStart();
|
|
23430
|
+
onError = (error) => this.fail(error);
|
|
23415
23431
|
keepalive;
|
|
23416
23432
|
width = 0;
|
|
23417
23433
|
height = 0;
|
|
@@ -23428,11 +23444,24 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23428
23444
|
start() {
|
|
23429
23445
|
if (this.running)
|
|
23430
23446
|
return this;
|
|
23431
|
-
const { session
|
|
23447
|
+
const { session } = this.opts;
|
|
23432
23448
|
this.running = true;
|
|
23449
|
+
this.startSegment = -1;
|
|
23433
23450
|
session.on("mediaData", this.onMedia);
|
|
23434
23451
|
session.on("commandData", this.onCommand);
|
|
23435
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;
|
|
23436
23465
|
this.startSegment = seg.next();
|
|
23437
23466
|
const sent2 = session.sendCommand(buildPortalPacket({
|
|
23438
23467
|
commandId: PORTAL_CMD_SET_PAYLOAD,
|
|
@@ -23448,7 +23477,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23448
23477
|
camera_type: 0,
|
|
23449
23478
|
key: "",
|
|
23450
23479
|
msg_id: 116,
|
|
23451
|
-
audio_chn: -1,
|
|
23480
|
+
audio_chn: nvr ? this.opts.channel : -1,
|
|
23452
23481
|
streamtype: 2,
|
|
23453
23482
|
stitch_mode: 1,
|
|
23454
23483
|
chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
|
|
@@ -23460,7 +23489,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23460
23489
|
}));
|
|
23461
23490
|
if (!sent2) {
|
|
23462
23491
|
queueMicrotask(() => this.fail(new Error(`rtc live ${this.tag}: command channel not open`)));
|
|
23463
|
-
return
|
|
23492
|
+
return;
|
|
23464
23493
|
}
|
|
23465
23494
|
this.keepalive = setInterval(() => {
|
|
23466
23495
|
if (!session.sendRaw(PORTAL_KEEPALIVE))
|
|
@@ -23468,20 +23497,22 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23468
23497
|
}, PORTAL_KEEPALIVE_MS);
|
|
23469
23498
|
this.keepalive.unref?.();
|
|
23470
23499
|
this.opts.logger?.debug?.(`[rtc] ${this.tag} live start sent`);
|
|
23471
|
-
return this;
|
|
23472
23500
|
}
|
|
23473
23501
|
stop() {
|
|
23474
23502
|
if (!this.running)
|
|
23475
23503
|
return;
|
|
23476
23504
|
this.teardown();
|
|
23477
23505
|
const { session, seg, accountId } = this.opts;
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
|
-
|
|
23482
|
-
|
|
23483
|
-
|
|
23484
|
-
|
|
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();
|
|
23485
23516
|
this.emit("stop");
|
|
23486
23517
|
}
|
|
23487
23518
|
teardown() {
|
|
@@ -23493,11 +23524,15 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23493
23524
|
session.off("mediaData", this.onMedia);
|
|
23494
23525
|
session.off("commandData", this.onCommand);
|
|
23495
23526
|
session.off("close", this.onClose);
|
|
23527
|
+
session.off("connected", this.onConnected);
|
|
23528
|
+
session.off("error", this.onError);
|
|
23496
23529
|
}
|
|
23497
23530
|
fail(err) {
|
|
23498
23531
|
if (!this.running)
|
|
23499
23532
|
return;
|
|
23500
23533
|
this.teardown();
|
|
23534
|
+
if (this.opts.nvr)
|
|
23535
|
+
this.opts.session.close();
|
|
23501
23536
|
this.emit("error", err);
|
|
23502
23537
|
}
|
|
23503
23538
|
/** The start's ACK: a non-zero result code ends the pull. */
|
|
@@ -23510,7 +23545,20 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23510
23545
|
}
|
|
23511
23546
|
onMediaFrame(frame) {
|
|
23512
23547
|
const h = parsePortalHeader(frame);
|
|
23513
|
-
if (!h || h.
|
|
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)
|
|
23514
23562
|
return;
|
|
23515
23563
|
if (h.paramLength > frame.length - PORTAL_HEADER_LENGTH)
|
|
23516
23564
|
return;
|
|
@@ -24771,38 +24819,54 @@ var RtcCommandRouter = class {
|
|
|
24771
24819
|
* session. Recording has no wire here and is refused.
|
|
24772
24820
|
*/
|
|
24773
24821
|
trackMediaProviderFor(route) {
|
|
24774
|
-
|
|
24775
|
-
|
|
24776
|
-
|
|
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);
|
|
24777
24841
|
if (!source) {
|
|
24778
24842
|
source = new SharedLiveSource({
|
|
24779
24843
|
makeStream: () => {
|
|
24780
24844
|
const identity = this.deps.identity();
|
|
24781
24845
|
if (!identity)
|
|
24782
|
-
throw new Error(`rtc: not logged in, cannot call ${
|
|
24783
|
-
const
|
|
24784
|
-
return
|
|
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);
|
|
24785
24849
|
},
|
|
24786
|
-
label:
|
|
24850
|
+
label: key,
|
|
24787
24851
|
logger: this.deps.logger,
|
|
24788
|
-
powered:
|
|
24789
|
-
preBufferSeconds:
|
|
24790
|
-
warmTimeoutMs
|
|
24852
|
+
powered: opts.powered,
|
|
24853
|
+
preBufferSeconds: opts.preBufferSeconds,
|
|
24854
|
+
warmTimeoutMs
|
|
24791
24855
|
});
|
|
24792
|
-
this.callSources.set(
|
|
24856
|
+
this.callSources.set(key, source);
|
|
24793
24857
|
}
|
|
24794
24858
|
return source;
|
|
24795
24859
|
};
|
|
24796
24860
|
return {
|
|
24797
24861
|
live: async (opts) => attachUnlessAborted(sourceFor(opts), opts?.signal),
|
|
24798
24862
|
openReadable: async (opts) => openReadableFromConsumer(attachUnlessAborted(sourceFor(opts), opts?.signal), opts),
|
|
24799
|
-
snapshotLive: (opts) => captureSnapshotFromShared(sourceFor(opts), {
|
|
24863
|
+
snapshotLive: async (opts) => captureSnapshotFromShared(sourceFor(opts), {
|
|
24800
24864
|
...opts,
|
|
24801
24865
|
logger: this.deps.logger,
|
|
24802
24866
|
ffmpegPath: this.deps.ffmpegPath
|
|
24803
24867
|
}),
|
|
24804
24868
|
record: async () => {
|
|
24805
|
-
throw new Error(
|
|
24869
|
+
throw new Error(recordError);
|
|
24806
24870
|
}
|
|
24807
24871
|
};
|
|
24808
24872
|
}
|
|
@@ -29435,19 +29499,24 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29435
29499
|
}
|
|
29436
29500
|
/**
|
|
29437
29501
|
* The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
|
|
29438
|
-
* `device_channel` resolves back to it on the station; `undefined` otherwise,
|
|
29439
|
-
* 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.
|
|
29440
29503
|
*/
|
|
29441
29504
|
rtcMediaFor(sn) {
|
|
29442
29505
|
const route = this.rtcRouteFor(sn);
|
|
29443
29506
|
if (route?.mediaTrack)
|
|
29444
29507
|
return this.rtc.trackMediaProviderFor(route);
|
|
29445
|
-
if (!route?.attached
|
|
29508
|
+
if (!route?.attached)
|
|
29446
29509
|
return void 0;
|
|
29447
|
-
const
|
|
29510
|
+
const camera = this.registry.list().find((d) => d.sn === sn);
|
|
29511
|
+
const raw = camera?.raw ?? {};
|
|
29448
29512
|
const channel = typeof raw.device_channel === "number" ? raw.device_channel : void 0;
|
|
29449
29513
|
if (channel === void 0 || this.registry.serialForFrame(route.stationSn, channel) !== sn)
|
|
29450
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
|
+
}
|
|
29451
29520
|
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29452
29521
|
}
|
|
29453
29522
|
/**
|