Голосовые системы

Подключение WebRTC: полный Trickle ICE

Браузер отправляет готовый SDP сразу, затем передаёт ICE-кандидаты по мере появления. WebRTC ingress одновременно собирает свои кандидаты и проверяет уже доступные пары. Медленный STUN на одном интерфейсе больше не задерживает рабочий TURN/host путь.

Схема 1
Схема 1 · Открыть в полном размере
Исходный Mermaid
sequenceDiagram
  participant B as Браузер
  participant C as WebRTC ingress
  B->>C: GET /core/rtc-capabilities
  C-->>B: trickle_ice: 1
  B->>C: POST /core/offer (trickle: true, SDP)
  C-->>B: SDP answer + connection_id + ice_endpoint + ice_token
  B->>C: WS /core/ice/{connection_id}, auth (token)
  C-->>B: ready
  par Кандидаты по мере появления
    B->>C: candidate / end
  and
    C-->>B: candidate / end
  end
  Note over B,C: ICE checks, DTLS и RTP начинаются до общего end
  B->>C: DELETE /core/ice/{connection_id} при остановке

Протокол и доступ

Маршрут Контракт
GET /core/rtc-capabilities JSON {"trickle_ice":1}. Только 404 включает legacy; ошибка сети/авторизации видна пользователю
POST /core/offer Прежние sdp, type, session_id; необязательный boolean trickle. Answer сохраняет прежние поля, добавляет connection_id, ice_endpoint, ice_token
WS /core/ice/{connection_id} Первое сообщение {"type":"auth","token":"..."}, затем серверный {"type":"ready"}. Авторизация не более 3 секунд
candidate {"type":"candidate","candidate":RTCIceCandidateInit}; sdpMid/sdpMLineIndex принадлежат согласованному audio media; usernameFragment, если есть, принадлежит этому SDP
end {"type":"end"} отдельно от кандидата; завершает сбор в соответствующую сторону
error Безопасное описание ошибки, без тела входного сообщения
DELETE /core/ice/{connection_id} Заголовок X-ICE-Token; 204 при освобождении, 404 при неизвестном подключении/неверном токене

Все маршруты входят в существующий защищённый OAuth /core ingress, включая WS handshake. Токен соединения случайный, только в памяти, не в URL/логах. До его проверки сервер не выдаёт кандидаты; неверный токен не отменяет попытку владельца. Очередь сохраняет ранние кандидаты; предел 128, сообщение до 16 КиБ, дубликаты отбрасываются. Текущий Trickle-профиль — один audio media с RTCP mux, UDP ICE (включая TURN/TCP как транспорт до relay). TCP host-кандидаты Chrome учитываются в лимите и пропускаются без отмены доступных UDP-пар. ICE restart и авто-reconnect не реализованы.

Браузерный ICE-обработчик устанавливается до offer. Локальная очередь переживает получение answer и открытие WS; серверная — появление кандидата до WS auth. Ответ SDP принимается до обработки серверных WS-кандидатов. Старый клиент без trickle продолжает использовать полное серверное gathering и прежний answer.

Адаптер и сроки

trickle_ice.py изолирует внутренние API aiortc==1.14.0 / aioice==0.10.2. Версии проверяются при запуске WebRTC ingress (также в image build) и создании Trickle peer. Обычные peer не используют этот адаптер. Host sockets доступны сразу; STUN/TURN выполняются параллельно, каждый результат публикуется отдельно. ICE не объявляется failed, пока возможны новые кандидаты. После nomination поздние кандидаты не заменяют выбранную пару. Сохраняются DATA indication / peer-reflexive и закрытие STUN-транзакций из aioice_compat.py.

Сигнализация нового клиента — 10 секунд, установление media — 20 секунд после SDP answer. Разрешение микрофона не входит в эти сроки. Незавершённый сбор отдельного кандидата не прерывает работающее аудио. Legacy frontend fallback сохраняет прежний бюджет iceGatherTimeoutMs + 20000.

Потеря ICE-канала при подключении завершает попытку. После connected появляется предупреждение, аудио продолжается. Явная остановка закрывает WS/peer, освобождает STUN/TURN sockets, задачи gathering/checks, таймеры и запись соединения. История, NATS subjects, PCM и ASR mode не меняются.

Проверки и поставка

Реальные aiortc tests устанавливают DTLS, отправляют Opus/RTP и получают PCM при ожидающем STUN на обеих сторонах, в том числе без кандидатов в исходном SDP. Проверяется стабильность поздней пары и закрытие незавершённого TURN allocation. HTTP/WS tests проверяют токен, очередь, MID, ufrag, лимиты, end, DELETE и разрывы. Frontend tests покрывают ранний offer, 404 legacy, ошибки доступа и отмену.

Поставка: backend ingress/Core → consumers Frontend/Ruby с проверкой OCI revision и digest настоящих tasks. Первое переименование заранее готовит полный состав нового namespace. Порядок, сохранение данных и совместимый откат описаны в DEPLOYMENT. TURN и соседние стэки не меняются.

Приёмка в Chrome: пять подключений в той же сети, медиана local SDP → connected менее 3 секунд; выбранный relay/prflx путь, DTLS, растущий RTP, первый decoded PCM, partial → final → ответ ассистента; текстовый чат/история, legacy и отказ новых HTTP/WS без OAuth. HTTP 200 и зелёный CI не заменяют эти сценарии.

Приёмка Trickle ICE · 05.10.2026

Датированный отчёт: пять подключений в той же Chrome-сессии, медиана local SDP → connected 1245 мс вместо 16108 мс. Проверены DTLS/RTP через TURN, partial/final/ответ, текст/история, legacy, OAuth negative и потеря ICE-канала после connected. STUN/TURN сохранены; это измерение подключения в текущей сети, без оценки физического микрофона/WER.

Исходник

Скачать Markdown · К материалам исследования