Skip to content

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

О WebRTC

WebRTC — это P2P протокол общения между двумя клиентами, регламентирующий передачу данных по заранее установленному соединению. Например, для связи двух браузеров по протоколу WebRTC необходимо зайти на один и тот же сайт в интернете. Также, можно использовать посредника — сигнальный сервер.

Прежде чем начать передачу видео-данных, клиенты должны установить соединение. Процесс установления соединения между двумя клиентами заключается в обмене данными о соединении двух видов:

  • текстовыми описаниями медиапотоков в формате SDP
  • ICE Candidates как часть SDP

Сигнальный сервер — посредник, позволяет передать данные о соединении от одного клиента к другому.

WebRTC идеально подходит для вбинаров, онлайн-коммуникации и видеочатов.

Подробнее читайте в главе Использование протокола WebRTC.

Как организовать проигрывание публикуемых потоков по WebRTC

На сервере Flussonic должен быть сконфигурирован публикуемый поток, т.е. поток с источником publish://. В этот поток клиенты публикуют видео, а нам потребуется забрать его с сервера, чтобы проиграть.

stream STREAM_NAME {
  input publish://;
}

ABR и WebRTC

Flussonic поддерживает адаптивное потоковое вещание для WebRTC. Адаптивное потоковое вещание основано на технологии ABR (Adaptive Bitrate, Адаптивный битрейт), предназначенной для эффективной доставки видео на большое количество разных устройств. Это технология, при которой для одного и того же источника генерируется несколько видео- и аудиопотоков с различными битрейтами и разрешениями. Эти потоки затем запрашиваются клиентскими устройствами для проигрывания. Создать несколько потоков разного разрешения и с разным битрейтом можно с помощью транскодера. Затем качество воспроизведение медиа подстраивается под текущую скорость сети.
Media Server автоматически переключается между разрешениями в зависимости от условий сети пользователя. При непрерывной передаче данных пользователь получает видео в максимально возможном качестве. Так вы можете предоставить конечному пользователю лучший пользовательский опыт на любом устройстве и в любой сети.

Во Flussonic реализован механизм, по которому он получает от браузера данные, на основе которых вычисляется приемлемый для пользователя битрейт. Это осуществляется на основе индикатора потерь пакетов — NACK (Negative ACKnowledgement).

Кроме того, Flussonic может использовать механизм REMB или TWCC для принятия решения о переключении на более высокий битрейт (см. Использование REMB или TWCC для ABR.)

Если настроен ABR для WebRTC, то плееры будут работать в режиме автоматического переключения auto, пока зритель вручную не изменит качество видео. Чтобы включить режим auto вновь, необходимо выбрать его в настройках плеера.

Чтобы включить режим ABR для проигрывания WebRTC-потока, добавьте опцию webrtc_abr в настройки потока:

stream webrtc-abr {
  input fake://;
  webrtc_abr;
  transcoder vb=1000k size=1920x1080 bf=0 vb=300 size=320x240 bf=0 ab=64k acodec=opus;
}

Если вы хотите иметь больше контроля над адаптивным вещанием, задайте дополнительные параметры для webrtc_abr:

Параметр Описание Пример
start_track Номер видеодорожки, с которой начнётся проигрывание. Возможный формат указания значения: v1, v2, v3 и т.д. По умолчанию start_track=v1.

Если параметр start_track не задан или вместо видео- указана аудиодорожка (start_track=a3, то проигрывание начнется с дорожки v1 и в дальнейшем подстроится под пропускную способность канала.

Если указанная видеодорожка не существует, то проигрывание начнётся с ближайшей дорожки с меньшим номером и в дальнейшем подстроится под пропускную способность канала.

Если дорожки ограничены параметром строки запроса ?filter=tracks:..., то Flussonic будет искать ближайшую доступную дорожку с меньшим номером, вплоть до v0.
Если такой дорожки не найдётся, то Flussonic будет искать ближайшую дорожку с большим номером.
start_track=v4
loss_count Целое число потерянных пакетов. По умолчанию loss_count=2. loss_count=3
up_window Значение битрейта увеличивается, если в последние up_window секунд число потерянных пакетов меньше значения loss_count.
По умолчанию up_window=20.
up_window=17
down_window Значение битрейта уменьшается, если в последние down_window секунд число потерянных пакетов больше значения loss_count.
По умолчанию down_window=5.
down_window=6
ignore_remb Если ignore_remb=true, Flussonic игнорирует значение REMB (Receiver Estimated Maximum Bitrate) от клиента при увеличении битрейта.

Если ignore_remb=false, значение битрейта не будет превышать значение REMB от клиента.
По умолчанию ignore_remb=false.
ignore_remb=true
bitrate_prober Если принимает значение true, Flussonic периодически отправляет пробные пакеты для измерения пропускной способности и переключает битрейт на более высокий, если возможно. Подробнее: Использование REMB или TWCC для ABR.
По умолчанию false.
bitrate_prober=true
bitrate_probing_interval Интервал отправки пробных пакетов, в секундах. Подробнее: Использование REMB или TWCC для ABR. bitrate_probing_interval=2

Для проигрывания потока используйте наш плеер embed.html, который вы можете открыть в браузере по ссылке ниже:

http://FLUSSONIC-IP/STREAM_NAME/embed.html?proto=webrtc

, где:

  • FLUSSONIC-IP — IP-адрес вашего сервера Flussonic,
  • STREAM_NAME — имя вашего WebRTC-потока.

либо воспользуйтесь другим плеером с поддержкой WebRTC и укажите URL вида:

  • ws://FLUSSONIC-IP/STREAM_NAME/webrtc
  • wss://FLUSSONIC-IP/STREAM_NAME/webrtc?transport=tcp — для передачи WebRTC-потока по TCP.

На клиенте нужно исполнить код для проигрывания видео из публикуемого потока. Для создания кода используйте библиотеку 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 на 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, и откройте в браузере, чтобы проверить работу.

Проигрывание потоков с помощью WHAP

Долгое время WebRTC не использовался в области вещания и проигрывания потоков, так как у него нет стандартного протокола сигнализации и он слишком сложен для внедрения в инструменты и приложения для вещания. Для решения этой проблемы был разработан протокол WHAP.

WHAP (WebRTC-HTTP access protocol) предоставляет простой и независимый от медиа-сервера способ проигрывания потоков по WebRTC, который легко интегрировать в существующие инструменты вещания. Процесс согласования WebRTC в WHAP сводится к отправке запроса HTTP POST с сообщением SDP и получению от медиа-сервера ответа 200/202 для получения ответного сообщения SDP. При этом WHAP сохраняет все преимущества протокола WebRTC: низкую задержку, отказоустойчивость, адаптацию загрузки канала, шифрование, использование популярных кодеков, адаптивный битрейт и т.д.

Note

Этот протокол работает аналогично WHIP, который используется для публикации потоков.

Flussonic Media Server позволяет проигрывать потоки по WHAP. Для этого не требуется никакой специальной конфгурации. Просто следуйте вышеописанным шагам для проигрывания по WebRTC, но в настройках объекта Player в WebRTC плеере добавьте опцию whipwhap: true.


import Player from '../player';
//...
player = new Player(
//...
  whipwhap: true,
//...
);

Описание класса Player и всех его параметров можно найти в npm.

Теперь для проигрывания потока в вашем приложении можно использовать следующий URL:

http://FLUSSONIC-IP:PORT/STREAM_NAME/whap

См. справочник Streaming API.

Использование REMB или TWCC для ABR

Проигрывая потоки по WebRTC, Flussonic использует для отправки видео и аудио кадров протокол RTP. Для этого протокола доступны два механизма измерения пропускной способности. Flussonic может использовать какой-либо из этих механизмов в алгоритме ABR для принятия решения о переключении битрейта на более высокое значение.

REMB

Первый механизм использует сообщение REMB (Receiver Estimated Maximum Bitrate), полученное от клиента. Битрейт отправленного видео не может превышать битрейт, указанный в сообщении REMB. Однако если значение REMB растет, Flussonic может переключиться на трек с более высоким битрейтом. Подробнее о REMB.

Этот механизм довольно прост, но у него есть ряд недостатков:

  • После кратковременной потери пакетов (например, из-за сбоя сетевого соединения), REMB стремительно падает, а затем растет очень медленно (в течение 5-15 минут). В результате Flussonic долго не может переключиться на трек с более высоким качеством, хотя клиент может его проиграть.
  • Flussonic не может контролировать это значение, т.к. оно вычисляется на стороне клиента.
  • Этот механизм отмечен как deprecated, и его дальнейшее развитие под вопросом.

Механизм REMB используется во Flussonic по умолчанию, но вы можете отключить его, указав в конфигурации потока параметр ignore_remb=true. В этом случае значения REMB, получаемые от клиента, будут игнорироваться.

TWCC

Можно переключиться на другой механизм, доступный как расширение RTP: TWCC (Transport-wide Congestion Control). Подробнее о расширении.

В этом случае Flussonic добавляет к каждому отправляемому пакету заголовок RTP, который содержит ID расширения и порядковый номер пакета. В ответ клиент отправляет сообщение RTCP, в котором содержится время получения и порядковый номер каждого полученного пакета. Таким образом, Flussonic знает время отправки и получения каждого пакета и может вычислить разницу между ними. Кроме того, Flussonic знает размер каждого пакета, так что может вычислить фактический битрайт, с которым он был отправлен.

Чтобы оценить максимально возможный битрейт, Flussonic периодически, через регулярные интервалы времени, отправляет группы так называемых пробных пакетов. Эти пакеты отправляются с битрейтом выше текущего. После получения этих пакетов Flussonic вычисляет их фактический битрейт, как описано выше. Если после очередной итерации вычисленный битрейт превышает битрейт следующего трека (с более высоким качеством) на 10 %, Flussonic переключается на следующий трек.

Этот механизм дает больше контроля и гибкости, так как большая часть его логики работает на стороне отправителя.

Note

Сейчас Flussonic использует этот механизм в тестовом режиме и только для проигрывания по WHAP.

Чтобы использовать механизм TWCC, добавьте в конфигурацию потока следующие параметры директивы webrtc_abr :

  • bitrate_prober=true – включить использование TWCC
  • bitrate_probing_interval– интервал отправки пробных пакетов, в секундах.

Например:

webrtc_abr bitrate_prober=true bitrate_probing_interval=2;