diff options
Diffstat (limited to 'src/lib/components/VideoPlayer.svelte')
| -rw-r--r-- | src/lib/components/VideoPlayer.svelte | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/src/lib/components/VideoPlayer.svelte b/src/lib/components/VideoPlayer.svelte new file mode 100644 index 0000000..0829c8b --- /dev/null +++ b/src/lib/components/VideoPlayer.svelte @@ -0,0 +1,185 @@ +<script lang="ts"> + import type { VideoFormat } from '$lib/api/youtube'; + + interface Props { + formats: VideoFormat[]; + title: string; + } + + let { formats, title }: Props = $props(); + + let videoElement: HTMLVideoElement | undefined = $state(); + let currentQuality = $state(''); + + const sortedFormats = $derived( + [...formats] + .filter(f => f.url && f.height) + .sort((a, b) => (b.height || 0) - (a.height || 0)) + ); + + const selectedFormat = $derived( + currentQuality + ? sortedFormats.find(f => f.qualityLabel === currentQuality) || sortedFormats[0] + : sortedFormats[0] + ); + + function handleQualityChange(e: Event) { + const select = e.target as HTMLSelectElement; + const newQuality = select.value; + const currentTime = videoElement?.currentTime || 0; + const wasPlaying = videoElement && !videoElement.paused; + + currentQuality = newQuality; + + if (videoElement) { + videoElement.load(); + videoElement.currentTime = currentTime; + if (wasPlaying) { + videoElement.play(); + } + } + } + + function handleKeydown(e: KeyboardEvent) { + if (!videoElement) return; + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; + + switch (e.key) { + case ' ': + case 'k': + e.preventDefault(); + if (videoElement.paused) { + videoElement.play(); + } else { + videoElement.pause(); + } + break; + case 'f': + e.preventDefault(); + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + videoElement.requestFullscreen(); + } + break; + case 'm': + e.preventDefault(); + videoElement.muted = !videoElement.muted; + break; + case 'ArrowLeft': + e.preventDefault(); + videoElement.currentTime -= 5; + break; + case 'ArrowRight': + e.preventDefault(); + videoElement.currentTime += 5; + break; + case 'ArrowUp': + e.preventDefault(); + videoElement.volume = Math.min(1, videoElement.volume + 0.1); + break; + case 'ArrowDown': + e.preventDefault(); + videoElement.volume = Math.max(0, videoElement.volume - 0.1); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + e.preventDefault(); + videoElement.currentTime = (parseInt(e.key) / 10) * videoElement.duration; + break; + } + } + + function getMimeType(ext: string): string { + const types: Record<string, string> = { + mp4: 'video/mp4', + webm: 'video/webm', + mkv: 'video/x-matroska' + }; + return types[ext] || 'video/mp4'; + } +</script> + +<svelte:window onkeydown={handleKeydown} /> + +<div class="player-container"> + {#if selectedFormat} + <video + bind:this={videoElement} + controls + autoplay + {title} + > + <source src={selectedFormat.url} type={getMimeType(selectedFormat.ext)} /> + Your browser does not support the video tag. + </video> + {:else} + <div class="no-video"> + <p>No playable video format found</p> + </div> + {/if} + + {#if sortedFormats.length > 1} + <div class="quality-selector"> + <label for="quality">Quality:</label> + <select id="quality" onchange={handleQualityChange} value={selectedFormat?.qualityLabel}> + {#each sortedFormats as format} + <option value={format.qualityLabel}>{format.qualityLabel || format.height + 'p'}</option> + {/each} + </select> + </div> + {/if} +</div> + +<style> + .player-container { + width: 100%; + background: #000; + border-radius: 8px; + overflow: hidden; + } + + video { + width: 100%; + aspect-ratio: 16 / 9; + display: block; + } + + .no-video { + aspect-ratio: 16 / 9; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + } + + .quality-selector { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background: var(--bg-secondary); + } + + .quality-selector label { + font-size: 0.9rem; + color: var(--text-secondary); + } + + .quality-selector select { + padding: 0.25rem 0.5rem; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.9rem; + } +</style> |
