@daboss2003/liveness-web 1.0.1 → 1.0.3
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/engine.js +11 -11
- package/dist/ui.js +24 -2
- package/package.json +1 -1
- package/src/engine.ts +11 -11
- package/src/ui.ts +25 -2
package/dist/engine.js
CHANGED
|
@@ -110,14 +110,14 @@ const config = {
|
|
|
110
110
|
// Number of frames averaged to produce the resting baseline per step
|
|
111
111
|
baselineFrames: 8,
|
|
112
112
|
// ── Head turns (relative to baseline) ─────────────────────────────────────
|
|
113
|
-
yawTurnDelta:
|
|
113
|
+
yawTurnDelta: 9, // degrees of YAW change needed from rest
|
|
114
114
|
yawWrongDirDelta: 16, // block if turned clearly the WRONG way
|
|
115
|
-
headTurnHoldMs:
|
|
115
|
+
headTurnHoldMs: 80, // sustain the turned pose for this long
|
|
116
116
|
// ── Nod (relative to baseline) ────────────────────────────────────────────
|
|
117
|
-
nodDownDelta:
|
|
118
|
-
nodReturnFraction: 0.
|
|
119
|
-
nodReturnMaxDelta:
|
|
120
|
-
maxYawDuringNod:
|
|
117
|
+
nodDownDelta: 4, // chin must DROP by this many degrees from baseline
|
|
118
|
+
nodReturnFraction: 0.75, // return to 75% of peak nod depth to complete
|
|
119
|
+
nodReturnMaxDelta: 9, // cap: never require returning past 9° from baseline
|
|
120
|
+
maxYawDuringNod: 32,
|
|
121
121
|
// ── Blink ──────────────────────────────────────────────────────────────────
|
|
122
122
|
blinkClosedThreshold: 0.35, // blendshape score = eyes closed
|
|
123
123
|
blinkOpenThreshold: 0.20, // blendshape score = eyes open
|
|
@@ -127,11 +127,11 @@ const config = {
|
|
|
127
127
|
maxYawDuringBlink: 25,
|
|
128
128
|
maxPitchDuringBlink: 25,
|
|
129
129
|
// ── Mouth ──────────────────────────────────────────────────────────────────
|
|
130
|
-
mouthOpenThreshold: 0.
|
|
131
|
-
mouthOpenMarThreshold: 0.
|
|
132
|
-
mouthHoldMs:
|
|
133
|
-
maxYawDuringMouth:
|
|
134
|
-
maxPitchDuringMouth:
|
|
130
|
+
mouthOpenThreshold: 0.20, // jawOpen blendshape
|
|
131
|
+
mouthOpenMarThreshold: 0.20,
|
|
132
|
+
mouthHoldMs: 50,
|
|
133
|
+
maxYawDuringMouth: 35,
|
|
134
|
+
maxPitchDuringMouth: 35,
|
|
135
135
|
// ── Face-in-oval ───────────────────────────────────────────────────────────
|
|
136
136
|
ovalCx: 0.50,
|
|
137
137
|
ovalCy: 0.42,
|
package/dist/ui.js
CHANGED
|
@@ -41,8 +41,9 @@ function createStyles() {
|
|
|
41
41
|
display: flex;
|
|
42
42
|
flex-direction: column;
|
|
43
43
|
align-items: center;
|
|
44
|
-
|
|
45
|
-
--oval-
|
|
44
|
+
/* Radii: keep oval inside viewport on mobile (diameter = 2× this), cap at desktop */
|
|
45
|
+
--oval-w: min(45vmin, ${OVAL_W}px);
|
|
46
|
+
--oval-h: min(60vmin, ${OVAL_H}px);
|
|
46
47
|
/* Half-height of the visible oval for positioning (oval uses full w/h as radii) */
|
|
47
48
|
--oval-half-h: var(--oval-h);
|
|
48
49
|
}
|
|
@@ -63,8 +64,20 @@ function createStyles() {
|
|
|
63
64
|
object-fit: cover;
|
|
64
65
|
/* Mirror so it feels like a selfie camera */
|
|
65
66
|
transform: scaleX(-1);
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transition: opacity 0.2s ease;
|
|
66
69
|
/* Clip the video to the oval using clip-path on the parent */
|
|
67
70
|
}
|
|
71
|
+
.lv-video.is-playing { opacity: 1; }
|
|
72
|
+
.lv-video::-webkit-media-controls,
|
|
73
|
+
.lv-video::-webkit-media-controls-panel,
|
|
74
|
+
.lv-video::-webkit-media-controls-play-button,
|
|
75
|
+
.lv-video::-webkit-media-controls-start-playback-button,
|
|
76
|
+
.lv-video::-webkit-media-controls-overlay-play-button,
|
|
77
|
+
.lv-video::-webkit-media-controls-enclosure {
|
|
78
|
+
display: none !important;
|
|
79
|
+
-webkit-appearance: none;
|
|
80
|
+
}
|
|
68
81
|
|
|
69
82
|
/* ── Dark overlay with oval cutout ──────────────────────────────────── */
|
|
70
83
|
.lv-overlay {
|
|
@@ -265,6 +278,7 @@ export function startLivenessWithUI(options) {
|
|
|
265
278
|
video.className = "lv-video";
|
|
266
279
|
video.setAttribute("autoplay", "");
|
|
267
280
|
video.setAttribute("playsinline", "");
|
|
281
|
+
video.setAttribute("webkit-playsinline", "");
|
|
268
282
|
video.setAttribute("muted", "");
|
|
269
283
|
videoBg.appendChild(video);
|
|
270
284
|
root.appendChild(videoBg);
|
|
@@ -356,6 +370,9 @@ export function startLivenessWithUI(options) {
|
|
|
356
370
|
}
|
|
357
371
|
function cleanup() {
|
|
358
372
|
engine.stop();
|
|
373
|
+
video.removeEventListener("playing", onVideoPlaying);
|
|
374
|
+
video.removeEventListener("pause", onVideoPause);
|
|
375
|
+
video.removeEventListener("waiting", onVideoPause);
|
|
359
376
|
root.remove();
|
|
360
377
|
}
|
|
361
378
|
// ── Engine ─────────────────────────────────────────────────────────────────
|
|
@@ -363,6 +380,11 @@ export function startLivenessWithUI(options) {
|
|
|
363
380
|
const sounds = options.sounds ?? {
|
|
364
381
|
...(Object.keys(DEFAULT_SOUND_DATA_URLS).length > 0 ? DEFAULT_SOUND_DATA_URLS : { baseUrl: "audios/" }),
|
|
365
382
|
};
|
|
383
|
+
const onVideoPlaying = () => video.classList.add("is-playing");
|
|
384
|
+
const onVideoPause = () => video.classList.remove("is-playing");
|
|
385
|
+
video.addEventListener("playing", onVideoPlaying);
|
|
386
|
+
video.addEventListener("pause", onVideoPause);
|
|
387
|
+
video.addEventListener("waiting", onVideoPause);
|
|
366
388
|
const engine = new LivenessEngine({
|
|
367
389
|
videoElement: video,
|
|
368
390
|
canvasElement: canvas,
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -188,15 +188,15 @@ const config = {
|
|
|
188
188
|
baselineFrames: 8,
|
|
189
189
|
|
|
190
190
|
// ── Head turns (relative to baseline) ─────────────────────────────────────
|
|
191
|
-
yawTurnDelta:
|
|
191
|
+
yawTurnDelta: 9, // degrees of YAW change needed from rest
|
|
192
192
|
yawWrongDirDelta: 16, // block if turned clearly the WRONG way
|
|
193
|
-
headTurnHoldMs:
|
|
193
|
+
headTurnHoldMs: 80, // sustain the turned pose for this long
|
|
194
194
|
|
|
195
195
|
// ── Nod (relative to baseline) ────────────────────────────────────────────
|
|
196
|
-
nodDownDelta:
|
|
197
|
-
nodReturnFraction: 0.
|
|
198
|
-
nodReturnMaxDelta:
|
|
199
|
-
maxYawDuringNod:
|
|
196
|
+
nodDownDelta: 4, // chin must DROP by this many degrees from baseline
|
|
197
|
+
nodReturnFraction: 0.75, // return to 75% of peak nod depth to complete
|
|
198
|
+
nodReturnMaxDelta: 9, // cap: never require returning past 9° from baseline
|
|
199
|
+
maxYawDuringNod: 32,
|
|
200
200
|
|
|
201
201
|
// ── Blink ──────────────────────────────────────────────────────────────────
|
|
202
202
|
blinkClosedThreshold: 0.35, // blendshape score = eyes closed
|
|
@@ -208,11 +208,11 @@ const config = {
|
|
|
208
208
|
maxPitchDuringBlink: 25,
|
|
209
209
|
|
|
210
210
|
// ── Mouth ──────────────────────────────────────────────────────────────────
|
|
211
|
-
mouthOpenThreshold: 0.
|
|
212
|
-
mouthOpenMarThreshold: 0.
|
|
213
|
-
mouthHoldMs:
|
|
214
|
-
maxYawDuringMouth:
|
|
215
|
-
maxPitchDuringMouth:
|
|
211
|
+
mouthOpenThreshold: 0.20, // jawOpen blendshape
|
|
212
|
+
mouthOpenMarThreshold: 0.20,
|
|
213
|
+
mouthHoldMs: 50,
|
|
214
|
+
maxYawDuringMouth: 35,
|
|
215
|
+
maxPitchDuringMouth: 35,
|
|
216
216
|
|
|
217
217
|
// ── Face-in-oval ───────────────────────────────────────────────────────────
|
|
218
218
|
ovalCx: 0.50,
|
package/src/ui.ts
CHANGED
|
@@ -56,8 +56,9 @@ function createStyles(): HTMLStyleElement {
|
|
|
56
56
|
display: flex;
|
|
57
57
|
flex-direction: column;
|
|
58
58
|
align-items: center;
|
|
59
|
-
|
|
60
|
-
--oval-
|
|
59
|
+
/* Radii: keep oval inside viewport on mobile (diameter = 2× this), cap at desktop */
|
|
60
|
+
--oval-w: min(45vmin, ${OVAL_W}px);
|
|
61
|
+
--oval-h: min(60vmin, ${OVAL_H}px);
|
|
61
62
|
/* Half-height of the visible oval for positioning (oval uses full w/h as radii) */
|
|
62
63
|
--oval-half-h: var(--oval-h);
|
|
63
64
|
}
|
|
@@ -78,8 +79,20 @@ function createStyles(): HTMLStyleElement {
|
|
|
78
79
|
object-fit: cover;
|
|
79
80
|
/* Mirror so it feels like a selfie camera */
|
|
80
81
|
transform: scaleX(-1);
|
|
82
|
+
opacity: 0;
|
|
83
|
+
transition: opacity 0.2s ease;
|
|
81
84
|
/* Clip the video to the oval using clip-path on the parent */
|
|
82
85
|
}
|
|
86
|
+
.lv-video.is-playing { opacity: 1; }
|
|
87
|
+
.lv-video::-webkit-media-controls,
|
|
88
|
+
.lv-video::-webkit-media-controls-panel,
|
|
89
|
+
.lv-video::-webkit-media-controls-play-button,
|
|
90
|
+
.lv-video::-webkit-media-controls-start-playback-button,
|
|
91
|
+
.lv-video::-webkit-media-controls-overlay-play-button,
|
|
92
|
+
.lv-video::-webkit-media-controls-enclosure {
|
|
93
|
+
display: none !important;
|
|
94
|
+
-webkit-appearance: none;
|
|
95
|
+
}
|
|
83
96
|
|
|
84
97
|
/* ── Dark overlay with oval cutout ──────────────────────────────────── */
|
|
85
98
|
.lv-overlay {
|
|
@@ -286,6 +299,7 @@ export function startLivenessWithUI(options: StartLivenessOptions): LivenessEngi
|
|
|
286
299
|
video.className = "lv-video";
|
|
287
300
|
video.setAttribute("autoplay", "");
|
|
288
301
|
video.setAttribute("playsinline", "");
|
|
302
|
+
video.setAttribute("webkit-playsinline", "");
|
|
289
303
|
video.setAttribute("muted", "");
|
|
290
304
|
videoBg.appendChild(video);
|
|
291
305
|
root.appendChild(videoBg);
|
|
@@ -390,6 +404,9 @@ export function startLivenessWithUI(options: StartLivenessOptions): LivenessEngi
|
|
|
390
404
|
|
|
391
405
|
function cleanup(): void {
|
|
392
406
|
engine.stop();
|
|
407
|
+
video.removeEventListener("playing", onVideoPlaying);
|
|
408
|
+
video.removeEventListener("pause", onVideoPause);
|
|
409
|
+
video.removeEventListener("waiting", onVideoPause);
|
|
393
410
|
root.remove();
|
|
394
411
|
}
|
|
395
412
|
|
|
@@ -399,6 +416,12 @@ export function startLivenessWithUI(options: StartLivenessOptions): LivenessEngi
|
|
|
399
416
|
...(Object.keys(DEFAULT_SOUND_DATA_URLS).length > 0 ? DEFAULT_SOUND_DATA_URLS : { baseUrl: "audios/" }),
|
|
400
417
|
};
|
|
401
418
|
|
|
419
|
+
const onVideoPlaying = () => video.classList.add("is-playing");
|
|
420
|
+
const onVideoPause = () => video.classList.remove("is-playing");
|
|
421
|
+
video.addEventListener("playing", onVideoPlaying);
|
|
422
|
+
video.addEventListener("pause", onVideoPause);
|
|
423
|
+
video.addEventListener("waiting", onVideoPause);
|
|
424
|
+
|
|
402
425
|
const engine = new LivenessEngine({
|
|
403
426
|
videoElement: video,
|
|
404
427
|
canvasElement: canvas,
|