118 lines
4.2 KiB
TypeScript
118 lines
4.2 KiB
TypeScript
// src/main/ipc/audio-handlers.ts
|
|
|
|
import { ipcMain } from 'electron'
|
|
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
|
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
|
|
import { getAudioCaptureService, calculateRMS, type CaptureLease } from '../services/AudioCaptureService'
|
|
import { getMainWindow } from '../windows/WindowManager'
|
|
import { configGet, configSet } from '../services/ConfigService'
|
|
import type { SetDeviceParams } from '@d3ro/core/types'
|
|
|
|
let testTimer: ReturnType<typeof setTimeout> | null = null
|
|
let testAudioHandler: ((payload: { buffer: Buffer; timestamp: number }) => void) | null = null
|
|
let testLevelInterval: ReturnType<typeof setInterval> | null = null
|
|
let testLastRms = 0
|
|
/**
|
|
* 테스트가 실제로 잡은 캡처 참조. 테스트가 돌고 있지 않을 때 stop() 을 부르면 회의·자막·받아쓰기가
|
|
* 잡고 있는 참조를 빼앗아 그쪽 마이크가 조용히 끊긴다 — 테스트는 자기 임대만 놓는다.
|
|
*/
|
|
let testLease: CaptureLease | null = null
|
|
|
|
function stopAudioTest(): void {
|
|
const service = getAudioCaptureService()
|
|
if (testAudioHandler) {
|
|
service.off('audio-data', testAudioHandler)
|
|
testAudioHandler = null
|
|
}
|
|
if (testLevelInterval) {
|
|
clearInterval(testLevelInterval)
|
|
testLevelInterval = null
|
|
}
|
|
if (testTimer) {
|
|
clearTimeout(testTimer)
|
|
testTimer = null
|
|
}
|
|
testLastRms = 0
|
|
const lease = testLease
|
|
testLease = null
|
|
lease?.release().catch(() => { /* ignore */ })
|
|
}
|
|
|
|
export function registerAudioHandlers(): void {
|
|
ipcMain.handle(IPC_CHANNELS.AUDIO.GET_DEVICES, async () => {
|
|
try {
|
|
const devices = await getAudioCaptureService().getDevices()
|
|
return ipcSuccess(devices)
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
return ipcError(ErrorCode.AudioDeviceNotFound, `Failed to enumerate audio devices: ${message}`)
|
|
}
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.AUDIO.GET_SELECTED_DEVICE, async () => {
|
|
return ipcSuccess(configGet('selectedDeviceId'))
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.AUDIO.SET_SELECTED_DEVICE, async (_event, params: SetDeviceParams) => {
|
|
try {
|
|
configSet('selectedDeviceId', params.deviceId)
|
|
return ipcSuccess(undefined)
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
return ipcError(ErrorCode.AudioDeviceNotFound, `Failed to set device: ${message}`)
|
|
}
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.AUDIO.TEST_DEVICE, async () => {
|
|
try {
|
|
// 이전 테스트가 진행 중이면 정리
|
|
stopAudioTest()
|
|
|
|
const service = getAudioCaptureService()
|
|
testLease = await service.acquire()
|
|
|
|
// 오디오 데이터에서 RMS 추적
|
|
testAudioHandler = (payload: { buffer: Buffer; timestamp: number }) => {
|
|
testLastRms = calculateRMS(payload.buffer)
|
|
}
|
|
service.on('audio-data', testAudioHandler)
|
|
|
|
// 100ms 간격으로 레벨을 렌더러에 전송.
|
|
// level 0은 "무음/초기화 중"일 뿐 종료가 아님 — 종료는 done:true로만 알린다.
|
|
testLevelInterval = setInterval(() => {
|
|
const level = testLastRms > 0 ? Math.min(1.0, Math.pow(testLastRms, 0.28)) : 0
|
|
const win = getMainWindow()
|
|
if (win && !win.isDestroyed()) {
|
|
win.webContents.send(IPC_CHANNELS.AUDIO.TEST_LEVEL, { level, done: false })
|
|
}
|
|
}, 100)
|
|
|
|
// 5초 후 자동 중지
|
|
testTimer = setTimeout(() => {
|
|
stopAudioTest()
|
|
const win = getMainWindow()
|
|
if (win && !win.isDestroyed()) {
|
|
win.webContents.send(IPC_CHANNELS.AUDIO.TEST_LEVEL, { level: 0, done: true })
|
|
}
|
|
}, 5000)
|
|
|
|
return ipcSuccess({ averageLevel: 0, peakLevel: 0, hasAudio: true })
|
|
} catch (err) {
|
|
stopAudioTest()
|
|
return ipcError(
|
|
ErrorCode.AudioDeviceNotFound,
|
|
`Audio test failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
// 테스트 수동 중지 (STOP 버튼)
|
|
ipcMain.handle(IPC_CHANNELS.AUDIO.STOP_TEST, async () => {
|
|
stopAudioTest()
|
|
const win = getMainWindow()
|
|
if (win && !win.isDestroyed()) {
|
|
win.webContents.send(IPC_CHANNELS.AUDIO.TEST_LEVEL, { level: 0, done: true })
|
|
}
|
|
return ipcSuccess(undefined)
|
|
})
|
|
}
|