diff --git a/VERSION.properties b/VERSION.properties index f563309..cea4b5e 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.29 +client.version=1.2.30 server.version=1.2.26 diff --git a/shine-UI/js/services/call-service.js b/shine-UI/js/services/call-service.js index 4ba47f7..64ac2c3 100644 --- a/shine-UI/js/services/call-service.js +++ b/shine-UI/js/services/call-service.js @@ -516,6 +516,21 @@ function cleanupTimers(call) { call.timers.transportProbe = null; } +async function flushPendingIceCandidates(call) { + if (!call?.pc) return; + const pending = Array.isArray(call.pendingRemoteIceCandidates) ? call.pendingRemoteIceCandidates : []; + if (!pending.length) return; + call.pendingRemoteIceCandidates = []; + for (const candidate of pending) { + try { + await call.pc.addIceCandidate(new RTCIceCandidate(candidate)); + await emitDebug(call, 'info', 'ice_processed_from_queue', 'candidate added'); + } catch (error) { + await emitDebug(call, 'warn', 'ice_process_failed_from_queue', toErrorText(error)); + } + } +} + async function closeMedia(call) { const pc = call?.pc || null; try { @@ -551,6 +566,7 @@ async function closeMedia(call) { call.audioSenders = []; call.connectionRouteLabel = ''; call.connectionRouteDetails = ''; + call.pendingRemoteIceCandidates = []; } function stopReconnectFlow(call) { @@ -1006,6 +1022,9 @@ async function ensurePeerConnection(call) { }; call.pc = pc; + if (!Array.isArray(call.pendingRemoteIceCandidates)) { + call.pendingRemoteIceCandidates = []; + } return pc; } @@ -1125,9 +1144,11 @@ export async function startDebugConnectionAsResponder({ runId, callId, peerLogin debugMode: true, debugRunId: String(runId || '').trim(), debugRole: 'responder', + pendingRemoteIceCandidates: [], }; calls.set(cleanCallId, call); } + if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = []; activeCallId = cleanCallId; await emitDebug(call, 'info', 'debug_prepare_responder', `peerSessionId=${cleanPeerSessionId}`); @@ -1158,6 +1179,7 @@ export async function startDebugConnectionAsInitiator({ runId, callId, peerLogin debugMode: true, debugRunId: String(runId || '').trim(), debugRole: 'initiator', + pendingRemoteIceCandidates: [], }; calls.set(cleanCallId, call); @@ -1202,6 +1224,7 @@ export async function startOutgoingCall(peerLogin) { debugMode: false, debugRunId: '', debugRole: '', + pendingRemoteIceCandidates: [], }; calls.set(callId, call); activeCallId = callId; @@ -1271,6 +1294,7 @@ export async function handleIncomingCallInvite(evt) { debugMode: false, debugRunId: '', debugRole: '', + pendingRemoteIceCandidates: [], }; calls.set(callId, call); } @@ -1364,6 +1388,7 @@ export async function handleIncomingCallSignal(evt) { try { const pc = await ensurePeerConnection(call); await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data))); + await flushPendingIceCandidates(call); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); await sendSignal(call, TYPES.ANSWER, JSON.stringify(answer)); @@ -1379,7 +1404,12 @@ export async function handleIncomingCallSignal(evt) { if (type === TYPES.ANSWER) { try { const pc = await ensurePeerConnection(call); + if (pc.signalingState === 'stable' && pc.remoteDescription) { + await emitDebug(call, 'warn', 'answer_duplicate_ignored', 'remote description already set'); + return; + } await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data))); + await flushPendingIceCandidates(call); setStatus(call, 'Соединяем…', 'connecting'); await emitDebug(call, 'info', 'answer_processed', 'remote description set'); } catch (error) { @@ -1392,7 +1422,14 @@ export async function handleIncomingCallSignal(evt) { if (type === TYPES.ICE) { try { const pc = await ensurePeerConnection(call); - await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(data))); + const candidate = JSON.parse(data); + if (!pc.remoteDescription) { + if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = []; + call.pendingRemoteIceCandidates.push(candidate); + await emitDebug(call, 'info', 'ice_queued_before_remote_description', `queue=${call.pendingRemoteIceCandidates.length}`); + return; + } + await pc.addIceCandidate(new RTCIceCandidate(candidate)); await emitDebug(call, 'info', 'ice_processed', 'candidate added'); } catch (error) { await emitDebug(call, 'error', 'ice_process_failed', toErrorText(error));