오디오 소스 선택 기능 추가
- AudioCaptureService.getDevices(): PowerShell로 Windows 오디오 디바이스 열거 - SettingsModal 오디오 탭: 마이크 선택 드롭다운 (기본 + 감지된 디바이스) - 선택 변경 시 ConfigService에 저장 → 다음 녹음 시 적용
This commit is contained in:
parent
48582e46ec
commit
438aa1de6c
2 changed files with 72 additions and 13 deletions
|
|
@ -227,15 +227,40 @@ class AudioCaptureService extends EventEmitter {
|
|||
}
|
||||
|
||||
async getDevices(): Promise<AudioDevice[]> {
|
||||
// 현재 Phase에서는 기본 디바이스만 반환. 실제 디바이스 열거는 추후.
|
||||
logger.debug('Getting audio devices (default only)')
|
||||
return [
|
||||
{
|
||||
deviceId: 'default',
|
||||
label: 'Default Microphone',
|
||||
isDefault: true
|
||||
}
|
||||
const devices: AudioDevice[] = [
|
||||
{ deviceId: 'default', label: '시스템 기본 마이크', isDefault: true },
|
||||
]
|
||||
|
||||
try {
|
||||
// PowerShell로 Windows 오디오 입력 디바이스 열거
|
||||
const { execSync } = await import('child_process')
|
||||
const psCommand = `Get-CimInstance Win32_SoundDevice | Where-Object { $_.StatusInfo -eq 3 } | Select-Object -Property DeviceID, Name | ConvertTo-Json -Compress`
|
||||
const output = execSync(`powershell -NoProfile -Command "${psCommand}"`, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
}).trim()
|
||||
|
||||
if (output) {
|
||||
const parsed: unknown = JSON.parse(output.startsWith('[') ? output : `[${output}]`)
|
||||
if (Array.isArray(parsed)) {
|
||||
for (const dev of parsed) {
|
||||
const d = dev as { DeviceID?: string; Name?: string }
|
||||
if (d.DeviceID && d.Name) {
|
||||
devices.push({
|
||||
deviceId: d.DeviceID,
|
||||
label: d.Name,
|
||||
isDefault: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to enumerate audio devices via PowerShell: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
|
||||
logger.debug(`Found ${devices.length} audio device(s)`)
|
||||
return devices
|
||||
}
|
||||
|
||||
getCurrentDevice(): AudioDevice | null {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ import KeyboardIcon from '@mui/icons-material/Keyboard'
|
|||
import EditIcon from '@mui/icons-material/Edit'
|
||||
import { d3roPalette } from '../theme'
|
||||
import { HotkeyRecordModal } from './HotkeyRecordModal'
|
||||
import type { ThemeMode, AppConfig, HotkeyBinding } from '@shared/types'
|
||||
import MicIcon from '@mui/icons-material/Mic'
|
||||
import type { ThemeMode, AppConfig, HotkeyBinding, AudioDevice } from '@shared/types'
|
||||
|
||||
interface SettingsModalProps {
|
||||
open: boolean
|
||||
|
|
@ -183,6 +184,10 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
|
|||
const [hotkeyModalOpen, setHotkeyModalOpen] = useState(false)
|
||||
const [hotkeyModalTarget, setHotkeyModalTarget] = useState<'dictation' | 'handsFree'>('dictation')
|
||||
|
||||
// 오디오 디바이스
|
||||
const [audioDevices, setAudioDevices] = useState<AudioDevice[]>([])
|
||||
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('default')
|
||||
|
||||
// 설정 로드
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
|
@ -193,8 +198,10 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
|
|||
window.electronAPI.hotkey.getDictationShortcut(),
|
||||
window.electronAPI.hotkey.getHandsFreeShortcut(),
|
||||
window.electronAPI.hotkey.isEnabled(),
|
||||
window.electronAPI.audio.getDevices(),
|
||||
window.electronAPI.audio.getSelectedDevice(),
|
||||
])
|
||||
.then(([configResult, dictResult, hfResult, enabledResult]) => {
|
||||
.then(([configResult, dictResult, hfResult, enabledResult, devicesResult, selectedResult]) => {
|
||||
if (configResult.success) setConfig(configResult.data)
|
||||
if (dictResult.success && dictResult.data) setDictationBinding(dictResult.data)
|
||||
if (hfResult.success && hfResult.data) setHandsFreeBinding(hfResult.data)
|
||||
|
|
@ -202,6 +209,8 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
|
|||
setHotkeyGlobalEnabled(enabledResult.data)
|
||||
setDictationEnabled(enabledResult.data)
|
||||
}
|
||||
if (devicesResult.success) setAudioDevices(devicesResult.data)
|
||||
if (selectedResult.success && selectedResult.data) setSelectedDeviceId(selectedResult.data)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [open])
|
||||
|
|
@ -412,9 +421,34 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
|
|||
{/* ── 오디오 탭 ────────────────────────────── */}
|
||||
<TabPanel value={activeTab} index={1}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
현재 시스템 기본 마이크를 사용합니다.
|
||||
마이크 선택 및 테스트 기능은 추후 업데이트에서 제공됩니다.
|
||||
<Typography variant="overline" sx={{ color: d3roPalette.text.label, letterSpacing: '1.5px' }}>
|
||||
마이크
|
||||
</Typography>
|
||||
|
||||
<FormControl size="small">
|
||||
<InputLabel>입력 장치</InputLabel>
|
||||
<Select
|
||||
label="입력 장치"
|
||||
value={selectedDeviceId}
|
||||
onChange={(e) => {
|
||||
const deviceId = e.target.value
|
||||
setSelectedDeviceId(deviceId)
|
||||
window.electronAPI.audio.setSelectedDevice({ deviceId })
|
||||
}}
|
||||
startAdornment={<MicIcon sx={{ color: d3roPalette.text.inactive, mr: 1, fontSize: 18 }} />}
|
||||
>
|
||||
{audioDevices.map((device) => (
|
||||
<MenuItem key={device.deviceId} value={device.deviceId}>
|
||||
{device.label}{device.isDefault ? ' (기본)' : ''}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Divider sx={{ borderColor: d3roPalette.border.subtle }} />
|
||||
|
||||
<Typography variant="overline" sx={{ color: d3roPalette.text.label, letterSpacing: '1.5px' }}>
|
||||
텍스트 삽입
|
||||
</Typography>
|
||||
|
||||
<FormControl size="small">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue