@camstack/addon-provider-dreame 0.2.93 → 0.2.95
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/addon.js +151 -10
- package/dist/addon.mjs +151 -10
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -69160,6 +69160,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69160
69160
|
"addon-crash-loop",
|
|
69161
69161
|
"addon-update-available",
|
|
69162
69162
|
"server-update-available",
|
|
69163
|
+
"wrapper-update-available",
|
|
69163
69164
|
"alarm-triggered",
|
|
69164
69165
|
"alarm-armed",
|
|
69165
69166
|
"alarm-disarmed",
|
|
@@ -72605,7 +72606,17 @@ var EventDensityBucketSchema = object({
|
|
|
72605
72606
|
bucketStart: number(),
|
|
72606
72607
|
motion: number().int(),
|
|
72607
72608
|
object: number().int(),
|
|
72608
|
-
audio: number().int()
|
|
72609
|
+
audio: number().int(),
|
|
72610
|
+
/**
|
|
72611
|
+
* Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
|
|
72612
|
+
* as opposed to `audio`, which is their COUNT (D431).
|
|
72613
|
+
*
|
|
72614
|
+
* ABSENT when the bucket holds no audio event. Absence means "nothing was
|
|
72615
|
+
* measured", which is not the same claim as silence, and a lane that cannot
|
|
72616
|
+
* tell them apart paints a quiet hour at full scale — which is exactly what
|
|
72617
|
+
* the count-on-a-level-meter did.
|
|
72618
|
+
*/
|
|
72619
|
+
audioDbfs: number().optional()
|
|
72609
72620
|
});
|
|
72610
72621
|
/**
|
|
72611
72622
|
* One camera's row in a `getEventDensityBatch` answer.
|
|
@@ -74706,6 +74717,53 @@ var ImageContractSchema = object({
|
|
|
74706
74717
|
/** One operator-grade sentence: this node runs image X; the contract says Y. */
|
|
74707
74718
|
message: string()
|
|
74708
74719
|
});
|
|
74720
|
+
/**
|
|
74721
|
+
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
74722
|
+
* produced it.
|
|
74723
|
+
*
|
|
74724
|
+
* There is deliberately NO `canSelfUpdate` and no apply action: a wrapper
|
|
74725
|
+
* update is an ANNOUNCEMENT. The notification body carries the exact command
|
|
74726
|
+
* for a `docker` shell and says the app restarts into the new build for
|
|
74727
|
+
* `electron`, and that sentence is chosen from `kind` alone — a boolean
|
|
74728
|
+
* nothing reads is the defect this repo keeps paying for.
|
|
74729
|
+
*/
|
|
74730
|
+
var WrapperIdentitySchema = object({
|
|
74731
|
+
kind: _enum([
|
|
74732
|
+
"docker",
|
|
74733
|
+
"electron",
|
|
74734
|
+
"native",
|
|
74735
|
+
"contradictory",
|
|
74736
|
+
"unknown"
|
|
74737
|
+
]),
|
|
74738
|
+
/** Every probe that ran, in a stable order. Never empty. */
|
|
74739
|
+
evidence: array(object({
|
|
74740
|
+
/** Which probe this row reports. */
|
|
74741
|
+
fact: _enum([
|
|
74742
|
+
"dockerenv-file",
|
|
74743
|
+
"proc-1-cgroup",
|
|
74744
|
+
"platform",
|
|
74745
|
+
"electron-app-version-env"
|
|
74746
|
+
]),
|
|
74747
|
+
/**
|
|
74748
|
+
* `observed` — this process read it itself. `declared` — the shell told the
|
|
74749
|
+
* process and nothing here can check it. Only the Electron app version is
|
|
74750
|
+
* ever `declared`, and only because a child cannot see its parent.
|
|
74751
|
+
*/
|
|
74752
|
+
mode: _enum(["observed", "declared"]),
|
|
74753
|
+
/** Did the probe support the fact it names? A `false` row is still evidence. */
|
|
74754
|
+
holds: boolean(),
|
|
74755
|
+
/** What was actually read, verbatim enough for an operator to argue with. */
|
|
74756
|
+
detail: string()
|
|
74757
|
+
})),
|
|
74758
|
+
/**
|
|
74759
|
+
* The version of the SHELL, when the shell has one it can state: the
|
|
74760
|
+
* Electron app version it declared, or the baked seed closure that
|
|
74761
|
+
* fingerprints a container / app bundle. `null` for `contradictory` and
|
|
74762
|
+
* `unknown` — which version belongs to the shell is precisely what is not
|
|
74763
|
+
* known there.
|
|
74764
|
+
*/
|
|
74765
|
+
currentVersion: string().nullable()
|
|
74766
|
+
});
|
|
74709
74767
|
var ServerRollbackInfoSchema = object({
|
|
74710
74768
|
/** The version that failed (or was manually rolled back). */
|
|
74711
74769
|
fromVersion: string(),
|
|
@@ -74747,7 +74805,13 @@ var ServerPackageStatusSchema = object({
|
|
|
74747
74805
|
* Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
|
|
74748
74806
|
* version skew: an older provider's payload simply omits it.
|
|
74749
74807
|
*/
|
|
74750
|
-
imageContract: ImageContractSchema.optional()
|
|
74808
|
+
imageContract: ImageContractSchema.optional(),
|
|
74809
|
+
/**
|
|
74810
|
+
* What this node runs INSIDE (see {@link WrapperIdentitySchema}). Optional
|
|
74811
|
+
* for version skew: an older provider's payload simply omits it, and an
|
|
74812
|
+
* absent wrapper is unknown — never `native`.
|
|
74813
|
+
*/
|
|
74814
|
+
wrapper: WrapperIdentitySchema.optional()
|
|
74751
74815
|
});
|
|
74752
74816
|
var ServerUpdateCheckResultSchema = object({
|
|
74753
74817
|
packageName: string(),
|
|
@@ -82793,7 +82857,23 @@ var RecordingRangeSchema = object({
|
|
|
82793
82857
|
});
|
|
82794
82858
|
var RecordingAvailabilitySchema = object({
|
|
82795
82859
|
deviceId: number(),
|
|
82796
|
-
ranges: array(RecordingRangeSchema)
|
|
82860
|
+
ranges: array(RecordingRangeSchema),
|
|
82861
|
+
/**
|
|
82862
|
+
* Every profile this camera has footage in — not only the one `ranges`
|
|
82863
|
+
* describes (D433).
|
|
82864
|
+
*
|
|
82865
|
+
* `ranges` answers for ONE profile by design: the timeline is a single bar,
|
|
82866
|
+
* and enumerating all of them triples the directory reads for a bar that
|
|
82867
|
+
* would look identical. But the answer used to say nothing about that, so a
|
|
82868
|
+
* caller asking "what can I export?" read the single preferred profile as
|
|
82869
|
+
* the only one that exists — on this deployment every camera records `high`
|
|
82870
|
+
* AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
|
|
82871
|
+
* UI could not see it.
|
|
82872
|
+
*
|
|
82873
|
+
* Cheap: it is the same shallow directory read the profile CHOICE already
|
|
82874
|
+
* makes. Ask for a specific profile's ranges with the `profile` input.
|
|
82875
|
+
*/
|
|
82876
|
+
profilesWithFootage: array(string())
|
|
82797
82877
|
});
|
|
82798
82878
|
var RecordingDaysSchema = object({
|
|
82799
82879
|
deviceId: number(),
|
|
@@ -82819,7 +82899,11 @@ var RecordingDaysSchema = object({
|
|
|
82819
82899
|
var RecordingAvailabilityForDeviceSchema = object({
|
|
82820
82900
|
deviceId: number(),
|
|
82821
82901
|
read: _enum(["read", "unreadable"]),
|
|
82822
|
-
ranges: array(RecordingRangeSchema).readonly()
|
|
82902
|
+
ranges: array(RecordingRangeSchema).readonly(),
|
|
82903
|
+
/** Same field, same meaning, as the singular answer (D433). A row that
|
|
82904
|
+
* dropped it would tell a grid caller the archive holds one profile.
|
|
82905
|
+
* Empty on an `'unreadable'` row: nobody looked. */
|
|
82906
|
+
profilesWithFootage: array(string()).readonly()
|
|
82823
82907
|
});
|
|
82824
82908
|
/**
|
|
82825
82909
|
* One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
|
|
@@ -83109,7 +83193,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
|
83109
83193
|
method(object({
|
|
83110
83194
|
deviceId: number(),
|
|
83111
83195
|
fromMs: number(),
|
|
83112
|
-
toMs: number()
|
|
83196
|
+
toMs: number(),
|
|
83197
|
+
/**
|
|
83198
|
+
* Answer for THIS profile instead of the preferred one (D433). Absent
|
|
83199
|
+
* keeps the timeline's behaviour — one bar, one profile, one set of
|
|
83200
|
+
* reads. `profilesWithFootage` on the answer says what may be asked
|
|
83201
|
+
* for.
|
|
83202
|
+
*/
|
|
83203
|
+
profile: string().optional()
|
|
83113
83204
|
}), RecordingAvailabilitySchema, {
|
|
83114
83205
|
kind: "query",
|
|
83115
83206
|
auth: "protected"
|
|
@@ -98475,7 +98566,7 @@ function dreameNavigationCatalog(disabledEntryIds = DREAME_NAVIGATION_DISABLED_E
|
|
|
98475
98566
|
function resolveNavigationFeatures(overrides) {
|
|
98476
98567
|
return {
|
|
98477
98568
|
...DREAME_NAVIGATION_DEFAULT_FEATURES,
|
|
98478
|
-
...overrides?.features
|
|
98569
|
+
...overrides?.features
|
|
98479
98570
|
};
|
|
98480
98571
|
}
|
|
98481
98572
|
/** The disabled entry ids in force for ONE device: the addon set plus its own. */
|
|
@@ -98585,7 +98676,7 @@ var DreameNetworkLinkMonitor = class {
|
|
|
98585
98676
|
meta: {
|
|
98586
98677
|
reason,
|
|
98587
98678
|
rawBars: bars,
|
|
98588
|
-
...next
|
|
98679
|
+
...next
|
|
98589
98680
|
}
|
|
98590
98681
|
});
|
|
98591
98682
|
this.reported = true;
|
|
@@ -144203,6 +144294,10 @@ async function startRfc4571Server(params) {
|
|
|
144203
144294
|
const firstLoss = deferred();
|
|
144204
144295
|
let pendingLoss = null;
|
|
144205
144296
|
let toreDown = false;
|
|
144297
|
+
let audioFrames = 0;
|
|
144298
|
+
let videoAccessUnits = 0;
|
|
144299
|
+
let lastAudioAt = 0;
|
|
144300
|
+
let audioStallReported = false;
|
|
144206
144301
|
let serving = false;
|
|
144207
144302
|
let lastVideoAt = 0;
|
|
144208
144303
|
const reportLoss = (loss) => {
|
|
@@ -144256,9 +144351,11 @@ async function startRfc4571Server(params) {
|
|
|
144256
144351
|
await stream.stop().catch(() => void 0);
|
|
144257
144352
|
throw err;
|
|
144258
144353
|
}
|
|
144354
|
+
const graceStartedAt = Date.now();
|
|
144259
144355
|
await Promise.race([firstAudio.promise, new Promise((resolve) => {
|
|
144260
144356
|
setTimeout(resolve, audioGraceMs).unref?.();
|
|
144261
144357
|
})]);
|
|
144358
|
+
const waitedMs = Date.now() - graceStartedAt;
|
|
144262
144359
|
if (pendingLoss !== null) {
|
|
144263
144360
|
await stream.stop().catch(() => void 0);
|
|
144264
144361
|
throw new Error(`relay lost before the server was up (${describeLoss(pendingLoss)})`);
|
|
@@ -144278,8 +144375,38 @@ async function startRfc4571Server(params) {
|
|
|
144278
144375
|
configHex: audioFacts.configHex
|
|
144279
144376
|
} : void 0);
|
|
144280
144377
|
const muxer = new Rfc4571Muxer(consoleInto(logger), VIDEO_PT, audioFacts !== null ? AUDIO_PT : void 0);
|
|
144281
|
-
stream.on("videoAccessUnit", (e) =>
|
|
144282
|
-
|
|
144378
|
+
stream.on("videoAccessUnit", (e) => {
|
|
144379
|
+
videoAccessUnits += 1;
|
|
144380
|
+
muxer.sendVideoAccessUnit("H264", e.data, e.isKeyframe, e.microseconds);
|
|
144381
|
+
});
|
|
144382
|
+
if (audioFacts !== null) {
|
|
144383
|
+
logger.info("rfc4571: serving audio", { meta: {
|
|
144384
|
+
codec: "aac",
|
|
144385
|
+
sampleRate: audioFacts.sampleRate,
|
|
144386
|
+
channels: audioFacts.channels,
|
|
144387
|
+
waitedMs
|
|
144388
|
+
} });
|
|
144389
|
+
lastAudioAt = Date.now();
|
|
144390
|
+
stream.on("audioFrame", (adts) => {
|
|
144391
|
+
audioFrames += 1;
|
|
144392
|
+
lastAudioAt = Date.now();
|
|
144393
|
+
muxer.sendAudioAdtsFrame(adts);
|
|
144394
|
+
});
|
|
144395
|
+
} else {
|
|
144396
|
+
logger.info("rfc4571: no audio within the grace — serving video-only", { meta: {
|
|
144397
|
+
waitedMs,
|
|
144398
|
+
audioGraceMs
|
|
144399
|
+
} });
|
|
144400
|
+
let reportedLate = false;
|
|
144401
|
+
stream.on("audioFrame", () => {
|
|
144402
|
+
if (reportedLate) return;
|
|
144403
|
+
reportedLate = true;
|
|
144404
|
+
logger.warn("rfc4571: audio arrived after the grace — this session stays video-only", { meta: {
|
|
144405
|
+
lateByMs: Date.now() - graceStartedAt - waitedMs,
|
|
144406
|
+
audioGraceMs
|
|
144407
|
+
} });
|
|
144408
|
+
});
|
|
144409
|
+
}
|
|
144283
144410
|
let liveClients = 0;
|
|
144284
144411
|
let idleTimer = null;
|
|
144285
144412
|
const server = node_net.default.createServer((socket) => {
|
|
@@ -144309,7 +144436,16 @@ async function startRfc4571Server(params) {
|
|
|
144309
144436
|
const addr = server.address();
|
|
144310
144437
|
if (addr === null || typeof addr === "string") throw new Error("failed to bind the RFC4571 server");
|
|
144311
144438
|
const stallSweep = setInterval(() => {
|
|
144312
|
-
if (toreDown
|
|
144439
|
+
if (toreDown) return;
|
|
144440
|
+
if (audioFacts !== null && !audioStallReported && Date.now() - lastAudioAt >= stallTimeoutMs) {
|
|
144441
|
+
audioStallReported = true;
|
|
144442
|
+
logger.warn("rfc4571: audio stopped while the video kept flowing", { meta: {
|
|
144443
|
+
audioFrames,
|
|
144444
|
+
videoAccessUnits,
|
|
144445
|
+
silentForMs: Date.now() - lastAudioAt
|
|
144446
|
+
} });
|
|
144447
|
+
}
|
|
144448
|
+
if (Date.now() - lastVideoAt < stallTimeoutMs) return;
|
|
144313
144449
|
reportLoss({
|
|
144314
144450
|
reason: "stalled",
|
|
144315
144451
|
error: null
|
|
@@ -144328,6 +144464,11 @@ async function startRfc4571Server(params) {
|
|
|
144328
144464
|
} : null,
|
|
144329
144465
|
close: async () => {
|
|
144330
144466
|
toreDown = true;
|
|
144467
|
+
logger.info("rfc4571: session ended", { meta: {
|
|
144468
|
+
videoAccessUnits,
|
|
144469
|
+
audioFrames,
|
|
144470
|
+
declaredAudio: audioFacts !== null
|
|
144471
|
+
} });
|
|
144331
144472
|
clearInterval(stallSweep);
|
|
144332
144473
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
144333
144474
|
try {
|
package/dist/addon.mjs
CHANGED
|
@@ -69157,6 +69157,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69157
69157
|
"addon-crash-loop",
|
|
69158
69158
|
"addon-update-available",
|
|
69159
69159
|
"server-update-available",
|
|
69160
|
+
"wrapper-update-available",
|
|
69160
69161
|
"alarm-triggered",
|
|
69161
69162
|
"alarm-armed",
|
|
69162
69163
|
"alarm-disarmed",
|
|
@@ -72602,7 +72603,17 @@ var EventDensityBucketSchema = object({
|
|
|
72602
72603
|
bucketStart: number(),
|
|
72603
72604
|
motion: number().int(),
|
|
72604
72605
|
object: number().int(),
|
|
72605
|
-
audio: number().int()
|
|
72606
|
+
audio: number().int(),
|
|
72607
|
+
/**
|
|
72608
|
+
* Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
|
|
72609
|
+
* as opposed to `audio`, which is their COUNT (D431).
|
|
72610
|
+
*
|
|
72611
|
+
* ABSENT when the bucket holds no audio event. Absence means "nothing was
|
|
72612
|
+
* measured", which is not the same claim as silence, and a lane that cannot
|
|
72613
|
+
* tell them apart paints a quiet hour at full scale — which is exactly what
|
|
72614
|
+
* the count-on-a-level-meter did.
|
|
72615
|
+
*/
|
|
72616
|
+
audioDbfs: number().optional()
|
|
72606
72617
|
});
|
|
72607
72618
|
/**
|
|
72608
72619
|
* One camera's row in a `getEventDensityBatch` answer.
|
|
@@ -74703,6 +74714,53 @@ var ImageContractSchema = object({
|
|
|
74703
74714
|
/** One operator-grade sentence: this node runs image X; the contract says Y. */
|
|
74704
74715
|
message: string()
|
|
74705
74716
|
});
|
|
74717
|
+
/**
|
|
74718
|
+
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
74719
|
+
* produced it.
|
|
74720
|
+
*
|
|
74721
|
+
* There is deliberately NO `canSelfUpdate` and no apply action: a wrapper
|
|
74722
|
+
* update is an ANNOUNCEMENT. The notification body carries the exact command
|
|
74723
|
+
* for a `docker` shell and says the app restarts into the new build for
|
|
74724
|
+
* `electron`, and that sentence is chosen from `kind` alone — a boolean
|
|
74725
|
+
* nothing reads is the defect this repo keeps paying for.
|
|
74726
|
+
*/
|
|
74727
|
+
var WrapperIdentitySchema = object({
|
|
74728
|
+
kind: _enum([
|
|
74729
|
+
"docker",
|
|
74730
|
+
"electron",
|
|
74731
|
+
"native",
|
|
74732
|
+
"contradictory",
|
|
74733
|
+
"unknown"
|
|
74734
|
+
]),
|
|
74735
|
+
/** Every probe that ran, in a stable order. Never empty. */
|
|
74736
|
+
evidence: array(object({
|
|
74737
|
+
/** Which probe this row reports. */
|
|
74738
|
+
fact: _enum([
|
|
74739
|
+
"dockerenv-file",
|
|
74740
|
+
"proc-1-cgroup",
|
|
74741
|
+
"platform",
|
|
74742
|
+
"electron-app-version-env"
|
|
74743
|
+
]),
|
|
74744
|
+
/**
|
|
74745
|
+
* `observed` — this process read it itself. `declared` — the shell told the
|
|
74746
|
+
* process and nothing here can check it. Only the Electron app version is
|
|
74747
|
+
* ever `declared`, and only because a child cannot see its parent.
|
|
74748
|
+
*/
|
|
74749
|
+
mode: _enum(["observed", "declared"]),
|
|
74750
|
+
/** Did the probe support the fact it names? A `false` row is still evidence. */
|
|
74751
|
+
holds: boolean(),
|
|
74752
|
+
/** What was actually read, verbatim enough for an operator to argue with. */
|
|
74753
|
+
detail: string()
|
|
74754
|
+
})),
|
|
74755
|
+
/**
|
|
74756
|
+
* The version of the SHELL, when the shell has one it can state: the
|
|
74757
|
+
* Electron app version it declared, or the baked seed closure that
|
|
74758
|
+
* fingerprints a container / app bundle. `null` for `contradictory` and
|
|
74759
|
+
* `unknown` — which version belongs to the shell is precisely what is not
|
|
74760
|
+
* known there.
|
|
74761
|
+
*/
|
|
74762
|
+
currentVersion: string().nullable()
|
|
74763
|
+
});
|
|
74706
74764
|
var ServerRollbackInfoSchema = object({
|
|
74707
74765
|
/** The version that failed (or was manually rolled back). */
|
|
74708
74766
|
fromVersion: string(),
|
|
@@ -74744,7 +74802,13 @@ var ServerPackageStatusSchema = object({
|
|
|
74744
74802
|
* Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
|
|
74745
74803
|
* version skew: an older provider's payload simply omits it.
|
|
74746
74804
|
*/
|
|
74747
|
-
imageContract: ImageContractSchema.optional()
|
|
74805
|
+
imageContract: ImageContractSchema.optional(),
|
|
74806
|
+
/**
|
|
74807
|
+
* What this node runs INSIDE (see {@link WrapperIdentitySchema}). Optional
|
|
74808
|
+
* for version skew: an older provider's payload simply omits it, and an
|
|
74809
|
+
* absent wrapper is unknown — never `native`.
|
|
74810
|
+
*/
|
|
74811
|
+
wrapper: WrapperIdentitySchema.optional()
|
|
74748
74812
|
});
|
|
74749
74813
|
var ServerUpdateCheckResultSchema = object({
|
|
74750
74814
|
packageName: string(),
|
|
@@ -82790,7 +82854,23 @@ var RecordingRangeSchema = object({
|
|
|
82790
82854
|
});
|
|
82791
82855
|
var RecordingAvailabilitySchema = object({
|
|
82792
82856
|
deviceId: number(),
|
|
82793
|
-
ranges: array(RecordingRangeSchema)
|
|
82857
|
+
ranges: array(RecordingRangeSchema),
|
|
82858
|
+
/**
|
|
82859
|
+
* Every profile this camera has footage in — not only the one `ranges`
|
|
82860
|
+
* describes (D433).
|
|
82861
|
+
*
|
|
82862
|
+
* `ranges` answers for ONE profile by design: the timeline is a single bar,
|
|
82863
|
+
* and enumerating all of them triples the directory reads for a bar that
|
|
82864
|
+
* would look identical. But the answer used to say nothing about that, so a
|
|
82865
|
+
* caller asking "what can I export?" read the single preferred profile as
|
|
82866
|
+
* the only one that exists — on this deployment every camera records `high`
|
|
82867
|
+
* AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
|
|
82868
|
+
* UI could not see it.
|
|
82869
|
+
*
|
|
82870
|
+
* Cheap: it is the same shallow directory read the profile CHOICE already
|
|
82871
|
+
* makes. Ask for a specific profile's ranges with the `profile` input.
|
|
82872
|
+
*/
|
|
82873
|
+
profilesWithFootage: array(string())
|
|
82794
82874
|
});
|
|
82795
82875
|
var RecordingDaysSchema = object({
|
|
82796
82876
|
deviceId: number(),
|
|
@@ -82816,7 +82896,11 @@ var RecordingDaysSchema = object({
|
|
|
82816
82896
|
var RecordingAvailabilityForDeviceSchema = object({
|
|
82817
82897
|
deviceId: number(),
|
|
82818
82898
|
read: _enum(["read", "unreadable"]),
|
|
82819
|
-
ranges: array(RecordingRangeSchema).readonly()
|
|
82899
|
+
ranges: array(RecordingRangeSchema).readonly(),
|
|
82900
|
+
/** Same field, same meaning, as the singular answer (D433). A row that
|
|
82901
|
+
* dropped it would tell a grid caller the archive holds one profile.
|
|
82902
|
+
* Empty on an `'unreadable'` row: nobody looked. */
|
|
82903
|
+
profilesWithFootage: array(string()).readonly()
|
|
82820
82904
|
});
|
|
82821
82905
|
/**
|
|
82822
82906
|
* One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
|
|
@@ -83106,7 +83190,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
|
83106
83190
|
method(object({
|
|
83107
83191
|
deviceId: number(),
|
|
83108
83192
|
fromMs: number(),
|
|
83109
|
-
toMs: number()
|
|
83193
|
+
toMs: number(),
|
|
83194
|
+
/**
|
|
83195
|
+
* Answer for THIS profile instead of the preferred one (D433). Absent
|
|
83196
|
+
* keeps the timeline's behaviour — one bar, one profile, one set of
|
|
83197
|
+
* reads. `profilesWithFootage` on the answer says what may be asked
|
|
83198
|
+
* for.
|
|
83199
|
+
*/
|
|
83200
|
+
profile: string().optional()
|
|
83110
83201
|
}), RecordingAvailabilitySchema, {
|
|
83111
83202
|
kind: "query",
|
|
83112
83203
|
auth: "protected"
|
|
@@ -98472,7 +98563,7 @@ function dreameNavigationCatalog(disabledEntryIds = DREAME_NAVIGATION_DISABLED_E
|
|
|
98472
98563
|
function resolveNavigationFeatures(overrides) {
|
|
98473
98564
|
return {
|
|
98474
98565
|
...DREAME_NAVIGATION_DEFAULT_FEATURES,
|
|
98475
|
-
...overrides?.features
|
|
98566
|
+
...overrides?.features
|
|
98476
98567
|
};
|
|
98477
98568
|
}
|
|
98478
98569
|
/** The disabled entry ids in force for ONE device: the addon set plus its own. */
|
|
@@ -98582,7 +98673,7 @@ var DreameNetworkLinkMonitor = class {
|
|
|
98582
98673
|
meta: {
|
|
98583
98674
|
reason,
|
|
98584
98675
|
rawBars: bars,
|
|
98585
|
-
...next
|
|
98676
|
+
...next
|
|
98586
98677
|
}
|
|
98587
98678
|
});
|
|
98588
98679
|
this.reported = true;
|
|
@@ -144200,6 +144291,10 @@ async function startRfc4571Server(params) {
|
|
|
144200
144291
|
const firstLoss = deferred();
|
|
144201
144292
|
let pendingLoss = null;
|
|
144202
144293
|
let toreDown = false;
|
|
144294
|
+
let audioFrames = 0;
|
|
144295
|
+
let videoAccessUnits = 0;
|
|
144296
|
+
let lastAudioAt = 0;
|
|
144297
|
+
let audioStallReported = false;
|
|
144203
144298
|
let serving = false;
|
|
144204
144299
|
let lastVideoAt = 0;
|
|
144205
144300
|
const reportLoss = (loss) => {
|
|
@@ -144253,9 +144348,11 @@ async function startRfc4571Server(params) {
|
|
|
144253
144348
|
await stream.stop().catch(() => void 0);
|
|
144254
144349
|
throw err;
|
|
144255
144350
|
}
|
|
144351
|
+
const graceStartedAt = Date.now();
|
|
144256
144352
|
await Promise.race([firstAudio.promise, new Promise((resolve) => {
|
|
144257
144353
|
setTimeout(resolve, audioGraceMs).unref?.();
|
|
144258
144354
|
})]);
|
|
144355
|
+
const waitedMs = Date.now() - graceStartedAt;
|
|
144259
144356
|
if (pendingLoss !== null) {
|
|
144260
144357
|
await stream.stop().catch(() => void 0);
|
|
144261
144358
|
throw new Error(`relay lost before the server was up (${describeLoss(pendingLoss)})`);
|
|
@@ -144275,8 +144372,38 @@ async function startRfc4571Server(params) {
|
|
|
144275
144372
|
configHex: audioFacts.configHex
|
|
144276
144373
|
} : void 0);
|
|
144277
144374
|
const muxer = new Rfc4571Muxer(consoleInto(logger), VIDEO_PT, audioFacts !== null ? AUDIO_PT : void 0);
|
|
144278
|
-
stream.on("videoAccessUnit", (e) =>
|
|
144279
|
-
|
|
144375
|
+
stream.on("videoAccessUnit", (e) => {
|
|
144376
|
+
videoAccessUnits += 1;
|
|
144377
|
+
muxer.sendVideoAccessUnit("H264", e.data, e.isKeyframe, e.microseconds);
|
|
144378
|
+
});
|
|
144379
|
+
if (audioFacts !== null) {
|
|
144380
|
+
logger.info("rfc4571: serving audio", { meta: {
|
|
144381
|
+
codec: "aac",
|
|
144382
|
+
sampleRate: audioFacts.sampleRate,
|
|
144383
|
+
channels: audioFacts.channels,
|
|
144384
|
+
waitedMs
|
|
144385
|
+
} });
|
|
144386
|
+
lastAudioAt = Date.now();
|
|
144387
|
+
stream.on("audioFrame", (adts) => {
|
|
144388
|
+
audioFrames += 1;
|
|
144389
|
+
lastAudioAt = Date.now();
|
|
144390
|
+
muxer.sendAudioAdtsFrame(adts);
|
|
144391
|
+
});
|
|
144392
|
+
} else {
|
|
144393
|
+
logger.info("rfc4571: no audio within the grace — serving video-only", { meta: {
|
|
144394
|
+
waitedMs,
|
|
144395
|
+
audioGraceMs
|
|
144396
|
+
} });
|
|
144397
|
+
let reportedLate = false;
|
|
144398
|
+
stream.on("audioFrame", () => {
|
|
144399
|
+
if (reportedLate) return;
|
|
144400
|
+
reportedLate = true;
|
|
144401
|
+
logger.warn("rfc4571: audio arrived after the grace — this session stays video-only", { meta: {
|
|
144402
|
+
lateByMs: Date.now() - graceStartedAt - waitedMs,
|
|
144403
|
+
audioGraceMs
|
|
144404
|
+
} });
|
|
144405
|
+
});
|
|
144406
|
+
}
|
|
144280
144407
|
let liveClients = 0;
|
|
144281
144408
|
let idleTimer = null;
|
|
144282
144409
|
const server = net.createServer((socket) => {
|
|
@@ -144306,7 +144433,16 @@ async function startRfc4571Server(params) {
|
|
|
144306
144433
|
const addr = server.address();
|
|
144307
144434
|
if (addr === null || typeof addr === "string") throw new Error("failed to bind the RFC4571 server");
|
|
144308
144435
|
const stallSweep = setInterval(() => {
|
|
144309
|
-
if (toreDown
|
|
144436
|
+
if (toreDown) return;
|
|
144437
|
+
if (audioFacts !== null && !audioStallReported && Date.now() - lastAudioAt >= stallTimeoutMs) {
|
|
144438
|
+
audioStallReported = true;
|
|
144439
|
+
logger.warn("rfc4571: audio stopped while the video kept flowing", { meta: {
|
|
144440
|
+
audioFrames,
|
|
144441
|
+
videoAccessUnits,
|
|
144442
|
+
silentForMs: Date.now() - lastAudioAt
|
|
144443
|
+
} });
|
|
144444
|
+
}
|
|
144445
|
+
if (Date.now() - lastVideoAt < stallTimeoutMs) return;
|
|
144310
144446
|
reportLoss({
|
|
144311
144447
|
reason: "stalled",
|
|
144312
144448
|
error: null
|
|
@@ -144325,6 +144461,11 @@ async function startRfc4571Server(params) {
|
|
|
144325
144461
|
} : null,
|
|
144326
144462
|
close: async () => {
|
|
144327
144463
|
toreDown = true;
|
|
144464
|
+
logger.info("rfc4571: session ended", { meta: {
|
|
144465
|
+
videoAccessUnits,
|
|
144466
|
+
audioFrames,
|
|
144467
|
+
declaredAudio: audioFacts !== null
|
|
144468
|
+
} });
|
|
144328
144469
|
clearInterval(stallSweep);
|
|
144329
144470
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
144330
144471
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-dreame",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.95",
|
|
4
4
|
"description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|