@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 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: 12, // degrees of YAW change needed from rest
113
+ yawTurnDelta: 9, // degrees of YAW change needed from rest
114
114
  yawWrongDirDelta: 16, // block if turned clearly the WRONG way
115
- headTurnHoldMs: 120, // sustain the turned pose for this long
115
+ headTurnHoldMs: 80, // sustain the turned pose for this long
116
116
  // ── Nod (relative to baseline) ────────────────────────────────────────────
117
- nodDownDelta: 8, // chin must DROP by this many degrees from baseline
118
- nodReturnFraction: 0.40, // return to 40% of peak nod depth to complete
119
- nodReturnMaxDelta: 5, // cap: never require returning past 5° from baseline
120
- maxYawDuringNod: 22,
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.28, // jawOpen blendshape
131
- mouthOpenMarThreshold: 0.28,
132
- mouthHoldMs: 120,
133
- maxYawDuringMouth: 25,
134
- maxPitchDuringMouth: 25,
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
- --oval-w: min(72vmin, ${OVAL_W}px);
45
- --oval-h: min(96vmin, ${OVAL_H}px);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daboss2003/liveness-web",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Web liveness detection using MediaPipe Face Landmarker (CDN)",
5
5
  "keywords": [
6
6
  "liveness",
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: 12, // degrees of YAW change needed from rest
191
+ yawTurnDelta: 9, // degrees of YAW change needed from rest
192
192
  yawWrongDirDelta: 16, // block if turned clearly the WRONG way
193
- headTurnHoldMs: 120, // sustain the turned pose for this long
193
+ headTurnHoldMs: 80, // sustain the turned pose for this long
194
194
 
195
195
  // ── Nod (relative to baseline) ────────────────────────────────────────────
196
- nodDownDelta: 8, // chin must DROP by this many degrees from baseline
197
- nodReturnFraction: 0.40, // return to 40% of peak nod depth to complete
198
- nodReturnMaxDelta: 5, // cap: never require returning past 5° from baseline
199
- maxYawDuringNod: 22,
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.28, // jawOpen blendshape
212
- mouthOpenMarThreshold: 0.28,
213
- mouthHoldMs: 120,
214
- maxYawDuringMouth: 25,
215
- maxPitchDuringMouth: 25,
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
- --oval-w: min(72vmin, ${OVAL_W}px);
60
- --oval-h: min(96vmin, ${OVAL_H}px);
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,