https://habrahabr.ru/company/flashphoner/blog/325978/- Разработка под iOS
- Разработка веб-сайтов
- Программирование
- JavaScript
- Блог компании Flashphoner
В апреле прошлого года по сети прокатился пресс-релиз о том, что Apple выкатывает поддержку WebRTC в браузерах Safari для Mac OS и iOS. С момента выхода пресс-релиза скоро пройдет ровно год, как Apple продолжает выкатывать WebRTC для Safari. Ждем.
Однако ждут не все. Кому-то требуется реал-тайм видео в Safari прямо сейчас и в этой статье мы расскажем как обходиться без WebRTC в браузере iOS Safari и Mac OS Safari и чем можно его заменить.
На сегодняшний день нам известны следующие варианты:
- HLS
- Flash
- Websockets
- WebRTC Plugin
- iOS native app
Так как мы ищем альтернативу RTC (Real Time Communication), то сравним эти варианты не только по платформам iOS / Mac OS, но и по средней задержке (Latency) в секундах.
|
iOS |
Mac OS |
Latency |
HLS, DASH |
Да |
Да |
15 |
Flash RTMP |
Нет |
Да |
3 |
Flash RTMFP |
Нет |
Да |
1 |
Websockets |
Да |
Да |
3 |
WebRTC Plugin |
Нет |
Да |
0.5 |
HLS
Как видно из этой таблицы, HLS совсем выпадает из реалтайма со своими 15 и более секундами задержки, хотя и прекрасно работает на обоих платформах.
Flash RTMP
Несмотря на то, что «Flash умер», он продолжает работать на Mac OS и даёт годную для реалтайма задержку. Но дела у флэша на Safari действительно обстоят не очень. Бывает так, что Flash оказывается попросту отключен.
Flash RTMFP
Тоже самое что и Flash RTMP, с той лишь разницей, что работает по UDP и умеет сбрасывать пакеты, что несомненно лучше для реалтайма. Хорошая задержка. Не работает на iOS.
Websockets
Некоторая альтернатива для HLS, если нужна сравнительно низкая задержка. Работает в iOS и Mac OS.
В этом случае видеопоток приходит через Websockets (RFC6455), декодируется на уровне JavaScript и отрисовывается на
HTML5 canvas с помощью
WebGL. Этот способ работает гораздо быстрее HLS, но имеет свои недостатки:
- Односторонняя доставка.Поток можно только проиграть в реалтайме, но нельзя захватить с камеры устройства и отправить на сервер.
- Ограничения по разрешению. При разрешении 800x400 и выше, нужен уже мощный CPU на последних iPhone или iPad для ровного декодирования такого потока на JavaScript. С iPhone5 и iPhone6 разрешение скорее всего не удастся поднять выше 640x480 и оставить плавным.
WebRTC Plugin
В Mac OS можно установить WebRTC плагин, который реализует WebRTC. Это несомненно дает самую лучшую задержку, но требует скачивания и установки пользователем стороннего программного обеспечения. Можно справедливо заметить, что Adobe Flash Player тоже плагин и тоже может требовать ручной установки, но «мертвый флэш» плагин от Adobe, очевидно, обгоняет Noname WebRTC плагины по распространенности. Кроме этого, WebRTC плагины не работают в iOS Safari.
Другие альтернативы WebRTC для iOS
Если не ограничиваться браузером Safari, можно рассмотреть следующие варианты:
iOS native app
Имплементим iOS приложение с поддержкой WebRTC и получаем низкую задержку и всю мощь WebRTC-технологии. Не браузер. Требует установки из App Store.
Bowser
Браузер с поддержкой WebRTC под iOS. Говорят, что поддерживает WebRTC, но мы не тестировали. Не очень популярный браузер. Но если есть возможность заставить пользователей им пользоваться, можно попробовать это сделать.
Ericsson
Тоже что и Bowser. Непонятно, насколько хорошо работает с WebRTC. Не популярен на iOS.
Ждун
Можно подождать внедрение WebRTC от Apple. Прошел уже год. Возможно осталось ждать не так уж и долго. Может у кого-то есть инсайд?
Websockets как замена WebRTC на iOS
Из таблицы выше видно, что на iOS Safari остается только два варианта:
HLS и
Websockets. Первый имеет задержку более
15 секунд. Второй имеет свои ограничения и задержку около
3 секунд. Есть еще
MPEG DASH, но это тот же HLS / HTTP в плане реалтайма.
В силу перечисленных выше ограничений:
- Декодинг на JavaScript и поддержка низких разрешений
- Односторонний стриминг (только воспроизведение)
Вебсокеты, конечно же, не могут претендовать на полноценную замену WebRTC в браузере iOS Safari, но позволяют играть реалтайм-потоки прямо сейчас.
В этом случае, схема реалтаймовой трансляции выглядит так:
- Отправляем WebRTC видеопоток, например с Mac OS или Win, браузера Chrome на WebRTC-сервер с поддержкой конвертации в вебсокеты.
- WebRTC-сервер конвертирует поток в MPEG + G.711 и заворачивает в транспортный протокол Websockets.
- iOS Safari устанавливает соединение с сервером по протоколу Websockets и забирает видеопоток. Далее распаковывает видеопоток и декодирует аудио и видео. Аудио проигрывает через Web Audio API, а видео рендерит в Canvas с использованием WebGL.
Тестирование воспроизведения по Websockets в iOS Safari
В качестве сервера используется Web Call Server 5, который поддерживает такую конвертацию и отдает поток на iOS Safari по Websockets. Источником реалтаймового видеопотока может быть вебкамера, которая отправляет видео на сервер или же IP камера, работающая по RTSP.
Так выглядит отправка реалтаймового WebRTC видеопотока на сервер в браузере Google Chrome с десктопа:
А так выглядит воспроизведение этого же видеопотока в реалтайме, в браузере iOS Safari:
Здесь мы в качестве имени видеопотока указали
d3c6, т.е. Тот видеопоток, который был отправлен с браузера Chrome по WebRTC.
В случае, если видеопоток забирается с IP-камеры, в iOS Safari это будет выглядеть так:
Как видно из скриншота, в качестве имени видеопотока мы использовали RTSP адрес. Сервер забрал RTSP поток и сконвертировал его в Websockets для iOS Safari.
Интеграция плеера для iOS Safari в веб страницу
Исходный код плеера доступен
здесь. Однако плеер по ссылке работает не только для iOS Safari. Он может переключаться между тремя технологиями: WebRTC, Flash, Websockets в порядке приоритета и содержит немного больше кода, чем требуется для воспроизведения в iOS Safari.
Попробуем минимизировать код плеера чтобы продемонстрировать минимальную конфигурацию, которая будет играть в iOS Safari
Минимальный код HTML-страницы будет выглядеть так:
player-ios-safari.html<html>
<head>
<script language="javascript" src="flashphoner.js"></script>
<script language="javascript" src="player-ios-safari.js"></script>
</head>
<body onLoad="init_page()">
<h1>The player</h1>
<div id="remoteVideo" style="width:320px;height:240px;border: 1px solid"></div>
<input type="button" value="start" onClick="start()"/>
<p id="status"></p>
</body>
</html>
Из этого кода видно, что главный элемент на странице — это div-блок
<div id="remoteVideo" style="width:320px;height:240px;border: 1px solid"></div>
Именно этот блок будет отвечать за воспроизведении видео, после того как скрипты API вставят туда HTML5 Canvas.
Далее идет скрипт плеера. Объем скрипта: 80 строк:
player-ios-safari.jsvar SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var remoteVideo;
var stream;
function init_page() {
//init api
try {
Flashphoner.init({
receiverLocation: '../../dependencies/websocket-player/WSReceiver2.js',
decoderLocation: '../../dependencies/websocket-player/video-worker2.js'
});
} catch(e) {
return;
}
//video display
remoteVideo = document.getElementById("remoteVideo");
onStopped();
}
function onStarted(stream) {
//on playback start
}
function onStopped() {
//on playback stop
}
function start() {
Flashphoner.playFirstSound();
var url = "wss://wcs5-eu.flashphoner.com:8443";
//create session
console.log("Create new session with url " + url);
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){
setStatus(session.status());
//session connected, start playback
playStream(session);
}).on(SESSION_STATUS.DISCONNECTED, function(){
setStatus(SESSION_STATUS.DISCONNECTED);
onStopped();
}).on(SESSION_STATUS.FAILED, function(){
setStatus(SESSION_STATUS.FAILED);
onStopped();
});
}
function playStream(session) {
var streamName = "12345";
var options = {
name: streamName,
display: remoteVideo
};
options.playWidth = 640;
options.playHeight = 480;
stream = session.createStream(options).on(STREAM_STATUS.PLAYING, function(stream) {
setStatus(stream.status());
onStarted(stream);
}).on(STREAM_STATUS.STOPPED, function() {
setStatus(STREAM_STATUS.STOPPED);
onStopped();
}).on(STREAM_STATUS.FAILED, function() {
setStatus(STREAM_STATUS.FAILED);
onStopped();
});
stream.play();
}
//show connection or remote stream status
function setStatus(status) {
//display stream status
}
К наиболее важным частям этого скрипта можно отнести инициализацию API
Flashphoner.init({
receiverLocation: '../../dependencies/websocket-player/WSReceiver2.js',
decoderLocation: '../../dependencies/websocket-player/video-worker2.js'
});
При инициализации подгружается еще два скрипта:
- WSReceiver2.js
- video-worker2.js
Эти скрипты являются ядром вебсокет плеера. Первый отвечает за доставку видеопотока, а второй за его обработку. Скрипты
flashphoner.js,
WSReceiver2.js и
video-worker2.js доступны для скачивания в сборке
Web SDK для Web Call Server и обязательно должны быть подключены для воспроизведения потока в iOS Safari.
Таким образом имеем следующие обязательные скрипты:
- flashphoner.js
- WSReceiver2.js
- video-worker2.js
- player-ios-safari.js
Установка соединения с сервером происходит с помощью следующего кода:
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){
setStatus(session.status());
//session connected, start playback
playStream(session);
}).on(SESSION_STATUS.DISCONNECTED, function(){
setStatus(SESSION_STATUS.DISCONNECTED);
onStopped();
}).on(SESSION_STATUS.FAILED, function(){
setStatus(SESSION_STATUS.FAILED);
onStopped();
});
}
Непосредственно воспроизведение видеопотока осуществляется с помощью метода API
createStream().play(). При воспроизведении видеопотока в div-элемент
remoteVideo будет встроен HTML-элемент Canvas, в котором будет происходить рендеринг видеопотока.
function playStream(session) {
var streamName = "12345";
var options = {
name: streamName,
display: remoteVideo
};
options.playWidth = 640;
options.playHeight = 480;
stream = session.createStream(options).on(STREAM_STATUS.PLAYING, function(stream) {
setStatus(stream.status());
onStarted(stream);
}).on(STREAM_STATUS.STOPPED, function() {
setStatus(STREAM_STATUS.STOPPED);
onStopped();
}).on(STREAM_STATUS.FAILED, function() {
setStatus(STREAM_STATUS.FAILED);
onStopped();
});
stream.play();
}
Мы захардкодили в коде две вещи:
1) Урл сервера
var url = "wss://wcs5-eu.flashphoner.com:8443";
Это публичный демо-сервер Web Call Server 5. Если с ним что-то не так, вам нужно
установить свой для тестирования.
2) Название потока для воспроизведения
var streamName = "12345";
Это название видеопотока, который мы воспроизводим.
В случае, если это поток с RTSP IP-камеры, его можно прописать так
var streamName = "rtsp://host:554/stream.sdp";
Самая неприметная, но очень важная функция
Flashphoner.playFirstSound();
На мобильных платформах, в частности на iOS Safari есть ограничение Web Audio API, которое не дает проигрывать звук из динамиков до тех пор, пока пользователь не щелкнет пальцем по какому-либо элементу web-страницы. Поэтому при нажатии кнопки Start мы вызываем метод playFirstSound(), который играет короткий кусок сгенерированного аудио, чтобы видео в итоге могло играть с аудио.
В конечном итоге наш кастомный минимальный плеер, состоящий из четырех скриптов и одного HTML файла
player-ios-safari.html выглядит так:
- flashphoner.js
- WSReceiver2.js
- video-worker2.js
- player-ios-safari.js
- player-ios-safari.html
Скачать исходный код плеера можно
здесь.
Таким образом, мы рассказали о текущих альтернативах WebRTC для iOS Safari и разобрали пример с реалтайм-плеером с передачей видео по технологии Websockets. Возможно он поможет кому-то дождаться прихода WebRTC на Safari.
Ссылки
Пресс-релиз — Apple выкатывает WebRTC для Safari
Websockets — RFC6455
WebGL — спецификация
Web Call Server — WebRTC-сервер, который может конвертировать поток в Websockets для воспроизведения на iOS Safari
Установка WCS — скачать и установить
Запуск на Amazon EC2 — запуск готового образа сервера на Amazon AWS
Исходный код — пример плеера: player-ios-safari.js и player-ios-safari.html
Web SDK — web API для сервера WCS, содержащее скрипты flashphoner.js, WSReceiver2.js, video-worker2.js