Содержание статьи:
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 – в ней находятся 4 файла PHP:
– webrtc2-sign.php – выполняет функции сигнального сервера по технологии WebRTC.
– webrtc2-sse.php – определяет каждые 2-3сек. статус пользователей (on/off) по технологии SSE.
– webrtc2-profile-tbls.php – создается список контактов в профайле пользователя зарегистрированных пользователей сайта, с которыми данный пользователь хочет иметь видео связь (адресная книга).
– webrtc2-template.php – используется клиентским приложением (WebRTC2_desktop). Это недавно разработанное приложение видео-связи на java для десктопов на windows.
папка 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-audio-processor.js – обработка аудио потока audio-processor (новое требование 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 интерфейс плагина в браузере пользователя.
файл 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 step1=1; var step2=1; var step3=1; var step4=1; var step5=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 (step1 == 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. [" + step1 + "x5sec.]"); } clearInterval(timer_send_sdp); } step1++; }, 5000); var timer_receive_answer = setInterval(async function() { // Receive sdp of webrtc2_guestId from server. remoteAnswer = await webrtc2_receive_sdp(webrtc2_guestId , "answer"); if (step2 == 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); }else{ webrtc2_dump_msg(webrtc2_hostId, ": Waiting for receive [Answer] from " + webrtc2_guestId + " expired. [" + step2 + "x5sec.]"); } clearInterval(timer_receive_answer); } step2++; }, 5000); var timer_receive_ice = setInterval(async function() { // Receive ice-candidates of webrtc2_guestId from server. iceCandidates = await webrtc2_receive_ice(webrtc2_guestId); if (step3 == 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. [" + step3 + "x5sec.]"); } clearInterval(timer_receive_ice); } step3++; }, 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 (step4 == 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(); // Send sdp of webrtc2_hostId to server. 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. [" + step4 + "x5sec.]"); } clearInterval(timer_receive_sdp); } step4++; }, 5000); var timer_receive_ice = setInterval(async function() { // Receive ice-candidates of webrtc2_guestId from server. iceCandidates = await webrtc2_receive_ice(webrtc2_guestId); if (step5 == 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. [" + step5 + "x5sec.]"); } clearInterval(timer_receive_ice); } step5++; }, 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 работает удовлетворительно. Практическое использование покажет истинное состояние дел в работоспособности плагина.