"use client"; import { useCallback, useMemo, useState } from "react"; import { MicIcon, StopIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { RefreshCw } from "lucide-react"; import { toast } from "sonner"; import { invokeAction } from "@/actions"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/utils/ui"; import { DEFAULT_AUDIO_INPUT_DEVICE_ID, listVoiceOverInputDevices, normalizeSelectedDeviceId, type VoiceOverInputDevice, } from "@/voiceover/devices"; import { VOICE_OVER_ENHANCEMENT_LABELS, type VoiceOverEnhancementSettings, } from "@/voiceover/enhancements"; import { VoiceOverLiveWaveform } from "@/voiceover/live-waveform"; import { formatElapsedSeconds, getVoiceOverErrorMessage, } from "@/voiceover/recorder"; import { useRecordingElapsedSeconds } from "@/voiceover/use-recording-elapsed"; import { useVoiceOverPreferencesStore, useVoiceOverStore, } from "@/voiceover/voiceover-store"; const ENHANCEMENT_ORDER: Array = [ "voiceBoost", "noiseGate", "highPass", "deEss", "limiter", "normalize", ]; export function VoiceOverRecorderPopover() { const [open, setOpen] = useState(false); const [devices, setDevices] = useState([]); const [isLoadingDevices, setIsLoadingDevices] = useState(false); const status = useVoiceOverStore((state) => state.status); const isRecording = status === "recording"; const isProcessing = status === "processing"; const elapsedSeconds = useRecordingElapsedSeconds(); const selectedDeviceId = useVoiceOverPreferencesStore( (state) => state.selectedDeviceId, ); const enhancements = useVoiceOverPreferencesStore( (state) => state.enhancements, ); const setSelectedDeviceId = useVoiceOverPreferencesStore( (state) => state.setSelectedDeviceId, ); const setEnhancementEnabled = useVoiceOverPreferencesStore( (state) => state.setEnhancementEnabled, ); const devicesWithDefault = useMemo(() => { if ( devices.some( (device) => device.deviceId === DEFAULT_AUDIO_INPUT_DEVICE_ID, ) ) { return devices; } return [ { deviceId: DEFAULT_AUDIO_INPUT_DEVICE_ID, label: "System default microphone", isDefault: true, }, ...devices, ]; }, [devices]); const selectedValue = selectedDeviceId ?? DEFAULT_AUDIO_INPUT_DEVICE_ID; const refreshDevices = useCallback( async ({ requestPermission }: { requestPermission: boolean }) => { setIsLoadingDevices(true); try { const nextDevices = await listVoiceOverInputDevices({ requestPermission, }); setDevices(nextDevices); const normalizedDeviceId = normalizeSelectedDeviceId({ deviceId: selectedDeviceId, devices: nextDevices, }); if (normalizedDeviceId !== selectedDeviceId) { setSelectedDeviceId({ deviceId: normalizedDeviceId }); } } catch (error) { toast.error(getVoiceOverErrorMessage({ error })); } finally { setIsLoadingDevices(false); } }, [selectedDeviceId, setSelectedDeviceId], ); const handleOpenChange = useCallback( (nextOpen: boolean) => { setOpen(nextOpen); if (nextOpen) { void refreshDevices({ requestPermission: true }); } }, [refreshDevices], ); const handleRecordClick = (event: React.MouseEvent) => { event.stopPropagation(); void invokeAction("voice-over"); }; return ( {isProcessing ? "Saving voice-over" : isRecording ? "Stop voice-over" : "Record voice-over"} event.stopPropagation()} >
Voice-over
{isRecording ? formatElapsedSeconds({ seconds: elapsedSeconds }) : isProcessing ? "Saving" : "Ready"}
Input
Enhancements
{ENHANCEMENT_ORDER.map((id) => { const option = VOICE_OVER_ENHANCEMENT_LABELS[id]; return (
{option.label} {option.description} setEnhancementEnabled({ id, enabled }) } />
); })}
); }