91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
|
|
/**
|
||
|
|
* AudioSettingsPanel — v0.13.1 TASK-AUDIO-002 + TASK-AUDIO-003
|
||
|
|
*
|
||
|
|
* Configurable crossfade (0-12s) and audio normalization toggle.
|
||
|
|
* Appears in the expanded player and settings.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { usePlayerStore } from '../store/playerStore';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { Slider } from '@/components/ui/slider';
|
||
|
|
import { Tooltip } from '@/components/ui/tooltip';
|
||
|
|
import { Volume2, AudioWaveform } from 'lucide-react';
|
||
|
|
|
||
|
|
interface AudioSettingsPanelProps {
|
||
|
|
className?: string;
|
||
|
|
compact?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function AudioSettingsPanel({ className, compact = false }: AudioSettingsPanelProps) {
|
||
|
|
const crossfadeSeconds = usePlayerStore((s) => s.crossfadeSeconds);
|
||
|
|
const setCrossfadeSeconds = usePlayerStore((s) => s.setCrossfadeSeconds);
|
||
|
|
const normalizationEnabled = usePlayerStore((s) => s.normalizationEnabled);
|
||
|
|
const setNormalizationEnabled = usePlayerStore((s) => s.setNormalizationEnabled);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={cn('flex flex-col gap-4', className)}>
|
||
|
|
{/* Crossfade control */}
|
||
|
|
<div className="flex flex-col gap-2">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<label className="flex items-center gap-2 text-sm font-medium text-foreground">
|
||
|
|
<AudioWaveform className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
||
|
|
Crossfade
|
||
|
|
</label>
|
||
|
|
<span className="text-xs text-muted-foreground font-mono">
|
||
|
|
{crossfadeSeconds === 0 ? 'Off' : `${crossfadeSeconds}s`}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<Slider
|
||
|
|
value={[crossfadeSeconds]}
|
||
|
|
onValueChange={(val) => setCrossfadeSeconds(val[0] ?? 0)}
|
||
|
|
min={0}
|
||
|
|
max={12}
|
||
|
|
step={1}
|
||
|
|
aria-label="Crossfade duration"
|
||
|
|
className={compact ? 'w-32' : undefined}
|
||
|
|
/>
|
||
|
|
{!compact && (
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
Smooth transition between tracks. Set to 0 for gapless playback.
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Normalization toggle */}
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<label
|
||
|
|
htmlFor="normalization-toggle"
|
||
|
|
className="flex items-center gap-2 text-sm font-medium text-foreground cursor-pointer"
|
||
|
|
>
|
||
|
|
<Volume2 className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
||
|
|
Volume normalization
|
||
|
|
</label>
|
||
|
|
<Tooltip content="Equalizes volume levels between tracks to prevent sudden jumps">
|
||
|
|
<button
|
||
|
|
id="normalization-toggle"
|
||
|
|
role="switch"
|
||
|
|
aria-checked={normalizationEnabled}
|
||
|
|
onClick={() => setNormalizationEnabled(!normalizationEnabled)}
|
||
|
|
className={cn(
|
||
|
|
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-background',
|
||
|
|
normalizationEnabled ? 'bg-primary' : 'bg-muted',
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<span
|
||
|
|
className={cn(
|
||
|
|
'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
|
||
|
|
normalizationEnabled ? 'translate-x-6' : 'translate-x-1',
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
</button>
|
||
|
|
</Tooltip>
|
||
|
|
</div>
|
||
|
|
{!compact && (
|
||
|
|
<p className="text-xs text-muted-foreground -mt-2">
|
||
|
|
Adjusts volume to consistent levels across all tracks (EBU R128).
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|