Spaces:
Running
Running
File size: 2,647 Bytes
31d3580 95e3d2a 15d8696 95e3d2a 31d3580 ec62d48 95e3d2a 31d3580 ec62d48 31d3580 ec62d48 31d3580 ec62d48 31d3580 ec62d48 31d3580 95e3d2a 31d3580 95e3d2a 8899818 0a96402 95e3d2a 31d3580 95e3d2a 31d3580 15d8696 31d3580 15d8696 31d3580 8899818 31d3580 95e3d2a 31d3580 8899818 31d3580 95e3d2a 31d3580 15d8696 31d3580 15d8696 31d3580 ec62d48 31d3580 | 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 | <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>
|