feat(fe): real mic capture for voice transmit (kirim suara)
Sebelumnya tombol Live/Muted cuma kirim voice:transmit:start/stop ke gateway — TIDAK ADA audio yang dikirim (0 getUserMedia/AudioContext di frontend). MicControl cuma toggle state kosong. - lib/audio/mic-transmit.ts (BARU): getUserMedia → AudioContext 48kHz → AudioWorklet (downsample 24kHz mono s16le + volume + chunk 20ms) → frame 'PCM\0' + Int16LE → ws.sendBinary. Worklet inline via Blob URL (aman untuk static export). - useMicTransmit(ws): aktif = start capture + voice:transmit:start; nonaktif = stop capture + voice:transmit:stop; setVolume untuk slider. - voice page: volume slider sekarang beneran ngatur gain mic; disconnect ikut matiin mic. Verifikasi: tsc PASS, biome 0 error, next build PASS. Test mic butuh real device (headless browser tidak punya mic) — protokol: connect voice → Live → ngomong → orang di channel denger.
This commit is contained in:
@@ -28,7 +28,7 @@ export default function VoicePage() {
|
||||
const { speakers, subscribe } = useSpeakers();
|
||||
const connectMut = useVoiceConnect();
|
||||
const disconnectMut = useVoiceDisconnect();
|
||||
const micMut = useMicTransmit();
|
||||
const micMut = useMicTransmit(ws);
|
||||
const [selectedChannel, setSelectedChannel] = useState("");
|
||||
const [micActive, setMicActive] = useState(false);
|
||||
const [volume, setVolume] = useState(75);
|
||||
@@ -41,16 +41,33 @@ export default function VoicePage() {
|
||||
|
||||
const handleMicToggle = useCallback(
|
||||
async (checked: boolean) => {
|
||||
setMicActive(checked);
|
||||
try {
|
||||
await micMut.mutateAsync(checked);
|
||||
} catch {
|
||||
setMicActive(!checked);
|
||||
if (checked) {
|
||||
try {
|
||||
await micMut.mutateAsync(true);
|
||||
setMicActive(true);
|
||||
} catch {
|
||||
setMicActive(false);
|
||||
}
|
||||
} else {
|
||||
setMicActive(false);
|
||||
try {
|
||||
await micMut.mutateAsync(false);
|
||||
} catch {
|
||||
// Stop already tore down the local transmitter — ignore remote errors
|
||||
}
|
||||
}
|
||||
},
|
||||
[micMut],
|
||||
);
|
||||
|
||||
const handleVolumeChange = useCallback(
|
||||
(v: number) => {
|
||||
setVolume(v);
|
||||
micMut.setVolume(v);
|
||||
},
|
||||
[micMut],
|
||||
);
|
||||
|
||||
const handleGuildChange = useCallback((guildId: string | null) => {
|
||||
if (!guildId) {
|
||||
setSelectedGuild("");
|
||||
@@ -89,7 +106,13 @@ export default function VoicePage() {
|
||||
channelId: selectedChannel,
|
||||
})
|
||||
}
|
||||
onDisconnect={() => disconnectMut.mutate(undefined)}
|
||||
onDisconnect={() => {
|
||||
if (micActive) {
|
||||
setMicActive(false);
|
||||
void micMut.mutateAsync(false).catch(() => {});
|
||||
}
|
||||
disconnectMut.mutate(undefined);
|
||||
}}
|
||||
connecting={connectMut.isPending}
|
||||
/>
|
||||
|
||||
@@ -101,7 +124,7 @@ export default function VoicePage() {
|
||||
active={micActive}
|
||||
onToggle={handleMicToggle}
|
||||
volume={volume}
|
||||
onVolumeChange={setVolume}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user