javascript

Amazon IVS: как быстро добавить видеотрансляции в приложение или на сайт

  • понедельник, 23 января 2023 г. в 00:48:30
https://habr.com/ru/post/707488/
  • JavaScript
  • Программирование
  • Amazon Web Services
  • Go
  • Облачные сервисы


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 долларов за такую трансляцию:

2 + 500 * 30 / 60 * (0,0375 + 0,0750) = 30,125\$

Заключение

Мы рассмотрели простой пример, как можно воспользоваться 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: