Обрыв соединения и ICE restart
Активная консультация может потерять сетевой маршрут или signaling. У этих обрывов разные последствия: peer может нуждаться в новом ICE-поиске, а серверная комната — в новом поколении пары. В этом уроке добавим контролируемое восстановление и разберём, когда вызывается restartIce, когда создаётся новый peer и когда автоматические попытки должны прекратиться.
Состояние lesson-10 продолжает все существующие ресурсы захвата. Краткий обрыв не требует скрытого нового запроса камеры или экрана. Локальные потоки остаются принадлежать странице, пока пользователь не завершил встречу. Provider сигнализации получает повторные попытки с увеличивающейся задержкой, а при новом ready клиент строит peer для нового epoch и освобождает прежние транспортные объекты.
ICE restart
restartIce просит peer начать новый ICE-процесс при следующем согласовании. Метод не означает, что связь уже восстановлена. Он вызывает необходимость переговоров, после чего существующий perfect negotiation передаёт новое описание и кандидаты. Поэтому операция имеет смысл только при доступной signaling-сессии и активном текущем peer. Сама функция не создаёт TURN и не исправляет недействительные credentials.
Состояние failed служит одним основанием для restart. disconnected может быть кратковременным, например при переключении сети, поэтому в нашем коде используется небольшая пауза перед действием. Если соединение за это время вернулось, таймер очищается. Конкретная задержка — учебная политика, а не фактически измеренный срок восстановления мобильных сетей.
Ниже дан точный фрагмент onconnectionstatechange из src/app.js. Он проверяет, что событие относится к текущему объекту. state.disconnectTimer принадлежит активному поколению, а функция dropPeer удаляет его при освобождении. WebSocket проверяется перед restart, поскольку новые переговорные данные должны иметь канал доставки.
pc.onconnectionstatechange=()=>{
if(pc!==state.pc)return;show(`Соединение: ${pc.connectionState}`);
if(LESSON>=10){
if(pc.connectionState==='failed' && state.ws?.readyState===WebSocket.OPEN)pc.restartIce();
clearTimeout(state.disconnectTimer);
if(pc.connectionState==='disconnected')state.disconnectTimer=setTimeout(()=>{if(pc===state.pc && pc.connectionState==='disconnected' && state.ws?.readyState===WebSocket.OPEN)pc.restartIce();},5000);
}
};
Потеря signaling
Когда WebSocket закрывается по обычному сетевому сбою, клиент очищает старый peer и epoch, затем планирует повторное соединение. Это выбранная учебная политика комнаты: сервер тоже считает транспортного участника вышедшим. Хотя медиа технически может какое-то время работать независимо от signaling, приложение не выдаёт старую пару за новую разрешённую сессию после повторного входа.
Задержка повторных попыток растёт до предела и имеет случайный разброс. Отказы по политике, неверный протокол и явное нормальное завершение не повторяются автоматически. Если сессия истекла, нужно заново выполнить разрешённый вход; если встреча завершена сервером, reconnect не должен её воскресить. Ошибка доступа требует другого действия, чем временная недоступность сети.
После самостоятельного краткого обрыва ожидается повторный WebSocket-вход, новое сообщение ready при наличии двух участников и новый peer. Если пропал только пригодный медиамаршрут при доступном signaling, ожидается переговорный процесс ICE restart. Эти сценарии по исходному коду не запускались при авторстве, поэтому время восстановления, успешность в конкретной сети и число попыток не измерены.
Новое поколение пары
Сервер создаёт новый случайный epoch при возвращении двух участников. Клиент очищает очередь кандидатов, переговорные флаги и ссылки обработчиков старого объекта. Поздние сообщения с прежним epoch отвергаются. Такая очистка предотвращает попытку применить старый answer к новой local description, которая могла бы дать трудную для понимания ошибку signaling state.
Асинхронная загрузка ICE-конфигурации также проверяет номер поколения. Представьте, что запрос /ice ещё выполнялся, когда Борис снова вышел. Создавать после его ухода уже устаревший peer бессмысленно. Контроллер сравнивает generation после ожидания и прекращает продолжение. Это правило владения ресурсом сохраняется независимо от того, занимает ли реальный запрос доли секунды или больше.
Захват остаётся отдельным
Локальные camera и microphone tracks при временном обрыве могут оставаться активными для возврата. Экран, уже выбранный пользователем, также не требуется запрашивать снова, пока дорожка пригодна. Новый video sender получает текущий активный источник. Однако окончательное завершение останавливает все эти дорожки, поэтому reconnect не должен использовать освобождённый stream или открывать новый системный диалог без явного действия.
Сохранение захвата имеет интерфейсную цену: человек должен видеть, что локальная камера всё ещё включена, хотя собеседника нет. В реальном продукте можно выбрать автоматическую паузу после заданного срока отсутствия пары. Такая политика должна быть явной и не путать mute с окончательным stop. Учебное состояние сохраняет ресурсы, чтобы разобрать сетевое восстановление отдельно от нового разрешения устройства.
Серверный срок возвращения
Оставшийся участник получает peer-left, а комната ждёт ограниченное время. Если второй не возвращается, сервер завершает встречу и больше не принимает вход в данный объект консультации. Это предотвращает бесконечное существование забытых комнат. Для публичного сервиса длительность ожидания, продление и новая встреча должны связываться с бронированием и полномочиями, а не только локальным таймером.
Параллельно heartbeat обнаруживает потерянные WebSocket-соединения, которые ещё не дали обычное событие закрытия. Его период влияет на скорость обнаружения, но не является обещанием мгновенного присутствия. Сервер должен удалять участника и освобождать место комнаты, а браузер — сообщать неопределённость своей связи. Наличие работающего peer не заменяет актуальность серверного membership.
Ограничение повторных попыток
Учебный код не реализует сложный бюджет числа ICE restart для длительного нестабильного соединения. Перед выпуском полезно ограничить попытки, наблюдать причины отказа и предложить человеку явное повторение или смену сети. Иначе интерфейс может бесконечно возвращаться к подключению, скрывая постоянную ошибку TURN. Повторение без изменения условий не является самостоятельным решением любого сетевого отказа.
Для самостоятельного анализа разделите четыре события: краткий disconnected, окончательный ICE failed, закрытие signaling и сообщение ended. Каждое должно вести к определённому действию, а не к одной универсальной функции «заново всё». Результат урока — восстановление с явными поколениями и границами прекращения. Далее добавим статистику для осторожного исследования фактического транспорта после разрешённого запуска.
Полезно явно обозначить кнопку конечного завершения во время ожидания reconnect. Пользователь сохраняет право закончить встречу, даже когда сервер недоступен. Локальная очистка не должна ждать успешного возврата signaling, иначе оборудование оставалось бы активным неопределённо долго. Сервер узнает о потере участника своим путём. Такое разделение локального ресурса и удалённого membership делает конечное действие предсказуемым при любом состоянии сети.
Перед повторным WebSocket клиент также проверяет endpoint действующей сессии. Если сервер уже потерял сессию или завершил комнату, интерфейс предлагает новый разрешённый вход вместо бесконечного повторения upgrade. При закрытии прежняя ссылка WebSocket очищается, чтобы форма действительно могла начать следующую попытку, а явный вход удаляет старый retry timer.
Оглавление серии · Исходные состояния проекта.
API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.