# Live (LiveKit rooms): moving from the room page to the call widget unsubscribes you from everyone's video (black tiles) - w/ proposed fix

**URL:** <https://meta.discourse.org/t/live-livekit-rooms-moving-from-the-room-page-to-the-call-widget-unsubscribes-you-from-everyones-video-black-tiles-w-proposed-fix/417380>\
**Category:** Bug\
**Tags:** live\
**Created:** [November 13, 2026, 6:00pm UTC](https://meta.discourse.org/t/live-livekit-rooms-moving-from-the-room-page-to-the-call-widget-unsubscribes-you-from-everyones-video-black-tiles-w-proposed-fix/417380 "2026-11-13T18:00:52Z")\
**Posts on this page:** 1\
**Page:** 1

## AI-generated summary

[sperry](/t/-/417380/1), a first-time contributor setting up a Discourse forum for an automotive group, reports a bug in the Live (LiveKit) integration where video tiles turn black when navigating from a room page to the floating call widget. The issue occurs specifically in LiveKit (SFU) rooms: after moving to the widget, the viewer stops receiving video from other participants, while those participants can still see the viewer. Rejoining or navigating back to the room page temporarily restores the video.

The root cause is a race condition in the `watching` state updates. When leaving the room page, the teardown logic sends a `watching=false` update. However, the call widget simultaneously sends a `watching=true` update. If the room page's `false` update is processed *after* the widget's `true` update, the SFU unsubscribes the viewer's video tracks, resulting in black tiles. This does not happen in peer-to-peer rooms or when switching from widget to page because of the timing of synchronous versus deferred calls.

Evidence includes the viewer's video element showing `muted: true` and `totalVideoFrames: 0` despite having a live stream, and network logs showing `watching` states updating in the order `true, true, false`. A confirmed workaround is manually calling `setWatching(roomId, true)` via the console. sperry provided a temporary JS wrapper that delays `false` updates to ensure the widget's state takes precedence.

The suggested permanent fix is to derive the `watching` state from whether either the room page or the call widget is mounted, rather than letting each component independently send conflicting set/clear updates. This would prevent ordering issues. Additionally, the report notes that failed `POST /live/rooms/<id>/state` requests (e.g., 429 errors) are silently caught, which could exacerbate state desyncs. sperry has offered to submit a pull request with a proposed fix.

<div class="post-metadata">

**Author:** ![sperry](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sperry/32/584571_2.png) [@sperry](https://meta.discourse.org/u/sperry)\
**Post date:** [November 13, 2026, 6:00pm UTC](https://meta.discourse.org/t/live-livekit-rooms-moving-from-the-room-page-to-the-call-widget-unsubscribes-you-from-everyones-video-black-tiles-w-proposed-fix/417380/1 "2026-11-13T18:00:52Z")

</div>

Hi all, first time contributor, so sorry if I’m not formatting this the proper way.

I’m setting up a Discourse forum for an automotive enthusiast group and they wanted to use Live video chats via LiveKit for larger rooms/presentations.

During testing we consistently ran into black tiles for video streams, but only when using the LiveKit integration. I used Claude Code to investigate and found what looks like the issue and a fix, the Claude generated report follows.

I’m happy to make a PR with a proposed fix if that would be helpful.

**Summary**

In a LiveKit (SFU) room, going from the room page to the floating call widget leaves the viewer with black tiles for everyone else. The other participants still see the viewer fine. Going back to the room page, or leaving and rejoining while in the widget, brings the video back. It happens every time, because the room page’s teardown sends `watching=false` _after_ the widget has sent `watching=true`.

**Version**

- Discourse at commit `1f09a33bffd908d6e38ef4b51697e8390e2a4c7f` (the `latest` branch, 2026-10), Live plugin as bundled
- LiveKit Cloud as the media server (`live_livekit_room_policy = per_room`)
- Firefox 159 on Linux (also observed on other browsers and phones)

**Steps**

1. Two accounts (one in a private window) join the same LiveKit-enabled room with cameras on.
2. In one window, click a link that leaves the room page, so the call moves to the widget.
3. In that window the other participant’s tile is black. The other window sees both fine.

**What I saw**

- In the black window, the other person’s `<video>` has a stream, is not paused and has `readyState` 4. But its track is `readyState: "live"`, `muted: true`, `8×8`, and `getVideoPlaybackQuality().totalVideoFrames` is 0. No media ever arrived on it.
- Network tab, one page → widget switch: three `POST /live/rooms/<id>/state` calls, with `watching` = **true, true, false** in that order.

**Cause (from reading the code at the commit above)**

- `room-page.gjs`, `willDestroy`: `next(() => liveWebrtc.setWatching(roomId, false, { keepVideo }))`
- `call-widget.gjs`: the widget’s `didInsert` calls `watchWidgetRoom`, which runs `next(() => setWatching(roomId, true))`
- `live-webrtc.js`, `setWatching`: on non-mesh rooms it calls `setVideoSubscriptionsEnabled(watching)`, which unsubscribes the viewer’s camera, screen and screen-audio tracks (`livekit-session.js`, `#applyDesiredSubscription`)

The page’s deferred `false` lands after the widget’s `true`, so the viewer ends up in the not-watching state while the widget is on screen. Going widget → room page works because the widget’s `stopWatchingWidgetRoom` runs synchronously on destroy and the page’s `true` is deferred, so `true` wins. A fresh join in the widget works because there is no old page.

Peer-to-peer rooms don’t unsubscribe anyone (the SFU branch is skipped), which would match this being specific to LiveKit rooms.

**Confirmed workaround, in the browser console**

1. With the tile black, `Discourse. __container__.lookup("service:live-webrtc").setWatching(1, true)` brought the video back.
2. This wrapper, installed before switching, kept the tile up on every page → widget switch:

```js
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);
};

```

**Suggested fix**

The room page’s teardown shouldn’t turn watching off when the widget is about to show the same room. For example, derive the watching state from whether the page or the widget is mounted, instead of letting each send its own set/clear. That way ordering can’t matter.

**Related, may be separate**

One `POST .../state` in the same session was answered with **429**. Failures on these calls are ignored (`.catch(() => {})`), so a refused “watching” update fails silently.
