WebRTC handbook
Введение
WebRTC - это набор спецификаций, описывающих трансляцию аудио\видео и сообщений (data streams) в режиме реального времени. Каждый раз обращаться к WebRTC как к набору спецификаций не слишком удобно, поэтому дальше в тексте я буду писать WebRTC, спецфикация, или просто протокол, в зависимости от контекста.
Основные действующие лица:
- WebRTC браузер, он же WebRTC User Agent (WebRTC UA) - это приложение, которое умеет WebRTC на уровне протокола и реализует правильное Javascript API
- WebRTC не браузер, известный так же как WebRTC приложение (WebRTC device\WebRTC native application) - это приложение, которое умеет WebRTC на уровне протокола и не предоставляет никакого Javascript API
- WebRTC конечная точка (WebRTC endpoint) - это приложение, которое умеет WebRTC на уровне протокола
- WebRTC-совместимая конечная точка (WebRTC-compatible endpoint) - это приложение, которое умеет WebRTC на уровне протокола, но не полностью, т.е. реализует только часть спецификации
- WebRTC шлюз - это приложение, которое транслирует WebRTC трафик WebRTC несовместимым клиентам
Базовая схема взаимодействия:
+-----------+ +-----------+
| Web | | Web |
| | Signaling | |
| |-------------| |
| Server | path | Server |
| | | |
+-----------+ +-----------+
/ \
/ \ Application-defined
/ \ over
/ \ HTTP/Websockets
/ Application-defined over \
/ HTTP/Websockets \
/ \
+-----------+ +-----------+
|JS/HTML/CSS| |JS/HTML/CSS|
+-----------+ +-----------+
+-----------+ +-----------+
| | | |
| | | |
| Browser | ------------------------- | Browser |
| | Media path | |
| | | |
+-----------+ +-----------+
Рис 1: Browser RTC Trapezoid (не берусь это переводить). Источник
На данной схеме изображено взаимодействие двух браузеров, но их можно заменить любой WebRTC-совместимой конечной точкой.
Протокол опиcывает два уровня взаимодействия:
- сигнальный (signaling path)
- медиа (media path)
Спецификация не накладывает практически никаких ограничений на реализацию сигнального уровня, дается только детальное описание сигнальных датаграмм (SDP) и их последовательность, но каким образом они будут доставлены от клиента клиенту, а так же добавление новых сигнальных сообщений, остается на откуп разработчикам. Медиа уровень служит для передачи данных\аудио\видео преимущественно по udp. Задачей сигнального уровня является наладка и открытие медиа канала(-ов), а так же управление ими. Допустим вполне нормальный кейс, когда открывается медиа канал (только data streams), после переписки один из клиентов звонит другому, а тот в свою очередь шарит ему свой экран. Так вот для реализации подобного сценария совершенно не нужно создавать новую сессию, а можно воспользоваться уже существующей, добавив новые медиа потоки. Подобные изменения осуществляются как раз на сигнальном уровне, когда информация о новых медиа-потоках синхронизируется между конечными точками и применяется.
Спецификация состоит из двух частей:
- описание протокола (IETF спецификации)
- описание Javascript API (W3C спецификации)
Здесь будет рассмотрена только IETF часть спецификации.
В целом предназначение WebRTC упростить и унифицировать разработку приложений, работающих с потоковыми данными. Протокол имеет peet-to-peer природу, где каждая из сторон имеет равные права и нет разницы, что за приложение на другой стороне соединения: другой браузер, медиа сервер, медиа гейтвей или мобильный клиент. Это означает, что при звонке от одного клиента к другому им нужен только сигнальный сервер для обмена SDP датаграммами.
Модель взаимодействия в реальном времени для браузерных приложений не подразумевает, что браузер будет обладать всей функциональностью, достаточной для того, чтобы выступать в роли телефона и юнита конференц-связи; цель в том чтобы предоставить web-приложению функционал, достаточный, чтобы работая совместно с бэкендом, реализовать функционал телефонии, конференц-связи и т.д.
Однако, даже та функциональность, которой наделен WebRTC совместимый браузер, позволяет двум браузерам обмениваться потоковыми данными напрямую, с минимальной поддержкой со стороны бекенда (только сигнальный уровень).
+------------------------+ On-the-wire
| | Protocols
| Servers |--------->
| |
| |
+------------------------+
^
|
|
| HTTP/
| Websockets
|
|
+----------------------------+
| Javascript/HTML/CSS |
+----------------------------+
Other ^ ^RTC
APIs | |APIs
+---|-----------------|------+
| | | |
| +---------+|
| | Browser || On-the-wire
| Browser | RTC || Protocols
| | Function|----------->
| | ||
| | ||
| +---------+|
+---------------------|------+
|
V
Native OS Services
Рис 2: WebRTC совместимый браузер.
Браузер является WebRTC совместимым если поддерживает:
- Транспорт данных (data transport) - TCP, UDP и средства для безопасной установки соединений между участниками обмена, а так же функциональность, позволяющую решить когда отправлять данные: контроль переполнения\загруженности, оценка пропускной способности и т.д.
- Кадрирование данных (data framing) - RTP и другие форматы, использующиеся в качестве контейнеров, а так же поддержку их функций по обеспечению конфиденциальности и целостности.
- Фоматы данных - описанный спецификацией набор audio\video кодеков, а так же форматов для передачи данных и шаринга документов. Плюс механизм, позволяющий описать поддерживаемые форматы другому клиенту, при установлении сессии.
- Управление соединением - установка соединения, соглашение по форматам данных, изменение форматов данных для установленного соединения.
- Представление и управление (presentation and control) - механизмы обеспечивающие предсказуемость и наглядность работы с потоковыми данными для клиента, т.е. браузер должен предоставить возможность web-приложению таким образом организовать работу с потоковыми данными, чтобы это было прозрачно для пользователя, чтобы он в любой момент времени понимал, куда\откуда и какие потоковые данные передаются, а так же предоставить возможность контролировать этот процесс, т.е. пользователь может отреагировать на соединение (принять\отклонить) при этом он должен иметь всю необходимую информацию для этого (кто звонит, например), а так же сам инициировать соединение. Так же пользователь должен иметь возможность самому решить какие потоковые данные передавать, а какие нет.
- Функции поддержки локальных систем (local system support functions) - механизмы локальной аутентификации и авторизации, доступ к системным вызовам ОС, эхо-подавление и возможность локальной записи медиаданных.
Первые три пункта описывают инфраструктуру медиа-транспорта. Остальные три пункта описывают медиа-сервис. Как минимум первые пять пунктов браузер должен поддерживать, чтобы быть WebRTC совместимым.
Транспорт данных
Под транспортом данных понимается отправка\получение данных через сетевые интерфейсы на обоих концах соединения, а так же взаимодействие с промежуточными сервисами, ретранслирующими данные без внесения изменений, например TURN серверы. А так же включает функционал по контролю перегрузки/блокирования канала, для прекращения передачи данных. WebTRC клиенты должны поддерживать работу с транспортными протоколами описанными здесь.
Кадрирование данных и безопасность
Транспортом для медиа-данных является RTP протокол. Однако, поддержка защищенного аналога SRTP ОБЯЗАТЕЛЬНА для всех WebRTC клиентов.
Более подробно работа с RTP\SRTP в рамках WebRTC описана здесь. Требования по безопасности WebRTC соединений описаны здесь, описание механизмов, реализующих эти требования, приведены здесь. Каждый WebRTC клиент (endpoint) должен поддерживать спецификации по безопасности.
Форматы данных
Данная спецификация предоставляет возможность клиентам договориться о форматах для каждого из медиа-потоков (видео\аудио), согласовав форматы из списка поддерживаемых на каждом из клиентов. При этом, чтобы такая договоренность в целом была достижима, определены минимальные требования по поддержке аудио-кодеков и видео-кодеков. При этом разработчики могут добавить поддержку любых дополнительных форматов и кодеков помимо минимума, описанного в спецификации.
Управление соединением
Методы, механизмы и требования по установке, поддержке и завершению соединений это обширная область, которая спроектирована таким образом, чтобы обеспечить, как совместимость, так и свободу для инноваций.
Основные принципы:
- Процесс согласования WebRTC соединений должен реализовывать ту же модель SDP запросов\ответов как в SIP, в связи с этим SIP сервер легко использовать в качестве сигнального сервера для WebRTC.
- Должна быть возможна поддержка старых (legacy) SIP устройств, которые поддерживают ICE, RTP и SDP механизмы, кодеки и механизмы безопасности без использования медиа-гейтвея. Возможно дополнительно понадобиться наличие сигнального сервиса, для согласования сингальных схем между WEB и SIP клиентами.
- Когда новый кодек определен и SDP для него описан рабочей группой MMUSIC (MMUSIC WG) - никакой дополнительной стандартизации не нужно для использования его в браузерах. Т.е. добавление новых кодеков со своими специфичными SDP параметрами никак не меняет API между браузером и web-приложением. Как только браузер начинает поддерживать новый кодек, старые приложение, написанные до этого момента, должны автоматически получить возможность использовать этот кодек, когда потребуется без изменений в JS коде.
Подробнее о браузероном API и управлении соединениями написано здесь. Все WebRTC браузеры должны реализовывать эту спецификацию. Другие WebRTC-приложения должны так же реализовывать механизмы описанные в данной спецификации (Bundle, RTCP-mux, Trickle ICE), кроме Javascript API.
Представление и управление
Самой важной частью управления является пользовательское управление взаимодействием браузера с IO устройствами и каналами коммуникации. Очень важно, чтобы пользователь понимал куда и кому его аудио\видео\текст транслируются и по какой причине. Данные механизмы описаны в peer connection API и в media capture API. Реализация этих спецификаций обязательна для WebRTC браузеров.
Функции поддержки локальных систем
Это функции напрямую влияющие на пользовательское взаимодействие с приложением, алгоритмы реализации которых не нуждаются в координации между клиентами. К локальным функциям относятся эхо-подавление, управление громкостью, управление камерой (настройка фокуса, например), различные видео-фильтры и т.д.
Отдельные части системы могут иметь сходные свойства, например:
- Эхо-подавление
- Настройки приватности, например, если разрешен удаленный доступ к камере, то приложение должно уведомить клиента кто использует его камеру и предоставить возможность отменить данное разрешение
- Автоматическая нормализация аудио-потоков
Требования для WebRTC систем по работе с аудио описаны здесь. Рекомендуемое API контроля локальных устройств - здесь.
Соображения о безопасности
Безопасность web-приложений работающих с потоковыми данными в реальном времени можно разделить на несколько частей:
- Безопасность компонент - браузеры, мобильные клиенты и все вовлеченные в процесс сервисы.
- Безопасность коммуникационных каналов.
- Безопасность идентификации - когда каждый из участников является тем за кого себя выдает.
Данная тема раскрывается в следующих спецификациях:
JSEP.
JSEP(Javascript Session Establishment Protocol) описывает как использовать интерфейс WEBRTC RTCPeerConnection для установки, управления и завршения мультимедиа сессии. JSEP позволяет полностью контролировать состояние сигнального уровня (сигнальную стейт-машину, если хотите) из Javascript.
Общий дизайн JSEP
Основная идея стоящая за установкой WebRTC соединения заключается в том, чтобы полностью определить и контролировать медиа-уровень, при этом оставив сингальный уровень приложению настолько, насколько это возможно. Профит в том, что разные приложения смогут выбрать различные протоколы, например SIP или Jingle, или что-то собственное, например, для реализации новых сценариев, или для упрощения встраивания WebRTC в существующую архитектуру.
Javasript Session Establishment Protocol (JSEP) максимально исключает браузер из основного потока сингалинга, который полностью контролирует Javascript через два интерфейса:
- работа с датаграммами сессии (создание\отправка\получение\применение)
- работа с ICE стейт-машиной
В данной спецификации JSEP описывается на примере взаимодействия двух браузеров, но на самом деле это может быть взаимодействие браузера с любой WebRTC-совместимой конечной точкой. Эта подмена незаметна для браузера, т.к. он просто следует инструкциям, получаемым через API.
Работа с датаграммами сессии в рамках JSEP проста и понятна. Когда необходим запрос\ответ обмен (offer\answer exchange) инициирующая сторона вызывает createOffer()
API для получения нового запроса. Приложение может опционально изменить полученный запрос, а затем использовать его для настройки своей локальной конфигурации посредством setLocalDescription()
API. После этого запрос отправляется адресату через сингальный сервер. Далее принимающая сторона получает этот запрос и настраивает удаленную конфигурацию с помощью setRemoteDescripton()
API.
Для завершения запрос\ответ обмена принимающей стороне необходимо создать ответ с помощью createAnswer()
API, применить его локально с помощью setLocalDescription()
API и отправить ответ обратно инициатору через сигнальный сервер. После того как инициатор получил ответ, он устанавливает его посредствам setRemoteDescription()
API. Начальная настройка завершена. Но этот процесс может быть повторен для дополнительных запросов\ответов.
В соответствие с ICE RFC5245, JSEP разделяет ICE стейт-машину от стейт-машины сигнального уровня (запрос\ответ обмен), ICE стейт-машина полностью управляться браузером, т.к. только браузер знает о кандидатах и обладает всей полнотой транспортной информации. Подобное разделение дает дополнительную гибкость; для протоколов отделяющих датаграммы сессии от транспорта, например Jingle, датаграмма сессии может быть отправлена сразу, а транспортная информация - когда станет доступна. Для протоколов не поддерживающих такой механизм, например SIP, информация может быть использована в аггрегированном виде. Возможность независимого обмена транспортной информацией позволяет ускорить настройку ICE и DTLS, т.к. ICE проверки могут начаться как только появится транспортная информация, не дожидаясь пока она станет доступна полностью.
Подобная абстракция сигнального уровня, означает, что приложению необходимо принимать непосредственное участие в сингальном обмене. Не смотря на то, что приложению необязательно понимать состав датаграмм сессии, чтобы начать соединение, оно должно вызывать нужные API в нужые моменты времени, а так же конвертировать датаграммы сессии и ICE информацию в сообщения выбранного сигнального протокола.
Этот процесс можно упростить, предоставив Javascript библиотеку, которая прячет всю эту сложность от разработчиков; библиотека может реализовывать конкретный сигнальный протокол вместе со стейт-машиной и сериализующим кодом, предоставляя более высокий уровень абстракции, соединение-ориентированный интерфейс для разработчика. Например, есть библиотеки адаптирующие JSEP API под SIP или XMPP. Однако, прямое использование JSEP дает больше гибкости и контроля квалифицированным разработчикам, при этом менее опытным разработчикам не придется осваивать дополнительный уровень абстракции.
Другие рассматривавшиеся подходы
В процессе работы над JSEP было рассмотрено несколько подходов, помимо того, который описывает JSEP. Одним из них был подход, при котором предполагалось наличие своей легковесной реализации сингального протокола, что позволило бы вместо сессионных датаграмм принимать и отправлять сообщения этого протокола. Но несмотря на то, что данный подход предоставляет более высокоуровневое API, он вынуждает браузер напрямую учавствовать в установке соединения и хранить состояние стейт-машины, на случай перезагрузки страницы.
Второй подход предполагал отделение настроек медиа-потоков от сессионных датаграмм, вместо этого предоставив API позволяющее контролировать каждую из компонент соединения отдельно, т.е. сессионную часть браузер бы брал на себя, а Javascript контролировал только media-потоки. Но этот подход был так же отвергнут, т.к. приводил к неоправданному усложению API и к гораздо более сложным сценариям работы с медиа-подсистемой на стороне клиента.
Так же рассматривался вариант, подразумевающий оставление базового датаграмма-ориентированного API, при этом убрав генерацию запросов\ответов (createOffer\createAnswer
) из сферы ответственности браузера. Вместо этого предполагалось использовать getCapabilities
API, предоставляющее приложению всю необходимую информацию для формирования сессионных датаграмм. Этот подход так же увеличивал сложность реализации web-приложений, т.к. ему теперь приходилось понимать сессионные датаграммами и уметь их формировать и валидировать, при этом в JSEP web-приложение чаще всего понятия не имеет что лежит в сессионных датаграммах, которыми она обменивается с другой стороной для установки соединения. Поэтому данный подход так же был отвергнут, к тому же в JSEP никто не ограничивает приложения в возможности создавать свои кастомные сессионные датаграммы, используя createOffer\createAnswer
в качестве getCapablilities
API, предоставляющем информацию о возможностях системы.
Сигнальная модель
JSEP не определяет никакую сигнальную модель или стейт-машину, кроме базового механизма обмена сессионными датаграммами, как описано в "An Offer/Answer Model with the Session Description Protocol (SDP)" RFC3264, чтобы оба участника знали как вести себя во время сессии. JSEP предоставляет механизмы создания запросов и ответов, а так же применение их к сессии. Однако, браузер исключен из механизма обмена сигнальными датаграммами. Эти задачи польностью находятся под контролем приложения, принимающего решения какие запросы\ответы и когда браузеру применять.
+-----------+ +-----------+
| Web App |<--- App-Specific Signaling -->| Web App |
+-----------+ +-----------+
^ ^
| SDP | SDP
V V
+-----------+ +-----------+
| Browser |<----------- Media ------------>| Browser |
+-----------+ +-----------+
Рис 3: Сигнальная модель JSEP. Источник
Сессионные датаграммы и стейт-машина
Чтобы настроить медиа-уровень пользовательскому агенту нужно, понимать, что умеет принимать противоположная сторона, а так же сообщить ей, что он может принимать сам. Это осуществляется с помощью обмена сессионными датаграммами в запросах и ответах, далее будут описаны детали этого процесса.
Применение сессионной датаграммы локально или удаленно изменяет значение этой датаграммы. Например, список кодеков, отправленный противоположной стороне, описывает, что локальная сторона собирается получать, который после пересечения со списком поддерживаемых кодеков на противоположной стороне, определяет, что удаленная сторона собирается отравлять. Однако, не все параметры следуют этому правилу; например, DTLS-SRTP параметры "Framework for Establishing a Secure Real-time Transport Protocol (SRTP) and Security Context Using Datagram Transport Layer Security (DTLS)" RFC5763. Подобные параметры описывают какой сертификат локальная сторона будет использовать для DTLS, при этом противоположная сторона должна это принять, не имея возможности поменять значения параметров.
Кроме того различные RFC накладывают свои ограничения на формат запросов и ответов. Например, в запросе может быть указано произвольное множество медиа-потоков (m= секций), но ответ должен содержать ровно столько же медиа-потоков, сколько было в запросе.
Но несмотря на то, что медиа-параметры становятся известны только после завершенного запрос\ответ обмена, всегда есть возможность, для инициатора получить медиа-поток, сразу поле отправки запроса, но до получения ответа. Чтобы правильно обработать входящий медиа-поток в подобной ситуации, обарботчик медиа на инициирующей стороне должен быть в курсе деталей запроса, до того как придет ответ.
Поэтому для того, чтобы правильно работать с сессионными датаграммами пользовательский агент должен:
- Знать чья это датаграмма (локальная или удаленная).
- Знать является ли датаграмма запросом или ответом.
- Позволять указать ответ независомо от вопроса.
В JSEP это реализуется с помощью предоставления методов setLocalDescription
и setRemoteDescription
, плюс объекты датаграмм содержат поле type
, где указан тип датаграммы. Это позволяет реализовать все требования приведенные выше, для обоих сторон. Инициатор сначала вызывает setLocalDescripton(sdp[offer])
, а потом setRemoteDescription(sdp[answer])
, адресат, же сначала вызывает setRemoteDescription(sdp[offer])
, а потом setLocalDescription(sdp[answer])
.
JSEP так же позволяет ответ считать предварительным на стороне приложения. Предварительные ответы предоставляют возможность ответчику, передать минимальный набор параметров, необходимых для создания сессии, при этом оставив финальный ответ на потом. Концепт финального ответа, очень важен для модели запрос\ответ; когда подобный ответ получен, все дополнительные ресурсы, аллоцированные вызывающей стороной, могут быть освобождены, т.к. точная конфигурация сессии определена. Ресурсами могут быть экстра ICE компоненты, TURN кандидаты или видео декодеры. Предварительные ответы, напротив, не приводят к освобождению ресурсов, поэтому несколько непохожих предварительных ответов, может быть получено в рамках открытия сессии.
В RFC3264 на сигнальный уровень накладывается ограничение, что для любой сессии должно быть не больше одного неотвеченного запроса, однако на уровне медиа-стека, новый запрос может быть создан в любой момент. Например, если использовать SIP для сигналинга, если один запрос отправлен, затем отменен спомощью SIP CANCEL, другой запрос может быть создан, даже если ответ на первый запрос так и не пришел. Поэтому JSEP медиа-уровень может предоставить новый запрос через createOffer()
метод, когда бы приложение этого не пожелало. Противоположная сторона может прислать ноль или больше предварительных ответов и, наконец, завершить запрос-ответ обмен отправкой финального ответа.
Ниже приведена стейт-машина, описывающая этот процесс.
setRemote(OFFER) setLocal(PRANSWER)
/-----\ /-----\
| | | |
v | v |
+---------------+ | +---------------+ |
| |----/ | |----/
| | setLocal(PRANSWER) | |
| Remote-Offer |------------------- >| Local-Pranswer|
| | | |
| | | |
+---------------+ +---------------+
^ | |
| | setLocal(ANSWER) |
setRemote(OFFER) | |
| V setLocal(ANSWER) |
+---------------+ |
| | |
| |<---------------------------+
| Stable |
| |<---------------------------+
| | |
+---------------+ setRemote(ANSWER) |
^ | |
| | setLocal(OFFER) |
setRemote(ANSWER) | |
| V |
+---------------+ +---------------+
| | | |
| | setRemote(PRANSWER) | |
| Local-Offer |------------------- >|Remote-Pranswer|
| | | |
| |----\ | |----\
+---------------+ | +---------------+ |
^ | ^ |
| | | |
\-----/ \-----/
setLocal(OFFER) setRemote(PRANSWER)
Рис 4: Стейт-машина JSEP. Источник.
На даннай диаграмме показана вся разница в обработке предворительных ответов (pranswer) и финального ответа (answer).
Формат сессионных датаграмм
В WebRTC сессионные датаграммы представляют из себя SDP сообщениия. Не смотря на то, что с данным форматом не так удобно работать в Javascript, он широко распространен, и часто пополяется новыми возможностями. Все альтернативные кодировки, приходилось бы держать в синхронизации с изменениями в SDP, покрайней мере до того момента, когда новая кодировки обогналабы SDP в популярности. Поэтому именно SDP используется для сессионных датаграмм JSEP.
К тому же чтобы упростить работу и предоставить гибкость в будущем SDP синтакс инкапсулирован в объекте SessionDescription, который может быть сформирован из SDP сообщения. И если последующие спецификации согласятся на использовании формата JSON для датаграмм, мы сможем легко адаптировать этот объект принимать и генерировать нужный JSON.
Другие методы могут быть добавлены в SessionDescription в будущем, чтобы упростить работу с датаграммами сессии в Javascript. Пока же сторонные Javascript библиотеки могут быть использованы для получения подобного функционала.
Обратите внимание, что в большинстве случаев приложение работает с объектами SessionDescription, как с блобами, получая их от одних методов и передавая другим, при этом вовсе нет необходимости читать их или модифицировать.
Контроль над сессионным датаграмми
Чтобы предоставить приложению контроль над различными сессионными параметрами, JSEP предоставляет контрольные поверхности, говорящие браузеру как создавать сессионные датаграммы. Это избавляет от необходимости менять датаграммы на стороне Javascript в большинстве случаев. Изменения этих SessionDescripton объектов приводит к изменению в сессионных датаграммах, полученных от createOffer\createAnswer методов.
RTP-трансиверы (RtpTransceivers)
RTP-трансиверы позволяют приложению контролировать RTP-медиа, ассоциированные с одной из m= секций в сессионной датаграмме. Каждый RTP-трансивер предоставляют интерфейсы приема(RtpReceiver) и передачи(RtpSender) RTP-медиа приложению. Приложение так же может изменить RTP-трансивер напрямую, например остановить его.
Трансиверы чаще всего 1:1 соотносятся с m= секциями, но возможна ситуация, когда трансиверов создано больше, но они еще не ассоцииорованы с m= секциями, или если трансивер был остановлен и разассоциирован с m= секцией. RTP-трансивер никогда не относится к более чем к одной m= секции и как только сессионная датаграмма применена, с каждой m= секцией всегда ассоциирован только один трансивер.
Трансиверы могут быть созданы явно приложением или неявно с помощью setRemoteDescription
и запроса, содержащего новые m= секции.
Интерфейс отправки (RtpSenders)
Интерфейс отправки позволяет приложению контролировать как RTP-медиа отправляется. В частности, приложение может контролировать является ли интерфейс активным или нет, что отражается в атрибуте направленности (directionality attribute) соответствующей m= секции.
Интерфейс получения (RtpReceiver)
Интерфейс получения позволяет приложению контролировать как RTP-медиа принимается. В частности, приложение может контролировать является ли интерфейс активным или нет, что отражается в аттрибуте направленности (directionlity attribute) соответствующей m= секции.
ICE
Сборка
JSEP собирает ICE кандидатов исходя их нужд приложения. Сборка ICE кандидатов называется сборочной фазой, и инициируется появлением новых или обновлением старых m= секций в локальном описании сессии, или новые ICE рекомендации(credentials) в описании, говорят о перзапуске ICE. Использование новых ICE рекомендаций(credentials) может быть инициировано приложением, или браузером в ответ на изменения в ICE конфигурации.
Когда изменения в ICE конфигурации приводят к новой сборочной фазе, взводится needs-ice-restart
бит. Когда этот бит взведен, вызовы createOffer
API будут создавать новые ICE рекомендации. Этот бит снимается вызовом setLocalDescription
API с новыми ICE-рекомендациями из запроса или ответа, например при локально- или удаленно-инициированом перезапуске ICE.
Когда новая сборочная фаза начата, ICE агент уведомит приложение, что сброка в процессе по средством события (event). Затем, когда каждый новый ICE-кандидат станет доступен, ICE агент доставит его приложению с помощью дополнительного события; эти кандидаты будут так же автоматически доабвлены в текущую и ожидающие сессионные датаграммы. В завершении, когда все ICE-кандидаты будут получены, будет отправлено событие сигнализирующее о завершении сборочной фазы.
Обратите внимание, что сборочные фазы собирают кандидатов, необходимых только для новой\измененной\возобновленной\ m= секций. Другие m= секции продолжат работу с уже существующими кандидатами. Также, когда активирована группировка (bundling), кандидаты собираются и меняются только для m= секций указанный в BUNDLE-tags, как описано здесь.
Стекание ICE кандидатов (ICE Candidate Trickling)
Стекание ICE кандидатов - это техника с помощью которой инициатор может последовательно предоставлять кандидатов адресату после того, как начальный запрос был отправлен; симантика "Trickle ICE" описана здесь. Этот процесс позволяет адресату начать взаимодействие в рамках вызова, настроить ICE (и возможно DTLS) соединения немедленно, недожидаясь пока инициатор соберет всех возможных кандидатов. Это позволяет ускорить установку media-соединений, где сборка не предшествует инициированию вызова.
JSEP поддерживает опциональное стекание кандидатов предоставляя API, как описано выше, которое дает контроль над и обратную связь с процессом сборки. Приложения, которые поддерживают стекание кандидатов могут отправить первоначальный запрос сразу и отправлять отдельных кандидатов, как только они будут готовы, реагируя на соотв. оповещения. Приложения не поддерживающие подобный функционал должны просто дождаться завешения сборочной фазы и после этого создать и отправить запрос со всеми доступными кандидатами одновременно.
При получении такого кандидата, принимающее приложение отдает его своему ICE агенту. Это заставляет ICE агента использовать нового кандидата для проверки соединения.
Формат ICE кандидата
Как и с описаниями сессии, IceCandidate объект предоставляет некую абстракцию, но может быть легко сконвертирован в форму строки SDP-кандидата.
Стока кандидата - это вся SDP информация, которую содержит IceCandidate. Вот пример строки кандидата:
candidate:1 1 UDP 1694498815 192.0.2.33 10000 typ host
IceCandidate также содержит поля указывающие с какой m= строкой он должен быть ассоциирован одним из двух способов: или с помощью индекса m= строки, или с помощью MID. Индекс m= строки - это число от нуля до кол-ва m= строк минус один (N-1). MID использует "media stream identification" аттрибут, как описано в RFC5888. Реализации JSEP должны поддерживать оба этих поля. Реализации, принимающие ICE кандидатов, должны в первую очередь использовать MID, и только при его отсутствии - индекс m= строки.
Политики ICE кандидатов
Обычно, когда собираются ICE кандидаты, браузер собирает все возможные формы из первоначальных кандидатов - хост, возвратный сервер или реле. Однако, в некоторых случаях, приложения могут захотеть больше контроля над процессом сборки, из соображений безопасности и т.д. Например, можно запретить использование хост-кандидатов, чтобы предотвратить раскрытие информации о локальной сети, или пойти еще дальше, разрешив использовать только релейные сервера, чтобы свести к минимуму возможность утечки локальной информации, при этом нужно помнить, что подобные ограничения приводят к повышенной стоимости этих операций. В целом браузер при необходимости должен предоставить приложению возможность явно указать какими кандидатами пользоваться во время сессии. Обратите внимание, что эта фильтрация применяется над всеми ограничениями, использующимися браузером при определении, какие адреса разрешены для приложения, как описано здесь.
Так же могут быть случаи, когда приложение хочет изменить тип кандидатов во время активной сессии. Простой пример, когда звонящий первоначально хотел использовать релейных кандидатов, чтобы предотвратить утечку локальной информации неизвестному звонящему, но потом выбрал использовать всех кандидатов, для ускорения работы, когда был уведомлен, что они нужны, чтобы принять звонок. Для этого сценария браузер должен изменение политики кандидатов при активной сессии.
Чтобы управлять политикой ICE кандидатов, браузер должен определить текущую настройку при старте каждой сборочной фазы. Тогда в течение сборочной фазы, браузер не должен предоставлять кандидатов запрещенных текущей политикой приложения, использовать их как источник для комунникационных проверок, или неявно выдавать их в других полях, таких как raddr\saddr атрибутов для других ICE кандидатов.
Пул ICE-кандидатов
JSEP приложения обычно инициируют браузер начать ICE сборку посредством информации отправляемой через setLocalDescription
, т.к. именно в этот момент приложение определяет количество медиа потоков и тем самым ICE компоненты, для которых собирать кандидатов. Однако, чтобы ускорить случаи, когда приложение знает несколько ICE компонент, еще до того, как они понадобятся, тогда оно может попросить браузер держать пул потенциальных ICE кандидатов, чтобы гарантировать быструю настройку соединения.
Когда setLocalDescription
вызван, браузер начинает собирать необходимых ICE кандидатов, он должен начать с кандидатов из пула. Если кандидаты есть, они возвращаются приложению немедленно, посредством оповещения об ICE кандидате. Если пул истощается, в результате большего чем ожидалось количества компонент, или по причине того, что пул еще не успел заполнится, так или иначе недостающие кандидаты будут собраны как обычно.
Примером того, где этот концепт применим является приложение, которое ожидает входящий вызов в какой-то конкретный момент времени в будущем, и хочет минимизировать время на установку соединения, чтобы минимизировать потерю первоначального медиа. Посредством собирания ICE кандидатов в пул, можно инициировать настройку соединения на этих кандадатах сразу после получение вызова. Однако, не стоит забывать, что хранение пресобранных кандидатов, которые остаются живыми, пока они нужны приложению, занимает ресурсы на STUN\TURN серверах, которые использует приложение.
Согласование размеров видео
Согласование размеров видео - это процесс с помощью которого получатель может использовать a=imagearrt
SDP атрибут RFC6236, чтобы указать какой размер видео фрейма он способен принимать. Принимающая сторона может быть ограничена в пределами видео-декодера, или оно это нужно приложению, например специфичный размер окна, в котором видео будет показано.
Создание imageattr аттрибута
Чтобы определить возможное для принимающей стороны разрешение видео, пересекаются лимиты декодера с обязательными ограничениями, которые применяются к MediaStreamTrack объекту. Если лимиты декодара неизвестны, например когда используется программный декодер, берутся только обязательные ограничения. Для ответчика эти обязательные ограничения могут быть применены к удаленному MediaStreamTrack, который получен при вызове setRemoteDescription и повлияют на ответ от подтверждающего createAnswer вызова. Любые ограничение наложенные после использования setLocalDescription для настройки ответа, приведут к новому запрос\ответ обмену. Инициатор, т.к. он не знает ни о каких удаленных MedisStreamTrack объектах, пока он не получит ответ, может только отразить пределы декодера. Если инициатор желает наложить обязательные ограничения на разрешение видео, он должен сделать это после получения ответа и результатом будет новый запрос\ответ обмен.
Если нет никаких известных ограничений декодера или обязательных ограничений, атрибут imageattr должен быть опущен.
В противном случае, a=imageattr
атрибут создается с recv
направлением, и результирующее разрешение формируется пересечением пределов декодара и ограничений используемых для определения максимальных и минимальных значений координат x= и y=. Если пересечением является пустое множество, результатом должны быть x=0 и y=0.
Правила здесь отражают единый набор настроек, и потому у аттрибута a=imagearrt
значение параметра q= не важно. Оно должно быть установлено в 1.0.
a=imageattr
поле имеет несколько специфичных значений в зависимости от типа нагрузки(payload). Когда все доступные видео-кодеки поддерживают одно и тоже разрешение - рекомендуется использовать один атрибут *
. Однако, если поддерживаемые видео-кодеки имеют различные возможности, соответствующие a=imageattr
аттрибуты должны быть добавлены для каждого типа нагрузки.
В качестве примера рассмотрим систему с HD совместимым, мультиформатным видео-декодером, где приложение ограничило разрешение входящего видео до 320p. В этом случае реализация должна создать такой атрибут:
a=imageattr:* recv [x=[16:640],y=[16:360],q=1.0]
Это описание говорит о том, что получатель может декодировать любое разрешение от 16х16 до 640х360 пикселей.
Интерпретация imageattr атрибута
RFC6236 определяет a=imageattr
как рекомендательное(advisory) поле. Это значит, что оно не ограничевает жестко рамки видео форматов, которые отправитель может использовать, но лишь дает информацию об рекомендуемых значениях.
Эта спецификация предписывает более определенное поведение. Когда отправитель данного MediaStreamTrack, который отдает видео определенного разрешения, получает a=imagearrt recv
атрибут, он должен проверить соответствует ли текущее резрешение параметрам в атрибуте, и если нет, то адаптировать, применяя масштабирование, если потребуется. Обратите внимание, что применительно к MediaStreamTrack производящему перевернутое видео, нужно использовать неперевернутое разрешение.
Для a=imageattr recv
атрибута, возможно только указать пределы размера по вертикали и горизонтали, все другие атрибуты должны быть игнорированы.
При взаимодействии с не-JSEP конечной точкой, может быть получено множество похожих a=imageattr recv
атрибутов. В таком случае нужно использовать атрибут с наибольшим значением q= поля.
Если a=imageattr recv
атрибут указывает видео-кодек, отличный от того, что используется в MediaStreamTrack, то он игнорируется.
Если оригинальное разрешение попадает в границы атрибута, то поток должен транслироваться без изменений.
Если оригинальное разрешение превышает границы атрибута, отправитель должен уменьшить масштаб видео MediaStreamTrack, чтобы соответствовать ограничениям. Уменьшение масштаба не должно изменять соотношение сторон.
Если оригинальное разрешение меньше границ атрибута, необходимо увеличение масштаба, но это применимо не для всех случаев. Поэтому приложение может установить политику увеличения масштаба для каждого отправляемого трека. По этой причине, если увеличение масштаба разрешено политикой, отправитель должен должен увеличить масштаб, чтобы соответсвовать нужному разрешению. Отправитель не должен увеличивать масштаб в других случаях, даже если политика разрешает это. Увеличение масштаба так же не должно менять соотношение сторон. Если нет применимого и разрешенного механизма масштабирования, требуемого лимитами принимающей стороны, отправитель не должен отправлять трек.
Когда принимается разрешение [0, 0], отправитель так же не отправляет трек.
Ветвление
Некоторые сигнальные системы вызовов поддерживают различные типы ветвления, когда SDP запрос может быть отравлен больше, чем одному устройству. Например, SIP, позволяет как "Паралелльный поиск (Parallel Search)", так и "Последовательный поиск (Sequential Search)". Так же определены первичные обязанности сигнального упровня, которые выходят за рамки JSEP, влияющие на конфигурацию релевантного медиа-потока. Когда происходит ветвление на сигнальном уровне, Javascript приложение берет на себя ответсвенность за сигналинг, принимая решение какое медиа должно быть оправлено или получено и когда. JSEP используется, чтобы убедиться, что media-подсистема может создавать RTP и передача media осуществляется как того желает приложение.
Базовые операции медиа-подсистемы:
- Начать обмен медиа-данными с противоположной стороной, при этом зарезервировать ресурсы.
- Начать обмен медиа-данными с противоположной стороной, при этом освободив все неиспользуемые ресурсы.
Последовательное ветвление
Последовательное ветвление подразумевает, что вызов отправляется множеству удаленных адресатов, где каждый адресат может принять вызов, но только одна сессия может быть активной, при этом никакого объединения входящих медиа-данных не происходит.
Описываемый JSEP механизм последовательного ветвления, позволяет приложению с легкостью контролировать политику выбора удаленной конечной точки. Когда ответ от одного из адресатов, приложение может выбрать применить этот ответ как предварительный, оставив возможность использовать другой ответ, при необходимости, или применить этот ответ как финальный, закончив настройку.
В first-one-wins
ситуации первый ответ применяется как финальный и приложение игнорирует все последующие ответы. В SIP это выглядело бы как ACK + BYE. В last-one-wins
ситуации, все ответы применяются как предварительные и каждый предыдущий вызов завершается. В какой-то момент приложение может завершить настройку, возможно по таймеру, применив текущее описание удаленной сессии, как финальный ответ.
Паралелльное ветвление
Параллельное ветвление подразумевает, что вызов отпавляется множеству удаленных адресатов, где каждый адресат может принять вызов и несколько активных сигнальных сессий могут появиться в результате. Если множество адресатов отправляют медиа одновременно, возможности по обработке подобной ситуации описаны в секции 3.1 RFC3960. Большинство SIP устройств сегодня поддерживают обмен медиа только с одним устройством в один момент времени и не пытаются смешивать много первичных аудио-потоков в непонятной ситуации. Например, допустим ситуацию при которой Европейская звуковая отбойка (ringback) смешивается с Северо-Американской отбойкой, результирующий звук не будет похож ни на один из этих тонов и будет сбивать пользователя. Если сигналящее приложение хочет обмениваться медиа только со одной из конечных точек в каждый конкретный момент времени, тогда с точки зрения медиа-подсистемы - это равносильно последовательному ветвлению.
В случае же параллельного ветвления, где Javascript приложение желает одновременно обмениваться медиа с несколькими удаленными точками, процесс обмена немного более сложный, но Javascript приложение может следовать стратегии которую RFC3960 описывает как UPDATE. Данная стратегия позволяет настроить независимый обмен медиа для каждой конечной точки. В JSEP данная стратегия осуществляется простым созданием нового PeerConnection объекта с требуемыми оокальными медиа-потоками. Тогда новый PeerConnection объект создаст запрос, который может быть использован на сигнальном уровне для осуществления UPDATE стратегии, описанной здесь.
Как результат шаринга медиа-потоков, приложение создает N параллельных PeerConnection сиссий, каждая с локальными и удаленными описаниями и локальными и удаленными адресами. Медиа-поток для этих сессий может управляться с помощью атрибутов направления SDP в описаниях, или приложение может пожелать воспроизвести медиа из всех сессий в смешанном(mixed) виде. Конечно, если приложение собирается работать только с одной сессией, оно может просто завершить лишние сессии.
Интерфейс
В этой секции описываются детали основных операций, которые должны быть реализованы в рамках JSEP. Актуальное API, представленное в W3C спецификации, может немного отличаться по синтаксису, но должно быть лекго переложимо на описываемые здесь концепты.
Конструктор
Конструктор PeerConnection позволяет приложению указать глобальные параметры для медиа-сессии, такие как адреса и порты STUN\TURN серверов, а так же первоначальную политику для ICE кандидитов и размер пула, а так же политику пакетов(bundle policy).
Если политика ICE кандидатов определена, она функционирует как описано в секции 3.5.3 JSEP, в рузультате браузер использует только разрешенных приложением кандидатов (включая внутреннюю фильтрацию на уровне браузера) и только эти кандидаты используются для проверки соединения. Доступны следующие политики:
- all - без ограничений.
- relay - все кандидаты кроме релейных игнорируются.
Политика ICE-кандидатов по-умолчанию - all. Позволяющая максимально снять нагрузку с TURN серверов.
Если указан размен пула ICE кандидатов, то это приводит к предварительной сборке нужного кол-ва ICE кандидатов. Так как результаты предварительной сборки приводят к утилизации дополнительных ресурсов на STUN\TURN серверах на потенциально продолжительные промежутки времени, это должно быть инициировано приложением явно, и по-умолчанию размер пула должен быть ноль.
Приложение может использовать политику объединения (bundle policy), механизм мультиплексирования, описанный здесь. В соответствие с политикой, приложение будет вести переговоры о создании бандла(bundle) для каждого транспорта и предлагать использовать один бандл для всех медиа-секций, но использование единого транспорта зависит от получателя бандла. Однако, указав политику из списка ниже, приложение может контролировать насколько аггресивно оно будет пытаться объединить медиа-потоки, что может повлиять на работу с конечными точками не поддерживающими объединение. Когда ведутся переговоры с конечной точкой не поддерживающей политику объединения, только соединения не помеченные как объединенные, могут быть установлены.
Доступны следующие политики:
-
balanced - первая медиа-секция каждого типа (
audio
,video
,application
) будет содержать транспортные параметры, что позволит адресату, разъеденить эту секцию. Вторая и последующие секции каждого типа будут помечены какbundle-only
. В результате если присутствует N отдельных медиа типов, тогда кандидаты будут собраны только для N медиа-потоков. Эта политика позволяет найти баланс между мультиплексированием и необходимостью договориться о базовых аудио- и видео-потоках. В случае адресата, если никакого объединения не объявлено в запросе, реализация проигнорирует все кроме первой m= секции для каждого медиа типа. -
max-compat - все медиа секции содержат транспортные параметры; ни один поток не помечается как объединенный(bundle-only). Эта политика позволяет всем потокам быть принятыми конечными точками не поддерживающими объединение. При этом возникает необходимость в отдельных кандидатов для каждого медиа-потока.
-
max-bundle - только первая медиа-секция содержит транспортную информацию, все остальные потоки, отличные от первого помечены как объединенные(bundle-only). Эта политика позволяет свести к минимуму кол-во необходимых кандидатов и максимизировать мультиплексирование, заплатив за это совместимостью с конечными точками не поддерживающими объединение. На стороне адресата, если в запросе нет объединения - все m= секции игнорируются кроме первой.
По-умолчанию используется balanced
политика, т.к. она утилизирует объединение и мультиплексирование, при этом сохраняя базовую совместимость с неподдерживающими объединение конечными точками.
Приложение может определить политику относительно использования RTP\RTCP мультиплексирования, в соотв. с RFC5761:
- negotiate - браузер будет собирать как RTP так и RTCP кандидатов, но всегда предлагать
a=rtcp-mux
, это сделано для совместимости с конечными точками, которые не могут RTCP мультиплексирование или в целом не поддерживают мультиплексирование. - require - браузер будет собирать только RTP кандидатов. Это вдвое уменьшает кол-во кандидатов для сборки. В случае адресата, реализация будет игнорировать любые m= секции, содержащие
a=rtcp-mux
атрибут.
По-умолчанию используется политика require
. Конечная реализация может проигнорировать попытку приложения установить negotiate
политику.
addTrack
Метод addTrack добавляет новый объект MediaStreamTrack в PeerConnection, используя MediaStream аргумент для представления трека, наряду с другими треками в одном и том же MediaStream объекте, таким образом они могут быть добавлены в одну LS
секцию при создании запроса или ответа. addTrack пытается минимизировать количество трансиверов следующим образом: трек добавляется к первому совместимому трансиверу (такого же медиа типа), у которого никогда не выставлено sendonly
или sendrecv
направление. Если подходящий трансивер не найден, тогда будет сконструирован новый, как описано в секции addTransceiver.
addTranciever
[TODO]
createDataChannel
[TODO]
createOffer
Метод создает блоб SDP, содержащий запрос в соответствие с RFC3264 с поддерживаемыми конфигурациями сессии, включая описания медиа добавленных в ReerConnection, опиции кодеков\RTP\RTCP, поддерживаемых реализацией, и любые кандидаты собранные ICE агентом. Параметр options
может использоваться для дополнительного контроля над созданным запросом. Этот параметр дает возможность приложению запускать перезапуск ICE, для переустановления соединения.
В первоначальном запросе, сгенерированный SDP содержит всю необходимую для создания сессии информацию. Более подробно процесс создания SDP запроса строчка за строчкой рассмотрен здесь, секция 5.2.1.
Вызов createOffer после настройки сессии создает запрос для модификации текущей сессии, например добавление или остановка трансиверов или перезапуск ICE. Более подробно создание такого запроса рассмотрено здесь, секция 5.2.2.
Сессионные датаграммы созданные createOffer должны быть сразу использованы в setLocalDescription; если система имеет лимитированные ресурсы, например конечное число декодеров, createOffer должен вернуть запрос отражающий текущее состояние системы, чтобы вызов setLocalDscription мог застать эти ресурсы доступными. Т.к. этот процесс требует обращения к системе для получения информации о доступных ресурсах, он можнет быть выполнен асинхронно.
Вызов данного метода может инициировать сборку новых ICE рекомандаций(credentials), но не должен приводить к сборке кандидатов или к запуску или остановке медиа потоков.
createAnswer
Метод createAnswer создает блоб SDP, содержащий ответ в соответствие с RFC3264 с поддерживаемыми конфигурациями сессии, совместимыми с параметрами указанными в предшествующем вызове setRemoteDscription, который должен всегда предшествовать вызову createAnswer. Как и при вызове createOffer, полученный блоб содержит описания медиа, добавленные в этот PeerConnection, опции кодеков\RTP\RTCP одобренных для сессии и любые кандидаты, собранные ICE агентом. Параметр options
так же предоставляет дополнительный контроль над созданным ответом.
Являясь ответом, созданный SDP содержит точную конфигурацию устанавливаемого медиа-соединения. Более подробно этот процесс описан здесь, секция 5.2.3.
Сессионные датаграммы, созданные createAnswer должны быть немедленно использованы в setLocalDescription; как и в случает с createOffer, полученное описание должно соответствовать реальному состоянию системы. Т.к. этот метод обращается к системе для получения информации о доступных ресурсах, он может быть выполнен асинхронно.
Вызов этого метода может привести к генерации новых ICE рекомендаций, но не должно приводить к сборке кандидатов или изменению состояния медиа-уровня.
SessionDescriptionType
Объекты сессионных датаграмм (RTCSessionDescription) могут быть следующих типов offer
, pranswer
, answer
или rollback
. Эти типы предоставляют информацию, о том как данное описание должно интерпретироваться и как медиа статус должен быть изменен.
offer
(запрос) означает, что описание должно быть интерпретировано как запрос; при этом описание может содержать много возможных медиа-конфигураций. Описание используемое в качестве запроса может быть применено в любое время, пока PeerConnection имеет статус stable
или как обновление к предыдущему, но не отвеченному запросу.
pranswer
(предварительный ответ) означает, что описание должно быть интерпретировано как предварительный ответ, но не финальный ответ, поэтому не должно приводить к освобождению локальных ресурсов. Применение данного описания может привести к началу передачи медиа-данных, если ответ не описывает неактивное медиа-направление. Описание использующееся как pranswer
может быть примененно как ответ на запрос offer
, или как обновление к отправленному до этого предварительному ответу pranswer
.
answer
(финальный ответ) означает, что описание должно быть интерпретировано как финальный ответ, при этом запрос\ответ обмен считается завершенным, и все аллоцированные ресурсы (декодеры, кандидаты), которые больше не нужны могут быть освобождены. Описание, используещееся как ответ answer
может быть применено как ответ за запрос offer
, или как обновление к предыдущему предварительному ответу pranswer
.
Единственная разница между предварительным и финальным ответом заключается в том, что финальный ответ приводит к освобождению любых неиспользуемых ресурсов, аллоцированных в процессе применения запроса. Так же приложение может предусмотрирельно принять собственное решение является ли данный ответ финальным или предварительным, поменяв его тип на тот, который нужен (answer\pranswer). Например, сценарии последовательного ветвления, приложение может получить множество финальных оветов, от каждой конечной точки. Приложение может применить первый финальный ответ как предварительный, и применить финальный ответ только тогда, когда он будет удовлетворять нужным критериям, например живой пользователь вместо голосовой почты.
rollback
это специальный тип сессионной датаграммы подразумевающий, что стейт-машина должна откатиться в предыдущее состояние, как описано в секции Rollback, ниже. Данное сообщение должно быть пустым, т.е. только тип.
Работа с предварительным ответом
Большинству web-приложений нет необходимости создавать ответы с типом pranswer
. Хотя это хорошая практика немедленно отправлять ответ, чтобы начать транспорт медиа и минимизировать потерю медиа-данных, предпочтительным поведением для web-приложения является создать и отправить неактивный (inactive) финальный ответ сразу после получения запроса. Позже, когда вызываемый пользователь на самом деле ответит на звонок, приложение может создать новый sendrecv
запрос, чтобы обновить предыдущую пару запрос\ответ и начать обмен медиа. Хотя это так же может быть сделано с помощью неактивного (inactive) предварительного ответа (pranswer) с последующим финальным sendrecv
финальным ответом, предварительный ответ оставляет запрос\ответ обмен открытым, что не дает ни одной из сторон отправить обновленный запрос в течение этого времени.
Как пример, представим типичное web-приложение, которое собирается установить канал для обмена данными (data channel), аудио-канал и видео-канал. Когда конечная точка получает запрос с этими каналами, она может отправить ответ, в котором обмен данными будет двусторонним, а аудио- и видео-каналы неактивными или только на получение receive-only
. Далее она просит пользователя принять звонок, тем самым получив доступ к локальным медиа-потокам, и отправляет новый запрос вызывающей стороне меняя аудио- и видео-потоки на двунаправленные. К этому времени пользователь принял вызов и инициировал создание нового запроса, так же вероятно, что к этому времени рукопожатие ICE и DTLS для всех каналов будет завершено.
Конечно, не всем приложениям нужно реализовывать подобную двойную модель запрос\ответ обмена, в частности шлюзы для старых сигнальных протоколов. В этих случаях предварительный ответ может приводить к началу передачи медиа-данных.
Откат(Rollback)
В некоторых ситуациях имеет смысл "откатить" изменения сделанные с помощью setLocalDescription или setRemoteDescription. Рассмотрим случай когда звонок уже идет и какая-то из сторон хочет поменять параметры сессии; эта сторона создает обновленный запрос и вызывает setLocalDescription. Однако, противоположная сторона либо до, либо после вызова setRemoteDescription, решает, что не желает принимать новые параметры, и отправляет сообщение об отказе отправителю. Теперь инициатор запроса, как возможно и адресат, должны откатиться к предыдущему стабильному состоянию, т.е. к предыдущей локальной\удаленной конфигурации(local\remote description). Чтобы это было возможно, добавлен концепт отката (rollback).
Откат отменяет любые предложенные изменения сессии, возвращая стейт-машину в предыдущее стабильное состояние и устанавливая ожидающие локальные и\или удаленные описания обратно в null. Любые ресурсы или кандидаты, которые были аллоцированы отмененными описаниями, освобождаются; и медиа входящее\исходящее будет обрабатываться в соответствие со старыми описаниями удаленной и локальной сессий. Откат может быть использован только для отката предложенных изменений; не предусматривается механизма отката стабильного состояния к предыдущему стабильному состоянию. Это означает, что как только ответчик применит setLocalDescription со своим ответом - это уже нельзя будет откатить.
Откат отделяет любые Rtp-трансиверы которые были ассоциированы с m= секциями приложения с откатывающейся сессией (подробности тут и тут). Это означает, что некоторые Rtp-трансиверы которые были ассоциированы с m= секциями, больше с ними аллоцированы не будут; в этих условиях значение mid аттрибута Rtp-трансивера должно быть установлено в null. Rtp-трансиверы, которые были созданы применением удаленного оффера, который был в последствие откачен, должны быть удалены. Однако, Rtp-трансивер не должен быть удален если соответствующий ему Rtp-отправитель(RtpSender) был активирован вызовом метода addTrack. Возможен следующий сценарий, приложение вызовет addTrack для Rtp-отправителя из нового оффера, после этого setRemoteDescription c оффером, потом откатит этот оффер, дальше вызовет createOffer и получит m= секцию для добавленного трека в новом оффере.
Откат выполняется применением сессионной датаграммы типа rollback
с пустым контентом к setLocalDescription или setRemoteDescription, в зависимости от того что последним использовалось (например, если новый оффер был применен к setLocalDescription, то откат нужно так же осуществлять вызовом setLocalDescription).
setLocalDescription
Этот метод инструктирует PeerConnection применить предложенную сессионную датаграмму в качестве локальной конфигурации. Поле type
сессионной датаграммы говорит о том как она должна быть интерпретирована (оффер, предватирельный ответ, финальный ответ). Ответы и офферы проверяются по разному, с помощью правил, которые описаны для каждой строки SDP.
Это API изменяет локальное состояние медиа-уровня; по мимо прочего оно настраивает локальные ресурсы для приема и декодирования медиа. Для успешной обработки сценариев, когда приложение хочет запросить изменение одного медиа формата на другой, несовместимый формат, PeerConnection должен одновременно поддержать как старое, так и ожидаемое описание сессии (например, использовать кодеки, присутствующие в обоих описаниях) пока финальный ответ не будет получен, после этого PeerConnection может полностью применить ожидаемое описание сессии или откатиться к текущему описанию, если удаленная сторона отказалась от изменений.
Это API косвенно контролирует процесс сборки кандидатов. Когда локальное описание предложено и количество транспортов, находящихся в использовании не соответствует транспортным нуждам предложенного описания, PeerConnection начинает создавать недостающие транспорты и запускает процесс сборки кандадатов для них.
Если предварительно был вызван setRemoteDescription с оффером и setLocalDescription вызывается с ответом (предварительным или финальным), при этом медиа направления совместимы и медиа готово к передаче - запускается процесс обмена медиа-данными.
setRemoteDescription
Метод setRemoteDescription инструктирует PeerConnection применить предложенную сессионную датаграмму в качестве желаемой удаленной конфигурации. Как и в случае с setLocalDescription, поле type
сессионной датаграммы определяет то, как она должна быть интерпретирована.
Это API изменяет локальное состояние медиа-уровня; помимо прочего оно настраивает локальные ресурсы для отправки и кодирования медиа.
Если предварительно был вызван setLocalDescription с оффером и setRemoteDescription вызывается с ответом (предварительными или финальным) при этом медиа направления совместимы и медиа готово к передаче - запускаетс процесс обмена медиа-данными.
currentLocalDescription
Метод currentLocalDescription возвращает копию текущего согласованного локального описания, т.е. локальное описание последнего успешного запрос\ответ SDP-обмена, плюс все локальномые кандидаты, которые были сгенерированы ICE-агентом после установки последнего локального описания.
Null объект возвращается если ни однин запрос\ответ обмен еще на завершился.
pendingLocalDescription
Метод pendingLocalDescription возвращает копию локального описания, находящегося на этапе согласования, например локальный оффер установлен без соответствующего удаленного ответа, а так же все локальные кандидаты, сгенеренные ICE-агентом после установки последнего локального описания.
Null объект возвращается, если PeerConnection находится в статусе stable
или have-remote-offer
.
currentRemoteDescription
Метод currentRemoteDescription возвращает копию текущего согласованного удаленного описания, т.е. удаленное (remote) описание последнего успешного запрос\ответ SDP-обмена, плюс все удаленные кандидаты, предложенные через processIceMessage с момента как последнее удаленное описание было примененно.
Null объект возвращается если ни один запрос\ответ обмен еще не завершен.
pendingRemoteDescription
Метод pendingRemoteDescription возвращает копию удаленного описания, находящегося в процессе согласования, например удаленный запрос применен без соответствующего локального ответа, а так же все удаленные кандидаты, предложенные через processIceMessage с момента как последнее удаленное описание было примененно.
Null объект возвращается если PeerConnection находится в статусе stable
или have-local-offer
.
canTrickleIceCandidates
Свойство catTrickleIceCandidates указывает как удаленная сторона поддерживает стекающих(trickled) кандидатов. Может содержать три значения:
null
: ни одного SDP не было получено от удаленной стороны, поэтому не известно поддерживает ли удаленная сторона стекание кандидатов или нет. Это дефолтное значение до первого вызова setRemoteDescription.
true
: от удаленной стороны был получен SDP, говорящий, что она поддерживает стекание кандидатов.
false
: от удаленной стороны был получен SDP, говорящий, что она не поддерживает стекание кандидатов.
Как описано тут JSEP реализации всегда предоставляют кандидатов приложению в индивидуальном порядке, в соответствие с тем, что требуется для стекания кандидатов. Однако, приложение может использовать canTrickleIceCandidates для определения, возможности удаленной стороны поддержать этот механизм, т.е. может принимать кандадатов независимо от офферов и ответов, по мере их поступления. Учитывая, что true
это единственное значение, которое однозначно говорит о том, что удаленная сторона поддерживает стекание кандидатов, приложение по-умолчанию пытается работать в полу-стекающем режиме (Half Trickle) принимая инициирующие офферы, и в полном режиме (Full Trickle) при последующих взаимодействиях с ICE-совместимым агентом.
setConfiguration
Метод setConfiguration позволяет поменять глобальную конфигурацию PeerConnection в течение сессии. Результаты выполнения метода зависят от того когда он был вызван и а так же какие параметры были изменены:
-
любые изменения STUN\TURN серверов для использования приводят к новой фазе сборки. Если ICE сборка уже начата или завершена устанавливается
needs-ice-restart
бит (см. тут). Это означает, что при следующем вызове createOffer будет начат ICE перезапуск (ICE restart), что означает новую фазу генерация ICE кандидатов включая кандидатов для новых STUN\TURN серверов. Если пул ICE кандидатов имеет не нулевой размер, любые существующие кандидаты будут отменены, а их место будет занято новыми кандидатами. -
любые изменения политик ICE кандидатов влияют на последующую сборку кандидатов. Если сборка уже началась или закончилась будет выставлен
need-ice-restart
бит. Смена политики никак не влияет на состав пула кандидатов, т.к. он не предоставляется приложению напрямую, а значит всегда можно выполнить фильтрацию в соответствие с выбранной политикой. -
любые изменения в размере пула ICE кандидатов оказывают эффект немедленно. Если размер увеличивается - собираются дополнительные кандадаты; если уменьшается - лишние кандидаты удаляются.
-
политики упаковки и RTCP мультиплексирования не должны меняться после создания PeerConnection.
Вызов этого метода может привести к изменению состояния ICE агента, а так же состояния медиа-уровня.
addIceCandidate
Метод addIceCandidate предоставляет удаленного кандидата ICE агенту, который, если будет распаршен успешно, будет добавлен в текущее и\или в находящееся на этапе согласования описание удаленной сессии в соответствие с правилами для Trickle(стекающего) ICE. Если MID, индекс m-строки или строка кандидата указанные в ICE кандидате - невалидны, возвращается ошибка. Для каждого нового кандадата осуществляются проверки сетевой доступности.
Этот мотод так же сообщает о завершении доступных кандидатов (end-of-candidates indication) (как описано здесь) ICE агенту для всех медиа-описаний из последнего описания удаленной сессии.
Вызов метода приводит к изменению статуса ICE агента и может привести к изменению статуса медиа-уровня, если приводит к установке соединения.
Работа с SDP
Процедуры и требования необходимые для создания и парсинга SDP объетов подробно рассмотрены здесь.
Настраиваемые SDP параметры
Существует возможность менять элементы SDP, возвращенного методом createOffer перед передачей его в setLocalDescription. Когда имплементация получает измененный SDP она должна сделать одно из двух:
- применить изменения и отрегулировать свое поведение в соответствие с SDP.
- отклонить изменения и вернуть ошибку через error-коллбек.
Изменения не должны быть молча проигнорированы.
Следующие элементы сессионной датаграммы не могут быть изменены после вызова createOffer и перед вызовом setLocalDescription (или между createAnswer и setLocalDescription), т.к. они относятся к транспортным атрибутам и находятся под контролем браузера и браузер не должен предоставлять возможности изменить их:
- номер, тип и порт в m= строках.
- сгенеренные MID атрибуты (a=mid).
- сгенеренные ICE кандидаты (a=ice-ufrag и a=ice-pwd).
- набор ICE кандидатов и их параметры (a=candidate).
- DTLS отпечаток\отпечатки (a=fingerprint).
- состав групп упаковки (bundle groups),
bundle-only
параметры илиa=rtcp-mux
параметры.
Следущие модификации, будучи сделанными самим браузером в отношении описания сессии между createOffer\createAnswer и setLocalDescription, не должны быть проигнорированы им:
- удаление или перемещение кодеков в m= строках.
Следующие параметры могут контролироваться опциями переданными в createOffer\createAnswer. В качестве открытого вопроса, данные изменения могут так же быть выполнены посредством модификации SDP возвращенного createOffer\createAnswer, как описано выше, пока не будут превышены возможности конечной точки, например запрос более высокого разрешения чем конечная точка может отдать:
- [[ОТКРЫТЫЙ ВОПРОС: это плейсхолдер для последующих модификаций, которые могут быть добавлены при появлении новых сценариев использования]]
Имплементации могут либо принять, либо отвергнуть любые элементы не входящие ни в одну из двух категории выше, но должны делать это явно, как описано в начале секции. Отметьте, что будушие стандарты могут добавить новые SDP элементы в список обязательных, но в виду возможного перекоса версий, приложения имплементирующие спецификацию должны предоставлять возможность для быстрого добавления\удаления обязательных параметров сессии.
Приложение может так же изменить SDP, чтобы уменьшить список своих возможностей (capabilities) в оффере, отправляемом удаленной стороне. Данный механизм безопасен, если измененные возможности (capabilities) являются подмножеством исходных возможностей, описанных в SDP полученном от createOffer. К тому же ответ не может расширить список возможностей, а должен просто отвечать на то, что указано в оффере.
Как всегда, приложение полностью ответственно за то, что оно отправляет противоположной стороне и все входящие SDP будут обработаны браузером в меру его возможностей. Ошибкой будет полагать, что все SDP будут правильно сформированы; все должны понимать, что любая имплементация данной спецификации должна быть способна обрабатывать, в качестве удаленного запроса или ответа, SDP пришедший от любой другой имплементации данной спецификации.
Примеры
В этой секции приводится несколько фрагментов SDP датаграмм, обратите внимание, что строки могут быть разбиты на несколько, в связи с форматированием по ширине, а так же добавлены пустые строки, для повышения читабельности, что недопустимо в валидном SDP.
Больше примеров SDP для WebRTC представлено здесь.
Простой пример
В этой секции проиллюстрирован простой пример установки минимального аудио\видео звонка между браузерами, и не используется Trickle ICE. В следующей секции представлен более реалистичный пример, того как это происходит при нормальном WebRTC соединении между браузерами.
Итак, браузер Алисы инициирует соедиение с браузером Боба. Сообщения от js-кода Алисы передаются js-коду Боба посредством какого-то сингального протокола через web-сервер. js-код на обоих сторонах ожидает всех кандидатов перед отправкой оффера или ответа, таким образом эти датаграммы содержат полную информацию о кандидатах и не отправляются пока информация о кандидатах не будет полностью заполнена. Trickle ICE не используется и Боб и Алиса используют ICE политику по-умолчанию (balanced).
@startuml
activate AliceJS
AliceJS->AliceUA: создание нового PeerConnection
note left: настройка локального медиа-уровня
activate AliceUA
AliceJS->AliceUA: вызов метода addTrack с двумя треками: аудио и видео
AliceJS->AliceUA: вызов метода createOffer для получения оффера
AliceJS->AliceUA: вызов метода setLocalDescription с полученным оффером
note left: сборка ICE кандидатов
AliceUA->AliceJS: несколько onicecandidate евентов с кандидатами
AliceJS ->AliceJS: ожидание завершения ICE сборки
AliceUA->AliceJS: onicecandidate евент с кандидатом == null
AliceJS->AliceUA: вызов метода pendingLocalDescription для получения |offer-A1|
note left: Алиса начинает звонок Бобу
AliceJS->WebServer: отправка сессионной датаграммы |offer-A1|
activate WebServer
WebServer->BobJS: получение сессионной датаграммы |offer-A1|
deactivate WebServer
activate BobJS
BobJS->BobUA: создание нового PeerConnection
activate BobUA
BobJS->BobUA: вызов метода setRemoteDescription с полученным SDP |offer-A1|
BobUA->BobJS: onaddstream евент с remoteStream объектом
note right: Боб принимает звонок
BobJS->BobUA: вызов метода addTrack с локальными треками
BobJS->BobUA: вызов метода createAnswer, чтобы получить SDP для ответа
BobJS->BobUA: вызов метода setLocalDescription с полученным ответом
note right: сброка ICE кандидатов
BobUA->BobJS: несколько onicecandidate евентов с кандидатами
BobJS->BobJS: ожидание завершения ICE сборки
BobUA->BobJS: onicecandidate евент с кандидатом == null
BobJS->BobUA: вызов метода currentLocalDescription для получения |answer-A1|
BobJS->WebServer: отпрака сессионной датаграммы |answer-A1|
deactivate BobJS
activate WebServer
WebServer->AliceJS: получение сессионной датаграммы |answer-A1|
deactivate WebServer
AliceJS->AliceUA: вызов метода setRemoteDescription с полученным SDP |answer-A1|
AliceUA->AliceJS: onaddstream евент с remoteStream объектом
deactivate AliceJS
note left: передача медиа начата
BobUA->AliceUA: медиа передается от Боба к Алисе
AliceUA->BobUA: медиа передется от Алисы к Бобу
@enduml
SDP |offer-A1|.
v=0
o=- 4962303333179871722 1 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 v1
a=ice-options:trickle
m=audio 56500 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 192.0.2.1
a=mid:a1
a=rtcp:56501 IN IP4 192.0.2.1
a=msid:47017fee-b6c1-4162-929c-a25110252400
f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:ETEn1v9DoTMB9J4r
a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=ssrc:1732846380 cname:EocUG1f0fcg/yvY7
a=candidate:3348148302 1 udp 2113937151 192.0.2.1 56500
typ host
a=candidate:3348148302 2 udp 2113937151 192.0.2.1 56501
typ host
a=end-of-candidates
m=video 56502 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 192.0.2.1
a=rtcp:56503 IN IP4 192.0.2.1
a=mid:v1
a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae
f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0
a=sendrecv
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=ice-ufrag:BGKkWnG5GmiUpdIV
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=ssrc:1366781083 cname:EocUG1f0fcg/yvY7
a=ssrc:1366781084 cname:EocUG1f0fcg/yvY7
a=ssrc-group:FID 1366781083 1366781084
a=candidate:3348148302 1 udp 2113937151 192.0.2.1 56502
typ host
a=candidate:3348148302 2 udp 2113937151 192.0.2.1 56503
typ host
a=end-of-candidates
SDP |answer-A1|:
v=0
o=- 6729291447651054566 1 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 v1
m=audio 20000 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 192.0.2.2
a=mid:a1
a=rtcp:20000 IN IP4 192.0.2.2
a=msid:PI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1
PI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1a0
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:6sFvz2gdLkEwjZEr
a=ice-pwd:cOTZKZNVlO9RSGsEGM63JXT2
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:active
a=rtcp-mux
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=ssrc:3429951804 cname:Q/NWs1ao1HmN4Xa5
a=candidate:2299743422 1 udp 2113937151 192.0.2.2 20000
typ host
a=end-of-candidates
m=video 20000 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 192.0.2.2
a=rtcp 20001 IN IP4 192.0.2.2
a=mid:v1
a=msid:PI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1
PI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1v0
a=sendrecv
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:active
a=rtcp-mux
a=rtcp-rsize
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=ssrc:3229706345 cname:Q/NWs1ao1HmN4Xa5
a=ssrc:3229706346 cname:Q/NWs1ao1HmN4Xa5
a=ssrc-group:FID 3229706345 3229706346
Реальный пример
В этой секции приведены типичные примеры сессий между двумя браузерами с установкой аудио- и дата-каналов. Используется Trickle ICE в полном режиме (full trickle mode) с политикой группировки max-bundle, и политикой RTCP mux при необходимости и одним TURN сервером. Позже два видео потока, один для ведущего, другой, чтобы шарить экран; будут добавлены в сессию. В этом примере показано, как браузер Алисы устанавливает сиссию с браузером Боба. Сообщения от js-кода Алисы передаются js-коду Боба посредством какого-то сингального протокола через web-сервер.
@startuml
activate AliceJS
note left: настройка локального медиа-уровня
AliceJS->AliceUA: создание нового PeerConnection
activate AliceUA
AliceJS->AliceUA: вызов метода addTrack с аудио треком
AliceJS->AliceUA: вызов метода createDataChannel для создания дата-канала
AliceJS->AliceUA: вызов метода createOffer для получения |offer-B1|
AliceJS->AliceUA: вызов метода setLocalDescription с |offer-B1|
note left: доставка |offer-B1| по сигнальному протоколу Бобу
AliceJS->WebServer: отправка SDP |offer-B1|
deactivate AliceJS
activate WebServer
WebServer->BobJS: получение SDP |offer-B1|
deactivate WebServer
activate BobJS
note left: SDP |offer-B1| прибыл к Бобу
BobJS->BobUA: создание нового PeerConnection
activate BobUA
BobJS->BobUA: вызов метода setRemoteDescription с |offer-B1|
BobUA->BobJS: onaddstream евент с аудио-треком Алисы
deactivate BobJS
note right: доставка кандидатов Бобу
AliceUA->AliceJS: onicecandidate евент |candidate-B1| (host)
activate AliceJS
AliceJS->WebServer: отправка |candidate-B1|
activate WebServer
AliceUA->AliceJS: onicecandidate евент с |candidate-B2| (srflx)
AliceJS->WebServer: отправка |candidate-B2|
deactivate AliceJS
WebServer->BobJS: получение |candidate-B1|
activate BobJS
BobJS->BobUA: вызов метода addIceCandidate с |candidate-B1|
WebServer->BobJS: получение |candidate-B2|
deactivate WebServer
BobJS->BobUA: вызов метода addIceCandidate с |candidate-B2|
note right: Боб принимает звонок
BobJS->BobUA: вызов addTrack с локальным аудио-треком
BobJS->BobUA: вызов метода createDataChannel для получения дата-канала
BobJS->BobUA: вызов метода createAnswer для получения |answer-B1|
BobJS->BobUA: вызов метода setLocalDescription с |answer-B1|
note right: доставка |answer-B1| Алисе
BobJS->WebServer: отправка |answer-B1|
deactivate BobJS
activate WebServer
WebServer->AliceJS: получение |answer-B1|
deactivate WebServer
activate AliceJS
AliceJS->AliceUA: вызов метода setRemoteDescription с |answer-B1|
AliceUA->AliceJS: onaddstream евент с аудио-треком от Боба
deactivate AliceJS
note left: доставка кандидатов Алисе
BobUA->BobJS: onicecandidate евент с |candidate-B3| (host)
activate BobJS
BobJS->WebServer: отправка |candidate-B3|
activate WebServer
BobUA->BobJS: onicecandidate евент с |candidate-B4| (srflx)
BobJS->WebServer: отправка |candidate-B4|
deactivate BobJS
WebServer->AliceJS: получение |candidate-B3|
activate AliceJS
AliceJS->AliceUA: вызов метода addIceCandidate с |candidate-B3|
WebServer->AliceJS: получение |candidate-B4|
deactivate WebServer
AliceJS->AliceUA: вызов метода addIceCandidate с |candidate-B4|
note left: открытие дата-канала
BobUA->BobJS: ondatachannel евент
activate BobJS
AliceUA->AliceJS: ondatachannel евент
deactivate AliceJS
BobUA->BobJS: onopen евент
deactivate BobJS
note right: медиа передается между браузерами
BobUA->AliceUA: аудио и дата передаются от Боба к Алисе
AliceUA->BobUA: аудио и дата передаются от Алисы к Бобу
note right
спустя какое-то время Боб добавляет два видео-потока
заметьте, обмена кандидатами не происходит, из-за группировки (bundle)
end note
BobJS->BobUA: вызов метода addTrack с первым видео-потоком
activate BobJS
BobJS->BobUA: вызов метода addTrack со вторым видео-потоком
BobJS->BobUA: вызов метода createOffer для получения |offer-B2|
BobJS->BobUA: вызов метода setLocalDescription с |offer-B2|
note right: доставка |offer-B2| Алисе
BobJS->WebServer: отправка |offer-B2|
deactivate BobJS
activate WebServer
WebServer->AliceJS: получение |offer-B2|
deactivate WebServer
activate AliceJS
AliceJS->AliceUA: вызов метода setRemoteDescription с |offer-B2|
AliceUA->AliceJS: onaddstream евент с первым видео-стримом
AliceUA->AliceJS: onaddstream евент со вторым видео-стримом
AliceJS->AliceUA: вызов метода createAnswer для получения |answer-B2|
AliceJS->AliceUA: вызов метода setLocalDescription с |answer-B2|
note left: доставка |answer-B2| Бобу
AliceJS->WebServer: отправка |answer-B2|
deactivate AliceJS
activate WebServer
WebServer->BobJS: отправка |answer-B2|
deactivate WebServer
activate BobJS
BobJS->BobUA: вызов метода setRemoteDescription с |answer-B2|
deactivate BobJS
note right: медиа передается между браузерами
BobUA->AliceUA: аудио, видео и дата передаются от Боба к Алисе
AliceUA->BobUA: аудио, видео и дата передаются от Алисы к Бобу
@enduml
SDP |offer-B1|:
v=0
o=- 4962303333179871723 1 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 d1
a=ice-options:trickle
m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=mid:a1
a=msid:57017fee-b6c1-4162-929c-a25110252400
e83006c5-a0ff-4e0a-9ed9-d3e6747be7d9
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:ATEn1v9DoTMB9J4r
a=ice-pwd:AtSK0WpNtpUjkY4+86js7ZQl
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=ssrc:1732846380 cname:FocUG1f0fcg/yvY7
m=application 0 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=bundle-only
a=mid:d1
a=fmtp:webrtc-datachannel max-message-size=65536
a=sctp-port 5000
a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
SDP |candidate-B1|:
candidate:109270923 1 udp 2122194687 192.168.1.2 51556 typ host
SDP |candidate-B2|:
candidate:4036177503 1 udp 1685987071 11.22.33.44 52546 typ srflx raddr 192.168.1.2 rport 51556
SDP |answer-B1|:
v=0
o=- 7729291447651054566 1 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 d1
a=ice-options:trickle
m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=mid:a1
a=msid:QI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1
QI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1a0
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:7sFvz2gdLkEwjZEr
a=ice-pwd:dOTZKZNVlO9RSGsEGM63JXT2
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:active
a=rtcp-mux
a=rtcp-rsize
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=ssrc:4429951804 cname:Q/NWs1ao1HmN4Xa5
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=mid:d1
a=fmtp:webrtc-datachannel max-message-size=65536
a=sctp-port 5000
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:active
SDP |candidate-B3|:
candidate:109270924 1 udp 2122194687 192.168.2.3 61665 typ host
SDP |candidate-B4|:
candidate:4036177504 1 udp 1685987071 55.66.77.88 64532 typ srflx raddr 192.168.2.3 rport 61665
SDP |offer-B2| (обратите внимание на инкремент номера версии в o= строке, и на строки c= и a=rtcp, в которых указаны выбранные локальные кандидаты):
v=0
o=- 7729291447651054566 2 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 d1 v1 v2
a=ice-options:trickle
m=audio 64532 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 55.66.77.88
a=rtcp:64532 IN IP4 55.66.77.88
a=mid:a1
a=msid:QI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1
QI39StLS8W7ZbQl1sJsWUXkr3Zf12fJUvzQ1a0
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:7sFvz2gdLkEwjZEr
a=ice-pwd:dOTZKZNVlO9RSGsEGM63JXT2
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=ssrc:4429951804 cname:Q/NWs1ao1HmN4Xa5
a=candidate:109270924 1 udp 2122194687 192.168.2.3 61665 typ host
a=candidate:4036177504 1 udp 1685987071 55.66.77.88 64532 typ srflx
raddr 192.168.2.3 rport 61665
a=candidate:3671762467 1 udp 41819903 66.77.88.99 50416 typ relay
raddr 55.66.77.88 rport 64532
a=end-of-candidates
m=application 64532 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 55.66.77.88
a=fmtp:webrtc-datachannel max-message-size=65536
a=sctp-port 5000
a=ice-ufrag:7sFvz2gdLkEwjZEr
a=ice-pwd:dOTZKZNVlO9RSGsEGM63JXT2
a=fingerprint:sha-256 6B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35
:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:actpass
a=candidate:109270924 1 udp 2122194687 192.168.2.3 61665 typ host
a=candidate:4036177504 1 udp 1685987071 55.66.77.88 64532 typ srflx
raddr 192.168.2.3 rport 61665
a=candidate:3671762467 1 udp 41819903 66.77.88.99 50416 typ relay
raddr 55.66.77.88 rport 64532
a=end-of-candidates
m=video 0 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 55.66.77.88
a=bundle-only
a=rtcp:64532 IN IP4 55.66.77.88
a=mid:v1
a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae
f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0
a=sendrecv
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=ssrc:1366781083 cname:Q/NWs1ao1HmN4Xa5
a=ssrc:1366781084 cname:Q/NWs1ao1HmN4Xa5
a=ssrc-group:FID 1366781083 1366781084
m=video 0 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 55.66.77.88
a=bundle-only
a=rtcp:64532 IN IP4 55.66.77.88
a=mid:v1
a=msid:71317484-2ed4-49d7-9eb7-1414322a7aae
f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0
a=sendrecv
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:actpass
a=rtcp-mux
a=rtcp-rsize
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=ssrc:2366781083 cname:Q/NWs1ao1HmN4Xa5
a=ssrc:2366781084 cname:Q/NWs1ao1HmN4Xa5
a=ssrc-group:FID 2366781083 2366781084
SDP |answer-B2| (обратите внимание на использование setup:passive для поддержки существующих DTLS ролей и на использование a=recvonly для индикации, что видео потоки - однонаправленные):
v=0
o=- 4962303333179871723 2 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE a1 d1 v1 v2
a=ice-options:trickle
m=audio 52546 UDP/TLS/RTP/SAVPF 96 0 8 97 98
c=IN IP4 11.22.33.44
a=rtcp:52546 IN IP4 11.22.33.44
a=mid:a1
a=msid:57017fee-b6c1-4162-929c-a25110252400
e83006c5-a0ff-4e0a-9ed9-d3e6747be7d9
a=sendrecv
a=rtpmap:96 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 telephone-event/8000
a=rtpmap:98 telephone-event/48000
a=maxptime:120
a=ice-ufrag:ATEn1v9DoTMB9J4r
a=ice-pwd:AtSK0WpNtpUjkY4+86js7ZQl
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:passive
a=rtcp-rsize
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=ssrc:1732846380 cname:FocUG1f0fcg/yvY7
a=candidate:109270923 1 udp 2122194687 192.168.1.2 51556 typ host
a=candidate:4036177503 1 udp 1685987071 11.22.33.44 52546 typ srflx
raddr 192.168.1.2 rport 51556
a=candidate:3671762466 1 udp 41819903 22.33.44.55 61405 typ relay
raddr 11.22.33.44 rport 52546
a=end-of-candidates
m=application 52546 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 11.22.33.44
a=mid:d1
a=fmtp:webrtc-datachannel max-message-size=65536
a=sctp-port 5000
a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:passive
m=video 52546 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 11.22.33.44
a=rtcp:52546 IN IP4 11.22.33.44
a=mid:v1
a=recvonly
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=fingerprint:sha-256
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:passive
a=rtcp-mux
a=rtcp-rsize
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
m=video 52546 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 11.22.33.44
a=rtcp:52546 IN IP4 11.22.33.44
a=mid:v2
a=recvonly
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04
:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2
a=setup:passive
a=rtcp-mux
a=rtcp-rsize
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
Вопросы безопасности
IETF были опубликованы документы I-D.ietf-rtcweb-security-arch I-D.ietf-rtcweb-security, описывающие архитектуру безопасности WebRTC в целом. В оставшейся части этого раздела описаны соображения по обеспечению безопасности в контексте данного документа.
Не смотря на то, что интерфейс JSEP представляет из себя API, лучше думать о нем, как об интернет протоколе, где браузер не доверяет Javascript коду. Таким образом применима модель уроз из RFC3552. В частности Javascript может вызывать API в любой последовательности с любыми входными данными, в том числе и с вредоносными. Это особенно актуально, когда мы рассматриваем SDP, который передается в setLocalDescription (). В то время как правильное использование API требует, чтобы приложение оперировало SDP, полученными из createOffer () или createAnswer () (возможно, соответствующим образом модифицированными, как описано в разделе 6), однако нет никакой гарантии, что приложение будет так делать. Браузер должен быть готов к тому, что Javascript может вызвать API c некорректными(фиктивными) данными.
С другой стороны, программист должен понимать, что Javascript не имеет полного контроля над поведением браузера. Один случай, который стоит особо отметить - это редактирование ICE кандидатов из SDP или подавление просочившихся (trickled) кандидатов - не имеет ожидаемого поведения: реализации будут по-прежнему выполнять проверки от этих кандидатов, даже если они не отправляются на другую сторону. Так, например, не представляется возможным, предотвратить ситуацию, получения удаленным партнером вашего публичного IP-адреса, удалением рефлексивных серверу (server reflexive) кандидатов. Приложения, которые хотят скрыть свой внешний IP-адрес, вместо этого должны настроить ICE агента на использование только кандидатов-ретрансляторов.
- check for errors <- I'm here
- video codecs
- media server
###thesaurus
session definitions - сессионные датаграммы