410013796724260
• Webmoney
R335386147728
Z369087728698
Пример простого RTCDataChannelAPI WebRTC включает интерфейс RTCDataChannel, который позволяет установить соединение между двумя узлами для отправки и получения по этому каналу произвольной информации. В представленном здесь примере демонстрируется процесс установления соединения двух узлов и передачи между ними данных в виде текстовой строки и изображения. В качестве прототипа будет использован пример, который Вы можете скачать с Github. В данном примере определяется последовательность установления соединения между двуми узлами, но на одной странице. Несмотря на то, что это явно надуманный сценарий, но он позволяет сделать небольшой шаг в освоении технологии WebRTC. Исходный код прототипа немного модифицируем и используем в контенте страницы дополнительно пример включения камеры и захвата изображения, которое будем передавать по установленному каналу с использованием RTCDataChannel. То есть, в отличие от прототипа, в нашем модифицированном примере по каналу связи мы будем передавать не только текстовую строку, но и изображение. Для реализации данного примера интерфейс страницы разделим на 2 блока. В первом блоке разместим компоненты управления камерой и представления видеопотока, а также компонент отображения захваченного видео-кадра. Второй блок будет состоять из двух частей. В первой части второго блока разместим компоненты установления соединения и отправки по каналу сообщения и/или изображения из первого блока. Во второй части разместим компонент отображения принятого сообщения. Видео-блок управления камеройИсходный код примера включения камеры и захвата изображения здесь немного изменим, чтобы при отключении камеры захваченый видео-кадр стирался и «вывешивался» флаг (в скриптах, не в интерфейсе). Это позволит отправить отдельно только текстовое сообщение. Для этого в методы takepicture и shutdown скриптового файла webcam.js (см. описание примера) внесем небольшие изменения. Так в метод takepicture добавим строку $('#photo').addClass('picture'), которая будет сигнализировать о захваченном кадре. В метод shutdown добавим строку $('#photo').removeClass('picture'), которая будет сбрасывать флаг захваченного кадра, после чего вызовем метод очистки изображения. Вы можете раскрыть листинг и посмотреть на изменения методов. Раскрыть листинг изменений webcam.js function takepicture() { var context = canvas.getContext('2d'); if (width && height) { canvas.width = width; canvas.height = height; context.drawImage(video, 0, 0, width, height); var data = canvas.toDataURL('image/png'); photo.setAttribute('src', data); // установка флага добавлением класса $('#photo').addClass('picture'); } else { clearphoto(); } } function shutdown() { if (streaming) { ... // сброс флага удалением класса $('#photo').removeClass('picture'); // очистка захваченного видео-кадра clearphoto(); } } Блок передачи данныхMessages received :
Примечание : Описание примераДля установения канала связи необходимо нажать кнопку «Подключение». При удачном соединении доступ к кнопке будет заблокирован, но откроется доступ к кнопке «Отключение» и будет разблокировано поле ввода текста «Введите сообщение» с кнопкой «Отправить сообщение». После этого можно ввести текст и нажать кнопку «Отправить сообщение». В результате текст сообщения появится в узле приема сообщений. Чтобы отправить изображение (фото) необходимо включить камеру и захватить изображение. После этого кнопкой «Отправить сообщение» фотография будет доставлена в блок приема. Для совместной передачи изображения и текстового сообщения необходимо захватить видео-кадр и написать текстовое сообщение; при отправке сообщения выполняется контроль захваченного кадра и текстовой строки. Изображение и текст отправляются разными сообщениями, т.е. двумя передачами. Листинг страницы htmlОписание html-кода камеры рассмотрено в примере захвата видеокадра с WebRTC. Поэтому здесь ограничимся рассмотрением только интерфейса второй части примера : установления соединения и отправки сообщения. В отличие от прототипа примера использования RTCDataChannel интерфейсные изменения коснулись только внесения дополнительных (уточняющих) надписей и комментария. В секции заголовка <head> определены css-файлы со стилями и js-файлы со скриптами. Кроме этого, дополнительно загружается файл адаптера adapter.js для обеспечения совместимости при использовании методов технологии WebRTC. Скрыть листинг html<head> <link href="webrtc-camera.css" type="text/css" rel="stylesheet" media="all"> <link href="webrtc-datachannel.css" type="text/css" rel="stylesheet" media="all"> <script src="webrtc-camera.js"></script> <script src="webrtc-datachannel.js"></script> <script src="adapter.js"></script> </head> <body> <div class="controlbox"> <button id="connectButton" name="connectButton" class="buttonleft">Подключение</button> <button id="disconnectButton" name="disconnectButton" class="buttonright" disabled>Отключение</button> </div> <b>Узел отправки сообщения</b> <div class="messagebox"> <label for="messageInput">Введите сообщение : <input type="text" name="messageInput" id="messageInput" placeholder="Message text" inputmode="latin" size=59 maxlength=120 disabled> </label> <button id="sendButton" name="sendButton" class="buttonsend" disabled>Отправить сообщение</button> <span style="font-size:0.75em;font-style:italic; float:left;margin-left:5px;color:#444"> В сообщение можно включить фото и текст (вместе или по отдельности)</span> </div> <b>Узел приема сообщения</b> <div class="receivebox" id="receivebox"> <p>Messages received : </p> </div> </body> Листинг datachannel.cssСодержимое css-файла незначительно отличается от исходного прототипа. Изменения касаются только размеров messagebox и controlbox, а также положения кнопки buttonsend. Скрыть листинг datachannel.css .messagebox { border: 1px solid black; padding: 5px; width: 450px; height: 90px; margin-bottom:10px; } .receivebox { border: 1px solid black; padding: 5px; width: 450px; height: 280px; } .buttonright { float: right; width: 120px; } .buttonleft { float: left; width: 120px; } .buttonsend { float: left; margin-top:10px; margin-bottom:5px; width: 160px; } .controlbox { padding: 5px; width: 450px; height: 28px; margin-bottom:10px; } Листинг datachannel.jsНиже представлен листинг скриптового файла datachannel.js, в котором обозначена только структура : переменные и наименования методов. Наименования переменных отражают элементы доменной структуры страницы. Исходные коды и описание методов будет представлено ниже. Обратите внимание, что скриптовый блок включен в анонимную функцию с целью избежания конфликтов глобальных переменных. В последней строке функции подключается слушатель загрузки страницы с последующим вызовом метода init, в котором выполняется инициализация переменных. Скрыть листинг datachannel.js (function() { // Определение глобальных переменных // кнопки управления var btnConnect = null; var btnDisconn = null; var btnSend = null; // блоки ввода и получения сообщений var boxInput = null; var boxReceive = null; // RTCPeerConnection соединения : локальное и удаленное var connectionLocal = null; var connectionRemote = null; // RTCDataChannel канал данных : // локальный (отправитель) и удаленный (получатель) var sendChannel = null; var receiveChannel = null; function init() {...} function createPeerConnection() {...} function handleCreateDescriptionError() {...} function handleLocalAddCandidateSuccess() {...} function handleRemoteAddCandidateSuccess() {...} function handleAddCandidateError() {...} function receiveChannelCallback() {...} function handleReceiveChannelStatusChange(){...} function handleSendChannelStatusChange() {...} function sendMessage() {...} function handleReceiveMessage() {...} function closePeerConnection() {...} // Подключение слушателя для вызова метода init после // загрузки страницы и формирования структуры DOM window.addEventListener('load', init, false); })(); Листинг метода initВ методе init выполняется инициализация переменных с использованием метода document.getElementById(), т.е. «связываются» переменные с элементами DOM страницы. Кроме этого, к кнопкам управления соединением подключаются слушатели, которые определяют вызываемые при нажатии на кнопки методы. Скрыть метод init function init() { btnConnect = document.getElementById('connectButton' ); btnDisconn = document.getElementById('disconnectButton'); btnSend = document.getElementById('sendButton' ); boxInput = document.getElementById('message' ); boxReceive = document.getElementById('receivebox' ); // Подключение слушателей к кнопкам управления btnConnect.addEventListener('click', createPeerConnection, false); btnDisconn.addEventListener('click', closePeerConnection , false); btnSend .addEventListener('click', sendMessage , false); } Листинг метода createPeerConnectionМетод createPeerConnection вызывается при нажатии на кнопку btnConnect. В данном методе создаются локальный и удаленный узлы соединения (connectionLocal, connectionRemote). После этого создается канал данных sendChannel типа RTCDataChannel, к которому подключаются слушатели установления связи (onopen) и закрытии канала onclose. К удаленному соединению подключается слушатель установления канала данных ondatachannel. Следующим шагом к локальному и удаленным узлам соединения подключаются слушатели ICE кандидатов. Они сработают когда будут созданы новые экземпляры ICE для обмена с другим узлом. Таким образом, к каждому узлу соединения PeerConnection подключается обработчик события для icecandidate. Для установления соединения между двумя узлами последним шагом в методе создается предложение createOffer, которое выглядит очень «навороченным»; на самом деле оно представляет цепочку действий, описанных после листинга. Скрыть метод createPeerConnection function createPeerConnection() { // Создание локального и удаленного соединений connectionLocal = new RTCPeerConnection(); connectionRemote = new RTCPeerConnection(); // Создание канала данных RTCDataChannel и // подключение слушателей sendChannel = connectionLocal.createDataChannel( "sendChannel"); sendChannel.onopen = handleSendChannelStatusChange; sendChannel.onclose = handleSendChannelStatusChange; // Подключениек к удаленному соединению слушателя // установления канала connectionRemote.ondatachannel = receiveChannelCallback; // Подключение слушателей ICE кандидатов для соединений connectionLocal.onicecandidate = e => !e.candidate || connectionRemote.addIceCandidate(e.candidate) .catch(handleAddCandidateError); connectionRemote.onicecandidate = e => !e.candidate || connectionLocal.addIceCandidate(e.candidate) .catch(handleAddCandidateError); // Создание предложения offer-answer на установление // соединения; старт процесса connectionLocal .createOffer() .then(offer => connectionLocal.setLocalDescription( offer)) .then(() => connectionRemote.setRemoteDescription( connectionLocal.localDescription)) .then(() => connectionRemote.createAnswer()) .then(answer => connectionRemote.setLocalDescription( answer)) .then(() => connectionLocal.setRemoteDescription( connectionRemote.localDescription)) .catch(handleCreateDescriptionError); } Процесс предложение-ответПроцесс «предложение-ответ» (offer-answer), описанный в RFC3264 и использующий SDP (Session Description Protocol), выполняется как для установления первого соединения, так и в любой момент, когда формат соединения или какие-либо конфигурации нуждаются в изменении. В этом процессе выделяется два агента : инициатор offerer — это тот, кто первым генерирует SDP-описание сессии для создания новой или модификации существующей, и answerer — это тот, кто получает SDP-описание сессии от другого агента и отвечает ему собственным описанием сессии (answer SDP). Рассмотрим процесс создания «предложения» и получения «ответа», организуемым в примере, по строкам/частям :
Листинг метода handleCreateDescriptionError// Ошибка может возникнуть как при создании // предложения offer, так и при формировании answer function handleCreateDescriptionError(error) { console.log("Unable to create an offer: " + error.toString()); } Обработка установления соединенияНа каждой из сторон успешного однорангового peer-to-peer соединения вызываются RTCPeerConnection обработчики icecandidate, которые можно использовать для выполнения необходимых действий. В примере эти обработчики используются для блокировки кнопки создания соединения connectButton и разблокировки кнопки закрытия соединения disconnectButton. В случае возникновения ошибки создания P2P (peer-to-peer) соединения вызывается метод handleAddCandidateError. Скрыть методы установления соединения // Функция успешного добавления ICE кандидата // на локальном узле соединения function handleLocalAddCandidateSuccess() { btnConnect.disabled = true; } // Функция успешного добавления ICE кандидата // на «удаленном» узле соединения function handleRemoteAddCandidateSuccess() { btnDisconn.disabled = false; } // Метод обработки ошибки при добавлении ICE кандидата function handleAddCandidateError() { console.log("add ICE candidate failed"); } Открытие канала передачи данных удаленного узлаКак только успешно установлено одноранговое P2P соединение RTCPeerConnection для завершения процесса открытия канала данных удаленному узлу связи отправляется событие datachannel, которое вызывает функцию обратной связи receiveChannelCallback, описанную после листинга : Скрыть методы установления соединения // Функция вызывается после установления соединения P2P и // готовности установления канала данных с удаленным узлом function receiveChannelCallback(event) { receiveChannel =event.channel; receiveChannel.onmessage=handleReceiveMessage; receiveChannel.onopen =handleReceiveChannelStatusChange; receiveChannel.onclose =handleReceiveChannelStatusChange; } // Обработчик изменения состояния канала данных удаленного // узла : event.currentTarget имеет тип RTCDataChannel function handleReceiveChannelStatusChange(event) { if (receiveChannel) { // при наличии канала данных 'open' console.log("Receive channel's status has changed to '"+ receiveChannel.readyState + "'"); } else { // при отсутствии канала данных 'closed' console.log("Receive channel's status has changed to '"+ event.currentTarget.readyState + "'"); } } Свойства события datachannel включают ссылку на RTCDataChannel, представляющий удаленный узел связи и включающий помимо прочего :
В методе receiveChannelCallback создается канал, у которого к слушателю события onmessage подключается метод получения данных handleReceiveMessage. Обработчик handleReceiveChannelStatusChange будет вызываться каждый раз, как только будет меняться статус канала связи, чтобы можно было бы вовремя реагировать. Параметр метода event включает свойство type, имеющее значение либо "open", либо "close". Свойства target и currentTarget параметра event имеют тип RTCDataChannel. Открытие канала передачи данных локального узлаПри открытии канала данных, равно как и при закрытии канала на локальном узле вызывается метод handleSendChannelStatusChange. Если состояние канала изменяется на "open", то это свидетельствует о том, что соединение между двумя P2P узлами установлено успешно. В этом случае блокировки с кнопок «Отправки сообщения» и «Отключения» соединения, а также компонента ввода сообщения разблокируются; кнопка «Подключения» блокируется. При закрытии канала связи все действия выполняются в обратном порядке. Скрыть метод изменения статуса function handleSendChannelStatusChange(event) { if (sendChannel) { if (sendChannel.readyState === "open") { boxInput .disabled = false; btnSend .disabled = false; btnDisconn.disabled = false; btnConnect.disabled = true; boxInput .focus(); } else { boxInput .disabled = true; btnSend .disabled = true; btnConnect.disabled = false; btnDisconn.disabled = true; } } } Отправка сообщенияПри нажатии на кнопку «Отправить сообщение» вызывается метод sendMessage, который сначала проверяет наличие захваченного изображения с помощью библиотеки jQuery. Если у элемента photo установлен класс 'picture', значит захват изображения произошел. Отправка сообщения/изображения выполняется вызовом метода sendChannel.send(data), где sendChannel имеет тип RTCDataChannel. При отправке сообщения до окончательного установления соединения, либо при закрытии канала данные буферизируются, если это возможно, в противном случае может выскочить ошибка. Тип отправляемых данных data может быть USVString (Unicode scalar values), Blob, ArrayBuffer или ArrayBufferView (Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array или DataView). После отправки изображения выполняется проверка текстового сообщения. Если сообщение имеет не нулевой размер, то на втором шаге отправляется текстовое сообщение. Скрыть метод отправки сообщения // Отправка сообщения удаленному узлу function sendMessage() { // отправка изображения var photo = document.getElementById('photo'); if ((photo!=null) && ($('#photo').hasClass('picture'))) sendChannel.send(photo.src); // отправка текстового сообщения var message = boxInput.value; if (message.length > 0) sendChannel.send(message); // очистка поля ввода сообщения boxInput.value = ""; boxInput.focus(); } |