@mega-yfue/eufy-sdk 0.6.0-beta.34 → 0.6.0-beta.36
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 +12 -5
- package/dist/index.js +74 -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/p2p/recording-download.d.ts +11 -2
- package/dist/transport/rtc/command-router.d.ts +4 -3
- package/dist/transport/rtc/live.d.ts +4 -1
- package/dist/transport/rtc/session.d.ts +16 -1
- package/package.json +1 -1
package/dist/core/contracts.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export declare class DeviceChannelUnresolvedError extends Error {
|
|
|
156
156
|
* - `invalid-recording` — the recording name is not one the camera's station could hold.
|
|
157
157
|
* - `key-unavailable` — the station stores the recording sealed and the key it was sealed under could not be
|
|
158
158
|
* obtained.
|
|
159
|
-
* - `no-data` — the station sent nothing within the wait.
|
|
159
|
+
* - `no-data` — the station sent no recording: it answered that it holds none, or sent nothing within the wait.
|
|
160
160
|
* - `incomplete` — the transfer ended before the station's finish frame.
|
|
161
161
|
* - `undecodable` — data arrived but no video frame could be decoded from it.
|
|
162
162
|
*/
|
|
@@ -172,13 +172,15 @@ export declare class RecordingDownloadError extends Error {
|
|
|
172
172
|
* A recording downloaded from the station, as elementary streams.
|
|
173
173
|
*
|
|
174
174
|
* `video` is Annex-B H.264 or H.265 in decode order, `audio` AAC-LC 16 kHz mono in ADTS framing (absent
|
|
175
|
-
* when the recording carried none that could be decoded). `
|
|
176
|
-
*
|
|
175
|
+
* when the recording carried none that could be decoded). `codec` is the codec of `video`, read off the parameter
|
|
176
|
+
* sets of its first keyframe that carries them, and is absent when no keyframe does. `frames` counts the distinct
|
|
177
|
+
* video sequences emitted. `missingFrames` counts gaps between received sequences and received sequences that yielded no
|
|
177
178
|
* usable picture, including at either end. `durationMs` spans the first to the last emitted picture by the
|
|
178
179
|
* camera's own timestamps; `fps` is derived from those stamps.
|
|
179
180
|
*/
|
|
180
181
|
export interface RecordingDownload {
|
|
181
182
|
video: Buffer;
|
|
183
|
+
codec?: VideoCodec;
|
|
182
184
|
audio?: Buffer;
|
|
183
185
|
frames: number;
|
|
184
186
|
missingFrames: number;
|
|
@@ -493,7 +495,7 @@ export interface TuyaDpInbound {
|
|
|
493
495
|
*/
|
|
494
496
|
onDps(sn: string, dps: Record<number, boolean | number | string>): void;
|
|
495
497
|
}
|
|
496
|
-
/** Elementary-stream video codec
|
|
498
|
+
/** Elementary-stream video codec — eufy cameras stream H.264 or H.265. */
|
|
497
499
|
export type VideoCodec = "h264" | "h265" | "av1";
|
|
498
500
|
/**
|
|
499
501
|
* One decoded video access unit, as Annex-B (H.264 or H.265).
|
|
@@ -745,9 +747,12 @@ export interface LiveStreamConsumer extends LiveStreamHandle {
|
|
|
745
747
|
export interface AbortableCall {
|
|
746
748
|
signal?: AbortSignal;
|
|
747
749
|
}
|
|
748
|
-
/**
|
|
750
|
+
/** NVR output size requested when a live pull starts. */
|
|
751
|
+
export type MediaOutputResolution = "1080p";
|
|
752
|
+
/** Select an optical view and output size; absence selects view 0 at the route's default output. */
|
|
749
753
|
export interface MediaSourceSelection {
|
|
750
754
|
view?: number;
|
|
755
|
+
outputResolution?: MediaOutputResolution;
|
|
751
756
|
}
|
|
752
757
|
export interface SharedSourceHints {
|
|
753
758
|
/**
|
|
@@ -774,6 +779,8 @@ export interface SharedSourceHints {
|
|
|
774
779
|
* because an unbound model has no live client (hence `?.` at the call site).
|
|
775
780
|
*/
|
|
776
781
|
export interface MediaProvider {
|
|
782
|
+
/** Output sizes supported by this bound media route. Absence means no size selection. */
|
|
783
|
+
outputResolutions?: readonly MediaOutputResolution[];
|
|
777
784
|
/** Optical view identifiers supported by this bound media route. */
|
|
778
785
|
views?: readonly number[];
|
|
779
786
|
/**
|
package/dist/index.js
CHANGED
|
@@ -3743,6 +3743,7 @@ var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
|
|
|
3743
3743
|
var NVR_MEDIA_CAMERA_MODELS = /* @__PURE__ */ new Set(["T8E00", "T8P00", "T8P10", "T8162", "T8410", "T8214"]);
|
|
3744
3744
|
var isNvrMediaCamera = (ctx) => ctx.model !== void 0 && NVR_MEDIA_CAMERA_MODELS.has(ctx.model);
|
|
3745
3745
|
var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
|
|
3746
|
+
var isNvrFullHdCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
|
|
3746
3747
|
var isCallDoorbell = (ctx) => ctx.model === "T8223" || ctx.model === "T8224";
|
|
3747
3748
|
var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
|
|
3748
3749
|
var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
|
|
@@ -6084,10 +6085,12 @@ function refuseWhenDisabled(ctx, read) {
|
|
|
6084
6085
|
if (CAMERA_MEMBERS.enabled.readReflectsWrite(ctx) && read("enabled")?.value === false)
|
|
6085
6086
|
throw new CameraDisabledError(ctx.name ?? ctx.serial);
|
|
6086
6087
|
}
|
|
6087
|
-
function
|
|
6088
|
+
function refuseUnsupportedSelection(media, opts) {
|
|
6088
6089
|
const view = opts?.view ?? 0;
|
|
6089
6090
|
if (!(media.views ?? [0]).includes(view))
|
|
6090
6091
|
throw new RangeError(`camera: unsupported optical view ${view}`);
|
|
6092
|
+
if (opts?.outputResolution !== void 0 && !media.outputResolutions?.includes(opts.outputResolution))
|
|
6093
|
+
throw new RangeError(`camera: unsupported output resolution ${opts.outputResolution}`);
|
|
6091
6094
|
}
|
|
6092
6095
|
var CAMERA_MEMBERS = {
|
|
6093
6096
|
/** Reads the latest cached report and exposes no setter. */
|
|
@@ -6376,6 +6379,7 @@ var CAMERA_MEMBERS = {
|
|
|
6376
6379
|
write: (v, ctx) => statusLedCommand(asBool(v), ctx)
|
|
6377
6380
|
},
|
|
6378
6381
|
views: provided("media", (m) => m.views && (() => m.views), "Optical view identifiers supported by this camera's media route."),
|
|
6382
|
+
outputResolutions: provided("media", (m) => m.outputResolutions && (() => m.outputResolutions), "Output sizes supported by this camera's live media route; omission preserves its default output."),
|
|
6379
6383
|
/**
|
|
6380
6384
|
* Media is not a property: it returns DATA rather than moving state, its options are richer than a
|
|
6381
6385
|
* value, and it exists only on a device bound to a provider. Each is declared once, with its
|
|
@@ -6396,12 +6400,12 @@ var CAMERA_MEMBERS = {
|
|
|
6396
6400
|
snapshotStored: provided("media", (m, { ctx }) => isCameraCodec(ctx) && m.snapshotStored && (() => m.snapshotStored()), "Latest validated push thumbnail retained in memory.", ["snapshot"]),
|
|
6397
6401
|
snapshotLive: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (opts) => {
|
|
6398
6402
|
refuseWhenDisabled(ctx, read);
|
|
6399
|
-
|
|
6403
|
+
refuseUnsupportedSelection(m, opts);
|
|
6400
6404
|
return m.snapshotLive({ powered: poweredOf(ctx), ...opts });
|
|
6401
6405
|
}), "Fresh still decoded from a short live burst."),
|
|
6402
6406
|
live: provided("media", (m, { ctx, read }) => async (opts) => {
|
|
6403
6407
|
refuseWhenDisabled(ctx, read);
|
|
6404
|
-
|
|
6408
|
+
refuseUnsupportedSelection(m, opts);
|
|
6405
6409
|
return m.live({ powered: poweredOf(ctx), ...opts });
|
|
6406
6410
|
}, "Open a managed live stream."),
|
|
6407
6411
|
record: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (...args) => {
|
|
@@ -6411,7 +6415,7 @@ var CAMERA_MEMBERS = {
|
|
|
6411
6415
|
/** The T8530 serves its live feed as a Readable too. */
|
|
6412
6416
|
openReadable: provided("media", (m, { ctx, read }) => (isCameraCodec(ctx) || isVideoSmartLock(ctx)) && m.openReadable && (async (opts) => {
|
|
6413
6417
|
refuseWhenDisabled(ctx, read);
|
|
6414
|
-
|
|
6418
|
+
refuseUnsupportedSelection(m, opts);
|
|
6415
6419
|
return m.openReadable({ powered: poweredOf(ctx), ...opts });
|
|
6416
6420
|
}), "Open a node:stream Readable of the live feed."),
|
|
6417
6421
|
recordFragments: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && m.recordFragments && ((opts) => {
|
|
@@ -6419,7 +6423,7 @@ var CAMERA_MEMBERS = {
|
|
|
6419
6423
|
return m.recordFragments({ powered: poweredOf(ctx), ...opts });
|
|
6420
6424
|
}), "Continuous fragmented-MP4 (CMAF) recording."),
|
|
6421
6425
|
/** A T8530 is offered the download behind a HomeBase 3 alone. */
|
|
6422
|
-
downloadRecording: provided("media", (m, { ctx }) => (isCameraCodec(ctx) || isVideoSmartLock(ctx) && ctx.stationSerial?.startsWith("T8030") === true) && m.downloadRecording && ((opts) => m.downloadRecording(opts)), "Download a recording the station holds \u2192 H.264 and AAC elementary streams."),
|
|
6426
|
+
downloadRecording: provided("media", (m, { ctx }) => (isCameraCodec(ctx) || isVideoSmartLock(ctx) && ctx.stationSerial?.startsWith("T8030") === true) && m.downloadRecording && ((opts) => m.downloadRecording(opts)), "Download a recording the station holds \u2192 H.264 or H.265 and AAC elementary streams."),
|
|
6423
6427
|
/**
|
|
6424
6428
|
* Push audio from the host to this camera's speaker. Gated on the **speaker** param specifically —
|
|
6425
6429
|
* not the `audio` capability, which resolves on a microphone alone and would advertise a speaker the
|
|
@@ -20058,7 +20062,16 @@ function homeBase3RecordingPath(channel, recording) {
|
|
|
20058
20062
|
}
|
|
20059
20063
|
var RECORDING_STATIONS = /* @__PURE__ */ new Map([
|
|
20060
20064
|
["T8010", { path: homeBase2RecordingPath, sealed: true, finishChannel: 0, chunkBytes: STATION_CHUNK_BYTES }],
|
|
20061
|
-
[
|
|
20065
|
+
[
|
|
20066
|
+
"T8030",
|
|
20067
|
+
{
|
|
20068
|
+
path: homeBase3RecordingPath,
|
|
20069
|
+
sealed: false,
|
|
20070
|
+
finishChannel: STATION_CHANNEL3,
|
|
20071
|
+
chunkBytes: 4e5,
|
|
20072
|
+
missingCode: -111
|
|
20073
|
+
}
|
|
20074
|
+
]
|
|
20062
20075
|
]);
|
|
20063
20076
|
function receiveRecording(session, request) {
|
|
20064
20077
|
let resolveFrames;
|
|
@@ -20102,6 +20115,14 @@ function receiveRecording(session, request) {
|
|
|
20102
20115
|
collected = [];
|
|
20103
20116
|
};
|
|
20104
20117
|
const onData = (frame) => {
|
|
20118
|
+
if (frame.commandId === CommandType.CMD_DOWNLOAD_VIDEO) {
|
|
20119
|
+
if (frame.channel !== request.channel || frame.dataType !== 0 || received || draining)
|
|
20120
|
+
return;
|
|
20121
|
+
if (frame.data.length < 4 || frame.data.readInt32LE(0) !== request.missingCode)
|
|
20122
|
+
return;
|
|
20123
|
+
deliver(new RecordingDownloadError("no-data", `the station holds no recording at ${request.path}`));
|
|
20124
|
+
return stop();
|
|
20125
|
+
}
|
|
20105
20126
|
if (frame.commandId === CommandType.CMD_DOWNLOAD_FINISH) {
|
|
20106
20127
|
if (frame.channel !== request.channel && frame.channel !== request.finishChannel)
|
|
20107
20128
|
return;
|
|
@@ -20172,6 +20193,7 @@ function decodeRecording(frames, station, eccPrivateKeyHex) {
|
|
|
20172
20193
|
const decoder = eccPrivateKey && new VideoFrameDecoder(eccPrivateKey);
|
|
20173
20194
|
const assembler = new AccessUnitAssembler(void 0, station.chunkBytes);
|
|
20174
20195
|
const video = [];
|
|
20196
|
+
let codec;
|
|
20175
20197
|
const audio = [];
|
|
20176
20198
|
const numbers = /* @__PURE__ */ new Set();
|
|
20177
20199
|
const receivedNumbers = /* @__PURE__ */ new Set();
|
|
@@ -20186,6 +20208,8 @@ function decodeRecording(frames, station, eccPrivateKeyHex) {
|
|
|
20186
20208
|
const units = assembler.push(raw, (payload) => decoder && header.keyframe ? decoder.decodeFrame(payload)?.h264 : clearVideoPayload(payload, header));
|
|
20187
20209
|
for (const unit of units) {
|
|
20188
20210
|
video.push(unit.data);
|
|
20211
|
+
if (unit.keyframe)
|
|
20212
|
+
codec ??= sniffAnnexbCodec(unit.data);
|
|
20189
20213
|
numbers.add(header.sequence);
|
|
20190
20214
|
const stamp = raw.readUIntLE(14, 6);
|
|
20191
20215
|
firstStamp ??= stamp;
|
|
@@ -20214,6 +20238,7 @@ function decodeRecording(frames, station, eccPrivateKeyHex) {
|
|
|
20214
20238
|
const durationMs = (lastStamp ?? 0) - (firstStamp ?? 0);
|
|
20215
20239
|
return {
|
|
20216
20240
|
video: Buffer.concat(video),
|
|
20241
|
+
...codec ? { codec } : {},
|
|
20217
20242
|
...audio.length ? { audio: Buffer.concat(audio) } : {},
|
|
20218
20243
|
frames: ordered.length,
|
|
20219
20244
|
missingFrames,
|
|
@@ -23028,6 +23053,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
23028
23053
|
accountId,
|
|
23029
23054
|
channel,
|
|
23030
23055
|
finishChannel: station.finishChannel,
|
|
23056
|
+
missingCode: station.missingCode,
|
|
23031
23057
|
timeoutMs: opts.timeoutMs,
|
|
23032
23058
|
signal: opts.signal
|
|
23033
23059
|
});
|
|
@@ -24478,6 +24504,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
24478
24504
|
audio_chn: nvr ? this.opts.channel : -1,
|
|
24479
24505
|
streamtype: 2,
|
|
24480
24506
|
stitch_mode: 1,
|
|
24507
|
+
...this.opts.outputResolution === "1080p" ? { stitch_resolution: { width: 1920, height: 1080 } } : {},
|
|
24481
24508
|
chn_list: [{ index: 0, chn: this.opts.channel, sensor: this.opts.view ?? 0, isUps: 0, isClicked: true }],
|
|
24482
24509
|
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
24483
24510
|
station_video_type: 6,
|
|
@@ -25518,13 +25545,14 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25518
25545
|
closeAnnounced = false;
|
|
25519
25546
|
sdpHandled = false;
|
|
25520
25547
|
chain = Promise.resolve();
|
|
25548
|
+
stageReached = "signing";
|
|
25549
|
+
lastPeerState;
|
|
25521
25550
|
constructor(opts) {
|
|
25522
25551
|
super();
|
|
25523
25552
|
this.opts = opts;
|
|
25524
25553
|
this.logger = opts.logger ?? noopLogger;
|
|
25525
25554
|
this.signaling = (opts.createSignaling ?? ((o) => new RtcSignalingClient(o)))(opts);
|
|
25526
25555
|
this.peer = (opts.createPeer ?? ((o) => new RtcPeer(o)))({ logger: this.logger, ...opts.peer });
|
|
25527
|
-
const mediaTrack = opts.peer?.mediaTrack === true;
|
|
25528
25556
|
this.signaling.on("message", (inner) => {
|
|
25529
25557
|
this.chain = this.chain.then(() => this.onSignaling(inner)).catch((e) => {
|
|
25530
25558
|
this.emit("error", e instanceof Error ? e : new Error(String(e)));
|
|
@@ -25538,6 +25566,7 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25538
25566
|
if (this.connected)
|
|
25539
25567
|
return;
|
|
25540
25568
|
this.connected = true;
|
|
25569
|
+
this.stageReached = "connected";
|
|
25541
25570
|
this.logger.debug(`[rtc] ${this.opts.stationSn} command channel open`);
|
|
25542
25571
|
this.emit("connected");
|
|
25543
25572
|
});
|
|
@@ -25549,9 +25578,14 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25549
25578
|
this.announceClose();
|
|
25550
25579
|
});
|
|
25551
25580
|
this.peer.on("connectionState", (state) => {
|
|
25552
|
-
|
|
25581
|
+
this.lastPeerState = state;
|
|
25582
|
+
if (state !== "failed" && state !== "closed")
|
|
25583
|
+
return;
|
|
25584
|
+
if (this.connected || this.mediaTrack) {
|
|
25553
25585
|
this.connected = false;
|
|
25554
25586
|
this.announceClose();
|
|
25587
|
+
} else {
|
|
25588
|
+
this.shutdown(new Error(`RTC ${this.opts.stationSn} peer ${state} before the command channel opened`));
|
|
25555
25589
|
}
|
|
25556
25590
|
});
|
|
25557
25591
|
this.peer.on("error", (e) => this.emit("error", e));
|
|
@@ -25564,9 +25598,20 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25564
25598
|
this.emit("commandData", frame, linkType);
|
|
25565
25599
|
});
|
|
25566
25600
|
}
|
|
25601
|
+
get mediaTrack() {
|
|
25602
|
+
return this.opts.peer?.mediaTrack === true;
|
|
25603
|
+
}
|
|
25567
25604
|
get isConnected() {
|
|
25568
25605
|
return this.connected;
|
|
25569
25606
|
}
|
|
25607
|
+
/** The last bring-up stage this session reached. */
|
|
25608
|
+
get stage() {
|
|
25609
|
+
return this.stageReached;
|
|
25610
|
+
}
|
|
25611
|
+
/** The peer connection's last reported state; `undefined` before it reports one. */
|
|
25612
|
+
get peerState() {
|
|
25613
|
+
return this.lastPeerState;
|
|
25614
|
+
}
|
|
25570
25615
|
/**
|
|
25571
25616
|
* Start the sequence; resolves once the call is sent. `connected` fires when the channel opens. A session
|
|
25572
25617
|
* closed by the time the sign fetch or the auth completes rejects instead of opening the socket or
|
|
@@ -25576,11 +25621,14 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25576
25621
|
await this.signaling.fetchSign();
|
|
25577
25622
|
if (this.closed)
|
|
25578
25623
|
throw new Error("RTC session closed before the call was placed");
|
|
25624
|
+
this.stageReached = "connecting";
|
|
25579
25625
|
await this.signaling.connect();
|
|
25626
|
+
this.stageReached = "authenticating";
|
|
25580
25627
|
await this.waitForAuth();
|
|
25581
25628
|
if (this.closed)
|
|
25582
25629
|
throw new Error("RTC session closed before the call was placed");
|
|
25583
25630
|
this.logger.debug(`[rtc] ${this.opts.stationSn} authenticated \u2014 ${this.opts.signalingMode ?? "scall"}`);
|
|
25631
|
+
this.stageReached = "calling";
|
|
25584
25632
|
this.signaling.sendCall();
|
|
25585
25633
|
}
|
|
25586
25634
|
/** Send one portal packet; false when the command channel isn't open. */
|
|
@@ -25676,6 +25724,9 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25676
25724
|
return;
|
|
25677
25725
|
case "hangup":
|
|
25678
25726
|
this.logger.debug(`[rtc] ${this.opts.stationSn} hub hung up`);
|
|
25727
|
+
if (!this.connected && this.opts.signalingMode !== "call") {
|
|
25728
|
+
this.shutdown(new Error(`RTC ${this.opts.stationSn} hub hung up before the command channel opened`));
|
|
25729
|
+
}
|
|
25679
25730
|
return;
|
|
25680
25731
|
default:
|
|
25681
25732
|
return;
|
|
@@ -25688,6 +25739,7 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25688
25739
|
const status = payload.status;
|
|
25689
25740
|
if (status === 100 && payload.turn) {
|
|
25690
25741
|
this.turn = payload.turn;
|
|
25742
|
+
this.stageReached = "relay-granted";
|
|
25691
25743
|
try {
|
|
25692
25744
|
await this.peer.init(payload.turn);
|
|
25693
25745
|
} catch (e) {
|
|
@@ -25743,6 +25795,7 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
25743
25795
|
}
|
|
25744
25796
|
const answer = await this.peer.handleRemoteOffer(offer);
|
|
25745
25797
|
this.signaling.sendInfoSdp(this.opts.signalingMode === "call" ? answer : JSON.stringify(sdpToScallJson(answer)));
|
|
25798
|
+
this.stageReached = "answered";
|
|
25746
25799
|
this.logger.debug(`[rtc] ${this.opts.stationSn} answered the hub's offer`);
|
|
25747
25800
|
}
|
|
25748
25801
|
};
|
|
@@ -25879,9 +25932,9 @@ var RtcCommandRouter = class {
|
|
|
25879
25932
|
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);
|
|
25880
25933
|
}
|
|
25881
25934
|
/** NVR media uses a dedicated call per camera, independent of station commands. */
|
|
25882
|
-
nvrMediaProviderFor(route, views) {
|
|
25935
|
+
nvrMediaProviderFor(route, views, outputResolutions) {
|
|
25883
25936
|
return {
|
|
25884
|
-
...this.callMediaProviderFor(route, route.cameraSn, (session, accountId, view) => new RtcLiveStream({
|
|
25937
|
+
...this.callMediaProviderFor(route, route.cameraSn, (session, accountId, view, outputResolution) => new RtcLiveStream({
|
|
25885
25938
|
session,
|
|
25886
25939
|
seg: new SegmentCounter(),
|
|
25887
25940
|
stationSn: route.stationSn,
|
|
@@ -25889,9 +25942,11 @@ var RtcCommandRouter = class {
|
|
|
25889
25942
|
accountId,
|
|
25890
25943
|
logger: this.deps.logger,
|
|
25891
25944
|
nvr: true,
|
|
25892
|
-
view
|
|
25945
|
+
view,
|
|
25946
|
+
outputResolution
|
|
25893
25947
|
}), "record is not available over an NVR portal call"),
|
|
25894
|
-
views
|
|
25948
|
+
views,
|
|
25949
|
+
outputResolutions
|
|
25895
25950
|
};
|
|
25896
25951
|
}
|
|
25897
25952
|
/**
|
|
@@ -25905,7 +25960,8 @@ var RtcCommandRouter = class {
|
|
|
25905
25960
|
const sourceFor = (opts = {}) => {
|
|
25906
25961
|
opts.signal?.throwIfAborted();
|
|
25907
25962
|
const view = opts.view ?? 0;
|
|
25908
|
-
const
|
|
25963
|
+
const viewKey = view === 0 ? key : `${key}:view:${view}`;
|
|
25964
|
+
const sourceKey = opts.outputResolution ? `${viewKey}:output:${opts.outputResolution}` : viewKey;
|
|
25909
25965
|
let source = this.callSources.get(sourceKey);
|
|
25910
25966
|
if (!source) {
|
|
25911
25967
|
source = new SharedLiveSource({
|
|
@@ -25914,7 +25970,7 @@ var RtcCommandRouter = class {
|
|
|
25914
25970
|
if (!identity)
|
|
25915
25971
|
throw new Error(`rtc: not logged in, cannot call ${route.stationSn}`);
|
|
25916
25972
|
const accountId = route.adminUserId || identity.userId;
|
|
25917
|
-
return makeStream(this.newSession(route, accountId, identity, this.callShards.get(key)), accountId, view);
|
|
25973
|
+
return makeStream(this.newSession(route, accountId, identity, this.callShards.get(key)), accountId, view, opts.outputResolution);
|
|
25918
25974
|
},
|
|
25919
25975
|
label: sourceKey,
|
|
25920
25976
|
logger: this.deps.logger,
|
|
@@ -26218,7 +26274,8 @@ var RtcCommandRouter = class {
|
|
|
26218
26274
|
/**
|
|
26219
26275
|
* One bounded bring-up: the session is up when its command channel opens, and it fails, with the
|
|
26220
26276
|
* session closed and every listener gone, when `connect()` throws, the session closes first (rejecting
|
|
26221
|
-
* with the failure that closed it, when it carries one), or {@link CONNECT_TIMEOUT_MS} passes
|
|
26277
|
+
* with the failure that closed it, when it carries one), or {@link CONNECT_TIMEOUT_MS} passes, naming the
|
|
26278
|
+
* stage the session reached and its peer's last state. A single
|
|
26222
26279
|
* promise, so an early failure cannot leave a second one rejecting unheard.
|
|
26223
26280
|
*/
|
|
26224
26281
|
bringUp(sn, session) {
|
|
@@ -26244,7 +26301,7 @@ var RtcCommandRouter = class {
|
|
|
26244
26301
|
};
|
|
26245
26302
|
const onConnected = () => finish();
|
|
26246
26303
|
const onClose = (err) => finish(err ?? new Error(`rtc: ${sn} session closed before the command channel opened`));
|
|
26247
|
-
const timer = setTimeout(() => finish(new Error(`rtc: ${sn} did not come up within ${CONNECT_TIMEOUT_MS2}ms`)), CONNECT_TIMEOUT_MS2);
|
|
26304
|
+
const timer = setTimeout(() => finish(new Error(`rtc: ${sn} did not come up within ${CONNECT_TIMEOUT_MS2}ms (stage ${session.stage}, peer ${session.peerState ?? "not started"})`)), CONNECT_TIMEOUT_MS2);
|
|
26248
26305
|
session.once("connected", onConnected);
|
|
26249
26306
|
session.once("close", onClose);
|
|
26250
26307
|
Promise.resolve().then(() => session.connect()).catch((e) => finish(e instanceof Error ? e : new Error(String(e))));
|
|
@@ -30765,7 +30822,7 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
|
|
|
30765
30822
|
if (route.signalingMode === "call") {
|
|
30766
30823
|
if (!camera || !isNvrMediaCamera(camera))
|
|
30767
30824
|
return void 0;
|
|
30768
|
-
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel }, isNvrDualLensCamera(camera) ? [0, 1] : [0]);
|
|
30825
|
+
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel }, isNvrDualLensCamera(camera) ? [0, 1] : [0], isNvrFullHdCamera(camera) ? ["1080p"] : void 0);
|
|
30769
30826
|
}
|
|
30770
30827
|
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
30771
30828
|
}
|