В своей предыдущей статье я рассказал читателям Хабра о пути, который привёл меня к разработке автоматизированного AI-радио с новостными блоками, подкастами и музыкальным контентом. Я получил много ценных отзывов — спасибо за это! Работа над AI-вещанием переросла в полноценную платформу. В этом посте я также расскажу о ряде неожиданных проблем, с которыми столкнулся при создании современной публичной стриминг-платформы — возможно, это убережёт вас от тех же ошибок.


Платформа

Поскольку я не стал ограничиваться одним эфиром и решил разделить вещание по жанрам, со временем меня перестал устраивать ручной процесс создания трансляций через базу данных и выполнение kubectl-команд. Кроме того, я получил несколько запросов от пользователей Хабра на запуск аналогичных тематических стримов.

В итоге я пришёл к выводу, что стоит сосредоточить усилия на создании полноценной платформы для запуска таких эфиров — с возможностью вещания на любом языке. Как говорится:

Хороший программист — ленивый программист

Идея в том, что «ленивый» программист будет стараться автоматизировать всё, что можно, чтобы не делать одну и ту же работу дважды.

Самым сложным, пожалуй, был выбор названия для будущей платформы. В итоге я остановился на «Tunio» — производное от tune (настройка) и I/O (ввод/вывод).

Ошибки на старте

Список ошибок на старте, которые впоследствии мне пришлось исправлять — порой очень мучительно. Полезно знать о них заранее, прежде чем браться за подобные проекты, связанные со стримингом.

  • Конвертация и вещание в MP3
    Изначально я настроил вещание, конвертацию и хранение в MP3. Уже при реализации ретрансляции на стриминговые платформы (YouTube, VK Live, Telegram) стало ясно, что выбранный формат требует перекодирования потока в AAC, поскольку современные платформы работают с ним и/или с контейнером M4A. Это создавало значительную нагрузку на CPU даже при одном стриме. Контейнер M4A для AAC также удобен тем, что позволяет упаковывать метаданные (включая обложки) и корректно сохраняет длительность трека — в отличие от "сырого" AAC.
    Если вы не используете в Liquidsoap эффекты вроде crossfade и обработку звука, можно транслировать поток напрямую в режиме passthrough, без перекодирования — и тогда нагрузка на процессор практически отсутствует.

  • Использование исключительно платных TTS-решений
    Изначально я использовал только платные TTS-провайдеры для генерации новостей, подкастов и джинглов. Однако быстро столкнулся с тем, что баланс расходовался очень быстро, что ограничивало масштабирование и развитие проекта. После того как я обнаружил Piper TTS — self-hosted движок, который легко запускается в Docker и работает довольно шустро даже на CPU — ситуация кардинально изменилась. Возможность генерировать неограниченное количество подкастов, новостей на разных языках и объявлений дала мощный толчок развитию платформы, при этом без дополнительных расходов — что критично для проекта на ранней стадии.

  • Заказ джинглов у профессиональных студий
    Каждый раз процесс заказа джинглов превращался в длительную переписку со студией: согласование голосов, ожидание доступности дикторов (которые нередко оказывались заняты), а результат не всегда соответствовал ожиданиям. Когда я начал создавать джинглы самостоятельно, используя голоса от ElevenLabs, большинство проблем исчезло. Я получил именно тот результат, который мне был нужен — быстро, недорого и без лишней волокиты. Также у меня появилась возможность создавать джинглы сразу на нескольких языках. Примеры джинглов с голосами ElevenLabs:

    Думаю, вы согласитесь, что в контексте AI-радио джинглы с голосами AI звучат органичнее, чем записанные с участием настоящих дикторов.

  • Проверка уникальности новостей через теги
    Первоначально я пытался проверять уникальность новостей по тегам, которые генерировались из текста новости. Но создание тегов — не идемпотентная операция: одна и та же новость при разных запусках давала разные теги, несмотря на попытки адаптировать промпты. Решением стали векторные эмбеддинги. Каждая новость преобразуется в вектор с помощью модели text-embedding-ada-002 от OpenAI, после чего сохраняется в базу. При добавлении новой новости я нахожу ближайшую по смыслу, и если score меня не устраивает, не пропускаю новость дальше:

SELECT *, embedding <-> $1 AS score FROM your_table_name WHERE created_at >= $2 ORDER BY score LIMIT 1;

Это дало надёжную проверку на дублирование и повысило качество контента.

  • Фронтенд на Preact
    Изначально я написал фронтенд на Preact ради лёгкости и скорости. Но отсутствие серверного рендеринга (SSR) и SEO в итоге стало критичным. Пришлось мигрировать на Next.js, что дало все необходимые преимущества: SEO, SSR, удобную маршрутизацию и гибкую архитектуру.

  • Telegram как источник новостей
    Сначала я полагал, что Telegram может служить универсальным источником мировых новостей. Однако на практике он оказался ориентирован на русскоязычную аудиторию. Поэтому я добавил поддержку RSS-лент, которые, как оказалось, до сих пор активно используются на Западе. Через них я подключил новостные потоки по различным тематикам, и пропустил их через ту же цепочку обработки: фильтрация, категоризация, суммаризация, блокирование негатива, политики и рекламы

  • Ожидания от Telegram Min App
    Изначально я надеялся, что Telegram mini app станет основной платформой для потребления контента. Но впоследствии стало понятно, что нужно полноценное мобильное приложение. Я реализовал Android-приложение на React Native (Expo) с использованием WebView — это позволило быстро запустить клиент с базовым функционалом.

  • 100% ручная работа
    Backend, frontend, mobile app я писал самостоятельно, чтобы прокачать навыки программирования на Go, Typescript, посмотреть на современные подходы во frontend'е. Однако на практике оказалось, что значительная часть задач — рутинные. После того как я начал активно использовать промпт-программирование с использованием агентов (в частности, Sonnet), темпы разработки выросли в разы. Особенно frontend задачи, будто мне выполняет команда программистов, остается только делать код-ревью.

Ambient и переключение источников

Популярные трансляции на YouTube в стиле “Lofi hip-hop 24/7” натолкнули меня на идею интеграции ambient-звуков в эфир. Liquidsoap оказался гораздо мощнее, чем я предполагал — он позволяет легко создавать многослойный эфир и управлять слоями, например, через Telnet.

Это открывает широкие возможности: можно добавлять в эфир любые ambient-звуки — будь то локальный файл, зацикленный в проигрывании (например дождь), или внешний поток (например, трансляция переговоров авиа-диспетчеров).

Более того, оказалось совсем несложно реализовать кнопку для переключения на живое вещание — например, если есть необходимость переключить трансляцию на человеческого ведущего.

Как это работает:

Liquidsoap поднимает сервер для приёма потока через ffmpeg и, получая аудио от вашего live-ведущего (например, из OBS), выводит его в эфир:

live_source = input.external.rawaudio(
  buffer=0.1,
  max=0.3,
  restart=true,
  restart_on_error=true,
  "while true; do ffmpeg -f flv -listen 1 -i rtmp://0.0.0.0:8181/live -threads 1 -preset ultrafast -tune zerolatency -f wav -acodec pcm_s16le -ac 2 -ar 44100 - 2>/dev/null || sleep 1; done"
)

Остаётся только добавить переключатель с помощью switch в скрипте Liquidsoap — и вы можете динамически переключаться между источниками.

Кроме того, вы можете расширить Telnet-интерфейс собственными командами: включение/отключение ambient-аудио, переход в live-режим, и любые другие функции управления эфиром.

Запуск и остановка радио потока

Изначально проект появился как способ разобраться с Kubernetes. Однако позже я понял, что выбор этой технологии оказался крайне удачным именно для такого рода задач.

После того как пользователь настраивает свой стрим и запускает его, в Kubernetes создаются два Pod'а: один с Liquidsoap, другой — с менеджером эфира, который управляет воспроизведением Liquidsoap через Telnet. Оба Pod'а обязательно должны быть размещены на одной ноде, поскольку менеджер подготавливает нужные аудиофайлы и сохраняет их на диск, откуда их затем воспроизводит Liquidsoap.

Можно было бы объединить оба контейнера в один Pod, но тогда я бы потерял возможность обновлять менеджер эфира без остановки трансляции. Liquidsoap обновляется крайне редко, а вот логика управления эфиром постоянно развивается, и не хотелось бы прерывать вещание при каждом деплое.

Менеджер эфира формирует плейлист с запасом примерно на 10 минут вперёд. Это даёт буфер времени — если что-то пойдёт не так, у меня есть 10 минут, чтобы всё исправить, прежде чем плейлист опустеет.

Остановка стрима происходит аналогично: удалением обоих Pod'ов — с Liquidsoap и менеджера.

Основная нагрузка ложится на Pod с Liquidsoap. Поскольку я использую crossfade, ambient-слои, а также обработку звука (нормализацию и эквалайзер), я не могу использовать passthrough-трансляцию в AAC. В результате, на 7-ядерной виртуальной машине с процессором Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz удаётся стабильно поддерживать около 15 Liquidsoap-подов.

Менеджер эфира почти не потребляет ресурсов — особенно в те моменты, когда виртуальный ведущий не активен.

Виртуальный ведущий

Один из читателей Хабра, работающий в сфере радиовещания уже несколько десятков лет, подсказал интересную идею для реализации виртуального ведущего. Суть в том, чтобы ведущий связывал два трека: в конце одного объявлял его название, вставлял короткую связующую фразу и затем называл следующий трек.

Чтобы не перегружать систему и не сжигать все кредиты у TTS-провайдеров, я решил реализовать предгенерацию озвучек.

Вот как это устроено:

  1. Я заранее сгенерировал аудио версии с названиями всех треков в библиотеке — на русском и английском языках.

  2. Сформировал шаблонные вступительные фразы, например: «Это была композиция...», «Вы слушали...» и тоже их сгенерировал в аудио

  3. Подготовил связующие фразы, такие как: «А далее в эфире...» также в аудио.

  4. На основе этого контента и рандома реализовал сборку аудиофайлов, содержащих фразы вида:
    «Это была композиция X, а далее в эфире — Y».

Далее, при подготовке трека X для плейлиста (если рандом решает что вступит ведущий):

  • Понижаю громкость в конце трека X

  • Накладываю сгенерированную голосовую вставку

  • Склеиваю всё с помощью ffmpeg

  • Добавляю следующий трек Y сразу за X в плейлист, создавая эффект непрерывного и "живого" эфира с участием ведущего.

Таким образом, удалось добиться эффекта настоящего ведущего без дополнительных затрат на онлайн-TTS в реальном времени.

Ретрансляция

Когда у вас уже есть предварительно сконвертированные материалы, готовые для стриминга, например:

  • Аудио: AAC (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 122 kb/s

  • Видео: H.264 (High) (avc1 / 0x31637661), yuv420p (progressive)

Рестриминг выполняется с минимальным потреблением ресурсов, поскольку отсутствует необходимость перекодирования. Пример команды ffmpeg для непрерывного рестриминга:

ffmpeg -stream_loop -1 -re -i video.mp4 -re -i https://app.tunio.ai/live/main_live \
-c:v copy -c:a copy -map 0:v:0 -map 1:a:0 -f flv rtmp://a.rtmp.youtube.com/live2/<key>

Для автоматизации этого процесса я разработал два сервиса:

  • Relay Controller

  • Relay Worker

Relay Controller получает по API payload с параметрами источников (видео, аудио), целевым URL и ключом трансляции. Он находит живой, наименее загруженный worker и передаёт ему команду на запуск трансляции.

Relay Worker, получив команду, проверяет наличие видео-обложки для стрима (если нет — загружает из S3), после чего запускает бесконечный процесс ffmpeg. В случае сбоя ffmpeg автоматически перезапускается через 10 секунд.

Кроме того, Worker при старте регистрируется у Controller’а и регулярно отправляет heartbeat с метриками: загрузкой CPU и списком активных стримов.

Если worker падает или перезапускается, при старте он восстанавливает свои активные трансляции, запрашивая их у Controller’а. Аналогично, если упал controller, worker при следующем heartbeat передаёт ему текущую актуальную информацию о всех запущенных стримах, позволяя системе продолжить работу без потерь.

Что удалось вынести в платформу:

Пока удалось перенести в интерфейс лишь часть ранее реализованного функционала. На текущий момент уже доступны следующие возможности:

  1. Создание и управление потоком
    Пользователь может создать стрим, выбрать музыкальные жанры, задать параметры дополнительного контента: включение/отключение новостей, джинглов и подкастов.

  2. Джинглы
    Можно создать любое количество плейлистов с джинглами, загрузить в них аудиофайлы, прикрепить плейлист к стриму и задать интервал (в количестве треков), с которым джинглы будут автоматически вставляться в эфир.

  3. Объявления
    По предложению одного из читателей Хабра реализована функция озвученных объявлений. Пользователь может ввести текст, выбрать голосовую модель (TTS), прослушать результат, а затем либо сразу воспроизвести объявление в эфире, либо задать интервал для его регулярного звучания.

  4. Регулярные аудиовставки
    Поддерживается загрузка пользовательских аудиофайлов с возможностью указания времени начала и окончания трансляции, а также интервала между повторами (в треках). Такие вставки могут автоматически добавляться в эфир.

  5. Рестриминг
    Пользователь может ретранслировать свой поток на YouTube, VK Live или даже напрямую в группу или канал Telegram, указав соответствующий ключ трансляции.

А что дальше?

С абсолютным удивлением я обнаружил, что существует запрос на такую платформу, где пользователи могли бы создавать собственные стримы с персонализированным контентом, не нарушающим авторские права. В связи с этим я планирую реализовать возможность генерации подкастов индивидуально для каждого пользователя (в настоящее время подкасты создаются глобально — для всей платформы).

С появлением self-hosted TTS-решений эти возможности практически не ограничены. Можно генерировать подкасты по заданному промпту, используя один или несколько виртуальных ведущих. Современные модели стремительно развиваются — некоторые уже умеют воспроизводить дыхание, шумы, интонации и даже эмоции. Лично я считаю, что TTS-технологии в ближайшее время смогут занять значительную часть ниши подкастов — и делать это на весьма достойном уровне.

Кроме того, в планах — предоставить пользователям возможность самостоятельно настраивать тематику новостей, которые будут звучать в их эфирах, а также автоматически генерировать джинглы, поскольку я понимаю, что далеко не все могут создать их вручную.

Заключение

Поскольку тема оказалась довольно востребованной среди читателей, я решил продолжать делиться процессом разработки подобного сервиса, возникающими проблемами и способами их решения. Я по-прежнему открыт к вашим вопросам — после публикации первой статьи получил множество откликов и обсуждений на схожие темы. Спасибо большое всем читателям за это!

Комментарии (10)


  1. copywr1ter
    07.06.2025 19:50

    Интересная задумка. Поздравляю с реализацией. Жаль только, что на Youtube ни одного слушателя. Единственный значимый момент - текущая генерация музыкальных треков в Suno, Riffusion и аналогов в низком битрейде, свистящих звуках. Очень сильно слышно и заметно для слуха. Больше одного трека лично я слушать такую сгенеренную ИИ "музыку" не могу.


    1. icevl Автор
      07.06.2025 19:50

      Спасибо, замечание абсолютно валидное. Некоторые жанры, такие как lofi, chillpop или даже elevator music, действительно звучат вполне достойно. Но я с вами согласен: в целом, качество звука пока уступает студийным трекам, записанным музыкантами.

      Есть ещё один важный момент.

      • В базе уже собрано около 3 тысяч треков, записанных людьми и выпущенных под лицензией Creative Commons. При создании эфира можно выбрать тип музыки — человеческая, AI или комбинированная. Однако, по моим наблюдениям, несмотря на более высокое качество звучания, такие треки часто проигрывают AI-трекам по содержанию. В основном это музыка начинающих артистов или так называемых "bedroom-продюсеров".

      • На текущем этапе я вижу основную нишу в фоновой музыке — для стримеров, кафе и других подобных сценариев, где качество не критично на 100%.

      Но в целом — да, момент, когда нейросетевая музыка достигнет студийного качества, уже не кажется мне таким отдалённым. Поэтому просто жду, пока они повзрослеют ;)


      1. copywr1ter
        07.06.2025 19:50

        В любом случае успехов вам! Идея-то перспективная, соотносящаяся с реалиями текущего времени. Сейчас нейросетевые сервисы и технологии развиваются столь стремительно, что в плане следующего этапа развития ИИ-генерации, полагаю, вы правы.

        У меня к вам вопрос по теме. Возможно сталкивались. Знаю, что сгенерированную музыку без проблем (если самим сервисом разрешено свободное использование сгенеренных треков) можно выкладывать на тот же YouTube, если недвусмысленно указать, что это AI-генерация. Вопрос относительно каверов - можно ли их выкладывать на YouTube. Например, очень условно: "Надежда Кадышева - Широка река (ИИ-кавер)". Важное уточнение - при некоммерческом канале и указании авторства и владельца прав исполнителем песни. Встречал сотни таких треков. И каналы не забанены, соответственно страйков они не боятся. Хотя миллионные просмотры таких треков.


        1. icevl Автор
          07.06.2025 19:50

          Если кто-то использует оригинальный текст или мелодию - даже с AI-голосом - могут возникнуть проблемы с авторским правом. Издатель вполне может подать жалобу. Пока такие случаи редки, но, вероятно, это лишь вопрос времени - правообладатели просто ещё не полностью адаптировались к новым технологиям. Безопаснее всего - получать лицензию или использовать треки с открытой лицензией.

          Вы, могли, заметили, что Suno запрещает генерацию песен с популярными текстами. И даже частичная замена слов не всегда помогает обойти запрет. Это связано с тем, что платформа несёт ответственность, и в случае нарушения авторских прав жалоба сначала поступит именно к ним.


          1. copywr1ter
            07.06.2025 19:50

            Пробовал в Riffusion. Действительно, при добавлении некоторых текстов песен "ругается". Но процентов 90%, которые добавлял спокойно пропускает и генерирует. Смысл каверов именно на популярности и известности оригинального трека, поэтому с открытой лицензией noname треки не интересны. Да, вопрос получения согласия у владельца прав актуален. Тем более при извлечении прибыли - отчисления роялтис. Интересно как каналы с милионными просмотрами таких ИИ-каверов существуют на YouTube уже длительное время. Поэтому и появился такой вопрос.


  1. poriogam
    07.06.2025 19:50

    И музыка на радио тоже ненатуральная?


    1. icevl Автор
      07.06.2025 19:50

      Да, к сожалению, сейчас это единственный компромисс, чтобы легально вещать музыку — как мне самому, так и пользователям платформы. Особенно на платформах вроде YouTube или Twitch — там сразу может прилететь блокировка или отключение звука, если алгоритмы распознают лицензионный контент.

      Кстати, интересный факт: даже если у стримингового сервиса, такого как Spotify, Яндекс Музыка, Apple Music есть лицензия на использование лицензионных треков, это не даёт права использовать эту музыку для публичного воспроизведения — например, в кафе, баре или магазине. Их лицензии покрывают только личное (домашнее) использование. Для того чтобы транслировать музыку в общественном месте, требуется отдельная публичная лицензия (public performance license), которую нужно оформлять через специальные организации по коллективному управлению авторскими правами (в зависимости от страны это могут быть BMI, ASCAP, GEMA, РАО и др.).

      Таким образом, воспроизведение даже легально купленной или стриминговой музыки без соответствующей публичной лицензии может быть расценено как нарушение авторских прав. Для меня стало это открытием.

      В целом, моя основная цель — чтобы пользователи могли вести трансляции и не попадали под санкции.


      1. poriogam
        07.06.2025 19:50

        Надо быть реалистом, без оголтелого ператсва это не взлетит.


        1. icevl Автор
          07.06.2025 19:50

          Пиратская музыка и даже лицензионная сразу обесценивает проект. Стримы с пиратской музыкой всё равно блокируются на YouTube, Twitch и других платформах. Когда проект выходит на серьёзный уровень, рано или поздно приходит РАО — либо ко мне, либо к пользователям, которые создадут радио на моей платформе.

          Допустим у меня есть права на публичное исполнение, но у пользователей, которые создают свои стримы на платформе прав то нет. И им прилетает штраф.


  1. FifthLeg
    07.06.2025 19:50

    Можно попробовать новости в виде песен. Типа даёшь чатгпт основные новости и просишь написать стихи для песни в каком либо стиле. Потом на suno генерируется песню в этом стиле.

    Будет вполне необычно, может кому зайдёт.