Использование WebRTC для проигрывания видео с Flussonic Media Server
О WebRTC
WebRTC — это P2P протокол общения между двумя клиентами, регламентирующий передачу данных по заранее установленному соединению. Например, для связи двух браузеров по протоколу WebRTC необходимо зайти на один и тот же сайт в интернете. Также, можно использовать посредника — сигнальный сервер.
Прежде чем начать передачу видео-данных, клиенты должны установить соединение. Процесс установления соединения между двумя клиентами заключается в обмене данными о соединении двух видов:
- текстовыми описаниями медиапотоков в формате SDP
- ICE Candidates как часть SDP
Сигнальный сервер — посредник, позволяет передать данные о соединении от одного клиента к другому.
О проигрывании по WebRTC с Flussonic
Flussonic Media Server использует WebRTC для проигрывания публикуемого видео с сервера Flussonic (источника) на клиентском устройстве или в программе (приемник). При этом Flussonic также играет роль сигнального сервера в процессе установления соединения, через который происходит обмен данными о соединении.
Почему мы используем именно WebRTC для обмена медиа-данными между двумя клиентами? Данный механизм позволяет нам достигнуть сверхнизкой задержки.
Таким образом, обмен видео по WebRTC через Flussonic Media Server нельзя считать peer-to-peer, правильнее будет называть это публикацией видео на Flussonic Media Server по WebRTC и проигрыванием видео с Flussonic сервера по WebRTC.
На схеме ниже показан процесс обмена данными для установления соединения между Flussonic и клиентским приложением, для проигрывания видео:
Стороны должны обменяться SDP через посредника (а это сигнальный сервер - Flussonic), после чего начать передачу данных напрямую. В случае проигрывания видео инициировать процесс и отправлять приглашение (SDP offer) нужно со стороны сервера Flussonic (источника).
Cоединение устанавливается по WebSocket, а видео передается по RTP.
Как организовать проигрывание публикуемых потоков по WebRTC
На сервере Flussonic должен быть сконфигурирован публикуемый поток, т.е. поток с источником publish://
, в этот поток клиенты публикуют видео, а нам потребуется забрать его с сервера, чтобы проиграть.
stream STREAMNAME {
url publish://;
}
На клиенте нужно исполнить код для проигрывания видео из публикуемого потока. Для создания кода используйте библиотеку Flussonic WebRTC player.
Инструкция по установке, описание классов библиотеки и код примера доступны на npm.
Установка с помощью NPM и webpack
Для варианта с импортом библиотеки в ваш проект через Webpack необходимо загрузить пакет:
npm install --save @flussonic/flussonic-webrtc-player
и импортировать его в ваше приложение:
import {
PUBLISHER_EVENTS,
PLAYER_EVENTS,
Player,
Publisher,
} from "@flussonic/flussonic-webrtc-player";
Описание классов библиотеки доступно на npm.
См. также демо-приложение ниже.
Установка без NPM и webpack
В секцию скриптов вашего HTML-файла добавьте:
<script src="https://cdn.jsdelivr.net/npm/@flussonic/flussonic-webrtc-player/dist/index.min.js"></script>
Полный пример кода страницы с плеером приведен ниже.
Пример плеера — с Webpack и без Webpack
Демо-приложение, использующее Webpack для импорта компонентов:
- Пример приложения с Webpack и нашим плеером WebRTC. Это пример с компонентами, которые импортируются с помощью Webpack в приложение. Приложение можно скачать и изучить, как реализован плеер.
Пример плеера WebRTC на JavaScript, который получает компоненты через <script>
:
- Код библиотеки Flussonic WebRTC Player для реализации плеера есть в CDN https://www.jsdelivr.com, откуда его нужно импортировать в свою веб-страницу. Для этого добавьте в секцию скриптов вашего HTML-файла строку:
<script src="https://cdn.jsdelivr.net/npm/@flussonic/flussonic-webrtc-player/dist/index.min.js"></script>
.
Полный пример страницы с плеером на JavaScript (похожий код есть в составе демо-приложения):
<!DOCTYPE html>
<html>
<head>
<style>
.app {
display: flex;
flex-direction: column;
justify-content: space-between;
height: calc(100vh - 16px);
}
.container {
margin-bottom: 32px;
}
.video-container {
display: flex;
}
.controls {
}
.config {
}
#player {
width: 640px; height: 480px; border-radius: 1px
}
.button {
height: 20px;
width: 96px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/@flussonic/flussonic-webrtc-player/dist/index.min.js"></script>
</head>
<body>
<div class="app">
<div class="video-container">
<video
id="player"
controls
muted
autoplay
playsinline
>
</video>
<pre id="debug"></pre>
</div>
<div class="container">
<div class="config" id="config">
<span id="hostContainer">
<label for="host">Host: </label><input name="host" id="host" value="" />
</span>
<span id="nameContainer">
<label for="name">Stream: </label><input name="name" id="name" value="" />
</span>
</div>
<div class="controls" id="controls">
<select id="quality">
<option value="4:3:240">4:3 320x240</option>
<option value="4:3:360">4:3 480x360</option>
<option value="4:3:480">4:3 640x480</option>
<option value="16:9:360" selected>16:9 640x360</option>
<option value="16:9:540">16:9 960x540</option>
<option value="16:9:720">16:9 1280x720 HD</option>
</select>
<button id="publish" class="button">Publish</button>
<button id="play" class="button">Play</button>
<button id="stop" class="button">Stop all</button>
</div>
<div class="errorMessageContainer" id="errorMessageContainer"></div>
</div>
</body>
<script>
let wrtcPlayer = null;
let publisher = null;
const { Player, Publisher, PUBLISHER_EVENTS, PLAYER_EVENTS } = this.FlussonicWebRTC;
const getHostElement = () => document.getElementById('host');
const getHostContainerElement = () => document.getElementById('hostContainer');
const getNameElement = () => document.getElementById('name');
const getNameContainerElement = () => document.getElementById('nameContainer');
const getPlayerElement = () => document.getElementById('player');
const getPlayElement = () => document.getElementById('play');
const getPublishElement = () => document.getElementById('publish');
const getStopElement = () => document.getElementById('stop');
const getQualityElement = () => document.getElementById('stop');
const getStreamUrl = (
hostElement = getHostElement(),
nameElement = getNameElement(),
) =>
`${hostElement && hostElement.value}/${nameElement && nameElement.value}`;
const getPublisherOpts = () => {
const [, , height] = document.getElementById('quality').value.split(/:/);
return {
preview: document.getElementById('preview'),
constraints: {
// video: {
// height: { exact: height }
// },
video: true,
audio: true,
},
};
};
const getPlayer = (
playerElement = getPlayerElement(),
streamUrl = getStreamUrl(),
playerOpts = {
retryMax: 10,
retryDelay: 1000,
},
shouldLog = true,
log = (...defaultMessages) => (...passedMessages) =>
console.log(...[...defaultMessages, ...passedMessages]),
) => {
const player = new Player(playerElement, streamUrl, playerOpts, true);
player.on(PLAYER_EVENTS.PLAY, log('Started playing', streamUrl));
player.on(PLAYER_EVENTS.DEBUG, log('Debugging play'));
return player;
};
const stopPublishing = () => {
if (publisher) {
publisher.stop && publisher.stop();
publisher = null;
}
};
const stopPlaying = () => {
if (wrtcPlayer) {
wrtcPlayer.destroy && wrtcPlayer.destroy();
wrtcPlayer = null;
}
};
const stop = () => {
stopPublishing();
stopPlaying();
getPublishElement().innerText = 'Publish';
getPlayElement().innerText = 'Play';
};
const play = () => {
wrtcPlayer = getPlayer();
getPlayElement().innerText = 'Playing...';
wrtcPlayer.play();
};
const publish = () => {
if (publisher) publisher.stop();
publisher = new Publisher(getStreamUrl(), getPublisherOpts(), true);
publisher.on(PUBLISHER_EVENTS.STREAMING, () => {
getPublishElement().innerText = 'Publishing...';
});
publisher.start();
};
const setDefaultValues = () => {
getHostElement().value = config.host;
getNameElement().value = config.name;
};
const setEventListeners = () => {
// Set event listeners
getPublishElement().addEventListener('click', publish);
getPlayElement().addEventListener('click', play);
getStopElement().addEventListener('click', stop);
getQualityElement().onchange = publish;
};
const main = () => {
setDefaultValues();
setEventListeners();
};
window.addEventListener('load', main);
</script>
</html>
Скопируйте этот код в файл, например index.html, и откройте в браузере, чтобы проверить работу.