// src/main/ipc/caption-handlers.ts // Phase 10.1: Live Caption IPC 핸들러 import { ipcMain, session, desktopCapturer } from 'electron' import { IPC_CHANNELS } from '@d3ro/core/ipc-channels' import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors' import { getCaptionService } from '../services/CaptionService' import type { CaptionConfig } from '@d3ro/core/types' import { endCaptionOverlayDrag, resetCaptionOverlayPosition, setCaptionOverlayInteractive, startCaptionOverlayDrag } from '../windows/WindowManager' export function registerCaptionHandlers(): void { // ── 자막 창 손잡이 (팝업 → 메인) ── ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.SET_INTERACTIVE, (_event, interactive: unknown) => { setCaptionOverlayInteractive(interactive === true) }) ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.DRAG_START, () => startCaptionOverlayDrag()) ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.DRAG_END, () => endCaptionOverlayDrag()) ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.RESET_POSITION, () => resetCaptionOverlayPosition()) // 시스템 오디오 루프백: setDisplayMediaRequestHandler로 audio: 'loopback' 설정 ipcMain.handle(IPC_CHANNELS.SYSTEM_AUDIO.ENABLE_LOOPBACK, async () => { session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => { const sources = await desktopCapturer.getSources({ types: ['screen'] }) if (sources.length === 0) { callback({}) return } callback({ video: sources[0], audio: 'loopback' }) }) return ipcSuccess(undefined) }) ipcMain.handle(IPC_CHANNELS.SYSTEM_AUDIO.DISABLE_LOOPBACK, async () => { session.defaultSession.setDisplayMediaRequestHandler(null) return ipcSuccess(undefined) }) ipcMain.handle(IPC_CHANNELS.CAPTION.START, async () => { try { await getCaptionService().start() return ipcSuccess(undefined) } catch (err) { const code = err instanceof Error && 'code' in err ? (err as { code: number }).code : ErrorCode.CaptionStartFailed const message = err instanceof Error ? err.message : String(err) return ipcError(code, message) } }) ipcMain.handle(IPC_CHANNELS.CAPTION.STOP, async () => { try { await getCaptionService().stop() return ipcSuccess(undefined) } catch (err) { const message = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.CaptionStartFailed, message) } }) ipcMain.handle(IPC_CHANNELS.CAPTION.GET_STATE, async () => { return ipcSuccess(getCaptionService().getState()) }) ipcMain.handle(IPC_CHANNELS.CAPTION.SET_CONFIG, async (_event, config: Partial) => { getCaptionService().setConfig(config) return ipcSuccess(undefined) }) ipcMain.handle(IPC_CHANNELS.CAPTION.GET_CONFIG, async () => { return ipcSuccess(getCaptionService().getConfig()) }) // 렌더러 → 메인: 시스템 오디오 PCM 데이터 수신 ipcMain.on(IPC_CHANNELS.CAPTION.SYSTEM_AUDIO_DATA, (_event, data: ArrayBuffer) => { getCaptionService().onSystemAudioData(Buffer.from(data)) }) }