Skip to content

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

Вы можете проиграть по WebRTC любой поток, например, полученный по WHIP как описано в разделе Публикация по WebRTC, или любой другой, настроенный на вашем сервере Flussonic (см. Варианты источников). Для проигрывания можно использовать наш плеер embed.html, любой плеер с поддержкой WebRTC или приложение вашей собственной разработки.

Проигрывание WebRTC-потоков осуществляется про стандарту WHEP. О том, что такое WebRTC, WHIP и WHEP, читайте в главе Использование протокола WebRTC.

Ссылки для проигрывания потоков по WebRTC

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

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

где:

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

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

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

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

На клиенте нужно исполнить код для проигрывания видео по этому URL. Ниже приведены рекомендации по созданию такого клиентского приложения, включая полный пример кода.

Рекомендации по созданию клиентского приложения

Для создания кода используйте библиотеку 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 и 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;
      }
      .preview {
        position: absolute;
        right: 0;
        bottom: 0;
        z-index: 100;
      }

      .preview-text {
        position: absolute;
        left: 0;
        top: 0;
        padding: 8px;
        background: black;
        color: white;
        z-index: 10;
      }

      #preview-video {
        width: 320px;
        height: auto;
        max-width: 320px;
        max-height: 240px;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/@flussonic/flussonic-webrtc-player/dist/index.js"></script>
  </head>
  <body>
    <div class="app">
    <div class="preview">
        <div class="preview-text">preview</div>
        <video id="preview-video" controls="false" muted autoplay playsinline />
    </div>
      <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>

  <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-video'),
        constraints: {
          // video: {
          //   height: { exact: height }
          // },
          video: true,
          audio: true,
        },
        canvasCallback: (canvasElement) => {
            window.myCanvasElement = canvasElement;
        },
      };
    };

    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, streaming);
      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();
    };

    const streaming = () => {
        getPublishElement().innerText = 'Publishing...';
        getPublishElement().disabled = true;

        // drawing on publishing canvas
        if (window.myCanvasElement) {
          const ctx = window.myCanvasElement.getContext('2d', { alpha: false });
          ctx.filter = 'sepia(0.75)'; // Testing filters
          ctx.font = '128px sans-serif';
          ctx.fillStyle = 'white';
          ctx.textAlign = 'center';
          ctx.textBaseline = 'center';
          (function loop() {
            ctx.fillText(
              `It's publishing to Flussonic!`,
              window.myCanvasElement.width / 2,
              window.myCanvasElement.height / 2,
              window.myCanvasElement.width - 100,
            );
            setTimeout(requestAnimationFrame(loop), 1000 / 30); // drawing at 30fps
          })();
      }
    };

    window.addEventListener('load', main);
  </script>
    </body>
</html>

Скопируйте этот код в файл, например index.html, и откройте в браузере, чтобы проверить работу.

Балансировка нагрузки при проигрывании по WHEP

Благодаря тому, что WHEP основан на HTTP POST-запросах, вы можете применять балансировщик нагрузки для распределения запросов на проигрывание между серверами в кластере. Балансировщик будет перенаправлять POST-запросы на серверы в кластере, используя HTTP код перенаправления 307.

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_abr из настроек потока.

Для того чтобы задать доступные в 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 не задан, или вместо видео- указана аудиодорожка (start_track=a3, или указанная видеодорожка не существует, то проигрывание начнётся с дорожки из середины списка доступных (например, v2 если у вас есть дорожки v1, v2 и v3) и в дальнейшем подстроится под пропускную способность канала.

Если дорожки ограничены параметром строки запроса ?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

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

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

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

Например:

webrtc_abr bitrate_prober=true bitrate_probing_interval=2;