feat(voice): add cachedUser carry-forward to ParticipantInfo
Extends ParticipantInfo with cachedUser: User | null. In updateParticipants, snapshots the previous participant list and carries forward the cached User object when spaceStore member lookup fails (user navigated to a different space). Local user falls back to authStore. homeUserId is now derived from cachedUser when the direct member lookup misses. Updates EMPTY_PARTICIPANT in PictureInPicture.tsx.
This commit is contained in:
@@ -14,6 +14,7 @@ const EMPTY_PARTICIPANT: ParticipantInfo = {
|
|||||||
isMuted: false, isDeafened: false, isCameraOn: false, isScreenSharing: false,
|
isMuted: false, isDeafened: false, isCameraOn: false, isScreenSharing: false,
|
||||||
isLocal: false, audioTrack: null, videoTrack: null, screenTrack: null,
|
isLocal: false, audioTrack: null, videoTrack: null, screenTrack: null,
|
||||||
screenAudioTrack: null, lkVideoTrack: null, lkScreenTrack: null,
|
screenAudioTrack: null, lkVideoTrack: null, lkScreenTrack: null,
|
||||||
|
cachedUser: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PIP_WIDTH = 320;
|
const PIP_WIDTH = 320;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import {
|
|||||||
import { getApiForOrigin, getChannelOrigin, getMyUserIdForOrigin, useSpaceStore } from '../stores/spaceStore';
|
import { getApiForOrigin, getChannelOrigin, getMyUserIdForOrigin, useSpaceStore } from '../stores/spaceStore';
|
||||||
import { wsSend } from './useWebSocket';
|
import { wsSend } from './useWebSocket';
|
||||||
import { useVoiceStore } from '../stores/voiceStore';
|
import { useVoiceStore } from '../stores/voiceStore';
|
||||||
|
import { useAuthStore } from '../stores/authStore';
|
||||||
|
import type { User } from '@backspace/shared';
|
||||||
import { broadcastVoiceStatus } from '../utils/voice';
|
import { broadcastVoiceStatus } from '../utils/voice';
|
||||||
import { AudioManager } from '../audio/AudioManager';
|
import { AudioManager } from '../audio/AudioManager';
|
||||||
import { SpeakingDetector } from '../audio/SpeakingDetector';
|
import { SpeakingDetector } from '../audio/SpeakingDetector';
|
||||||
@@ -55,6 +57,7 @@ export interface ParticipantInfo {
|
|||||||
screenAudioTrack: MediaStreamTrack | null;
|
screenAudioTrack: MediaStreamTrack | null;
|
||||||
lkVideoTrack: Track | null; // LiveKit Track for attach/detach (adaptive stream)
|
lkVideoTrack: Track | null; // LiveKit Track for attach/detach (adaptive stream)
|
||||||
lkScreenTrack: Track | null; // LiveKit Track for attach/detach (adaptive stream)
|
lkScreenTrack: Track | null; // LiveKit Track for attach/detach (adaptive stream)
|
||||||
|
cachedUser: User | null; // Hydrated User from member lookup, carried forward across space switches
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserTile {
|
export interface UserTile {
|
||||||
@@ -170,12 +173,35 @@ export function useLiveKit() {
|
|||||||
const updateParticipants = useCallback(() => {
|
const updateParticipants = useCallback(() => {
|
||||||
const r = roomRef.current;
|
const r = roomRef.current;
|
||||||
if (!r) return;
|
if (!r) return;
|
||||||
|
|
||||||
|
// Carry-forward: snapshot previous participants for cachedUser preservation
|
||||||
|
const prevParticipants = useVoiceStore.getState().participants;
|
||||||
|
const prevCacheMap = new Map<string, User | null>();
|
||||||
|
for (const prev of prevParticipants) {
|
||||||
|
prevCacheMap.set(prev.identity, prev.cachedUser);
|
||||||
|
}
|
||||||
|
|
||||||
const allParticipants: ParticipantInfo[] = [];
|
const allParticipants: ParticipantInfo[] = [];
|
||||||
const processParticipant = (p: Participant, isLocal: boolean) => {
|
const processParticipant = (p: Participant, isLocal: boolean) => {
|
||||||
if (!p.identity) return;
|
if (!p.identity) return;
|
||||||
const { userId, username } = parseIdentity(p.identity);
|
const { userId, username } = parseIdentity(p.identity);
|
||||||
const memberMatch = useSpaceStore.getState().members.find(m => m.userId === userId);
|
const memberMatch = useSpaceStore.getState().members.find(m => m.userId === userId);
|
||||||
const homeUserId = memberMatch?.user.homeUserId ?? null;
|
let cachedUser: User | null;
|
||||||
|
let homeUserId: string | null;
|
||||||
|
|
||||||
|
if (memberMatch) {
|
||||||
|
// Fresh data available — use and update cache
|
||||||
|
cachedUser = memberMatch.user as User;
|
||||||
|
homeUserId = memberMatch.user.homeUserId ?? null;
|
||||||
|
} else if (isLocal) {
|
||||||
|
// Local user safety net — authStore is always available
|
||||||
|
cachedUser = useAuthStore.getState().user;
|
||||||
|
homeUserId = cachedUser?.homeUserId ?? cachedUser?.id ?? null;
|
||||||
|
} else {
|
||||||
|
// Space switched — carry forward from previous cycle
|
||||||
|
cachedUser = prevCacheMap.get(p.identity) ?? null;
|
||||||
|
homeUserId = cachedUser?.homeUserId ?? null;
|
||||||
|
}
|
||||||
let audioTrack: MediaStreamTrack | null = null;
|
let audioTrack: MediaStreamTrack | null = null;
|
||||||
let videoTrack: MediaStreamTrack | null = null;
|
let videoTrack: MediaStreamTrack | null = null;
|
||||||
let screenTrack: MediaStreamTrack | null = null;
|
let screenTrack: MediaStreamTrack | null = null;
|
||||||
@@ -229,6 +255,7 @@ export function useLiveKit() {
|
|||||||
userId,
|
userId,
|
||||||
username,
|
username,
|
||||||
homeUserId,
|
homeUserId,
|
||||||
|
cachedUser,
|
||||||
isMuted: isPartMuted,
|
isMuted: isPartMuted,
|
||||||
isDeafened: isPartDeafened,
|
isDeafened: isPartDeafened,
|
||||||
isCameraOn: hasCameraPublication && p.isCameraEnabled, // True even when unsubscribed
|
isCameraOn: hasCameraPublication && p.isCameraEnabled, // True even when unsubscribed
|
||||||
|
|||||||
Reference in New Issue
Block a user