Live (комнаты LiveKit): переход со страницы комнаты в виджет звонка отписывает вас от видео всех участников (черные плитки) — с предложенным исправлением

Всем привет. Я новичок в контрибуции, поэтому извините, если форматирование неверное.

Я настраиваю форум Discourse для сообщества автомобильных энтузиастов, и они хотели использовать живые видеочаты через LiveKit для больших комнат/презентаций.

Во время тестирования мы постоянно сталкивались с черными плитками видеопотоков, но только при использовании интеграции с LiveKit. Я использовал Claude Code для расследования и обнаружил то, что выглядит как причина проблемы и её решение. Ниже приведен отчет, сгенерированный Claude.

Я буду рад создать PR с предложенным исправлением, если это будет полезно.

Резюме

В комнате LiveKit (SFU) переход со страницы комнаты в плавающий виджет звонка оставляет зрителя с черными плитками всех остальных участников. Другие участники по-прежнему корректно видят зрителя. Возвращение на страницу комнаты или выход из виджета и повторное присоединение во время работы виджета возвращает видео. Это происходит каждый раз, потому что при закрытии страницы комнаты отправляется сигнал watching=false после того, как виджет уже отправил watching=true.

Версия

  • Discourse на коммите 1f09a33bffd908d6e38ef4b51697e8390e2a4c7f (ветка latest, октябрь 2026), плагин Live в стандартной поставке
  • LiveKit Cloud в качестве медиа-сервера (live_livekit_room_policy = per_room)
  • Firefox 159 на Linux (также наблюдали на других браузерах и телефонах)

Шаги для воспроизведения

  1. Две учетные записи (одна в приватном окне) присоединяются к одной и той же комнате LiveKit с включенными камерами.
  2. В одном окне нажмите ссылку, которая покидает страницу комнаты, в результате чего звонок переходит в виджет.
  3. В этом окне плитка другого участника становится черной. В другом окне оба участника видны нормально.

Что я наблюдал

  • В окне с черным экраном тег <video> другого участника имеет поток, не на паузе и имеет readyState 4. Однако его дорожка имеет readyState: "live", muted: true, разрешение 8×8, и getVideoPlaybackQuality().totalVideoFrames равно 0. Медиаданные никогда не поступали на него.
  • Вкладка сети, переключение со страницы на виджет: три запроса POST /live/rooms/<id>/state со значением watching равным true, true, false в указанном порядке.

Причина (на основе анализа кода в указанном коммите)

  • room-page.gjs, willDestroy: next(() => liveWebrtc.setWatching(roomId, false, { keepVideo }))
  • call-widget.gjs: метод didInsert виджета вызывает watchWidgetRoom, который запускает next(() => setWatching(roomId, true))
  • live-webrtc.js, setWatching: для не-mesh комнат вызывается setVideoSubscriptionsEnabled(watching), что отключает подписки на камеры, демонстрацию экрана и аудио демонстрации зрителя (livekit-session.js, #applyDesiredSubscription)

Отложенный сигнал false от страницы приходит после сигнала true от виджета, в результате чего зритель оказывается в состоянии “не смотрит”, пока виджет находится на экране. Переход виджет → страница комнаты работает, потому что stopWatchingWidgetRoom выполняется синхронно при уничтожении виджета, а сигнал true от страницы откладывается, поэтому побеждает true. Новое присоединение через виджет работает, потому что нет старой страницы.

В p2p комнатах никто не отписывается (ветка SFU пропускается), что согласуется с тем, что проблема специфична для комнат LiveKit.

Подтвержденное обходное решение через консоль браузера

  1. При черной плитке выполнение Discourse.__container__.lookup("service:live-webrtc").setWatching(1, true) восстановило видео.
  2. Этот обертка, установленная перед переключением, поддерживала работу плитки при каждом переключении страница → виджет:
const svc = Discourse.__container__.lookup("service:live-webrtc");
const orig = svc.setWatching.bind(svc);
svc.setWatching = (roomId, watching, options = {}) => {
  if (watching) return orig(roomId, true, options);
  setTimeout(() => {
    const shown = document.querySelector(".live-call-widget, .live-room-page");
    orig(roomId, !!shown, options);
  }, 500);
};

Предлагаемое исправление

При закрытии страницы комнаты не следует отключать состояние “watching”, если виджет собирается показать ту же самую комнату. Например, состояние “watching” должно определяться тем, смонтированы ли страница или виджет, вместо того чтобы каждый из них отправлял свои сигналы set/clear. Таким образом, порядок отправки не будет иметь значения.

Связанный вопрос, возможно, отдельная проблема

Один запрос POST .../state в том же сеансе получил ответ 429. Ошибки при этих вызовах игнорируются (.catch(() => {})), поэтому отказанные обновления “watching” молча игнорируются.