Анатомия плагина WP-WebRTC2

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

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

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

папка chart – в ней находится модуль создания диаграмм потоковых данных:
– smoothie.js от сторонних разработчиков (Smoothie Charts)
папка css – в ней находятся файлы CSS:
– dump.css определяет стиль внешнего файла-отчета, созданного нажатием кнопки Report плагина.
– webrtc2-dark.css создает темный стиль графического интерфейса плагина.
– webrtc2-light.css создает светлый стиль графического интерфейса плагина.
– webrtc2-backend.css – определяет стиль раздела “Users for Video chat” profile зарегистрированного пользователя сайта.
папка doc – в ней находится документация пользователя на русском и английском языках плагина.
папка images – в ней находятся картинки для графического интерфейса плагина.
папка includes – в ней находятся 6 файлов PHP:
– webrtc2-sign.php – выполняет функции сигнального сервера по технологии WebRTC.
– webrtc2-sse.php – определяет каждые 2-3сек. статус пользователей (on/off) по технологии SSE.
– webrtc2-profile-tbls.php – создается список контактов в профайле пользователя зарегистрированных пользователей сайта, с которыми данный пользователь хочет иметь видео связь (адресная книга).
– webrtc2-template.php – используется клиентским приложением (download: WebRTC2_desktop). Инструкция по использованию приложения WebRTC2_desktop: [ru]
Это недавно разработанное приложение видео-связи на java для десктопов на windows.
– webrtc2-create-tables.php – создает 3 пользовательские таблицы (webrtc2_countries, webrtc2_call_stat, webrtc2_stun_servers) в БД сайта.
– webrtc2-ip-info.php – берет на вход IP пользователя и возвращает информацию об этом IP.
папка js – в ней находятся 8 файлов JS:
– webrtc2-variables.js – перечень глобальный переменных для удобства программиста – собранные в один файл.
– webrtc2-init.js – инициирование объектов WebRTC с их параметрами из настроек плагина.
– webrtc2-interface.js – формирует графический интерфейс и управление им.
– webrtc2-service.js – дополнительные сервисные функции плагина.
– webrtc2-sign.js – вызов функций сигнального сервера на стороне клиента.
– webrtc2-modify-sdp.js – позволяет на лету изменять пропускную способность MediaBitrate в случае плохой видео-связи.
– webrtc2-profile.js – управляет списком контактов в профайле пользователя.
– webrtc2-meter-wrklt.js / webrtc2-meter-prcsr.js – обработка аудио потока (новое требование Google с 2020 года вместо устаревшего JavaScriptNode). Вызывается из модуля webrtc2-service.js
папка 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 интерфейс плагина в браузере пользователя.
файл class-webrtc2-stun-client.php – проверяет работоспособность Stun серверов по списку.
файл class-webrtc2-tbl-stat.php – создает таблицу статистики видеозвонков в админке сайта.
файл class-webrtc2-tbl-srv.php – создает таблицу Stun серверов в админке сайта.
файл readme.txt – описание плагина по стандарту WordPress.
файл uninstall.php – удаление плагина по стандарту WordPress.
файл wp-webrtc2.php – основной файл плагина (регистрация, подключения CSS, JS).

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

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

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

/**
 * Save to file.html selected content of win_messages.
 */
function webrtc2_msg_report() {
  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. как реализован принцип сигнализации в плагине?
Весь остальной функционал имеет пусть и нужное, но все-таки второстепенное значение. Итак:
Ниже представлен код основной функции сигнализации на стороне клиента (webrtc2-sign.js).
Сама по себе эта функция webrtc2_chat_init() создает соединение путем отправки по мере готовности определенной информации (sdp, ice-candidates) и приема соответствующей информации (sdp, ice-candidates) в ожидающем цикле setInterval().

/**
 * Peer to Peer video-chat.
 */
function webrtc2_chat_init() {
	let webrtc2_guestId = sessionStorage.getItem("webrtc2_guestId");
	let timer_receive_answer;
	let timer_receive_ice;

	if ( "true" == sessionStorage.getItem("webrtc2_initiator") ) {
		// Init data channel for receive/send of messages and files.
		webrtc2_dataChannel = webrtc2_pc.createDataChannel("dataChannel");
		webrtc2_dataChannel.binaryType = "arraybuffer";
		webrtc2_datachannel_msg();

		// Init data channel for statistics data of guestId.
		webrtc2_statChannel = webrtc2_pc.createDataChannel("statChannel");
		webrtc2_statChannel.binaryType = "arraybuffer";
		webrtc2_datachannel_stat();

		webrtc2_pc.createOffer(webrtc2_offerOptions)
		// Modifying the SDP to check ice-ufrag, ice-pwd.
		.then(offer => {
			offer.sdp = webrtc2_chk_ufrag_pwd(webrtc2_hostId, "Offer", offer.sdp);
			return offer;
		})
		.then(offer => {
			webrtc2_pc.setLocalDescription(offer);
			return offer;
		})
		// modify the SDP after calling setLocalDescription.
		.then(offer => {
			offer.sdp = webrtc2_setMediaBitrates(webrtc2_hostId, "Offer", offer.sdp);
			return offer;
		})
		.then(offer => {
			webrtc2_log_sdp(webrtc2_hostId, " -> Send [Offer] to " + webrtc2_guestId + ":", offer.sdp);
			webrtc2_send_sdp(JSON.stringify(offer), "offer");
		})
		// Receive answer sdp of webrtc2_guestId from server.
		.then(timer_receive_answer = setInterval(() => {
			webrtc2_receive_sdp("answer").then(answer => {
				if (answer) {
					webrtc2_log_sdp(webrtc2_hostId, " -> Receive [Answer] from " + webrtc2_guestId + ":", answer.sdp);
					webrtc2_pc.setRemoteDescription(answer);
					clearInterval(timer_receive_answer);
				}
			})
		}, 10000))
		// Receive ice-candidates of webrtc2_guestId from server.
		.then(timer_receive_ice = setInterval(() => {
			webrtc2_receive_ice().then(iceCandidates => {
				if (iceCandidates) {
					webrtc2_ice_remote(iceCandidates);
					for (let iceCandidate of iceCandidates) {
						webrtc2_pc.addIceCandidate(iceCandidate).catch(err => console.log(err));
					}
					clearInterval(timer_receive_ice);
				}
			})
		}, 10000));
	}else{
		// Receive offer sdp of webrtc2_guestId from server.
		let timer_receive_offer = setInterval(function () {
			webrtc2_receive_sdp("offer").then(offer => {
				if (offer) {
					webrtc2_log_sdp(webrtc2_hostId, " -> Receive [Offer] from " + webrtc2_guestId + " :", offer.sdp);
					webrtc2_pc.setRemoteDescription(offer).then(() => webrtc2_pc.createAnswer())
					.then((answer) => {
						// Modifying the SDP to check ice-ufrag, ice-pwd.
						answer.sdp = webrtc2_chk_ufrag_pwd(webrtc2_hostId, "Answer", answer.sdp);
						return answer;
					})
					.then(answer => {
						webrtc2_pc.setLocalDescription(answer);
						return answer;
					})
					// modify the SDP after calling setLocalDescription.
					.then(answer => {
						answer.sdp = webrtc2_setMediaBitrates(webrtc2_hostId, "Answer", answer.sdp);
						return answer;
					})
					.then(answer => {
						webrtc2_log_sdp(webrtc2_hostId, " -> Send [Answer] to " + webrtc2_guestId + ":", answer.sdp);
						webrtc2_send_sdp(JSON.stringify(answer), "answer");
					})
					// Receive ice-candidates of webrtc2_guestId from server.
					.then(timer_receive_ice = setInterval(() => {
						webrtc2_receive_ice().then(iceCandidates => {
							if (iceCandidates) {
								webrtc2_ice_remote(iceCandidates);
								for (let iceCandidate of iceCandidates) {
									webrtc2_pc.addIceCandidate(iceCandidate).catch(err => console.log(err));
								}
								clearInterval(timer_receive_ice);
							}
						})
					}, 10000));
					clearInterval(timer_receive_offer);
				}
			})
		}, 10000);
	}
}

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

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