Video live streaming - это технология потоковой трансляции видео тысячам и миллионам зрителей в режиме близком к реальному времени. Видеостриминг находит своё применение в различных областях:
фитнес и образование;
онлайн события: презентации, конференции, семинары;
электронная коммерция;
музыкальные концерты;
игры и киберспорт.
В этой статье мы рассмотрим, как можно легко интегрировать видеотрансляции в приложение или на сайт, используя Amazon Interactive Video Service.
Что такое AWS Interactive Video Service?
AWS Interactive Video Service - это решение для прямых трансляций, которое быстро интегрируется в мобильные и веб-приложения.
В рамках своего сервиса Amazon предлагает:
быстрое создание новых трансляций в течении пары минут;
низкая (менее 3 секунд) задержка доставки видеопотока для зрителей;
простая интеграция с мобильными приложениями и веб-сайтами через SDK для трансляции и проигрывания видео;
масштабирование до десятков тысяч одновременных зрителей;
сохранение трансляции для просмотра в записи.
Ещё одна интересная возможность - это встраивание в трансляцию метаданных с гарантией их получения всеми зрителями. Метаданные будут синхронизированы с видео- и аудиопотоками. С помощью этой функциональности можно добавлять интерактивное взаимодействие со зрителями:
обновлять статистику в видеоигре;
показывать зрителю опросник и давать возможность выбрать ответ во время вебинара;
предоставлять информацию о продукте и давать возможность купить демонстрируемый товар во время трансляций в e-commerce приложениях.
Amazon предоставляет SDK для iOS, Android и Web для интеграции вещания и проигрывания видео. Также на сайте AWS можно найти SDK для работы с IVS более чем на 10 языках программирования, включая Java, Python и Go.
Чтобы понять как работать с AWS IVS, давайте реализуем пример, состоящий из трёх компонент:
стриминг видео из браузера;
проигрывание видео в браузере;
управление каналами и отправка метаданных зрителям на бэкенде.
Отправка видео- и аудиопотоков
Начнём с создания минималистичной веб-страницы, на которой мы организуем вещание видеотрансляции.
Создадим html страницу и включим туда ссылку на broadcast SDK:
<script src="https://web-broadcast.live-video.net/1.2.0/amazon-ivs-web-broadcast.js"></script>
Далее нужно добавить canvas для отображения видео, которое мы будем транслировать:
<canvas id="preview"></canvas>
Добавим также на страницу input элементы для ingest endpoint и stream key:
<section>
<label for="ingest-endpoint">Ingest Endpoint</label>
<input type="text" id="ingest-endpoint" value="" />
</section>
<section>
<label for="stream-key">Stream Key</label>
<input type="text" id="stream-key" value="" />
</section>
Ingest endpoint - это url сервера Amazon, куда будут отправляться видео- и аудиопотоки. Stream Key - текстовый ключ, который уникально идентифицирует канал вещания. Они предоставляются Amazon после создания канала.
Ну и ещё нам понадобятся 2 кнопки для запуска и остановки трансляции:
<button class="button" id="start" onclick="startBroadcast()">Start Broadcast</button>
<button class="button" id="stop" onclick="stopBroadcast()">Stop Broadcast</button>
Как вы видите, на событиях onclick вызываются функции startBroadcast()
и stopBroadcast()
. Про них поговорим чуть позже.
С веб-страницей мы закончили, поэтому теперь перейдем к javascript коду.
Первым делом нам надо создать broadcast client:
const streamConfig = IVSBroadcastClient.BASIC_LANDSCAPE;
const client = IVSBroadcastClient.create({
streamConfig: streamConfig,
});
SDK предлагает несколько предопределенных конфигураций, которыми можно воспользоваться. Полный список можно посмотреть в документации к библиотеке. Давайте для примера возьмём IVSBroadcastClient.BASIC_LANDSCAPE
.
Чтобы отобразить на странице видео, которое мы будем захватывать с помощью камеры, привяжем созданный клиент к элементу preview
:
const previewEl = document.getElementById('preview');
client.attachPreview(previewEl);
Чтобы получить список устройств, с которых мы можем производить захват видео и аудио, сделаем navigator.mediaDevices.enumerateDevices()
:
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter((d) => d.kind === 'videoinput');
const audioDevices = devices.filter((d) => d.kind === 'audioinput');
Теперь нужно получить поток одного из доступных видео устройств и зарегистрировать его в broadcast client:
const cameraStream = await navigator.mediaDevices.getUserMedia({
video: {
deviceId: videoDevices[0].deviceId,
width: {
ideal: streamConfig.maxResolution.width,
max: streamConfig.maxResolution.width,
},
height: {
ideal: streamConfig.maxResolution.height,
max: streamConfig.maxResolution.height,
},
},
});
client.addVideoInputDevice(cameraStream, 'camera1', {index: 0});
То же самое нужно сделать и для аудиопотока:
const microphoneStream = await navigator.mediaDevices.getUserMedia({
audio: {deviceId: audioDevices[0].deviceId},
});
client.addAudioInputDevice(microphoneStream, 'mic1');
На этом мы закончили с конфигурацией клиента. Если открыть получившуюся html страницу, то браузер запросит разрешение на использование видеокамеры и микрофона. После полученных разрешений на странице появится изображение с камеры.
Всё почти готово к отправке медиапотоков на сервера AWS и началу трансляции. Осталось несколько штрихов.
Во-первых, напишем обработчика для кнопки Start Broadcast
:
async function startBroadcast() {
const streamKeyEl = document.getElementById("stream-key");
const endpointEl = document.getElementById("ingest-endpoint");
const start = document.getElementById("start");
try {
start.disabled = true;
await window.client.startBroadcast(streamKeyEl.value, endpointEl.value);
} catch (err) {
start.disabled = false;
setError(err.toString());
}
}
Тут мы получаем ingest endpoint и stream key из input элементов на странице и вызываем метод startBroadcast()
у клиента.
Во-вторых, сделаем остановку вещания через метод stopBroadcast
:
async function stopBroadcast() {
const start = document.getElementById("start");
start.disabled = false;
try {
await window.client.stopBroadcast();
} catch (err) {
setError(err.toString());
}
}
Итак, для вещания трансляции всё готово.
Проигрывание видеопотока
Перейдем к интеграции веб-плеера для проигрывания трансляции зрителям. Также, как и в примере с вещанием, создадим простую веб-страницу.
Включим ссылку на Amazon IVS Player на нашу страницу:
<script src="https://player.live-video.net/1.14.0/amazon-ivs-player.min.js">
Добавим на страницу 3 блока: поле для ввода url видеотрансляции, счётчик онлайн зрителей и видео элемент для плеера:
<label for="playback-url">Playback URL</label>
<input type="text" id="playback-url" value=""/>
Online viewers:
<span id="viewers">0</span>
<video id="video-player" controls playsinline></video>
С html кодом для страницы плеера закончили. Теперь перейдем к javascript коду.
Первым делом проверим, поддерживает ли браузер плеер от Amazon:
if (!IVSPlayer.isPlayerSupported) {
setError("Player is not supported by browser");
}
Далее создадим сам плеер и привяжем его к video элементу на странице:
function createPlayer() {
const player = IVSPlayer.create();
player.attachHTMLVideoElement(document.getElementById('video-player'));
return player;
}
У плеера есть метод addEventListener
, через который можно добавить подписчиков на изменения состояния трансляции и получать уведомления о них. В нашем примере нас интересуют 2 события: об ошибке и о полученных метаданных. Именно через метаданные потока мы будем передавать текущее количество онлайн зрителей.
Напишем функцию, которая будет выводить ошибку пользователю на события ERROR и обновлять значение в элементе viewers
при получении новых метаданных в трансляции:
function attachEventListeners(player) {
const PlayerEventType = IVSPlayer.PlayerEventType;
player.addEventListener(PlayerEventType.ERROR, function (err) {
setError(err.message);
});
player.addEventListener(PlayerEventType.TEXT_METADATA_CUE, function func(e) {
const viewersEl = document.getElementById("viewers");
viewersEl.innerHTML = e.text;
});
}
Всё почти готово. Нам только осталось запустить проигрывание потока. Для этого у плеера есть функция load
, которая принимает url видеопотока. Чтобы запустить проигрывание, нужно вызвать play()
. Для запуска проигрывания подпишемся на обновления поля playback-url
:
function initURLInput() {
const urlInput = document.getElementById("playback-url");
urlInput.addEventListener("input", function(e) {
player.load(e.target.value);
player.play();
}, true);
}
Проигрывание готово.
Работа с AWS на бэкенде
Вещания и проигрывания трансляций недостаточно, чтобы интегрировать AWS Interactive Video Service. Наверняка вы не захотите создавать трансляции вручную, поэтому потребуется автоматизация на бэкенде. Amazon предоставляет SDK для взаимодействия с API их сервисов для большинства популярных языков программирования. В нашем примере давайте напишем бэкенд на Golang.
Создадим go module в директории проекта:
go mod init github.com/mastersobg/aws-ivs
Подключим в проект зависимости на aws sdk, прописам их в go.mod
:
go get github.com/aws/aws-sdk-go-v2/service/ivs
go get github.com/aws/aws-sdk-go-v2/config
Чтобы подключиться к API Amazon из кода, надо получить AWS access keys. Это можно сделать в веб-интерфейсе AWS:
перейдите в IAM console
в меню слева выберите Users
далее выберите своего пользователя или создайте нового
во вкладке Security credentials нажмите Create access key
скачайте созданные секреты и сохраните в
~/.aws/credentials
Также нужно в файл ~/.aws/config
сохранить регион по умолчанию, в котором у нас будут трансляции:
[default]
region = eu-west-1
Перейдем к написанию клиента, который будет в нашем бэкенде обёрткой к обращениям к AWS API.
Сначала загрузим конфигурацию из ~/.aws/config
и создадим клиент к API IVS:
import (
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ivs"
)
type Client struct {
client *ivs.Client
}
func NewClient(ctx context.Context) (*Client, error) {
// Load the shared AWS configuration from ~/.aws/config
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
// Create an IVS service client
return &Client{
client: ivs.NewFromConfig(cfg),
}, nil
}
Теперь мы можем использовать созданный клиент для общения с API IVS.
Перейдем к созданию нового Channel (канал), в который можно будет транслировать видео. Канал в терминах Amazon хранит всю конфигурацию, относящуюся к трансляции. При создании канала можно указать несколько важных настроек:
LatencyMode
. Данный параметр принимает 2 значения:NORMAL
иLOW
.LOW
значение по умолчанию и означает низкую задержку доставки медиапотока зрителям.Type
. Тип канала может бытьBASIC
илиSTANDARD
.BASIC
означает, что видео будет доставляться зрителям без перекодирования.STANDARD
подразумевает, что видео будет перекодировано и оптимизировано под устройства пользователей и состояние их сети. По умолчанию используется режимSTANDARD
.Name
. Можно задать имя, чтобы было проще ориентироваться между каналами. Например, вName
можно указать текстовыйid
трансляции во внутренней системе.
func (c *Client) CreateChannel(ctx context.Context, name string) (*types.Channel, *types.StreamKey, error) {
resp, err := c.client.CreateChannel(ctx, &ivs.CreateChannelInput{
Name: aws.String(name),
})
if err != nil {
return nil, nil, err
}
return resp.Channel, resp.StreamKey, nil
}
Метод CreateChannel()
возвращает описание Channel. Нас оттуда интересуют следующие поля:
Arn
- уникальный amazon resource name. Его нужно куда-нибудь сохранить, например, в базу, т.к. он нам понадобится позднее для работы с каналом через API Amazon.IngestEndpoint
иStreamKey
- этот url и ключ, которые понадобятся для отправки видео- и аудиопотоков.PlaybackUrl
- это url для просмотра видеопотока зрителями.
Запустим, что у нас получилось к данному моменту:
func run(ctx context.Context) error {
client, err := ivsclient.NewClient(ctx)
if err != nil {
return err
}
channel, err = client.CreateChannel(ctx, "test")
if err != nil {
return err
}
}
Если вы перейдете в раздел IVS в интерфейсе AWS, то увидите в списке новый канал под названием test.
Ранее мы обсудили, что будем через метаданные потока передавать текущее количество онлайн зрителей. API Amazon предоставляет объект Stream
, в котором представлены следующие поля:
ViewerCount
- текущее количество зрителей.State
- состояние стрима, принимает значенияLIVE
иOFFLINE
.Health
обозначает уровень "здоровья" стрима. Доступные значенияHEALTHY
,STARVING
иUNKNOWN
. СостояниеSTARVING
означает, что есть проблемы с отправкой видео/аудиопотоков. Например, у ведущего трансляции наблюдаются проблемы с интернетом, и происходят задержки отправки потоков.StartTime
- время начала трансляции.
Полный список доступных полей можно посмотреть в документации. Нас больше всего интересует поле ViewCount
. Именно его мы будем использовать, чтобы отправлять эти данные зрителям.
Чтобы получить данные о Stream
, мы можем воспользоваться методом GetStream()
. На входе ему надо передать ARN канала:
func (c *Client) GetStream(ctx context.Context, channelARN *string) (*types.Stream, error) {
resp, err := c.client.GetStream(ctx, &ivs.GetStreamInput{
ChannelArn: channelARN,
})
if err != nil {
return nil, err
}
return resp.Stream, nil
}
Чтобы добавить метаданные в медиапоток, существует метод PutMetadata()
. Этот метод принимает ARN канала и строку-метаданные. Важно отметить, что максимальный размер метаданных, которые можно передать за один вызов, ограничен 1KB. Это ограничение стоит держать в голове, потому что передавать мегабайты текста, к сожалению, не получится. Также при вызове PutMetadata()
стоит сделать проверку, находится ли Stream
в состоянии вещания (LIVE
). Иначе AWS вернёт ошибку ChannelNotBroadcasting
.
func (c *Client) PutMetadata(ctx context.Context, channelARN *string, metadata string) error {
_, err := c.client.PutMetadata(ctx, &ivs.PutMetadataInput{
ChannelArn: channelARN,
Metadata: &metadata,
})
return err
}
Теперь объединим методы GetStream
и PutMetadata
и реализуем функцию streamOnlineViewers
:
func streamOnlineViewers(ctx context.Context, client *ivsclient.Client) error {
if channelArn == nil || len(*channelArn) == 0 {
return errors.New("empty channel arn")
}
for {
stream, err := client.GetStream(ctx, channelArn)
if err != nil {
return err
}
if stream.State == types.StreamStateStreamLive {
metadata := strconv.FormatInt(stream.ViewerCount, 10)
if err := client.PutMetadata(ctx, channelArn, metadata); err != nil {
return nil
}
}
time.Sleep(time.Second)
}
return nil
}
Данная функция в цикле запрашивает состояние и отправляет актуальный счётчик онлайн зрителей в метаданные трансляции. Стоит заметить, что метаданные также будут доступны и в записи трансляции.
Ну и напоследок давайте реализуем удаление канала. Дело в том, что у AWS есть лимит на количество каналов - 5000 в одном регионе. Поэтому после окончания трансляции имеет смысл канал удалить:
func (c *Client) DeleteChannel(ctx context.Context, channelARN *string) error {
_, err := c.client.DeleteChannel(ctx, &ivs.DeleteChannelInput{
Arn: channelARN,
})
return err
}
Проверяем всё вместе
Теперь давайте посмотрим, что у нас получилось. В одной вкладке браузера откроем страницу с трансляцией видеопотока и дадим разрешение на доступ к камере и микрофону. На другой откроем страницу с плеером.
Запустим наш Go код, чтобы создать новый канал:
$ go run . --operation=createChannel
Channel arn: arn:aws:ivs:eu-central-1:064934160738:channel/3mLp3ITPmt9t
Ingest endpoint: 9c9ef534b100.global-contribute.live-video.net
Playback url: https://9c9ef534b100.eu-central-1.playback.live-video.net/api/video/v1/eu-central-1.064934160738.channel.3mLp3ITPmt9t.m3u8
Stream key: sk_eu-central-1_WgIXgdnD8L6G_DiLVpk66XYPD2iOT0SHLa12iJFL58H
Далее запустим обновление счётчика онлайн зрителей:
$ go run . --operation=streamOnlineViewers --channelArn=arn:aws:ivs:eu-central-1:064934160738:channel/3mLp3ITPmt9t
Скопируем ingest endpoint и stream key на вкладку с трансляцией видео. Вставим playback url в соответствующее поле на вкладке с плеером. Запустим трансляцию, нажав на кнопку Start Broadcast
.
На скриншоте видно, что трансляция успешно запустилась, а плеер корректно проигрывает видеопоток. Задержка составляет 4.5 секунды, что немного больше 3 секунд, но тоже достаточно близко. Также видим, что счётчик зрителей показывает 1. Если мы откроем плеер в другой вкладке или зайдем в админку AWS IVS, то счётчик обновится.
После окончания трансляции удалим канал:
$ go run . --operation=deleteChannel --channelArn=arn:aws:ivs:eu-central-1:064934160738:channel/3mLp3ITPmt9t
Сколько это стоит
Amazon Interactive Video Service тарифицирует отдельно отправку видео на их серверы (video input) и доставку до зрителей (video output).
Отправка оплачивается за час видеопотока. В зависимости от типа выбранного канала (basic или standard) цена за час составляет $0.2 или $2.
Доставка также оплачивается за час видеопотока. Цены существенно различаются в зависимости от региона зрителей и общего количества часов, которые были просмотрены.
Чтобы понять, сколько это может стоить, посчитаем на примере:
1 час видеотрансляции;
1000 зрителей из Европы, в среднем каждый посмотрел по 30 минут;
одна половина зрителей смотрели в HD, а другая - в FullHD.
Итого получаем 30 долларов за такую трансляцию:
Заключение
Мы рассмотрели простой пример, как можно воспользоваться Amazon Interactive Video Service для организации трансляций с низкой задержкой доставки видео зрителям. Amazon сделали удобный сервис, который скрывает всю сложность работы с видеотрансляциями за простым интерфейсом библиотек для разных языков.
Interactive Video Service предлагает и другие возможности, которые мы не рассмотрели в этой статье. Например, сохранение трансляций в S3 с возможностью просмотра зрителями в записи. Другой интересной возможностью является IVS Chat для организации общения зрителей во время трансляций. При интеграции с IVS также полезно получать события об изменениях в состоянии трансляций (начало/конец, ошибки, запись стала доступна в S3 для просмотра) через сервис AWS EventBridge. AWS IVS публикует события в EventBridge, на которые можно подписаться в своём бэкенд сервисе.
Из минусов Interactive Video Service можно выделить стоимость сервиса: с ростом объёмов цена может начать кусаться. С другой стороны для быстрого старта и проверки полезности функции видеотрансляций для ваших пользователей Amazon Interactive Video Service может отлично подойти.
Ну и в заключение список полезных ссылок, которые могут помочь в интеграции Interactive Video Service:
https://ivs.rocks/ - сайт с примерами применения.
https://aws.amazon.com/ru/developer/tools/ - доступные SDK.
https://docs.aws.amazon.com/ivs/latest/userguide/what-is.html - руководство пользователя.
https://aws.amazon.com/ivs/pricing/ - цены на сервис.
https://github.com/mastersobg/awsivs - репозиторий с исходным кодом для данной статьи.