Cloudflare Stream — это мощная платформа для трансляций, которая для многих наших клиентов просто работает. Но что если вы захотите отрисовывать динамические аннотации на прямой трансляции или создать альтернативную версию размещённого видео с вшитыми субтитрами? Для этого потребуется запустить собственный видеопайплайн.
Сегодня мы выпускаем новую площадку для разработчиков, Streamline, которая демонстрирует, как можно построить систему для доставки таких кастомных видеопреложений на Платформе разработчиков Cloudflare. Мы покажем, как Streamline использует Workers, Containers и несколько медиапротоколов для модификации видео — и как сразу публиковать этот вывод как прямую трансляцию или новое размещённое видео. Вы также сможете опробовать это для своих проектов.
Пайплайн обработки требует надёжной, длительно работающей среды, способной запускать специализированный, скомпилированный код с предсказуемыми ресурсами памяти и CPU. Видеопотоки могут идти минуты или часы, поэтому медиапроцесс должен иметь жизненный цикл, независимый от запроса, который его запустил. Приложение должно уметь запускать пайплайн, отправлять ему входные данные, инспектировать состояние и останавливать его, не удерживая один и тот же запрос открытым в течение всего времени работы.
Cloudflare предоставляет примитивы, необходимые нам для этого. Containers — это долгоживущие рантаймы, подходящие для медиапроцессинга. Durable Objects помогают с оркестрацией. Наконец, Workers отлично подходят для управляющей сигнализации и мониторинга.
Для Streamline мы построили медиадвижок, работающий в Container, который обрабатывает медиа в реальном времени. Контейнер контролируется Worker’ом, который предоставляет управление, превью и тестирование для агента или пользователя. Обработка продолжается даже если Worker отключится. Мы спроектировали Streamline с модульными компонентами, чтобы медиадвижок можно было заменить в будущем специализированными продуктами кодирования.
Компоненты Streamline
Развертывание Streamline состоит из двух компонентов: Media Engine, который обрабатывает медиа вход/выход и процессы, и управляющего Application, который создаёт, конфигурирует, наблюдает и останавливает медиа-сессии.
Media Engine содержит два компонента:
- Controller. Это управляющий каркас, написанный на Go, который реализует HTTP-сервер, принимает входящие запросы и переводит их в операции, исполняемые медиадвижком.
- Processor, который выполняет фактическую медиаобработку. Текущая реализация использует FFmpeg, но это внутренняя деталь реализации, а не часть API, доступного пользователю.
Media Engine размещается в Container и обрабатывает весь медиа вход/выход, а также саму обработку. Он может тянуть RTMPS-плейбек по сети с одного Stream Live input и публиковать RTMPS-выход в другой Stream Live input. Он может загружать HLS-манифест Cloudflare Stream и его сегменты, используя размещённые видео как вход. Он может принимать видеовход из источника, предоставленного управляющим приложением, например с веб-камеры. Он может публиковать видео-превью через исходящий WebSocket на реле Durable Object. Приложение, которое нуждается в превью, может подключиться к этому реле через собственный WebSocket.
Приложение строится с использованием Workers и может быть full-stack браузерным приложением, агентом или встраиваемой системой. Оно состоит из:
- Пользовательского интерфейса (UI), включая клиентскую логику, идентификацию и политику доступа. В этом посте в качестве конкретного примера используется браузерное приложение, поэтому включён также интерфейс для браузера.
- Orchestrator координирует сессию, жизненный цикл Container и реле превью. Оркестратор реализован с помощью Durable Object.
Систему можно запускать локально во время разработки, в этом случае контейнер — просто локальный экземпляр Docker, и Durable Object не используется: есть один пользователь, управляющее приложение не требует авторизации для локального доступа, а превью видео может подключаться напрямую к WebSocket на localhost.
Когда эти компоненты развёрнуты в Cloudflare, авторизованный пользователь или агент может зайти на Worker, чтобы запустить новую сессию. Это запускает новый контейнер Streamline при необходимости, автоматически управляет его жизненным циклом, открывает API для выполнения ряда операций по манипуляции видео и маршрутизирует входы от и выходы обратно в Cloudflare Stream.
Технический разбор: как работает система
Управляющее приложение Worker инициирует длительно работающую сессию обработки медиа. После старта сессии приложение может отключиться и снова подключиться безопасно, в то время как Container продолжит обработку до тех пор, пока управляющее приложение не остановит его. Мы также предусматриваем максимальную продолжительность, чтобы сессия в конце концов всегда закрывалась и не могла работать бесконечно без внешнего контроля. Пока сессия обработки медиа запущена, экземпляр контейнера недоступен для использования другими приложениями.
Cloudflare Container автоматически усыпляется, если за заданный интервал не поступали входящие запросы. Однако в нашем случае, как только пайплайн запущен, он должен продолжать работу даже если управляющее приложение отключилось и запросы не приходят. Мы реализуем это поведение, переопределяя обратный вызов onActivityExpired() в контейнере. Если время истечения ещё не достигнуто, мы продлеваем активность, иначе уничтожаем контейнер.
HTTP-сервер, реализованный в Go-каркасе, и Durable Object, связанный с контейнером, вместе определяют низкоуровневый интерфейс к системе. Тем не менее, мы хотели предоставить абстракцию поверх этого, чтобы система была максимально агностична к тому, кто или что управляет сессией, и к ненужным деталям реализации бэкенда.
Мы реализуем это, экспортируя два пакета из Streamline:
- @cloudflare/streamline/client Определяет высокоуровневое session-ориентированное API.
- @cloudflare/streamline Экспонирует базовый класс Durable Object, связанный с контейнером. Он маршрутизирует API-запросы, реализует сервер реле превью (описанный ниже) и предоставляет хуки для безопасности и политики доступа.
В удалённом развертывании ожидается, что управляющий Worker импортирует @streamline/cloudflare и определит конкретный подкласс Durable Object, экспортируемого контейнером, который можно использовать для логики приложения и хранения данных.
В локальном режиме, где Durable Object отсутствует, фронтенд определяет тонкий адаптерный слой, который поддерживает session-ориентированное API, но подключается напрямую к локальному Docker-экземпляру без контролей доступа и т. п.
Пример использования API
Пример ниже показывает, как управляющее приложение может использовать API для доступа к Streamline, подготовки сессии и запуска видеопайплайна.
config — это JSON-объект, определяющий выполняемый пайплайн обработки, подробнее описанный в последующих разделах.
Полный список всех вызовов API включает:
- Отправку чанка видеоданных в режиме «вебкам».
- Обновление прозрачного слоя с аннотацией.
- Получение метрик о текущей сессии.
- Остановку обработки в текущей сессии.
Определение и запуск видеопайплайна
session.start() конструирует и запускает пайплайн обработки. Он принимает один аргумент — JSON-конфигурацию, описывающую выполняемую обработку.
Пример запускает пайплайн, который берёт RTMP (real-time messaging protocol) трансляцию как вход (например, поток с Stream Live input, принимающий входящую трансляцию), применяет оверлей-изображение с прозрачностью и отправляет выход на RTMP-назначение (например, на другой Stream Live input для записи или трансляции). Это позволяет приложению Worker в реальном времени создавать модифицированную версию прямой трансляции.
Streamline также может принимать входящие стримы через HLS (HTTP Live Streaming), например видео, размещённое в Cloudflare Stream. Пример показывает, как Worker-приложение может запустить пайплайн, который загружает Stream-видео, читает встроенные субтитры (closed caption) и рендерит их как текст на видео, а затем отправляет выход через RTMP, например в Stream Live Input для трансляции или записи модифицированной версии.
Часто полезно быстро просмотреть результат пайплайна, отправляя видеоданные прямо в Streamline, например с веб-камеры. Агент или приложение встраиваемого устройства может также использовать эту возможность, например, чтобы отправлять footage с заводских камер для AI-анализа или комбинировать несколько камер в составной вид.
Пример создаёт пайплайн, который ожидает ввод от Worker-приложения и создаёт превью-видео вывод, доступный через WebSocket (об этом далее). Он применяет два фильтра и «аннотацию» — оверлей, заданный как PNG-изображение, которое можно обновлять во время выполнения, например для реализации анимированной графики.
Приведённый кодовый фрагмент просто запускает пайплайн. Управляющий Worker пока не отправляет медиа в Streamline. Мы обсудим функцию openViewer() ниже.
Управляющее приложение отправляет видеоданные в Streamline с помощью вызова session.ingest(). Пример демонстрирует, как веб-приложение может получать чанки с веб-камеры и пересылать их в Streamline.
Оверлей-аннотацию можно обновлять через вызов session.annotation(). Пример показывает, как приложение Worker может сделать снимок canvas и отправить его в Streamline. Это можно выполнять в цикле анимации, хотя фактическая частота обновлений может ограничиваться размером PNG-аннотаций, доступной пропускной способностью и мощностью обработки.
Приём превью-видео от Streamline
Streamline может также производить превью-видео, если указать output: { mode: ‘websocket’ }.
Streamline использует WebSocket для низкозадержечной доставки превью-видео обратно управляющему приложению: контейнер публикует fMP4-фрагменты в Durable Object, который пересылает их на выходное реле, доступное по WebSocket по пути /relay/view относительно origin приложения. Приложение должно подключить WebSocket к этому URL и будет получать видеоданные по мере их появления из Streamline. Пример показывает, как браузерное приложение может отобразить фид превью.
В production-плеере на базе MediaSource необходимо ставить фрагменты в очередь, пока SourceBuffer.updating равно true. В локальной разработке браузер или другое управляющее приложение просто открывает WebSocket-соединение напрямую к локальному контейнеру.
В конфигурационном объекте, передаваемом в session.start() в примерах выше, pipeline — это массив операций из набора, поддерживаемого медиадвижком. Порядок операций в настоящее время фиксируется движком; порядок в массиве не принципиален. Текущий список поддерживаемых операций и порядок их применения:
- Применение фильтров, например blur, saturation.
- Наложение изображения, указанного по URL или как бинарный PNG, переданный отдельно в вызове annotation().
- Указание параметров кодирования вывода.
Поскольку это Cloudflare, безопасность должна быть частью дизайна, а не добавлением в конце. Нужно обеспечить, чтобы только авторизованные пользователи могли создавать новые сессии или брать под контроль существующие, а сессии были изолированы друг от друга. Необходимо обращаться с ключами Stream RTMPS input/output как с секретами, которые не должны утекать в управляющее приложение. Также нужно гарантировать ограниченность использования ресурсов.
Деплой владельца оставляется приватным с использованием интеграции Workers’ Access. Настроенная identity владельца и другие разрешённые пользователи могут редактировать одинаковые общие профили и запускать сессии, пока singleton не занят. Worker проверяет Access-сессию перед принятием управляющих запросов и связывает активную сессию с верифицированным субъектом. В любой момент может выполняться только одна сессия, и другой субъект не сможет остановить или заменить текущую сессию.
Ключи Stream Live Input хранятся в секретах Worker или как запись только для записи в Durable Object storage. Они никогда не возвращаются через settings API и не сохраняются в браузерном хранилище. Управляющее приложение указывает RTMPS вход и выход, ссылаясь на именованный профиль. Worker разрешает профиль перед обращением к контейнеру.
Поток превью-видео использует две учётные записи с разными целями. Cloudflare Access service token аутентифицирует контейнерную рабочую нагрузку перед endpoint-публикатором. Случайная per-session capability авторизует публикацию только для текущего активного реле. Service token внедряется исходящим Worker’ом контейнера и никогда не попадает в память контейнера. На начальном этапе развертывания используется временный path-specific Access Bypass, в то время как per-session capability остаётся обеспеченным; после развертывания и успешного smoke-теста обход заменяется Service Auth.
Деплой владельца намеренно приватен и маршрутизируется как singleton. Это не модель безопасности для публичного многопользовательского сервиса.
Мы хотим, чтобы вы опробовали Streamline и начали строить! Поэтому вместе с этой публикацией мы открываем код системы как open source и разворачиваем публичную площадку.
Контейнер Streamline можно запускать локально или развернуть в вашем аккаунте. Он экспортирует Worker API для использования вашим управляющим приложением.
Есть также пример управляющего Worker-приложения с фронтендом на Astro, демонстрирующий функциональность Streamline с несколькими типичными сценариями использования, включая оверлеи, декодирование субтитров, фильтры и picture-in-picture. Имеется функциональность probe, которая предоставляет метрики производительности и трассировку системы и может быть полезна для отладки при разработке новых функций. Примерное приложение можно запускать на локальном Astro-сервере или развернуть за Cloudflare Access, чтобы вы могли контролировать, кто имеет доступ к вашему экземпляру Streamline.
Оба репозитория доступны как open source на GitHub Cloudflare:
- https://github.com/cloudflare/streamline — медиадвижок и экспорт пакетов для приложений.
- https://github.com/cloudflare/streamline-demo — примерное приложение Worker, фронтенд на Astro, профили развертывания и инструменты Access.
Мы опубликовали публичное развёртывание площадки с примерным приложением. Пользователь также может развернуть такое у себя. Площадка использует собственную конфигурацию Access, одну идентичность контейнера на проверенного пользователя, одну активную сессию на пользователя, глобальный admission control, ограничения по параллелизму, медиа и сессиям, и не позволяет одному пользователю заменить сессию другого пользователя.
Вы можете опробовать публичную площадку по адресу:
https://playground.streamline-video.workers.dev
Streamline демонстрирует один из способов сочетать управляемые сервисы, такие как Stream, с низкоуровневыми примитивами для создания высоконастраиваемых медиапайплайнов. В этой итерации Streamline использует CPU контейнера для медиапроцессинга, что создаёт узкое место при более высоких качествах или частотах кадров.
Далее нам интересно, как мы и сообщество разработчиков сможем расширить эту архитектуру для поддержки компьютерного зрения, аппаратно-ускоренной обработки медиа, realtime-опыта с протоколами следующего поколения, такими как WebRTC и MoQ, и в конечном итоге для добавления примитивов кодирования и декодирования видео нативно в Workers.
Сегодня мы приглашаем вас посмотреть наш размещённый демо-пример Streamline, чтобы увидеть, насколько мощными могут быть эти инструменты. Затем изучите открытые кодовые базы, чтобы увидеть, как просто развернуть Streamline в своём аккаунте и использовать его для создания собственных решений.
Подпишитесь, чтобы получать уведомления о новых публикациях.
