🎤 О проекте Twitch TTS
Twitch TTS (Text-to-Speech) — это клиентское веб-приложение (SPA), разработанное для стримеров. Оно позволяет в реальном времени интегрировать текстовые сообщения из чата Twitch в голосовую озвучку прямо на трансляции, используя гибкую систему кастомных голосов и прав доступа.
Интерфейс выполнен в фирменной темной палитре Twitch для бесшовной интеграции в рабочее окружение контент-мейкера.
🛠️ Архитектура проекта
Приложение представляет собой легковесный автономный скрипт, не требующий сложного серверного деплоя:
- Twitch IRC Интеграция: Использует библиотеку
Comfy.js(подключена через CDN). Это позволяет слушать события чата без необходимости прохождения OAuth-авторизации со стороны стримера — достаточно указать имя канала. - Бизнес-логика: Чистый ванильный JavaScript (ES6+), управляющий очередями воспроизведения, механикой повторных запросов, обработкой ошибок сети и событий элементов HTML5 Audio.
- UI/Интерфейс: Трехколоночная структура (Голоса, Команды управления, Активность пользователей), построенная на семантичном HTML и кастомных CSS стилях без применения тяжелых UI-фреймворков.
⚙️ Конфигурация и константы
В коде скрипта зашиты базовые параметры взаимодействия с инфраструктурой синтеза речи:
| Константа | Тип / Значение | Описание |
|---|---|---|
TTS_API_URL |
String |
Эндпоинт генерации аудиофайлов речи (возвращает бинарный аудио-поток). |
SPEAKERS_URL |
String |
Эндпоинт для выгрузки дерева доступных спикеров (категории и ID голосов). |
API_TOKEN |
String |
Токен авторизации (передается в HTTP-заголовке Authorization: Bearer ...). |
TTS_GAP_BETWEEN_MS |
4000 |
Пауза в миллисекундах между окончаниями одного трека и стартом следующего. |
TTS_MAX_TEXT_CHARS |
400 |
Максимально допустимая длина текстового фрагмента для отправки в синтез. |
TTS_QUEUE_MAX_ITEMS |
100 |
Лимит на число элементов, одновременно находящихся в очереди воспроизведения. |
💬 Команды для зрителей
Обычные участники чата могут взаимодействовать с TTS с помощью двух основных команд (если доступ для них открыт модераторами):
| Команда | Пример использования | Описание |
|---|---|---|
!tts [текст] |
!tts Привет стример! |
Отправляет указанный текст в очередь на генерацию и воспроизведение речи от текущего голоса пользователя. User |
!s [voice_id] |
!s 23 или !s glados |
Закрепляет за профилем пользователя выбранный уникальный идентификатор голоса. User |
🛡️ Административные команды управления
Модераторы и владелец канала (Broadcaster) обладают расширенным синтаксисом для динамической настройки поведения системы:
Управление режимами автоматического чтения чата:
!tts on/!tts off— Включение или отключение глобального перехвата и озвучивания **всех** рядовых сообщений чата.!tts on [username]/!tts off [username]— Таргетированное включение/исключение автоматической озвучки для конкретного зрителя.!tts random [секунды]— Активация режима «Случайный спикер». Система будет брать последнее живое сообщение из чата и зачитывать его раз в указанный интервал времени со случайным голосом. Для выключения используется!tts random off.!tts clear— Экстренная полная очистка текущей очереди и мгновенное прерывание проигрываемого в данный момент аудиопотока.
Менеджмент прав доступа (Синтаксис !open / !close):
!open all/!close all— Выдача или отзыв прав на использование ручных команд!ttsи!sдля абсолютно всех зрителей.!open vip/!close vip— Открытие или закрытие функционала эксклюзивно для аудитории со статусом VIP.!open [username]/!close [username]— Индивидуальный белый или черный список для конкретного аккаукта.
Административное назначение голосов:
!s [username] [voice_id]— Принудительное назначение или изменение голоса выбранному пользователю модератором.
🧠 Механика управления очередью (PlayNext)
Воспроизведение базируется на изолированном асинхронном цикле во избежание наложения звуковых дорожек друг на друга:
async function playNext() {
if (playing || queue.length === 0) return;
playing = true;
const item = queue[0];
// Формирование URLSearchParams, валидация токенов...
try {
const res = await fetchWithRetry(...);
let blob = await res.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.onended = () => {
playing = false;
URL.revokeObjectURL(url);
setTimeout(() => playNext(), TTS_GAP_BETWEEN_MS); // Пауза 4 секунды
};
await audio.play();
} catch(err) {
playing = false;
setTimeout(() => playNext(), 1200);
}
}
Каждому запуску функции присваивается уникальный инкрементируемый playbackToken. Это защищает систему от дублирования звука в случаях, когда модератор прожал очистку очереди во время ожидания сетевого ответа от бэкенда.
📡 Защита от сбоев и Retry-политика
Приложение спроектировано с учетом нестабильности сетевых маршрутов и работы под высокими нагрузками:
- Обработка HTTP HTTP 429 (Too Many Requests): При возникновении лимитов на стороне сервера генерации, функция
fetchWithRetryпытается проанализировать HTTP-заголовокRetry-After. Если заголовка нет, запускается алгоритм экспоненциального отката (увеличение задержки в геом. прогрессии2^(attempt-1)) с информированием стримера на панели статусов. - Фильтрация спама ссылками: На этапе перехвата события
onChatстрока очищается регулярными выражениями от любых URL-адресов. Это защищает стрим от зачитывания роботом длинных бессмысленных веб-ссылок.
💾 Экосистема локального хранения (LocalStorage)
Для сохранения конфигурации при перезапуске вкладки браузера (или OBS сцены) задействован механизм localStorage:
ttsChannel— Хранит текстовое имя последнего подключенного Twitch-канала.ttsAutoPlayState— Сериализованный в JSON объект, содержащий глобальный статус флага Авто-TTS и списки исключений (массивы пользователей с персонально включенной/выключенной озвучкой).ttsPerUserSpeakers— Таблица маппинга вида{"логин_пользователя": "идентификатор_голоса"}, к которой обращается система для поиска голоса при генерации.