feat(voice): саундборд, звуковая палитра, push-to-talk и статистика соединения
Клиентская часть Фазы 3 (AGENT.md 7.13, 7.14): звуки сервера, локальные
звуки интерфейса и телеметрия медиа.
- саундборд: `api/sounds.ts` (лимиты 512 КБ и 30 на вид, multipart-загрузка,
play, `SOUND_EVENTS`), `lib/soundboard.ts` (blob-кэш `/files/{id}`,
наложение до 8 звуков, громкость = `outputVolume`, тишина при дефене),
попап в панели комнаты (`USE_SOUNDBOARD`), таймер на `rate_limited`,
подпись «включил звук» и защита от двойного воспроизведения;
- звуковая палитра: `SOUNDBOARD_PLAY` и события сервера (вход/выход
участника, упоминание, сообщение, вход/выход из голосовой, свои
микрофон/камера/экран, серверный мьют) → `playEventSound`;
- секция «Звуки» в настройках сервера (`MANAGE_SOUNDS`): два вида со
счётчиками, drag&drop, имя, эмодзи, событие палитры, переименование,
удаление и клиентские проверки файла;
- push-to-talk: режим и клавиша (`event.code`), хук в `AppLayout`, вход в
комнату с выключенным микрофоном, удержание включает публикацию, отпускание
и потеря фокуса выключают, серверный мьют приоритетнее;
- статистика: поповер с разрешением, fps, кодеком, битрейтом, RTT, потерями
и jitter (опрос раз в секунду, остановка при закрытии), выбор качества
публикации low/medium/high, пресеты шаринга экрана 1080p60…480p30;
- данные: `guilds[].sounds` из READY, `GUILD_SOUNDS_UPDATE`, селектор
`useGuildSounds`, тумблеры звуков в «Аудио-видео» (localStorage);
- тесты: `soundboard`, `soundSettings`, `pushToTalk`, `voiceStats` и
расширенный фейковый LiveKit SDK.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* Подставной `Audio` для тестов саундборда: в jsdom `play()` не реализован,
|
||||
* поэтому звук подменяется целиком. Экземпляры собираются в массив — по ним
|
||||
* проверяют, что звук скачали, проиграли или остановили.
|
||||
*/
|
||||
|
||||
export class FakeAudio {
|
||||
src: string;
|
||||
volume = 1;
|
||||
currentTime = 0;
|
||||
paused = true;
|
||||
playCalls = 0;
|
||||
pauseCalls = 0;
|
||||
/** Считаем и нативные, и «событийные» подписки: `ended` глушит звук. */
|
||||
readonly listeners = new Map<string, Set<() => void>>();
|
||||
|
||||
constructor(src = '') {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
addEventListener(type: string, handler: () => void): void {
|
||||
const set = this.listeners.get(type) ?? new Set<() => void>();
|
||||
set.add(handler);
|
||||
this.listeners.set(type, set);
|
||||
}
|
||||
|
||||
removeEventListener(type: string, handler: () => void): void {
|
||||
this.listeners.get(type)?.delete(handler);
|
||||
}
|
||||
|
||||
play(): Promise<void> {
|
||||
this.playCalls += 1;
|
||||
this.paused = false;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
this.pauseCalls += 1;
|
||||
this.paused = true;
|
||||
}
|
||||
|
||||
/** Сообщить о конце звука: движок снимает элемент с учёта. */
|
||||
emit(type: string): void {
|
||||
for (const handler of [...(this.listeners.get(type) ?? [])]) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Ставит подставной `Audio` и возвращает список созданных элементов. */
|
||||
export function installFakeAudio(): FakeAudio[] {
|
||||
const instances: FakeAudio[] = [];
|
||||
class StubAudio extends FakeAudio {
|
||||
constructor(src = '') {
|
||||
super(src);
|
||||
instances.push(this);
|
||||
}
|
||||
}
|
||||
vi.stubGlobal('Audio', StubAudio);
|
||||
return instances;
|
||||
}
|
||||
Reference in New Issue
Block a user