feat: Optimize WebRTC pipeline for 60fps screen sharing
- Implemented 'Overdrive' logic to force high bitrates on Chrome - Fixed 'Auto' preset to default to stable 720p60 - Added persistent 'Triple-Kick' hammer to prevent bitrate throttling - Fixed sidebar connection status sync - Added comprehensive diagnostic logger
This commit is contained in:
@@ -80,7 +80,8 @@ export const useChatStore = create((set, get) => ({
|
||||
const isDm = isDmChannel(channelId);
|
||||
if (isDm) {
|
||||
await api.dm.updateMessage(messageId, { content });
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
await api.messages.update(messageId, { content });
|
||||
}
|
||||
// Update will arrive via WebSocket
|
||||
@@ -89,7 +90,8 @@ export const useChatStore = create((set, get) => ({
|
||||
const isDm = isDmChannel(channelId);
|
||||
if (isDm) {
|
||||
await api.dm.deleteMessage(messageId);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
await api.messages.delete(messageId);
|
||||
}
|
||||
// Deletion will arrive via WebSocket
|
||||
@@ -106,8 +108,10 @@ export const useChatStore = create((set, get) => ({
|
||||
});
|
||||
},
|
||||
updateMessage: (message) => {
|
||||
// DM messages have dmChannelId instead of channelId — check both
|
||||
const channelKey = message.channelId || message.dmChannelId;
|
||||
if (!channelKey) return;
|
||||
if (!channelKey)
|
||||
return;
|
||||
set((state) => {
|
||||
const newMessages = new Map(state.messages);
|
||||
const current = newMessages.get(channelKey);
|
||||
@@ -219,7 +223,8 @@ export const useChatStore = create((set, get) => ({
|
||||
},
|
||||
markChannelUnread: (channelId) => {
|
||||
set((state) => {
|
||||
if (state.unreadChannels.has(channelId)) return state;
|
||||
if (state.unreadChannels.has(channelId))
|
||||
return state;
|
||||
const newUnread = new Set(state.unreadChannels);
|
||||
newUnread.add(channelId);
|
||||
return { unreadChannels: newUnread };
|
||||
@@ -227,10 +232,13 @@ export const useChatStore = create((set, get) => ({
|
||||
},
|
||||
ackChannel: (channelId) => {
|
||||
const msgs = get().messages.get(channelId);
|
||||
if (!msgs || msgs.length === 0) return;
|
||||
if (!msgs || msgs.length === 0)
|
||||
return;
|
||||
const lastMsg = msgs[msgs.length - 1];
|
||||
if (!lastMsg) return;
|
||||
if (!lastMsg)
|
||||
return;
|
||||
const messageId = lastMsg.id;
|
||||
// Update local state immediately
|
||||
set((state) => {
|
||||
const newReadStates = new Map(state.readStates);
|
||||
newReadStates.set(channelId, messageId);
|
||||
@@ -238,6 +246,7 @@ export const useChatStore = create((set, get) => ({
|
||||
newUnread.delete(channelId);
|
||||
return { readStates: newReadStates, unreadChannels: newUnread };
|
||||
});
|
||||
// Send to server
|
||||
wsSend({ type: 'channel_ack', channelId, messageId });
|
||||
},
|
||||
onChannelAck: (channelId, messageId) => {
|
||||
|
||||
@@ -140,6 +140,7 @@ export const useServerStore = create((set, get) => ({
|
||||
inviteCode: s.inviteCode,
|
||||
createdAt: s.createdAt,
|
||||
}));
|
||||
// Build channel→server map and channel→lastMessageId map
|
||||
const channelToServerMap = new Map();
|
||||
const channelLastMessageIds = new Map();
|
||||
for (const srv of servers) {
|
||||
@@ -150,6 +151,7 @@ export const useServerStore = create((set, get) => ({
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also map DM channels
|
||||
const dms = dmChannels || [];
|
||||
for (const dm of dms) {
|
||||
if (dm.lastMessage?.id) {
|
||||
@@ -165,7 +167,11 @@ export const useServerStore = create((set, get) => ({
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Data-driven DM channel detection. Returns true if the given channelId
|
||||
* belongs to a DM channel. Authoritative because dmChannels is populated
|
||||
* from the WS ready event and DM/server channel IDs never overlap.
|
||||
*/
|
||||
export function isDmChannel(channelId) {
|
||||
const dmChannels = useServerStore.getState().dmChannels;
|
||||
if (dmChannels.length > 0) {
|
||||
|
||||
@@ -29,4 +29,9 @@ export const useUIStore = create((set) => ({
|
||||
closeUserProfile: () => set({
|
||||
userProfilePopout: { user: null, position: null }
|
||||
}),
|
||||
voiceChatOpen: false,
|
||||
voiceFullscreen: false,
|
||||
toggleVoiceChat: () => set((state) => ({ voiceChatOpen: !state.voiceChatOpen })),
|
||||
toggleVoiceFullscreen: () => set((state) => ({ voiceFullscreen: !state.voiceFullscreen })),
|
||||
setVoiceFullscreen: (fullscreen) => set({ voiceFullscreen: fullscreen }),
|
||||
}));
|
||||
|
||||
@@ -34,6 +34,11 @@ interface UIState {
|
||||
closeImagePreview: () => void;
|
||||
openUserProfile: (user: User, position: { top: number; left: number }) => void;
|
||||
closeUserProfile: () => void;
|
||||
voiceChatOpen: boolean;
|
||||
voiceFullscreen: boolean;
|
||||
toggleVoiceChat: () => void;
|
||||
toggleVoiceFullscreen: () => void;
|
||||
setVoiceFullscreen: (fullscreen: boolean) => void;
|
||||
}
|
||||
|
||||
export const useUIStore = create<UIState>((set) => ({
|
||||
@@ -72,4 +77,10 @@ export const useUIStore = create<UIState>((set) => ({
|
||||
closeUserProfile: () => set({
|
||||
userProfilePopout: { user: null, position: null }
|
||||
}),
|
||||
|
||||
voiceChatOpen: false,
|
||||
voiceFullscreen: false,
|
||||
toggleVoiceChat: () => set((state) => ({ voiceChatOpen: !state.voiceChatOpen })),
|
||||
toggleVoiceFullscreen: () => set((state) => ({ voiceFullscreen: !state.voiceFullscreen })),
|
||||
setVoiceFullscreen: (fullscreen) => set({ voiceFullscreen: fullscreen }),
|
||||
}));
|
||||
|
||||
@@ -9,6 +9,25 @@ export const useVoiceStore = create((set, get) => ({
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
videoQuality: 'auto',
|
||||
participantVolumes: new Map(),
|
||||
setParticipantVolume: (userId, volume) => {
|
||||
set((state) => {
|
||||
const newMap = new Map(state.participantVolumes);
|
||||
newMap.set(userId, volume);
|
||||
return { participantVolumes: newMap };
|
||||
});
|
||||
},
|
||||
getParticipantVolume: (userId) => get().participantVolumes.get(userId) ?? 100,
|
||||
incomingCall: null,
|
||||
outgoingCall: null,
|
||||
activeDmCall: null,
|
||||
setIncomingCall: (call) => set({ incomingCall: call }),
|
||||
setOutgoingCall: (call) => set({ outgoingCall: call }),
|
||||
setActiveDmCall: (call) => set({ activeDmCall: call }),
|
||||
setVoiceUsers: (channelId, userIds) => {
|
||||
set((state) => {
|
||||
const newMap = new Map(state.voiceUsers);
|
||||
@@ -38,10 +57,14 @@ export const useVoiceStore = create((set, get) => ({
|
||||
setParticipants: (participants) => set({ participants }),
|
||||
setConnectionError: (error) => set({ connectionError: error }),
|
||||
setIsLiveKitConnected: (connected) => set({ isLiveKitConnected: connected }),
|
||||
setInputVolume: (volume) => set({ inputVolume: volume }),
|
||||
setOutputVolume: (volume) => set({ outputVolume: volume }),
|
||||
toggleMic: () => set((state) => ({ isMuted: !state.isMuted })),
|
||||
toggleDeafen: () => set((state) => ({ isDeafened: !state.isDeafened })),
|
||||
toggleCamera: () => set((state) => ({ isCameraOn: !state.isCameraOn })),
|
||||
toggleScreenShare: () => set((state) => ({ isScreenSharing: !state.isScreenSharing })),
|
||||
setFocusedParticipant: (id) => set({ focusedParticipantId: id }),
|
||||
setVideoQuality: (quality) => set({ videoQuality: quality }),
|
||||
getVoiceUsers: (channelId) => get().voiceUsers.get(channelId) ?? [],
|
||||
clearAllVoiceUsers: () => set({ voiceUsers: new Map() }),
|
||||
// Leave voice without wiping the voiceUsers map (so sidebar still shows others)
|
||||
@@ -54,6 +77,11 @@ export const useVoiceStore = create((set, get) => ({
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
activeDmCall: null,
|
||||
outgoingCall: null,
|
||||
}),
|
||||
reset: () => set({
|
||||
voiceUsers: new Map(),
|
||||
@@ -65,5 +93,12 @@ export const useVoiceStore = create((set, get) => ({
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
participantVolumes: new Map(),
|
||||
incomingCall: null,
|
||||
outgoingCall: null,
|
||||
activeDmCall: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,19 @@ interface VoiceState {
|
||||
isLiveKitConnected: boolean;
|
||||
inputVolume: number; // 0-200 (100 = default)
|
||||
outputVolume: number; // 0-200 (100 = default)
|
||||
focusedParticipantId: string | null;
|
||||
videoQuality: 'auto' | '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p';
|
||||
// Per-participant volume (userId → 0-200, 100 = default)
|
||||
participantVolumes: Map<string, number>;
|
||||
setParticipantVolume: (userId: string, volume: number) => void;
|
||||
getParticipantVolume: (userId: string) => number;
|
||||
// DM call state
|
||||
incomingCall: { dmChannelId: string; callerId: string; callerName: string } | null;
|
||||
outgoingCall: { dmChannelId: string } | null;
|
||||
activeDmCall: { dmChannelId: string } | null;
|
||||
setIncomingCall: (call: { dmChannelId: string; callerId: string; callerName: string } | null) => void;
|
||||
setOutgoingCall: (call: { dmChannelId: string } | null) => void;
|
||||
setActiveDmCall: (call: { dmChannelId: string } | null) => void;
|
||||
setVoiceUsers: (channelId: string, userIds: string[]) => void;
|
||||
addVoiceUser: (channelId: string, userId: string) => void;
|
||||
removeVoiceUser: (channelId: string, userId: string) => void;
|
||||
@@ -26,6 +39,8 @@ interface VoiceState {
|
||||
toggleCamera: () => void;
|
||||
toggleScreenShare: () => void;
|
||||
toggleDeafen: () => void;
|
||||
setFocusedParticipant: (id: string | null) => void;
|
||||
setVideoQuality: (quality: 'auto' | '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p') => void;
|
||||
getVoiceUsers: (channelId: string) => string[];
|
||||
clearAllVoiceUsers: () => void;
|
||||
leaveVoice: () => void;
|
||||
@@ -44,6 +59,25 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
videoQuality: 'auto',
|
||||
participantVolumes: new Map(),
|
||||
setParticipantVolume: (userId, volume) => {
|
||||
set((state) => {
|
||||
const newMap = new Map(state.participantVolumes);
|
||||
newMap.set(userId, volume);
|
||||
return { participantVolumes: newMap };
|
||||
});
|
||||
},
|
||||
getParticipantVolume: (userId) => get().participantVolumes.get(userId) ?? 100,
|
||||
|
||||
incomingCall: null,
|
||||
outgoingCall: null,
|
||||
activeDmCall: null,
|
||||
|
||||
setIncomingCall: (call) => set({ incomingCall: call }),
|
||||
setOutgoingCall: (call) => set({ outgoingCall: call }),
|
||||
setActiveDmCall: (call) => set({ activeDmCall: call }),
|
||||
|
||||
setVoiceUsers: (channelId, userIds) => {
|
||||
set((state) => {
|
||||
@@ -87,6 +121,9 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
toggleCamera: () => set((state) => ({ isCameraOn: !state.isCameraOn })),
|
||||
toggleScreenShare: () => set((state) => ({ isScreenSharing: !state.isScreenSharing })),
|
||||
|
||||
setFocusedParticipant: (id) => set({ focusedParticipantId: id }),
|
||||
setVideoQuality: (quality) => set({ videoQuality: quality }),
|
||||
|
||||
getVoiceUsers: (channelId) => get().voiceUsers.get(channelId) ?? [],
|
||||
|
||||
clearAllVoiceUsers: () => set({ voiceUsers: new Map() }),
|
||||
@@ -103,6 +140,9 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
activeDmCall: null,
|
||||
outgoingCall: null,
|
||||
}),
|
||||
|
||||
reset: () => set({
|
||||
@@ -117,5 +157,10 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
isLiveKitConnected: false,
|
||||
inputVolume: 100,
|
||||
outputVolume: 100,
|
||||
focusedParticipantId: null,
|
||||
participantVolumes: new Map(),
|
||||
incomingCall: null,
|
||||
outgoingCall: null,
|
||||
activeDmCall: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user