@camstack/addon-pipeline 1.2.58 → 1.2.60
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/stream-broker/index.js +78 -34
- package/dist/stream-broker/index.mjs +78 -34
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Bxm34WFr.js → MaskShapeCanvas-DI4BY7W2-BWAtF7b-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-S8uFj2nP.js → MotionZonesSettings-NcxxQN8r-ByQWV87C.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-nhHVCSoL.js → PrivacyMaskSettings-APgPLF7p-B9uGNcFR.js} +1 -1
- package/embed-dist/assets/{index-hICPL_hd.js → index-Dcfe-5Ig.js} +4 -4
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -21552,11 +21552,13 @@ var AdaptiveController = class {
|
|
|
21552
21552
|
*/
|
|
21553
21553
|
armed = false;
|
|
21554
21554
|
failures;
|
|
21555
|
+
upgradeCount;
|
|
21555
21556
|
constructor(opts) {
|
|
21556
21557
|
this.opts = opts;
|
|
21557
21558
|
this.tracker = new LossTracker(opts.config);
|
|
21558
21559
|
this.tier = opts.initialTier;
|
|
21559
21560
|
this.failures = new Map(opts.priorFailures ?? []);
|
|
21561
|
+
this.upgradeCount = Math.max(0, opts.priorUpgradeCount ?? 0);
|
|
21560
21562
|
}
|
|
21561
21563
|
get currentTier() {
|
|
21562
21564
|
return this.tier;
|
|
@@ -21569,6 +21571,9 @@ var AdaptiveController = class {
|
|
|
21569
21571
|
get failureCounts() {
|
|
21570
21572
|
return this.failures;
|
|
21571
21573
|
}
|
|
21574
|
+
get successfulUpgradeCount() {
|
|
21575
|
+
return this.upgradeCount;
|
|
21576
|
+
}
|
|
21572
21577
|
onLoss(sample, now) {
|
|
21573
21578
|
this.tracker.track(sample, now);
|
|
21574
21579
|
}
|
|
@@ -21621,8 +21626,9 @@ var AdaptiveController = class {
|
|
|
21621
21626
|
*/
|
|
21622
21627
|
requiredHealthyMs(tier) {
|
|
21623
21628
|
const failures = this.failures.get(tier) ?? 0;
|
|
21624
|
-
|
|
21625
|
-
|
|
21629
|
+
const progressiveMs = Math.min(this.opts.config.upgradeHealthyMs * this.opts.config.upgradeHealthyGrowthFactor ** this.upgradeCount, this.opts.config.maxProgressiveUpgradeHealthyMs);
|
|
21630
|
+
if (failures <= 0) return progressiveMs;
|
|
21631
|
+
return Math.min(progressiveMs * 2 ** failures, this.opts.config.maxUpgradeBackoffMs);
|
|
21626
21632
|
}
|
|
21627
21633
|
commitChange(now) {
|
|
21628
21634
|
this.lastChangeAt = now;
|
|
@@ -21659,6 +21665,7 @@ var AdaptiveController = class {
|
|
|
21659
21665
|
this.tier = next;
|
|
21660
21666
|
this.mtuReduced = false;
|
|
21661
21667
|
this.transcodeEngaged = false;
|
|
21668
|
+
this.upgradeCount += 1;
|
|
21662
21669
|
this.opts.onSwitchTier(next);
|
|
21663
21670
|
this.commitChange(now);
|
|
21664
21671
|
this.healthySince = now;
|
|
@@ -21718,21 +21725,32 @@ function clampPlaybackRate(rate) {
|
|
|
21718
21725
|
if (!Number.isFinite(rate) || rate <= 0) return 0;
|
|
21719
21726
|
return Math.min(4, Math.max(.25, rate));
|
|
21720
21727
|
}
|
|
21721
|
-
var ServerMessageSchema = require_dist.discriminatedUnion("t", [
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
|
|
21734
|
-
|
|
21735
|
-
|
|
21728
|
+
var ServerMessageSchema = require_dist.discriminatedUnion("t", [
|
|
21729
|
+
require_dist.object({
|
|
21730
|
+
t: require_dist.literal("state"),
|
|
21731
|
+
state: require_dist._enum([
|
|
21732
|
+
"live",
|
|
21733
|
+
"loading",
|
|
21734
|
+
"playing",
|
|
21735
|
+
"paused",
|
|
21736
|
+
"gap",
|
|
21737
|
+
"ended",
|
|
21738
|
+
"error"
|
|
21739
|
+
])
|
|
21740
|
+
}),
|
|
21741
|
+
require_dist.object({
|
|
21742
|
+
t: require_dist.literal("position"),
|
|
21743
|
+
epochMs: require_dist.number()
|
|
21744
|
+
}),
|
|
21745
|
+
require_dist.object({
|
|
21746
|
+
t: require_dist.literal("liveProfile"),
|
|
21747
|
+
profile: require_dist._enum([
|
|
21748
|
+
"high",
|
|
21749
|
+
"mid",
|
|
21750
|
+
"low"
|
|
21751
|
+
])
|
|
21752
|
+
})
|
|
21753
|
+
]);
|
|
21736
21754
|
function parseControlMessage(raw) {
|
|
21737
21755
|
try {
|
|
21738
21756
|
const parsed = ControlMessageSchema.safeParse(JSON.parse(raw));
|
|
@@ -22071,7 +22089,7 @@ var SEEK_DISCONTINUITY_MS = 250;
|
|
|
22071
22089
|
* sustainable there — cross-fragment moves remain paced by the demux + the
|
|
22072
22090
|
* one-in-flight guard, so this cannot spawn parallel demuxes.
|
|
22073
22091
|
*/
|
|
22074
|
-
var SCRUB_COALESCE_MS =
|
|
22092
|
+
var SCRUB_COALESCE_MS = 33;
|
|
22075
22093
|
/**
|
|
22076
22094
|
* How much forward slack two fragments may have and still count as ADJACENT
|
|
22077
22095
|
* for sweep continuity. Two real cases live inside it: the ±1 ms rounding of a
|
|
@@ -23608,6 +23626,12 @@ var TimelineSession = class {
|
|
|
23608
23626
|
mode = "live";
|
|
23609
23627
|
feeder = null;
|
|
23610
23628
|
profile = "mid";
|
|
23629
|
+
/** Transport rate belongs to the session, not to one profile-specific feeder.
|
|
23630
|
+
* Scrub pins `low`; release restores the selected playback profile and may
|
|
23631
|
+
* therefore rebuild the feeder. Carrying the rate keeps a paused scrub
|
|
23632
|
+
* paused across that required quality transition. */
|
|
23633
|
+
rate = 1;
|
|
23634
|
+
liveProfileReported = false;
|
|
23611
23635
|
/**
|
|
23612
23636
|
* The last position the viewer was SHOWN (every `_emitPosition`). Handed to
|
|
23613
23637
|
* `seek` as `travelFromMs` so the seek travel animation works across feeder
|
|
@@ -23631,6 +23655,7 @@ var TimelineSession = class {
|
|
|
23631
23655
|
return this.mode;
|
|
23632
23656
|
}
|
|
23633
23657
|
async handleControl(msg) {
|
|
23658
|
+
this.emitLiveProfile();
|
|
23634
23659
|
switch (msg.t) {
|
|
23635
23660
|
case "playRecorded": {
|
|
23636
23661
|
const profileChanged = msg.profile !== this.profile;
|
|
@@ -23659,6 +23684,7 @@ var TimelineSession = class {
|
|
|
23659
23684
|
if (this.mode === "recorded") this.feeder?.scrubAck();
|
|
23660
23685
|
break;
|
|
23661
23686
|
case "setRate":
|
|
23687
|
+
this.rate = msg.rate;
|
|
23662
23688
|
this.feeder?.setRate(msg.rate);
|
|
23663
23689
|
break;
|
|
23664
23690
|
case "stepFrame":
|
|
@@ -23683,6 +23709,7 @@ var TimelineSession = class {
|
|
|
23683
23709
|
this.deps.session.setLiveFeedGate(true);
|
|
23684
23710
|
this.mode = "recorded";
|
|
23685
23711
|
const feeder = this.deps.makeFeeder(this.profile);
|
|
23712
|
+
if (this.rate !== 1) feeder.setRate(this.rate);
|
|
23686
23713
|
this.feeder = feeder;
|
|
23687
23714
|
return feeder;
|
|
23688
23715
|
}
|
|
@@ -23710,6 +23737,14 @@ var TimelineSession = class {
|
|
|
23710
23737
|
epochMs
|
|
23711
23738
|
}));
|
|
23712
23739
|
}
|
|
23740
|
+
emitLiveProfile() {
|
|
23741
|
+
if (this.liveProfileReported || this.deps.initialLiveProfile === void 0) return;
|
|
23742
|
+
this.liveProfileReported = true;
|
|
23743
|
+
this.deps.session.sendControl(serializeServerMessage({
|
|
23744
|
+
t: "liveProfile",
|
|
23745
|
+
profile: this.deps.initialLiveProfile
|
|
23746
|
+
}));
|
|
23747
|
+
}
|
|
23713
23748
|
dispose() {
|
|
23714
23749
|
this.feeder?.dispose();
|
|
23715
23750
|
this.feeder = null;
|
|
@@ -31369,8 +31404,10 @@ var ADAPTIVE_CONFIG = {
|
|
|
31369
31404
|
analyzeWindowMs: 15e3,
|
|
31370
31405
|
minFailPercent: .05,
|
|
31371
31406
|
waitAfterResetMs: 8e3,
|
|
31372
|
-
upgradeHealthyMs:
|
|
31373
|
-
|
|
31407
|
+
upgradeHealthyMs: 4e3,
|
|
31408
|
+
upgradeHealthyGrowthFactor: 3,
|
|
31409
|
+
maxProgressiveUpgradeHealthyMs: 3e4,
|
|
31410
|
+
minTimeBetweenChangesMs: 4e3,
|
|
31374
31411
|
maxUpgradeBackoffMs: 9e5
|
|
31375
31412
|
};
|
|
31376
31413
|
/**
|
|
@@ -31538,8 +31575,7 @@ function scoreBroker(tier, broker, hints) {
|
|
|
31538
31575
|
const fallback = tier ? LABEL_DEFAULTS[tier] : void 0;
|
|
31539
31576
|
const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : fallback?.bitrateKbps ?? 2e3;
|
|
31540
31577
|
const srcPixels = fallback?.pixels ?? 1920 * 1080;
|
|
31541
|
-
const
|
|
31542
|
-
const targetPixels = (hints.viewportWidth ?? 1920) * dpr * ((hints.viewportHeight ?? 1080) * dpr);
|
|
31578
|
+
const targetPixels = (hints.viewportWidth ?? 1920) * (hints.viewportHeight ?? 1080);
|
|
31543
31579
|
const pixelDistance = Math.abs(srcPixels - targetPixels) / 1e6;
|
|
31544
31580
|
let bandwidthPenalty = 0;
|
|
31545
31581
|
if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {
|
|
@@ -32284,6 +32320,7 @@ var BrokerWebrtcServer = class {
|
|
|
32284
32320
|
}
|
|
32285
32321
|
});
|
|
32286
32322
|
const scrubCache = new ScrubFragmentCache();
|
|
32323
|
+
const initialLiveProfile = this.profileTierResolver?.(brokerId) ?? null;
|
|
32287
32324
|
const readGopAccessor = this.readRecordedGopBytes;
|
|
32288
32325
|
const readGop = readGopAccessor ? async (args) => {
|
|
32289
32326
|
const res = await readGopAccessor({
|
|
@@ -32303,6 +32340,7 @@ var BrokerWebrtcServer = class {
|
|
|
32303
32340
|
timeline = new TimelineSession({
|
|
32304
32341
|
session,
|
|
32305
32342
|
deviceId,
|
|
32343
|
+
...initialLiveProfile !== null ? { initialLiveProfile } : {},
|
|
32306
32344
|
warm: readGop ? (profile, epochMs) => {
|
|
32307
32345
|
warmFragment({
|
|
32308
32346
|
deviceId,
|
|
@@ -32437,7 +32475,7 @@ var BrokerWebrtcServer = class {
|
|
|
32437
32475
|
};
|
|
32438
32476
|
if (streamId.endsWith("/adaptive")) {
|
|
32439
32477
|
const initialTier = this.profileTierResolver?.(brokerId) ?? "high";
|
|
32440
|
-
this.startAdaptive(entry, deviceId, initialTier, sessionId, intent?.failures);
|
|
32478
|
+
this.startAdaptive(entry, deviceId, initialTier, sessionId, intent?.failures, intent?.upgradeCount);
|
|
32441
32479
|
}
|
|
32442
32480
|
return {
|
|
32443
32481
|
sessionId,
|
|
@@ -32531,17 +32569,18 @@ var BrokerWebrtcServer = class {
|
|
|
32531
32569
|
* server.
|
|
32532
32570
|
*
|
|
32533
32571
|
* The client-loss % is turned into a monotonic {@link LossSample} LOCALLY:
|
|
32534
|
-
*
|
|
32535
|
-
*
|
|
32536
|
-
*
|
|
32572
|
+
* per-controller cumulative sequence/loss counters advance for every
|
|
32573
|
+
* reported interval (including 0% loss), so a perfectly healthy LAN is real
|
|
32574
|
+
* evidence rather than silence, exactly like RTCP's cumulative fields.
|
|
32537
32575
|
* Both paths feed the same controller; the tracker naturally honours
|
|
32538
32576
|
* whichever produces the higher loss ratio across its window. The async
|
|
32539
32577
|
* poll is guarded against overlapping ticks via `tickInFlight`.
|
|
32540
32578
|
*/
|
|
32541
|
-
startAdaptive(entry, deviceId, initialTier, sessionId, priorFailures) {
|
|
32579
|
+
startAdaptive(entry, deviceId, initialTier, sessionId, priorFailures, priorUpgradeCount) {
|
|
32542
32580
|
const controller = new AdaptiveController({
|
|
32543
32581
|
initialTier,
|
|
32544
32582
|
priorFailures,
|
|
32583
|
+
priorUpgradeCount,
|
|
32545
32584
|
config: ADAPTIVE_CONFIG,
|
|
32546
32585
|
onSwitchTier: (tier) => {
|
|
32547
32586
|
this.switchSessionTier(entry, deviceId, tier, sessionId);
|
|
@@ -32553,7 +32592,8 @@ var BrokerWebrtcServer = class {
|
|
|
32553
32592
|
});
|
|
32554
32593
|
entry.session.onReceiverReport((s) => controller.onLoss(s, Date.now()));
|
|
32555
32594
|
entry.adaptiveController = controller;
|
|
32556
|
-
let
|
|
32595
|
+
let clientHighestSequence = 0;
|
|
32596
|
+
let clientPacketsLost = 0;
|
|
32557
32597
|
let tickInFlight = false;
|
|
32558
32598
|
const CLIENT_LOSS_SPAN = 1e3;
|
|
32559
32599
|
entry.adaptiveTimer = setInterval(() => {
|
|
@@ -32567,11 +32607,12 @@ var BrokerWebrtcServer = class {
|
|
|
32567
32607
|
}
|
|
32568
32608
|
const pct = this.clientLossPct ? await this.clientLossPct(deviceId) : null;
|
|
32569
32609
|
const now = Date.now();
|
|
32570
|
-
if (pct !== null && pct !== void 0
|
|
32571
|
-
|
|
32610
|
+
if (pct !== null && pct !== void 0) {
|
|
32611
|
+
clientHighestSequence += CLIENT_LOSS_SPAN;
|
|
32612
|
+
clientPacketsLost += Math.round(Math.max(0, Math.min(100, pct)) / 100 * CLIENT_LOSS_SPAN);
|
|
32572
32613
|
controller.onLoss({
|
|
32573
|
-
packetsLost:
|
|
32574
|
-
highestSequence:
|
|
32614
|
+
packetsLost: clientPacketsLost,
|
|
32615
|
+
highestSequence: clientHighestSequence
|
|
32575
32616
|
}, now);
|
|
32576
32617
|
}
|
|
32577
32618
|
controller.tick(now);
|
|
@@ -32597,10 +32638,12 @@ var BrokerWebrtcServer = class {
|
|
|
32597
32638
|
switchSessionTier(entry, deviceId, tier, sessionId, forceTranscodeDown = false) {
|
|
32598
32639
|
const prev = this.adaptiveIntent.get(sessionId);
|
|
32599
32640
|
const failures = new Map(entry.adaptiveController?.failureCounts ?? prev?.failures);
|
|
32641
|
+
const upgradeCount = entry.adaptiveController?.successfulUpgradeCount ?? prev?.upgradeCount ?? 0;
|
|
32600
32642
|
this.adaptiveIntent.set(sessionId, {
|
|
32601
32643
|
prefersTier: tier,
|
|
32602
32644
|
forceTranscodeDown: forceTranscodeDown || (prev?.forceTranscodeDown ?? false),
|
|
32603
|
-
failures
|
|
32645
|
+
failures,
|
|
32646
|
+
upgradeCount
|
|
32604
32647
|
});
|
|
32605
32648
|
const epoch = (entry.session.pendingRenegotiation?.epoch ?? 0) + 1;
|
|
32606
32649
|
entry.session.pendingRenegotiation = {
|
|
@@ -32615,7 +32658,8 @@ var BrokerWebrtcServer = class {
|
|
|
32615
32658
|
tier,
|
|
32616
32659
|
forceTranscodeDown,
|
|
32617
32660
|
epoch,
|
|
32618
|
-
failures: Object.fromEntries(failures)
|
|
32661
|
+
failures: Object.fromEntries(failures),
|
|
32662
|
+
upgradeCount
|
|
32619
32663
|
}
|
|
32620
32664
|
});
|
|
32621
32665
|
}
|
|
@@ -21546,11 +21546,13 @@ var AdaptiveController = class {
|
|
|
21546
21546
|
*/
|
|
21547
21547
|
armed = false;
|
|
21548
21548
|
failures;
|
|
21549
|
+
upgradeCount;
|
|
21549
21550
|
constructor(opts) {
|
|
21550
21551
|
this.opts = opts;
|
|
21551
21552
|
this.tracker = new LossTracker(opts.config);
|
|
21552
21553
|
this.tier = opts.initialTier;
|
|
21553
21554
|
this.failures = new Map(opts.priorFailures ?? []);
|
|
21555
|
+
this.upgradeCount = Math.max(0, opts.priorUpgradeCount ?? 0);
|
|
21554
21556
|
}
|
|
21555
21557
|
get currentTier() {
|
|
21556
21558
|
return this.tier;
|
|
@@ -21563,6 +21565,9 @@ var AdaptiveController = class {
|
|
|
21563
21565
|
get failureCounts() {
|
|
21564
21566
|
return this.failures;
|
|
21565
21567
|
}
|
|
21568
|
+
get successfulUpgradeCount() {
|
|
21569
|
+
return this.upgradeCount;
|
|
21570
|
+
}
|
|
21566
21571
|
onLoss(sample, now) {
|
|
21567
21572
|
this.tracker.track(sample, now);
|
|
21568
21573
|
}
|
|
@@ -21615,8 +21620,9 @@ var AdaptiveController = class {
|
|
|
21615
21620
|
*/
|
|
21616
21621
|
requiredHealthyMs(tier) {
|
|
21617
21622
|
const failures = this.failures.get(tier) ?? 0;
|
|
21618
|
-
|
|
21619
|
-
|
|
21623
|
+
const progressiveMs = Math.min(this.opts.config.upgradeHealthyMs * this.opts.config.upgradeHealthyGrowthFactor ** this.upgradeCount, this.opts.config.maxProgressiveUpgradeHealthyMs);
|
|
21624
|
+
if (failures <= 0) return progressiveMs;
|
|
21625
|
+
return Math.min(progressiveMs * 2 ** failures, this.opts.config.maxUpgradeBackoffMs);
|
|
21620
21626
|
}
|
|
21621
21627
|
commitChange(now) {
|
|
21622
21628
|
this.lastChangeAt = now;
|
|
@@ -21653,6 +21659,7 @@ var AdaptiveController = class {
|
|
|
21653
21659
|
this.tier = next;
|
|
21654
21660
|
this.mtuReduced = false;
|
|
21655
21661
|
this.transcodeEngaged = false;
|
|
21662
|
+
this.upgradeCount += 1;
|
|
21656
21663
|
this.opts.onSwitchTier(next);
|
|
21657
21664
|
this.commitChange(now);
|
|
21658
21665
|
this.healthySince = now;
|
|
@@ -21712,21 +21719,32 @@ function clampPlaybackRate(rate) {
|
|
|
21712
21719
|
if (!Number.isFinite(rate) || rate <= 0) return 0;
|
|
21713
21720
|
return Math.min(4, Math.max(.25, rate));
|
|
21714
21721
|
}
|
|
21715
|
-
var ServerMessageSchema = discriminatedUnion("t", [
|
|
21716
|
-
|
|
21717
|
-
|
|
21718
|
-
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21722
|
+
var ServerMessageSchema = discriminatedUnion("t", [
|
|
21723
|
+
object({
|
|
21724
|
+
t: literal("state"),
|
|
21725
|
+
state: _enum([
|
|
21726
|
+
"live",
|
|
21727
|
+
"loading",
|
|
21728
|
+
"playing",
|
|
21729
|
+
"paused",
|
|
21730
|
+
"gap",
|
|
21731
|
+
"ended",
|
|
21732
|
+
"error"
|
|
21733
|
+
])
|
|
21734
|
+
}),
|
|
21735
|
+
object({
|
|
21736
|
+
t: literal("position"),
|
|
21737
|
+
epochMs: number()
|
|
21738
|
+
}),
|
|
21739
|
+
object({
|
|
21740
|
+
t: literal("liveProfile"),
|
|
21741
|
+
profile: _enum([
|
|
21742
|
+
"high",
|
|
21743
|
+
"mid",
|
|
21744
|
+
"low"
|
|
21745
|
+
])
|
|
21746
|
+
})
|
|
21747
|
+
]);
|
|
21730
21748
|
function parseControlMessage(raw) {
|
|
21731
21749
|
try {
|
|
21732
21750
|
const parsed = ControlMessageSchema.safeParse(JSON.parse(raw));
|
|
@@ -22065,7 +22083,7 @@ var SEEK_DISCONTINUITY_MS = 250;
|
|
|
22065
22083
|
* sustainable there — cross-fragment moves remain paced by the demux + the
|
|
22066
22084
|
* one-in-flight guard, so this cannot spawn parallel demuxes.
|
|
22067
22085
|
*/
|
|
22068
|
-
var SCRUB_COALESCE_MS =
|
|
22086
|
+
var SCRUB_COALESCE_MS = 33;
|
|
22069
22087
|
/**
|
|
22070
22088
|
* How much forward slack two fragments may have and still count as ADJACENT
|
|
22071
22089
|
* for sweep continuity. Two real cases live inside it: the ±1 ms rounding of a
|
|
@@ -23602,6 +23620,12 @@ var TimelineSession = class {
|
|
|
23602
23620
|
mode = "live";
|
|
23603
23621
|
feeder = null;
|
|
23604
23622
|
profile = "mid";
|
|
23623
|
+
/** Transport rate belongs to the session, not to one profile-specific feeder.
|
|
23624
|
+
* Scrub pins `low`; release restores the selected playback profile and may
|
|
23625
|
+
* therefore rebuild the feeder. Carrying the rate keeps a paused scrub
|
|
23626
|
+
* paused across that required quality transition. */
|
|
23627
|
+
rate = 1;
|
|
23628
|
+
liveProfileReported = false;
|
|
23605
23629
|
/**
|
|
23606
23630
|
* The last position the viewer was SHOWN (every `_emitPosition`). Handed to
|
|
23607
23631
|
* `seek` as `travelFromMs` so the seek travel animation works across feeder
|
|
@@ -23625,6 +23649,7 @@ var TimelineSession = class {
|
|
|
23625
23649
|
return this.mode;
|
|
23626
23650
|
}
|
|
23627
23651
|
async handleControl(msg) {
|
|
23652
|
+
this.emitLiveProfile();
|
|
23628
23653
|
switch (msg.t) {
|
|
23629
23654
|
case "playRecorded": {
|
|
23630
23655
|
const profileChanged = msg.profile !== this.profile;
|
|
@@ -23653,6 +23678,7 @@ var TimelineSession = class {
|
|
|
23653
23678
|
if (this.mode === "recorded") this.feeder?.scrubAck();
|
|
23654
23679
|
break;
|
|
23655
23680
|
case "setRate":
|
|
23681
|
+
this.rate = msg.rate;
|
|
23656
23682
|
this.feeder?.setRate(msg.rate);
|
|
23657
23683
|
break;
|
|
23658
23684
|
case "stepFrame":
|
|
@@ -23677,6 +23703,7 @@ var TimelineSession = class {
|
|
|
23677
23703
|
this.deps.session.setLiveFeedGate(true);
|
|
23678
23704
|
this.mode = "recorded";
|
|
23679
23705
|
const feeder = this.deps.makeFeeder(this.profile);
|
|
23706
|
+
if (this.rate !== 1) feeder.setRate(this.rate);
|
|
23680
23707
|
this.feeder = feeder;
|
|
23681
23708
|
return feeder;
|
|
23682
23709
|
}
|
|
@@ -23704,6 +23731,14 @@ var TimelineSession = class {
|
|
|
23704
23731
|
epochMs
|
|
23705
23732
|
}));
|
|
23706
23733
|
}
|
|
23734
|
+
emitLiveProfile() {
|
|
23735
|
+
if (this.liveProfileReported || this.deps.initialLiveProfile === void 0) return;
|
|
23736
|
+
this.liveProfileReported = true;
|
|
23737
|
+
this.deps.session.sendControl(serializeServerMessage({
|
|
23738
|
+
t: "liveProfile",
|
|
23739
|
+
profile: this.deps.initialLiveProfile
|
|
23740
|
+
}));
|
|
23741
|
+
}
|
|
23707
23742
|
dispose() {
|
|
23708
23743
|
this.feeder?.dispose();
|
|
23709
23744
|
this.feeder = null;
|
|
@@ -31360,8 +31395,10 @@ var ADAPTIVE_CONFIG = {
|
|
|
31360
31395
|
analyzeWindowMs: 15e3,
|
|
31361
31396
|
minFailPercent: .05,
|
|
31362
31397
|
waitAfterResetMs: 8e3,
|
|
31363
|
-
upgradeHealthyMs:
|
|
31364
|
-
|
|
31398
|
+
upgradeHealthyMs: 4e3,
|
|
31399
|
+
upgradeHealthyGrowthFactor: 3,
|
|
31400
|
+
maxProgressiveUpgradeHealthyMs: 3e4,
|
|
31401
|
+
minTimeBetweenChangesMs: 4e3,
|
|
31365
31402
|
maxUpgradeBackoffMs: 9e5
|
|
31366
31403
|
};
|
|
31367
31404
|
/**
|
|
@@ -31529,8 +31566,7 @@ function scoreBroker(tier, broker, hints) {
|
|
|
31529
31566
|
const fallback = tier ? LABEL_DEFAULTS[tier] : void 0;
|
|
31530
31567
|
const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : fallback?.bitrateKbps ?? 2e3;
|
|
31531
31568
|
const srcPixels = fallback?.pixels ?? 1920 * 1080;
|
|
31532
|
-
const
|
|
31533
|
-
const targetPixels = (hints.viewportWidth ?? 1920) * dpr * ((hints.viewportHeight ?? 1080) * dpr);
|
|
31569
|
+
const targetPixels = (hints.viewportWidth ?? 1920) * (hints.viewportHeight ?? 1080);
|
|
31534
31570
|
const pixelDistance = Math.abs(srcPixels - targetPixels) / 1e6;
|
|
31535
31571
|
let bandwidthPenalty = 0;
|
|
31536
31572
|
if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {
|
|
@@ -32275,6 +32311,7 @@ var BrokerWebrtcServer = class {
|
|
|
32275
32311
|
}
|
|
32276
32312
|
});
|
|
32277
32313
|
const scrubCache = new ScrubFragmentCache();
|
|
32314
|
+
const initialLiveProfile = this.profileTierResolver?.(brokerId) ?? null;
|
|
32278
32315
|
const readGopAccessor = this.readRecordedGopBytes;
|
|
32279
32316
|
const readGop = readGopAccessor ? async (args) => {
|
|
32280
32317
|
const res = await readGopAccessor({
|
|
@@ -32294,6 +32331,7 @@ var BrokerWebrtcServer = class {
|
|
|
32294
32331
|
timeline = new TimelineSession({
|
|
32295
32332
|
session,
|
|
32296
32333
|
deviceId,
|
|
32334
|
+
...initialLiveProfile !== null ? { initialLiveProfile } : {},
|
|
32297
32335
|
warm: readGop ? (profile, epochMs) => {
|
|
32298
32336
|
warmFragment({
|
|
32299
32337
|
deviceId,
|
|
@@ -32428,7 +32466,7 @@ var BrokerWebrtcServer = class {
|
|
|
32428
32466
|
};
|
|
32429
32467
|
if (streamId.endsWith("/adaptive")) {
|
|
32430
32468
|
const initialTier = this.profileTierResolver?.(brokerId) ?? "high";
|
|
32431
|
-
this.startAdaptive(entry, deviceId, initialTier, sessionId, intent?.failures);
|
|
32469
|
+
this.startAdaptive(entry, deviceId, initialTier, sessionId, intent?.failures, intent?.upgradeCount);
|
|
32432
32470
|
}
|
|
32433
32471
|
return {
|
|
32434
32472
|
sessionId,
|
|
@@ -32522,17 +32560,18 @@ var BrokerWebrtcServer = class {
|
|
|
32522
32560
|
* server.
|
|
32523
32561
|
*
|
|
32524
32562
|
* The client-loss % is turned into a monotonic {@link LossSample} LOCALLY:
|
|
32525
|
-
*
|
|
32526
|
-
*
|
|
32527
|
-
*
|
|
32563
|
+
* per-controller cumulative sequence/loss counters advance for every
|
|
32564
|
+
* reported interval (including 0% loss), so a perfectly healthy LAN is real
|
|
32565
|
+
* evidence rather than silence, exactly like RTCP's cumulative fields.
|
|
32528
32566
|
* Both paths feed the same controller; the tracker naturally honours
|
|
32529
32567
|
* whichever produces the higher loss ratio across its window. The async
|
|
32530
32568
|
* poll is guarded against overlapping ticks via `tickInFlight`.
|
|
32531
32569
|
*/
|
|
32532
|
-
startAdaptive(entry, deviceId, initialTier, sessionId, priorFailures) {
|
|
32570
|
+
startAdaptive(entry, deviceId, initialTier, sessionId, priorFailures, priorUpgradeCount) {
|
|
32533
32571
|
const controller = new AdaptiveController({
|
|
32534
32572
|
initialTier,
|
|
32535
32573
|
priorFailures,
|
|
32574
|
+
priorUpgradeCount,
|
|
32536
32575
|
config: ADAPTIVE_CONFIG,
|
|
32537
32576
|
onSwitchTier: (tier) => {
|
|
32538
32577
|
this.switchSessionTier(entry, deviceId, tier, sessionId);
|
|
@@ -32544,7 +32583,8 @@ var BrokerWebrtcServer = class {
|
|
|
32544
32583
|
});
|
|
32545
32584
|
entry.session.onReceiverReport((s) => controller.onLoss(s, Date.now()));
|
|
32546
32585
|
entry.adaptiveController = controller;
|
|
32547
|
-
let
|
|
32586
|
+
let clientHighestSequence = 0;
|
|
32587
|
+
let clientPacketsLost = 0;
|
|
32548
32588
|
let tickInFlight = false;
|
|
32549
32589
|
const CLIENT_LOSS_SPAN = 1e3;
|
|
32550
32590
|
entry.adaptiveTimer = setInterval(() => {
|
|
@@ -32558,11 +32598,12 @@ var BrokerWebrtcServer = class {
|
|
|
32558
32598
|
}
|
|
32559
32599
|
const pct = this.clientLossPct ? await this.clientLossPct(deviceId) : null;
|
|
32560
32600
|
const now = Date.now();
|
|
32561
|
-
if (pct !== null && pct !== void 0
|
|
32562
|
-
|
|
32601
|
+
if (pct !== null && pct !== void 0) {
|
|
32602
|
+
clientHighestSequence += CLIENT_LOSS_SPAN;
|
|
32603
|
+
clientPacketsLost += Math.round(Math.max(0, Math.min(100, pct)) / 100 * CLIENT_LOSS_SPAN);
|
|
32563
32604
|
controller.onLoss({
|
|
32564
|
-
packetsLost:
|
|
32565
|
-
highestSequence:
|
|
32605
|
+
packetsLost: clientPacketsLost,
|
|
32606
|
+
highestSequence: clientHighestSequence
|
|
32566
32607
|
}, now);
|
|
32567
32608
|
}
|
|
32568
32609
|
controller.tick(now);
|
|
@@ -32588,10 +32629,12 @@ var BrokerWebrtcServer = class {
|
|
|
32588
32629
|
switchSessionTier(entry, deviceId, tier, sessionId, forceTranscodeDown = false) {
|
|
32589
32630
|
const prev = this.adaptiveIntent.get(sessionId);
|
|
32590
32631
|
const failures = new Map(entry.adaptiveController?.failureCounts ?? prev?.failures);
|
|
32632
|
+
const upgradeCount = entry.adaptiveController?.successfulUpgradeCount ?? prev?.upgradeCount ?? 0;
|
|
32591
32633
|
this.adaptiveIntent.set(sessionId, {
|
|
32592
32634
|
prefersTier: tier,
|
|
32593
32635
|
forceTranscodeDown: forceTranscodeDown || (prev?.forceTranscodeDown ?? false),
|
|
32594
|
-
failures
|
|
32636
|
+
failures,
|
|
32637
|
+
upgradeCount
|
|
32595
32638
|
});
|
|
32596
32639
|
const epoch = (entry.session.pendingRenegotiation?.epoch ?? 0) + 1;
|
|
32597
32640
|
entry.session.pendingRenegotiation = {
|
|
@@ -32606,7 +32649,8 @@ var BrokerWebrtcServer = class {
|
|
|
32606
32649
|
tier,
|
|
32607
32650
|
forceTranscodeDown,
|
|
32608
32651
|
epoch,
|
|
32609
|
-
failures: Object.fromEntries(failures)
|
|
32652
|
+
failures: Object.fromEntries(failures),
|
|
32653
|
+
upgradeCount
|
|
32610
32654
|
}
|
|
32611
32655
|
});
|
|
32612
32656
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-
|
|
1
|
+
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-Dcfe-5Ig.js";var o=n(t()),s=n(e(),1),c=Math.PI/180;function l(){return typeof window<`u`&&({}.toString.call(window)===`[object Window]`||{}.toString.call(window)===`[object global]`)}var u=typeof global<`u`?global:typeof window<`u`?window:typeof WorkerGlobalScope<`u`?self:{},d={_global:u,version:`10.3.0`,isBrowser:l(),isUnminified:/param/.test(function(e){}.toString()),dblClickWindow:400,getAngle(e){return d.angleDeg?e*c:e},enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_renderBackend:`web`,legacyTextRendering:!1,pixelRatio:typeof window<`u`&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging(){return d.DD.isDragging},isTransforming(){return d.Transformer?.isTransforming()??!1},isDragReady(){return!!d.DD.node},releaseCanvasOnDestroy:!0,document:u.document,_injectGlobal(e){u.Konva!==void 0&&console.error(`Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.`),u.Konva=e}},f=e=>{d[e.prototype.getClassName()]=e};d._injectGlobal(d);var p=`Konva.js unsupported environment.
|
|
2
2
|
|
|
3
3
|
Looks like you are trying to use Konva.js in Node.js environment. because "document" object is undefined.
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-Dcfe-5Ig.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-BWAtF7b-.js";var d=i(r(),1),f=i(n(),1),p=o(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),m=110,h=`motion-zones`,g=0,_=[1,2,3],v=1,y=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,b=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:n}){let r=t(l().trpcClient,n),[i,o]=(0,d.useState)(null),[w,D]=(0,d.useState)(!1),[O,k]=(0,d.useState)(null),[A,j]=(0,d.useState)(null),[M,N]=(0,d.useState)(v),[P,F]=(0,d.useState)(!1),[I,L]=(0,d.useState)(!1),R=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(!r)return;let e=!1;return R.current=!1,o(null),D(!1),k(null),j(null),N(v),L(!1),(async()=>{try{let t=await r.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(o(t),R.current)return;let n=await r.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);R.current=!0;let i=n.regions.find(e=>e.shape.kind===`grid`),a=x(i?i.shape.cells:[],t.grid.width,t.grid.height);j(a),k(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;c(t)?D(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[r]);let z=i?i.grid.width:0,B=i?i.grid.height:0,V=C(z,M),H=C(B,M),U=(0,d.useMemo)(()=>O&&i?E(O,z,B,M):null,[O,i,z,B,M]),W=(0,d.useMemo)(()=>U!==null&&A!==null&&!S(U,A),[U,A]),G=(0,d.useMemo)(()=>U?U.reduce((e,t)=>t?e+1:e,0):0,[U]),K=z*B,q=V*H,J=(0,d.useCallback)(e=>{q>0&&k(Array.from({length:q},()=>e))},[q]),Y=(0,d.useCallback)(()=>{k(e=>e&&e.map(e=>!e))},[]),X=(0,d.useCallback)(()=>{A&&i&&k(T(A,z,B,M))},[A,i,z,B,M]),Z=(0,d.useCallback)(e=>{e===M||!i||k(t=>{if(!t)return N(e),t;let n=T(E(t,z,B,M),z,B,e);return N(e),n})},[M,i,z,B]),Q=(0,d.useMemo)(()=>i&&O?[{id:g,shape:{kind:`grid`,gridWidth:V,gridHeight:H,cells:[...O]}}]:[],[i,O,V,H]),$=(0,d.useCallback)((e,t)=>{t.kind===`grid`&&k(t.cells)},[]),ee=(0,d.useCallback)(async()=>{if(!(!r||!O||!i)){F(!0);try{let e=E(O,i.grid.width,i.grid.height,M),t={kind:`grid`,gridWidth:i.grid.width,gridHeight:i.grid.height,cells:e};await r.motionZones?.setZone({patch:{regions:[{id:g,enabled:!0,shape:t}]}});let n=await r.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],i.grid.width,i.grid.height);j(t),k(T(t,i.grid.width,i.grid.height,M))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{F(!1)}}},[r,O,i,M]);a((0,d.useMemo)(()=>I&&!w&&i&&O?{id:h,order:m,node:(0,f.jsx)(u,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:V,height:H},selectedId:g,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[I,w,i,O,Q,$,V,H]));let te=!w&&i!==null&&O!==null;return r?(0,f.jsx)(e,{title:`Motion Zones`,icon:(0,f.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,f.jsx)(`div`,{className:`flex flex-col gap-3`,children:w?(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`This camera doesn't expose an on-board motion zones grid.`}):te?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`p`,{className:`${s} leading-relaxed`,children:[`Toggle `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Edit grid`}),` to paint the region directly on the live frame, then `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push the mask to the camera. Pick a bigger`,` `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Cell size`}),` for quicker, broad-stroke painting — it's resampled to the camera's native `,i.grid.width,`×`,i.grid.height,` grid on save (×1 is the finest).`]}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>L(e=>!e),disabled:P,"aria-pressed":I,className:I?b:y,children:I?`Done editing`:`Edit grid`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!0),disabled:P,className:y,children:`All on`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!1),disabled:P,className:y,children:`All off`}),(0,f.jsx)(`button`,{type:`button`,onClick:Y,disabled:P,className:y,children:`Invert`}),(0,f.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":`Cell size`,children:[(0,f.jsx)(`span`,{className:`${s} mr-0.5`,children:`Cell size`}),_.map(e=>(0,f.jsxs)(`button`,{type:`button`,onClick:()=>Z(e),disabled:P,"aria-pressed":M===e,title:e===1?`Camera grid ${z}×${B} (finest)`:`${C(z,e)}×${C(B,e)} painting grid · cells ×${e} bigger`,className:M===e?b:y,children:[`×`,e]},e))]}),(0,f.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[G,` / `,K,` cells · `,i.grid.width,`×`,i.grid.height,M===1?``:` · paint ${V}×${H}`]}),(0,f.jsx)(`span`,{className:`flex-1`}),(0,f.jsx)(`button`,{type:`button`,onClick:X,disabled:P||!W,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void ee(),disabled:P||!W,className:b,children:P?`Saving…`:`Save`})]})]}):(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`Loading the camera's grid…`})})}):null}export{D as MotionZonesSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-Dcfe-5Ig.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-BWAtF7b-.js";var f=i(r(),1),p=i(n(),1),m=c(`hexagon`,[[`path`,{d:`M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z`,key:`yt0hxn`}]]),h=120,g=`privacy-mask`,_=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,v=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function y(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function b(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function x(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(JSON.stringify(e[n])!==JSON.stringify(t[n]))return!1;return!0}function S({deviceId:n}){let r=t(ee().trpcClient,n),[i,c]=(0,f.useState)(null),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(null),[P,F]=(0,f.useState)(null),I=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!r)return;let e=!1;return I.current=!1,c(null),C(!1),T(null),D(null),j(!1),N(null),F(null),(async()=>{try{let t=await r.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(c(t),I.current)return;let n=await r.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);I.current=!0;let i={enabled:n.enabled,regions:n.regions};D(i),T(i)}catch(t){if(e)return;u(t)?C(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[r]);let L=(0,f.useMemo)(()=>w!==null&&E!==null&&(w.enabled!==E.enabled||!x(w.regions,E.regions)),[w,E]),R=w?w.regions.length:0,z=i?i.maxRegions:0,B=(0,f.useRef)(0);(0,f.useEffect)(()=>{B.current=z},[z]);let V=z>0&&R>=z,H=(0,f.useCallback)(()=>{T(e=>e&&{...e,enabled:!e.enabled})},[]),U=(0,f.useCallback)(e=>{N(typeof e==`number`?e:null)},[]),W=(0,f.useMemo)(()=>w?w.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:`Zone ${String(e.id)}`})):[],[w]),G=(0,f.useCallback)((e,t)=>{let n=y(t);n&&T(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),K=(0,f.useCallback)(e=>{let t=y(e);t&&(F(null),T(e=>{if(!e)return e;let n=B.current;if(n>0&&e.regions.length>=n)return e;let r=b(e.regions),i={id:r,enabled:!0,shape:t};return N(r),{...e,regions:[...e.regions,i]}}))},[]),q=(0,f.useCallback)(e=>{j(!0),N(null),F(e)},[]),J=(0,f.useCallback)(e=>{j(!0),F(null),N(e)},[]),Y=(0,f.useCallback)(e=>{N(t=>t===e?null:t),T(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),X=(0,f.useCallback)(()=>{F(null),N(null),E&&T({enabled:E.enabled,regions:E.regions})},[E]),Z=(0,f.useCallback)(async()=>{if(!(!r||!w)){k(!0);try{await r.privacyMask?.setMask({patch:{enabled:w.enabled,regions:[...w.regions]}});let e=await r.privacyMask?.getStatus({});if(e){let t={enabled:e.enabled,regions:e.regions};D(t),T(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{k(!1)}}},[r,w]),ne=(0,f.useCallback)(()=>{j(e=>(e&&(F(null),N(null)),!e))},[]),Q=i?.supportedShapes??[];o((0,f.useMemo)(()=>A&&!S&&i&&w?{id:g,order:h,node:(0,p.jsx)(te,{transparent:!0,items:W,supportedShapes:Q,polygonVertices:i.polygonVertices,selectedId:M,onSelect:U,onShapeChange:G,onDrawComplete:K,drawingKind:P})}:null,[A,S,i,w,W,Q,M,U,G,K,P]));let re=i?.supportedShapes.includes(`rect`)??!1,ie=i?.supportedShapes.includes(`polygon`)??!1,$=i!==null&&(i.maxRegions<=0||i.supportedShapes.length===0),ae=!S&&!$&&i!==null&&w!==null;return r?(0,p.jsx)(e,{title:`Privacy Mask`,icon:(0,p.jsx)(d,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,p.jsx)(`div`,{className:`flex flex-col gap-3`,children:S||$?(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`This camera doesn't support an on-board privacy mask.`}):ae?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`p`,{className:`${l} leading-relaxed`,children:[`Toggle `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Edit mask`}),` to draw blanked-out zones on the live frame, then `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push them to the camera. Drag a rectangle to move, its corner to resize; drag polygon vertices, click an edge midpoint to add one, or right-click a vertex to remove it.`]}),(0,p.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,p.jsx)(`button`,{type:`button`,onClick:ne,disabled:O,"aria-pressed":A,className:A?v:_,children:A?`Done editing`:`Edit mask`}),(0,p.jsx)(`button`,{type:`button`,onClick:H,disabled:O,"aria-pressed":w.enabled,className:w.enabled?v:_,children:w.enabled?`Mask on`:`Mask off`}),re&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`rect`),disabled:O||V,"aria-pressed":P===`rect`,className:P===`rect`?v:_,title:V?`Maximum zones reached`:`Add a rectangle zone`,children:`+ Rect`}),ie&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`polygon`),disabled:O||V,"aria-pressed":P===`polygon`,className:P===`polygon`?v:_,title:V?`Maximum zones reached`:`Add a polygon zone`,children:`+ Polygon`}),(0,p.jsxs)(`span`,{className:`${l} ml-1 tabular-nums`,children:[R,` / `,z,` zones`]}),(0,p.jsx)(`span`,{className:`flex-1`}),(0,p.jsx)(`button`,{type:`button`,onClick:X,disabled:O||!L,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>void Z(),disabled:O||!L,className:v,children:O?`Saving…`:`Save`})]}),R>0?(0,p.jsx)(`div`,{className:`flex flex-col gap-1`,children:w.regions.map(e=>{let t=M===e.id,n=e.shape.kind===`polygon`?m:s;return(0,p.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border px-2 py-1 transition-colors ${t?`border-primary/50 bg-primary/10`:`border-border bg-surface`}`,children:[(0,p.jsxs)(`button`,{type:`button`,onClick:()=>J(e.id),disabled:O,className:`flex flex-1 items-center gap-2 text-left text-[11px] font-medium text-foreground-subtle hover:text-foreground disabled:opacity-40 transition-colors`,children:[(0,p.jsx)(n,{className:`h-3.5 w-3.5 shrink-0`}),(0,p.jsxs)(`span`,{children:[`Zone `,e.id]}),(0,p.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>Y(e.id),disabled:O,"aria-label":`Delete zone ${String(e.id)}`,title:`Delete zone`,className:`inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-surface text-foreground-subtle hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-400 disabled:opacity-40 transition-colors`,children:(0,p.jsx)(a,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`Loading the camera's privacy mask…`})})}):null}export{S as PrivacyMaskSettings};
|