@camstack/addon-pipeline 1.2.54 → 1.2.57
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/recorder/index.js +38 -8
- package/dist/recorder/index.mjs +38 -8
- package/dist/stream-broker/index.js +111 -22
- package/dist/stream-broker/index.mjs +111 -22
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DY1AX2y_.js → MaskShapeCanvas-DI4BY7W2-Bxm34WFr.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-D46Ft0YB.js → MotionZonesSettings-NcxxQN8r-S8uFj2nP.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BLD9p-W3.js → PrivacyMaskSettings-APgPLF7p-nhHVCSoL.js} +1 -1
- package/embed-dist/assets/{index-CWxqVyhE.js → index-hICPL_hd.js} +4 -4
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -22089,9 +22089,9 @@ var SCRUB_ADJACENT_TOLERANCE_MS = 50;
|
|
|
22089
22089
|
* feeder serves, arriving AFTER the landing. 16 at ~60 ms is ~0.6 MB, still
|
|
22090
22090
|
* reads as travel, and leaves headroom for the landing GOP behind it.
|
|
22091
22091
|
*/
|
|
22092
|
-
var SEEK_ANIMATION_BUDGET_MS =
|
|
22092
|
+
var SEEK_ANIMATION_BUDGET_MS = 250;
|
|
22093
22093
|
var SEEK_ANIMATION_TICK_MS = 60;
|
|
22094
|
-
var SEEK_ANIMATION_MAX_STILLS =
|
|
22094
|
+
var SEEK_ANIMATION_MAX_STILLS = 4;
|
|
22095
22095
|
/**
|
|
22096
22096
|
* Below this distance there is at most ONE intermediate keyframe (the camera
|
|
22097
22097
|
* GOP is fixed at 4 s) — the landing burst already paints the target, so
|
|
@@ -22206,8 +22206,9 @@ var RecordedFeeder = class {
|
|
|
22206
22206
|
currentSegment = null;
|
|
22207
22207
|
/** Index (into `currentSegment.aus`) of the last video AU fed; -1 = none. */
|
|
22208
22208
|
lastFedVideoIdx = -1;
|
|
22209
|
-
/** Serializes frame steps
|
|
22209
|
+
/** Serializes frame steps without dropping rapid taps. */
|
|
22210
22210
|
stepInFlight = false;
|
|
22211
|
+
pendingStepDirs = [];
|
|
22211
22212
|
/**
|
|
22212
22213
|
* Target epoch of the seek currently in flight (null when none). A rate
|
|
22213
22214
|
* change re-enters via seek(); while a seek is still resolving, `cursorMs`
|
|
@@ -22323,6 +22324,11 @@ var RecordedFeeder = class {
|
|
|
22323
22324
|
* seek bumps the token and silently abandons this one.
|
|
22324
22325
|
*/
|
|
22325
22326
|
async seek(epochMs, opts) {
|
|
22327
|
+
const seekStartedAt = performance.now();
|
|
22328
|
+
let locateMs = 0;
|
|
22329
|
+
let travelMs = 0;
|
|
22330
|
+
let loadMs = 0;
|
|
22331
|
+
let firstFrameMs = 0;
|
|
22326
22332
|
const token = ++this.seekToken;
|
|
22327
22333
|
const wasScrubbing = this.scrubbing;
|
|
22328
22334
|
if (this.scrubbing) {
|
|
@@ -22349,11 +22355,13 @@ var RecordedFeeder = class {
|
|
|
22349
22355
|
this.clock.markDiscontinuity(SEEK_DISCONTINUITY_MS);
|
|
22350
22356
|
this.audioClock.markDiscontinuity(SEEK_DISCONTINUITY_MS);
|
|
22351
22357
|
}
|
|
22358
|
+
const locateStartedAt = performance.now();
|
|
22352
22359
|
let loc = await this.withTimeout(this.deps.locate({
|
|
22353
22360
|
deviceId: this.deps.deviceId,
|
|
22354
22361
|
profile: this.deps.profile,
|
|
22355
22362
|
epochMs
|
|
22356
22363
|
}));
|
|
22364
|
+
locateMs += performance.now() - locateStartedAt;
|
|
22357
22365
|
if (this.disposed || token !== this.seekToken) return;
|
|
22358
22366
|
let landMs = epochMs;
|
|
22359
22367
|
if (loc.kind === "gap") {
|
|
@@ -22371,11 +22379,13 @@ var RecordedFeeder = class {
|
|
|
22371
22379
|
this.deps.onState("gap");
|
|
22372
22380
|
return;
|
|
22373
22381
|
}
|
|
22382
|
+
const snapLocateStartedAt = performance.now();
|
|
22374
22383
|
const snapped = await this.withTimeout(this.deps.locate({
|
|
22375
22384
|
deviceId: this.deps.deviceId,
|
|
22376
22385
|
profile: this.deps.profile,
|
|
22377
22386
|
epochMs: decision.snapMs
|
|
22378
22387
|
}));
|
|
22388
|
+
locateMs += performance.now() - snapLocateStartedAt;
|
|
22379
22389
|
if (this.disposed || token !== this.seekToken) return;
|
|
22380
22390
|
if (snapped.kind !== "segment") {
|
|
22381
22391
|
this.deps.onLog?.("recorded:seek-no-footage", {
|
|
@@ -22401,23 +22411,79 @@ var RecordedFeeder = class {
|
|
|
22401
22411
|
}
|
|
22402
22412
|
const travelFromMs = opts?.travelFromMs ?? (this.clockHasAdvanced ? this.cursorMs : null);
|
|
22403
22413
|
if (!wasScrubbing && travelFromMs !== null) {
|
|
22414
|
+
const travelStartedAt = performance.now();
|
|
22404
22415
|
await this.animateSeekTravel(travelFromMs, landMs, token);
|
|
22416
|
+
travelMs = performance.now() - travelStartedAt;
|
|
22405
22417
|
if (this.disposed || token !== this.seekToken) return;
|
|
22406
22418
|
}
|
|
22407
22419
|
this.cursorMs = loc.startMs;
|
|
22408
|
-
const
|
|
22420
|
+
const loadStartedAt = performance.now();
|
|
22421
|
+
let prefixFed = false;
|
|
22422
|
+
let gopFirst = false;
|
|
22423
|
+
const fullLoad = this.loadAus(loc.startMs, this.rate !== 0, this.deps.readGop ? void 0 : (video) => {
|
|
22424
|
+
if (this.disposed || token !== this.seekToken) return;
|
|
22425
|
+
this.feedPrefix(loc, video, landMs);
|
|
22426
|
+
prefixFed = true;
|
|
22427
|
+
firstFrameMs = performance.now() - seekStartedAt;
|
|
22428
|
+
});
|
|
22429
|
+
fullLoad.catch(() => {});
|
|
22430
|
+
if (this.deps.readGop) {
|
|
22431
|
+
const cached = this.scrubCache.covering(this.deps.profile, landMs);
|
|
22432
|
+
let fragment;
|
|
22433
|
+
if (cached) fragment = cached;
|
|
22434
|
+
else {
|
|
22435
|
+
const fragmentLoad = await this.loadScrubFragment(loc, landMs);
|
|
22436
|
+
if (fragmentLoad === null) return;
|
|
22437
|
+
fragment = fragmentLoad.seg;
|
|
22438
|
+
this.scrubCache.put(this.deps.profile, fragment);
|
|
22439
|
+
}
|
|
22440
|
+
if (this.disposed || token !== this.seekToken) return;
|
|
22441
|
+
const fragmentLoc = {
|
|
22442
|
+
kind: "segment",
|
|
22443
|
+
startMs: fragment.startMs,
|
|
22444
|
+
durMs: fragment.durMs,
|
|
22445
|
+
bytes: loc.bytes
|
|
22446
|
+
};
|
|
22447
|
+
this.feedPrefix(fragmentLoc, fragment.aus, landMs);
|
|
22448
|
+
prefixFed = true;
|
|
22449
|
+
gopFirst = true;
|
|
22450
|
+
firstFrameMs = performance.now() - seekStartedAt;
|
|
22451
|
+
}
|
|
22452
|
+
const loaded = await fullLoad;
|
|
22453
|
+
loadMs = performance.now() - loadStartedAt;
|
|
22409
22454
|
if (this.disposed || token !== this.seekToken || loaded === null) return;
|
|
22410
|
-
this.feedPrefix(loc, loaded.video, landMs);
|
|
22455
|
+
if (!prefixFed) this.feedPrefix(loc, loaded.video, landMs);
|
|
22456
|
+
if (gopFirst) {
|
|
22457
|
+
this.currentSegment = {
|
|
22458
|
+
startMs: loc.startMs,
|
|
22459
|
+
durMs: loc.durMs,
|
|
22460
|
+
aus: loaded.video
|
|
22461
|
+
};
|
|
22462
|
+
const targetPtsMs = (loaded.video[0]?.ptsMs ?? 0) + Math.max(0, this.cursorMs - loc.startMs);
|
|
22463
|
+
this.lastFedVideoIdx = -1;
|
|
22464
|
+
for (let i = 0; i < loaded.video.length; i++) {
|
|
22465
|
+
const au = loaded.video[i];
|
|
22466
|
+
if (!au || au.ptsMs > targetPtsMs) break;
|
|
22467
|
+
this.lastFedVideoIdx = i;
|
|
22468
|
+
}
|
|
22469
|
+
if (this.clockHasAdvanced) {
|
|
22470
|
+
this.clock.markSeek();
|
|
22471
|
+
this.audioClock.markSeek();
|
|
22472
|
+
}
|
|
22473
|
+
}
|
|
22411
22474
|
if (this.rate === 0) {
|
|
22412
22475
|
this.deps.onState("paused");
|
|
22413
22476
|
this.deps.onPosition(this.cursorMs);
|
|
22414
22477
|
this.startPositionReports();
|
|
22478
|
+
this.logSeekReady(epochMs, seekStartedAt, locateMs, travelMs, loadMs, firstFrameMs, "paused");
|
|
22415
22479
|
return;
|
|
22416
22480
|
}
|
|
22417
22481
|
this.deps.onState("playing");
|
|
22418
22482
|
this.deps.onPosition(this.cursorMs);
|
|
22419
22483
|
this.startPositionReports();
|
|
22420
|
-
|
|
22484
|
+
this.logSeekReady(epochMs, seekStartedAt, locateMs, travelMs, loadMs, firstFrameMs, "playing");
|
|
22485
|
+
const firstFullAu = loaded.video[0];
|
|
22486
|
+
const resumeAfterPtsMs = gopFirst ? firstFullAu ? firstFullAu.ptsMs + Math.max(0, this.cursorMs - loc.startMs) : null : this.lastFedVideoIdx >= 0 ? loaded.video[this.lastFedVideoIdx]?.ptsMs ?? null : null;
|
|
22421
22487
|
this.startAdvance({
|
|
22422
22488
|
prepared: {
|
|
22423
22489
|
startMs: loc.startMs,
|
|
@@ -22425,7 +22491,7 @@ var RecordedFeeder = class {
|
|
|
22425
22491
|
aus: loaded.video,
|
|
22426
22492
|
audio: loaded.audio
|
|
22427
22493
|
},
|
|
22428
|
-
resumeAfterPtsMs
|
|
22494
|
+
resumeAfterPtsMs
|
|
22429
22495
|
});
|
|
22430
22496
|
} catch (err) {
|
|
22431
22497
|
if (!this.disposed && token === this.seekToken) {
|
|
@@ -22444,9 +22510,23 @@ var RecordedFeeder = class {
|
|
|
22444
22510
|
this.deps.onState("error");
|
|
22445
22511
|
}
|
|
22446
22512
|
} finally {
|
|
22447
|
-
if (token === this.seekToken)
|
|
22513
|
+
if (token === this.seekToken) {
|
|
22514
|
+
this.inFlightSeekEpochMs = null;
|
|
22515
|
+
this.drainStepQueue();
|
|
22516
|
+
}
|
|
22448
22517
|
}
|
|
22449
22518
|
}
|
|
22519
|
+
logSeekReady(epochMs, startedAt, locateMs, travelMs, loadMs, firstFrameMs, state) {
|
|
22520
|
+
this.deps.onLog?.("recorded:seek-ready", {
|
|
22521
|
+
epochMs,
|
|
22522
|
+
state,
|
|
22523
|
+
totalMs: Math.round(performance.now() - startedAt),
|
|
22524
|
+
locateMs: Math.round(locateMs),
|
|
22525
|
+
travelMs: Math.round(travelMs),
|
|
22526
|
+
loadMs: Math.round(loadMs),
|
|
22527
|
+
firstFrameMs: Math.round(firstFrameMs)
|
|
22528
|
+
});
|
|
22529
|
+
}
|
|
22450
22530
|
/**
|
|
22451
22531
|
* Set playback rate. 0 pauses in place (the pacing loop aborts on its next
|
|
22452
22532
|
* guard check); any other value is clamped to [0.25, 4].
|
|
@@ -22464,10 +22544,12 @@ var RecordedFeeder = class {
|
|
|
22464
22544
|
if (next === this.rate) return;
|
|
22465
22545
|
this.rate = next;
|
|
22466
22546
|
if (next === 0) {
|
|
22547
|
+
this.advanceToken++;
|
|
22467
22548
|
this.stopPositionReports();
|
|
22468
22549
|
this.deps.onState("paused");
|
|
22469
22550
|
return;
|
|
22470
22551
|
}
|
|
22552
|
+
this.pendingStepDirs.length = 0;
|
|
22471
22553
|
this.clock.setRate(next);
|
|
22472
22554
|
this.seek(this.inFlightSeekEpochMs ?? this.cursorMs);
|
|
22473
22555
|
}
|
|
@@ -23073,10 +23155,19 @@ var RecordedFeeder = class {
|
|
|
23073
23155
|
* step is still in flight.
|
|
23074
23156
|
*/
|
|
23075
23157
|
stepFrame(dir) {
|
|
23076
|
-
if (this.disposed || this.
|
|
23158
|
+
if (this.disposed || this.scrubbing) return;
|
|
23159
|
+
if (this.rate !== 0) this.setRate(0);
|
|
23160
|
+
if (this.pendingStepDirs.length < 16) this.pendingStepDirs.push(dir);
|
|
23161
|
+
this.drainStepQueue();
|
|
23162
|
+
}
|
|
23163
|
+
drainStepQueue() {
|
|
23164
|
+
if (this.disposed || this.scrubbing || this.rate !== 0 || this.stepInFlight || this.inFlightSeekEpochMs !== null) return;
|
|
23165
|
+
const dir = this.pendingStepDirs.shift();
|
|
23166
|
+
if (dir === void 0) return;
|
|
23077
23167
|
this.stepInFlight = true;
|
|
23078
23168
|
this.doStep(dir).catch(() => {}).finally(() => {
|
|
23079
23169
|
this.stepInFlight = false;
|
|
23170
|
+
this.drainStepQueue();
|
|
23080
23171
|
});
|
|
23081
23172
|
}
|
|
23082
23173
|
async doStep(dir) {
|
|
@@ -23401,7 +23492,7 @@ var RecordedFeeder = class {
|
|
|
23401
23492
|
* (latest-wins). Returns null on abort. Audio demux failures yield [] (audio
|
|
23402
23493
|
* never blocks video). `wantAudio: false` skips the audio transcode entirely
|
|
23403
23494
|
* (paused scrubs would throw it away). */
|
|
23404
|
-
async loadAus(startMs, wantAudio = true) {
|
|
23495
|
+
async loadAus(startMs, wantAudio = true, onVideoReady) {
|
|
23405
23496
|
const token = this.seekToken;
|
|
23406
23497
|
const bytes = await this.withTimeout(this.deps.readBytes({
|
|
23407
23498
|
deviceId: this.deps.deviceId,
|
|
@@ -23409,17 +23500,13 @@ var RecordedFeeder = class {
|
|
|
23409
23500
|
startMs
|
|
23410
23501
|
}));
|
|
23411
23502
|
if (this.disposed || token !== this.seekToken) return null;
|
|
23412
|
-
const
|
|
23503
|
+
const audioPromise = wantAudio && this.deps.demuxAudio && this.resolveAudioPush() !== void 0 ? this.withTimeout(this.deps.demuxAudio(bytes)).catch(() => []) : Promise.resolve([]);
|
|
23504
|
+
const videoPromise = this.withTimeout(this.deps.demux(bytes)).then((video) => {
|
|
23505
|
+
if (!this.disposed && token === this.seekToken) onVideoReady?.(video);
|
|
23506
|
+
return video;
|
|
23507
|
+
});
|
|
23508
|
+
const [video, audio] = await Promise.all([videoPromise, audioPromise]);
|
|
23413
23509
|
if (this.disposed || token !== this.seekToken) return null;
|
|
23414
|
-
let audio = [];
|
|
23415
|
-
if (wantAudio && this.deps.demuxAudio && this.resolveAudioPush() !== void 0) {
|
|
23416
|
-
try {
|
|
23417
|
-
audio = await this.withTimeout(this.deps.demuxAudio(bytes));
|
|
23418
|
-
} catch {
|
|
23419
|
-
audio = [];
|
|
23420
|
-
}
|
|
23421
|
-
if (this.disposed || token !== this.seekToken) return null;
|
|
23422
|
-
}
|
|
23423
23510
|
return {
|
|
23424
23511
|
video,
|
|
23425
23512
|
audio
|
|
@@ -23530,10 +23617,12 @@ var TimelineSession = class {
|
|
|
23530
23617
|
}
|
|
23531
23618
|
async handleControl(msg) {
|
|
23532
23619
|
switch (msg.t) {
|
|
23533
|
-
case "playRecorded":
|
|
23620
|
+
case "playRecorded": {
|
|
23621
|
+
const profileChanged = msg.profile !== this.profile;
|
|
23534
23622
|
this.profile = msg.profile;
|
|
23535
|
-
await this.enterRecorded().seek(msg.epoch, { travelFromMs: this.lastPositionMs });
|
|
23623
|
+
await (this.mode === "recorded" && this.feeder && !profileChanged ? this.feeder : this.enterRecorded()).seek(msg.epoch, { travelFromMs: this.lastPositionMs });
|
|
23536
23624
|
break;
|
|
23625
|
+
}
|
|
23537
23626
|
case "seek":
|
|
23538
23627
|
await (this.mode === "recorded" && this.feeder ? this.feeder : this.enterRecorded()).seek(msg.epoch, { travelFromMs: this.lastPositionMs });
|
|
23539
23628
|
break;
|
|
@@ -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-hICPL_hd.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-hICPL_hd.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-Bxm34WFr.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-hICPL_hd.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-Bxm34WFr.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};
|