Publish the clip as a screen share alongside the audio

VIDEO_ENABLED makes the bot publish a second LiveKit track with the
picture. Stoat only grants screen_share in the call token when the bot
has the Video permission and the instance has video enabled, so a refusal
is reported in chat and playback continues with sound alone.

Two constraints shaped the pipeline, both found by testing rather than
assumption:

- Only progressive formats can be streamed. Separate video+audio streams
  make yt-dlp download both in full before muxing a single byte, and
  direct CDN URLs handed to ffmpeg simply hang — YouTube no longer serves
  them to other clients. That caps video at the 360p single file YouTube
  offers, and the format is checked before committing to the video path,
  since audio would otherwise come from the same broken pipeline.
- One ffmpeg with two outputs, paced by -re: an unpaced decode races
  ahead of the sound and eats memory at 1.4 MB per frame.

The same CDN-URL finding removes the audio seek shortcut, which resolved
such a URL and would have hung the same way; seeking now decodes up to
the offset like it already did behind a proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-09-09 01:10:11 +03:00
co-authored by Claude Opus 5
parent 24dfcf9232
commit 49b87dc172
10 changed files with 387 additions and 23 deletions
+30
View File
@@ -225,6 +225,36 @@ docker compose build --build-arg YTDLP_VERSION=$(date +%Y.%m.%d) && docker compo
Актуальный тег — на [странице релизов yt-dlp](https://github.com/yt-dlp/yt-dlp/releases).
## Видео: клип как демонстрация экрана
`VIDEO_ENABLED=true` — и вместе со звуком бот публикует картинку клипа отдельной дорожкой
(screen share), которую видно в голосовом канале.
```dotenv
VIDEO_ENABLED=true
VIDEO_WIDTH=640
VIDEO_HEIGHT=360
VIDEO_FPS=24
```
Что нужно на стороне Stoat: у роли бота — право **Video** в канале, а на инстансе включённое
видео (`VIDEO_ENABLED` при генерации конфига, он же `video_resolution` в `Revolt.toml`). Токен
на вход в звонок выдаёт разрешение публиковать `screen_share` только при обоих условиях; иначе
бот сообщит в чат, что видео недоступно, и продолжит играть звук.
Как это устроено и почему так:
- **Только YouTube и только 360p.** Стримить можно лишь прогрессивный формат — один файл со
звуком и картинкой. Раздельные дорожки (720p и выше) yt-dlp обязан сначала скачать целиком
и лишь потом склеить, то есть воспроизведение началось бы после полной загрузки. Отдавать
ffmpeg прямые ссылки на CDN тоже нельзя: YouTube их для сторонних клиентов подвешивает.
- **Один процесс ffmpeg, два выхода:** PCM для голосовой дорожки и сырые I420-кадры для видео.
Темп задаёт `-re`, иначе кадры улетали бы вперёд звука и съедали память — распакованный кадр
720p весит 1.4 МБ.
- **Цена.** Кодирование видео ложится на CPU сервера и держится всё время трека, в отличие от
почти бесплатного звука. Ставьте `VIDEO_FPS` пониже, если нагрузка мешает.
- Живые трансляции и не-YouTube источники играют звуком, как раньше.
## Прокси, когда YouTube недоступен
`YTDLP_PROXY` пропускает через прокси **все** обращения yt-dlp — поиск, метаданные и сам