diff options
Diffstat (limited to 'files/ru/web/api/webrtc_api')
-rw-r--r-- | files/ru/web/api/webrtc_api/adapter.js/index.html | 42 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/index.html | 195 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/session_lifetime/index.html | 90 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/signaling_and_video_calling/index.html | 665 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/simple_rtcdatachannel_sample/index.html | 272 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/taking_still_photos/index.html | 222 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/using_data_channels/index.html | 94 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/webrtc_basics/index.html | 350 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/протоколы/index.html | 38 | ||||
-rw-r--r-- | files/ru/web/api/webrtc_api/связь/index.html | 70 |
10 files changed, 2038 insertions, 0 deletions
diff --git a/files/ru/web/api/webrtc_api/adapter.js/index.html b/files/ru/web/api/webrtc_api/adapter.js/index.html new file mode 100644 index 0000000000..97e09d25e2 --- /dev/null +++ b/files/ru/web/api/webrtc_api/adapter.js/index.html @@ -0,0 +1,42 @@ +--- +title: Увеличиваем совместимость с WebRTC adapter.js +slug: Web/API/WebRTC_API/adapter.js +tags: + - adapter.js +translation_of: Web/API/WebRTC_API/adapter.js +--- +<p>{{WebRTCSidebar}}</p> + +<p>Несмотря на то, что WebRTC <a href="http://www.w3.org/TR/webrtc/">спецификация</a> относительно стабильна, не все еще браузеры полностью реализуют её функциональность. Некоторые реализации в браузерах все еще содержат префексы производителей в некоторых, или даже всех WebRTC интерфейсах, и разработчик может самостоятельно, в ручную, учесть вопросы несовместимости в своем коде. Но есть более простой выход. Организация <span class="seoSummary">WebRTC</span> <span class="seoSummary"><a href="https://github.com/webrtc/adapter/">предлагает библиотеку adapter.js</a> для обработки вопросов несовместимостей в различных браузерных реализациях WebRTC. Эта библиотека является JavaScript клином, позволяющим писать код в соответствии со спецификацией, чтобы он работал во всех браузерах с различным уровнем поддержки WebRTC. С ней нет необходимости условно использовать префиксные интерфейсы или реализовывать обходные пути</span></p> + +<div class="note"> +<p><strong>Примечание :</strong> Поскольку функциональность и названия API-терминов в WebRTC и поддерживаемых браузерах постоянно изменяются, обычно рекомендуется использовать этот адаптер.</p> +</div> + +<p>Адаптер предоставляется по лицензии <a href="https://github.com/webrtc/adapter/blob/master/LICENSE.md">BSD-style license</a>.</p> + +<h2 id="Как_работает_adapter.js">Как работает adapter.js</h2> + +<p>Для каждой версии браузера, поддерживающего WebRTC, <code>adapter.js</code> реализует необходимые полизаполнители, устанавливает имена API без префиксов и применяет любые другие изменения, необходимые для того, чтобы браузер выполнял код, в сообтветствии со спецификацией WebRTC.</p> + +<p>Например, в версиях Firefox старше 38 адаптер добавляет свойство {{domxref ("RTCPeerConnection.urls")}}; Firefox изначально не поддерживает это свойство до Firefox 38, а в Chrome адаптер добавляет поддержку API {{jsxref ("Promise")}}, если он отсутствует. Это всего лишь пара примеров. Вот в кратце, какие корректировки производит библиотека.</p> + +<p>В настоящее время адаптер WebRTC поддерживает Mozilla Firefox, Google Chrome, Apple Safari и Microsoft Edge.</p> + +<h2 id="Использование_adapter.js">Использование adapter.js</h2> + +<p>Для того чтобы использовать <code>adapter.js</code>, вам нужно включить <code>adapter.js</code> в любую страницу, которая использует API WebRTC:</p> + +<ol> + <li>Скопируйте <a href="https://github.com/webrtc/adapter/tree/master/release">последнюю версию adapter.js</a> с GitHub.</li> + <li>Поместите копию в структурную директорию вашего сайта (к примеру, в корневую директорию скриптов).</li> + <li>Поместите элемент скрипта со ссылкой на библиотеку <code>adapter.js</code> в ваш проект: <code><script src="adapter.js"></script></code></li> + <li>При кодировании, используйте интерфейсы WebRTC как указано в спецификации (без всяких префиксов производителей) , будучи уверенным, что он будет работать во всех браузерах .</li> + <li>Помните, что даже присутствие хорошего клина, не означает отмену тестирования вашего кода на различных браузерах (а идеально, и в различных версиях каждого браузера).</li> +</ol> + +<h2 id="Смотри_так_же">Смотри так же</h2> + +<ul> + <li><a href="https://github.com/webrtc/adapter">Проект WebRTC adapter.js на GitHub</a></li> +</ul> diff --git a/files/ru/web/api/webrtc_api/index.html b/files/ru/web/api/webrtc_api/index.html new file mode 100644 index 0000000000..78971cd1df --- /dev/null +++ b/files/ru/web/api/webrtc_api/index.html @@ -0,0 +1,195 @@ +--- +title: WebRTC API +slug: Web/API/WebRTC_API +translation_of: Web/API/WebRTC_API +--- +<p>{{WebRTCSidebar}}</p> + +<p><span class="seoSummary"><strong>WebRTC</strong> (Web Real-Time Communications) - это технология, которая позволяет Web-приложениям и сайтам захватывать и выборочно передавать аудио и/или видео медиа-потоки, а также обмениваться произвольными данными между браузерами, без обязательного использования посредников. Набор стандартов, которые включает в себя технология WebRTC, позволяет обмениваться данными и проводить пиринговые телеконференции, без необходимости пользователю устанавливать плагины или любое другое стороннее программное обеспечение.</span></p> + +<p>WebRTC состоит из нескольких взаимосвязанных программных интерфейсов (API) и протоколов, которые работают вместе. Документация, которую вы здесь найдете, поможет вам понять основы WebRTC, как настроить и использовать соединение для передачи данных и медиа-потока, и многое другое.</p> + +<h2 id="Совместимость">Совместимость</h2> + +<p>Поскольку реализация WebRTC находится в процессе становления, и каждый браузер имеет <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs">различный уровень поддержки кодеков</a> и WebRTC функций, настоятельно рекомендуется использовать полифил-библиотеку <a href="https://github.com/webrtcHacks/adapter">Adapter.js</a> от Google до начала работы над вашим кодом.</p> + +<p>Adapter.js использует клинья и полифилы для гладкой стыковки различий в реализациях WebRTC среди контекстов, его поддерживающих. Adapter.js также обрабатывает префиксы производителей и иные различия именования свойств, облегчая процесс разработки на WebRTC с наиболее совместимым результатом. Библиотека также доступна как <a href="https://www.npmjs.com/package/webrtc-adapter">NPM пакет</a>.</p> + +<p>Для дальнейшего изучения библиотеки Adapter.js смотрите <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/adapter.js">Улучшаем совместимость при использовании WebRTC adapter.js</a>.</p> + +<h2 id="Понятия_и_использование_WebRTC">Понятия и использование WebRTC </h2> + +<p>WebRTC является многоцелевым и вместе с <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API">Media Capture and Streams API</a>, предоставляют мощные мультимедийные возможности для Web, включая поддержку аудио и видео конференций, обмен файлами, захват экрана, управление идентификацией и взаимодействие с устаревшими телефонными системами, включая поддержку передачи сигналов тонового набора {{Glossary("DTMF")}}. Соединения между узлами могут создаваться без использования специальных драйверов или плагинов, и часто без промежуточных сервисов.</p> + +<p>Соединение между двумя узлами представлено как объект интерфейса {{DOMxRef("RTCPeerConnection")}}. Как только соединение установлено и открыто, используя объект <code>RTCPeerConnection</code>, медиапотоки ({{DOMxRef("MediaStream")}}s) и/или каналы данных ({{DOMxRef("RTCDataChannel")}}s) могут быть добавлены в соединение.</p> + +<p>Медиа потоки могут состоять из любого количества треков (дорожек) медиаинформации. Эти треки, представлены объектами интерфейса {{DOMxRef("MediaStreamTrack")}} , и могут содержать один или несколько типов медиаданных, включая аудио, видео, текст (такие как субтитры или название глав). Большинство потоков состоят, как минимум, только из одного аудио трека (одной аудио дорожки), или видео дорожки, и могут быть отправлены и получены, как потоки (медиаданные в настоящим времени) или сохранены в файл.</p> + +<p>Так же, можно использовать соединение между двумя узлами для обмена произвольными данными, используя объект интерфейса {{DOMxRef("RTCDataChannel")}}, что может быть использовано для передачи служебной информации, биржевых данных , пакетов игровых статусов, передача файлов или закрытых каналов передачи данных.</p> + +<p><em><strong>more details and links to relevant guides and tutorials needed</strong></em></p> + +<h2 id="WebRTC_интерфейсы">WebRTC интерфейсы</h2> + +<p>По причине того, что WebRTC предоставляет интерфейсы, работающие совместно для выполнения различных задач, мы разделили их на категории. Смотрите алфавитный указатель боковой панели для быстрой навигации.</p> + +<h3 id="Настройка_соединения_и_управление">Настройка соединения и управление</h3> + +<p>Эти интерфейсы используются для настройки, открытия и управлением WebRTC соединениями. Они представляют одноуровневые медиа соединения, каналы данных, и интерфейсы, использующиеся при обмене информацией о возможностях каждого узла, для выбора наилучшей конфигурации при установки двустороннего мультимедийного соединения.</p> + +<dl> + <dt>{{domxref("RTCPeerConnection")}}</dt> + <dd>Представляет WebRTC соединение между локальным компьютером и удаленным узлом. Используется для обработки успешной передачи данных между двумя узлами.</dd> + <dt>{{domxref("RTCSessionDescription")}}</dt> + <dd>Представляет параметры сессии. Каждый <code>RTCSessionDescription </code>содержит описания <a href="/en-US/docs/Web/API/RTCSessionDescription/type">типа</a>, показывающего какую часть (предложение/ответ) процесса переговоров он описывает, и <a href="/en-US/docs/Glossary/SDP">SDP</a>-дескриптор сессии<code>.</code></dd> + <dt>{{domxref("RTCIceCandidate")}}</dt> + <dd>Представляет собой кандидата сервера установки интернет соединения (ICE) для установленовки соединения {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCIceTransport")}}</dt> + <dd>Представляет информацию о средстве подключения к Интернету (ICE).</dd> + <dt>{{domxref("RTCPeerConnectionIceEvent")}}</dt> + <dd>Представляет события, которые происходят в отношении кандидатов ICE, обычно {{domxref ("RTCPeerConnection")}}. Один тип передается данному объекту события: {{event ("icecandidate")}}.</dd> + <dt>{{domxref("RTCRtpSender")}}</dt> + <dd>Управляет кродированием и передачей данных через объект типа {{domxref("MediaStreamTrack")}} для объекта типа {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCRtpReceiver")}}</dt> + <dd>Управляет получением и декодированием данных через объект типа {{domxref("MediaStreamTrack")}} для объекта типа {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCTrackEvent")}}</dt> + <dd>Указывает на то, что новый входящий объект типа {{domxref("MediaStreamTrack")}} был создан и объект типа {{domxref("RTCRtpReceiver")}} был добавлен в объект {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCCertificate")}}</dt> + <dd>Представляет сертификат, который использует объект {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCDataChannel")}}</dt> + <dd>Представляет двунапрвленный канал данных между двумя узлами соединения.</dd> + <dt>{{domxref("RTCDataChannelEvent")}}</dt> + <dd>Представляет события, которые возникают при присоединении объекта типа {{domxref("RTCDataChannel")}} к объекту типа {{domxref("RTCPeerConnection")}}. Один тип передается этому событию {{event("datachannel")}}.</dd> + <dt>{{domxref("RTCDTMFSender")}}</dt> + <dd>Управляет кодированием и передачей двутональной мультичастотной (DTMF) сигнализацией для объекта типа {{domxref("RTCPeerConnection")}}.</dd> + <dt>{{domxref("RTCDTMFToneChangeEvent")}}</dt> + <dd>Указывает на входящее событие изменение тона двутоновой мультичастотной сигнализации (DTMF). Это событие не всплывает (если не указано иначе) и не является отменяемым (если не указано иначе).</dd> + <dt>{{domxref("RTCStatsReport")}}</dt> + <dd>Ассинхронно сообщает статус для переданного объекта типа {{domxref("MediaStreamTrack")}} .</dd> + <dt>{{domxref("RTCIdentityProviderRegistrar")}}</dt> + <dd>Регистрирует провайдер идентификации (idP).</dd> + <dt>{{domxref("RTCIdentityProvider")}}</dt> + <dd>Активирует возможность браузеру запросить создание или проверку обяъвления идентификации.</dd> + <dt>{{domxref("RTCIdentityAssertion")}}</dt> + <dd>Представляет идентификатор удаленного узла текущего соединения. Если узел еще не установлен и подтвержден, ссылка на интерфейс вернет <code>null</code>. После установки не изменяется.</dd> + <dt>{{domxref("RTCIdentityEvent")}}</dt> + <dd>Представляет объект события объявление идентификатора провайдером идентификации (idP). Событие объекта типа {{domxref("RTCPeerConnection")}}. Один тип передается этому событию {{event("identityresult")}}.</dd> + <dt>{{domxref("RTCIdentityErrorEvent")}}</dt> + <dd>Представляет объект события ошибки, связанной с провайдером идентификации (idP). Событие объекта типа {{domxref("RTCPeerConnection")}}. Два типа ошибки передаются этому событию : {{event("idpassertionerror")}} и {{event("idpvalidationerror")}}.</dd> +</dl> + +<h2 id="Руководства">Руководства</h2> + +<dl> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Architecture">Обзор архитектуры WebRTC</a></dt> + <dd>Под API, который применяют разработчики, чтобы создавать и использовать WebRTC, расположен набор сетевых протоколов и стандартов соединения. Этот обзор - витрина этих стандартов.</dd> + <dt><a href="https://developer.mozilla.org/ru/docs/Web/API/WebRTC_API/Session_lifetime">Жизнь WebRTC-сессии</a></dt> + <dd>WebRTC позволяет вам организовать соединение в режиме узел-узел для передачи произвольных данных, аудио-, видео-потоков или любую их комбинацию в браузере. В этой статье мы взглянем на жизнь WebRTC-сессии, начиная с установки соединения и пройдем весь путь до его завершения, когда оно больше не нужно.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Overview">Обзор WebRTC API</a></dt> + <dd>WebRTC состоит из нескольких взаимосвязанных программных интерфейсов (API) и протоколов, которые работают вместе, чтобы обеспечить поддержку обмена данными и медиа-потоками между двумя и более узлами. В этой статье представлен краткий обзор каждого из этих API и какую цель он преследует.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/WebRTC_basics">Основы WebRTC</a></dt> + <dd>Эта статья проведет вас через создание кросс-браузерного RTC-приложения. К концу этой статьи вы должны иметь работающий дата- и медиа-канал, работающий в режиме точка-точка.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Protocols">Протоколы WebRTC</a></dt> + <dd>В этой статье представлены протоколы, в дополнение к которым создан API WebRTC.</dd> +</dl> + +<dl> + <dt><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Using_data_channels">Использование каналов данных в WebRTC</a></dt> + <dd>Это руководство описывает как вы можете использовать соединение узел-узел и связанный {{domxref("RTCDataChannel")}} для обмена произвольными данными между двумя узлами.</dd> +</dl> + +<dl> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Connectivity">Взаимосвязи WebRTC</a></dt> + <dd>В этой статье описано то, как протоколы, связанные с WebRTC, взаимодействуют друг с другом для того, чтобы создать соединение и передавать данные и/или медиа-потоки между узлами.</dd> +</dl> + +<h2 id="Учебные_материалы">Учебные материалы</h2> + +<dl> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/adapter.js">Увеличение совместимости, используя WebRTC adapter.js</a></dt> + <dd>Организация WebRTC <a href="https://github.com/webrtc/adapter/">предлагает на GitHub библиотеку adapter.js</a> для решения вопросов совместимости WebRTC реализаций в различных браузерах. Эта библиотека является JavaScript клином, который позволяет писать код, согласно спецификации, так, что бы он просто взял, и заработал во всех браузерах с поддержкой WebRTC, не смотря на проблемы совместимости браузеров.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Taking_still_photos">Захват кадров с WebRTC</a></dt> + <dd>Статья описывает как использовать WebRTC для получения доступа к камере на компьютере или мобильном устройстве с поддержкой WebRTC, и захват кадров с его помощью.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample">Простой пример канала данных RTCDataChannel</a></dt> + <dd>Интерфейс {{domxref("RTCDataChannel")}} - это функциональность, которая позволяет открыть канал передачи данных между двумя узлами, по которому можно предавать произвольные данные. Эти API намеренно подобны <a href="/en-US/docs/Web/API/WebSocket_API">WebSocket API</a>, так, что бы в обоих могла использоваться единая модель программирования.</dd> + <dt><a href="/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling">Сигнализация и двухсторонние видео вызовы</a></dt> + <dd>Например, мы берем чат на веб сокете, который мы создали в другом примере, и добавляем в него способность создавать видео вызовы. Сервер чата расширяется функциональностью обработки WebRTC сигнализации.</dd> +</dl> + +<h2 id="Ресурсы_2"><a id="Ресурсы" name="Ресурсы">Ресурсы</a></h2> + +<h3 id="Протоколы">Протоколы</h3> + +<h4 id="WebRTC-_текущие_протоколы">WebRTC- текущие протоколы</h4> + +<ul> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-alpn/"><cite>Протокол согласования соединения для Web RTC</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-audio/"><cite>WebRTC Аудио кодек и требования к обработке</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-data-channel/"><cite>RTCWeb Каналы данных</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-data-protocol/"><cite>RTCWeb Протокол канала данных</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-rtp-usage/"><cite>Связь в реальном времени (WebRTC): Медиа транспорт и использование RTP</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-security-arch/"><cite>WebRTC Безопасная архитектура</cite></a></li> + <li><a href="http://datatracker.ietf.org/doc/draft-ietf-rtcweb-transports/"><cite>Транспорты для RTCWEB</cite></a></li> +</ul> + +<h4 id="Связанные_поддерживающие_протоколы">Связанные поддерживающие протоколы</h4> + +<ul> + <li><a href="https://tools.ietf.org/html/rfc5245">Установка интерактивной связи (ICE): Протокол обхода транслятора сетевых адресов (NAT) при доставки объектов Offer/Answer</a></li> + <li><a href="https://tools.ietf.org/html/rfc5389"><cite>Сети обхода NAT (STUN)</cite></a></li> + <li><a href="https://tools.ietf.org/html/rfc7064"><cite>Схема URI для протокола сетей обхода NAT (STUN)</cite></a></li> + <li><a href="https://tools.ietf.org/html/rfc7065"><cite>Traversal Using Relays around NAT (TURN) Uniform Resource Identifiers</cite></a></li> + <li><a href="https://tools.ietf.org/html/rfc3264"><cite>An Offer/Answer Model with Session Description Protocol (SDP)</cite></a></li> + <li><a href="https://datatracker.ietf.org/doc/draft-ietf-tram-turn-third-party-authz/"><cite>Session Traversal Utilities for NAT (STUN) Extension for Third Party Authorization</cite></a></li> +</ul> + +<h4 id="WebRTC_статистика"><cite>WebRTC статистика</cite></h4> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/API/WebRTC_Statistics_API">WebRTC Statistics API</a></li> +</ul> + +<h2 id="Спецификации">Спецификации</h2> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Спецификация</th> + <th scope="col">Статус</th> + <th scope="col">Комментарий</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{SpecName('WebRTC 1.0')}}</td> + <td>{{Spec2('WebRTC 1.0')}}</td> + <td>The initial definition of the API of WebRTC.</td> + </tr> + <tr> + <td>{{SpecName('Media Capture')}}</td> + <td>{{Spec2('Media Capture')}}</td> + <td>The initial definition of the object conveying the stream of media content.</td> + </tr> + <tr> + <td>{{SpecName('Media Capture DOM Elements')}}</td> + <td>{{Spec2('Media Capture DOM Elements')}}</td> + <td>The initial definition on how to obtain stream of content from DOM Elements</td> + </tr> + </tbody> +</table> + +<p>В дополнение к этим спецификациям, определяющим API, необходимый для использования WebRTC, имеется несколько протоколов, перечисленных в разделе <a href="#Ресурсы">ресурсы</a>.</p> + +<h2 class="Related_Topics" id="Смотрите_также">Смотрите также</h2> + +<ul> + <li>{{domxref("MediaDevices")}}</li> + <li>{{domxref("MediaStreamEvent")}}</li> + <li>{{domxref("MediaStreamConstraints")}}</li> + <li>{{domxref("MediaStreamTrack")}}</li> + <li>{{domxref("MessageEvent")}}</li> + <li>{{domxref("MediaStream")}}</li> + <li><a href="https://hacks.mozilla.org/2015/06/firefox-multistream-and-renegotiation-for-jitsi-videobridge/">Firefox multistream and renegotiation for Jitsi Videobridge</a></li> + <li><a href="https://hacks.mozilla.org/2015/04/peering-through-the-webrtc-fog-with-socketpeer/">Peering Through the WebRTC Fog with SocketPeer</a></li> + <li><a href="https://hacks.mozilla.org/2014/04/inside-the-party-bus-building-a-web-app-with-multiple-live-video-streams-interactive-graphics/">Inside the Party Bus: Building a Web App with Multiple Live Video Streams + Interactive Graphics</a></li> +</ul> diff --git a/files/ru/web/api/webrtc_api/session_lifetime/index.html b/files/ru/web/api/webrtc_api/session_lifetime/index.html new file mode 100644 index 0000000000..958fd99136 --- /dev/null +++ b/files/ru/web/api/webrtc_api/session_lifetime/index.html @@ -0,0 +1,90 @@ +--- +title: Жизнь WebRTC-сессии +slug: Web/API/WebRTC_API/Session_lifetime +translation_of: Web/API/WebRTC_API/Session_lifetime +--- +<p>{{WebRTCSidebar}}{{draft}}</p> + +<div class="summary"> +<dl> + <dd>WebRTC позволяет браузерным приложениям построить соединение в режиме узел-узел для передачи произвольных данных, аудио-, видео-потоков или любую их комбинацию. В этой статье мы увидим то, как живет WebRTC-сессия, начиная с установки соединения и пройдём через весь путь до его завершения, если соединение больше не нужно.</dd> +</dl> +</div> + +<p>Эта статья не вдается в детали фактически использованных API в установке и обработке WebRTC-соединения. Это просто обзор процесса вцелом с некоторой информацией о том, для чего нужен каждый шаг. Смотрите статью <a href="/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling">Signaling and video calling</a>, чтобы получить пример с пошаговым объяснением того, что делает код.</p> + +<div class="note"> +<p>Эта страница находится в стадии разработки, и некоторое из содержания будут перемещаться на другие страницы, как направляющий материал. </p> + +<p>Вы можете помочь перевести документацию для других разработчиков. Пожалуйста принесите пользу миру и помогите с качественным переводом этой документации.</p> +</div> + +<h2 id="Установка_соединения">Установка соединения</h2> + +<p>Интернет большой. Реально большой. Умные люди, несколько лет назад, заметив то, насколько он велик, каким большим он может стать и то как быстро растёт, а также ограничения 32-битной системы адресации протокола IP, и поняли, что нужно начать что-то делать, чтобы создать новую 64-битную систему адресации. Но в какой-то момент они так же пришли к выводу, что переход на новую систему займёт больше времени, чем продержатся 32-разрядные адреса. Затем другие умные люди придумали способ, позволяющий нескольким компьютерам использовать один и тот же 32-итный IP-адрес. Network Address Translation ({{Glossary("NAT")}}) - это стандарт, который поддерживает разделение адреса путем маршрутизации входящих и исходящих пакетов данных в и из локальной сети (LAN), которые разделяют единственный WAN (глобальный) адрес.</p> + +<p>Проблемой для пользователя является то, что каждый отдельный компьютер в сети Интернет не обязан иметь уникальный IP-адрес, и посути, IP-адрес устройства может измениться не только тогда, когда оно перемещяется из одной сети в другую, но и если их сетевой адрес был изменён {{Glossary("NAT")}} и/или {{interwiki("wikipedia", "DHCP")}}. Для разработчиков, пытающихся строить одноранговые сети, эта ситуация является хорошей головоломкой: без уникального идентификатора для каждого устройства, нет возможности моментально автоматически выяснить то, как подключиться к конкретному устройству в Интернет. Если вызнаете, с кем вы хотите поговорить, вам не обязательно знать, какой адрес у вашего собеседника.</p> + +<p>Это похоже на попытку отправить письмо подруге Мишель, написав только на конверте слово "Мишель" и опустить в почтовый ящик. Вам необходимо выяснить её адрес и указать его на конверте, иначе она сильно удивится, почему вы забыли про её день рождения.</p> + +<p>Всё это входит в процесс сигнализации.</p> + +<h3 id="Процесс_Сигнализации">Процесс Сигнализации</h3> + +<p>Сигнализация - это процесс передачи управляющей информации между двумя устройствами для опредения протоколов связи, каналов, кодирования и формата медиа-данных, методов передачи данных, а также информации, необходимой для маршрутизации. Наиболее важная вещь, о которой нужно знать о процессе сигнализации для WebRTC - <strong>этот процесс не определен в спецификации</strong>.</p> + +<p>Вы можете задаться вопросом, почему нечто основоположное для процесса установки WebRTC-соединения вынесено из спецификации? Ответ прост: потому как два устройства не могут контактировать друг с другом, и спецификация не может предусмотреть все возможные способы использования WebRTC, также это приобретает ещё больший смысл с точки зрения предоставления разработчику возможности выбора наиболее подходящей сетевой технологии и протоколов передачи сообщений.</p> + +<p>Для обмена сигнальной информацией, вы можете выбрать отправку JSON-объектов через WebSocket-соединение, можете использовать протокол XMPP/SIP через соответствующий канал, так же можете использовать {{domxref("XMLHttpRequest")}} через {{Glossary("HTTPS")}} с техникой пуллинга ({{Glossary("HTTPS")}} with polling), или же другие комбинации технологий, которые вам могут прийти в голову. Вы даже можете использовать электронную почту в качестве сигнального канала.</p> + +<p>Стоит также отметить, что сигнальный канал может вообще находиться вне компьютерной сети. Один узел может выпустить объект данных, который затем может быть распечатан на принтере, физически перемещается (пешком или голубиной почтой) до другого устройства, данные вводятся в устройство, ответ устройства затем возвращается обратно, так же пешком, и так далее, пока WebRTC-соединение между узлами открыто. В этом случае, будет очень высокая латентность, но этот сценарий возможен.</p> + +<h4 id="Обмен_информации_во_время_процесса_сигнализации">Обмен информации во время процесса сигнализации</h4> + +<p>Существует три основных типа информации, которой нужно обмениваться во время процесса сигнализации:</p> + +<ul> + <li>Управляющие сообщения, используемые для настройки, открытия и закрытия каналов коммуникации, а также для обработки ошибок</li> + <li>Информация, необходимая для того, чтобы настроить соединение: информация об IP-адресе и порте необходима узлам, чтобы они могли разговаривать друг с другом.</li> + <li>Необходимо согласовать медиа-потоки: какие могут использоваться между узлами кодеки и форматы медиа-данных? Все это необходимо согласовать дотого, как будет установлена WebRTC-сессия.</li> +</ul> + +<p>Только после успешного завершения процесса сигнализации, может быть возможен процесс открытия WebRTC-соединения между узлами.</p> + +<p>Стоит также отметить, что сигнальному серверу не нужно понимать данные, которыми через него обмениваются между собой два узла, или что-нибудь с ними делать. Сигнальный сервер, по существу, является ретранслятором - общей точкой, которую знают обе стороны могут к ней подключиться чтобы передавать данные через неё. Сервер не должен реагировать на передаваемую информацию ни коим образом.</p> + +<h4 id="Процесс_сигнализации">Процесс сигнализации</h4> + +<p>Существует последовательность действий, которую нужно выполнить, чтобы стало возможным начало WebRTC-сессии:</p> + +<ol> + <li>Каждый узел создает объект {{domxref("RTCPeerConnection")}}, представляющий собой WebRTC-сессию и сохраняющийся до её завершения.</li> + <li>Каждый узел устанавливает обработчик события {{event("icecandidate")}},которая занимается отправкой этих кандидатов в другую сторону по каналу сигнализации.</li> + <li>Каждый узел устанавливает обработчик события {{event("addstream")}}, которое срабатывает когда начинает приходить поток данных от удаленного узла. Этот обработчик должен подключить этот поток к потребителю, например к элементу {{HTMLElement("video")}}.</li> + <li>Вызывающий узел создает уникальный идентификатор, токен или нечто, что сможет идентифицировать вызов на сигнальном сервере, и обмениваться с принимающим узлом. Форма и содержимое идентификатора остается на усмотрение разработчика.</li> + <li>Каждый узел подключается к согласованному сигнальному серверу, такому например как известный обоим WebSocket-сервер, для обмена сообщениями.</li> + <li>Каждый узел сообщает сигнальному серверу, что хочет подключиться к одной и той же WebRTC-сессии (идентифицируемой токеном, определенным на шаге 4)</li> + <li><strong><em>descriptions, candidates, etc. -- more coming up</em></strong></li> +</ol> + +<h2 id="Перезапуск_сессии_ICE_агент"><strong>Перезапуск сессии ICE агент</strong></h2> + +<p>Иногда, во время срока службы WebRTC сессии, сетевые условия изменяются. Один из пользователей, возможно, перейдет от сотовой сети к сети WiFi или сеть может стать перегруженной. Например: когда это произойдет, ICE агент может перезапустить сессию. Это процесс, с помощью которого сетевое соединение перезапустится и восстановится, точно таким же образом выполняется начальная установка сессии, за одним исключением того пока не установится новая сессия. Тогда сессия сменяется и переходит к новому сетевому соединению, а старое соединение закрывается.</p> + +<div class="note"> +<p>Различные браузеры поддерживают перезапуск сессии при разных условиях. Не все браузеры будут выполнять перезапуск сессии из-за перегрузки сети, например:</p> +</div> + +<p>Есть два уровня перезапуска сессии: полная перезагрузка сессии вызывает все мультимедийные потоки в сеансе и должны быть пересмотрены. Частичная перезагрузка сессии позволяет агенту сессии перезапустить конкретный медиапоток вместо того, чтобы перезапускать все медиаданные. Некоторые браузеры пока не поддерживают частичную перезагрузку сессии, однако. <<< Все зависит от вашего кодерства... >>></p> + +<p>Если вам необходимо изменить конфигурацию соединения каким-либо образом (например, изменение к другому набору связи), вы можете сделать это перед <code><a href="https://developer.mozilla.org/ru/docs/Web/API/RTCPeerConnection/setConfiguration" title="Документация об этом ещё не написана; пожалуйста, поспособствуйте её написанию!">RTCPeerConnection.setConfiguration()</a>(перед назначением конфигурации)</code> с обновленной <code><a href="https://developer.mozilla.org/ru/docs/Web/API/RTCConfiguration" title="Документация об этом ещё не написана; пожалуйста, поспособствуйте её написанию!">RTCConfiguration</a>(конфигурацией)</code> перед повторным запуском движка.</p> + +<p>Чтобы явно вызвать перезапуск сессии, нужно начать переговорный процесс с помощью вызова <code><a href="https://developer.mozilla.org/ru/docs/Web/API/RTCPeerConnection/createOffer" title="Документация об этом ещё не написана; пожалуйста, поспособствуйте её написанию!">RTCPeerConnection.createOffer()</a>,</code> указав параметр iceRestart(перезапуск сессии) со значением истины(true). Затем обработать процесс соединения так, как вы это обычно делаете.</p> + +<h2 id="Transmission">Transmission</h2> + +<h3 id="getUserMedia">getUserMedia</h3> + +<p>LocalMediaStream object</p> + +<h2 id="Reception">Reception</h2> diff --git a/files/ru/web/api/webrtc_api/signaling_and_video_calling/index.html b/files/ru/web/api/webrtc_api/signaling_and_video_calling/index.html new file mode 100644 index 0000000000..4c4f7ea418 --- /dev/null +++ b/files/ru/web/api/webrtc_api/signaling_and_video_calling/index.html @@ -0,0 +1,665 @@ +--- +title: Сигнализирование и видео вызов +slug: Web/API/WebRTC_API/Signaling_and_video_calling +translation_of: Web/API/WebRTC_API/Signaling_and_video_calling +--- +<div>{{WebRTCSidebar}}</div> + +<p><span class="seoSummary"><a href="/en-US/docs/Web/API/WebRTC_API">WebRTC</a> позволяет обмениваться медиаданными между двумя устройствами напрямую (peer-to-peer) в режиме реального времени. Соединение устанавливается путем обнаружения и согласования, называемым <strong>сигнализацией (signaling)</strong>. Эта статья объясняет, как сделать двусторонний видеозвонок.</span></p> + +<p><a href="/en-US/docs/Web/API/WebRTC_API">WebRTC</a> это технология прямого обмена аудио-, видео- и другими данными в режиме реального времени с одним ключевым условием. Процесс обнаружения и согласования медиаформатов должен происходить так чтобы два устройства, подключенные к разным сетям, могли локализовать друг друга, <a href="/en-US/docs/Web/API/WebRTC_API/Session_lifetime#Establishing_a_connection">как обсуждалось здесь</a>. Этот процесс назван <span class="seoSummary"><strong>сигнализацией </strong></span>и подразумевает, что оба устройства подключаются к третьему, обоюдно согласованному серверу. Через третью сторону устройства определяют адреса друг друга и обмениваются согласующими сообщениями.</p> + +<p>В этой статье мы будем дорабатывать <a class="external external-icon" href="https://webrtc-from-chat.glitch.me/" rel="noopener">WebSocket-чат</a>, созданный для нашей документации к WebSocket, — добавим к нему двусторонний видеозвонок между двумя пользователями. Вы можете <a href="https://webrtc-from-chat.glitch.me/">использовать этот пример на Glitch</a> или <a href="https://glitch.com/edit/#!/remix/webrtc-from-chat">клонировать его</a>, чтобы поэкспериментировать самим. <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-from-chat">Весь проект</a> можно посмотреть на GitHub.</p> + +<div class="note"> +<p><strong>Note:</strong> If you try out the example on Glitch, please note that any changes made to the code will immediately reset any connections. In addition, there is a short timeout period; the Glitch instance is for quick experiments and testing only.</p> +</div> + +<h2 id="Сервер_сигнализации">Сервер сигнализации</h2> + +<p>Для установление WebRTC-соединения между двумя устройствами необходим <strong>сервер сигнализации</strong>, чтобы определить, как соединять эти устройства через Интернет. Сервер сигнализации выступает посредником между пирами, позволяя им найти адреса друг друга и установить соединение, и предельно минимизирует риск утечки информации, которая может оказаться личной. Как создать такой сервер и как устроен процесс сигнализации?</p> + +<p>Во-первых, нужен сам сервер сигнализации. Спецификация WebRTC не определяет, какой транспорт используется для передачи сигнальной информации. Можете использовать какой вам нравится, от <a href="/en-US/docs/Web/API/WebSocket_API">WebSocket</a> до {{domxref("XMLHttpRequest")}} и почтовых голубей, чтобы передать сигнальную информацию между пирами.</p> + +<p>Важно, что серверу не нужно понимать или интерпретировать сигнальные данные. Хотя они в формате {{Glossary("SDP")}}, это не имеет особого значения: содержание сообщений, проходящих через сигнальный сервер - по сути, черный ящик. Значение имеет лишь то, что когда подсистема {{Glossary("ICE")}} дает команду передать данные другому пиру, вы просто это делаете, а уже пир знает, как получить эту информацию и доставить ее на свою подсистему ICE. Все что нужно - передавать сообщения туда и обратно. Содержание совершенно не важно для сигнального сервера.</p> + +<h3 id="Подготовка_сервера_чата_к_сигнализиции">Подготовка сервера чата к сигнализиции</h3> + +<p>Наш <a href="https://github.com/mdn/samples-server/tree/master/s/websocket-chat">сервер чата</a> использует <a href="/en-US/docs/Web/API/WebSocket_API">WebSocket API</a> для отправки информации как {{Glossary("JSON")}} между каждым клиентом и сервером. Сервер поддерживает несколько типов сообщений для нескольких задач : регистрация нового пользователя, установки имен пользователей, отправка сообщений чата.</p> + +<p>Для того, что бы сервер мог поддерживать функциональность сигнализации и согласование соединения, нам нужно обновить код. Нам нужно направлять сообщения одному конкретному пользователю вместо того, чтобы транслировать их всем подключенным пользователям, а также обеспечить передачу и доставку неизвестных типов сообщений, при этом серверу не нужно будет знать, что это такое. Это позволит нам посылать сигнальные сообщения, используя один и тот же сервер, вместо того, чтобы использовать отдельный сервер.</p> + +<p>Let's take a look which changes we need to make to the chat server support WebRTC signaling. This is in the file <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-from-chat/chatserver.js">chatserver.js</a>.</p> + +<p>First up is the addition of the function <code>sendToOneUser()</code>. As the name suggests, this sends a stringified JSON message to a particular username.</p> + +<pre class="brush: js notranslate">function sendToOneUser(target, msgString) { + var isUnique = true; + var i; + + for (i=0; i<connectionArray.length; i++) { + if (connectionArray[i].username === target) { + connectionArray[i].send(msgString); + break; + } + } +}</pre> + +<p>This function iterates over the list of connected users until it finds one matching the specified username, then sends the message to that user. The parameter <code>msgString</code> is a stringified JSON object. We could have made it receive our original message object, but in this example it's more efficient this way. Since the message has already been stringified, we can send it with no further processing. Each entry in <code>connectionArray</code> is a {{domxref("WebSocket")}} object, so we can just call its {{domxref("WebSocket.send", "send()")}} method directly.</p> + +<p>Our original chat demo didn't support sending messages to a specific user. The next task is to update the main WebSocket message handler to support doing so. This involves a change near the end of the <code>"connection"</code> message handler:</p> + +<pre class="brush: js notranslate">if (sendToClients) { + var msgString = JSON.stringify(msg); + var i; + + if (msg.target && msg.target !== undefined && msg.target.length !== 0) { + sendToOneUser(msg.target, msgString); + } else { + for (i=0; i<connectionArray.length; i++) { + connectionArray[i].send(msgString); + } + } +}</pre> + +<p>This code now looks at the pending message to see if it has a <code>target</code> property. If that property is present, it specifies the username of the client to which the message is to be sent, and we call <code>sendToOneUser()</code> to send the message to them. Otherwise, the message is broadcast to all users by iterating over the connection list, sending the message to each user.</p> + +<p>As the existing code allows the sending of arbitrary message types, no additional changes are required. Our clients can now send messages of unknown types to any specific user, letting them send signaling messages back and forth as desired.</p> + +<p>That's all we need to change on the server side of the equation. Now let's consider the signaling protocol we will implement.</p> + +<h3 id="Designing_the_signaling_protocol">Designing the signaling protocol</h3> + +<p>Now that we've built a mechanism for exchanging messages, we need a protocol defining how those messages will look. This can be done in a number of ways; what's demonstrated here is just one possible way to structure signaling messages.</p> + +<p>This example's server uses stringified JSON objects to communicate with its clients. This means our signaling messages will be in JSON format, with contents which specify what kind of messages they are as well as any additional information needed in order to handle the messages properly.</p> + +<h4 id="Exchanging_session_descriptions">Exchanging session descriptions</h4> + +<p>When starting the signaling process, an <strong>offer</strong> is created by the user initiating the call. This offer includes a session description, in {{Glossary("SDP")}} format, and needs to be delivered to the receiving user, which we'll call the <strong>callee</strong>. The callee responds to the offer with an <strong>answer</strong> message, also containing an SDP description. Our signaling server will use WebSocket to transmit offer messages with the type <code>"video-offer"</code>, and answer messages with the type <code>"video-answer"</code>. These messages have the following fields:</p> + +<dl> + <dt><code>type</code></dt> + <dd>The message type; either <code>"video-offer"</code> or <code>"video-answer"</code>.</dd> + <dt><code>name</code></dt> + <dd>The sender's username.</dd> + <dt><code>target</code></dt> + <dd>The username of the person to receive the description (if the caller is sending the message, this specifies the callee, and vice-versa).</dd> + <dt><code>sdp</code></dt> + <dd>The SDP (Session Description Protocol) string describing the local end of the connection from the perspective of the sender (or the remote end of the connection from the receiver's point of view).</dd> +</dl> + +<p>At this point, the two participants know which codecs and video parameters are to be used for this call. They still don't know how to transmit the media data itself though. This is where {{Glossary('ICE', 'Interactive Connectivity Establishment (ICE)')}} comes in.</p> + +<h3 id="Exchanging_ICE_candidates">Exchanging ICE candidates</h3> + +<p>Two peers need to exchange ICE candidates to negotiate the actual connection between them. Every ICE candidate describes a method that the sending peer is able to use to communicate. Each peer sends candidates in the order they're discovered, and keeps sending candidates until it runs out of suggestions, even if media has already started streaming.</p> + +<p>An <code>icecandidate</code> event is sent to the {{domxref("RTCPeerConnection")}} to complete the process of adding a local description using <code>pc.setLocalDescription(offer)</code>.</p> + +<p>Once the two peers agree upon a mutually-compatible candidate, that candidate's SDP is used by each peer to construct and open a connection, through which media then begins to flow. If they later agree on a better (usually higher-performance) candidate, the stream may change formats as needed.</p> + +<p>Though not currently supported, a candidate received after media is already flowing could theoretically also be used to downgrade to a lower-bandwidth connection if needed.</p> + +<p>Each ICE candidate is sent to the other peer by sending a JSON message of type <code>"new-ice-candidate"</code> over the signaling server to the remote peer. Each candidate message include these fields:</p> + +<dl> + <dt><code>type</code></dt> + <dd>The message type: <code>"new-ice-candidate"</code>.</dd> + <dt><code>target</code></dt> + <dd>The username of the person with whom negotiation is underway; the server will direct the message to this user only.</dd> + <dt><code>candidate</code></dt> + <dd>The SDP candidate string, describing the proposed connection method. You typically don't need to look at the contents of this string. All your code needs to do is route it through to the remote peer using the signaling server.</dd> +</dl> + +<p>Each ICE message suggests a communication protocol (TCP or UDP), IP address, port number, connection type (for example, whether the specified IP is the peer itself or a relay server), along with other information needed to link the two computers together. This includes NAT or other networking complexity.</p> + +<div class="note"> +<p><strong>Note:</strong> The important thing to note is this: the only thing your code is responsible for during ICE negotiation is accepting outgoing candidates from the ICE layer and sending them across the signaling connection to the other peer when your {{domxref("RTCPeerConnection.onicecandidate", "onicecandidate")}} handler is executed, and receiving ICE candidate messages from the signaling server (when the <code>"new-ice-candidate"</code> message is received) and delivering them to your ICE layer by calling {{domxref("RTCPeerConnection.addIceCandidate()")}}. That's it.</p> + +<p>The contents of the SDP are irrelevant to you in essentially all cases. Avoid the temptation to try to make it more complicated than that until you really know what you're doing. That way lies madness.</p> +</div> + +<p>All your signaling server now needs to do is send the messages it's asked to. Your workflow may also demand login/authentication functionality, but such details will vary.</p> + +<h3 id="Signaling_transaction_flow">Signaling transaction flow</h3> + +<p>The signaling process involves this exchange of messages between two peers using an intermediary, the signaling server. The exact process will vary, of course, but in general there are a few key points at which signaling messages get handled:</p> + +<p>The signaling process involves this exchange of messages among a number of points:</p> + +<ul> + <li>Each user's client running within a web browser</li> + <li>Each user's web browser</li> + <li>The signaling server</li> + <li>The web server hosting the chat service</li> +</ul> + +<p>Imagine that Naomi and Priya are engaged in a discussion using the chat software, and Naomi decides to open a video call between the two. Here's the expected sequence of events:</p> + +<p><a href="https://mdn.mozillademos.org/files/12363/WebRTC%20-%20Signaling%20Diagram.svg"><img alt="Diagram of the signaling process" src="https://mdn.mozillademos.org/files/16137/WebRTC_-_Signaling_Diagram.svg" style="height: 1117px; width: 901px;"></a></p> + +<p>We'll see this detailed more over the course of this article.</p> + +<h3 id="ICE_candidate_exchange_process">ICE candidate exchange process</h3> + +<p>When each peer's ICE layer begins to send candidates, it enters into an exchange among the various points in the chain that looks like this:</p> + +<p><a href="https://mdn.mozillademos.org/files/12365/WebRTC%20-%20ICE%20Candidate%20Exchange.svg"><img alt="Diagram of ICE candidate exchange process" src="https://mdn.mozillademos.org/files/12365/WebRTC%20-%20ICE%20Candidate%20Exchange.svg" style="height: 590px; width: 700px;"></a></p> + +<p>Each side sends candidates to the other as it receives them from their local ICE layer; there is no taking turns or batching of candidates. As soon as the two peers agree upon one candidate that they can both use to exchange the media, media begins to flow. Each peer continues to send candidates until it runs out of options, even after the media has already begun to flow. This is done in hopes of identifying even better options than the one initially selected.</p> + +<p>If conditions change—for example the network connection deteriorates—one or both peers might suggest switching to a lower-bandwidth media resolution, or to an alternative codec. This triggers a new exchange of candidates, after which a another media format and/or codec change may take place.</p> + +<p>Optionally, see {{RFC(8445, "Interactive Connectivity Establishment")}}, <a href="https://tools.ietf.org/html/rfc5245#section-2.3">section 2.3 ("Negotiating Candidate Pairs and Concluding ICE")</a> if you want greater understanding of this process is completed inside the ICE layer. You should note that candidates are exchanged and media starts to flow as soon as the ICE layer is satisfied. This all taken care of behind the scenes. Our role is to simply send the candidates, back and forth, through the signaling server.</p> + +<h2 id="The_client_application">The client application</h2> + +<p>The core to any signaling process is its message handling. It's not necessary to use WebSockets for signaling, but it is a common solution. You should, of course, select a mechanism for exchanging signaling information that is appropriate for your application.</p> + +<p>Let's update the chat client to support video calling.</p> + +<h3 id="Updating_the_HTML">Updating the HTML</h3> + +<p>The HTML for our client needs a location for video to be presented. This requires video elements, and a button to hang up the call:</p> + +<pre class="brush: html notranslate"><div class="flexChild" id="camera-container"> + <div class="camera-box"> + <video id="received_video" autoplay></video> + <video id="local_video" autoplay muted></video> + <button id="hangup-button" onclick="hangUpCall();" disabled> + Hang Up + </button> + </div> +</div></pre> + +<p>The page structure defined here is using {{HTMLElement("div")}} elements, giving us full control over the page layout by enabling the use of CSS. We'll skip layout detail in this guide, but <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-from-chat/chat.css">take a look at the CSS</a> on Github to see how we handled it. Take note of the two {{HTMLElement("video")}} elements, one for your self-view, one for the connection, and the {{HTMLElement("button")}} element.</p> + +<p>The <code><video></code> element with the <code>id</code> "<code>received_video</code>" will present video received from the connected user. We specify the <code>autoplay</code> attribute, ensuring once the video starts arriving, it immediately plays. This removes any need to explicitly handle playback in our code. The "<code>local_video</code>" <code><video></code> element presents a preview of the user's camera; specifiying the <code>muted</code> attribute, as we don't need to hear local audio in this preview panel.</p> + +<p>Finally, the "<code>hangup-button</code>" {{HTMLElement("button")}}, to disconnect from a call, is defined and configured to start disabled (setting this as our default for when no call is connected) and apply the function <code>hangUpCall()</code> on click. This function's role is to close the call, and send a signalling server notification to the other peer, requesting it also close.</p> + +<h3 id="The_JavaScript_code">The JavaScript code</h3> + +<p>We'll divide this code into functional areas to more easily describe how it works. The main body of this code is found in the <code>connect()</code> function: it opens up a {{domxref("WebSocket")}} server on port 6503, and establishes a handler to receive messages in JSON object format. This code generally handles text chat messages as it did previously.</p> + +<h4 id="Sending_messages_to_the_signaling_server">Sending messages to the signaling server</h4> + +<p>Throughout our code, we call <code>sendToServer()</code> in order to send messages to the signaling server. This function uses the <a href="/en-US/docs/Web/API/WebSockets_API">WebSocket</a> connection to do its work:</p> + +<pre class="brush: js notranslate">function sendToServer(msg) { + var msgJSON = JSON.stringify(msg); + + connection.send(msgJSON); +}</pre> + +<p>The message object passed into this function is converted into a JSON string by calling {{jsxref("JSON.stringify()")}}, then we call the WebSocket connection's {{domxref("WebSocket.send", "send()")}} function to transmit the message to the server.</p> + +<h4 id="UI_to_start_a_call">UI to start a call</h4> + +<p>The code which handles the <code>"userlist"</code> message calls <code>handleUserlistMsg()</code>. Here we set up the handler for each connected user in the user list displayed to the left of the chat panel. This function receives a message object whose <code>users</code> property is an array of strings specifying the user names of every connected user.</p> + +<pre class="brush: js notranslate">function handleUserlistMsg(msg) { + var i; + var listElem = document.querySelector(".userlistbox"); + + while (listElem.firstChild) { + listElem.removeChild(listElem.firstChild); + } + + msg.users.forEach(function(username) { + var item = document.createElement("li"); + item.appendChild(document.createTextNode(username)); + item.addEventListener("click", invite, false); + + listElem.appendChild(item); + }); +}</pre> + +<p>After getting a reference to the {{HTMLElement("ul")}} which contains the list of user names into the variable <code>listElem</code>, we empty the list by removing each of its child elements.</p> + +<div class="note"> +<p><strong>Note:</strong> Obviously, it would be more efficient to update the list by adding and removing individual users instead of rebuilding the whole list every time it changes, but this is good enough for the purposes of this example.</p> +</div> + +<p>Then we iterate over the array of user names using {{jsxref("Array.forEach", "forEach()")}}. For each name, we create a new {{HTMLElement("li")}} element, then create a new text node containing the user name using {{domxref("Document.createTextNode", "createTextNode()")}}. That text node is added as a child of the <code><li></code> element. Next, we set a handler for the {{event("click")}} event on the list item, that clicking on a user name calls our <code>invite()</code> method, which we'll look at in the next section.</p> + +<p>Finally, we append the new item to the <code><ul></code> that contains all of the user names.</p> + +<h4 id="Starting_a_call">Starting a call</h4> + +<p>When the user clicks on a username they want to call, the <code>invite()</code> function is invoked as the event handler for that {{event("click")}} event:</p> + +<pre class="brush: js notranslate">var mediaConstraints = { + audio: true, // We want an audio track + video: true // ...and we want a video track +}; + +function invite(evt) { + if (myPeerConnection) { + alert("You can't start a call because you already have one open!"); + } else { + var clickedUsername = evt.target.textContent; + + if (clickedUsername === myUsername) { + alert("I'm afraid I can't let you talk to yourself. That would be weird."); + return; + } + + targetUsername = clickedUsername; + createPeerConnection(); + + navigator.mediaDevices.getUserMedia(mediaConstraints) + .then(function(localStream) { + document.getElementById("local_video").srcObject = localStream; + localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream)); + }) + .catch(handleGetUserMediaError); + } +}</pre> + +<p>This begins with a basic sanity check: is the user even connected? If there's no {{domxref("RTCPeerConnection")}}, they obviously can't make a call. Then the name of the user that was clicked upon is obtained from the event target's {{domxref("Node.textContent", "textContent")}} property, and we check to be sure that it's not the same user that's trying to start the call.</p> + +<p>Then we copy the name of the user we're calling into the variable <code>targetUsername</code> and call <code>createPeerConnection()</code>, a function which will create and do basic configuration of the {{domxref("RTCPeerConnection")}}.</p> + +<p>Once the <code>RTCPeerConnection</code> has been created, we request access to the user's camera and microphone by calling {{domxref("MediaDevices.getUserMedia()")}}, which is exposed to us through the {{domxref("Navigator.mediaDevices.getUserMedia")}} property. When this succeeds, fulfilling the returned promise, our <code>then</code> handler is executed. It receives, as input, a {{domxref("MediaStream")}} object representing the stream with audio from the user's microphone and video from their webcam.</p> + +<div class="note"> +<p><strong>Note:</strong> We could restrict the set of permitted media inputs to a specific device or set of devices by calling {{domxref("MediaDevices.enumerateDevices", "navigator.mediaDevices.enumerateDevices()")}} to get a list of devices, filtering the resulting list based on our desired criteria, then using the selected devices' {{domxref("MediaTrackConstraints.deviceId", "deviceId")}} values in the <code>deviceId</code> field of the the <code>mediaConstraints</code> object passed into <code>getUserMedia()</code>. In practice, this is rarely if ever necessary, since most of that work is done for you by <code>getUserMedia()</code>.</p> +</div> + +<p>We attach the incoming stream to the local preview {{HTMLElement("video")}} element by setting the element's {{domxref("HTMLMediaElement.srcObject", "srcObject")}} property. Since the element is configured to automatically play incoming video, the stream begins playing in our local preview box.</p> + +<p>We then iterate over the tracks in the stream, calling {{domxref("RTCPeerConnection.addTrack", "addTrack()")}} to add each track to the <code>RTCPeerConnection</code>. Even though the connection is not fully established yet, it's important to begin sending media to it as soon as possible, because the media will help the ICE layer decide on the best connectivity approach to take, aiding in the negotiation process.</p> + +<p>As soon as media is attached to the <code>RTCPeerConnection</code>, a {{event("negotiationneeded")}} event is triggered at the connection, so that ICE negotiation can be started.</p> + +<p>If an error occurs while trying to get the local media stream, our catch clause calls <code>handleGetUserMediaError()</code>, which displays an appropriate error to the user as required.</p> + +<h4 id="Handling_getUserMedia_errors">Handling getUserMedia() errors</h4> + +<p>If the promise returned by <code>getUserMedia()</code> concludes in a failure, our <code>handleGetUserMediaError()</code> function performs.</p> + +<pre class="brush: js notranslate">function handleGetUserMediaError(e) { + switch(e.name) { + case "NotFoundError": + alert("Unable to open your call because no camera and/or microphone" + + "were found."); + break; + case "SecurityError": + case "PermissionDeniedError": + // Do nothing; this is the same as the user canceling the call. + break; + default: + alert("Error opening your camera and/or microphone: " + e.message); + break; + } + + closeVideoCall(); +}</pre> + +<p>An error message is displayed in all cases but one. In this example, we ignore <code>"SecurityError"</code> and <code>"PermissionDeniedError"</code> results, treating refusal to grant permission to use the media hardware the same as the user canceling the call.</p> + +<p>Regardless of why an attempt to get the stream fails, we call our <code>closeVideoCall()</code> function to shut down the {{domxref("RTCPeerConnection")}}, and release any resources already allocated by the process of attempting the call. This code is designed to safely handle partially-started calls.</p> + +<h4 id="Creating_the_peer_connection">Creating the peer connection</h4> + +<p>The <code>createPeerConnection()</code> function is used by both the caller and the callee to construct their {{domxref("RTCPeerConnection")}} objects, their respective ends of the WebRTC connection. It's invoked by <code>invite()</code> when the caller tries to start a call, and by <code>handleVideoOfferMsg()</code> when the callee receives an offer message from the caller.</p> + +<pre class="brush: js notranslate">function createPeerConnection() { + myPeerConnection = new RTCPeerConnection({ + iceServers: [ // Information about ICE servers - Use your own! + { + urls: "stun:stun.stunprotocol.org" + } + ] + }); + + myPeerConnection.onicecandidate = handleICECandidateEvent; + myPeerConnection.ontrack = handleTrackEvent; + myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent; + myPeerConnection.onremovetrack = handleRemoveTrackEvent; + myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent; + myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent; + myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent; +} +</pre> + +<p>When using the {{domxref("RTCPeerConnection.RTCPeerConnection", "RTCPeerConnection()")}} constructor, we will specify an {{domxref("RTCConfiguration")}}-compliant object providing configuration parameters for the connection. We use only one of these in this example: <code>iceServers</code>. This is an array of objects describing STUN and/or TURN servers for the {{Glossary("ICE")}} layer to use when attempting to establish a route between the caller and the callee. These servers are used to determine the best route and protocols to use when communicating between the peers, even if they're behind a firewall or using {{Glossary("NAT")}}.</p> + +<div class="note"> +<p><strong>Note:</strong> You should always use STUN/TURN servers which you own, or which you have specific authorization to use. This example is using a known public STUN server but abusing these is bad form.</p> +</div> + +<p>Each object in <code>iceServers</code> contains at least a <code>urls</code> field providing URLs at which the specified server can be reached. It may also provide <code>username</code> and <code>credential</code> values to allow authentication to take place, if needed.</p> + +<p>After creating the {{domxref("RTCPeerConnection")}}, we set up handlers for the events that matter to us.</p> + +<p>The first three of these event handlers are required; you have to handle them to do anything involving streamed media with WebRTC. The rest aren't strictly required but can be useful, and we'll explore them. There are a few other events available that we're not using in this example, as well. Here's a summary of each of the event handlers we will be implementing:</p> + +<dl> + <dt>{{domxref("RTCPeerConnection.onicecandidate")}}</dt> + <dd>The local ICE layer calls your {{event("icecandidate")}} event handler, when it needs you to transmit an ICE candidate to the other peer, through your signaling server. See {{anch("Sending ICE candidates")}} for more information and to see the code for this example.</dd> + <dt>{{domxref("RTCPeerConnection.ontrack")}}</dt> + <dd>This handler for the {{event("track")}} event is called by the local WebRTC layer when a track is added to the connection. This lets you connect the incoming media to an element to display it, for example. See {{anch("Receiving new streams")}} for details.</dd> + <dt>{{domxref("RTCPeerConnection.onnegotiationneeded")}}</dt> + <dd>This function is called whenever the WebRTC infrastructure needs you to start the session negotiation process anew. Its job is to create and send an offer, to the callee, asking it to connect with us. See {{anch("Starting negotiation")}} to see how we handle this.</dd> + <dt>{{domxref("RTCPeerConnection.onremovetrack")}}</dt> + <dd>This counterpart to <code>ontrack</code> is called to handle the {{event("removetrack")}} event; it's sent to the <code>RTCPeerConnection</code> when the remote peer removes a track from the media being sent. See {{anch("Handling the removal of tracks")}}.</dd> + <dt>{{domxref("RTCPeerConnection.oniceconnectionstatechange")}}</dt> + <dd>The {{event("iceconnectionstatechange")}} event is sent by the ICE layer to let you know about changes to the state of the ICE connection. This can help you know when the connection has failed, or been lost. We'll look at the code for this example in {{anch("ICE connection state")}} below.</dd> + <dt>{{domxref("RTCPeerConnection.onicegatheringstatechange")}}</dt> + <dd>The ICE layer sends you the {{event("icegatheringstatechange")}} event, when the ICE agent's process of collecting candidates shifts, from one state to another (such as starting to gather candidates or completing negotiation). See {{anch("ICE gathering state")}} below.</dd> + <dt>{{domxref("RTCPeerConnection.onsignalingstatechange")}}</dt> + <dd>The WebRTC infrastructure sends you the {{event("signalingstatechange")}} message when the state of the signaling process changes (or if the connection to the signaling server changes). See {{anch("Signaling state")}} to see our code.</dd> +</dl> + +<h4 id="Starting_negotiation">Starting negotiation</h4> + +<p>Once the caller has created its {{domxref("RTCPeerConnection")}}, created a media stream, and added its tracks to the connection as shown in {{anch("Starting a call")}}, the browser will deliver a {{event("negotiationneeded")}} event to the {{domxref("RTCPeerConnection")}} to indicate that it's ready to begin negotiation with the other peer. Here's our code for handling the {{event("negotiationneeded")}} event:</p> + +<pre class="brush: js notranslate">function handleNegotiationNeededEvent() { + myPeerConnection.createOffer().then(function(offer) { + return myPeerConnection.setLocalDescription(offer); + }) + .then(function() { + sendToServer({ + name: myUsername, + target: targetUsername, + type: "video-offer", + sdp: myPeerConnection.localDescription + }); + }) + .catch(reportError); +}</pre> + +<p>To start the negotiation process, we need to create and send an SDP offer to the peer we want to connect to. This offer includes a list of supported configurations for the connection, including information about the media stream we've added to the connection locally (that is, the video we want to send to the other end of the call), and any ICE candidates gathered by the ICE layer already. We create this offer by calling {{domxref("RTCPeerConnection.createOffer", "myPeerConnection.createOffer()")}}.</p> + +<p>When <code>createOffer()</code> succeeds (fulfilling the promise), we pass the created offer information into {{domxref("RTCPeerConnection.setLocalDescription", "myPeerConnection.setLocalDescription()")}}, which configures the connection and media configuration state for the caller's end of the connection.</p> + +<div class="note"> +<p><strong>Note:</strong> Technically speaking, the string returned by <code>createOffer()</code> is an {{RFC(3264)}} offer.</p> +</div> + +<p>We know the description is valid, and has been set, when the promise returned by <code>setLocalDescription()</code> is fulfilled. This is when we send our offer to the other peer by creating a new <code>"video-offer"</code> message containing the local description (now the same as the offer), then sending it through our signaling server to the callee. The offer has the following members:</p> + +<dl> + <dt><code>type</code></dt> + <dd>The message type: <code>"video-offer"</code>.</dd> + <dt><code>name</code></dt> + <dd>The caller's username.</dd> + <dt><code>target</code></dt> + <dd>The name of the user we wish to call.</dd> + <dt><code>sdp</code></dt> + <dd>The SDP string describing the offer.</dd> +</dl> + +<p>If an error occurs, either in the initial <code>createOffer()</code> or in any of the fulfillment handlers that follow, an error is reported by invoking our <code>reportError()</code> function.</p> + +<p>Once <code>setLocalDescription()</code>'s fulfillment handler has run, the ICE agent begins sending {{event("icecandidate")}} events to the {{domxref("RTCPeerConnection")}}, one for each potential configuration it discovers. Our handler for the <code>icecandidate</code> event is responsible for transmitting the candidates to the other peer.</p> + +<h4 id="Session_negotiation">Session negotiation</h4> + +<p>Now that we've started negotiation with the other peer and have transmitted an offer, let's look at what happens on the callee's side of the connection for a while. The callee receives the offer and calls <code>handleVideoOfferMsg()</code> function to process it. Let's see how the callee handles the <code>"video-offer"</code> message.</p> + +<h5 id="Handling_the_invitation">Handling the invitation</h5> + +<p>When the offer arrives, the callee's <code>handleVideoOfferMsg()</code> function is called with the <code>"video-offer"</code> message that was received. This function needs to do two things. First, it needs to create its own {{domxref("RTCPeerConnection")}} and add the tracks containing the audio and video from its microphone and webcam to that. Second, it needs to process the received offer, constructing and sending its answer.</p> + +<pre class="brush: js notranslate">function handleVideoOfferMsg(msg) { + var localStream = null; + + targetUsername = msg.name; + createPeerConnection(); + + var desc = new RTCSessionDescription(msg.sdp); + + myPeerConnection.setRemoteDescription(desc).then(function () { + return navigator.mediaDevices.getUserMedia(mediaConstraints); + }) + .then(function(stream) { + localStream = stream; + document.getElementById("local_video").srcObject = localStream; + + localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream)); + }) + .then(function() { + return myPeerConnection.createAnswer(); + }) + .then(function(answer) { + return myPeerConnection.setLocalDescription(answer); + }) + .then(function() { + var msg = { + name: myUsername, + target: targetUsername, + type: "video-answer", + sdp: myPeerConnection.localDescription + }; + + sendToServer(msg); + }) + .catch(handleGetUserMediaError); +}</pre> + +<p class="brush: js">This code is very similar to what we did in the <code>invite()</code> function back in {{anch("Starting a call")}}. It starts by creating and configuring an {{domxref("RTCPeerConnection")}} using our <code>createPeerConnection()</code> function. Then it takes the SDP offer from the received <code>"video-offer"</code> message and uses it to create a new {{domxref("RTCSessionDescription")}} object representing the caller's session description.</p> + +<p class="brush: js">That session description is then passed into {{domxref("RTCPeerConnection.setRemoteDescription", "myPeerConnection.setRemoteDescription()")}}. This establishes the received offer as the description of the remote (caller's) end of the connection. If this is successful, the promise fulfillment handler (in the <code>then()</code> clause) starts the process of getting access to the callee's camera and microphone using {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}}, adding the tracks to the connection, and so forth, as we saw previously in <code>invite()</code>.</p> + +<p class="brush: js">Once the answer has been created using {{domxref("RTCPeerConnection.createAnswer", "myPeerConnection.createAnswer()")}}, the description of the local end of the connection is set to the answer's SDP by calling {{domxref("RTCPeerConnection.setLocalDescription", "myPeerConnection.setLocalDescription()")}}, then the answer is transmitted through the signaling server to the caller to let them know what the answer is</p> + +<p>Any errors are caught and passed to <code>handleGetUserMediaError()</code>, described in {{anch("Handling getUserMedia() errors")}}.</p> + +<div class="note"> +<p><strong>Note:</strong> As is the case with the caller, once the <code>setLocalDescription()</code> fulfillment handler has run, the browser begins firing {{event("icecandidate")}} events that the callee must handle, one for each candidate that needs to be transmitted to the remote peer.</p> +</div> + +<h5 id="Sending_ICE_candidates">Sending ICE candidates</h5> + +<p>The ICE negotiation process involves each peer sending candidates to the other, repeatedly, until it runs out of potential ways it can support the <code>RTCPeerConnection</code>'s media transport needs. Since ICE doesn't know about your signaling server, your code handles transmission of each candidate in your handler for the {{event("icecandidate")}} event.</p> + +<p>Your {{domxref("RTCPeerConnection.onicecandidate", "onicecandidate")}} handler receives an event whose <code>candidate</code> property is the SDP describing the candidate (or is <code>null</code> to indicate that the ICE layer has run out of potential configurations to suggest). The contents of <code>candidate</code> are what you need to transmit using your signaling server. Here's our example's implementation:</p> + +<pre class="brush: js notranslate">function handleICECandidateEvent(event) { + if (event.candidate) { + sendToServer({ + type: "new-ice-candidate", + target: targetUsername, + candidate: event.candidate + }); + } +}</pre> + +<p>This builds an object containing the candidate, then sends it to the other peer using the <code>sendToServer()</code> function previously described in {{anch("Sending messages to the signaling server")}}. The message's properties are:</p> + +<dl> + <dt><code>type</code></dt> + <dd>The message type: <code>"new-ice-candidate"</code>.</dd> + <dt><code>target</code></dt> + <dd>The username the ICE candidate needs to be delivered to. This lets the signaling server route the message.</dd> + <dt><code>candidate</code></dt> + <dd>The SDP representing the candidate the ICE layer wants to transmit to the other peer.</dd> +</dl> + +<p>The format of this message (as is the case with everything you do when handling signaling) is entirely up to you, depending on your needs; you can provide other information as required.</p> + +<div class="note"> +<p><strong>Note:</strong> It's important to keep in mind that the {{event("icecandidate")}} event is <strong>not</strong> sent when ICE candidates arrive from the other end of the call. Instead, they're sent by your own end of the call so that you can take on the job of transmitting the data over whatever channel you choose. This can be confusing when you're new to WebRTC.</p> +</div> + +<h5 id="Receiving_ICE_candidates">Receiving ICE candidates</h5> + +<p>The signaling server delivers each ICE candidate to the destination peer using whatever method it chooses; in our example this is as JSON objects, with a <code>type</code> property containing the string <code>"new-ice-candidate"</code>. Our <code>handleNewICECandidateMsg()</code> function is called by our main <a href="/en-US/docs/Web/API/WebSockets_API">WebSocket</a> incoming message code to handle these messages:</p> + +<pre class="brush: js notranslate">function handleNewICECandidateMsg(msg) { + var candidate = new RTCIceCandidate(msg.candidate); + + myPeerConnection.addIceCandidate(candidate) + .catch(reportError); +}</pre> + +<p>This function constructs an {{domxref("RTCIceCandidate")}} object by passing the received SDP into its constructor, then delivers the candidate to the ICE layer by passing it into {{domxref("RTCPeerConnection.addIceCandidate", "myPeerConnection.addIceCandidate()")}}. This hands the fresh ICE candidate to the local ICE layer, and finally, our role in the process of handling this candidate is complete.</p> + +<p>Each peer sends to the other peer a candidate for each possible transport configuration that it believes might be viable for the media being exchanged. At some point, the two peers agree that a given candidate is a good choice and they open the connection and begin to share media. It's important to note, however, that ICE negotiation does <em>not</em> stop once media is flowing. Instead, candidates may still keep being exchanged after the conversation has begun, either while trying to find a better connection method, or simply because they were already in transport when the peers successfully established their connection.</p> + +<p>In addition, if something happens to cause a change in the streaming scenario, negotiation will begin again, with the {{event("negotiationneeded")}} event being sent to the {{domxref("RTCPeerConnection")}}, and the entire process starts again as described before. This can happen in a variety of situations, including:</p> + +<ul> + <li>Changes in the network status, such as a bandwidth change, transitioning from WiFi to cellular connectivity, or the like.</li> + <li>Switching between the front and rear cameras on a phone.</li> + <li>A change to the configuration of the stream, such as its resolution or frame rate.</li> +</ul> + +<h5 id="Receiving_new_streams">Receiving new streams</h5> + +<p>When new tracks are added to the <code>RTCPeerConnection</code>— either by calling its {{domxref("RTCPeerConnection.addTrack", "addTrack()")}} method or because of renegotiation of the stream's format—a {{event("track")}} event is set to the <code>RTCPeerConnection</code> for each track added to the connection. Making use of newly added media requires implementing a handler for the <code>track</code> event. A common need is to attach the incoming media to an appropriate HTML element. In our example, we add the track's stream to the {{HTMLElement("video")}} element that displays the incoming video:</p> + +<pre class="brush: js notranslate">function handleTrackEvent(event) { + document.getElementById("received_video").srcObject = event.streams[0]; + document.getElementById("hangup-button").disabled = false; +}</pre> + +<p>The incoming stream is attached to the <code>"received_video"</code> {{HTMLElement("video")}} element, and the "Hang Up" {{HTMLElement("button")}} element is enabled so the user can hang up the call.</p> + +<p>Once this code has completed, finally the video being sent by the other peer is displayed in the local browser window!</p> + +<h5 id="Handling_the_removal_of_tracks">Handling the removal of tracks</h5> + +<p>Your code receives a {{event("removetrack")}} event when the remote peer removes a track from the connection by calling {{domxref("RTCPeerConnection.removeTrack()")}}. Our handler for <code>"removetrack"</code> is:</p> + +<pre class="brush: js notranslate">function handleRemoveTrackEvent(event) { + var stream = document.getElementById("received_video").srcObject; + var trackList = stream.getTracks(); + + if (trackList.length == 0) { + closeVideoCall(); + } +}</pre> + +<p>This code fetches the incoming video {{domxref("MediaStream")}} from the <code>"received_video"</code> {{HTMLElement("video")}} element's {{htmlattrxref("srcObject", "video")}} attribute, then calls the stream's {{domxref("MediaStream.getTracks", "getTracks()")}} method to get an array of the stream's tracks.</p> + +<p>If the array's length is zero, meaning there are no tracks left in the stream, we end the call by calling <code>closeVideoCall()</code>. This cleanly restores our app to a state in which it's ready to start or receive another call. See {{anch("Ending the call")}} to learn how <code>closeVideoCall()</code> works.</p> + +<h4 id="Ending_the_call">Ending the call</h4> + +<p>There are many reasons why calls may end. A call might have completed, with one or both sides having hung up. Perhaps a network failure has occurred, or one user might have quit their browser, or had a system crash. In any case, all good things must come to an end.</p> + +<h5 id="Hanging_up">Hanging up</h5> + +<p>When the user clicks the "Hang Up" button to end the call, the <code>hangUpCall()</code> function is called:</p> + +<pre class="brush: js notranslate">function hangUpCall() { + closeVideoCall(); + sendToServer({ + name: myUsername, + target: targetUsername, + type: "hang-up" + }); +}</pre> + +<p><code>hangUpCall()</code> executes <code>closeVideoCall()</code> to shut down and reset the connection and release resources. It then builds a <code>"hang-up"</code> message and sends it to the other end of the call to tell the other peer to neatly shut itself down.</p> + +<h5 id="Ending_the_call_2">Ending the call</h5> + +<p>The <code>closeVideoCall()</code> function, shown below, is responsible for stopping the streams, cleaning up, and disposing of the {{domxref("RTCPeerConnection")}} object:</p> + +<pre class="brush: js notranslate">function closeVideoCall() { + var remoteVideo = document.getElementById("received_video"); + var localVideo = document.getElementById("local_video"); + + if (myPeerConnection) { + myPeerConnection.ontrack = null; + myPeerConnection.onremovetrack = null; + myPeerConnection.onremovestream = null; + myPeerConnection.onicecandidate = null; + myPeerConnection.oniceconnectionstatechange = null; + myPeerConnection.onsignalingstatechange = null; + myPeerConnection.onicegatheringstatechange = null; + myPeerConnection.onnegotiationneeded = null; + + if (remoteVideo.srcObject) { + remoteVideo.srcObject.getTracks().forEach(track => track.stop()); + } + + if (localVideo.srcObject) { + localVideo.srcObject.getTracks().forEach(track => track.stop()); + } + + myPeerConnection.close(); + myPeerConnection = null; + } + + remoteVideo.removeAttribute("src"); + remoteVideo.removeAttribute("srcObject"); + localVideo.removeAttribute("src"); + remoteVideo.removeAttribute("srcObject"); + + document.getElementById("hangup-button").disabled = true; + targetUsername = null; +} +</pre> + +<p>After pulling references to the two {{HTMLElement("video")}} elements, we check if a WebRTC connection exists; if it does, we proceed to disconnect and close the call:</p> + +<ol> + <li>All of the event handlers are removed. This prevents stray event handlers from being triggered while the connection is in the process of closing, potentially causing errors.</li> + <li>For both remote and local video streams, we iterate over each track, calling the {{domxref("MediaStreamTrack.stop()")}} method to close each one.</li> + <li>Close the {{domxref("RTCPeerConnection")}} by calling {{domxref("RTCPeerConnection.close", "myPeerConnection.close()")}}.</li> + <li>Set <code>myPeerConnection</code> to <code>null</code>, ensuring our code learns there's no ongoing call; this is useful when the user clicks a name in the user list.</li> +</ol> + +<p>Then for both the incoming and outgoing {{HTMLElement("video")}} elements, we remove their {{htmlattrxref("src", "video")}} and {{htmlattrxref("srcObject", "video")}} attributes using their {{domxref("Element.removeAttribute", "removeAttribute()")}} methods. This completes the disassociation of the streams from the video elements.</p> + +<p>Finally, we set the {{domxref("HTMLElement.disabled", "disabled")}} property to <code>true</code> on the "Hang Up" button, making it unclickable while there is no call underway; then we set <code>targetUsername</code> to <code>null</code> since we're no longer talking to anyone. This allows the user to call another user, or to receive an incoming call.</p> + +<h4 id="Dealing_with_state_changes">Dealing with state changes</h4> + +<p>There are a number of additional events you can set listeners for which notifying your code of a variety of state changes. We use three of them: {{event("iceconnectionstatechange")}}, {{event("icegatheringstatechange")}}, and {{event("signalingstatechange")}}.</p> + +<h5 id="ICE_connection_state">ICE connection state</h5> + +<p>{{event("iceconnectionstatechange")}} events are sent to the {{domxref("RTCPeerConnection")}} by the ICE layer when the connection state changes (such as when the call is terminated from the other end).</p> + +<pre class="brush: js notranslate">function handleICEConnectionStateChangeEvent(event) { + switch(myPeerConnection.iceConnectionState) { + case "closed": + case "failed": + case "disconnected": + closeVideoCall(); + break; + } +}</pre> + +<p>Here, we apply our <code>closeVideoCall()</code> function when the ICE connection state changes to <code>"closed"</code>, <code>"failed"</code>, or <code>"disconnected"</code>. This handles shutting down our end of the connection so that we're ready start or accept a call once again.</p> + +<h5 id="ICE_signaling_state">ICE signaling state</h5> + +<p>Similarly, we watch for {{event("signalingstatechange")}} events. If the signaling state changes to <code>closed</code>, we likewise close the call out.</p> + +<pre class="brush: js notranslate">function handleSignalingStateChangeEvent(event) { + switch(myPeerConnection.signalingState) { + case "closed": + closeVideoCall(); + break; + } +};</pre> + +<div class="blockIndicator note"> +<p><strong>Note:</strong> The <code>closed</code> signaling state has been deprecated in favor of the <code>closed</code> {{domxref("RTCPeerConnection.iceConnectionState", "iceConnectionState")}}. We are watching for it here to add a bit of backward compatibility.</p> +</div> + +<h5 id="ICE_gathering_state">ICE gathering state</h5> + +<p>{{event("icegatheringstatechange")}} events are used to let you know when the ICE candidate gathering process state changes. Our example doesn't use this for anything, but it can be useful to watch these events for debugging purposes, as well as to detect when candidate collection has finished.</p> + +<pre class="brush: js notranslate">function handleICEGatheringStateChangeEvent(event) { + // Our sample just logs information to console here, + // but you can do whatever you need. +} +</pre> + +<h2 id="Next_steps">Next steps</h2> + +<p>You can now <a href="https://webrtc-from-chat.glitch.me/">try out this example on Glitch</a> to see it in action. Open the Web console on both devices and look at the logged output—although you don't see it in the code as shown above, the code on the server (and on <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-from-chat">GitHub</a>) has a lot of console output so you can see the signaling and connection processes at work.</p> + +<p>Another obvious improvement would be to add a "ringing" feature, so that instead of just asking the user for permission to use the camera and microphone, a "User X is calling. Would you like to answer?" prompt appears first.</p> diff --git a/files/ru/web/api/webrtc_api/simple_rtcdatachannel_sample/index.html b/files/ru/web/api/webrtc_api/simple_rtcdatachannel_sample/index.html new file mode 100644 index 0000000000..5d818e7829 --- /dev/null +++ b/files/ru/web/api/webrtc_api/simple_rtcdatachannel_sample/index.html @@ -0,0 +1,272 @@ +--- +title: Простой пример RTCDataChannel +slug: Web/API/WebRTC_API/Simple_RTCDataChannel_sample +translation_of: Web/API/WebRTC_API/Simple_RTCDataChannel_sample +--- +<p>{{WebRTCSidebar}}</p> + +<p>Интерфейс {{domxref("RTCDataChannel")}} является функциональностью <a href="/en-US/docs/Web/API/WebRTC_API">WebRTC API</a> , который позволяет открыть канал между узлами соединения, по которому можно отправлять и получать произвольные данные. Эти API намеренно сходны с <a href="/en-US/docs/Web/API/WebSocket_API">WebSocket API</a>, для использования единой програмной модели.</p> + +<p>В этом примере мы откроем соединение {{domxref ("RTCDataChannel")}}, связывающее два элемента на одной странице. Хотя это явно надуманный сценарий, он полезен для демонстрации последовательности соединения двух узлов. Мы расскажем о механизме выполнения соединения, передачи и получения данных, но оставим немного информации о поиске и подключении к удаленному компьютеру для другого примера.</p> + +<h2 id="Разметка_HTML">Разметка HTML</h2> + +<p>Сначала быстро посмотрим на <a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-simple-datachannel/index.html" rel="noopener">необходимую разметку HTML </a>. В ней нет ничего сложного. В начале мы определяем пару кнопок, создающих и закрывающих соединение:</p> + +<pre class="brush: html"><button id="connectButton" name="connectButton" class="buttonleft"> + Connect +</button> +<button id="disconnectButton" name="disconnectButton" class="buttonright" disabled> + Disconnect +</button></pre> + +<p>Затем, определяем блок, который содержит элемент управления ввода текста, в который пользователь печатает текст свого сообщения, предназначенного для отправки, по нажатию кнопки. Элемент {{HTMLElement("div")}} будет представлять первый узлел в канале передачи (сторона отправителя).</p> + +<pre class="brush: html"> <div class="messagebox"> + <label for="message">Enter a message: + <input type="text" name="message" id="message" placeholder="Message text" + inputmode="latin" size=60 maxlength=120 disabled> + </label> + <button id="sendButton" name="sendButton" class="buttonright" disabled> + Send + </button> + </div></pre> + +<p>И наконец, небольшой блок, в который будем помещать получаемое сообщение. Элемент {{HTMLElement("div")}} будет представлять второй узел соединения (сторона получателя).</p> + +<pre class="brush: html"><div class="messagebox" id="receivebox"> + <p>Messages received:</p> +</div></pre> + +<h2 id="Код_JavaScript">Код JavaScript</h2> + +<p>While you can just <a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-simple-datachannel/main.js" rel="noopener">look at the code itself on GitHub</a>, below we'll review the parts of the code that do the heavy lifting.</p> + +<p>The WebRTC API makes heavy use of {{jsxref("Promise")}}s. They make it very easy to chain the steps of the connection process together; if you haven't already read up on this functionality of <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla">ECMAScript 2015</a>, you should read up on them. Similarly, this example uses <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">arrow functions</a> to simplify syntax.</p> + +<h3 id="Starting_up">Starting up</h3> + +<p>When the script is run, we set up an {{event("load")}} event listener, so that once the page is fully loaded, our <code>startup()</code> function is called.</p> + +<pre class="brush: js">function startup() { + connectButton = document.getElementById('connectButton'); + disconnectButton = document.getElementById('disconnectButton'); + sendButton = document.getElementById('sendButton'); + messageInputBox = document.getElementById('message'); + receiveBox = document.getElementById('receivebox'); + + // Set event listeners for user interface widgets + + connectButton.addEventListener('click', connectPeers, false); + disconnectButton.addEventListener('click', disconnectPeers, false); + sendButton.addEventListener('click', sendMessage, false); +}</pre> + +<p>This is quite straightforward. We grab references to all the page elements we'll need to access, then set {{domxref("EventListener", "event listeners")}} on the three buttons.</p> + +<h3 id="Establishing_a_connection">Establishing a connection</h3> + +<p>When the user clicks the "Connect" button, the <code>connectPeers()</code> method is called. We're going to break this up and look at it a bit at a time, for clarity.</p> + +<div class="note"> +<p><strong>Note:</strong> Even though both ends of our connection will be on the same page, we're going to refer to the one that starts the connection as the "local" one, and to the other as the "remote" end.</p> +</div> + +<h4 id="Set_up_the_local_peer">Set up the local peer</h4> + +<pre class="brush: js">localConnection = new RTCPeerConnection(); + +sendChannel = localConnection.createDataChannel("sendChannel"); +sendChannel.onopen = handleSendChannelStatusChange; +sendChannel.onclose = handleSendChannelStatusChange; +</pre> + +<p>The first step is to create the "local" end of the connection. This is the peer that will send out the connection request. The next step is to create the {{domxref("RTCDataChannel")}} by calling {{domxref("RTCPeerConnection.createDataChannel()")}} and set up event listeners to monitor the channel so that we know when it's opened and closed (that is, when the channel is connected or disconnected within that peer connection).</p> + +<p>It's important to keep in mind that each end of the channel has its own {{domxref("RTCDataChannel")}} object.</p> + +<h4 id="Set_up_the_remote_peer">Set up the remote peer</h4> + +<pre class="brush: js">remoteConnection = new RTCPeerConnection(); +remoteConnection.ondatachannel = receiveChannelCallback;</pre> + +<p>The remote end is set up similarly, except that we don't need to explicitly create an {{domxref("RTCDataChannel")}} ourselves, since we're going to be connected through the channel established above. Instead, we set up a {{event("datachannel")}} event handler; this will be called when the data channel is opened; this handler will receive an <code>RTCDataChannel</code> object; you'll see this below.</p> + +<h4 id="Set_up_the_ICE_candidates">Set up the ICE candidates</h4> + +<p>The next step is to set up each connection with ICE candidate listeners; these will be called when there's a new ICE candidate to tell the other side about.</p> + +<div class="note"> +<p><strong>Note:</strong> In a real-world scenario in which the two peers aren't running in the same context, the process is a bit more involved; each side provides, one at a time, a suggested way to connect (for example, UDP, UDP with a relay, TCP, etc.) by calling {{domxref("RTCPeerConnection.addIceCandidate()")}}, and they go back and forth until agreement is reached. But here, we just accept the first offer on each side, since there's no actual networking involved.</p> +</div> + +<pre class="brush: js"> localConnection.onicecandidate = e => !e.candidate + || remoteConnection.addIceCandidate(e.candidate) + .catch(handleAddCandidateError); + + remoteConnection.onicecandidate = e => !e.candidate + || localConnection.addIceCandidate(e.candidate) + .catch(handleAddCandidateError);</pre> + +<p>We configure each {{domxref("RTCPeerConnection")}} to have an event handler for the {{event("icecandidate")}} event.</p> + +<h4 id="Start_the_connection_attempt">Start the connection attempt</h4> + +<p>The last thing we need to do in order to begin connecting our peers is to create a connection offer.</p> + +<pre class="brush: js"> localConnection.createOffer() + .then(offer => localConnection.setLocalDescription(offer)) + .then(() => remoteConnection.setRemoteDescription(localConnection.localDescription)) + .then(() => remoteConnection.createAnswer()) + .then(answer => remoteConnection.setLocalDescription(answer)) + .then(() => localConnection.setRemoteDescription(remoteConnection.localDescription)) + .catch(handleCreateDescriptionError);</pre> + +<p>Let's go through this line by line and decipher what it means.</p> + +<ol> + <li>First, we call {{domxref("RTCPeerConnection.createOffer()")}} method to create an {{Glossary("SDP")}} (Session Description Protocol) blob describing the connection we want to make. This method accepts, optionally, an object with constraints to be met for the connection to meet your needs, such as whether the connection should support audio, video, or both. In our simple example, we don't have any constraints.</li> + <li>If the offer is created successfully, we pass the blob along to the local connection's {{domxref("RTCPeerConnection.setLocalDescription()")}} method. This configures the local end of the connection.</li> + <li>The next step is to connect the local peer to the remote by telling the remote peer about it. This is done by calling <code>remoteConnection.</code>{{domxref("RTCPeerConnection.setRemoteDescription()")}}. Now the <code>remoteConnection</code> knows about the connection that's being built. In a real application, this would require a signaling server to exchange the description object.</li> + <li>That means it's time for the remote peer to reply. It does so by calling its {{domxref("RTCPeerConnection.createAnswer", "createAnswer()")}} method. This generates a blob of SDP which describes the connection the remote peer is willing and able to establish. This configuration lies somewhere in the union of options that both peers can support.</li> + <li>Once the answer has been created, it's passed into the remoteConnection by calling {{domxref("RTCPeerConnection.setLocalDescription()")}}. That establishes the remote's end of the connection (which, to the remote peer, is its local end. This stuff can be confusing, but you get used to it). Again, this would normally be exchanged through a signalling server.</li> + <li>Finally, the local connection's remote description is set to refer to the remote peer by calling localConnection's {{domxref("RTCPeerConnection.setRemoteDescription()")}}.</li> + <li>The <code>catch()</code> calls a routine that handles any errors that occur.</li> +</ol> + +<div class="note"> +<p><strong>Note:</strong> Once again, this process is not a real-world implementation; in normal usage, there's two chunks of code running on two machines, interacting and negotiating the connection. A side channel, commonly called a “signalling server,” is usually used to exchange the description (which is in <strong>application/sdp</strong> form) between the two peers.</p> +</div> + +<h4 id="Handling_successful_peer_connection">Handling successful peer connection</h4> + +<p>As each side of the peer-to-peer connection is successfully linked up, the corresponding {{domxref("RTCPeerConnection")}}'s {{event("icecandidate")}} event is fired. These handlers can do whatever's needed, but in this example, all we need to do is update the user interface:</p> + +<pre class="brush: js"> function handleLocalAddCandidateSuccess() { + connectButton.disabled = true; + } + + function handleRemoteAddCandidateSuccess() { + disconnectButton.disabled = false; + }</pre> + +<p>The only thing we do here is disable the "Connect" button when the local peer is connected and enable the "Disconnect" button when the remote peer connects.</p> + +<h4 id="Connecting_the_data_channel">Connecting the data channel</h4> + +<p>Once the {{domxref("RTCPeerConnection")}} is open, the {{event("datachannel")}} event is sent to the remote to complete the process of opening the data channel; this invokes our <code>receiveChannelCallback()</code> method, which looks like this:</p> + +<pre class="brush: js"> function receiveChannelCallback(event) { + receiveChannel = event.channel; + receiveChannel.onmessage = handleReceiveMessage; + receiveChannel.onopen = handleReceiveChannelStatusChange; + receiveChannel.onclose = handleReceiveChannelStatusChange; + }</pre> + +<p>The {{event("datachannel")}} event includes, in its channel property, a reference to a {{domxref("RTCDataChannel")}} representing the remote peer's end of the channel. This is saved, and we set up, on the channel, event listeners for the events we want to handle. Once this is done, our <code>handleReceiveMessage()</code> method will be called each time data is received by the remote peer, and the <code>handleReceiveChannelStatusChange()</code> method will be called any time the channel's connection state changes, so we can react when the channel is fully opened and when it's closed.</p> + +<h3 id="Handling_channel_status_changes">Handling channel status changes</h3> + +<p>Both our local and remote peers use a single method to handle events indicating a change in the status of the channel's connection.</p> + +<p>When the local peer experiences an open or close event, the <code>handleSendChannelStatusChange()</code> method is called:</p> + +<pre class="brush: js"> function handleSendChannelStatusChange(event) { + if (sendChannel) { + var state = sendChannel.readyState; + + if (state === "open") { + messageInputBox.disabled = false; + messageInputBox.focus(); + sendButton.disabled = false; + disconnectButton.disabled = false; + connectButton.disabled = true; + } else { + messageInputBox.disabled = true; + sendButton.disabled = true; + connectButton.disabled = false; + disconnectButton.disabled = true; + } + } + }</pre> + +<p>If the channel's state has changed to "open", that indicates that we have finished establishing the link between the two peers. The user interface is updated correspondingly by enabling the text input box for the message to send, focusing the input box so that the user can immediately begin to type, enabling the "Send" and "Disconnect" buttons, now that they're usable, and disabling the "Connect" button, since it is not needed when the conneciton is open.</p> + +<p>If the state has changed to "closed", the opposite set of actions occurs: the input box and "Send" button are disabled, the "Connect" button is enabled so that the user can open a new connection if they wish to do so, and the "Disconnect" button is disabled, since it's not useful when no connection exists.</p> + +<p>Our example's remote peer, on the other hand, ignores the status change events, except for logging the event to the console:</p> + +<pre class="brush: js"> function handleReceiveChannelStatusChange(event) { + if (receiveChannel) { + console.log("Receive channel's status has changed to " + + receiveChannel.readyState); + } + }</pre> + +<p>The <code>handleReceiveChannelStatusChange()</code> method receives as an input parameter the event which occurred; this will be an {{domxref("RTCDataChannelEvent")}}.</p> + +<h3 id="Sending_messages">Sending messages</h3> + +<p>When the user presses the "Send" button, the sendMessage() method we've established as the handler for the button's {{event("click")}} event is called. That method is simple enough:</p> + +<pre class="brush: js"> function sendMessage() { + var message = messageInputBox.value; + sendChannel.send(message); + + messageInputBox.value = ""; + messageInputBox.focus(); + }</pre> + +<p>First, the text of the message is fetched from the input box's {{htmlattrxref("value", "input")}} attribute. This is then sent to the remote peer by calling {{domxref("RTCDataChannel.send", "sendChannel.send()")}}. That's all there is to it! The rest of this method is just some user experience sugar -- the input box is emptied and re-focused so the user can immediately begin typing another message.</p> + +<h3 id="Receiving_messages">Receiving messages</h3> + +<p>When a "message" event occurs on the remote channel, our <code>handleReceiveMessage()</code> method is called as the event handler.</p> + +<pre class="brush: js"> function handleReceiveMessage(event) { + var el = document.createElement("p"); + var txtNode = document.createTextNode(event.data); + + el.appendChild(txtNode); + receiveBox.appendChild(el); + }</pre> + +<p>This method simply performs some basic {{Glossary("DOM")}} injection; it creates a new {{HTMLElement("p")}} (paragraph) element, then creates a new {{domxref("Text")}} node containing the message text, which is received in the event's <code>data</code> property. This text node is appended as a child of the new element, which is then inserted into the <code>receiveBox</code> block, thereby causing it to draw in the browser window.</p> + +<h3 id="Disconnecting_the_peers">Disconnecting the peers</h3> + +<p>When the user clicks the "Disconnect" button, the <code>disconnectPeers()</code> method previously set as that button's handler is called.</p> + +<pre class="brush: js"> function disconnectPeers() { + + // Close the RTCDataChannels if they're open. + + sendChannel.close(); + receiveChannel.close(); + + // Close the RTCPeerConnections + + localConnection.close(); + remoteConnection.close(); + + sendChannel = null; + receiveChannel = null; + localConnection = null; + remoteConnection = null; + + // Update user interface elements + + connectButton.disabled = false; + disconnectButton.disabled = true; + sendButton.disabled = true; + + messageInputBox.value = ""; + messageInputBox.disabled = true; + } +</pre> + +<p>This starts by closing each peer's {{domxref("RTCDataChannel")}}, then, similarly, each {{domxref("RTCPeerConnection")}}. Then all the saved references to these objects are set to <code>null</code> to avoid accidental reuse, and the user interface is updated to reflect the fact that the connection has been closed.</p> + +<h2 id="Следующие_шаги">Следующие шаги</h2> + +<p><a href="https://mdn-samples.mozilla.org/s/webrtc-simple-datachannel">Попробуйте пример в деле</a> и посмотрите на <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-simple-datachannel">исходный код простого примера</a>, доступный на GitHub.</p> diff --git a/files/ru/web/api/webrtc_api/taking_still_photos/index.html b/files/ru/web/api/webrtc_api/taking_still_photos/index.html new file mode 100644 index 0000000000..ec5e7ec42d --- /dev/null +++ b/files/ru/web/api/webrtc_api/taking_still_photos/index.html @@ -0,0 +1,222 @@ +--- +title: Захват кадров с WebRTC +slug: Web/API/WebRTC_API/Taking_still_photos +tags: + - Захват WebRTC +translation_of: Web/API/WebRTC_API/Taking_still_photos +--- +<p dir="rtl"><span><span><span><span>{{WebRTCSidebar}}</span></span></span></span></p> + +<p><span class="seoSummary"><span><span><span><span>В этой статье объясняется как использовать WebRTC для получения доступа к камере компьютера или мобильного устройства, и захвата кадров с их помощью. </span></span></span></span></span><a href="https://mdn-samples.mozilla.org/s/webrtc-capturestill"><span><span><span><span>Ознакомтесь с примером,</span></span></span></span></a><span><span><span><span> а затем узнайте как это работает.</span></span></span></span></p> + +<p><img alt="Uz WebRTC balstīta attēla uztveršanas lietotne - kreisajā pusē un bez tīmekļa kameras uzņemšanas video straumē un poga" src="https://mdn.mozillademos.org/files/10281/web-rtc-demo.png" style="display: block; height: 252px; margin: 0 auto; width: 677px;"></p> + +<p><span><span><span><span>Перейдите непостредственно </span></span></span></span><a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill" rel="noopener"><span><span><span><span>к коду на Github</span></span></span></span></a><span><span><span><span> , при желании.</span></span></span></span></p> + +<h2 id="Разметка_HTML"><span><span><span><span>Разметка HTML</span></span></span></span></h2> + +<p><a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill/index.html" rel="noopener"><span><span><span><span>Наш HTML интерфейс</span></span></span></span></a><span><span><span><span> состоит из двух секций : панель отображения видео потока, из которого будет производиться захват и панель отображения результата захвата. Каждая панель имеет свой элемент </span></span></span><span><span><span>{{HTMLElement ("div")}}, для облегчения стилизации и управления.</span></span></span></span></p> + +<p><span><span><span><span>Первая панель слева содержит два компонента : элемент {{HTMLElement ("video")}} , который будет получать поток, отводимый с камеры, и элемент </span></span></span></span>{{HTMLElement("button")}}, каторый будет использоваться пользователем для активации захвата видео кадра.</p> + +<pre> <div class="camera"> + <video id="video">Video stream not available.</video> + <button id="startbutton">Take photo</button> + </div></pre> + +<p>Все это просто, и мы увидим как они связаны между собой, когда обратимся к коду JavaScript .</p> + +<p>В разметке имеется элемент {{HTMLElement("canvas")}} , который сохраняет захваченный кадр, который может быть дополнительно обработан и конвертируется в выходной файл изображения. Элемент<code> canvas </code>является скрытым, в его стиле свойство {{cssxref("display")}}<code>:none</code>, во избежании поломки интерфейса, где пользователю совершенно не обязательно видеть служебные элементы.</p> + +<p>Для отображения пользователю результата захвата кадра, в интерфейсе расположен элемент {{HTMLElement("img")}}.</p> + +<pre> <canvas id="canvas"> + </canvas> + <div class="output"> + <img id="photo" alt="The screen capture will appear in this box."> + </div></pre> + +<p>Вот и все, что касается HTML. Остальное - просто пух макета страницы и немного текста, предлагающего ссылку на эту страницу.</p> + +<h2 id="Код_JavaScript">Код JavaScript</h2> + +<p>Посмотрим на <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill/capture.js" rel="noopener">JavaScript code</a>. Разобъем его на части, для упрощения объяснения.</p> + +<h3 id="Инициализация">Инициализация</h3> + +<p>Начнем с обертки всего скрипта в анонимную функцию, во избежании конфликтов глобальных переменных, затем инициализируем различные нужные переменные.</p> + +<pre>(function() { + var width = 320; // Этим создадим ширину фотографии + var height = 0; // Это будет вычисляться на основе входящего потока + + var streaming = false; + + var video = null; + var canvas = null; + var photo = null; + var startbutton = null;</pre> + +<p>Все переменные выше:</p> + +<dl> + <dt><code>width</code></dt> + <dd>Какой бы не был размер входящего видео, мы намерены масштабировать результирующее изображение к ширине в 320 px.</dd> + <dt><code>height</code></dt> + <dd>Результирующая высота изображения будет вычисляться на основе переданной ширины и соотношению сторон потока с камеры.</dd> + <dt><code>streaming</code></dt> + <dd>Указывает на текущую активность видеопотока.</dd> + <dt><code>video</code></dt> + <dd>Будет содержать ссылку на элемент {{HTMLElement("video")}} после загрузки страницы.</dd> + <dt><code>canvas</code></dt> + <dd>Содержит ссылку на элемент {{HTMLElement("canvas")}} после загрузки страницы.</dd> + <dt><code>photo</code></dt> + <dd>Содержит ссылку на элемент {{HTMLElement("img")}} после загрузки страницы.</dd> + <dt><code>startbutton</code></dt> + <dd>Содержит ссылку на элемент {{HTMLElement("button")}} после загрузки страницы, используюется для старта захвата.</dd> +</dl> + +<h3 id="Функция_startup">Функция startup()</h3> + +<p>Функция <code>startup()</code> запускается, когда страница закончила загрузку, благодаря установке {{domxref("window.addEventListener()")}}. Работа функции состоит в том, что бы запросить доступ у пользователя к его камере, инициализировать элемент {{HTMLElement("img")}} в значение по умолчанию, и установить обработчики событий, необходимых для получения каждого видеокадра с камеры, запускать захват изображения, при нажатии на кнопку.</p> + +<h4 id="Получаем_ссылки_на_элементы">Получаем ссылки на элементы</h4> + +<p>Сначала, получим ссылки на основные элементы, доступ к которым нам необходим.</p> + +<pre> function startup() { + video = document.getElementById('video'); + canvas = document.getElementById('canvas'); + photo = document.getElementById('photo'); + startbutton = document.getElementById('startbutton');</pre> + +<h4 id="Получаем_медиапоток">Получаем медиапоток </h4> + +<p>Следующая задача - получение медиапотока:</p> + +<pre> navigator.mediaDevices.getUserMedia({ video: true, audio: false }) + .then(function(stream) { + video.srcObject = stream; + video.play(); + }) + .catch(function(err) { + console.log("An error occurred: " + err); + }); +</pre> + +<p>Здесь мы вазываем метод {{domxref("MediaDevices.getUserMedia()")}} , запрашивая медиапоток без аудиопотока (<code>audio : false</code>). Он возвращает промис, на котором мы определяем методы успешного и не успешного выполнений.</p> + +<p>Успешное выполнение промиса передает объект потока( <code>stream</code> ) в качестве параметра функции метода <code>then()</code>., который присваевается свойству <code>srcObject</code> элемента {{HTMLElement("video")}}, направляя поток в него.</p> + +<p>Как только поток связан с элементом <code><video></code> , запускаем его воспроизведение, вызовом метода <code><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#play">HTMLMediaElement.play()</a></code>.</p> + +<p>Метод обработки ошибки промиса вызывается в случае, если получение потока окажется неудачным, к примеру, когда к устройству подключена несовместимая камера, или пользователь запретил к ней доступ.</p> + +<h4 id="Обработка_события_начала_воспроизведения">Обработка события начала воспроизведения</h4> + +<p>После момента вызова метода <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#play">HTMLMediaElement.play()</a></code> на элементе {{HTMLElement("video")}}, возникает промежуток времени до начала воспроизведения видеопотока. Для недопущения блокирования интерфейса пользователя в это промежуток, нужно установить обработчик события {{event("canplay")}} элемента <code>video</code> , который сработает, когда элемент начнет воспроизведение видеопотока. В этот момент все свойства элемента <code>video</code> конфигурируются на основе формата потока.</p> + +<pre> video.addEventListener('canplay', function(ev){ + if (!streaming) { + height = video.videoHeight / (video.videoWidth/width); + + video.setAttribute('width', width); + video.setAttribute('height', height); + canvas.setAttribute('width', width); + canvas.setAttribute('height', height); + streaming = true; + } + }, false);</pre> + +<p>Функциональность обработчика не будет запущена, если он запускается повторно. Это отслеживает переменная <code>streaming</code> , которая содержит значение <code>false</code> при первом запуске обработчика.</p> + +<p>Если это действительно первый запуск, мы устанавливаем высоту видео на основе разницы в размере между фактическим размером видео - <code>video.videoWidth</code> и шириной, на которую мы собираемся его визуализировать - <code>width</code></p> + +<p>Наконец, свойства <code>width</code> и <code>height</code> элементов<code> video</code> и <code>canvas</code> устанавливаются так, чтобы соответствовать друг другу, вызывая метод {{domxref("Element.setAttribute()")}} на каждом из двух свойств каждого элемента, и, при необходимости, устанавливая ширину и высоту. Наконец, мы установили для переменной <code>streaming</code> значение <code>true</code>, чтобы предотвратить случайное повторное выполнение этого установочного кода.</p> + +<h4 id="Обработка_нажатий_кнопки">Обработка нажатий кнопки</h4> + +<p>Для захвата кадра, пользователь каждый раз нажимает кнопку <code>startbutton</code>, нужно добавить обработчик события кнопки, для его вызова при возникновении события {{event("click")}} :</p> + +<pre> startbutton.addEventListener('click', function(ev){ + takepicture(); + ev.preventDefault(); + }, false);</pre> + +<p>Метод прост, он вызывает функцию <code>takepicture()</code>, определяемую ниже в секции {{anch("Capturing a frame from the stream")}}, затем вызывает метод {{domxref("Event.preventDefault()")}} на полученном объекте события, для предотвращения действия обработки события более одного раза.</p> + +<h4 id="Завершение_метода_startup">Завершение метода startup() </h4> + +<p>Еще пара строк кода в методе <code>startup()</code>:</p> + +<pre> clearphoto(); + }</pre> + +<p>Вызов метода <code>clearphoto()</code> описывается в секции {{anch("Clearing the photo box")}}.</p> + +<h3 id="Отчистка_бокса_для_фотографии">Отчистка бокса для фотографии</h3> + +<p>Очистка бокса фотографии включает создание изображения, а затем преобразование его в формат, используемый элементом {{HTMLElement ("img")}}, который отображает последний снятый кадр. Код ниже:</p> + +<pre> function clearphoto() { + var context = canvas.getContext('2d'); + context.fillStyle = "#AAA"; + context.fillRect(0, 0, canvas.width, canvas.height); + + var data = canvas.toDataURL('image/png'); + photo.setAttribute('src', data); + }</pre> + +<p>Начнем с получения ссылки на скрытый элемент {{HTMLElement ("canvas")}}, который мы используем для рендеринга за пределами экрана. Затем мы устанавливаем свойсто <code>fillStyle</code> в <code>#AAA</code> ( светло-серый) и заполняем весь холст этим цветом, вызывая метод {{domxref("CanvasRenderingContext2D.fillRect()","fillRect()")}}.</p> + +<p>Наконец, в этой функции мы конвертируем <code>canvas</code> в изображение PNG и вызываем метод <code>{{domxref("Element.setAttribute", "photo.setAttribute()")}}</code> отображая захваченный цветовой фон в элементе изображения (бокса для фотографии).</p> + +<h3 id="Захват_кадра_из_видеопотока">Захват кадра из видеопотока</h3> + +<p>Последняя функция, требующая определения и являющаяся основным смыслом всего примера - <code>takepicture()</code> , которая захватывает текущий видеокадр, конвертирует его в формат PNG, и отображает его в блоке отображения кадра. Код её ниже:</p> + +<pre> 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); + } else { + clearphoto(); + } + }</pre> + +<p>Как и в случае, когда нам нужно работать с содержимым <code>canvas</code>, мы начинаем с {{domxref("CanvasRenderingContext2D","2D drawing context")}} для скрытого <code>canvas</code>.</p> + +<p>Затем, если ширина и высота не равны нулю (имеется в виду, что есть, по крайней мере, потенциально допустимые данные изображения), мы устанавливаем ширину и высоту <code>canvas</code>, чтобы они соответствовали ширине захваченного кадра, затем вызываем метод {{domxref("CanvasRenderingContext2D.drawImage()", "drawImage()")}} , что бы отрисовать текущий кадр видео в контексте <code>canvas</code>, заполнив весь холст изображением кадра.</p> + +<div class="blockIndicator note"> +<p><strong>Примечание :</strong> Используется факт того, что интерфейс {{domxref("HTMLVideoElement")}} похож на интерфейс {{domxref("HTMLImageElement")}} для любых API , которые принимают <code>HTMLImageElement</code> в качестве параметра, с текущим кадром видео, представленным как содержимое изображения.</p> +</div> + +<p>Как тоько <code>canvas</code> будет содержать захваченное видео, конвертируем его в PNG формат, вызывая метод {{domxref("HTMLCanvasElement.toDataURL()")}} на нем; наконец вызываем метод {{domxref("Element.setAttribute", "photo.setAttribute()")}} отображая захваченное изображение в элементе изображения (бокса фотографии).</p> + +<p>Если подходящее изображение не доступно (то есть, <code>width</code> и <code>height</code> равны 0), отчищаем содержимое элемента изображения, вызывая метод <code>clearphoto()</code>.</p> + +<h2 id="Приколы_с_фильтрами">Приколы с фильтрами</h2> + +<p>Поскольку мы снимаем изображения с веб-камеры пользователя, захватывая кадры из элемента {{HTMLElement("video")}} , можно легко применить фильтры и забавные эффекты к элементу <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">video</span></font>. Оказывается, любые CSS-фильтры, которые вы применяете к элементу с помощью свойства {{cssxref ("filter")}}, влияют на захваченную фотографию.Эти фильтры могут варьироваться от простых (делая изображение черно-белым) до экстремальных (размытие по Гауссу и вращение оттенка).</p> + +<p>Вы можете экспериментировать с этими эффектами, используя, например, инструмент разработчика FirefoxYou <a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Style_Editor">редактор стилей</a>; смотрим <a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Edit_CSS_filters">Редактирование с CSS фильтрами</a> о подробностях выполнения.</p> + +<h2 id="Использование_определенных_устройств">Использование определенных устройств</h2> + +<p>При необходимости вы можете ограничить набор разрешенных источников видео, определенным устройством или набором устройств. Для этого нужно вызвать метод {{domxref("navigator.mediaDevices.enumerateDevices()")}}. Когда промис разрешиться массивом объектов {{domxref("MediaDeviceInfo")}} , описывающих доступные устройства , выберите те, которым хотите разрешить доступ и укажите соответствующий идентификатор устройства {{domxref("MediaTrackConstraints.deviceId", "deviceId")}} или несколько <code>deviceId</code> в объекте {{domxref("MediaTrackConstraints")}} , переданном в {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}}.</p> + +<h2 id="Смотри_так_же">Смотри так же</h2> + +<ul> + <li><a href="https://mdn-samples.mozilla.org/s/webrtc-capturestill">Пробуем пример</a></li> + <li><a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill">Примеры на Github</a></li> + <li>{{domxref("Navigator.mediaDevices.getUserMedia()")}}</li> + <li>{{SectionOnPage("/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images","Использование изображений")}}</li> + <li>{{domxref("CanvasRenderingContext2D.drawImage()")}}</li> +</ul> diff --git a/files/ru/web/api/webrtc_api/using_data_channels/index.html b/files/ru/web/api/webrtc_api/using_data_channels/index.html new file mode 100644 index 0000000000..f8074830d4 --- /dev/null +++ b/files/ru/web/api/webrtc_api/using_data_channels/index.html @@ -0,0 +1,94 @@ +--- +title: Использование каналов данных в WebRTC +slug: Web/API/WebRTC_API/Using_data_channels +translation_of: Web/API/WebRTC_API/Using_data_channels +--- +<p>{{WebRTCSidebar}}{{draft}}</p> + +<p>Как только WebRTC соединение установлено, используя интерфейс {{domxref("RTCPeerConnection")}}, приложение в состоянии отправлять и получать медиаданные между двумя узлами в соединении. Но от WebRTC можно получить больше. В этом руководстве мы изучим то, как добавить канал данных в соединение, который будет использован для безопасной передачи произвольных данных (данных любого типа, в любом формате).</p> + +<div class="note"> +<p> Поскольку все компоненты WebRTC требуют использования кодирования, любые данные, передаваемые через <code>RTCDataChannel</code> автоматически защищаются, используя Datagram Transport Layer Security (<strong>DTLS</strong>). Смотри {{anch("Security")}} ниже для подробной информации.</p> +</div> + +<h2 id="Создание_канала_данных">Создание канала данных</h2> + +<p>Основной транспорт передачи данных, использующийся объектом типа {{domxref("RTCDataChannel")}} может быть создан двумя способами:</p> + +<ul> + <li>Позволить WebRTC создать транспорт и сообщить об этом удаленному узлу (вызвав у него событие типа {{event("datachannel")}} ). Это простой способ, и он подходит для многих случаев, но не достаточно гибок для широких нужд.</li> + <li>Написать свои скрипты по согласованию транспорта данных, и сигнализированию другому узлу о необходимости присоединения к новому каналу данных.</li> +</ul> + +<p>Разберем оба случая, начиная с первого, как с наиболее распространенного.</p> + +<h3 id="Автоматический_режим_согласования">Автоматический режим согласования</h3> + +<p>Зачастую, разработчик может позволить объекту соединения обработать согласование {{domxref("RTCDataChannel")}} соединения за него. Для этого нужно вызвать метод {{domxref("RTCPeerConnection.createDataChannel", "createDataChannel()")}} без определения значения свойства {{domxref("RTCDataChannelInit.negotiated", "negotiated")}}, или определить свойство значением <code>false</code>. Это автоматически активирует <code>RTCPeerConnection</code> на обработку согласования соединения за разработчика, вызывая событие создание канала данных у удаленного узла, связывая два узла вместе по сети.</p> + +<p>Вызов метода <code>createDataChannel()</code> немедленно возвращает объект типа <code>RTCDataChannel</code>. Подписываясь на событие {{domxref("RTCDataChannel.open_event", "open")}} , можно будет точно определить когда соединение успешно откроется.</p> + +<pre class="brush: js">let dataChannel = pc.createDataChannel("MyApp Channel"); + +dataChannel.addEventListener("open", (event) => { + beginTransmission(dataChannel); +}); +</pre> + +<h3 id="Ручной_режим_согласования">Ручной режим согласования</h3> + +<p>Для ручного согласования соединения, сначала необходимо создать новый объект типа {{domxref("RTCDataChannel")}}, используя метод {{domxref("RTCPeerConnection.createDataChannel", "createDataChannel()")}} объекта {{domxref("RTCPeerConnection")}}, определяя свойство {{domxref("RTCDataChannelInit.negotiated", "negotiated")}} в значение <code>true</code>. Это сигнализирует объекту соединения не пытыться согласовать соединение автоматически.</p> + +<p>Затем нужно согласовать соединение, используя веб сервер или иные средства коммуникации. Этот процесс должен сигнализировать удаленному узлу, что нужно создать собственный объект типа <code>RTCDataChannel</code> со свойством <code>negotiated</code>, установленным в значение <code>true</code>, используя тот же идентификатор канала {{domxref("RTCDataChannel.id", "id")}}. Это свяжет два объекта типа <code>RTCDataChannel </code>через объет типа <code>RTCPeerConnection</code>.</p> + +<pre class="brush: js">let dataChannel = pc.createDataChannel("MyApp Channel", { + negotiated: true +}); + +dataChannel.addEventListener("open", (event) => { + beginTransmission(dataChannel); +}); + +requestRemoteChannel(dataChannel.id);</pre> + +<p>В данном примере канал создается установкой значения свойства <code>negotiated</code> в <code>true</code>, затем вызывается функция <code>requestRemoteChannel()</code> , запуская согласование соединения для создания удаленного канала с тем же идентификатором как у локального канала. Таким образом создание каналов данных позволяет использовать различные свойства, создавая их декларативно, использьзуя одно и тоже значение идентификатора канала <code>id</code>.</p> + +<h2 id="Буферизация">Буферизация</h2> + +<p>Каналы данных WebRTC поддерживают буферизацию исходящих данных. Это работает автоматически. Несмотря на то, что нет способа контролировать размер буфера, вы можете узнать, сколько данных в настоящее время буферизуется, и вы можете выбрать уведомление о событии, когда в буфере начинают заканчиваться данные в очереди. Это облегчает написание эффективных подпрограмм, которые гарантируют, что всегда есть данные, готовые к отправке, без чрезмерного использования памяти или полного переполнения канала. </p> + +<p><strong><<<write more about using bufferedAmount, bufferedAmountLowThreshold, onbufferedamountlow, and bufferedamountlow here>>></strong></p> + +<p>...</p> + +<h2 id="Ограничения_размеров_сообщений">Ограничения размеров сообщений</h2> + +<p>Для любых данных, передаваемых по сети, существуют ограничения по размеру. На фундаментальном уровне отдельные сетевые пакеты не могут быть больше определенного значения (точное число зависит от сети и используемого транспортного уровня). На уровне приложения, то есть в пределах {{Glossary("user agent", "user agent's")}} реализация WebRTC, в которой работает ваш код, реализует функции поддержки сообщений, размер которых превышает максимальный размер пакета на транспортном уровне сети.</p> + +<p>Это может усложнить ситуацию, поскольку вы не знаете, каковы ограничения по размеру для различных пользовательских агентов и как они реагируют на отправку или получение сообщения большего размера. Даже когда пользовательские агенты совместно используют одну и ту же базовую библиотеку для обработки данных протокола управления потоком (SCTP), могут существовать различия в зависимости от того, как используется библиотека. Например, и Firefox, и Google Chrome используют библиотеку <code>usrsctp</code> для реализации SCTP, но все еще существуют ситуации, в которых передача данных по <code>RTCDataChannel</code> каналу может завершиться сбоем из-за различий в том, как они вызывают библиотеку и обрабатывают ошибки, которые она возвращает.</p> + +<p>Когда два пользователя, использующие Firefox, обмениваются данными по каналу данных, ограничение размера сообщения намного больше, чем когда Firefox и Chrome обмениваются данными, потому что Firefox реализует устаревшую технику для отправки больших сообщений в нескольких сообщениях SCTP, чего нет в Chrome. Вместо этого Chrome увидит серию сообщений, которые он считает завершенными, и доставит их получающему <code>RTCDataChannel</code> каналу в виде нескольких сообщений</p> + +<p>Сообщения размером менее 16 КБ могут отправляться без проблем, поскольку все основные пользовательские агенты обрабатывают их одинаково.</p> + +<h3 id="Проблемы_с_большими_сообщениями">Проблемы с большими сообщениями</h3> + +<p>В настоящее время нецелесообразно использовать <code>RTCDataChannel </code>для сообщений размером более 64 КБ (16 КБ, если вы хотите поддерживать кросс-браузерный обмен данными). Проблема возникает из-за того факта, что SCTP - протокол, используемый для отправки и получения данных по <code>RTCDataChannel</code> - изначально был разработан для использования в качестве протокола сигнализации. Ожидалось, что сообщения будут относительно небольшими. Поддержка сообщений, превышающих размер сетевого уровня {{interwiki ("wikipedia", "Maximum unit unit", "MTU")}}, была добавлена в качестве запоздалой мысли, в случае, если сигнальные сообщения должны были быть больше, чем MTU. Эта функция требует, чтобы каждый фрагмент сообщения имел последовательные порядковые номера, поэтому они должны передаваться один за другим, без каких-либо других данных, чередующихся между ними.</p> + +<p>В конечном итоге это стало проблемой. Со временем различные приложения (в том числе внедряющие WebRTC) начали использовать SCTP для передачи больших и больших сообщений. В конце концов стало ясно, что когда сообщения становятся слишком большими, передача большого сообщения может блокировать все другие передачи данных в этом канале данных, включая критические сообщения сигнализации.</p> + +<p>Это станет проблемой, когда браузеры будут должным образом поддерживать текущий стандарт поддержки больших сообщений - флаг конца записи (EOR), который указывает, когда сообщение является последним в серии, которое следует рассматривать как одну полезную нагрузку. Это реализовано в Firefox 57, но еще не реализовано в Chrome (см. <a href="https://bugs.chromium.org/p/webrtc/issues/detail?id=7774">Chromium Bug 7774</a>). С поддержкой EOR полезная нагрузка <code>RTCDataChannel</code> может быть намного больше (официально до 256 КБ, но реализация Firefox ограничивает их колоссальным 1 ГБ). Даже при 256 кБ этого достаточно, чтобы вызвать заметные задержки при обработке срочного трафика.</p> + +<p>Чтобы решить эту проблему, была разработана новая система планировщиков потоков (обычно называемая «спецификацией данных SCTP»), позволяющая чередовать сообщения, отправленные в разных потоках, включая потоки, используемые для реализации каналов данных WebRTC. Это предложение <a href="https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata">предложение</a> все еще находится в черновой форме IETF, но после его реализации оно позволит отправлять сообщения практически без ограничений по размеру, поскольку уровень SCTP автоматически чередует лежащие в основе под-сообщения, чтобы обеспечить возможность получения данных каждого канала.</p> + +<p>Поддержка Firefox для ndata находится в процессе реализации. <span style="font-size: 1rem; letter-spacing: -0.00278rem;">Команда Chrome отслеживает реализацию поддержки ndata в</span><span style="font-size: 1rem; letter-spacing: -0.00278rem;"> </span><a href="https://bugs.chromium.org/p/webrtc/issues/detail?id=5696" style="font-size: 1rem; letter-spacing: -0.00278rem;">Chrome Bug 5696</a><span style="font-size: 1rem; letter-spacing: -0.00278rem;">.</span></p> + +<div class="originaldocinfo"> +<p>Большая часть информации в этом разделе частично основана на блоге <a href="https://lgrahl.de/articles/demystifying-webrtc-dc-size-limit.html">Demystifyijng WebRTC's Data Channel Message Size Limitations</a>, написанный Леннартом Гралем. Там он немного подробнее рассказывает, но поскольку браузеры были обновлены с тех пор, некоторые посты могут быть устаревшими. Кроме того, со временем поддержки будет становиться все больше, особенно после того, как EOR и поддержка ndata будут полностью интегрированы в основные браузеры.</p> +</div> + +<h2 id="Безопасность">Безопасность</h2> + +<p>Все данные, переданные с помощью WebRTC, зашифрованы на основе <a href="/en-US/docs/Web/Security/Transport_Layer_Security">Transport Layer Security</a> (TLS). Поскольку TLS используется для защиты каждого HTTPS-соединения, любые данные, которые вы отправляете по каналу данных, так же безопасны, как и любые другие данные, отправляемые или получаемые браузером пользователя. </p> + +<p>Поскольку WebRTC является одноранговым соединением между двумя пользовательскими агентами, данные никогда не проходят через веб-сервер или сервер приложений, что снижает возможность перехвата данных.</p> diff --git a/files/ru/web/api/webrtc_api/webrtc_basics/index.html b/files/ru/web/api/webrtc_api/webrtc_basics/index.html new file mode 100644 index 0000000000..d1a5b5bb61 --- /dev/null +++ b/files/ru/web/api/webrtc_api/webrtc_basics/index.html @@ -0,0 +1,350 @@ +--- +title: Основы WebRTC +slug: Web/API/WebRTC_API/WebRTC_basics +translation_of: Web/API/WebRTC_API/Signaling_and_video_calling +--- +<p>{{WebRTCSidebar}}</p> + +<p>{{Draft}}</p> + +<div class="summary"> +<p>После того, как вы понимаете <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Architecture" title="/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture">WebRTC architecture</a>, вы можете прочитать эту статью, которая сопроводит вас через создание кросс-браузерного RTC приложения. К концу этой документации, вы должны иметь рабочие каналы соединения равноправных узлов ЛВС и передачи данных средств массовой информации.</p> +</div> + +<h2 id="Полу-старое_содержание_из">Полу-старое содержание, из</h2> + +<h2 id="RTCPeerConnection">RTCPeerConnection</h2> + +<p>Материал здесь происходит от RTCPeerConnection; она может остаться здесь, или же может переместится в другое место.</p> + +<p><strong>Основы использования</strong><br> + Базовое использование RTCPeerConnection предполагает переговоры связь между локальной машиной и удаленной машиной один генерируя Session Description Protocol для обмена между ними. Вызывающая программа начинает процесс, отправив предложение на удаленное устройство, которое реагирует либо принять или отклонить запрос на соединение.</p> + +<p>Обе стороны (вызывающий и вызываемый абонент) необходимо настроить свои собственные экземпляры RTCPeerConnection, чтобы представить их конец соединения равноправных узлов ЛВС:</p> + +<pre class="brush: js notranslate">var pc = new RTCPeerConnection(); +pc.onaddstream = function(obj) { + var vid = document.createElement("video"); + document.appendChild(vid); + vid.srcObject = obj.stream; +} + +// функция помощник +function endCall() { + var videos = document.getElementsByTagName("video"); + for (var i = 0; i < videos.length; i++) { + videos[i].pause(); + } + + pc.<a href="#close()">close</a>(); + + +function error(err) { + endCall(); +} +</pre> + +<h3 id="При_инициализации_вызова">При инициализации вызова</h3> + +<p>Если вы один инициирующий вызов, вы будете использовать navigator.getUserMedia(), чтобы получить видеопоток, а затем добавить поток в RTCPeerConnection. Как только это было сделано, вызов RTCPeerConnection, чтобы создать предложение, настроить предложение, а затем отправить его на сервер, через соединение которое было создано.</p> + +<pre class="brush: js notranslate">// Получить список людей с сервера +// Пользователь выбирает список людей, чтобы установить соединение с нужным человеком +navigator.getUserMedia({video: true}, function(stream) { + // Добавление локального потока не вызовет onaddstream обратного вызова, + // так называют его вручную. + pc.onaddstream = e => video.src = URL.createObjectURL(e.stream); + pc.<a href="#addStream()">addStream</a>(stream); + + pc.<a href="#createOffer()">createOffer</a>(function(offer) { + pc.<a href="#setLocalDescription()">setLocalDescription</a>(offer, function() { + // send the offer to a server to be forwarded to the friend you're calling. + }, error); + }, error); +}); +</pre> + +<h3 id="Ответ_на_вызов">Ответ на вызов</h3> + +<p>На противоположном конце, друг получит предложение от сервера, используя любой протокол используется для того чтобы сделать это. После того, как предложение прибывает, {{domxref ("navigator.getUserMedia ()")}} вновь используется для создания потока, который добавляется к RTCPeerConnection. {{Domxref ("RTCSessionDescription")}} объект создается и установить в качестве удаленного описания с помощью вызова {{domxref ("RTCPeerConnection.setRemoteDescription ()")}}.</p> + +<p>Тогда ответ создается с помощью RTCPeerConnection.createAnswer () и отправляется обратно на сервер, который направляет его к вызывающему абоненту.</p> + +<pre class="brush: js notranslate">var offer = getOfferFromFriend(); +navigator.getUserMedia({video: true}, function(stream) { + pc.onaddstream = e => video.src = URL.createObjectURL(e.stream); + pc.<a href="#addStream()">addStream</a>(stream); + + pc.setRemoteDescription(new <span class="nx">RTCSessionDescription</span>(offer), function() { + pc.<a href="#createAnswer()">createAnswer</a>(function(answer) { + pc.<a href="#setLocalDescription()">setLocalDescription</a>(answer, function() { + // send the answer to a server to be forwarded back to the caller (you) + }, error); + }, error); + }, error); +}); +</pre> + +<p><strong>Ответ на вызов</strong></p> + +<p>На противоположном конце, человек получит предложение от сервера, используя любой протокол используется для того чтобы сделать это. После того, как предложение принято, navigator.getUserMedia () вновь используется для создания потока, который добавляется к RTCPeerConnection. объект создается и установить в качестве удаленного описания с помощью вызова {{domxref ("RTCPeerConnection.setRemoteDescription ()")}}.</p> + +<p>Тогда ответ создается с помощью RTCPeerConnection.createAnswer () и отправляется обратно на сервер, который направляет его к вызывающему абоненту.</p> + +<pre class="brush: js notranslate">// ПК был создан раньше, когда мы сделали первоначальное предложение +var offer = getResponseFromFriend(); +pc.<a href="#createAnswer()">setRemoteDescription</a>(new <span class="nx">RTCSessionDescription</span>(offer), function() { }, error);</pre> + +<h2 id="Old_content_follows!">Old content follows!</h2> + +<p>Все, что находится ниже этого пункта, потенциально устарело. Это по-прежнему находится в стадии рассмотрения и возможного включения в другие части документации, если они все еще актуальны.</p> + +<div class="note"> +<p><strong>Не используйте примеры на этой странице.</strong> Смотрите статью <a href="/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling">Signaling and video calling</a> для работы, актуальный пример с использованием WebRTC media.</p> +</div> + +<h3 id="Note">Note</h3> + +<p>Due to recent changes in the API there are many old examples that require fixing:</p> + +<ul> + <li><a href="http://louisstow.github.io/WebRTC/media.html">louisstow</a></li> + <li><a href="http://mozilla.github.io/webrtc-landing/">mozilla</a></li> + <li><a href="https://www.webrtc-experiment.com/">webrtc-experiment</a></li> +</ul> + +<p>The currently working example is:</p> + +<ul> + <li><a href="https://apprtc.appspot.com">apprtc</a> (<a href="https://github.com/webrtc/apprtc">source</a>)</li> +</ul> + +<p>Implementation may be inferred from the <a href="http://w3c.github.io/webrtc-pc/">specification</a>.</p> + +<p>This remainder of this page contains outdated information as <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1119285">noted on bugzilla</a>.</p> + +<h3 id="Shims">Shims</h3> + +<p>As you can imagine, with such an early API, you must use the browser prefixes and shim it to a common variable.</p> + +<pre class="brush: js notranslate">var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; +var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; +var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; +navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;</pre> + +<h3 id="RTCPeerConnection_2">RTCPeerConnection</h3> + +<p>This is the starting point to creating a connection with a peer. It accepts configuration options about ICE servers to use to establish a connection.</p> + +<pre class="brush: js notranslate">var pc = new RTCPeerConnection(configuration);</pre> + +<h3 id="RTCConfiguration"><strong><code>RTCConfiguration</code></strong></h3> + +<p>The {{domxref("RTCConfiguration")}} object contains information about which TURN and/or STUN servers to use for ICE. This is required to ensure most users can actually create a connection by avoiding restrictions in NAT and firewalls.</p> + +<pre class="brush: js notranslate">var configuration = { + iceServers: [ + {urls: "stun:23.21.150.121"}, + {urls: "stun:stun.l.google.com:19302"}, + {urls: "turn:numb.viagenie.ca", credential: "webrtcdemo", username: "louis%40mozilla.com"} + ] +}</pre> + +<p>Google runs a <a href="https://code.google.com/p/natvpn/source/browse/trunk/stun_server_list">public STUN server</a> that we can use. I also created an account at http://numb.viagenie.ca/ for a free TURN server to access. You may want to do the same and replace with your own credentials.</p> + +<h3 id="ICECandidate">ICECandidate</h3> + + + +<p>After creating the PeerConnection and passing in the available <a href="/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture#What_is_STUN.3F">STUN</a> and <a href="/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture#What_is_TURN.3F">TURN</a> servers, an event will be fired once the ICE framework has found some “candidates” that will allow you to connect with a peer. This is known as an ICE Candidate and will execute a callback function on <a href="/en-US/docs/Web/API/RTCPeerConnection.onicecandidate">PeerConnection#onicecandidate</a>.</p> + +<pre class="brush: js notranslate" lang="javascript">pc.onicecandidate = function (e) { + // candidate exists in e.candidate + if (!e.candidate) return; + send("icecandidate", JSON.stringify(e.candidate)); +};</pre> + +<p>When the callback is executed, we must use the signal channel to send the Candidate to the peer. On Chrome, multiple ICE candidates are usually found, we only need one so I typically send the first one then remove the handler. Firefox includes the Candidate in the Offer SDP.</p> + +<h3 id="Signal_Channel">Signal Channel</h3> + +<p>Now that we have an ICE candidate, we need to send that to our peer so they know how to connect with us. However this leaves us with a chicken and egg situation; we want PeerConnection to send data to a peer but before that we need to send them metadata…</p> + +<p>This is where the signal channel comes in. It’s any method of data transport that allows two peers to exchange information. In this article, we’re going to use <a href="http://firebase.com">FireBase</a> because it’s incredibly easy to setup and doesn't require any hosting or server-code.</p> + +<p>For now just imagine two methods exist: <code>send()</code> will take a key and assign data to it and <code>recv()</code> will call a handler when a key has a value.</p> + +<p>The structure of the database will look like this:</p> + +<pre class="brush: js notranslate" lang="json">{ + "": { + "candidate:": … + "offer": … + "answer": … + } +}</pre> + +<p>Connections are divided by a <code>roomId</code> and will store 4 pieces of information, the ICE candidate from the offerer, the ICE candidate from the answerer, the offer SDP and the answer SDP.</p> + +<h3 id="Offer">Offer</h3> + +<p>An Offer SDP (Session Description Protocol) is metadata that describes to the other peer the format to expect (video, formats, codecs, encryption, resolution, size, etc etc).</p> + +<p>An exchange requires an offer from a peer, then the other peer must receive the offer and provide back an answer.</p> + +<pre class="brush: js notranslate" lang="javascript">pc.createOffer(function (offer) { + pc.setLocalDescription(offer, function() { + send("offer", JSON.stringify(pc.localDescription); + }, errorHandler); +}, errorHandler, options);</pre> + +<h4 id="errorHandler"><strong><code>errorHandler</code></strong></h4> + +<p>If there was an issue generating an offer, this method will be executed with error details as the first argument.</p> + +<pre class="brush: js notranslate" lang="javascript">var errorHandler = function (err) { + console.error(err); +};</pre> + +<h5 id="options"><strong><code>options</code></strong></h5> + +<p>Options for the offer SDP.</p> + +<pre class="brush: js notranslate" lang="javascript">var options = { + offerToReceiveAudio: true, + offerToReceiveVideo: true +};</pre> + +<p><code>offerToReceiveAudio/Video</code> tells the other peer that you would like to receive video or audio from them. This is not needed for DataChannels.</p> + +<p>Once the offer has been generated we must set the local SDP to the new offer and send it through the signal channel to the other peer and await their Answer SDP.</p> + +<h3 id="Answer">Answer</h3> + +<p>An Answer SDP is just like an offer but a response; sort of like answering the phone. We can only generate an answer once we have received an offer.</p> + +<pre class="brush: js notranslate" lang="javascript">recv("offer", function (offer) { + offer = new SessionDescription(JSON.parse(offer)) + pc.setRemoteDescription(offer); + + pc.createAnswer(function (answer) { + pc.setLocalDescription(answer, function() { + send("answer", JSON.stringify(pc.localDescription)); + }, errorHandler); + }, errorHandler); +});</pre> + +<h3 id="DataChannel">DataChannel</h3> + +<p>I will first explain how to use PeerConnection for the DataChannels API and transferring arbitrary data between peers.</p> + +<p><em>Note: At the time of this article, interoperability between Chrome and Firefox is not possible with DataChannels. Chrome supports a similar but private protocol and will be supporting the standard protocol soon.</em></p> + +<pre class="brush: js notranslate" lang="javascript">var channel = pc.createDataChannel(channelName, channelOptions);</pre> + +<p>The offerer should be the peer who creates the channel. The answerer will receive the channel in the callback <code>ondatachannel</code> on PeerConnection. You must call <code>createDataChannel()</code> once before creating the offer.</p> + +<h4 id="channelName"><strong><code>channelName</code></strong></h4> + +<p>This is a string that acts as a label for your channel name. <em>Warning: Make sure your channel name has no spaces or Chrome will fail on <code>createAnswer()</code>.</em></p> + +<h4 id="channelOptions"><strong><code>channelOptions</code></strong></h4> + +<pre class="brush: js notranslate" lang="javascript">var channelOptions = {};</pre> + +<p>Currently these options are not well supported on Chrome so you can leave this empty for now. Check the <a href="http://dev.w3.org/2011/webrtc/editor/webrtc.html#attributes-7">RFC</a> for more information about the options.</p> + +<h4 id="Channel_Events_and_Methods">Channel Events and Methods</h4> + +<h5 id="onopen"><strong><code>onopen</code></strong></h5> + +<p>Executed when the connection is established.</p> + +<h5 id="onerror"><strong><code>onerror</code></strong></h5> + +<p>Executed if there is an error creating the connection. First argument is an error object.</p> + +<pre class="brush: js notranslate" lang="javascript">channel.onerror = function (err) { + console.error("Channel Error:", err); +};</pre> + +<h5 id="onmessage"><strong><code>onmessage</code></strong></h5> + +<pre class="brush: js notranslate" lang="javascript">channel.onmessage = function (e) { + console.log("Got message:", e.data); +}</pre> + +<p>The heart of the connection. When you receive a message, this method will execute. The first argument is an event object which contains the data, time received and other information.</p> + +<h5 id="onclose"><strong><code>onclose</code></strong></h5> + +<p>Executed if the other peer closes the connection.</p> + +<h4 id="Binding_the_Events"><strong>Binding the Events</strong></h4> + +<p>If you were the creator of the channel (meaning the offerer), you can bind events directly to the DataChannel you created with <code>createChannel</code>. If you are the answerer, you must use the <code>ondatachannel</code> callback on PeerConnection to access the same channel.</p> + +<pre class="brush: js notranslate" lang="javascript">pc.ondatachannel = function (e) { + e.channel.onmessage = function () { … }; +};</pre> + +<p>The channel is available in the event object passed into the handler as <code>e.channel</code>.</p> + +<h5 id="send"><strong><code>send()</code></strong></h5> + +<pre class="brush: js notranslate" lang="javascript">channel.send("Hi Peer!");</pre> + +<p>This method allows you to send data directly to the peer! Amazing. You must send either String, Blob, ArrayBuffer or ArrayBufferView, so be sure to stringify objects.</p> + +<h5 id="close"><strong><code>close()</code></strong></h5> + +<p>Close the channel once the connection should end. It is recommended to do this on page unload.</p> + +<h3 id="Media">Media</h3> + +<p>Now we will cover transmitting media such as audio and video. To display the video and audio you must include a <code><video></code> tag on the document with the attribute <code>autoplay</code>.</p> + +<h4 id="Get_User_Media">Get User Media</h4> + +<pre class="brush: js notranslate"><video id="preview" autoplay></video> + +var video = document.getElementById("preview"); +navigator.getUserMedia(constraints, function (stream) { + video.src = URL.createObjectURL(stream); +}, errorHandler);</pre> + +<p><strong><code>constraints</code></strong></p> + +<p>Constraints on what media types you want to return from the user.</p> + +<pre class="brush: js notranslate" lang="javascript">var constraints = { + video: true, + audio: true +};</pre> + +<p>If you just want an audio chat, remove the <code>video</code> member.</p> + +<h5 id="errorHandler_2"><strong><code>errorHandler</code></strong></h5> + +<p>Executed if there is an error returning the requested media.</p> + +<h4 id="Media_Events_and_Methods">Media Events and Methods</h4> + +<h5 id="addStream"><strong><code>addStream</code></strong></h5> + +<p>Add the stream from <code>getUserMedia</code> to the PeerConnection.</p> + +<pre class="brush: js notranslate" lang="javascript">pc.addStream(stream);</pre> + +<h5 id="onaddstream"><strong><code>onaddstream</code></strong></h5> + +<pre class="brush: js notranslate"><video id="otherPeer" autoplay></video> + +var otherPeer = document.getElementById("otherPeer"); +pc.onaddstream = function (e) { + otherPeer.src = URL.createObjectURL(e.stream); +};</pre> + +<p>Executed when the connection has been setup and the other peer has added the stream to the peer connection with <code>addStream</code>. You need another <code><video></code> tag to display the other peer's media.</p> + +<p>The first argument is an event object with the other peer's media stream.</p> diff --git a/files/ru/web/api/webrtc_api/протоколы/index.html b/files/ru/web/api/webrtc_api/протоколы/index.html new file mode 100644 index 0000000000..df618ab083 --- /dev/null +++ b/files/ru/web/api/webrtc_api/протоколы/index.html @@ -0,0 +1,38 @@ +--- +title: Введение в протоколы WebRTC +slug: Web/API/WebRTC_API/протоколы +translation_of: Web/API/WebRTC_API/Protocols +--- +<p>{{APIRef("WebRTC")}}{{draft}}</p> + +<p>В этой статье представлены протоколы, поверх которых построен WebRTC API.</p> + +<h2 id="ICE">ICE</h2> + +<p><a href="http://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment">Interactive Connectivity Establishment (ICE)</a> "Установка интерактивного подключения" представляет собой каркас, позволяющий вашему веб-браузеру соединяться с узлами. Есть много причин, почему прямое соединение от узла A к узлу B просто не будет работать. Оно должно обойти межсетевые экраны, которые будут препятствовать открытию соединений, дать вам уникальный адрес, если, как в большинстве ситуаций, ваше устройство не имеет публичного IP-адреса, и передавать данные через сервер, если ваш маршрутизатор не позволяет вам напрямую соединяться с узлами. ICE использует некоторые из следующих технических приёмов, описанных ниже, для достижения этой цели:</p> + +<h2 id="STUN">STUN</h2> + +<p><a href="http://en.wikipedia.org/wiki/STUN">Session Traversal Utilities for <u>NAT</u> (STU<u>N</u>)</a> (акроним в акрониме) это протокол для нахождения и определения вашего публичного адреса и любых ограничений в вашем маршрутизаторе, которые препятствуют прямому соединению с узлом.</p> + +<p>Клиент отправит запрос к STUN серверу в интернете, который ответит публичным адресом клиента и, доступен ли, или нет, клиент за NAT маршрутизатором.</p> + +<p><img alt="An interaction between two users of a WebRTC application involving a STUN server." src="https://mdn.mozillademos.org/files/6115/webrtc-stun.png" style="display: block; height: 378px; margin: 0px auto; width: 259px;"></p> + +<h2 id="NAT">NAT</h2> + +<p><a href="http://en.wikipedia.org/wiki/NAT">Network Address Translation (NAT)</a> используется для того, чтобы дать вашему устройству публичный IP-адрес. Маршрутизатор имеет публичный IP-адрес, а каждое устройство, подключённое к маршрутизатору имеет частный IP-адрес. Запросы будут транслированы от частного IP-адреса устройства к публичному IP-адресу маршрутизатора (с уникальным портом). Таким образом вам не нужен уникальный IP-адрес для каждого устройства, тем не менее, оно может быть обнаружено в интернете.</p> + +<p>Некоторые маршрутизаторы имеют ограничения на то, кто может подключаться к устройствам в сети. Это может означать, что даже если мы имеем публичный IP-адрес, найденный STUN сервером, никто не может создать соединение. В этой ситуации нам нужно обратиться к TURN.</p> + +<h2 id="TURN">TURN</h2> + +<p>Некоторые маршрутизаторы, использующие NAT применяют ограничение, называемое "Симметричный NAT" (Symmetric NAT). Это означает, что маршрутизатор будет принимать соединения только от узлов, к которым вы ранее подключились.</p> + +<p><a href="http://en.wikipedia.org/wiki/TURN">Traversal Using Relays around NAT (TURN)</a> предназначен для обхода ограничения "Симметричный NAT" путём открытия соединения с TURN сервером и ретрансляции всей информации через этот сервер. Вы создадите соединение с TURN сервером и сообщите всем узлам слать пакеты этому серверу, которые затем будут переправлены вам. Очевидно, что они приходят с некоторыми накладными расходами, поэтому это используется, только если нет других альтернатив.</p> + +<p><img alt="An interaction between two users of a WebRTC application involving STUN and TURN servers." src="https://mdn.mozillademos.org/files/6117/webrtc-turn.png" style="display: block; height: 297px; margin: 0px auto; width: 295px;"></p> + +<h2 id="SDP">SDP</h2> + +<p><a href="http://en.wikipedia.org/wiki/Session_Description_Protocol">Session Description Protocol (SDP)</a> - это стандарт для описания мультимедийного контента соединения, как например: разрешение, форматы, кодеки, шифрование и т.д. Так, чтобы оба узла могли понять друг друга, после того как данные начнут передаваться. Это, в сущности, метаданные, описывающие содержимое, а не медиа контент сам по себе.</p> diff --git a/files/ru/web/api/webrtc_api/связь/index.html b/files/ru/web/api/webrtc_api/связь/index.html new file mode 100644 index 0000000000..7c4f173c05 --- /dev/null +++ b/files/ru/web/api/webrtc_api/связь/index.html @@ -0,0 +1,70 @@ +--- +title: WebRTC подключение +slug: Web/API/WebRTC_API/связь +translation_of: Web/API/WebRTC_API/Connectivity +--- +<p>{{WebRTCSidebar}}{{draft}}</p> + +<p>Теперь, когда мы рассмотрели протоколы по отдельности, мы можем сложить их вместе. Эта статья описывает, как различные связанные с WebRTC протоколы взаимодействуют друг с другом для того, чтобы создать соединение и передать данные и/или медиа между узлами.</p> + +<div class="note"> +<p>Эта страница требует серьёзной переделки для структурной целостности и полноты содержания. Много информации здесь - это хорошо, но организация являет собой путаницу, поскольку сейчас являет собой вид "местности разгрузки"(dumping ground).</p> +</div> + +<h2 id="Что_такое_ПредложениеОтвет_и_Канал_сигнализации">Что такое Предложение/Ответ и Канал сигнализации?</h2> + +<p>К сожалению, WebRTC не может создавать соединения без какого-либо сервера посередине. Мы называем его "каналом сигнализации". Это любого рода канал связи для обмена информацией перед установкой соединения, будь то электронная почта, почтовая открытка или почтовый голубь... Зависит от вас.<br> + </p> + +<p>Информация, которой мы должны обменяться - это "предложение" и "ответ", которые содержат SDP, упомянутую ниже.</p> + +<p>Узел A, тот кто будет инициатором соединения, создаст "предложение". Затем отправит это "предложение" узлу B, используя выбранный "сигнальный канал". Узел B получит "предложение" от "сигнального канала" и создаст "ответ". Затем отправит его обратно узлу A посредством "сигнального канала".</p> + +<h3 id="Описания_сессии">Описания сессии</h3> + +<p>Конфигурация конечной точки WebRTC-соединения называется <strong>"описание сессии"(session description)</strong>. Описание включает информацию о типе посылаемого медиа, его формате, используемом протоколе передачи, IP-адресе и порте конечной точки, и другую информацию, необходимую для описания конечной точки передачи медиа. Эта информация обменивается и хранится с помощью <strong>"протокола описания сессии". Session Description Protocol</strong>({{Glossary("SDP")}}). Если вы хотите подробную информацию о формате данных SDP, вы можете найти её в {{RFC(2327)}}.</p> + +<p>Когда пользователь запускает WebRTC вызов другого пользователя, создаётся специальное описание, называемое <strong>"предложение"(offer)</strong>. Это описание включает всю информацию для соединения, о предлагаемой конфигурации вызывающего абонента. Получатель затем откликается <strong>"ответом"(answer)</strong>, являющимся описанием его конца соединения. Таким образом, оба устройства разделяют друг с другом информацию, необходимую для того, чтобы обмениваться медиа данными. Этот обмен обрабатывается с помощью "интерактивного создания подключения". Interactive Connectivity Establishment{{Glossary("ICE")}}. ICE - протокол, который позволяет двум устройствам использовать посредника для обмена "предложениями"(offers) и "ответами"(answers), даже если эти два устройства разделены механизмом "преобразования сетевых адресов". ({{Glossary("NAT")}}(Network Address Translation).</p> + +<p>Каждый узел, тогда, держит два описания под рукой: <strong>локальное описание (local description)</strong>, описывающее себя и <strong>удалённое описание(remote description)</strong>, описывающее другой конец соединения.</p> + +<p>Процесс "предложение/ответ"(offer/answer) выполняется как, когда соединение впервые устанавливается, так и в любой момент, когда формат соединения или другая конфигурация нуждается в изменении. Независимо от того, является ли это новым соединением, или реконфигурированием существующего, это основные шаги, которые должны произойти для обмена "предложением"(offer) и "ответом"(answer). Пропустим ICE-слой на данный момент:</p> + +<ol> + <li>Вызывающий вызывает {{domxref("RTCPeerConnection.createOffer()")}} для создания "предложения"(offer)</li> + <li>Вызывающий вызывает {{domxref("RTCPeerConnection.setLocalDescription()")}} для установки этого "предложения" как локального описания (то есть описания локального конца соединения).</li> + <li>Вызывающий использует сигнальный сервер для передачи "предложения" к требуемому получателю вызова.</li> + <li>Получатель получает "предложение" и вызывает {{domxref("RTCPeerConnection.setRemoteDescription()")}} для записи его, как удалённого описания(описания другого конца соединения).</li> + <li>Получатель делает всякую настройку, которую должен сделать для его конца соединения, включая добавления исходящих потоков для соединения.</li> + <li>Получатель затем создаёт "ответ"(answer) посредством вызова {{domxref("RTCPeerConnection.createAnswer()")}}.</li> + <li>Получатель вызывает {{domxref("RTCPeerConnection.setLocalDescription()")}}, чтобы установить "ответ"(answer) в качестве локального описания. Получатель теперь знает конфигурацию обоих концов соединения.</li> + <li>Получатель использует сигнальный сервер для отправки "ответа"(answer) вызывающему.</li> + <li>Вызывающий получает "ответ"(answer).</li> + <li>Вызывающий вызывает {{domxref("RTCPeerConnection.setRemoteDescription()")}} для установки "ответа"(answer) как удалённого описания для его конца соединения. Теперь известна конфигурация обоих узлов. Медиа начинает течь в соответствии с настройками.</li> +</ol> + +<h3 id="Рассматриваемые_и_текущие_описания">Рассматриваемые и текущие описания</h3> + +<p>Спускаясь на один шаг глубже в процесс, мы находим, что localDescription и remoteDescription, свойства, возвращаемые эти двумя описаниями, не так просты, как выглядят. Потому что во время повторных переговоров(перезаключения) (renegotiation), "предложение"(offer) может быть отклонено, поскольку оно предлагает несовместимый формат. Необходимо, чтобы каждая конечная точка имела возможность предложить новый формат, но не переключаться на него, пока он не принят другим узлом. По этой причине, WebRTC использует <em>"рассматриваемые"</em> и <em>"текущие"</em> "описания".</p> + +<p><strong>"Текущее описание"(current description)</strong> (которое возвращается свойствами {{domxref("RTCPeerConnection.currentLocalDescription")}} и {{domxref("RTCPeerConnection.currentRemoteDescription")}}) представляет собой описание, в данный момент, фактически используемое соединением. Это самое недавнее соединение, которое обе стороны полностью согласились использовать.</p> + +<p><strong>"Рассматриваемое описание"(pending description)</strong> (возвращаемое {{domxref("RTCPeerConnection.pendingLocalDescription")}} и {{domxref("RTCPeerConnection.pendingRemoteDescription")}}) указывает на то описание, которое в настоящий момент находится на рассмотрении после вызова setLocalDescription() или setRemoteDescription(), соответственно.</p> + +<p>При чтении описания (возвращаемого {{domxref("RTCPeerConnection.localDescription")}} и {{domxref("RTCPeerConnection.remoteDescription")}}), возвращаемым значением является значение pendingLocalDescription/pendingRemoteDescription, если есть рассматриваемое описание (то есть, рассматриваемое описание не null). В противном случае, возвращается текущее описание (currentLocalDescription/currentRemoteDescription).</p> + +<p>При изменении описания путём вызова setLocalDescription() или setRemoteDescription(), указанное описание устанавливается как "рассматриваемое описание"(pending description), и WebRTC-слой начинает оценивать, действительно ли это приемлемо. После того, как предложенное описание было согласовано, значение currentLocalDescription или currentRemoteDescription изменяется на "рассматриваемое описание"(pending description), и pending description устанавливается снова в null, указывая, что "отложенного описания"(pending description) не существует.</p> + +<div class="note"> +<p>pendingLocalDescription содержит не только "предложение" или "ответ" на стадии рассмотрения, но и каких-либо ICE-кандидатов, которые уже были собраны с тех пор, как "предложение" или "ответ" были созданы. Подобным образом, pendingRemoteDescription включает любых удалённых ICE-кандидатов, которые были предоставлены вызовами {{domxref("RTCPeerConnection.addIceCandidate()")}}.</p> +</div> + +<p>Смотрите отдельные статьи по этим свойствам и методам для большей конкретики.</p> + +<h2 id="Что_такое_ICE-кандидат">Что такое ICE-кандидат?</h2> + +<p>В дополнение к обмену информацией о медиа(обсуждённой выше в offer/answer и SDP), узлы должны обменяться информацией о сетевом соединении. Об этом известно как о ICE-кандидате и деталях доступных методов, которыми узел может общаться (непосредственно или через TURN-сервер).</p> + +<h2 id="Весь_обмен_в_сложной_схеме">Весь обмен в сложной схеме</h2> + +<p><a href="https://hacks.mozilla.org/wp-content/uploads/2013/07/webrtc-complete-diagram.png"><img alt="A complete architectural diagram showing the whole WebRTC process." src="https://mdn.mozillademos.org/files/6119/webrtc-complete-diagram.png" style="display: block; height: 559px; margin: 0px auto; width: 641px;"></a></p> |