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:
2026-09-20 19:30:43 +03:00
parent 2538e07ec3
commit 619bd91df4
40 changed files with 4269 additions and 48 deletions
+133 -4
View File
@@ -70,7 +70,17 @@
},
"voice.disabled": "Голос не настроен на инстансе.",
"voice.channel_full": "В комнате нет свободных мест.",
"voice.token_refresh_failed": "Не удалось продлить сессию голоса. Подключитесь заново."
"voice.token_refresh_failed": "Не удалось продлить сессию голоса. Подключитесь заново.",
"sound": {
"invalid_type": "Поддерживаются MP3, OGG, WAV, WebM и M4A.",
"invalid_name": "Имя звука: 2–32 символа, латиница, цифры и «_».",
"name_taken": "Звук с таким именем уже есть."
},
"sounds": {
"limit_reached": "Достигнут лимит звуков этого вида.",
"event_required": "Для звука палитры выберите событие."
},
"voice.not_connected": "Сначала войдите в голосовую комнату."
},
"theme": {
"switchToLight": "Светлая тема",
@@ -268,7 +278,14 @@
},
"screen": {
"on": "Начать демонстрацию экрана",
"off": "Остановить демонстрацию экрана"
"off": "Остановить демонстрацию экрана",
"quality": "Качество демонстрации",
"presets": {
"1080p60": "1080p · 60 fps",
"1080p30": "1080p · 30 fps",
"720p30": "720p · 30 fps",
"480p30": "480p · 30 fps"
}
},
"quality": {
"excellent": "Отличное соединение",
@@ -289,6 +306,44 @@
},
"channelMenu": {
"label": "Голосовая комната {{name}}"
},
"soundboard": {
"title": "Саундборд",
"open": "Открыть саундборд",
"searchLabel": "Поиск звука",
"searchPlaceholder": "Название звука",
"empty": "На сервере пока нет звуков саундборда.",
"noResults": "Ничего не найдено.",
"playLabel": "Проиграть звук {{name}}",
"rateLimited": "Слишком часто: повторите через {{seconds}} с.",
"notConnected": "Сначала войдите в голосовую комнату.",
"playedBy": "{{name}} включил звук «{{sound}}»"
},
"stats": {
"title": "Статистика",
"open": "Открыть статистику соединения",
"resolution": "Разрешение",
"frameRate": "Частота кадров",
"codec": "Кодек",
"bitrate": "Битрейт",
"kbps": "кбит/с",
"ms": "мс",
"rtt": "Задержка (RTT)",
"packetLoss": "Потери пакетов",
"jitter": "Джиттер"
},
"publishQuality": {
"label": "Качество видео",
"short": {
"low": "Низкое",
"medium": "Среднее",
"high": "Высокое"
},
"level": {
"low": "Низкое качество",
"medium": "Среднее качество",
"high": "Высокое качество"
}
}
},
"friends": {
@@ -490,7 +545,8 @@
"memberJoin": "{{name}} присоединился к серверу",
"memberKick": "{{name}} покинул сервер (исключён)",
"channelCreated": "Комната #{{name}} создана",
"voiceChannelCreated": "Голосовая комната {{name}} создана"
"voiceChannelCreated": "Голосовая комната {{name}} создана",
"memberLeave": "{{name}} покинул сервер"
}
},
"user": {
@@ -761,6 +817,59 @@
"fileSelected": "{{name}} · {{size}}",
"upload": "Загрузить",
"uploading": "Загружаем…"
},
"sounds": {
"title": "Звуки",
"description": "Саундборд и звуковая палитра сервера: до {{limit}} звуков каждого вида, каждый до 512 КБ.",
"counter": "{{count}} из {{limit}}",
"empty": "Звуков этого вида пока нет.",
"uploadTitle": "Загрузить звук",
"uploadHint": "Перетащите файл в область ниже или выберите его: MP3, OGG, WAV, WebM или M4A до {{limit}}.",
"name": "Имя звука",
"namePlaceholder": "например, airhorn",
"nameHint": "2–32 символа: латиница, цифры и «_».",
"emoji": "Эмодзи",
"emojiPlaceholder": "например, 📣",
"emojiHint": "Необязательная иконка для сетки саундборда.",
"event": "Событие",
"dropHint": "Перетащите файл сюда",
"file": "Файл звука",
"chooseFile": "Выбрать файл",
"fileSelected": "{{name}} · {{size}}",
"upload": "Загрузить",
"uploading": "Загружаем…",
"rename": "Переименовать",
"renameLabel": "Переименовать звук {{name}}",
"renameTitle": "Переименовать звук",
"delete": "Удалить",
"deleteLabel": "Удалить звук {{name}}",
"deleteConfirmTitle": "Удалить звук?",
"deleteConfirmBody": "Звук {{name}} будет удалён без возможности восстановления.",
"deleteConfirm": "Удалить",
"kinds": {
"soundboard": "Саундборд",
"ui": "Звуковая палитра"
},
"events": {
"member_join": "Участник присоединился",
"member_leave": "Участник вышел",
"mention": "Упоминание",
"message": "Новое сообщение",
"message_send": "Отправка сообщения",
"voice_join": "Вход в голосовую комнату",
"voice_leave": "Выход из голосовой комнаты",
"mic_on": "Микрофон включён",
"mic_off": "Микрофон выключен",
"camera_on": "Камера включена",
"camera_off": "Камера выключена",
"screen_on": "Демонстрация начата",
"screen_off": "Демонстрация остановлена",
"kick": "Исключение участника",
"ban": "Бан участника",
"mute": "Мьют участника",
"role_grant": "Выдача роли",
"role_revoke": "Снятие роли"
}
}
},
"navLabel": "Пункты настроек",
@@ -777,7 +886,27 @@
"output": "Звук",
"micVolume": "Громкость микрофона",
"outputVolume": "Громкость звука",
"stub": "Изменения применяются сразу и сохраняются на этом устройстве."
"stub": "Изменения применяются сразу и сохраняются на этом устройстве.",
"micMode": "Активация микрофона",
"micModes": {
"voice": "Голосовая активация",
"push_to_talk": "Push-to-talk"
},
"pttKey": "Клавиша",
"pttHint": "Удерживайте клавишу, чтобы говорить. Работает, когда окно в фокусе и курсор не в поле ввода.",
"pttKeys": {
"space": "Пробел",
"v": "V",
"b": "B",
"n": "N",
"alt": "Alt",
"ctrl": "Ctrl"
},
"sounds": "Звуки",
"eventSounds": "Звуки событий сервера",
"eventSoundsHint": "Проигрывать звуки палитры сервера: вход участника, упоминания и другие события.",
"soundboard": "Саундборд",
"soundboardHint": "Проигрывать звуки саундборда в голосовой комнате."
},
"language": {
"title": "Язык и регион",