File size: 1,981 Bytes
2f31a74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
(async () => {
  const meta = await (await fetch("/slide_meta")).json();
  const slides = meta.slides;
  let current = 0;

  const slideImg = document.getElementById("slide");
  const count = document.getElementById("count");

  function show(i) {
    if (slides.length === 0) {
      count.innerText = "No slides in /slides folder";
      return;
    }
    current = Math.max(0, Math.min(slides.length - 1, i));
    slideImg.src = "/slides/" + slides[current];
    count.innerText = `${current + 1} / ${slides.length}`;
  }

  show(0);

  // Webcam + Mediapipe
  const video = document.getElementById("webcam");
  const canvas = document.getElementById("overlay");
  const ctx = canvas.getContext("2d");

  let lastX = null, lastTime = null, cooldown = 0;

  function onResults(res) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    if (res.multiHandLandmarks && res.multiHandLandmarks.length > 0) {
      const lm = res.multiHandLandmarks[0];
      window.drawLandmarks(ctx, lm);

      const x = lm[0].x;
      const now = performance.now();

      if (lastX !== null) {
        const dx = x - lastX;
        const dt = (now - lastTime) / 1000;
        const speed = dx / dt;

        if (now > cooldown) {
          if (speed > 1.3) {
            show(current + 1);      // Next slide
            cooldown = now + 800;
          }
          if (speed < -1.3) {
            show(current - 1);      // Previous slide
            cooldown = now + 800;
          }
        }
      }

      lastX = x;
      lastTime = now;
    }
  }

  const hands = new Hands({
    locateFile: f => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${f}`
  });

  hands.setOptions({
    maxNumHands: 1,
    modelComplexity: 0,
    minDetectionConfidence: 0.6,
    minTrackingConfidence: 0.6
  });

  hands.onResults(onResults);

  const camera = new Camera(video, {
    onFrame: async () => await hands.send({ image: video }),
    width: 320,
    height: 240
  });
  camera.start();
})();