Документация Flussonic Media Server

Содержание

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

WebRTC Проигрывание

Стороны должны обменяться 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 для импорта компонентов:

Пример плеера 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>
    <meta charset="utf-8"/>
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <title>FlussonicWebRTC Demo Page</title>
    <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, и откройте в браузере, чтобы проверить работу.