Skip to content

Commit 2918ccd

Browse files
authored
Merge pull request #34 from 8JP8/fix-voip-reconnect-12874636545607941746
Fix VoIP audio and auto-reconnect reliability
2 parents 68dc8b0 + c6fe7cb commit 2918ccd

2 files changed

Lines changed: 68 additions & 24 deletions

File tree

backend/socketio_handlers.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,32 @@ def handle_disconnect():
206206
# Only emit user_left_topic for topic rooms
207207
if isinstance(tracked_room, str) and tracked_room.startswith('topic_'):
208208
topic_id = tracked_room.split('topic_', 1)[1]
209-
try:
210-
emit('user_left_topic', {
211-
'user_id': user_id,
212-
'username': username,
209+
try:
210+
emit('user_left_topic', {
211+
'user_id': user_id,
212+
'username': username,
213213
'topic_id': topic_id
214214
}, room=tracked_room, skip_sid=sid)
215-
except (RuntimeError, AttributeError):
215+
except (RuntimeError, AttributeError):
216216
logger.debug(f"Could not emit user_left_topic for room {tracked_room}")
217+
218+
# Handle VOIP disconnects
219+
if isinstance(tracked_room, str) and tracked_room.startswith('voip_'):
220+
call_id = tracked_room.split('voip_', 1)[1]
221+
try:
222+
from models.voip import VoipCall
223+
voip_model = VoipCall(current_app.db)
224+
voip_model.set_disconnected_status(call_id, user_id, True)
225+
226+
# Broadcast disconnect status to room (skipping the disconnected user)
227+
emit('voip_user_disconnected', {
228+
'call_id': call_id,
229+
'user_id': user_id,
230+
'username': username,
231+
'is_disconnected': True
232+
}, room=tracked_room, skip_sid=sid)
233+
except Exception as e:
234+
logger.error(f"Failed to handle VOIP disconnect for call {call_id}: {e}")
217235
except (RuntimeError, AttributeError) as room_error:
218236
logger.debug(f"Could not leave room {tracked_room}: {room_error}")
219237
del user_rooms[user_id]

frontend/contexts/VoipContext.tsx

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,44 @@ export const VoipProvider: React.FC<VoipProviderProps> = ({ children }) => {
338338
setIsMuted(false);
339339
}, [stopVoiceActivityDetection]);
340340

341+
// Initialize peer connections with a list of participants
342+
const initializePeerConnections = useCallback(async (callId: string, currentParticipants: VoipParticipant[]) => {
343+
if (!user || !socket || !connected) return;
344+
345+
console.log('[VOIP] Initializing peer connections for participants:', currentParticipants.length);
346+
347+
for (const participant of currentParticipants) {
348+
// Skip self
349+
if (participant.user_id === user.id) continue;
350+
351+
try {
352+
// If we already have a connection, check its state
353+
if (peerConnectionsRef.current.has(participant.user_id)) {
354+
const pc = peerConnectionsRef.current.get(participant.user_id)?.connection;
355+
if (pc && (pc.connectionState === 'connected' || pc.connectionState === 'connecting')) {
356+
console.log(`[VOIP] Connection already healthy for ${participant.user_id}`);
357+
continue;
358+
}
359+
// Close unhealthy connection
360+
if (pc) pc.close();
361+
}
362+
363+
console.log(`[VOIP] Creating offer for ${participant.user_id}`);
364+
const pc = createPeerConnection(participant.user_id);
365+
const offer = await pc.createOffer();
366+
await pc.setLocalDescription(offer);
367+
368+
socket.emit('voip_offer', {
369+
call_id: callId,
370+
target_user_id: participant.user_id,
371+
offer: offer
372+
});
373+
} catch (error) {
374+
console.error(`[VOIP] Failed to create offer for ${participant.user_id}:`, error);
375+
}
376+
}
377+
}, [user, socket, connected, createPeerConnection]);
378+
341379
// Create call
342380
const createCall = useCallback(async (roomId: string, roomType: 'group' | 'dm', roomName?: string) => {
343381
if (!socket || !connected) {
@@ -585,24 +623,8 @@ export const VoipProvider: React.FC<VoipProviderProps> = ({ children }) => {
585623
}
586624
setConnectionStatus('connected');
587625

588-
// Create offers to all existing participants
589-
for (const participant of data.call.participants || []) {
590-
if (participant.user_id !== user?.id) {
591-
try {
592-
const pc = createPeerConnection(participant.user_id);
593-
const offer = await pc.createOffer();
594-
await pc.setLocalDescription(offer);
595-
596-
socket.emit('voip_offer', {
597-
call_id: data.call.id,
598-
target_user_id: participant.user_id,
599-
offer: offer
600-
});
601-
} catch (error) {
602-
console.error('[VOIP] Failed to create offer:', error);
603-
}
604-
}
605-
}
626+
// Initialize connections with all existing participants
627+
await initializePeerConnections(data.call.id, data.call.participants || []);
606628
},
607629

608630
'voip_user_joined': async (data) => {
@@ -835,6 +857,9 @@ export const VoipProvider: React.FC<VoipProviderProps> = ({ children }) => {
835857
localStreamRef.current = await getUserMedia(savedDeviceId || undefined);
836858
startVoiceActivityDetection(localStreamRef.current);
837859
console.log('[VOIP] Reconnected to call:', data.call.id);
860+
861+
// Initialize connections with participants
862+
await initializePeerConnections(data.call.id, data.call.participants || []);
838863
} catch (error) {
839864
console.error('[VOIP] Failed to restore microphone on reconnection:', error);
840865
}
@@ -916,7 +941,8 @@ export const VoipProvider: React.FC<VoipProviderProps> = ({ children }) => {
916941
const handleReconnect = () => {
917942
if (activeCall) {
918943
console.log('[VOIP] Socket reconnected, restoring call...');
919-
socket.emit('voip_heartbeat', { call_id: activeCall.id });
944+
// Emit join_call to ensure socket is re-added to the signaling room
945+
socket.emit('voip_join_call', { call_id: activeCall.id });
920946
setConnectionStatus('connected');
921947
}
922948
};

0 commit comments

Comments
 (0)