皆さん、こんにちは。初参加なので、書式が不適切でしたら申し訳ありません。
私は、自動車趣味のコミュニティ向けに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 (他のブラウザやスマホでも同様に観測)
手順
- 2つのアカウント (片方はシークレットウィンドウ) で、カメラをオンにした状態の同一の LiveKit ルームに参加します。
- 1つのウィンドウで、ルームページから離れるリンクをクリックし、コールがウィジェットに移動します。
- そのウィンドウでは、他の参加者のタイルが黒くなります。もう一方のウィンドウでは両方とも正常に表示されています。
観察された内容
- 黒いタイルが表示されているウィンドウでは、他の人の
<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ルーム特有であることと一致しています。
確認済みの回避策 (ブラウザコンソールで)
- タイルが黒い状態で、
Discourse.__container__.lookup("service:live-webrtc").setWatching(1, true)を実行すると、ビデオが復帰しました。 - このラッパーを、切り替え前にインストールすることで、すべてのページ→ウィジェット切り替えでタイルが正常に保たれました:
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” 更新は静かに失敗します。