Live (stanze LiveKit): passare dalla pagina della stanza al widget della chiamata annulla l'iscrizione al video di tutti (mattonelle nere) - con correzione proposta

Ciao a tutti, è la mia prima partecipazione, quindi scusate se non sto formattando questa segnalazione nel modo corretto.

Sto configurando un forum Discourse per un gruppo di appassionati di automobili e volevano utilizzare le chat video live tramite LiveKit per stanze/presentazioni più grandi.

Durante i test abbiamo riscontrato costantemente tessere nere per i flussi video, ma solo quando utilizzavamo l’integrazione LiveKit. Ho usato Claude Code per indagare e ho trovato quello che sembra essere il problema e una soluzione, il report generato da Claude segue.

Sono felice di aprire una PR con una correzione proposta se fosse utile.

Riepilogo

In una stanza LiveKit (SFU), passare dalla pagina della stanza al widget di chiamata fluttuante lascia lo spettatore con tessere nere per tutti gli altri partecipanti. Gli altri partecipanti continuano a vedere correttamente lo spettatore. Tornando alla pagina della stanza, oppure uscendo e rientrando mentre si è nel widget, il video ritorna. Succede ogni volta, perché la distruzione della pagina della stanza invia watching=false dopo che il widget ha inviato watching=true.

Versione

  • Discourse al commit 1f09a33bffd908d6e38ef4b51697e8390e2a4c7f (branch latest, ottobre 2026), plugin Live incluso nel bundle
  • LiveKit Cloud come media server (live_livekit_room_policy = per_room)
  • Firefox 159 su Linux (osservato anche su altri browser e telefoni)

Passi per riprodurre

  1. Due account (uno in una finestra in incognito) entrano nella stessa stanza abilitata per LiveKit con le telecamere attive.
  2. In una finestra, clicca su un link che esce dalla pagina della stanza, quindi la chiamata si sposta nel widget.
  3. In quella finestra, la tessera dell’altro partecipante è nera. L’altra finestra vede entrambi correttamente.

Cosa ho osservato

  • Nella finestra con la tessera nera, l’elemento <video> dell’altra persona ha un flusso, non è in pausa e ha readyState 4. Ma la sua traccia ha readyState: "live", muted: true, dimensione 8×8 e getVideoPlaybackQuality().totalVideoFrames è 0. Nessun mezzo è mai arrivato ad essa.
  • Scheda di rete, passaggio da una pagina → widget: tre chiamate POST /live/rooms/<id>/state, con watching = true, true, false in quest’ordine.

Causa (leggendo il codice al commit sopra)

  • room-page.gjs, willDestroy: next(() => liveWebrtc.setWatching(roomId, false, { keepVideo }))
  • call-widget.gjs: il didInsert del widget chiama watchWidgetRoom, che esegue next(() => setWatching(roomId, true))
  • live-webrtc.js, setWatching: nelle stanze non mesh chiama setVideoSubscriptionsEnabled(watching), che cancella la sottoscrizione alle tracce della camera, dello schermo e dell’audio dello schermo dello spettatore (livekit-session.js, #applyDesiredSubscription)

Il false differito della pagina arriva dopo il true del widget, quindi lo spettatore finisce nello stato di non-osservazione mentre il widget è sullo schermo. Passare da widget → pagina della stanza funziona perché stopWatchingWidgetRoom del widget viene eseguito sincronamente alla distruzione e il true della pagina è differito, quindi il true prevale. Un nuovo ingresso nel widget funziona perché non c’è una vecchia pagina.

Le stanze peer-to-peer non cancellano le sottoscrizioni di nessuno (il ramo SFU viene saltato), il che corrisponde al fatto che questo sia specifico delle stanze LiveKit.

Workaround confermato, nella console del browser

  1. Con la tessera nera, Discourse.__container__.lookup("service:live-webrtc").setWatching(1, true) ha riportato il video.
  2. Questo wrapper, installato prima del passaggio, ha mantenuto la tessera attiva ad ogni passaggio pagina → widget:
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);
};

Correzione suggerita

La distruzione della pagina della stanza non dovrebbe disattivare l’osservazione quando il widget sta per mostrare la stessa stanza. Ad esempio, deriva lo stato di osservazione dalla presenza o meno del montaggio della pagina o del widget, invece di lasciare che ognuno invii la propria impostazione/reset. In questo modo l’ordine non può avere importanza.

Correlato, potrebbe essere separato

Una chiamata POST .../state nella stessa sessione ha ricevuto risposta 429. I fallimenti su queste chiamate vengono ignorati (.catch(() => {})), quindi un rifiuto dell’aggiornamento “watching” fallisce silenziosamente.