fix(video): show avatar tiles for audio-only and camera-off callers (#325)

Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
Co-committed-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
This commit is contained in:
Aditya Chaudhary 2026-07-20 23:34:06 +00:00 committed by hodlbod
parent caa16b6770
commit 16a446095a
2 changed files with 12 additions and 8 deletions

View file

@ -131,7 +131,7 @@ export const videoCallLayout = writable<VideoCallLayout>(VideoCallLayout.Split)
export const videoPrimaryTileKey = writable<string | undefined>(undefined) export const videoPrimaryTileKey = writable<string | undefined>(undefined)
export const videoTileCount = derived( export const videoTileCount = derived(
[currentCallSession, callState, videoTrackRevision], [currentCallSession, callState, videoTrackRevision, participantMediaState],
([$session, $state]) => { ([$session, $state]) => {
if ($state !== CallState.Connected || !$session) return 0 if ($state !== CallState.Connected || !$session) return 0
return countLiveVisualFeeds($session) return countLiveVisualFeeds($session)
@ -478,7 +478,7 @@ const countLiveVisualFeeds = (session: CallSession): number => {
for (const rp of livekit.remoteParticipants.values()) { for (const rp of livekit.remoteParticipants.values()) {
for (const source of VISUAL_SOURCES) { for (const source of VISUAL_SOURCES) {
const pub = rp.getTrackPublication(source) const pub = rp.getTrackPublication(source)
if (pub?.isSubscribed && pub.track) n += 1 if (pub?.isSubscribed && pub.track && !pub.isMuted) n += 1
} }
} }
return n return n

View file

@ -21,7 +21,9 @@
currentCallSession, currentCallSession,
callTargetRoom, callTargetRoom,
mediaStateByIdentity, mediaStateByIdentity,
participantMediaState,
pubkeyFromLiveKitIdentity, pubkeyFromLiveKitIdentity,
videoTrackRevision,
} from "@app/call" } from "@app/call"
type Props = { type Props = {
@ -74,6 +76,10 @@
const videoTiles = $derived.by(() => { const videoTiles = $derived.by(() => {
const session = $currentCallSession const session = $currentCallSession
// LiveKit mutates remoteParticipants/tracks in place; these stores are what
// actually change on join/leave and track subscribe/unsubscribe.
void $participantMediaState
void $videoTrackRevision
if (!session || $callTargetRoom?.url !== url || $callTargetRoom?.h !== h) { if (!session || $callTargetRoom?.url !== url || $callTargetRoom?.h !== h) {
return [] return []
} }
@ -106,7 +112,8 @@
for (const rp of livekit.remoteParticipants.values()) { for (const rp of livekit.remoteParticipants.values()) {
const camPub = rp.getTrackPublication(Track.Source.Camera) const camPub = rp.getTrackPublication(Track.Source.Camera)
if (camPub?.isSubscribed && camPub.track) { // Camera off mutes the publication rather than unsubscribing; still render avatar.
if (camPub?.isSubscribed && camPub.track && !camPub.isMuted) {
videoTiles.push({ videoTiles.push({
liveKitIdentity: rp.identity, liveKitIdentity: rp.identity,
isLocal: false, isLocal: false,
@ -116,7 +123,7 @@
}) })
} }
const screenPub = rp.getTrackPublication(Track.Source.ScreenShare) const screenPub = rp.getTrackPublication(Track.Source.ScreenShare)
if (screenPub?.isSubscribed && screenPub.track) { if (screenPub?.isSubscribed && screenPub.track && !screenPub.isMuted) {
videoTiles.push({ videoTiles.push({
liveKitIdentity: rp.identity, liveKitIdentity: rp.identity,
isLocal: false, isLocal: false,
@ -290,10 +297,7 @@
{:else} {:else}
<div <div
class="flex min-h-[12rem] flex-1 flex-col items-center justify-center gap-2 rounded-2xl bg-surface p-4 text-center text-sm opacity-80"> class="flex min-h-[12rem] flex-1 flex-col items-center justify-center gap-2 rounded-2xl bg-surface p-4 text-center text-sm opacity-80">
<p>No one is sharing video yet.</p> <p>Waiting for participants…</p>
<p class="text-xs">
Participants appear here when they turn on their camera or share their screen.
</p>
</div> </div>
{/if} {/if}
{/snippet} {/snippet}