opencs2-dataset-viewer / src /lib /components /timeline-bar.svelte
blanchon's picture
Video player: cycle-through playback speed control (0.5/1/1.5/2)
ec62d48
<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>