Live (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)
  • Linux 上の Firefox 159 (他のブラウザやスマホでも同様に観測)

手順

  1. 2つのアカウント (片方はシークレットウィンドウ) で、カメラをオンにした状態の同一の LiveKit ルームに参加します。
  2. 1つのウィンドウで、ルームページから離れるリンクをクリックし、コールがウィジェットに移動します。
  3. そのウィンドウでは、他の参加者のタイルが黒くなります。もう一方のウィンドウでは両方とも正常に表示されています。

観察された内容

  • 黒いタイルが表示されているウィンドウでは、他の人の <video> 要素にはストリームがあり、一時停止されておらず、readyState は 4 です。しかし、そのトラックは readyState: "live"、muted: true、解像度 8×8 であり、getVideoPlaybackQuality().totalVideoFrames は 0 です。メディアは一度も到着していません。
  • ネットワークタブを確認すると、1つのページからウィジェットへの切り替え時に、3回の 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 の後に到達するため、ウィジェットが画面上に表示されているにもかかわらず、視聴者は “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” 状態を導出し、それぞれが独自に設定/クリアを行うのではなく、この方法で順序が影響しないようにします。

関連する可能性のある別問題

同じセッション内の1回の POST .../state に対する応答として 429 が返されました。これらの呼び出しの失敗は (catch(() => {}) により) 無視されているため、拒否された “watching” 更新は静かに失敗します。