Lukeetah commited on
Commit
8fcabb2
·
verified ·
1 Parent(s): 6164704

Upload 7 files

Browse files
components/ConnectedDeviceCard.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import type { BluetoothDevice, Action } from '../types.ts';
4
+ import { InfoIcon } from './icons.tsx';
5
+
6
+ interface ConnectedDeviceCardProps {
7
+ device: BluetoothDevice;
8
+ dispatch: React.Dispatch<Action>;
9
+ onDisconnect: (id: string) => void;
10
+ }
11
+
12
+ export const ConnectedDeviceCard: React.FC<ConnectedDeviceCardProps> = ({ device, dispatch, onDisconnect }) => {
13
+ const totalDelay = device.latency + device.delay;
14
+
15
+ const handleUpdateVolume = (id: string, volume: number) => {
16
+ dispatch({ type: 'UPDATE_VOLUME', payload: { id, volume } });
17
+ }
18
+ const handleUpdateDelay = (id: string, delay: number) => {
19
+ dispatch({ type: 'UPDATE_DELAY', payload: { id, delay } });
20
+ }
21
+
22
+ const calibrationClass = device.isCalibrating ? 'calibrating-shimmer' : '';
23
+
24
+ return (
25
+ <div className={`bg-slate-700/50 p-4 rounded-lg relative overflow-hidden ${calibrationClass}`}>
26
+ <div className="flex justify-between items-start">
27
+ <div>
28
+ <p className="font-semibold text-white">{device.name}</p>
29
+ <div className="text-xs text-slate-400 space-x-2">
30
+ <span title="Inherent device latency">{`Latency: ${device.latency}ms`}</span>
31
+ <span className="font-bold text-cyan-400" title="Total effective delay after calibration">{`Total Delay: ${totalDelay}ms`}</span>
32
+ </div>
33
+ </div>
34
+ <button
35
+ onClick={() => onDisconnect(device.id)}
36
+ className="text-xs text-red-400 hover:text-red-300 transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 rounded"
37
+ aria-label={`Disconnect ${device.name}`}
38
+ >
39
+ Disconnect
40
+ </button>
41
+ </div>
42
+ <div className="mt-4 space-y-3">
43
+ <div>
44
+ <label htmlFor={`volume-${device.id}`} className="text-xs text-slate-300">Volume: {device.volume}</label>
45
+ <input
46
+ id={`volume-${device.id}`}
47
+ type="range"
48
+ min="0" max="100"
49
+ value={device.volume}
50
+ onChange={(e) => handleUpdateVolume(device.id, parseInt(e.target.value))}
51
+ className="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-blue-500"
52
+ aria-label={`${device.name} volume control`}
53
+ />
54
+ </div>
55
+ <div>
56
+ <label htmlFor={`delay-${device.id}`} className="flex items-center space-x-1 text-xs text-slate-300">
57
+ <span>Manual Delay: {device.delay}ms</span>
58
+ <span title="Manual offset to sync with other devices. Auto-calibrate sets this to compensate for latency.">
59
+ <InfoIcon className="w-3 h-3"/>
60
+ </span>
61
+ </label>
62
+ <input
63
+ id={`delay-${device.id}`}
64
+ type="range"
65
+ min="0" max="100"
66
+ value={device.delay}
67
+ onChange={(e) => handleUpdateDelay(device.id, parseInt(e.target.value))}
68
+ className="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-cyan-500"
69
+ aria-label={`${device.name} manual delay control`}
70
+ />
71
+ </div>
72
+ </div>
73
+ </div>
74
+ );
75
+ }
components/DeviceCard.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import type { BluetoothDevice } from '../types.ts';
4
+ import { DeviceType, DeviceStatus } from '../types.ts';
5
+ import { SpeakerIcon, HeadphonesIcon, SoundbarIcon } from './icons.tsx';
6
+
7
+ interface DeviceCardProps {
8
+ device: BluetoothDevice;
9
+ onConnect: (id: string) => void;
10
+ }
11
+
12
+ const DeviceIcon: React.FC<{ type: DeviceType }> = ({ type }) => {
13
+ const className = "w-8 h-8 text-slate-400 flex-shrink-0";
14
+ if (type === DeviceType.Headphones) return <HeadphonesIcon className={className} />;
15
+ if (type === DeviceType.Soundbar) return <SoundbarIcon className={className} />;
16
+ return <SpeakerIcon className={className} />;
17
+ };
18
+
19
+ const Battery: React.FC<{ level: number }> = ({ level }) => {
20
+ const width = `${level}%`;
21
+ const color = level > 50 ? 'bg-green-500' : level > 20 ? 'bg-yellow-500' : 'bg-red-500';
22
+
23
+ return (
24
+ <div className="w-6 h-3 border border-slate-500 rounded-sm p-0.5 flex items-center" title={`Battery level: ${level}%`}>
25
+ <div className={`h-full rounded-sm ${color}`} style={{ width }}></div>
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export const DeviceCard: React.FC<DeviceCardProps> = ({ device, onConnect }) => {
31
+ const isConnecting = device.status === DeviceStatus.Connecting;
32
+ const connectingClass = isConnecting ? 'animate-pulse ring-2 ring-blue-500 ring-offset-2 ring-offset-slate-800' : '';
33
+
34
+ return (
35
+ <div className={`flex items-center justify-between bg-slate-700/50 p-3 rounded-lg hover:bg-slate-700 transition-all duration-200 ${connectingClass}`}>
36
+ <div className="flex items-center space-x-4 overflow-hidden">
37
+ <DeviceIcon type={device.type} />
38
+ <div className="truncate">
39
+ <p className="font-medium text-white truncate">{device.name}</p>
40
+ <div className="flex items-center space-x-2 text-xs text-slate-400">
41
+ <span>{device.type}</span>
42
+ <span className="text-slate-600">&bull;</span>
43
+ <Battery level={device.battery} />
44
+ <span>{device.battery}%</span>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <button
49
+ onClick={() => onConnect(device.id)}
50
+ disabled={isConnecting}
51
+ className="ml-2 flex-shrink-0 px-3 py-1.5 text-sm font-semibold bg-slate-600 text-slate-200 rounded-md hover:bg-slate-500 disabled:bg-slate-800 disabled:text-slate-500 disabled:cursor-wait transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-700"
52
+ aria-label={isConnecting ? `Connecting to ${device.name}` : `Connect to ${device.name}`}
53
+ >
54
+ {isConnecting ? 'Connecting...' : 'Connect'}
55
+ </button>
56
+ </div>
57
+ );
58
+ };
components/DeviceScanner.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useCallback } from 'react';
3
+ import type { BluetoothDevice, Action } from '../types.ts';
4
+ import { DeviceStatus } from '../types.ts';
5
+ import { DeviceCard } from './DeviceCard.tsx';
6
+ import { ScanIcon } from './icons.tsx';
7
+
8
+ interface DeviceScannerProps {
9
+ devices: BluetoothDevice[];
10
+ onScan: () => void;
11
+ dispatch: React.Dispatch<Action>;
12
+ isScanning: boolean;
13
+ }
14
+
15
+ export const DeviceScanner: React.FC<DeviceScannerProps> = ({ devices, onScan, dispatch, isScanning }) => {
16
+ const handleConnect = useCallback((id: string) => {
17
+ dispatch({ type: 'UPDATE_STATUS', payload: { id, status: DeviceStatus.Connecting } });
18
+ setTimeout(() => {
19
+ const randomLatency = Math.floor(Math.random() * 50) + 5;
20
+ dispatch({ type: 'UPDATE_STATUS', payload: { id, status: DeviceStatus.Connected, latency: randomLatency } });
21
+ }, 1500);
22
+ }, [dispatch]);
23
+
24
+ return (
25
+ <div className="bg-slate-800/50 rounded-lg p-6 shadow-lg h-full flex flex-col">
26
+ <div className="flex justify-between items-center mb-4">
27
+ <h2 className="text-xl font-semibold text-white">Available Devices</h2>
28
+ <button
29
+ onClick={onScan}
30
+ disabled={isScanning}
31
+ className="flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-900"
32
+ aria-label={isScanning ? 'Scanning for devices' : 'Scan for new devices'}
33
+ >
34
+ {isScanning ? (
35
+ <svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
36
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
37
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
38
+ </svg>
39
+ ) : (
40
+ <ScanIcon className="h-5 w-5" />
41
+ )}
42
+ <span>{isScanning ? 'Scanning...' : 'Scan'}</span>
43
+ </button>
44
+ </div>
45
+ <div className="flex-grow overflow-y-auto pr-2 -mr-2 space-y-3">
46
+ {isScanning && devices.length === 0 && (
47
+ <div className="text-center py-10 text-slate-400">
48
+ <p>Searching for nearby devices...</p>
49
+ </div>
50
+ )}
51
+ {!isScanning && devices.length === 0 && (
52
+ <div className="text-center py-10 text-slate-400">
53
+ <p>No devices found. Try scanning again.</p>
54
+ </div>
55
+ )}
56
+ {devices.map(device => (
57
+ <DeviceCard key={device.id} device={device} onConnect={handleConnect} />
58
+ ))}
59
+ </div>
60
+ </div>
61
+ );
62
+ };
components/Footer.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ export const Footer: React.FC = () => {
5
+ return (
6
+ <footer className="container mx-auto p-4 mt-8 text-center text-slate-500 text-sm">
7
+ <p>Bluetooth Stereo Sync v1.0.0</p>
8
+ <p>This is a UI simulation. It does not control actual Bluetooth hardware.</p>
9
+ </footer>
10
+ );
11
+ };
components/Header.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { BluetoothIcon } from './icons.tsx';
4
+
5
+ export const Header: React.FC = () => {
6
+ return (
7
+ <header className="bg-slate-800/50 backdrop-blur-sm border-b border-slate-700/50 sticky top-0 z-10">
8
+ <div className="container mx-auto p-4 flex items-center justify-center space-x-4">
9
+ <BluetoothIcon className="text-blue-400 h-8 w-8" />
10
+ <div>
11
+ <h1 className="text-2xl font-bold text-white tracking-tight">Bluetooth Stereo Sync</h1>
12
+ <p className="text-sm text-slate-400">Combine multiple speakers into one synchronized audio output.</p>
13
+ </div>
14
+ </div>
15
+ </header>
16
+ );
17
+ }
components/SyncDashboard.tsx ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import type { BluetoothDevice, Action } from '../types.ts';
4
+ import { DeviceStatus } from '../types.ts';
5
+ import { ConnectedDeviceCard } from './ConnectedDeviceCard.tsx';
6
+ import { VolumeHighIcon, VolumeMuteIcon } from './icons.tsx';
7
+
8
+ interface SyncDashboardProps {
9
+ connectedDevices: BluetoothDevice[];
10
+ dispatch: React.Dispatch<Action>;
11
+ masterVolume: number;
12
+ isMuted: boolean;
13
+ showNotification: (message: string) => void;
14
+ }
15
+
16
+ const AudioVisualizer: React.FC<{ volume: number, isMuted: boolean }> = ({ volume, isMuted }) => {
17
+ const barCount = 32;
18
+ const [bars, setBars] = useState<number[]>(Array(barCount).fill(2));
19
+
20
+ useEffect(() => {
21
+ const interval = setInterval(() => {
22
+ if (isMuted || volume === 0) {
23
+ setBars(Array(barCount).fill(2));
24
+ return;
25
+ }
26
+ const maxEffect = Math.max(2, (volume / 100) * 98 + 2);
27
+ setBars(bars.map(() => Math.random() * maxEffect + 2));
28
+ }, 150);
29
+ return () => clearInterval(interval);
30
+ }, [bars, volume, isMuted]);
31
+
32
+ return (
33
+ <div className="flex items-end justify-center h-24 space-x-1 bg-slate-900/50 p-4 rounded-lg">
34
+ {bars.map((height, i) => (
35
+ <div
36
+ key={i}
37
+ className="w-full bg-gradient-to-t from-blue-500 to-cyan-400 rounded-t-full"
38
+ style={{ height: `${height}%`, transition: 'height 0.1s ease-in-out' }}
39
+ ></div>
40
+ ))}
41
+ </div>
42
+ );
43
+ };
44
+
45
+ export const SyncDashboard: React.FC<SyncDashboardProps> = ({ connectedDevices, dispatch, masterVolume, isMuted, showNotification }) => {
46
+ const [isCalibrating, setIsCalibrating] = useState(false);
47
+
48
+ const handleAutoCalibrate = () => {
49
+ setIsCalibrating(true);
50
+ dispatch({ type: 'START_CALIBRATION' });
51
+
52
+ const maxLatency = Math.max(...connectedDevices.map(d => d.latency), 0);
53
+
54
+ const calibrationPromises = connectedDevices.map((device, index) => {
55
+ return new Promise<void>(resolve => {
56
+ setTimeout(() => {
57
+ const delay = maxLatency - device.latency;
58
+ dispatch({ type: 'UPDATE_DELAY', payload: { id: device.id, delay } });
59
+ resolve();
60
+ }, (index + 1) * 200); // Faster calibration
61
+ });
62
+ });
63
+
64
+ Promise.all(calibrationPromises).then(() => {
65
+ setTimeout(() => {
66
+ dispatch({ type: 'FINISH_CALIBRATION' });
67
+ setIsCalibrating(false);
68
+ showNotification("Calibration complete! Devices are now in sync.");
69
+ }, 500);
70
+ });
71
+ };
72
+
73
+ const handleDisconnect = (id: string) => {
74
+ dispatch({ type: 'UPDATE_STATUS', payload: {id, status: DeviceStatus.Disconnected } });
75
+ };
76
+
77
+ if (connectedDevices.length === 0) {
78
+ return (
79
+ <div className="bg-slate-800/50 rounded-lg p-6 shadow-lg h-full flex items-center justify-center min-h-[300px]">
80
+ <div className="text-center text-slate-400">
81
+ <h2 className="text-xl font-semibold mb-2 text-white">Synchronized Group</h2>
82
+ <p>Connect devices from the 'Available Devices' list to create a stereo group.</p>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <div className="bg-slate-800/50 rounded-lg p-6 shadow-lg h-full space-y-6">
90
+ <div>
91
+ <div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4 mb-4">
92
+ <h2 className="text-xl font-semibold text-white">Synchronized Group ({connectedDevices.length})</h2>
93
+ <button
94
+ onClick={handleAutoCalibrate}
95
+ disabled={isCalibrating}
96
+ className="px-4 py-2 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 disabled:bg-slate-600 disabled:cursor-not-allowed transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-slate-800"
97
+ aria-label={isCalibrating ? 'Calibrating device delays' : 'Auto-calibrate device delays'}
98
+ >
99
+ {isCalibrating ? 'Calibrating...' : 'Auto-Calibrate Delays'}
100
+ </button>
101
+ </div>
102
+
103
+ <div className="space-y-4">
104
+ <div className="bg-slate-900/50 p-4 rounded-lg">
105
+ <label className="text-sm font-medium text-white">Master Controls</label>
106
+ <div className="flex items-center space-x-4 mt-2">
107
+ <button onClick={() => dispatch({ type: 'TOGGLE_MUTE' })} className="p-2 rounded-full hover:bg-slate-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label={isMuted ? 'Unmute master volume' : 'Mute master volume'}>
108
+ {isMuted ? <VolumeMuteIcon className="h-6 w-6 text-slate-300" /> : <VolumeHighIcon className="h-6 w-6 text-slate-300" />}
109
+ </button>
110
+ <input
111
+ type="range"
112
+ min="0"
113
+ max="100"
114
+ value={isMuted ? 0 : masterVolume}
115
+ onChange={(e) => dispatch({ type: 'SET_MASTER_VOLUME', payload: parseInt(e.target.value) })}
116
+ className="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-blue-500"
117
+ aria-label="Master volume control"
118
+ />
119
+ </div>
120
+ </div>
121
+ <AudioVisualizer volume={masterVolume} isMuted={isMuted} />
122
+ </div>
123
+ </div>
124
+
125
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
126
+ {connectedDevices.map(device => (
127
+ <ConnectedDeviceCard
128
+ key={device.id}
129
+ device={device}
130
+ dispatch={dispatch}
131
+ onDisconnect={handleDisconnect}
132
+ />
133
+ ))}
134
+ </div>
135
+ </div>
136
+ );
137
+ };
components/icons.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+
4
+ type IconProps = React.SVGProps<SVGSVGElement>;
5
+
6
+ export const BluetoothIcon: React.FC<IconProps> = (props) => (
7
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
8
+ <path strokeLinecap="round" strokeLinejoin="round" d="M17.293 4.293a1 1 0 011.414 0l2 2a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7a1 1 0 010-1.414l2-2a1 1 0 011.414 0L12 7.586l5.293-3.293zM12 20v-8" />
9
+ </svg>
10
+ );
11
+
12
+ export const ScanIcon: React.FC<IconProps> = (props) => (
13
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
14
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
15
+ </svg>
16
+ );
17
+
18
+ export const SpeakerIcon: React.FC<IconProps> = (props) => (
19
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
20
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" />
21
+ </svg>
22
+ );
23
+
24
+ export const HeadphonesIcon: React.FC<IconProps> = (props) => (
25
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
26
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15.75 8.25v7.5" />
27
+ <path strokeLinecap="round" strokeLinejoin="round" d="M9 16.5v-2.25m3.75 2.25v-2.25m-7.5 0v2.25m7.5-6.75v-2.25m-3.75 2.25v-2.25m-3.75 0v2.25M9 12a3 3 0 11-6 0 3 3 0 016 0zm9 0a3 3 0 11-6 0 3 3 0 016 0z" />
28
+ </svg>
29
+ );
30
+
31
+ export const SoundbarIcon: React.FC<IconProps> = (props) => (
32
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
33
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 18.75v-3.75m16.5 3.75v-3.75m-14.25-3.75h12c.966 0 1.75-.784 1.75-1.75V8.25c0-.966-.784-1.75-1.75-1.75h-12c-.966 0-1.75.784-1.75 1.75v3.25c0 .966.784 1.75 1.75 1.75z" />
34
+ </svg>
35
+ );
36
+
37
+ export const InfoIcon: React.FC<IconProps> = (props) => (
38
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
39
+ <path strokeLinecap="round" strokeLinejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
40
+ </svg>
41
+ );
42
+
43
+ export const VolumeHighIcon: React.FC<IconProps> = (props) => (
44
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
45
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" />
46
+ </svg>
47
+ );
48
+
49
+ export const VolumeMuteIcon: React.FC<IconProps> = (props) => (
50
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
51
+ <path strokeLinecap="round" strokeLinejoin="round" d="M17.25 9.75L19.5 12m0 0l2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" />
52
+ </svg>
53
+ );