feat(fe): play Discord voice live + fix recording play/download
Build & Deploy (Nix) / build-and-deploy (proxy) (push) Successful in 3m42s
Build & Deploy (Nix) / build-and-deploy (backend) (push) Successful in 4m11s
Build & Deploy (Nix) / build-and-deploy (discord-gateway) (push) Successful in 11m4s
Build & Deploy (Nix) / build-and-deploy (proxy) (push) Successful in 3m42s
Build & Deploy (Nix) / build-and-deploy (backend) (push) Successful in 4m11s
Build & Deploy (Nix) / build-and-deploy (discord-gateway) (push) Successful in 11m4s
Voice page (connection tab): - ListenControl baru: toggle Listen (Headphones) — mulai PcmPlayer dari user gesture, subscribe onPcm WS, volume slider, bar level per-user REAL dari PCM (bukan random). - lib/audio/pcm-player.ts (baru): ScriptProcessorNode mixer — ring buffer 2s per user (hash FNV-1a sama dengan gateway), upsampling 24k→48k linear, mix semua user ke mono, gain volume, cleanup ring diam 5s. - useVoiceListen + hashUserId di hooks; auto-stop saat disconnect. Recordings: - recording-player: reset src+load+play() eksplisit (bukan autoPlay doang), tampilkan filename + error state 'playback failed' kalau file rusak. - recording-card: tombol Download fetch blob (CORS tele open) → objectURL → force download dengan nama asli; fallback buka tab baru kalau fetch gagal; spinner saat mendownload. Verified: FE tsc 0, next build 10/10 static pages.
This commit is contained in:
@@ -153,6 +153,7 @@ export default function RecordingsPage() {
|
||||
|
||||
<RecordingPlayer
|
||||
url={currentTrack?.download_url ?? undefined}
|
||||
filename={currentTrack?.filename ?? undefined}
|
||||
onClose={() => setPlayingId(null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
|
||||
import { SubNav } from "@/components/layout/sub-nav";
|
||||
import { VoiceActivityTimeline } from "@/components/voice/activity-timeline";
|
||||
import { VoiceConnectionCard } from "@/components/voice/connection-card";
|
||||
import { ListenControl } from "@/components/voice/listen-control";
|
||||
import { MicControl } from "@/components/voice/mic-control";
|
||||
import { SpeakerWaveform } from "@/components/voice/speaker-waveform";
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
useVoiceChannels,
|
||||
useVoiceConnect,
|
||||
useVoiceDisconnect,
|
||||
useVoiceListen,
|
||||
useVoiceStatus,
|
||||
} from "@/hooks";
|
||||
import { useWebSocket } from "@/lib/ws/context";
|
||||
@@ -29,9 +31,11 @@ export default function VoicePage() {
|
||||
const connectMut = useVoiceConnect();
|
||||
const disconnectMut = useVoiceDisconnect();
|
||||
const micMut = useMicTransmit(ws);
|
||||
const listen = useVoiceListen(ws);
|
||||
const [selectedChannel, setSelectedChannel] = useState("");
|
||||
const [micActive, setMicActive] = useState(false);
|
||||
const [volume, setVolume] = useState(75);
|
||||
const [listenVolume, setListenVolume] = useState(75);
|
||||
const [tab, setTab] = useState<VoiceTab>("connection");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -111,6 +115,7 @@ export default function VoicePage() {
|
||||
setMicActive(false);
|
||||
void micMut.mutateAsync(false).catch(() => {});
|
||||
}
|
||||
if (listen.active) listen.toggle(false);
|
||||
disconnectMut.mutate(undefined);
|
||||
}}
|
||||
connecting={connectMut.isPending}
|
||||
@@ -119,13 +124,27 @@ export default function VoicePage() {
|
||||
{tab === "connection" && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<SpeakerWaveform speakers={activeSpeakers} />
|
||||
<MicControl
|
||||
connected={connected}
|
||||
active={micActive}
|
||||
onToggle={handleMicToggle}
|
||||
volume={volume}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<ListenControl
|
||||
connected={connected}
|
||||
active={listen.active}
|
||||
levels={listen.levels}
|
||||
speakers={speakers}
|
||||
onToggle={(on) => listen.toggle(on)}
|
||||
volume={listenVolume}
|
||||
onVolumeChange={(v) => {
|
||||
setListenVolume(v);
|
||||
listen.setVolume(v);
|
||||
}}
|
||||
/>
|
||||
<MicControl
|
||||
connected={connected}
|
||||
active={micActive}
|
||||
onToggle={handleMicToggle}
|
||||
volume={volume}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user