feat(voice): голосовой интерфейс на LiveKit (Фаза 3, клиент)
- `livekit-client` подключён как ленивый чанк: SDK грузится только при входе в комнату, стартовый бандл не вырос; - `src/lib/livekit.ts` — единственное место знания о SDK: подключение, дорожки микрофона/камеры/экрана, снимки участников (речь, флаги, качество связи), выбор устройств на лету, разбор exp токена; - `src/stores/voiceConnection.ts` — сессия голоса: статусы idle/connecting/connected/reconnecting/error, продление токена за минуту до истечения с переподключением и повторным применением флагов, серверный мьют и глушение, громкость участников, перемещение, подсказка «говорите с выключенным микрофоном» не чаще раза в 5 с; - UI: состав голосовой комнаты в сайдбаре (речь, флаги, качество), панель комнаты с плитками и модерацией, меню участника (личная громкость, мьют, перемещение), drag&drop между голосовыми комнатами, кнопки панели пользователя управляют реальной сессией, при выключенном голосе — понятная подсказка; - 368 тестов Vitest (было 330), check/lint/test/build зелёные.
This commit is contained in:
@@ -197,7 +197,7 @@ export const useVoiceConnectionStore = create<VoiceConnectionState>((set, get) =
|
||||
};
|
||||
|
||||
/** Не удалось продлить сессию: выходим из комнаты и показываем ошибку. */
|
||||
const failRefresh = async (): Promise<void> => {
|
||||
const failRefresh = async (guildId: string, channelId: string): Promise<void> => {
|
||||
clearTokenTimer();
|
||||
await closeRoom();
|
||||
const channel = joinedChannelId;
|
||||
@@ -211,7 +211,10 @@ export const useVoiceConnectionStore = create<VoiceConnectionState>((set, get) =
|
||||
}
|
||||
set({
|
||||
...defaults,
|
||||
// Комнату оставляем в состоянии: так подсказка видна у канала и в панели.
|
||||
status: 'error',
|
||||
guildId,
|
||||
channelId,
|
||||
error: { code: VOICE_SESSION_EXPIRED_CODE },
|
||||
});
|
||||
};
|
||||
@@ -270,7 +273,7 @@ export const useVoiceConnectionStore = create<VoiceConnectionState>((set, get) =
|
||||
return;
|
||||
} catch {
|
||||
if (attempt === TOKEN_REFRESH_ATTEMPTS) {
|
||||
await failRefresh();
|
||||
await failRefresh(guildId, channelId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -347,7 +350,7 @@ export const useVoiceConnectionStore = create<VoiceConnectionState>((set, get) =
|
||||
useSessionStore.getState().applyVoiceState(result.state);
|
||||
scheduleTokenRefresh(result.token, mine);
|
||||
|
||||
const handle = createVoiceRoom({
|
||||
const handle = await createVoiceRoom({
|
||||
onParticipants: (participants) => {
|
||||
if (mine === generation) {
|
||||
set({ participants });
|
||||
@@ -370,6 +373,10 @@ export const useVoiceConnectionStore = create<VoiceConnectionState>((set, get) =
|
||||
},
|
||||
onSpeakingWhileMuted: warnSpeakingWhileMuted,
|
||||
});
|
||||
if (mine !== generation) {
|
||||
handle.dispose();
|
||||
return;
|
||||
}
|
||||
room = handle;
|
||||
await handle.connect(result.url, result.token);
|
||||
if (mine !== generation) {
|
||||
|
||||
Reference in New Issue
Block a user