Общие выводы после разработки плагина WP-WebRTC2

Содержание статьи:
1.Состав плагина.
2.Использование модулей сторонних разработчиков.
3.Инструментарий разработчика.
4.Документация для разработки.
5.Цель разработки плагина.
6.Почему плагин бесплатный?
7.Отдельные моменты в разработке плагина.
8.Заключение.

1.Состав плагина.

Содержимое папки плагина после установки на сайт:

папка chart – в ней находится модуль создания диаграмм потоковых данных:
– smoothie.js от сторонних разработчиков (Smoothie Charts)
папка css – в ней находятся файлы CSS:
– dump.css определяет стиль внешнего файла-отчета, созданного нажатием кнопки Save плагина.
– webrtc2-dark.css создает темный стиль графического интерфейса плагина.
– webrtc2-light.css создает светлый стиль графического интерфейса плагина.
папка doc – в ней находится документация пользователя на русском и английском языках плагина.
папка images – в ней находятся картинки для графического интерфейса плагина.
папка includes – в ней находятся 2 файла PHP:
– webrtc2-sign.php – выполняет функции сигнального сервера по технологии WebRTC.
– webrtc2-sse.php – определяет каждые 2-3сек. статус пользователей (on/off) по технологии SSE.
папка js – в ней находятся 4 файла JS:
– webrtc2-init.js – инициирование объектов WebRTC с их параметрами из настроек плагина.
– webrtc2-interface – формирует графический интерфейс и управление им.
– webrtc2-service – дополнительные сервисные функции плагина.
– webrtc2-sign – вызов функций сигнального сервера на стороне клиента.
папка parser – в ней находится модуль определения браузера пользователя:
– detect.js от сторонних разработчиков (Darcy Clarke, Tobie Langel).
папка sound – в ней находятся 3 звуковых файла mp3:
– receive_call.mp3 – оповещение о вызове пользователя на видео-связь.
– receive_file – оповещение пользователя о приеме файла.
– receive_msg – оповещение пользователя о приеме сообщения.
файл class-webrtc2-core.php – формирует backend интерфейс плагина в административной панели управления сайтом.
файл class-webrtc2-shortcode.php – формирует frontend интерфейс плагина в браузере пользователя.
файл readme.txt – описание плагина по стандарту WordPress.
файл uninstall.php – удаление плагина по стандарту WordPress.
файл wp-webrtc2.php – основной файл плагина (регистрация, подключения CSS, JS).

2.Использование модулей сторонних разработчиков.

1. Модуль detect.js формирует информацию о браузере пользователя, которая передается плагином в шапку файла отчета, сформированного после нажатия кнопки Save плагина:

Использование модуля detect.js в плагине (модуль: webrtc2-service.js):

/**
 * Save to file.html selected content of win_messages.
 */
function webrtc2_msg_save() {
  var html      = document.createElement("html");
  var link      = document.createElement("link");
  var head      = document.createElement("head");
  var body      = document.createElement("body");
  var foot      = document.createElement("foot");
  var copyright = document.createElement("label");
  var img_flag  = document.createElement("img");

  var title    = document.createElement("h1");

  var info      = document.createElement("div");
  var userName  = document.createElement("label");
  var browser   = document.createElement("label");
  var device    = document.createElement("label");
  var os        = document.createElement("label");
  var date_time = document.createElement("label");

  var content  = document.createElement("div");

  var a        = document.createElement("a");
  var bl;

  //browser.family      Имя браузера
  //browser.name        Имя браузера и его версия
  //browser.version     Полная версия браузер
  //browser.major       Основной номер версии браузера
  //browser.minor       Дополнительный номер версии браузера
  //browser.patch       Номер патча браузера
  //device.family       Имя устройства
  //device.name         Имя устройства и версия
  //device.version      Полная версия устройства
  //device.major        Основной номер версии устройства
  //device.major        Дополнительный номер версии устройства
  //device.patch        Номер патча устройства
  //device.type         Тип устройства (например, "Desktop" или "Mobile")
  //device.manufacturer Производитель устройства
  //os.family           Название операционной системы
  //os.name             Полне имя операционной системы
  //os.version          Полная версия операционной системы
  //os.major            Основной номер версии операционной системы
  //os.minor            Дополнительный номер версии операционной системы
  //os.patch            Номер патча операционной системы

  var agentInfo = detect.parse(navigator.userAgent);

  userName.innerHTML  = "User name: " + webrtc2_hostId;
  userName.className  = "info";
  browser.innerHTML   = "Browser: " + agentInfo.browser.family + " version " + agentInfo.browser.major + "." + agentInfo.browser.minor + "." +  agentInfo.browser.patch;
  browser.className   = "info";
  device.innerHTML    = "Device: " + agentInfo.device.type;
  device.className    = "info";
  os.innerHTML        = "OS: " + agentInfo.os.name;
  os.className        = "info";
  date_time.innerHTML = "Date: " + new Date().toLocaleString();
  date_time.className = "info";

  if ("visible" == document.getElementById("fld_dump").style.visibility) {
    title.innerHTML = "WP-WebRTC2 protocol of dump";
  }
  if ("visible" == document.getElementById("fld_chat").style.visibility) {
    title.innerHTML = "WP-WebRTC2 protocol of chat";
  }

  img_flag.src = webrtc2_url + "doc/img/BY.gif";

  copyright.innerHTML = "Belarus, Minsk © 2019. Developer: Oleg Klenitsky";

  info.appendChild(userName);
  info.appendChild(browser);
  info.appendChild(device);
  info.appendChild(os);
  info.appendChild(date_time);

  link.rel = "stylesheet";
  link.href = webrtc2_url + "css/dump.css";
  link.type = "text/css";
  head.appendChild(link);

  html.appendChild(head);
  html.appendChild(title);
  body.appendChild(info);

  if ("visible" == document.getElementById("fld_dump").style.visibility) {
    var fld_dump = document.createElement("div");
    fld_dump.innerHTML = document.getElementById("fld_dump").innerHTML;
    fld_dump.id = "fld_dump_copy";
    fld_dump.style = "margin-top: 5px;";
    body.appendChild(fld_dump);
  }
  if ("visible" == document.getElementById("fld_chat").style.visibility) {
    var fld_chat = document.createElement("div");
    fld_chat.innerHTML = document.getElementById("fld_chat").innerHTML;
    fld_chat.id = "fld_chat_copy";
    fld_chat.style = "margin-top: 5px;";
    body.appendChild(fld_chat);
  }
  html.appendChild(body);
  foot.appendChild(img_flag);
  foot.appendChild(copyright);
  html.appendChild(foot);

  html = [html.outerHTML];

  bl = new Blob(html, {type: "text/html"});

  a.href = URL.createObjectURL(bl);
  if ("visible" == document.getElementById("fld_dump").style.visibility) {
    a.download = "dump.html";
  }
  if ("visible" == document.getElementById("fld_chat").style.visibility) {
    a.download = "chat.html";
  }

  html = null;

  a.hidden = true;
  document.body.appendChild(a);
  a.click();
}

2. Модуль smoothie.js создания диаграмм потоковых данных формирует графики статистики WebRTC:
– кнопка Graph1: report.type == “inbound-rtp” && report.mediaType == “video”
– кнопка Graph2 report.type == “outbound-rtp” && report.mediaType == “video”
– кнопка Graph3: report.type == “transport”
– кнопка Graph4: report.type == “remote-inbound-rtp” && report.kind == “video”
Использование модуля smoothie.js в плагине (модуль: webrtc2-service.js) на примере одного графика:

/**
 * Display graph1.
 */
function webrtc2_graph1() {
  document.getElementById("graph_local").style.visibility = "visible";
  document.getElementById("graph_remote").style.visibility = "visible";
  // Нажата
  document.getElementById("btn_graph1").style.boxShadow =
  "inset -2px -2px 3px rgba(200, 200, 200, .6),inset 2px 2px 3px rgba(0, 0, 0, .6)";
  // Отжата
    document.getElementById("btn_graph3").style.boxShadow =
  "inset 2px 2px 3px rgba(255, 255, 255, .6),inset -2px -2px 3px rgba(0, 0, 0, .6)";
  document.getElementById("btn_graph2").style.boxShadow =
  "inset 2px 2px 3px rgba(255, 255, 255, .6),inset -2px -2px 3px rgba(0, 0, 0, .6)";
  document.getElementById("btn_graph4").style.boxShadow =
  "inset 2px 2px 3px rgba(255, 255, 255, .6),inset -2px -2px 3px rgba(0, 0, 0, .6)";

  graph = "graph1";

  var smoothie = new SmoothieChart({grid:{sharpLines:true},tooltip:true,timestampFormatter:SmoothieChart.timeFormatter});

  // Legends
  document.getElementById("local_legend1").innerHTML  = "KbytesReceived/sec";
  document.getElementById("local_legend2").innerHTML  = "PacketsReceived/sec";
  // Data
  var line1 = new TimeSeries();
  var line2 = new TimeSeries();

  // Add to SmoothieChart
  smoothie.addTimeSeries(line1,
    { strokeStyle:'rgb(0, 255, 0)', fillStyle:'rgba(0, 255, 0, 0.4)', lineWidth:2 });
  smoothie.addTimeSeries(line2,
    { strokeStyle:'rgb(255, 0, 255)', fillStyle:'rgba(255, 0, 255, 0.3)', lineWidth:2 });
  smoothie.streamTo(document.getElementById("canvas_local"), 500);

  // Add value to each line every second
  var graph1_interval_local = setInterval(function() {
    var pliCount = (!!inbound_rtp_video.pliCount) ? inbound_rtp_video.pliCount : "0";
    var packetsLost = (!!inbound_rtp_video.packetsLost) ? inbound_rtp_video.packetsLost : "0";
    document.getElementById("graph_title_local").innerHTML =
    "<b>Local: </b> [inbound-rtp]:video packetsLost: " + packetsLost + "; pliCount: " + pliCount + ";";
    line1.append(inbound_rtp_video.timestamp, inbound_rtp_video.bytesReceivedPerSec);
    line2.append(inbound_rtp_video.timestamp, inbound_rtp_video.packetsReceivedPerSec);
    if (graph !== "graph1") {
      document.getElementById("graph_title_local").innerHTML = "<b>Local: Wait...</b>";
      clearInterval(graph1_interval_local);
    }
  }, 1000);
  document.getElementById("graph_title_remote").innerHTML = "<b>Remote:</b> Wait...";
  webrtc2_graph1_remote();
}

3.Инструментарий разработчика.

В процессе разработки плагина постоянно использовалась отладка WebRTC, встроенная в браузеры:
1. Для Chrome: chrome://webrtc-internals/
2. Для FireFox: about:webrtc
3. Для Opera: opera://webrtc-internals
4. Для Edge: edge://webrtc-internals/
5. Для Yandex: browser://webrtc-internals/

4.Документация для разработки.

Существует много интересной информации ознакомительного характера о технологии WebRTC. Ее можно без особого труда найти на просторах интернета. Поэтому я лишь приведу перечень документации, которая была полезна в конкретных случаях реализации функционала в плагине:
1. WebRTC 1.0
2. Getting Started WebRTC
3. Anatomy of a WebRTC SDP.
4. WebRTC-stats.
На мой личный взгляд, будущее WebRTC 1.0 – туманно, неоднозначно. С одной стороны – технология WebRTC – великолепна. Даже со своими текущими багами. Но ведь это проблемы роста. Обычное дело. Но вот что смущает: зреющий новый WebRTC 2.0 может стать могильщиком, а не продолжателем WebRTC 1.0. Звериный оскал капитализма не предполагает преемственности поколений, технологий. Это скорее похоже на тараканьи бега по вертикали. Кто первый, любой ценой. И если продолжать без аллегорий, то может однажды придут веселые дяди из Microsoft (Edge), Google, Hookflash и скажут: все что вы все сделали на основе WebRTC 1.0 – выбросить, теперь все будете работать по WebRTC 2.0.
Откуда такое настроение? Читайте сами, думайте сами:
1. OBJECT-RTC – IS IT WRTC 2.0?
2. What Developers Should Know About ORTC

5.Цель разработки плагина.

Когда-то, работая в одном учреждении, мы использовали Skype для организации видео-мостов между странами на высоком государственном уровне. К сожалению были обрывы связи, замедление трансляции. Да и смущал тот факт, что некоторые реплики, как впрочем и вся трансляция писалась на английских серверах без нашего на то решения. Потому, что Skype.
С тех пор прошло много времени. Перестройка, дружба с Западным “цивилизованным миром” в засос, развал СССР – в прошлом. Растет внучка 12 лет. И захотелось делать с ней уроки не по телефону, а по видео-связи. Вот так и появился WP-WebRTC2.

6.Почему плагин бесплатный?

Мне нравится философия WordPress: бесплатность и доступность для ВСЕХ к современным технологиям. Это не реклама. Это и мой жизненный принцип. Тут, что называется: нашли друг друга два одиночества. 🙂

7.Отдельные моменты в разработке плагина.

На мой взгляд центральными и основными моментами в этом плагине видео-связи являются два:
1. как реализован основной принцип сигнализации в плагине?
2. как реализованы дополнительные возможности сигнализации, и в связи с чем?
Весь остальной функционал имеет пусть и нужное, но все-таки второстепенное значение. Итак:
Ниже представлен код основной функции сигнализации на стороне клиента (webrtc2-sign.js).
Сама по себе эта функция webrtc2_chat_init(offerOptions) создает соединение с вероятностью 50-60%.

/**
 * Peer to Peer video-chat.
 *
 * @param string offerOptions Offer options of webrtc2_hostId.
 */
async function webrtc2_chat_init(offerOptions) {
  var tableMembersRows = document.getElementById("wins1_tbody2").getElementsByTagName("tr");
  var localOffer;
  var remoteAnswer;
  var iceCandidates;
  var step   = 1;
  var td1    = tableMembersRows[1].querySelectorAll("td")[1];

  webrtc2_guestId = td1.innerHTML.replace( /<(.+?)> /, "" );

	try{
    if (webrtc2_initiator) {
      // Init data channel for receive/send of messages and files.
      var channel_msg = pc.createDataChannel("dataChannel");
      channel_msg.binaryType = "arraybuffer";
      webrtc2_datachannel_msg(channel_msg);

      // Init data channel for statistics connection of guestId.
      var channel_stat = pc.createDataChannel("statChannel");
      channel_stat.binaryType = "arraybuffer";
      webrtc2_datachannel_stat(channel_stat);

      localOffer = await pc.createOffer(offerOptions);
      await pc.setLocalDescription(localOffer);
      webrtc2_log_sdp(webrtc2_hostId, "[Offer]:", pc.localDescription);
      var timer_send_sdp = setInterval(async function() {
        // Send sdp of webrtc2_hostId to server.
        if (step == 36 || "complete" === pc.iceGatheringState) {
          if ("complete" === pc.iceGatheringState) {
            webrtc2_send_sdp(JSON.stringify(pc.localDescription), "offer");
            webrtc2_dump_msg(webrtc2_hostId, ": Send [Offer] to "+ webrtc2_guestId);
          }else{
            webrtc2_dump_msg(webrtc2_hostId, ": Waiting for send [Offer] from " + webrtc2_hostId + " expired. [" + step + "x5sec.]");
          }
          clearInterval(timer_send_sdp);
        }
        step++;
      }, 5000);
      var timer_receive_answer = setInterval(async function() {
        // Receive sdp of webrtc2_guestId from server.
        remoteAnswer = await webrtc2_receive_sdp(webrtc2_guestId , "answer");
        if (step == 36 || remoteAnswer) {
          if (remoteAnswer) {
            webrtc2_dump_msg(webrtc2_hostId, ": Receive [Answer] from "+ webrtc2_guestId);
            await pc.setRemoteDescription(remoteAnswer);
            webrtc2_log_sdp(webrtc2_guestId, "[Answer]:", remoteAnswer);
            var timer_receive_ice = setInterval(async function() {
              // Receive ice-candidates of webrtc2_guestId from server.
              iceCandidates = await webrtc2_receive_ice(webrtc2_guestId);
              if (step == 36 || iceCandidates) {
                if (iceCandidates) {
                  webrtc2_dump_msg(webrtc2_hostId, ": Receive [iceCandidates] from "+ webrtc2_guestId);
                  for (var iceCandidate of iceCandidates) {
                    try {await pc.addIceCandidate(new RTCIceCandidate(iceCandidate));}
                    catch {error => webrtc2_log_err(error.name + ": ",  error.message);}
                  }
                }else{
                  webrtc2_dump_msg(webrtc2_hostId, ": Waiting for receive [iceCandidates] from " + webrtc2_guestId + " expired. [" + step + "x5sec.]");
                }
                clearInterval(timer_receive_ice);
              }
              step++;
            }, 5000);
          }else{
            webrtc2_dump_msg(webrtc2_hostId, ": Waiting for receive [Answer] from " + webrtc2_guestId + " expired. [" + step + "x5sec.]");
          }
          clearInterval(timer_receive_answer);
        }
        step++;
      }, 5000);
    }else{
      var timer_receive_sdp = setInterval(async function() {
        // Receive sdp of webrtc2_guestId from server.
        localOffer = await webrtc2_receive_sdp(webrtc2_guestId , "offer");
        if (step == 36 || localOffer) {
          if (localOffer) {
            webrtc2_dump_msg(webrtc2_hostId, ": Receive [Offer] from "+ webrtc2_guestId);
            await pc.setRemoteDescription(localOffer);
            webrtc2_log_sdp(webrtc2_guestId, "[Offer]:", localOffer);
            remoteAnswer = await pc.createAnswer();
            webrtc2_send_sdp(JSON.stringify(remoteAnswer), "answer");
            webrtc2_dump_msg(webrtc2_hostId, ": Send [Answer] to "+ webrtc2_guestId);
            await pc.setLocalDescription(remoteAnswer);
            webrtc2_log_sdp(webrtc2_hostId, "[Answer]: ", remoteAnswer);
          }else{
            webrtc2_dump_msg(webrtc2_hostId, ": Waiting for receive [Offer] from " + webrtc2_guestId + " expired. [" + step + "x5sec.]");
          }
          clearInterval(timer_receive_sdp);
        }
        step++;
      }, 5000);
      var timer_receive_ice = setInterval(async function() {
        // Receive ice-candidates of webrtc2_guestId from server.
        iceCandidates = await webrtc2_receive_ice(webrtc2_guestId);
        if (step == 36 || iceCandidates) {
          if (iceCandidates) {
            webrtc2_dump_msg(webrtc2_hostId, ": Receive [iceCandidates] from "+ webrtc2_guestId);
            for (var iceCandidate of iceCandidates) {
              try {await pc.addIceCandidate(new RTCIceCandidate(iceCandidate));}
              catch {error => webrtc2_log_err(error.name+ ": ",  error.message);}
            }
          }else{
            webrtc2_dump_msg(webrtc2_hostId, ": Waiting for response [iceCandidates] from " + webrtc2_guestId + " expired. [" + step + "x5sec.]");
          }
          clearInterval(timer_receive_ice);
        }
        step++;
      }, 5000);
    }
  }catch{error => webrtc2_log_err(error.name + ": ",  error.message);}
}

Чтобы повысить вероятность соединения где-то до 80% используется другая функция webrtc2_peer_init() из модуля webrtc2-init.js

pc.oniceconnectionstatechange = e => {
... фрагмент кода ...
if (pc.iceConnectionState == "disconnected" || pc.iceConnectionState == "failed") {
  document.getElementById("btn_send_file").style.color = "white";
  document.getElementById("btn_send_file").disabled    = true;

  document.getElementById("wins1_ticker").innerHTML = "Chat is closed.";
  document.getElementById("wins1_ticker").style.color = "black";
  document.getElementById("wins2_ticker").innerHTML = "Chat is closed.";
  document.getElementById("wins2_ticker").style.color = "white";

  clearInterval(screen_stat_timer);
  clearInterval(local_stat_timer);

  offerOptions.iceRestart = true;

  if (webrtc2_initiator) {
    webrtc2_delete_sdp(webrtc2_hostId, webrtc2_guestId, "offer");
    webrtc2_delete_sdp(webrtc2_hostId, webrtc2_guestId, "answer");
  }
  var time_failure = new Date();
  time_failure = time_failure.getTime();
  webrtc2_send_signal(time_failure);

  // Receive time_failure of webrtc2_hostId and webrtc2_guestId from server.
  var step = 1;
  var receive_signal = setInterval(async function() {
    var parts = await webrtc2_receive_signal( webrtc2_hostId, webrtc2_guestId );
    if (parts) {
      parts = parts.split( "," );
      // Time failure of webrtc2_hostId is parts[0].
      // Time failure of webrtc2_guestId is parts[1].
      if ( "" !== parts[0] && "" !== parts[1] ){
        webrtc2_dump_msg(webrtc2_hostId, ": Video-chat [iceRestart : " + offerOptions.iceRestart + '] ...');
        webrtc2_chat_init(offerOptions);
        clearInterval(receive_signal);
      }
    }
    if ( step == 36 ){
      webrtc2_dump_msg(webrtc2_hostId, ": waiting for response signal from server expired. [" + step + "x5sec.]");
      clearInterval(receive_signal);
    }
    step++;
  }, 5000);
}

Ключевой момент в строке 15: offerOptions.iceRestart = true;

8.Заключение.

Предварительные испытания показали, что плагин WP-WebRTC2 работает удовлетворительно. Практическое использование покажет истинное состояние дел в работоспособности плагина.

Leave a Reply

Your email address will not be published. Required fields are marked *