@pose-tracker/pose-estimation-web 0.1.0 → 0.2.0
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/README.md +21 -4
- package/dist/index.cjs +536 -129
- package/dist/index.d.cts +100 -11
- package/dist/index.d.ts +100 -11
- package/dist/index.js +536 -130
- package/dist/pose-tracker.global.js +6 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sha256 } from 'js-sha256';
|
|
2
2
|
|
|
3
3
|
// src/version.ts
|
|
4
|
-
var SDK_VERSION = "0.
|
|
4
|
+
var SDK_VERSION = "0.2.0";
|
|
5
5
|
var SDK_NAME = "posetracker-web";
|
|
6
6
|
|
|
7
7
|
// src/api/configure.ts
|
|
@@ -459,6 +459,131 @@ function stopMediaStream(stream) {
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
// src/media/resolveMedia.ts
|
|
463
|
+
function isVideoElement(v) {
|
|
464
|
+
return typeof HTMLVideoElement !== "undefined" && v instanceof HTMLVideoElement;
|
|
465
|
+
}
|
|
466
|
+
function isImageElement(v) {
|
|
467
|
+
return typeof HTMLImageElement !== "undefined" && v instanceof HTMLImageElement;
|
|
468
|
+
}
|
|
469
|
+
function isImageBitmap(v) {
|
|
470
|
+
return typeof ImageBitmap !== "undefined" && v instanceof ImageBitmap;
|
|
471
|
+
}
|
|
472
|
+
function mediaSize(el) {
|
|
473
|
+
if (isImageBitmap(el)) {
|
|
474
|
+
return { width: el.width || 0, height: el.height || 0 };
|
|
475
|
+
}
|
|
476
|
+
if (isVideoElement(el)) {
|
|
477
|
+
return { width: el.videoWidth || 0, height: el.videoHeight || 0 };
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
width: el.naturalWidth || el.width || 0,
|
|
481
|
+
height: el.naturalHeight || el.height || 0
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function resolveObjectUrl(src) {
|
|
485
|
+
if (typeof src === "string") {
|
|
486
|
+
return { url: src, revoke: () => void 0 };
|
|
487
|
+
}
|
|
488
|
+
const url = URL.createObjectURL(src);
|
|
489
|
+
return {
|
|
490
|
+
url,
|
|
491
|
+
revoke: () => {
|
|
492
|
+
try {
|
|
493
|
+
URL.revokeObjectURL(url);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function waitForVideoReady(video, timeoutMs = 15e3) {
|
|
500
|
+
return new Promise((resolve, reject) => {
|
|
501
|
+
if (video.readyState >= 2 && video.videoWidth > 0) {
|
|
502
|
+
resolve();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const onReady = () => {
|
|
506
|
+
cleanup();
|
|
507
|
+
resolve();
|
|
508
|
+
};
|
|
509
|
+
const onError = () => {
|
|
510
|
+
cleanup();
|
|
511
|
+
reject(new Error("Failed to load video source"));
|
|
512
|
+
};
|
|
513
|
+
const timer = setTimeout(() => {
|
|
514
|
+
cleanup();
|
|
515
|
+
reject(new Error("Timed out waiting for video metadata"));
|
|
516
|
+
}, timeoutMs);
|
|
517
|
+
const cleanup = () => {
|
|
518
|
+
clearTimeout(timer);
|
|
519
|
+
video.removeEventListener("loadeddata", onReady);
|
|
520
|
+
video.removeEventListener("error", onError);
|
|
521
|
+
};
|
|
522
|
+
video.addEventListener("loadeddata", onReady);
|
|
523
|
+
video.addEventListener("error", onError);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function waitForImageReady(img, timeoutMs = 15e3) {
|
|
527
|
+
return new Promise((resolve, reject) => {
|
|
528
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
529
|
+
resolve();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const onReady = () => {
|
|
533
|
+
cleanup();
|
|
534
|
+
resolve();
|
|
535
|
+
};
|
|
536
|
+
const onError = () => {
|
|
537
|
+
cleanup();
|
|
538
|
+
reject(new Error("Failed to load image source"));
|
|
539
|
+
};
|
|
540
|
+
const timer = setTimeout(() => {
|
|
541
|
+
cleanup();
|
|
542
|
+
reject(new Error("Timed out waiting for image"));
|
|
543
|
+
}, timeoutMs);
|
|
544
|
+
const cleanup = () => {
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
img.removeEventListener("load", onReady);
|
|
547
|
+
img.removeEventListener("error", onError);
|
|
548
|
+
};
|
|
549
|
+
img.addEventListener("load", onReady);
|
|
550
|
+
img.addEventListener("error", onError);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
async function imageBitmapToObjectUrl(bitmap) {
|
|
554
|
+
const canvas = document.createElement("canvas");
|
|
555
|
+
canvas.width = bitmap.width;
|
|
556
|
+
canvas.height = bitmap.height;
|
|
557
|
+
const ctx = canvas.getContext("2d");
|
|
558
|
+
if (!ctx) throw new Error("2d context unavailable");
|
|
559
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
560
|
+
const blob = await new Promise((resolve, reject) => {
|
|
561
|
+
canvas.toBlob((b) => b ? resolve(b) : reject(new Error("toBlob failed")), "image/png");
|
|
562
|
+
});
|
|
563
|
+
return resolveObjectUrl(blob);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/types/source.ts
|
|
567
|
+
function normalizePoseSource(source, extras) {
|
|
568
|
+
if (source && typeof source === "object" && "type" in source) {
|
|
569
|
+
return source;
|
|
570
|
+
}
|
|
571
|
+
const kind = source === "video" || source === "image" || source === "camera" ? source : "camera";
|
|
572
|
+
if (kind === "camera") {
|
|
573
|
+
return { type: "camera", facingMode: extras?.facingMode };
|
|
574
|
+
}
|
|
575
|
+
const src = extras?.sourceFile ?? extras?.sourceUrl;
|
|
576
|
+
if (!src) {
|
|
577
|
+
throw new Error(
|
|
578
|
+
`PoseSource type "${kind}" requires sourceUrl or sourceFile (or pass a full PoseSource object)`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (kind === "video") {
|
|
582
|
+
return { type: "video", src };
|
|
583
|
+
}
|
|
584
|
+
return { type: "image", src };
|
|
585
|
+
}
|
|
586
|
+
|
|
462
587
|
// src/exercises/aliases.ts
|
|
463
588
|
var EXERCISE_ALIASES = {
|
|
464
589
|
face_squat: "squat",
|
|
@@ -911,6 +1036,18 @@ async function resolvePoseDetection(options) {
|
|
|
911
1036
|
}
|
|
912
1037
|
|
|
913
1038
|
// src/models/adapters/blazepose.ts
|
|
1039
|
+
function blazeInputSize(input) {
|
|
1040
|
+
if (typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) {
|
|
1041
|
+
return { vw: input.videoWidth || 0, vh: input.videoHeight || 0 };
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) {
|
|
1044
|
+
return { vw: input.naturalWidth || input.width || 0, vh: input.naturalHeight || input.height || 0 };
|
|
1045
|
+
}
|
|
1046
|
+
if (typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) {
|
|
1047
|
+
return { vw: input.width || 0, vh: input.height || 0 };
|
|
1048
|
+
}
|
|
1049
|
+
return { vw: input.width || 0, vh: input.height || 0 };
|
|
1050
|
+
}
|
|
914
1051
|
var BLAZE_TO_COCO = {
|
|
915
1052
|
nose: "nose",
|
|
916
1053
|
left_eye: "left_eye",
|
|
@@ -954,11 +1091,12 @@ var BlazePoseAdapter = class {
|
|
|
954
1091
|
}
|
|
955
1092
|
);
|
|
956
1093
|
}
|
|
957
|
-
async estimate(
|
|
1094
|
+
async estimate(input, options) {
|
|
958
1095
|
if (!this.detector) throw new Error("BlazePose adapter not loaded");
|
|
959
|
-
|
|
1096
|
+
const size = blazeInputSize(input);
|
|
1097
|
+
if (!(size.vw > 0 && size.vh > 0)) return null;
|
|
960
1098
|
const t0 = performance.now();
|
|
961
|
-
const poses = await this.detector.estimatePoses(
|
|
1099
|
+
const poses = await this.detector.estimatePoses(input, {
|
|
962
1100
|
flipHorizontal: false,
|
|
963
1101
|
maxPoses: 1
|
|
964
1102
|
});
|
|
@@ -978,15 +1116,15 @@ var BlazePoseAdapter = class {
|
|
|
978
1116
|
letterbox: {
|
|
979
1117
|
offsetX: 0,
|
|
980
1118
|
offsetY: 0,
|
|
981
|
-
drawW:
|
|
982
|
-
drawH:
|
|
983
|
-
vw:
|
|
984
|
-
vh:
|
|
1119
|
+
drawW: size.vw,
|
|
1120
|
+
drawH: size.vh,
|
|
1121
|
+
vw: size.vw,
|
|
1122
|
+
vh: size.vh
|
|
985
1123
|
}
|
|
986
1124
|
};
|
|
987
1125
|
}
|
|
988
|
-
const vw =
|
|
989
|
-
const vh =
|
|
1126
|
+
const vw = size.vw || 1;
|
|
1127
|
+
const vh = size.vh || 1;
|
|
990
1128
|
const dispW = options.displayWidth || 1;
|
|
991
1129
|
const dispH = options.displayHeight || 1;
|
|
992
1130
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1052,6 +1190,18 @@ var BlazePoseAdapter = class {
|
|
|
1052
1190
|
};
|
|
1053
1191
|
|
|
1054
1192
|
// src/models/adapters/movenet.ts
|
|
1193
|
+
function inputSize(input) {
|
|
1194
|
+
if (typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) {
|
|
1195
|
+
return { vw: input.videoWidth || 0, vh: input.videoHeight || 0 };
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) {
|
|
1198
|
+
return { vw: input.naturalWidth || input.width || 0, vh: input.naturalHeight || input.height || 0 };
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) {
|
|
1201
|
+
return { vw: input.width || 0, vh: input.height || 0 };
|
|
1202
|
+
}
|
|
1203
|
+
return { vw: input.width || 0, vh: input.height || 0 };
|
|
1204
|
+
}
|
|
1055
1205
|
var INPUT_SIZE = 192;
|
|
1056
1206
|
var SMOOTH_ALPHA = 0.5;
|
|
1057
1207
|
function modelNormToVideo(xNorm, yNorm, lb) {
|
|
@@ -1115,9 +1265,10 @@ var MoveNetGraphAdapter = class {
|
|
|
1115
1265
|
if (!this.offCtx) throw new Error("2d context unavailable");
|
|
1116
1266
|
return this.offCtx;
|
|
1117
1267
|
}
|
|
1118
|
-
prepareInput(
|
|
1119
|
-
const vw
|
|
1120
|
-
const
|
|
1268
|
+
prepareInput(input) {
|
|
1269
|
+
const { vw: rawW, vh: rawH } = inputSize(input);
|
|
1270
|
+
const vw = rawW || 1;
|
|
1271
|
+
const vh = rawH || 1;
|
|
1121
1272
|
const scale = Math.min(INPUT_SIZE / vw, INPUT_SIZE / vh);
|
|
1122
1273
|
const drawW = vw * scale;
|
|
1123
1274
|
const drawH = vh * scale;
|
|
@@ -1127,7 +1278,7 @@ var MoveNetGraphAdapter = class {
|
|
|
1127
1278
|
const c2d = this.poseCanvasCtx();
|
|
1128
1279
|
c2d.fillStyle = "#000";
|
|
1129
1280
|
c2d.fillRect(0, 0, INPUT_SIZE, INPUT_SIZE);
|
|
1130
|
-
c2d.drawImage(
|
|
1281
|
+
c2d.drawImage(input, 0, 0, vw, vh, offsetX, offsetY, drawW, drawH);
|
|
1131
1282
|
return this.offscreen;
|
|
1132
1283
|
}
|
|
1133
1284
|
smooth(raw) {
|
|
@@ -1143,16 +1294,19 @@ var MoveNetGraphAdapter = class {
|
|
|
1143
1294
|
}
|
|
1144
1295
|
return this.smoothed;
|
|
1145
1296
|
}
|
|
1146
|
-
async estimate(
|
|
1297
|
+
async estimate(input, options) {
|
|
1147
1298
|
if (!this.tf || !this.model) throw new Error("MoveNet adapter not loaded");
|
|
1148
|
-
|
|
1299
|
+
const size = inputSize(input);
|
|
1300
|
+
if (!(size.vw > 0 && size.vh > 0)) return null;
|
|
1149
1301
|
const t0 = performance.now();
|
|
1150
|
-
const canvas = this.prepareInput(
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1302
|
+
const canvas = this.prepareInput(input);
|
|
1303
|
+
const tensorIn = this.tf.tidy(
|
|
1304
|
+
() => this.tf.expandDims(this.tf.browser.fromPixels(canvas), 0)
|
|
1305
|
+
);
|
|
1306
|
+
const out = this.model.execute(tensorIn);
|
|
1153
1307
|
const tensor = Array.isArray(out) ? out[0] : out;
|
|
1154
1308
|
const data = tensor.dataSync();
|
|
1155
|
-
|
|
1309
|
+
tensorIn.dispose();
|
|
1156
1310
|
if (Array.isArray(out)) out.forEach((t) => t.dispose());
|
|
1157
1311
|
else out.dispose();
|
|
1158
1312
|
const inferenceMs = performance.now() - t0;
|
|
@@ -1281,11 +1435,11 @@ var SHELL_CSS = `
|
|
|
1281
1435
|
position: relative; width: 100%; height: 100%; min-height: 240px;
|
|
1282
1436
|
background: #000; overflow: hidden; isolation: isolate;
|
|
1283
1437
|
}
|
|
1284
|
-
.pt-root video, .pt-root canvas.pt-overlay {
|
|
1438
|
+
.pt-root video, .pt-root img.pt-still, .pt-root canvas.pt-overlay {
|
|
1285
1439
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
1286
1440
|
object-fit: cover; opacity: 0; transition: opacity 180ms ease-out;
|
|
1287
1441
|
}
|
|
1288
|
-
.pt-root video { z-index: 1; background: #000; }
|
|
1442
|
+
.pt-root video, .pt-root img.pt-still { z-index: 1; background: #000; }
|
|
1289
1443
|
.pt-root canvas.pt-overlay { z-index: 2; pointer-events: none; }
|
|
1290
1444
|
.pt-boot {
|
|
1291
1445
|
position: absolute; inset: 0; z-index: 4;
|
|
@@ -1359,6 +1513,7 @@ function mountDomShell(host, options = {}) {
|
|
|
1359
1513
|
root.className = "pt-root";
|
|
1360
1514
|
root.innerHTML = `
|
|
1361
1515
|
<video playsinline muted autoplay></video>
|
|
1516
|
+
<img class="pt-still" alt="" draggable="false" />
|
|
1362
1517
|
<canvas class="pt-overlay"></canvas>
|
|
1363
1518
|
<div class="pt-boot">
|
|
1364
1519
|
<div class="pt-boot-powered">Powered by</div>
|
|
@@ -1375,6 +1530,7 @@ function mountDomShell(host, options = {}) {
|
|
|
1375
1530
|
`;
|
|
1376
1531
|
host.appendChild(root);
|
|
1377
1532
|
const video = root.querySelector("video");
|
|
1533
|
+
const image = root.querySelector("img.pt-still");
|
|
1378
1534
|
const canvas = root.querySelector("canvas.pt-overlay");
|
|
1379
1535
|
const boot = root.querySelector(".pt-boot");
|
|
1380
1536
|
const bootMsg = root.querySelector(".pt-boot-msg");
|
|
@@ -1384,6 +1540,7 @@ function mountDomShell(host, options = {}) {
|
|
|
1384
1540
|
return {
|
|
1385
1541
|
root,
|
|
1386
1542
|
video,
|
|
1543
|
+
image,
|
|
1387
1544
|
canvas,
|
|
1388
1545
|
boot,
|
|
1389
1546
|
bootMsg,
|
|
@@ -1395,10 +1552,21 @@ function mountDomShell(host, options = {}) {
|
|
|
1395
1552
|
bootMsg.classList.toggle("is-error", isError);
|
|
1396
1553
|
},
|
|
1397
1554
|
setCameraVisible(visible) {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1555
|
+
if (visible) {
|
|
1556
|
+
this.setMediaMode(image.src ? "image" : "video");
|
|
1557
|
+
} else {
|
|
1558
|
+
this.setMediaMode("none");
|
|
1559
|
+
}
|
|
1560
|
+
},
|
|
1561
|
+
setMediaMode(mode) {
|
|
1562
|
+
const showVideo = mode === "video";
|
|
1563
|
+
const showImage = mode === "image";
|
|
1564
|
+
const showMedia = showVideo || showImage;
|
|
1565
|
+
video.style.opacity = showVideo ? "1" : "0";
|
|
1566
|
+
image.style.opacity = showImage ? "1" : "0";
|
|
1567
|
+
canvas.style.opacity = showMedia ? "1" : "0";
|
|
1568
|
+
boot.classList.toggle("hide", showMedia);
|
|
1569
|
+
if (!showMedia) {
|
|
1402
1570
|
bootMsg.textContent = loadingText;
|
|
1403
1571
|
bootMsg.classList.remove("is-error");
|
|
1404
1572
|
}
|
|
@@ -1423,6 +1591,7 @@ function mountDomShell(host, options = {}) {
|
|
|
1423
1591
|
applyMirror(facingMode) {
|
|
1424
1592
|
const t = facingMode === "user" ? "scaleX(-1)" : "none";
|
|
1425
1593
|
video.style.transform = t;
|
|
1594
|
+
image.style.transform = "none";
|
|
1426
1595
|
canvas.style.transform = t;
|
|
1427
1596
|
},
|
|
1428
1597
|
destroy() {
|
|
@@ -1626,6 +1795,10 @@ var PoseTrackerClient = class {
|
|
|
1626
1795
|
this.keypointsSuppressionLogged = false;
|
|
1627
1796
|
this.shell = null;
|
|
1628
1797
|
this.stream = null;
|
|
1798
|
+
this.poseSource = { type: "camera" };
|
|
1799
|
+
this.objectUrlRevoke = null;
|
|
1800
|
+
this.estimateInput = null;
|
|
1801
|
+
this.imageShotPending = false;
|
|
1629
1802
|
this.adapter = null;
|
|
1630
1803
|
this.running = false;
|
|
1631
1804
|
this.busy = false;
|
|
@@ -1652,6 +1825,15 @@ var PoseTrackerClient = class {
|
|
|
1652
1825
|
this.resolved = resolvePoseModel({ model: options.model, modelUrl: options.modelUrl });
|
|
1653
1826
|
this.skeletonDef = options.skeletonDef ?? null;
|
|
1654
1827
|
this.coldStartMode = options.coldStart === "full" ? "full" : "basic";
|
|
1828
|
+
try {
|
|
1829
|
+
this.poseSource = normalizePoseSource(options.source, {
|
|
1830
|
+
sourceUrl: options.sourceUrl,
|
|
1831
|
+
sourceFile: options.sourceFile,
|
|
1832
|
+
facingMode: facingFromOptions(options)
|
|
1833
|
+
});
|
|
1834
|
+
} catch {
|
|
1835
|
+
this.poseSource = { type: "camera", facingMode: facingFromOptions(options) };
|
|
1836
|
+
}
|
|
1655
1837
|
this.files = options.fileStore !== void 0 ? options.fileStore : createWebFileStore();
|
|
1656
1838
|
this.engineLoader = options.engineLoader ?? new EngineLoader({
|
|
1657
1839
|
fileStore: this.files,
|
|
@@ -1698,6 +1880,10 @@ var PoseTrackerClient = class {
|
|
|
1698
1880
|
getColdStartMode() {
|
|
1699
1881
|
return this.coldStartMode;
|
|
1700
1882
|
}
|
|
1883
|
+
/** Active pose input source (`camera` | `video` | `image`). */
|
|
1884
|
+
getSource() {
|
|
1885
|
+
return this.poseSource;
|
|
1886
|
+
}
|
|
1701
1887
|
addEventListener(listener) {
|
|
1702
1888
|
this.listeners.add(listener);
|
|
1703
1889
|
return () => this.listeners.delete(listener);
|
|
@@ -1761,31 +1947,98 @@ var PoseTrackerClient = class {
|
|
|
1761
1947
|
warmup(options) {
|
|
1762
1948
|
return this.preload(options);
|
|
1763
1949
|
}
|
|
1764
|
-
/**
|
|
1950
|
+
/**
|
|
1951
|
+
* Mount + load model + open source + start inference.
|
|
1952
|
+
* Camera / video stream continuously; image runs a single shot (re-run with {@link analyze}).
|
|
1953
|
+
*/
|
|
1765
1954
|
async start() {
|
|
1766
1955
|
if (this.disposed) throw new Error("PoseTracker disposed");
|
|
1767
1956
|
if (!this.shell) throw new Error("Call mount() before start()");
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1957
|
+
const needsMedia = this.poseSource.type === "camera" || this.poseSource.type === "video" || this.poseSource.type === "image";
|
|
1958
|
+
await this.preload({
|
|
1959
|
+
coldStart: this.opts.coldStart === "basic" && this.poseSource.type === "camera" ? "basic" : needsMedia ? "full" : "basic"
|
|
1960
|
+
});
|
|
1961
|
+
if (this.coldStartMode === "basic" || !this.estimateInput) {
|
|
1962
|
+
await this.ensureSource();
|
|
1963
|
+
}
|
|
1964
|
+
if (this.poseSource.type === "image") {
|
|
1965
|
+
this.imageShotPending = true;
|
|
1771
1966
|
}
|
|
1772
1967
|
this.beginLoop();
|
|
1773
1968
|
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Switch input source. Stops the current media, attaches the new source, and
|
|
1971
|
+
* restarts the loop when previously running.
|
|
1972
|
+
*/
|
|
1973
|
+
async setSource(source, extras) {
|
|
1974
|
+
if (this.disposed) throw new Error("PoseTracker disposed");
|
|
1975
|
+
const wasRunning = this.running;
|
|
1976
|
+
this.stopMediaOnly();
|
|
1977
|
+
this.poseSource = normalizePoseSource(source, {
|
|
1978
|
+
sourceUrl: extras?.sourceUrl ?? this.opts.sourceUrl,
|
|
1979
|
+
sourceFile: extras?.sourceFile ?? this.opts.sourceFile,
|
|
1980
|
+
facingMode: extras?.facingMode ?? facingFromOptions(this.opts)
|
|
1981
|
+
});
|
|
1982
|
+
this.opts.source = this.poseSource;
|
|
1983
|
+
if (this.poseSource.type === "camera" && this.poseSource.facingMode) {
|
|
1984
|
+
this.opts.facingMode = this.poseSource.facingMode;
|
|
1985
|
+
}
|
|
1986
|
+
if (!this.shell) return;
|
|
1987
|
+
await this.ensureSource();
|
|
1988
|
+
if (wasRunning || this.poseSource.type === "image") {
|
|
1989
|
+
if (this.poseSource.type === "image") this.imageShotPending = true;
|
|
1990
|
+
this.beginLoop();
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Re-run pose estimation on the current still image (no-op for camera/video
|
|
1995
|
+
* while the loop is already running — for those, call {@link start}).
|
|
1996
|
+
*/
|
|
1997
|
+
async analyze() {
|
|
1998
|
+
if (this.disposed) throw new Error("PoseTracker disposed");
|
|
1999
|
+
if (!this.shell) throw new Error("Call mount() before analyze()");
|
|
2000
|
+
await this.ensureAdapter();
|
|
2001
|
+
if (!this.estimateInput) await this.ensureSource();
|
|
2002
|
+
if (this.poseSource.type === "image") {
|
|
2003
|
+
this.imageShotPending = true;
|
|
2004
|
+
if (!this.running) this.beginLoop();
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
return this.runOneFrame();
|
|
2008
|
+
}
|
|
1774
2009
|
stop() {
|
|
1775
2010
|
this.running = false;
|
|
1776
2011
|
this.loopToken += 1;
|
|
1777
2012
|
this.busy = false;
|
|
1778
|
-
|
|
1779
|
-
this.
|
|
2013
|
+
this.imageShotPending = false;
|
|
2014
|
+
this.stopMediaOnly();
|
|
1780
2015
|
if (this.shell) {
|
|
1781
|
-
this.shell.
|
|
1782
|
-
this.shell.setCameraVisible(false);
|
|
2016
|
+
this.shell.setMediaMode("none");
|
|
1783
2017
|
this.shell.setPlacementVisible(false);
|
|
1784
2018
|
this.cameraRevealed = false;
|
|
1785
2019
|
this.applyWatermark();
|
|
1786
2020
|
}
|
|
1787
2021
|
if (this.status !== "error") this.setStatus("idle");
|
|
1788
2022
|
}
|
|
2023
|
+
stopMediaOnly() {
|
|
2024
|
+
stopMediaStream(this.stream);
|
|
2025
|
+
this.stream = null;
|
|
2026
|
+
this.estimateInput = null;
|
|
2027
|
+
if (this.objectUrlRevoke) {
|
|
2028
|
+
this.objectUrlRevoke();
|
|
2029
|
+
this.objectUrlRevoke = null;
|
|
2030
|
+
}
|
|
2031
|
+
if (this.shell) {
|
|
2032
|
+
try {
|
|
2033
|
+
this.shell.video.pause();
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
this.shell.video.removeAttribute("src");
|
|
2037
|
+
this.shell.video.srcObject = null;
|
|
2038
|
+
this.shell.video.load();
|
|
2039
|
+
this.shell.image.removeAttribute("src");
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
1789
2042
|
async dispose() {
|
|
1790
2043
|
this.stopExercise();
|
|
1791
2044
|
this.stop();
|
|
@@ -2054,7 +2307,7 @@ var PoseTrackerClient = class {
|
|
|
2054
2307
|
async ensureColdStartMode(mode) {
|
|
2055
2308
|
if (mode !== "full") return;
|
|
2056
2309
|
if (!this.shell) return;
|
|
2057
|
-
await this.
|
|
2310
|
+
await this.ensureSource();
|
|
2058
2311
|
}
|
|
2059
2312
|
async ensureAdapter() {
|
|
2060
2313
|
if (this.adapter) return;
|
|
@@ -2071,48 +2324,102 @@ var PoseTrackerClient = class {
|
|
|
2071
2324
|
this.adapter = createPoseAdapter({ resolved: this.resolved });
|
|
2072
2325
|
await this.adapter.load();
|
|
2073
2326
|
}
|
|
2074
|
-
async
|
|
2075
|
-
if (!this.shell) throw new Error("Call mount() before opening
|
|
2076
|
-
|
|
2327
|
+
async ensureSource() {
|
|
2328
|
+
if (!this.shell) throw new Error("Call mount() before opening a pose source");
|
|
2329
|
+
const src = this.poseSource;
|
|
2330
|
+
if (src.type === "camera") {
|
|
2331
|
+
if (this.stream && this.estimateInput) return;
|
|
2332
|
+
this.emit({
|
|
2333
|
+
type: "initialization",
|
|
2334
|
+
step: "accessing_webcam",
|
|
2335
|
+
message: "accessing webcam (source=camera)",
|
|
2336
|
+
ready: false
|
|
2337
|
+
});
|
|
2338
|
+
try {
|
|
2339
|
+
this.stream = await openCameraStream({
|
|
2340
|
+
facingMode: src.facingMode ?? facingFromOptions(this.opts),
|
|
2341
|
+
idealWidth: this.opts.idealWidth,
|
|
2342
|
+
idealHeight: this.opts.idealHeight
|
|
2343
|
+
});
|
|
2344
|
+
} catch (err) {
|
|
2345
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2346
|
+
const denied = /NotAllowed|Permission|denied/i.test(msg);
|
|
2347
|
+
this.reportError({
|
|
2348
|
+
type: "error",
|
|
2349
|
+
code: denied ? "camera_denied" : "camera_unavailable",
|
|
2350
|
+
message: msg
|
|
2351
|
+
});
|
|
2352
|
+
this.shell.setBootMessage(
|
|
2353
|
+
denied ? "Camera permission denied \u2014 allow access in the browser" : "Camera unavailable",
|
|
2354
|
+
true
|
|
2355
|
+
);
|
|
2356
|
+
throw err;
|
|
2357
|
+
}
|
|
2358
|
+
this.shell.video.srcObject = this.stream;
|
|
2359
|
+
this.shell.applyMirror(src.facingMode ?? facingFromOptions(this.opts));
|
|
2360
|
+
this.shell.setMediaMode("video");
|
|
2361
|
+
try {
|
|
2362
|
+
await this.shell.video.play();
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
this.estimateInput = this.shell.video;
|
|
2366
|
+
this.scheduleReveal("camera");
|
|
2367
|
+
void this.handleCameraStart({
|
|
2368
|
+
backend: this.acceleration.backend ?? "webgl",
|
|
2369
|
+
profileId: null
|
|
2370
|
+
});
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
if (src.type === "video") {
|
|
2374
|
+
this.emit({
|
|
2375
|
+
type: "initialization",
|
|
2376
|
+
step: "loading_media",
|
|
2377
|
+
message: "loading video (source=video)",
|
|
2378
|
+
ready: false
|
|
2379
|
+
});
|
|
2380
|
+
try {
|
|
2381
|
+
await this.attachVideoSource(src.src, {
|
|
2382
|
+
autoplay: src.autoplay !== false,
|
|
2383
|
+
loop: src.loop !== false,
|
|
2384
|
+
muted: src.muted !== false
|
|
2385
|
+
});
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2388
|
+
this.reportError({ type: "error", code: "media_load_failed", message: msg });
|
|
2389
|
+
this.shell.setBootMessage("Could not load video source", true);
|
|
2390
|
+
throw err;
|
|
2391
|
+
}
|
|
2392
|
+
this.shell.applyMirror("none");
|
|
2393
|
+
this.shell.setMediaMode("video");
|
|
2394
|
+
this.scheduleReveal("video");
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2077
2397
|
this.emit({
|
|
2078
2398
|
type: "initialization",
|
|
2079
|
-
step: "
|
|
2080
|
-
message: "
|
|
2399
|
+
step: "loading_media",
|
|
2400
|
+
message: "loading image (source=image)",
|
|
2081
2401
|
ready: false
|
|
2082
2402
|
});
|
|
2083
2403
|
try {
|
|
2084
|
-
this.
|
|
2085
|
-
facingMode: facingFromOptions(this.opts),
|
|
2086
|
-
idealWidth: this.opts.idealWidth,
|
|
2087
|
-
idealHeight: this.opts.idealHeight
|
|
2088
|
-
});
|
|
2404
|
+
await this.attachImageSource(src.src);
|
|
2089
2405
|
} catch (err) {
|
|
2090
2406
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2091
|
-
|
|
2092
|
-
this.
|
|
2093
|
-
type: "error",
|
|
2094
|
-
code: denied ? "camera_denied" : "camera_unavailable",
|
|
2095
|
-
message: msg
|
|
2096
|
-
});
|
|
2097
|
-
this.shell.setBootMessage(
|
|
2098
|
-
denied ? "Camera permission denied \u2014 allow access in the browser" : "Camera unavailable",
|
|
2099
|
-
true
|
|
2100
|
-
);
|
|
2407
|
+
this.reportError({ type: "error", code: "media_load_failed", message: msg });
|
|
2408
|
+
this.shell.setBootMessage("Could not load image source", true);
|
|
2101
2409
|
throw err;
|
|
2102
2410
|
}
|
|
2103
|
-
this.shell.
|
|
2104
|
-
this.shell.
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
}
|
|
2411
|
+
this.shell.applyMirror("none");
|
|
2412
|
+
this.shell.setMediaMode("image");
|
|
2413
|
+
this.scheduleReveal("image");
|
|
2414
|
+
}
|
|
2415
|
+
scheduleReveal(kind) {
|
|
2109
2416
|
const started = Date.now();
|
|
2110
2417
|
const tick = () => {
|
|
2111
2418
|
this.revealCamera();
|
|
2112
2419
|
if (this.cameraRevealed) return;
|
|
2113
2420
|
if (Date.now() - started > 8e3) {
|
|
2114
2421
|
this.shell?.setBootMessage(
|
|
2115
|
-
|
|
2422
|
+
`${kind} loaded but preview is empty \u2014 check the file or CORS`,
|
|
2116
2423
|
true
|
|
2117
2424
|
);
|
|
2118
2425
|
return;
|
|
@@ -2120,88 +2427,182 @@ var PoseTrackerClient = class {
|
|
|
2120
2427
|
setTimeout(tick, 120);
|
|
2121
2428
|
};
|
|
2122
2429
|
setTimeout(tick, 60);
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2430
|
+
}
|
|
2431
|
+
async attachVideoSource(src, opts) {
|
|
2432
|
+
if (!this.shell) throw new Error("no shell");
|
|
2433
|
+
const video = this.shell.video;
|
|
2434
|
+
if (isVideoElement(src)) {
|
|
2435
|
+
if (src.parentElement && src !== video) {
|
|
2436
|
+
this.estimateInput = src;
|
|
2437
|
+
if (opts.autoplay) {
|
|
2438
|
+
try {
|
|
2439
|
+
await src.play();
|
|
2440
|
+
} catch {
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
await waitForVideoReady(src);
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
if (src.srcObject) {
|
|
2447
|
+
video.srcObject = src.srcObject;
|
|
2448
|
+
} else if (src.currentSrc || src.src) {
|
|
2449
|
+
video.src = src.currentSrc || src.src;
|
|
2450
|
+
}
|
|
2451
|
+
} else {
|
|
2452
|
+
const resolved = await resolveObjectUrl(src);
|
|
2453
|
+
this.objectUrlRevoke = resolved.revoke;
|
|
2454
|
+
video.src = resolved.url;
|
|
2455
|
+
}
|
|
2456
|
+
video.loop = opts.loop;
|
|
2457
|
+
video.muted = opts.muted;
|
|
2458
|
+
video.playsInline = true;
|
|
2459
|
+
await waitForVideoReady(video);
|
|
2460
|
+
if (opts.autoplay) {
|
|
2461
|
+
try {
|
|
2462
|
+
await video.play();
|
|
2463
|
+
} catch {
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
this.estimateInput = video;
|
|
2467
|
+
}
|
|
2468
|
+
async attachImageSource(src) {
|
|
2469
|
+
if (!this.shell) throw new Error("no shell");
|
|
2470
|
+
if (isImageBitmap(src)) {
|
|
2471
|
+
const resolved2 = await imageBitmapToObjectUrl(src);
|
|
2472
|
+
this.objectUrlRevoke = resolved2.revoke;
|
|
2473
|
+
this.shell.image.src = resolved2.url;
|
|
2474
|
+
await waitForImageReady(this.shell.image);
|
|
2475
|
+
this.estimateInput = src;
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (isImageElement(src)) {
|
|
2479
|
+
if (src.complete && src.naturalWidth > 0) {
|
|
2480
|
+
this.shell.image.src = src.currentSrc || src.src;
|
|
2481
|
+
this.estimateInput = src;
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
await waitForImageReady(src);
|
|
2485
|
+
this.shell.image.src = src.currentSrc || src.src;
|
|
2486
|
+
this.estimateInput = src;
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
const resolved = await resolveObjectUrl(src);
|
|
2490
|
+
this.objectUrlRevoke = resolved.revoke;
|
|
2491
|
+
this.shell.image.src = resolved.url;
|
|
2492
|
+
await waitForImageReady(this.shell.image);
|
|
2493
|
+
this.estimateInput = this.shell.image;
|
|
2127
2494
|
}
|
|
2128
2495
|
beginLoop() {
|
|
2129
2496
|
this.running = true;
|
|
2130
2497
|
this.fpsTimer = performance.now();
|
|
2131
2498
|
void this.loop(++this.loopToken);
|
|
2132
2499
|
}
|
|
2500
|
+
frameReady(input) {
|
|
2501
|
+
const size = mediaSize(
|
|
2502
|
+
input
|
|
2503
|
+
);
|
|
2504
|
+
if (!(size.width > 0 && size.height > 0)) return false;
|
|
2505
|
+
if (isVideoElement(input)) {
|
|
2506
|
+
if (input.readyState < 2) return false;
|
|
2507
|
+
if (this.poseSource.type === "video" && (input.paused || input.ended)) return false;
|
|
2508
|
+
}
|
|
2509
|
+
return true;
|
|
2510
|
+
}
|
|
2511
|
+
async runOneFrame() {
|
|
2512
|
+
if (!this.adapter || !this.shell || !this.estimateInput) return null;
|
|
2513
|
+
if (!this.frameReady(this.estimateInput)) return null;
|
|
2514
|
+
const input = this.estimateInput;
|
|
2515
|
+
const dispW = this.shell.canvas.clientWidth || this.shell.root.clientWidth || 1;
|
|
2516
|
+
const dispH = this.shell.canvas.clientHeight || this.shell.root.clientHeight || 1;
|
|
2517
|
+
const facing = this.poseSource.type === "camera" ? facingFromOptions(this.opts) : "environment";
|
|
2518
|
+
const result = await this.adapter.estimate(input, {
|
|
2519
|
+
facingMode: facing,
|
|
2520
|
+
displayWidth: dispW,
|
|
2521
|
+
displayHeight: dispH
|
|
2522
|
+
});
|
|
2523
|
+
if (!result) return null;
|
|
2524
|
+
const ctx = this.shell.canvas.getContext("2d");
|
|
2525
|
+
if (ctx && this.opts.drawSkeleton !== false) {
|
|
2526
|
+
const mapped = mapVideoKeypointsToDisplay(
|
|
2527
|
+
result.videoKeypoints,
|
|
2528
|
+
result.letterbox,
|
|
2529
|
+
dispW,
|
|
2530
|
+
dispH
|
|
2531
|
+
);
|
|
2532
|
+
drawSkeleton(ctx, mapped, dispW, dispH, true, this.skeletonDef);
|
|
2533
|
+
} else if (ctx && this.opts.drawSkeleton === false) {
|
|
2534
|
+
ctx.clearRect(0, 0, dispW, dispH);
|
|
2535
|
+
}
|
|
2536
|
+
const pose = {
|
|
2537
|
+
keypoints: result.keypoints,
|
|
2538
|
+
score: result.score,
|
|
2539
|
+
timestampMs: Date.now()
|
|
2540
|
+
};
|
|
2541
|
+
this.ingestPose(pose);
|
|
2542
|
+
this.inferMs.push(result.inferenceMs);
|
|
2543
|
+
if (this.inferMs.length > 30) this.inferMs.shift();
|
|
2544
|
+
this.fpsWindow += 1;
|
|
2545
|
+
const now = performance.now();
|
|
2546
|
+
if (now - this.fpsTimer >= 1e3) {
|
|
2547
|
+
const fps = this.fpsWindow;
|
|
2548
|
+
this.fpsWindow = 0;
|
|
2549
|
+
this.fpsTimer = now;
|
|
2550
|
+
const med = median(this.inferMs);
|
|
2551
|
+
this.acceleration.medianInferenceMs = med;
|
|
2552
|
+
const above = result.keypoints.filter((k) => k.score >= 0.3).length;
|
|
2553
|
+
const backend = "getBackend" in this.adapter && typeof this.adapter.getBackend === "function" ? this.adapter.getBackend() : null;
|
|
2554
|
+
this.acceleration.backend = backend;
|
|
2555
|
+
const size = mediaSize(input);
|
|
2556
|
+
if (this.opts.debugHud) {
|
|
2557
|
+
this.shell.setHud(
|
|
2558
|
+
`${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
|
|
2559
|
+
true
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
this.emit({
|
|
2563
|
+
type: "stats",
|
|
2564
|
+
fps,
|
|
2565
|
+
medianInferenceMs: med,
|
|
2566
|
+
backend,
|
|
2567
|
+
keypointsAbove03: above,
|
|
2568
|
+
meanScore: result.score,
|
|
2569
|
+
videoSize: `${size.width}x${size.height}`,
|
|
2570
|
+
timestampMs: Date.now()
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
this.revealCamera();
|
|
2574
|
+
return pose;
|
|
2575
|
+
}
|
|
2133
2576
|
async loop(token) {
|
|
2134
2577
|
if (token !== this.loopToken || !this.running || !this.adapter || !this.shell || this.disposed) {
|
|
2135
2578
|
return;
|
|
2136
2579
|
}
|
|
2137
|
-
const
|
|
2138
|
-
|
|
2580
|
+
const input = this.estimateInput;
|
|
2581
|
+
const shouldInfer = !!input && !this.busy && this.frameReady(input) && (this.poseSource.type !== "image" || this.imageShotPending);
|
|
2582
|
+
if (shouldInfer) {
|
|
2139
2583
|
this.busy = true;
|
|
2140
2584
|
try {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
});
|
|
2148
|
-
if (result) {
|
|
2149
|
-
const ctx = this.shell.canvas.getContext("2d");
|
|
2150
|
-
if (ctx && this.opts.drawSkeleton !== false) {
|
|
2151
|
-
const mapped = mapVideoKeypointsToDisplay(
|
|
2152
|
-
result.videoKeypoints,
|
|
2153
|
-
result.letterbox,
|
|
2154
|
-
dispW,
|
|
2155
|
-
dispH
|
|
2156
|
-
);
|
|
2157
|
-
drawSkeleton(ctx, mapped, dispW, dispH, true, this.skeletonDef);
|
|
2158
|
-
} else if (ctx && this.opts.drawSkeleton === false) {
|
|
2159
|
-
ctx.clearRect(0, 0, dispW, dispH);
|
|
2160
|
-
}
|
|
2161
|
-
const pose = {
|
|
2162
|
-
keypoints: result.keypoints,
|
|
2163
|
-
score: result.score,
|
|
2164
|
-
timestampMs: Date.now()
|
|
2165
|
-
};
|
|
2166
|
-
this.ingestPose(pose);
|
|
2167
|
-
this.inferMs.push(result.inferenceMs);
|
|
2168
|
-
if (this.inferMs.length > 30) this.inferMs.shift();
|
|
2169
|
-
this.fpsWindow += 1;
|
|
2170
|
-
const now = performance.now();
|
|
2171
|
-
if (now - this.fpsTimer >= 1e3) {
|
|
2172
|
-
const fps = this.fpsWindow;
|
|
2173
|
-
this.fpsWindow = 0;
|
|
2174
|
-
this.fpsTimer = now;
|
|
2175
|
-
const med = median(this.inferMs);
|
|
2176
|
-
this.acceleration.medianInferenceMs = med;
|
|
2177
|
-
const above = result.keypoints.filter((k) => k.score >= 0.3).length;
|
|
2178
|
-
const backend = "getBackend" in this.adapter && typeof this.adapter.getBackend === "function" ? this.adapter.getBackend() : null;
|
|
2179
|
-
this.acceleration.backend = backend;
|
|
2180
|
-
if (this.opts.debugHud) {
|
|
2181
|
-
this.shell.setHud(
|
|
2182
|
-
`${this.resolved.modelId} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
|
|
2183
|
-
true
|
|
2184
|
-
);
|
|
2185
|
-
}
|
|
2186
|
-
this.emit({
|
|
2187
|
-
type: "stats",
|
|
2188
|
-
fps,
|
|
2189
|
-
medianInferenceMs: med,
|
|
2190
|
-
backend,
|
|
2191
|
-
keypointsAbove03: above,
|
|
2192
|
-
meanScore: result.score,
|
|
2193
|
-
videoSize: `${video.videoWidth}x${video.videoHeight}`,
|
|
2194
|
-
timestampMs: Date.now()
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2585
|
+
await this.runOneFrame();
|
|
2586
|
+
if (this.poseSource.type === "image") {
|
|
2587
|
+
this.imageShotPending = false;
|
|
2588
|
+
this.running = false;
|
|
2589
|
+
this.busy = false;
|
|
2590
|
+
return;
|
|
2197
2591
|
}
|
|
2198
|
-
this.revealCamera();
|
|
2199
2592
|
} catch (err) {
|
|
2200
2593
|
const message = err instanceof Error ? err.message : String(err);
|
|
2201
2594
|
this.emit({ type: "error", code: "internal", message: `inference: ${message}` });
|
|
2202
2595
|
}
|
|
2203
2596
|
this.busy = false;
|
|
2204
2597
|
}
|
|
2598
|
+
if (token !== this.loopToken || !this.running) return;
|
|
2599
|
+
const videoEl = input && isVideoElement(input) ? input : this.shell.video;
|
|
2600
|
+
if (this.poseSource.type !== "image" && typeof videoEl.requestVideoFrameCallback === "function" && (this.poseSource.type === "video" || this.poseSource.type === "camera")) {
|
|
2601
|
+
videoEl.requestVideoFrameCallback(() => {
|
|
2602
|
+
void this.loop(token);
|
|
2603
|
+
});
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2205
2606
|
requestAnimationFrame(() => {
|
|
2206
2607
|
void this.loop(token);
|
|
2207
2608
|
});
|
|
@@ -2458,14 +2859,19 @@ var PoseTrackerClient = class {
|
|
|
2458
2859
|
}
|
|
2459
2860
|
revealCamera() {
|
|
2460
2861
|
if (!this.shell || this.cameraRevealed) return;
|
|
2461
|
-
const
|
|
2462
|
-
if (!
|
|
2862
|
+
const input = this.estimateInput;
|
|
2863
|
+
if (!input) return;
|
|
2864
|
+
const size = mediaSize(input);
|
|
2865
|
+
if (!(size.width > 0 && size.height > 0)) return;
|
|
2463
2866
|
requestAnimationFrame(() => {
|
|
2464
2867
|
requestAnimationFrame(() => {
|
|
2465
2868
|
if (this.cameraRevealed || !this.shell) return;
|
|
2466
|
-
if (!(this.shell.video.videoWidth > 0)) return;
|
|
2467
2869
|
this.cameraRevealed = true;
|
|
2468
|
-
this.
|
|
2870
|
+
if (this.poseSource.type === "image") {
|
|
2871
|
+
this.shell.setMediaMode("image");
|
|
2872
|
+
} else {
|
|
2873
|
+
this.shell.setMediaMode("video");
|
|
2874
|
+
}
|
|
2469
2875
|
this.applyWatermark();
|
|
2470
2876
|
});
|
|
2471
2877
|
});
|
|
@@ -2547,6 +2953,6 @@ var PoseCamera = {
|
|
|
2547
2953
|
mount: PoseCamera_mount
|
|
2548
2954
|
};
|
|
2549
2955
|
|
|
2550
|
-
export { BRAND_COLORS, BlazePoseAdapter, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, ConfigureError, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, EXERCISE_ALIASES, EngineLoader, FREE_PLAN_FEATURES_MESSAGE, INVALID_TOKEN_MESSAGE, MoveNetGraphAdapter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PoseCamera, PoseCamera_mount, PoseTrackerClient, SDK_NAME, SDK_VERSION, SkeletonFetchError, TrackError, UsageTracker, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, mapVideoKeypointsToDisplay, mountDomShell, openCameraStream, resolveExerciseName, resolveFeatures, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
|
|
2956
|
+
export { BRAND_COLORS, BlazePoseAdapter, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, ConfigureError, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, EXERCISE_ALIASES, EngineLoader, FREE_PLAN_FEATURES_MESSAGE, INVALID_TOKEN_MESSAGE, MoveNetGraphAdapter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PoseCamera, PoseCamera_mount, PoseTrackerClient, SDK_NAME, SDK_VERSION, SkeletonFetchError, TrackError, UsageTracker, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, mapVideoKeypointsToDisplay, mountDomShell, normalizePoseSource, openCameraStream, resolveExerciseName, resolveFeatures, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
|
|
2551
2957
|
//# sourceMappingURL=index.js.map
|
|
2552
2958
|
//# sourceMappingURL=index.js.map
|