@mega-yfue/eufy-sdk 0.5.0-beta.76 → 0.5.0-beta.78
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 +9 -3
- package/dist/index.js +42 -22
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/camera.d.ts +1 -0
- package/dist/model/device-family.d.ts +2 -0
- package/dist/transport/p2p/p2p-session.d.ts +1 -0
- package/dist/transport/rtc/command-router.d.ts +1 -1
- package/dist/transport/rtc/live.d.ts +3 -1
- package/package.json +1 -1
package/dist/core/contracts.d.ts
CHANGED
|
@@ -734,6 +734,10 @@ export interface LiveStreamConsumer extends LiveStreamHandle {
|
|
|
734
734
|
export interface AbortableCall {
|
|
735
735
|
signal?: AbortSignal;
|
|
736
736
|
}
|
|
737
|
+
/** Select an optical view; absence selects view 0. */
|
|
738
|
+
export interface MediaSourceSelection {
|
|
739
|
+
view?: number;
|
|
740
|
+
}
|
|
737
741
|
export interface SharedSourceHints {
|
|
738
742
|
/**
|
|
739
743
|
* Power source, a runtime device fact (`"battery"` incl. solar, or `"wired"`) — never a device-family
|
|
@@ -759,6 +763,8 @@ export interface SharedSourceHints {
|
|
|
759
763
|
* because an unbound model has no live client (hence `?.` at the call site).
|
|
760
764
|
*/
|
|
761
765
|
export interface MediaProvider {
|
|
766
|
+
/** Optical view identifiers supported by this bound media route. */
|
|
767
|
+
views?: readonly number[];
|
|
762
768
|
/**
|
|
763
769
|
* Return the latest validated push thumbnail retained in memory. This passive operation performs no
|
|
764
770
|
* network, storage, P2P, live-media, or transcoding work at call time. It rejects with
|
|
@@ -785,7 +791,7 @@ export interface MediaProvider {
|
|
|
785
791
|
timeoutMs?: number;
|
|
786
792
|
collectMs?: number;
|
|
787
793
|
skipKeyframes?: number;
|
|
788
|
-
} & SharedSourceHints & AbortableCall): Promise<{
|
|
794
|
+
} & SharedSourceHints & AbortableCall & MediaSourceSelection): Promise<{
|
|
789
795
|
jpeg: Buffer;
|
|
790
796
|
width: number;
|
|
791
797
|
height: number;
|
|
@@ -817,7 +823,7 @@ export interface MediaProvider {
|
|
|
817
823
|
* stream?.stop(); // detach this consumer
|
|
818
824
|
* ```
|
|
819
825
|
*/
|
|
820
|
-
live(opts?: SharedSourceHints & AbortableCall & Record<string, unknown>): Promise<LiveStreamConsumer>;
|
|
826
|
+
live(opts?: SharedSourceHints & AbortableCall & MediaSourceSelection & Record<string, unknown>): Promise<LiveStreamConsumer>;
|
|
821
827
|
/**
|
|
822
828
|
* Record `seconds` of video → an mp4/h264 buffer.
|
|
823
829
|
*
|
|
@@ -843,7 +849,7 @@ export interface MediaProvider {
|
|
|
843
849
|
openReadable?(opts?: {
|
|
844
850
|
objectMode?: boolean;
|
|
845
851
|
arrivalTime?: boolean;
|
|
846
|
-
} & SharedSourceHints & AbortableCall): Promise<import("node:stream").Readable>;
|
|
852
|
+
} & SharedSourceHints & AbortableCall & MediaSourceSelection): Promise<import("node:stream").Readable>;
|
|
847
853
|
/**
|
|
848
854
|
* Continuously record the live feed as fragmented-MP4 (CMAF). The caller-owned
|
|
849
855
|
* {@link FragmentRecordingHandle} yields an init segment then keyframe-bounded media fragments,
|
package/dist/index.js
CHANGED
|
@@ -3656,6 +3656,7 @@ 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
3658
|
var isNvrMediaCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
|
|
3659
|
+
var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
|
|
3659
3660
|
var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
|
|
3660
3661
|
var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
|
|
3661
3662
|
var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
|
|
@@ -5591,6 +5592,11 @@ function refuseWhenDisabled(ctx, read) {
|
|
|
5591
5592
|
if (CAMERA_MEMBERS.enabled.readReflectsWrite(ctx) && read("enabled")?.value === false)
|
|
5592
5593
|
throw new CameraDisabledError(ctx.name ?? ctx.serial);
|
|
5593
5594
|
}
|
|
5595
|
+
function refuseUnsupportedView(media, opts) {
|
|
5596
|
+
const view = opts?.view ?? 0;
|
|
5597
|
+
if (!(media.views ?? [0]).includes(view))
|
|
5598
|
+
throw new RangeError(`camera: unsupported optical view ${view}`);
|
|
5599
|
+
}
|
|
5594
5600
|
var CAMERA_MEMBERS = {
|
|
5595
5601
|
/** Reads the latest cached report and exposes no setter. */
|
|
5596
5602
|
wifiRssi: {
|
|
@@ -5861,6 +5867,7 @@ var CAMERA_MEMBERS = {
|
|
|
5861
5867
|
description: "Camera status LED. Video doorbells report the same state under their button-ring LED parameter.",
|
|
5862
5868
|
write: (v, ctx) => statusLedCommand(asBool(v), ctx)
|
|
5863
5869
|
},
|
|
5870
|
+
views: provided("media", (m) => m.views && (() => m.views), "Optical view identifiers supported by this camera's media route."),
|
|
5864
5871
|
/**
|
|
5865
5872
|
* Media is not a property: it returns DATA rather than moving state, its options are richer than a
|
|
5866
5873
|
* value, and it exists only on a device bound to a provider. Each is declared once, with its
|
|
@@ -5881,10 +5888,12 @@ var CAMERA_MEMBERS = {
|
|
|
5881
5888
|
snapshotStored: provided("media", (m, { ctx }) => isCameraCodec(ctx) && m.snapshotStored && (() => m.snapshotStored()), "Latest validated push thumbnail retained in memory.", ["snapshot"]),
|
|
5882
5889
|
snapshotLive: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (opts) => {
|
|
5883
5890
|
refuseWhenDisabled(ctx, read);
|
|
5891
|
+
refuseUnsupportedView(m, opts);
|
|
5884
5892
|
return m.snapshotLive({ powered: poweredOf(ctx), ...opts });
|
|
5885
5893
|
}), "Fresh still decoded from a short live burst."),
|
|
5886
5894
|
live: provided("media", (m, { ctx, read }) => async (opts) => {
|
|
5887
5895
|
refuseWhenDisabled(ctx, read);
|
|
5896
|
+
refuseUnsupportedView(m, opts);
|
|
5888
5897
|
return m.live({ powered: poweredOf(ctx), ...opts });
|
|
5889
5898
|
}, "Open a managed live stream."),
|
|
5890
5899
|
record: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (...args) => {
|
|
@@ -5893,6 +5902,7 @@ var CAMERA_MEMBERS = {
|
|
|
5893
5902
|
}), "Record N seconds \u2192 an mp4/h264 buffer."),
|
|
5894
5903
|
openReadable: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && m.openReadable && (async (opts) => {
|
|
5895
5904
|
refuseWhenDisabled(ctx, read);
|
|
5905
|
+
refuseUnsupportedView(m, opts);
|
|
5896
5906
|
return m.openReadable({ powered: poweredOf(ctx), ...opts });
|
|
5897
5907
|
}), "Open a node:stream Readable of the live feed."),
|
|
5898
5908
|
recordFragments: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && m.recordFragments && ((opts) => {
|
|
@@ -17841,15 +17851,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
|
|
|
17841
17851
|
}
|
|
17842
17852
|
this.emit("data", frame);
|
|
17843
17853
|
}
|
|
17844
|
-
send(addr, type, payload, socket = this.socket) {
|
|
17854
|
+
send(addr, type, payload, socket = this.socket, done) {
|
|
17845
17855
|
if (!socket)
|
|
17846
17856
|
return;
|
|
17847
17857
|
const packet = frameMessage(type, payload);
|
|
17848
17858
|
socket.send(packet, addr.port, addr.host, (err) => {
|
|
17849
17859
|
if (err)
|
|
17850
17860
|
this.logger.warn(`[p2p] send err ${addr.host}:${addr.port}`, err.message);
|
|
17861
|
+
done?.();
|
|
17851
17862
|
});
|
|
17852
17863
|
}
|
|
17864
|
+
/** Finish the END send on the current socket before closing it; send completion is not a peer ACK. */
|
|
17853
17865
|
async close() {
|
|
17854
17866
|
if (this.closed)
|
|
17855
17867
|
return;
|
|
@@ -17878,12 +17890,14 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
|
|
|
17878
17890
|
this.liveStartRetransmitTimer = void 0;
|
|
17879
17891
|
this.unackedLiveStarts.clear();
|
|
17880
17892
|
this.resetInboundSequencing();
|
|
17881
|
-
|
|
17882
|
-
|
|
17893
|
+
const socket = this.socket;
|
|
17894
|
+
if (socket && this.connectAddress) {
|
|
17895
|
+
await new Promise((resolve) => this.send(this.connectAddress, RequestMessageType.END, void 0, socket, resolve));
|
|
17896
|
+
}
|
|
17883
17897
|
await new Promise((resolve) => {
|
|
17884
|
-
if (!
|
|
17898
|
+
if (!socket)
|
|
17885
17899
|
return resolve();
|
|
17886
|
-
|
|
17900
|
+
socket.close(() => resolve());
|
|
17887
17901
|
});
|
|
17888
17902
|
this.socket = void 0;
|
|
17889
17903
|
this.emit("close");
|
|
@@ -23497,7 +23511,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23497
23511
|
audio_chn: nvr ? this.opts.channel : -1,
|
|
23498
23512
|
streamtype: 2,
|
|
23499
23513
|
stitch_mode: 1,
|
|
23500
|
-
chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
|
|
23514
|
+
chn_list: [{ index: 0, chn: this.opts.channel, sensor: this.opts.view ?? 0, isUps: 0, isClicked: true }],
|
|
23501
23515
|
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
23502
23516
|
station_video_type: 6,
|
|
23503
23517
|
play_id: 1
|
|
@@ -24839,22 +24853,28 @@ var RtcCommandRouter = class {
|
|
|
24839
24853
|
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);
|
|
24840
24854
|
}
|
|
24841
24855
|
/** NVR media uses a dedicated call per camera, independent of station commands. */
|
|
24842
|
-
nvrMediaProviderFor(route) {
|
|
24843
|
-
return
|
|
24844
|
-
session,
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24856
|
+
nvrMediaProviderFor(route, views) {
|
|
24857
|
+
return {
|
|
24858
|
+
...this.callMediaProviderFor(route, route.cameraSn, (session, accountId, view) => new RtcLiveStream({
|
|
24859
|
+
session,
|
|
24860
|
+
seg: new SegmentCounter(),
|
|
24861
|
+
stationSn: route.stationSn,
|
|
24862
|
+
channel: route.channel,
|
|
24863
|
+
accountId,
|
|
24864
|
+
logger: this.deps.logger,
|
|
24865
|
+
nvr: true,
|
|
24866
|
+
view
|
|
24867
|
+
}), "record is not available over an NVR portal call"),
|
|
24868
|
+
views
|
|
24869
|
+
};
|
|
24852
24870
|
}
|
|
24853
24871
|
/** Share a dedicated-call source and its consumer operations under one route key. */
|
|
24854
24872
|
callMediaProviderFor(route, key, makeStream, recordError, warmTimeoutMs) {
|
|
24855
24873
|
const sourceFor = (opts = {}) => {
|
|
24856
24874
|
opts.signal?.throwIfAborted();
|
|
24857
|
-
|
|
24875
|
+
const view = opts.view ?? 0;
|
|
24876
|
+
const sourceKey = view === 0 ? key : `${key}:view:${view}`;
|
|
24877
|
+
let source = this.callSources.get(sourceKey);
|
|
24858
24878
|
if (!source) {
|
|
24859
24879
|
source = new SharedLiveSource({
|
|
24860
24880
|
makeStream: () => {
|
|
@@ -24862,15 +24882,15 @@ var RtcCommandRouter = class {
|
|
|
24862
24882
|
if (!identity)
|
|
24863
24883
|
throw new Error(`rtc: not logged in, cannot call ${route.stationSn}`);
|
|
24864
24884
|
const accountId = route.adminUserId || identity.userId;
|
|
24865
|
-
return makeStream(this.newSession(route, accountId, identity), accountId);
|
|
24885
|
+
return makeStream(this.newSession(route, accountId, identity), accountId, view);
|
|
24866
24886
|
},
|
|
24867
|
-
label:
|
|
24887
|
+
label: sourceKey,
|
|
24868
24888
|
logger: this.deps.logger,
|
|
24869
24889
|
powered: opts.powered,
|
|
24870
24890
|
preBufferSeconds: opts.preBufferSeconds,
|
|
24871
24891
|
warmTimeoutMs
|
|
24872
24892
|
});
|
|
24873
|
-
this.callSources.set(
|
|
24893
|
+
this.callSources.set(sourceKey, source);
|
|
24874
24894
|
}
|
|
24875
24895
|
return source;
|
|
24876
24896
|
};
|
|
@@ -29443,7 +29463,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29443
29463
|
try {
|
|
29444
29464
|
return await media.snapshotLive(opts);
|
|
29445
29465
|
} catch (error) {
|
|
29446
|
-
if (!(error instanceof LiveSnapshotUnavailableError))
|
|
29466
|
+
if (!(error instanceof LiveSnapshotUnavailableError) || (opts?.view ?? 0) !== 0)
|
|
29447
29467
|
throw error;
|
|
29448
29468
|
const retained = await retainedStill().catch(() => void 0);
|
|
29449
29469
|
const geometry = retained && jpegGeometry(retained);
|
|
@@ -29532,7 +29552,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29532
29552
|
if (route.signalingMode === "call") {
|
|
29533
29553
|
if (!camera || !isNvrMediaCamera(camera))
|
|
29534
29554
|
return void 0;
|
|
29535
|
-
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29555
|
+
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel }, isNvrDualLensCamera(camera) ? [0, 1] : [0]);
|
|
29536
29556
|
}
|
|
29537
29557
|
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29538
29558
|
}
|