Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 126 127 128 129 | 67x | export function seekEnd(media: HTMLMediaElement): number | undefined { if (isFinite(media.duration)) return media.duration; if (media.seekable.length) return media.seekable.end(media.seekable.length - 1); return undefined; } export function handleMediaKeydown(event: KeyboardEvent, media: HTMLMediaElement) { const isVideo = media instanceof HTMLVideoElement; switch (event.key) { case ' ': case 'k': case 'K': if (event.repeat) return; event.preventDefault(); event.stopPropagation(); if (media.paused) { void media.play().catch(() => {}); } else { media.pause(); } break; case 'j': case 'J': event.preventDefault(); event.stopPropagation(); media.currentTime = Math.max(0, media.currentTime - 10); break; case 'l': case 'L': { event.preventDefault(); event.stopPropagation(); const end = seekEnd(media); if (end !== undefined) media.currentTime = Math.min(end, media.currentTime + 10); break; } case 'ArrowLeft': event.preventDefault(); event.stopPropagation(); media.currentTime = Math.max(0, media.currentTime - 5); break; case 'ArrowRight': { event.preventDefault(); event.stopPropagation(); const end = seekEnd(media); if (end !== undefined) media.currentTime = Math.min(end, media.currentTime + 5); break; } case 'ArrowUp': event.preventDefault(); event.stopPropagation(); media.volume = Math.min(1, media.volume + 0.05); break; case 'ArrowDown': event.preventDefault(); event.stopPropagation(); media.volume = Math.max(0, media.volume - 0.05); break; case 'f': case 'F': if (!isVideo) return; if (event.repeat) return; event.preventDefault(); event.stopPropagation(); if (document.fullscreenElement) { document.exitFullscreen?.().catch(() => {}); } else { (media as HTMLVideoElement).requestFullscreen?.().catch(() => {}); } break; case 'm': case 'M': if (event.repeat) return; event.preventDefault(); event.stopPropagation(); media.muted = !media.muted; break; case ',': if (!isVideo) return; event.preventDefault(); event.stopPropagation(); if (media.paused && 'requestVideoFrameCallback' in media) { (media as any).requestVideoFrameCallback((_: DOMHighResTimeStamp, metadata: any) => { media.currentTime = Math.max(0, metadata.mediaTime - 0.001); }); // Re-assign to trigger a seek, which composites a frame and fires the callback media.currentTime = media.currentTime; } break; case '.': if (!isVideo) return; event.preventDefault(); event.stopPropagation(); if (media.paused && 'requestVideoFrameCallback' in media) { const wasMuted = media.muted; media.muted = true; (media as any).requestVideoFrameCallback(() => { media.pause(); media.muted = wasMuted; }); void media.play().catch(() => {}); } break; case 'Home': event.preventDefault(); event.stopPropagation(); media.currentTime = 0; break; case 'End': { event.preventDefault(); event.stopPropagation(); const end = seekEnd(media); if (end !== undefined) media.currentTime = end; break; } default: if (event.key >= '0' && event.key <= '9') { event.preventDefault(); event.stopPropagation(); const end = seekEnd(media); if (end !== undefined) media.currentTime = end * parseInt(event.key, 10) / 10; } break; } } export function handleVideoKeydown(event: KeyboardEvent, video: HTMLVideoElement) { handleMediaKeydown(event, video); } |