File size: 3,478 Bytes
fb7e94b
 
 
 
 
 
 
 
 
765c156
fb7e94b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a3a16d
fb7e94b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
---
interface Props {
  src: string;
}
const { src } = Astro.props;
const id = `video-${Math.random().toString(36).slice(2, 9)}`;
---

<div class="video-player" data-video-player={id}>
  <video id={id} src={src} controls muted preload="auto" playsinline style="width:100%; border-radius: 8px; display: block; background: #000;" />
  <div class="speed-controls">
    <span class="speed-label">Speed:</span>
    <button class="speed-btn active" data-speed="1">1x</button>
    <button class="speed-btn" data-speed="2">2x</button>
    <button class="speed-btn" data-speed="4">4x</button>
    <button class="speed-btn" data-speed="8">8x</button>
    <button class="speed-btn" data-speed="16">16x</button>
  </div>
</div>

<script>
  document.querySelectorAll<HTMLElement>('[data-video-player]').forEach(player => {
    const videoId = player.dataset.videoPlayer!;
    const video = document.getElementById(videoId) as HTMLVideoElement;
    if (!video) return;

    let speed = 1;
    let rafId: number | null = null;
    let lastTime: number | null = null;
    let seeking = false;

    function stopSeekLoop() {
      if (rafId !== null) {
        cancelAnimationFrame(rafId);
        rafId = null;
      }
      lastTime = null;
      seeking = false;
    }

    function startSeekLoop() {
      stopSeekLoop();
      if (speed <= 2) return;

      seeking = true;
      video.pause();
      lastTime = performance.now();

      function step(now: number) {
        if (!seeking || lastTime === null) return;
        const dt = (now - lastTime) / 1000;
        lastTime = now;
        video.currentTime = Math.min(video.currentTime + dt * speed, video.duration);
        if (video.currentTime >= video.duration) {
          stopSeekLoop();
          return;
        }
        rafId = requestAnimationFrame(step);
      }
      rafId = requestAnimationFrame(step);
    }

    player.querySelectorAll<HTMLButtonElement>('.speed-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        speed = parseFloat(btn.dataset.speed || '1');
        player.querySelectorAll('.speed-btn').forEach(b => b.classList.remove('active'));
        btn.classList.add('active');

        if (speed <= 2) {
          stopSeekLoop();
          video.playbackRate = speed;
          if (video.paused && video.currentTime < video.duration) video.play();
        } else {
          video.playbackRate = 1;
          startSeekLoop();
        }
      });
    });

    video.addEventListener('play', () => {
      if (speed > 2) startSeekLoop();
    });

    video.addEventListener('pause', () => {
      if (speed > 2 && !seeking) stopSeekLoop();
    });
  });
</script>

<style>
  .video-player {
    position: relative;
  }
  .speed-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    margin-bottom: 4px;
  }
  .speed-label {
    font-size: 0.8rem;
    color: var(--text-color-secondary, #888);
    margin-right: 2px;
  }
  .speed-btn {
    font-size: 0.75rem;
    padding: 3px 10px;
    border-radius: 4px;
    border: 1px solid var(--border-color, #ddd);
    background: var(--surface-bg, #f5f5f5);
    color: var(--text-color, #333);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
  }
  .speed-btn:hover {
    border-color: var(--text-color-secondary, #888);
  }
  .speed-btn.active {
    background: var(--text-color, #333);
    color: var(--surface-bg, #fff);
    border-color: var(--text-color, #333);
  }
</style>