Spaces:
Running
Running
| <script lang="ts"> | |
| import { Button } from '$lib/components/ui/button'; | |
| import type { BufferedRange } from '$lib/types'; | |
| import PlayIcon from 'phosphor-svelte/lib/PlayIcon'; | |
| import PauseIcon from 'phosphor-svelte/lib/PauseIcon'; | |
| import SpeakerHighIcon from 'phosphor-svelte/lib/SpeakerHighIcon'; | |
| import SpeakerSlashIcon from 'phosphor-svelte/lib/SpeakerSlashIcon'; | |
| import { cn } from '$lib/utils'; | |
| import { formatDuration } from '$lib/utils/format'; | |
| import Playbar from './video-player/playbar.svelte'; | |
| interface Props { | |
| currentTime: number; | |
| duration: number; | |
| paused: boolean; | |
| muted: boolean; | |
| playbackRate?: number; | |
| bufferedRanges?: BufferedRange[]; | |
| disabled?: boolean; | |
| onTogglePlay: () => void; | |
| onToggleMute: () => void; | |
| onCyclePlaybackRate?: () => void; | |
| onSeek: (t: number) => void; | |
| onScrubStart?: () => void; | |
| onScrubEnd?: () => void; | |
| } | |
| let { | |
| currentTime, | |
| duration, | |
| paused, | |
| muted, | |
| playbackRate = 1, | |
| bufferedRanges = [], | |
| disabled = false, | |
| onTogglePlay, | |
| onToggleMute, | |
| onCyclePlaybackRate, | |
| onSeek, | |
| onScrubStart, | |
| onScrubEnd | |
| }: Props = $props(); | |
| const rateLabel = $derived( | |
| Number.isInteger(playbackRate) ? `${playbackRate}×` : `${playbackRate.toFixed(1)}×` | |
| ); | |
| </script> | |
| <div | |
| data-disabled={disabled || undefined} | |
| aria-disabled={disabled} | |
| class={cn( | |
| 'flex items-center gap-3 rounded-lg border bg-card px-3 py-2 shadow-sm', | |
| 'data-disabled:pointer-events-none data-disabled:opacity-60' | |
| )} | |
| > | |
| <Button | |
| variant="ghost" | |
| size="icon-sm" | |
| {disabled} | |
| onclick={onTogglePlay} | |
| aria-label={paused ? 'Play' : 'Pause'} | |
| > | |
| {#if paused} | |
| <PlayIcon weight="fill" /> | |
| {:else} | |
| <PauseIcon weight="fill" /> | |
| {/if} | |
| </Button> | |
| <span class="w-12 text-right font-mono text-[11px] text-foreground tabular-nums"> | |
| {formatDuration(currentTime)} | |
| </span> | |
| <div class="flex-1"> | |
| <Playbar {currentTime} {duration} {bufferedRanges} {onSeek} {onScrubStart} {onScrubEnd} /> | |
| </div> | |
| <span class="w-12 font-mono text-[11px] text-muted-foreground tabular-nums"> | |
| {formatDuration(duration)} | |
| </span> | |
| <Button | |
| variant="ghost" | |
| size="icon-sm" | |
| {disabled} | |
| onclick={onToggleMute} | |
| aria-label={muted ? 'Unmute' : 'Mute'} | |
| > | |
| {#if muted} | |
| <SpeakerSlashIcon weight="duotone" /> | |
| {:else} | |
| <SpeakerHighIcon weight="duotone" /> | |
| {/if} | |
| </Button> | |
| {#if onCyclePlaybackRate} | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| {disabled} | |
| onclick={onCyclePlaybackRate} | |
| aria-label={`Playback speed ${rateLabel} — click to change`} | |
| title={`Playback speed (${rateLabel})`} | |
| class="h-6 w-11 px-0 font-mono text-[11px] tabular-nums" | |
| > | |
| {rateLabel} | |
| </Button> | |
| {/if} | |
| </div> | |