@mega-yfue/eufy-sdk 0.5.0-beta.77 → 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 +33 -17
- 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/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) => {
|
|
@@ -23501,7 +23511,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23501
23511
|
audio_chn: nvr ? this.opts.channel : -1,
|
|
23502
23512
|
streamtype: 2,
|
|
23503
23513
|
stitch_mode: 1,
|
|
23504
|
-
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 }],
|
|
23505
23515
|
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
23506
23516
|
station_video_type: 6,
|
|
23507
23517
|
play_id: 1
|
|
@@ -24843,22 +24853,28 @@ var RtcCommandRouter = class {
|
|
|
24843
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);
|
|
24844
24854
|
}
|
|
24845
24855
|
/** NVR media uses a dedicated call per camera, independent of station commands. */
|
|
24846
|
-
nvrMediaProviderFor(route) {
|
|
24847
|
-
return
|
|
24848
|
-
session,
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
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
|
+
};
|
|
24856
24870
|
}
|
|
24857
24871
|
/** Share a dedicated-call source and its consumer operations under one route key. */
|
|
24858
24872
|
callMediaProviderFor(route, key, makeStream, recordError, warmTimeoutMs) {
|
|
24859
24873
|
const sourceFor = (opts = {}) => {
|
|
24860
24874
|
opts.signal?.throwIfAborted();
|
|
24861
|
-
|
|
24875
|
+
const view = opts.view ?? 0;
|
|
24876
|
+
const sourceKey = view === 0 ? key : `${key}:view:${view}`;
|
|
24877
|
+
let source = this.callSources.get(sourceKey);
|
|
24862
24878
|
if (!source) {
|
|
24863
24879
|
source = new SharedLiveSource({
|
|
24864
24880
|
makeStream: () => {
|
|
@@ -24866,15 +24882,15 @@ var RtcCommandRouter = class {
|
|
|
24866
24882
|
if (!identity)
|
|
24867
24883
|
throw new Error(`rtc: not logged in, cannot call ${route.stationSn}`);
|
|
24868
24884
|
const accountId = route.adminUserId || identity.userId;
|
|
24869
|
-
return makeStream(this.newSession(route, accountId, identity), accountId);
|
|
24885
|
+
return makeStream(this.newSession(route, accountId, identity), accountId, view);
|
|
24870
24886
|
},
|
|
24871
|
-
label:
|
|
24887
|
+
label: sourceKey,
|
|
24872
24888
|
logger: this.deps.logger,
|
|
24873
24889
|
powered: opts.powered,
|
|
24874
24890
|
preBufferSeconds: opts.preBufferSeconds,
|
|
24875
24891
|
warmTimeoutMs
|
|
24876
24892
|
});
|
|
24877
|
-
this.callSources.set(
|
|
24893
|
+
this.callSources.set(sourceKey, source);
|
|
24878
24894
|
}
|
|
24879
24895
|
return source;
|
|
24880
24896
|
};
|
|
@@ -29447,7 +29463,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29447
29463
|
try {
|
|
29448
29464
|
return await media.snapshotLive(opts);
|
|
29449
29465
|
} catch (error) {
|
|
29450
|
-
if (!(error instanceof LiveSnapshotUnavailableError))
|
|
29466
|
+
if (!(error instanceof LiveSnapshotUnavailableError) || (opts?.view ?? 0) !== 0)
|
|
29451
29467
|
throw error;
|
|
29452
29468
|
const retained = await retainedStill().catch(() => void 0);
|
|
29453
29469
|
const geometry = retained && jpegGeometry(retained);
|
|
@@ -29536,7 +29552,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29536
29552
|
if (route.signalingMode === "call") {
|
|
29537
29553
|
if (!camera || !isNvrMediaCamera(camera))
|
|
29538
29554
|
return void 0;
|
|
29539
|
-
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29555
|
+
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel }, isNvrDualLensCamera(camera) ? [0, 1] : [0]);
|
|
29540
29556
|
}
|
|
29541
29557
|
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29542
29558
|
}
|