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>