🎤 О проекте 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 — Таблица маппинга вида {"логин_пользователя": "идентификатор_голоса"}, к которой обращается система для поиска голоса при генерации.