Завершение комнаты и подготовка выпуска
Финальное состояние «Разговора» объединяет разрешение устройств, выбор источников, собственную signaling-комнату, peer connection, переговоры, ICE, воспроизведение, mute, экран, восстановление и статистику. Теперь нужно завершать консультацию одним понятным действием и передать проект с точным описанием границ. В этом уроке соберём очистку ресурсов и серверное завершение комнаты, сохраняя различие между подготовленным исходным кодом и проверенным реальным звонком.
Каталог lesson-12 содержит полное состояние клиента и локального сервера. Он предназначен для самостоятельного исследования Анной и Борисом в комнате consult-42. При авторстве приложение не запускалось, зависимости не устанавливались, тесты не выполнялись, камера и экран не запрашивались. Архив и текст дают согласованную исходную поставку с ожидаемым поведением, а не отчёт о проведённой консультации.
Единая очистка
Сначала функция завершения пытается отправить серверу finish, затем помечает локальное состояние конечным. После этого увеличиваются номера поколения и запроса захвата. Такой порядок делает поздние результаты ненужными: старый запрос камеры или конфигурации не должен заново открыть уже завершённую встречу. Повторное завершение возвращается без новой побочной операции.
Затем удаляются таймер reconnect, peer, удалённый поток, экран и локальные дорожки. Каждый ресурс освобождается своим способом. pc.close относится к транспорту, track.stop — к захвату и дорожкам, удаление interval — к диагностике, очищение srcObject — к представлению. Один общий флаг ended недостаточен, если реальные объекты продолжают удерживаться обработчиками или таймерами.
Ниже дан полный finish из src/app.js. Функции dropPeer и stopTracks определены в том же файле. Кнопка завершения и pagehide вызывают одинаковую локальную очистку. Полученное от сервера сообщение ended использует эту функцию без повторного сообщения finish, чтобы стороны не создавали бесконечный обмен завершениями.
function finish(notify=true,reason='Консультация завершена'){
if(state.ended)return;
if(notify && state.ws?.readyState===WebSocket.OPEN)state.ws.send(JSON.stringify({type:'finish'}));
state.ended=true;++state.generation;++state.request;clearTimeout(state.retryTimer);dropPeer();
stopTracks(state.screen);state.screen=null;stopTracks(state.stream);state.stream=null;$('local').srcObject=null;
if(state.ws){state.ws.onclose=null;state.ws.onmessage=null;state.ws.close(1000,'finished');state.ws=null;}
show(reason);
}
После самостоятельного нажатия завершения ожидается остановка собственных tracks, исчезновение потоков у элементов и закрытие signaling. Собеседник должен получить серверное сообщение о завершённой комнате и выполнить свою локальную очистку. Эти эффекты следуют из предусмотренного жизненного цикла; их фактическое время и поведение индикаторов устройств при авторстве не наблюдались.
Доставка последнего сообщения
Вызов WebSocket send перед close выражает намерение отправить конечное сообщение, но уход страницы не гарантирует доставку любого последнего пакета. Браузер и сеть могут завершиться внезапно. Поэтому сервер также обрабатывает исчезновение соединения и heartbeat, а затем ограниченное ожидание возвращения. Нельзя полагаться исключительно на красивую кнопку, если процесс может пропасть без её нажатия.
При pagehide не следует ожидать бесконечно долгую асинхронную очистку. Основные ресурсы страницы освобождаются локально, а сервер должен иметь собственный путь обнаружения потери участника. Этот принцип полезен для любой комнаты: клиент сообщает намерение, но сервер управляет membership и сроком жизни независимо от надёжности последнего пользовательского события.
Конечная комната
В примере closeRoom помечает объект консультации завершённым, уведомляет участников и прекращает приём новых upgrade. Повторный reconnect не оживляет эту встречу. Для нового локального упражнения создаётся новый экземпляр сервера. В реальном сервисе новая консультация должна иметь собственный разрешённый объект, связанный с бронированием, участниками и сроком действия.
Завершение не равнозначно долговременному журналу встреч. Наш сервер держит membership и сессии в памяти. Он не сохраняет оплату, согласие записи, запись разговора или аудиторский протокол. Если продукту нужны эти функции, их нужно проектировать и хранить отдельно. Наличие серверного finish лишь завершает текущий учебный транспортный объект комнаты.
Сервер назначает identity отправителя и epoch пары, проверяет Origin и ограничивает форму сообщений. Это необходимые учебные границы, но локальные пароли из окружения не являются промышленными аккаунтами. Перед публичным использованием понадобятся HTTPS, защищённые cookie, полноценный вход и отзыв сессий. Допуск к конкретной встрече должен связываться с бизнес-ресурсом, а не только с именем, которое прислал браузер.
Инфраструктура медиа
TURN задаётся явно владельцем процесса. Пустой iceServers не обещает внешнюю достижимость, а STUN не заменяет ретранслятор для всех сетей. Для выпуска потребуется рабочий TURN с короткоживущими credentials, подходящими транспортами, наблюдаемой доступностью и бюджетом трафика. Пример не создаёт такую инфраструктуру и не содержит реальных ключей или скрытого публичного demo-provider.
Двухместная peer-to-peer модель также имеет масштабную границу. Для группового звонка нельзя просто добавить десятки людей в ту же карту и считать задачу решённой. Понадобятся другая организация медиапотоков, возможный SFU, новые полномочия и бюджет клиента. Этот курс завершает конкретную двухстороннюю консультацию, поэтому её ограничения должны оставаться заметными при дальнейшем развитии.
Устройство и интерфейс
Нужно самостоятельно наблюдать выбранные браузеры, разрешение и отказ камеры, исчезновение источника, autoplay, mute, screen ended и поздний ответ захвата после ухода. Несколько sender меняются последовательно, поэтому частичный отказ замены требует более полного rollback. Такая граница указана в исходниках и тексте, чтобы подготовленный пример не воспринимался как скрыто проверенный универсальный контроллер оборудования.
Статистика выводит только данные собственного соединения после появления выборок. Здесь нет подставленных скоростей, снимков предполагаемого звонка и результата вымышленной relay-проверки. Перед эксплуатацией можно создать диагностическую процедуру с фактическими данными и понятной политикой журналов. Не нужно отправлять полные IP-адреса и SDP в публичный отчёт только потому, что они доступны API.
Передача проекта
В архиве каждое состояние сохраняет одинаковые имена комнаты и участников, а номер этапа включает изученные функции общего контроллера. Верхнеуровневая версия ws зафиксирована; lockfile не создавался, поскольку установка запрещена запросом автора. Это ограничение воспроизводимости раскрыто прямо. Читателю дана карта файлов и ожидаемые эффекты, которые предстоит проверить в разрешённом локальном окружении.
Учебный результат завершён, когда вы можете проследить путь от пользовательского разрешения до освобождения последней дорожки и объяснить роль каждого слоя. Подготовка исходной поставки не заменяет реальную проверку браузеров, сети и TURN. Такая честная граница позволяет продолжать работу предметно: конкретные недостающие проверки и инфраструктура известны, а исходные уроки и пример можно изучать без выдуманных доказательств успешности.
Оглавление серии · Исходные состояния проекта.
API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.