Захват VOD из файла¶
WCS предоставляет возможность захвата медиапотока из файла MP4, расположенного на локальном диске сервера (Video on Demand, VOD). Полученный поток можно воспроизвести, ретранслировать, управлять им, как любым потоком на WCS-сервере. Прежде всего, данная возможность предназначена для воспроизведения записанных ранее трансляций в браузере или мобильном приложении клиента.
Описание¶
Для захвата VOD из файла в качестве имени потока при вызове функции session.createStream()
должна быть указана ссылка на файл в виде:
где sample.mp4
- имя файла, который должен находиться в каталоге /usr/local/FlashphonerWebCallServer/media
. Начиная со сборки 5.2.687, каталог для размещения файлов, может быть указан при помощи настройки в файле flashphoner.properties
В случае, если файл с таким именем отсутствует, сервер вернет событие STREAM_STATUS.FAILED
, в поле info
которого будет указан диагноз File not found
.
Поток, созданный таким образом, предназначен для трансляции одному пользователю (персональный VOD). На этот поток нельзя подписаться, его нельзя транскодировать, добавить в микшер или проиграть по HLS
В случае, если необходимо организовать полноценную онлайн-трансляцию, следует указать ссылку на файл в виде:
К такому потоку могут подключиться одновременно несколько пользователей в реальном времени, он может быть транскодирован, добавлен в микшер или воспроизведен по HLS.
Поддерживаемые форматы и кодеки¶
- Контейнер: MP4
- Видео: H.264
- Аудио: AAC
Схема работы¶
- Браузер соединяется с сервером по протоколу Websocket и отправляет команду
publishStream
. - Браузер захватывает микрофон и камеру и отправляет WebRTC поток H.264 + AAC на сервер с параметром
record: true
. - WCS-сервер записывает поток в файл.
- Браузер останавливает публикацию.
- Второй браузер устанавливает соединение по Websocket, создает поток с указанием имени файла и отправляет команду
playStream
. - Второй браузер получает WebRTC поток и воспроизводит этот поток на странице.
Краткое руководство по тестированию¶
-
Для теста используем веб-приложение Player для воспроизведения файла.
-
Загрузите файл в каталог
/usr/local/FlashphonerWebCallServer/media/
-
Откройте веб-приложение Player, укажите в поле
Stream
имя файла:
-
Нажмите
Start
. Начнется воспроизведение файла:
-
Нажмите
Stop
для остановки воспроизведения. -
Удалите файл из каталога
/usr/local/FlashphonerWebCallServer/media/
-
Нажмите
Start
. Отобразится статусFAILED
и сообщениеFile not found
:
Последовательность выполнения операций¶
Ниже описана последовательность вызовов при использовании:
- примера Stream Recording для публикации потока и записи файла
- recording.html
-
примера Player для воспроизведения VOD-потока
- player.html
- player.js
-
Установка соединения с сервером для публикации и записи потока
Flashphoner.createSession()
code
-
Получение от сервера события, подтверждающего успешное соединение
SESSION_STATUS.ESTABLISHED
code
-
Публикация потока с указанием признака записи
Stream.publish()
code -
Получение от сервера события, подтверждающего успешную публикацию потока
STREAM_STATUS.PUBLISHING
code
session.createStream({ name: streamName, display: localVideo, record: true, receiveVideo: false, receiveAudio: false }).on(STREAM_STATUS.PUBLISHING, function(stream) { setStatus(stream.status()); onStarted(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { ... }).on(STREAM_STATUS.FAILED, function(stream) { ... }).publish();
-
Отправка аудио-видео потока по WebRTC
-
Остановка публикации потока
Stream.stop()
code
-
Получение от сервера события, подтверждающего остановку публикации потока
STREAM_STATUS.UNPUBLISHED
code
session.createStream({ name: streamName, display: localVideo, record: true, receiveVideo: false, receiveAudio: false }).on(STREAM_STATUS.PUBLISHING, function(stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { setStatus(stream.status()); showDownloadLink(stream.getRecordInfo()); onStopped(); }).on(STREAM_STATUS.FAILED, function(stream) { ... }).publish();
-
Установка соединения с сервером для воспроизведения потока
Flashphoner.createSession()
code -
Получение от сервера события, подтверждающего успешное соединение
SESSION_STATUS.ESTABLISHED
code
-
Воспроизведение потока
Stream.play()
code
if (Flashphoner.getMediaProviders()[0] === "MSE" && mseCutByIFrameOnly) { options.mediaConnectionConstraints = { cutByIFrameOnly: mseCutByIFrameOnly } } if (resolution_for_wsplayer) { options.playWidth = resolution_for_wsplayer.playWidth; options.playHeight = resolution_for_wsplayer.playHeight; } else if (resolution) { options.playWidth = resolution.split("x")[0]; options.playHeight = resolution.split("x")[1]; } stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }); stream.play();
-
Получение от сервера события, подтверждающего успешное воспроизведение потока.
STREAM_STATUS.PLAYING
code
stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }).on(STREAM_STATUS.PLAYING, function(stream) { $("#preloader").show(); setStatus(stream.status()); onStarted(stream); }).on(STREAM_STATUS.STOPPED, function() { ... }).on(STREAM_STATUS.FAILED, function(stream) { ... }).on(STREAM_STATUS.NOT_ENOUGH_BANDWIDTH, function(stream){ ... }); stream.play();
-
Прием аудио-видео потока по Websocket и воспроизведение по WebRTC
-
Остановка воспроизведения потока
Stream.stop()
code
-
Получение от сервера события, подтверждающего остановку воспроизведения потока
STREAM_STATUS.STOPPED code
stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }).on(STREAM_STATUS.PLAYING, function(stream) { ... }).on(STREAM_STATUS.STOPPED, function() { setStatus(STREAM_STATUS.STOPPED); onStopped(); }).on(STREAM_STATUS.FAILED, function(stream) { ... }).on(STREAM_STATUS.NOT_ENOUGH_BANDWIDTH, function(stream){ ... }); stream.play();
Циклический захват потока из файла¶
Для трансляций VOD live поддерживается циклический захват потока, после окончания файла захват начинается сначала. Эта возможность включается настройкой в файле flashphoner.properties
Захват файла, размещенного на AWS или другом S3 хранилище¶
Поток может быть захвачен из файла, размещенного на AWS в хранилище S3. В отличие от VOD захвата файла с локального диска, файл, размещенный на внешнем хранилище, загружается и воспроизводится последовательно.
Для захвата VOD из файла на AWS в качестве имени потока при вызове функции Session.createStream()
должна быть указана ссылка на файл в виде:
где
bucket
- имя корзины S3sample.mp4
- имя файла
В сборке 5.2.939 добавлена возможность указать полный URL файла в S3 хранилище, это позволяет захватывать файлы из других S3 хранилищ (Digital Ocean, Selectel и т.д.)
Пример для Digital Ocean Spaces
Пример для Selectel
Схема работы¶
- Браузер запрашивает захват потока из файла на AWS
- WCS сервер направляет запрос AWS
- Файл загружается на WCS сервер
- WebRTC поток из файла передается в браузер для воспроизведения
Настройка¶
Доступ к хранилищу S3¶
AWS¶
Для загрузки файлов из AWS необходимо указать в файле настроек flashphoner.properties данные для доступа к хранилищу S3
Здесь
zone
- регион, где размещено хранилищеlogin
- идентификатор ключа доступа (Access Key ID)hash
- секретный ключ доступа (Secret Accesss Key)
Пример настройки доступа:
Digital Ocean Spaces¶
Для загрузки файлов из DO Spaces необходимо указать настройку
Здесь
ams3
- поддомен digitaloceanspaces.comaccess_key
- ключ доступа к хранилищуsecret
- секретный код доступа к хранилищу
Selectel¶
Для загрузки файлов из Selectel S3 необходимо указать настройку
Здесь
ru-1a
- регион хранилищаlogin
- имя пользователяpassword
- пароль
Захват VOD из файла во время загрузки¶
Чтобы захватывать поток из файла во время его загрузки, необходимо указать следующую настройку
При низкой пропускной способности канала между WCS и хранилищем S3, либо при недостаточной его стабильности, может быть включена буферизация файла при загрузке. Размер буфера задается в миллисекундах настройкой
В данном случае, размер буфера составит 10 секунд.
Требования к формату файлов¶
Заголовок (moov) должен всегда располагаться перед данными (mdat). Примерная структура файла должна быть такой:
Atom ftyp @ 0 of size: 32, ends @ 32
Atom moov @ 32 of size: 357961, ends @ 357993
...
Atom free @ 357993 of size: 8, ends @ 358001
Atom mdat @ 358001 of size: 212741950, ends @ 213099951
Проверить структуру файла можно при помощи утилиты AtomicParsley
Если структура файла не соответствует требованиям, файл не будет воспроизводиться. При необходимости, структуру файла можно исправить при помощи ffmpeg без перекодирования
Требования к именам файлов¶
Официальная документация по AWS S3 не рекомендует использование пробелов наряду с другими специальными символами, но и не запрещает их. Если пробелы в именах файлов все же используются, их необходимо заменять на %20
, например
Управление VOD при помощи REST API¶
REST-запрос должен быть HTTP/HTTPS POST запросом в таком виде:
- HTTP:
http://test.flashphoner.com:8081/rest-api/vod/startup
- HTTPS:
https://test.flashphoner.com:8444/rest-api/vod/startup
Здесь:
test.flashphoner.com
- адрес WCS-сервера8081
- стандартный REST / HTTP порт WCS-сервера8444
- стандартный HTTPS портrest-api
- обязательная часть URL/vod/startup
- используемый REST-метод
REST-методы и статусы ответа¶
/vod/startup¶
Захватить поток из указанного файла
Request example¶
POST /rest-api/vod/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"vod-live://sample.mp4",
"localStreamName": "test"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
409 | Conflict |
500 | Internal error |
/vod/find¶
Найти VOD-потоки по указанному критерию
Request example¶
POST /rest-api/vod/find HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"localStreamName": "test"
}
Response example¶
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
[
{
"localMediaSessionId": "29ec3236-1093-42bb-88d6-d4ac37af3ac0",
"localStreamName": "test",
"uri": "vod-live://sample.mp4",
"status": "PROCESSED_LOCAL",
"hasAudio": true,
"hasVideo": true,
"record": false,
"loop": false
}
]
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
/vod/find_all¶
Найти все VOD потоки
Request example¶
Response example¶
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
[
{
"localMediaSessionId": "29ec3236-1093-42bb-88d6-d4ac37af3ac0",
"localStreamName": "test",
"uri": "vod-live://sample.mp4",
"status": "PROCESSED_LOCAL",
"hasAudio": true,
"hasVideo": true,
"record": false,
"loop": false
}
]
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
/vod/terminate¶
Завершить VOD-поток
Request example¶
POST /rest-api/vod/find_all HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"vod://sample.mp4",
"localStreamName": "test"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
Параметры¶
Параметр | ОПисание | Пример |
---|---|---|
uri | Имя файла для захвата потока |
vod://sample.mp4
|
localStreamName | Имя создаваемого потока |
test
|
status | Текущий статус потока |
PROCESSED_LOCAL
|
localMediaSessionId | Идентификатор медиасессии |
29ec3236-1093-42bb-88d6-d4ac37af3ac0
|
hasAudio | В потоке есть аудио |
true
|
hasVideo | В потоке есть видео |
true
|
record | Поток записывается |
false
|
loop | Файл захватывается циклически |
false
|
Циклический захват потока из файла по запросу¶
В сборке 5.2.1528 добавлена возможность указать, должен ли файл захватываться циклически, при создании VOD live трансляции по REST API
По умолчанию, если параметр loop
не указан, применяется настройка vod_live_loop
. Если параметр указан, то, в зависимости от его значения
true
- файл будет захватываться циклическиfalse
- файл будет проигран однократно, после чего VOD live трансляция остановится
Значение параметра loop
имеет приоритет над значением настройки vod_live_loop
.
Ограничения¶
Запрос /vod/startup
может применяться только для создания VOD live трансляций. При этом, запросы /vod/find
, /vod/find_all
и /vod/terminate
могут быть применены как к VOD, так и к VOD live трансляциям.
Настройка продолжительности публикации VOD потока после отключения подписчиков¶
По умолчанию, VOD поток остается опубликованным на сервере в течение 30 секунд после отключения последнего подписчика, при условии, что продолжительность файла превышает этот интервал. Данное время может быть изменено при помощи настройки
В этом случае, VOD поток останется опубликованным в течение 60 секунд.
Известные проблемы¶
1. AAC фреймы типа 0 не поддерживаются декодером на базе ffmpeg и будут игнорироваться при воспроизведении захваченного потока¶
Симптомы
Предупреждения в клиентском логе:
2. Файлы, содержащие B-фреймы, могут проигрываться неплавно, с фризами или артефактами¶
Симптомы
Периодические фризы, артефакты при проигрывании файла через VOD, предупреждения в клиентском логе
Решение
Перекодировать файл таким образом, чтобы исключить B-фреймы, например
3. Проигрывание VOD может требовать много оперативной памяти¶
При захвате VOD из продолжительного файла или при одновременном захвате нескольких VOD потоков процесс сервера может завершиться с Out of memory
Симптомы
Процесс сервера завершается с Map failed
в серверном логе и в error*.log
19:30:53,277 ERROR DefaultMp4SampleList - Thread-34 java.io.IOException: Map failed
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:940)
at com.googlecode.mp4parser.FileDataSourceImpl.map(FileDataSourceImpl.java:62)
at com.googlecode.mp4parser.BasicContainer.getByteBuffer(BasicContainer.java:223)
at com.googlecode.mp4parser.authoring.samples.DefaultMp4SampleList$SampleImpl.asByteBuffer(DefaultMp4SampleList.java:204)
at com.flashphoner.media.F.A.A.A$1.A(Unknown Source)
at com.flashphoner.media.M.B.C.D(Unknown Source)
at com.flashphoner.server.C.A.B.A(Unknown Source)
at com.flashphoner.server.C.A.B.C(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.OutOfMemoryError: Map failed
at sun.nio.ch.FileChannelImpl.map0(Native Method)
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:937)
... 8 more