Перейти к содержанию

RTCPeerConnection и отправители

В разрешённой комнате «Разговора» уже находятся два участника. Теперь создадим объект RTCPeerConnection и добавим локальные дорожки для отправки. Peer connection отвечает за согласование и медиатранспорт, но не знает, какую консультацию разрешил сервер. Мы связываем эти уровни своим контроллером: создавать соединение можно только после серверного сообщения ready для текущего поколения комнаты.

Состояние lesson-04 продолжает локальный MediaStream и signaling. У каждого браузера появляется собственный peer connection и отдельный контейнер удалённых дорожек. Автоматический обмен offer и answer включится в следующем уроке. Сейчас результатом является правильная организация объектов и обработчиков; сам по себе вызов конструктора не обещает, что собеседник уже слышит микрофон.

Один объект на встречу

RTCPeerConnection представляет состояние соединения текущего браузера с удалённым участником. В нашей двухместной комнате нужен один такой объект на активную пару. При смене epoch старый объект закрывается, а новый создаётся заново. Это предотвращает смешивание обработчиков и кандидатов от разных попыток консультации, даже если имена пользователей и URL комнаты совпадают.

Для начального локального состояния конфигурация iceServers пуста. Это не рекомендация для связи через Интернет: здесь мы ещё не изучили STUN и TURN. Конструктор принимает будущую конфигурацию, а сам выбор сетевого пути выполняется позднее ICE-процессом. Не следует считать отсутствие исключения в конструкторе подтверждением, что обе сети допускают прямое соединение.

Ниже показан фрагмент создания peer из src/app.js. Поток уже получен через явное превью, а функция dropPeer освобождает прежнее состояние. Полный контроллер в архиве использует номер поколения, чтобы позднее асинхронное создание после нового события комнаты не подключило устаревший объект. Текст ниже разбирает активную часть четвёртого состояния.

 const pc=new RTCPeerConnection(config);state.pc=pc;state.remote=new MediaStream();
 for(const track of state.stream.getTracks()){
  const active=track.kind==='video' && state.screen?state.screen.getVideoTracks()[0]:track;
  pc.addTrack(active,state.stream);
 }
 pc.ontrack=({track})=>{
  if(pc!==state.pc)return;
  if(!state.remote.getTracks().some(x=>x.id===track.id))state.remote.addTrack(track);
  if(LESSON>=7){$('remote').srcObject=state.remote;playRemote();}else show(`Удалённая дорожка: ${track.kind}`);
 };

От дорожки к sender

Для каждой локальной дорожки вызывается pc.addTrack(track, stream). Метод создаёт или использует соответствующую отправляющую часть соединения и возвращает RTCRtpSender. Объект sender представляет отправку дорожки, а MediaStreamTrack представляет источник. Такое разделение понадобится при замене камеры экраном: источник меняется, а согласованный отправитель может остаться тем же.

Передача stream помогает удалённой стороне связать дорожки в поток через сведения сигнализации. Но код приёма не должен предполагать, что event.streams всегда содержит единственный объект. Дорожка может быть связана с потоками иначе или прийти без удобного готового контейнера. В «Разговоре» мы собираем свой MediaStream из полученных tracks, чтобы явно управлять удалённым представлением.

Одна дорожка не должна случайно добавляться снова при каждом обновлении UI. Повторные вызовы способны создавать ненужные изменения согласования или ошибку. Поэтому локальный поток добавляется при создании нового peer. Для смены уже отправляемого источника позднее применяется replaceTrack с найденным sender, а не бесконтрольный новый addTrack на каждое нажатие.

Приём дорожек

Событие track сообщает о полученной удалённой дорожке. Оно не является само по себе сигналом, что видеоплеер уже воспроизводит кадры. Между появлением дорожки, поступлением медиа и выполнением браузерного play существуют разные состояния. Мы сначала добавляем track в контейнер и отдельно управляем воспроизведением, когда дойдём до соответствующего урока.

Контейнер проверяет ID, чтобы не добавлять одну и ту же дорожку повторно. Поле вида audio или video помогает показать, что именно получено. Для интерфейса двухместной консультации одна камера и один микрофон достаточны, но WebRTC способен иметь несколько отправляющих направлений. Поэтому поиск sender по виду дорожки является конкретным ограничением нашего проекта, а не универсальным адресованием любого звонка.

После самостоятельного входа двух участников ожидается создание peer и добавление локальных tracks. Удалённые tracks без offer/answer ещё не должны появиться. На этом этапе можно читать объектную структуру, но нельзя выдавать состояние за рабочую консультацию. Авторский запуск, захват камеры и наблюдение событий не проводились; сетевой результат появится только как ожидаемый сценарий дальнейших уроков.

Изменения согласования

Добавление дорожек может вызвать negotiationneeded: браузер сообщает, что параметры соединения необходимо согласовать. Это событие связано с состоянием peer, а не с кнопкой «Позвонить» как бизнес-действием. Один пользовательский шаг способен вызвать переговорный процесс, а изменение источника иногда обходится без полного нового согласования. Следующий урок обработает событие устойчивым шаблоном переговоров.

Обработчики должны быть связаны с текущим объектом. Если старый peer получил позднее событие после смены комнаты, оно не должно изменить статус нового. В контроллере проверяется совпадение pc с текущим state.pc, а при освобождении ссылки обработчиков снимаются. Такой подход делает асинхронный поток предсказуемее, особенно во время повторного входа и обрыва.

Потоки и жизненный цикл

Закрытие peer останавливает его транспорт, но не является автоматическим обещанием освобождения всех ранее полученных локальных источников. Камера существует в state.stream независимо от объекта соединения. При временной смене пары локальное превью можно оставить для возвращения. При окончательном завершении консультации нужно отдельно остановить дорожки камеры, микрофона и экрана.

Удалённый контейнер тоже освобождается: ссылка srcObject очищается, полученные дорожки больше не удерживаются страницей. Смысл этой операции — завершить собственное представление, а не удалённо выключить физическую камеру Бориса. У каждого браузера свои ресурсы захвата, и сигнал о завершении комнаты лишь предлагает другой стороне выполнить её локальную очистку.

Для самостоятельного анализа нарисуйте связь четырёх объектов: локальная дорожка, sender, peer connection и удалённая дорожка. Объясните, почему смена камеры и закрытие видеоплеера затрагивают разные звенья. Такое разделение подготавливает следующий результат: обмен согласованными offer и answer, который соединит уже созданные отправляющие и принимающие части.

Если пользователь завершил страницу во время асинхронной подготовки конфигурации, новый peer уже не нужен. Проверка поколения после ожидания должна остановить продолжение функции. Созданный позже объект иначе удерживал бы обработчики и мог бы изменить статус завершённой консультации. Такая проверка относится к владению ресурсом в контроллере, а не к успешности ICE. Она полезна даже тогда, когда сервер отвечает быстро и обычный сценарий выглядит последовательным.

Оглавление серии · Исходные состояния проекта.

API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.