# Kế Hoạch Audio Source Picker Cho Soniox Real-Time Và Qwen3-ASR

## Summary
- Cập nhật tính năng capture audio theo source hiện tại: app chỉ còn 2 recognizer trong UI là `Soniox Real-Time` và `Qwen3-ASR 1.7B`.
- Thêm “Audio source” picker dùng chung cho cả `useSonioxRecording` và `useRealtimeRecording`.
- Protocol hiện tại:
  - Soniox và Qwen đều gửi audio từ browser lên server WebSocket; server xử lý STT và lưu transcript qua backend.
- Mục tiêu là linh động theo nhiều use case: họp online, phim/video, system audio, mic thật, Stereo Mix, VB-CABLE, VoiceMeeter.

## Key Changes

### Audio Source UI
- Trong workspace recording mới, thêm phần chọn audio trước khi Start meeting:
  - `Input device only`
  - `Input device + shared tab/system audio`
- Thêm input-device combobox lấy từ `navigator.mediaDevices.enumerateDevices()`.
- `Input device only` dùng được cho:
  - mic thật;
  - Stereo Mix;
  - VB-CABLE / CABLE Output;
  - VoiceMeeter Output;
  - virtual microphone bất kỳ.
- `Input device + shared tab/system audio` dùng cho Google Meet/Zoom/webinar:
  - input device là mic của mình;
  - browser hỏi chọn tab/window/system audio;
  - user cần bật “Share tab audio” khi chọn Google Meet tab.
- Lưu lựa chọn gần nhất vào `localStorage`: mode + `deviceId`. Nếu device không còn tồn tại thì fallback về default device.

### Shared Capture Helper
- Tạo helper mới, ví dụ `frontend/src/lib/audioCapture.ts`.
- Public types:
  - `AudioSourceMode = 'input_only' | 'input_plus_shared'`
  - `AudioInputDeviceOption = { deviceId, label }`
  - `createAudioCapture({ mode, inputDeviceId })`
- `createAudioCapture` trả:
  - `stream`: final mixed `MediaStream`;
  - `audioContext`;
  - `analyser`;
  - `cleanup()`.
- `input_only`:
  - gọi `getUserMedia` với `deviceId` nếu có.
  - giữ `echoCancellation/noiseSuppression/autoGainControl` bật cho mic/default input.
  - với virtual/system devices vẫn cho browser xử lý theo khả năng thiết bị.
- `input_plus_shared`:
  - gọi `getUserMedia` cho input device;
  - gọi `getDisplayMedia({ video: true, audio: true })`;
  - trộn mic + shared audio bằng Web Audio API;
  - mỗi nguồn đi qua `GainNode`, mặc định `0.75` để giảm clipping;
  - nếu shared stream không có audio track thì báo lỗi rõ và cleanup toàn bộ tracks.

### Soniox Integration
- `useSonioxRecording` nhận thêm:
  - `audioSourceMode`
  - `audioInputDeviceId`
- Thay `getUserMedia` trực tiếp bằng `createAudioCapture`.
- Truyền `capture.stream` vào `SonioxClient.start({ stream })`.
- Waveform dùng `capture.analyser`, nghĩa là waveform hiển thị đúng mixed audio.
- Cleanup khi stop/unmount/page-hide phải gọi `capture.cleanup()` để dừng cả mic, virtual input, shared tab/system capture.

### Qwen/WebSocket Integration
- `useRealtimeRecording` nhận cùng options:
  - `audioSourceMode`
  - `audioInputDeviceId`
- Thay `getUserMedia` trực tiếp bằng `createAudioCapture`.
- Web Audio → downsample 16 kHz → PCM16 → WebSocket giữ nguyên.
- Waveform dùng analyser của mixed stream.
- Stop vẫn flush pending PCM rồi gửi `{ type: 'stop' }` như hiện tại.

### UI Copy Và Error Handling
- Error copy cập nhật thêm:
  - không có audio input;
  - không được cấp quyền mic/input;
  - shared tab/system không có audio;
  - browser không hỗ trợ shared audio.
- Hint theo mode:
  - `Input device only`: “Use this for mic, Stereo Mix, VB-CABLE, VoiceMeeter, or movie/system audio routed into a virtual input.”
  - `Input device + shared tab/system audio`: “Use this for online meetings. Select the meeting tab/window and enable audio sharing.”
- Với Soniox, giữ nguyên phần context/translation hiện tại.
- Với Qwen, giữ nguyên behavior transcript final theo VAD/model server.

## Test Plan
- Soniox:
  - `Input device only + mic thật`: nhận giọng người dùng.
  - `Input device only + VB-CABLE/Stereo Mix`: nhận audio máy/phim, không cần mic thật.
  - `Input device + shared Google Meet tab + mic`: nhận cả khách nói và mình nói.
  - Translation/context vẫn hoạt động.
  - Stop lưu final + partial hiện tại vào backend như cũ.
- Qwen3-ASR:
  - `Input device only`: model server nhận PCM liên tục và trả segment.
  - `Input device + shared tab/system audio`: cả mic và meeting audio đi vào cùng PCM stream.
  - Pause không gửi audio; resume gửi tiếp.
  - Stop flush pending PCM trước khi finalizing.
- Browser/manual:
  - Chrome/Edge Windows là target chính.
  - Nếu `getDisplayMedia` không có audio track, UI báo lỗi và hướng dẫn dùng VB-CABLE/Stereo Mix với `Input device only`.
  - Khi stop/unmount, browser capture indicator biến mất, không còn track nào chạy nền.
- Regression:
  - Provider picker chỉ còn `Soniox Real-Time` và `Qwen3-ASR 1.7B`.
  - Resume recording vẫn chọn đúng provider/model đã lưu.
  - Không thay đổi backend/model server API.

## Assumptions
- V1 chỉ sửa frontend audio capture/mixing; backend và model server không cần đổi protocol.
- `Only Virtual Mic` được hiểu là `Input device only` + chọn device virtual như Stereo Mix, VB-CABLE, hoặc VoiceMeeter Output.
- Browser không thể tự bắt audio tab khác một cách im lặng; mode `Input device + shared tab/system audio` luôn cần user chọn tab/window/system audio.
- Không thêm lại FunASR/OpenAI/Whisper vào UI; source hiện tại giữ Soniox làm default và Qwen3-ASR là local fallback/option.
