라이브(LiveKit 방): 방 페이지에서 통화 위젯으로 이동 시 모든 사용자의 영상 구독 해제(검은색 타일) - 수정 제안 포함

안녕하세요, 모든 분! 첫 기여자라 제대로 형식을 맞추지 못했다면 죄송합니다.

자동차 애호가 그룹을 위해 Discourse 포럼을 설정하고 있으며, 대형 룸/발표 시 LiveKit을 통한 실시간 화상 채팅을 사용하고 싶어 합니다.

테스트 중 LiveKit 통합을 사용할 때만 비디오 스트림에 검은색 타일이 표시되는 문제가 지속적으로 발생했습니다. Claude Code를 사용하여 조사한 결과, 문제점과 수정 방법을 발견한 것 같습니다. 아래는 Claude가 생성한 보고서입니다.

유용하다면 제안된 수정 사항으로 PR을 만들 수 있습니다.

요약

LiveKit(SFU) 룸에서 룸 페이지를 부동 콜 위젯으로 전환하면 시청자에게 다른 모든 사용자의 타일이 검은색으로 표시됩니다. 다른 참여자들은 시청자를 정상적으로 볼 수 있습니다. 룸 페이지로 돌아가거나, 위젯 상태인 채로 나갔다 다시 입장하면 비디오가 다시 표시됩니다. 이는 위젯이 watching=true를 보낸 후에 룸 페이지의 종료 처리가 watching=false를 보내기 때문에 매번 발생합니다.

버전

  • Discourse 커밋 1f09a33bffd908d6e38ef4b51697e8390e2a4c7f (latest 브랜치, 2026-10), 번들된 대로 Live 플러그인
  • 미디어 서버로 LiveKit Cloud (live_livekit_room_policy = per_room)
  • 리눅스용 Firefox 159 (다른 브라우저 및 휴대폰에서도 관찰됨)

단계

  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: 메시가 아닌 룸의 경우 setVideoSubscriptionsEnabled(watching)를 호출하여 시청자의 카메라, 화면 및 화면 오디오 트랙을 구독 취소합니다 (livekit-session.js, #applyDesiredSubscription).

페이지의 지연된 false가 위젯의 true보다 나중에 도착하므로, 위젯이 화면에 있는 동안 시청자는 not-watching 상태가 됩니다. 위젯 → 룸 페이지 전환이 작동하는 이유는 위젯의 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” 업데이트는 조용히 실패합니다.