voice v1.1
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
import { useVoiceRoom } from '../realtime/voice/useVoiceRoom';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getVoiceState, subscribeVoice } from '../realtime/voice/voiceStore';
|
||||
|
||||
export function VoicePanel({
|
||||
roomId,
|
||||
@ -7,19 +9,26 @@ export function VoicePanel({
|
||||
roomId: string;
|
||||
username: string;
|
||||
}) {
|
||||
const voice = useVoiceRoom(roomId, username);
|
||||
const [voice, setVoice] = useState(getVoiceState());
|
||||
const { connect, disconnect, toggleMute } = useVoiceRoom(roomId, username);
|
||||
|
||||
useEffect(() => {
|
||||
return subscribeVoice(() => {
|
||||
setVoice({ ...getVoiceState() });
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={voice.connect} disabled={voice.connected || !username}>
|
||||
Войти в голос
|
||||
<button disabled={voice.connected} onClick={connect}>
|
||||
Войти
|
||||
</button>
|
||||
|
||||
<button onClick={voice.toggleMute}>
|
||||
<button onClick={toggleMute}>
|
||||
{voice.muted ? 'Включить микрофон' : 'Выключить микрофон'}
|
||||
</button>
|
||||
|
||||
<button onClick={voice.disconnect}>Выйти</button>
|
||||
<button onClick={disconnect}>Выйти</button>
|
||||
|
||||
<ul>
|
||||
{voice.participants.map((u) => (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { rtcConfig } from './rtcConfig';
|
||||
import type { WSMessage } from './types';
|
||||
import { setVoiceState, getVoiceState } from './voiceStore';
|
||||
|
||||
type PeerMap = Map<string, RTCPeerConnection>;
|
||||
|
||||
@ -9,9 +10,9 @@ export function useVoiceRoom(roomId: string, username: string) {
|
||||
const peersRef = useRef<PeerMap>(new Map());
|
||||
const streamRef = useRef<MediaStream | null>(null);
|
||||
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [participants, setParticipants] = useState<string[]>([]);
|
||||
const [muted, setMuted] = useState(false);
|
||||
// const [connected, setConnected] = useState(false);
|
||||
// const [participants, setParticipants] = useState<string[]>([]);
|
||||
// const [muted, setMuted] = useState(false);
|
||||
|
||||
const pendingIceRef = useRef<Map<string, RTCIceCandidateInit[]>>(new Map());
|
||||
|
||||
@ -36,27 +37,48 @@ export function useVoiceRoom(roomId: string, username: string) {
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
setConnected(true);
|
||||
setParticipants([username]);
|
||||
setVoiceState({
|
||||
connected: true,
|
||||
participants: [username],
|
||||
});
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
setVoiceState({
|
||||
connected: false,
|
||||
participants: [],
|
||||
muted: false,
|
||||
});
|
||||
cleanup();
|
||||
setConnected(false);
|
||||
};
|
||||
|
||||
// ws.onclose = () => {
|
||||
// cleanup();
|
||||
// setConnected(false);
|
||||
// };
|
||||
|
||||
ws.onmessage = async (ev) => {
|
||||
const msg: WSMessage = JSON.parse(ev.data);
|
||||
|
||||
if (msg.type === 'join' && msg.user !== username) {
|
||||
await createPeer(msg.user, false);
|
||||
|
||||
setParticipants((p) => (p.includes(msg.user) ? p : [...p, msg.user]));
|
||||
const { participants } = getVoiceState();
|
||||
if (!participants.includes(msg.user)) {
|
||||
setVoiceState({
|
||||
participants: [...participants, msg.user],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'leave') {
|
||||
removePeer(msg.user);
|
||||
setParticipants((p) => p.filter((u) => u !== msg.user));
|
||||
|
||||
setVoiceState({
|
||||
participants: getVoiceState().participants.filter(
|
||||
(u) => u !== msg.user,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.type === 'signal') {
|
||||
@ -191,10 +213,12 @@ export function useVoiceRoom(roomId: string, username: string) {
|
||||
// --- mute ---
|
||||
const toggleMute = () => {
|
||||
if (!streamRef.current) return;
|
||||
streamRef.current.getAudioTracks().forEach((t) => {
|
||||
t.enabled = !t.enabled;
|
||||
setMuted(!t.enabled);
|
||||
});
|
||||
|
||||
const enabled = !getVoiceState().muted;
|
||||
|
||||
streamRef.current.getAudioTracks().forEach((t) => (t.enabled = !enabled));
|
||||
|
||||
setVoiceState({ muted: enabled });
|
||||
};
|
||||
|
||||
// --- cleanup ---
|
||||
@ -213,16 +237,16 @@ export function useVoiceRoom(roomId: string, username: string) {
|
||||
|
||||
const disconnect = () => {
|
||||
cleanup();
|
||||
setParticipants([]);
|
||||
setConnected(false);
|
||||
setVoiceState({
|
||||
connected: false,
|
||||
participants: [],
|
||||
muted: false,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
connect,
|
||||
disconnect,
|
||||
toggleMute,
|
||||
connected,
|
||||
muted,
|
||||
participants,
|
||||
};
|
||||
}
|
||||
|
||||
30
src/renderer/realtime/voice/voiceStore.ts
Normal file
30
src/renderer/realtime/voice/voiceStore.ts
Normal file
@ -0,0 +1,30 @@
|
||||
type VoiceState = {
|
||||
connected: boolean;
|
||||
participants: string[];
|
||||
muted: boolean;
|
||||
};
|
||||
|
||||
const state: VoiceState = {
|
||||
connected: false,
|
||||
participants: [],
|
||||
muted: false,
|
||||
};
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
export function getVoiceState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
export function setVoiceState(patch: Partial<VoiceState>) {
|
||||
Object.assign(state, patch);
|
||||
listeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
export function subscribeVoice(cb: () => void): () => void {
|
||||
listeners.add(cb);
|
||||
|
||||
return () => {
|
||||
listeners.delete(cb);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user