fix(call): кнопки входящего и автопереподключение после обрыва
This commit is contained in:
parent
97a2bee81a
commit
d7c7bb3c23
@ -135,8 +135,8 @@ function getCallStateSnapshot() {
|
||||
muted: Boolean(call.muted),
|
||||
canAnswer: callPhase === 'incoming',
|
||||
canDecline: callPhase === 'incoming',
|
||||
canHangup: callPhase !== 'ended',
|
||||
canMute: callPhase === 'active' || callPhase === 'connecting' || callPhase === 'ringing',
|
||||
canHangup: callPhase !== 'ended' && callPhase !== 'incoming',
|
||||
canMute: callPhase === 'active' || callPhase === 'connecting' || callPhase === 'ringing' || callPhase === 'reconnecting',
|
||||
};
|
||||
}
|
||||
|
||||
@ -177,6 +177,76 @@ async function closeMedia(call) {
|
||||
call.localStream = null;
|
||||
}
|
||||
|
||||
function stopReconnectFlow(call) {
|
||||
if (!call?.timers) return;
|
||||
if (call.timers.reconnectStep) {
|
||||
clearTimeout(call.timers.reconnectStep);
|
||||
call.timers.reconnectStep = null;
|
||||
}
|
||||
if (call.timers.reconnectDeadline) {
|
||||
clearTimeout(call.timers.reconnectDeadline);
|
||||
call.timers.reconnectDeadline = null;
|
||||
}
|
||||
call.reconnectInProgress = false;
|
||||
call.reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
function startReconnectFlow(call, reason = 'disconnected') {
|
||||
if (!call || call.phase === 'ended') return;
|
||||
if (!call.connectedAtMs) return;
|
||||
if (!call.pc) return;
|
||||
if (call.reconnectInProgress) return;
|
||||
|
||||
call.reconnectInProgress = true;
|
||||
call.reconnectAttempts = 0;
|
||||
setStatus(call, 'Связь прервалась. Переподключаем…', 'reconnecting');
|
||||
void emitDebug(call, 'warn', 'peer_connection_reconnect_start', `reason=${reason}`);
|
||||
|
||||
const maxAttempts = 6;
|
||||
const attemptDelayMs = 2500;
|
||||
const totalDeadlineMs = 17000;
|
||||
|
||||
call.timers.reconnectDeadline = setTimeout(() => {
|
||||
if (!calls.has(call.callId) || call.phase === 'ended') return;
|
||||
if (call.pc?.connectionState === 'connected') return;
|
||||
stopReconnectFlow(call);
|
||||
void finalizeCall(call, { localReasonCode: 'error', debugReason: 'reconnect_timeout' });
|
||||
}, totalDeadlineMs);
|
||||
|
||||
const runAttempt = async () => {
|
||||
if (!calls.has(call.callId) || call.phase === 'ended') return;
|
||||
if (!call.pc || call.pc.connectionState === 'connected') {
|
||||
stopReconnectFlow(call);
|
||||
return;
|
||||
}
|
||||
|
||||
call.reconnectAttempts += 1;
|
||||
try {
|
||||
const offer = await call.pc.createOffer({ iceRestart: true });
|
||||
await call.pc.setLocalDescription(offer);
|
||||
await sendSignal(call, TYPES.OFFER, JSON.stringify(offer));
|
||||
await emitDebug(call, 'info', 'peer_connection_reconnect_offer_sent', `attempt=${call.reconnectAttempts}`);
|
||||
} catch (error) {
|
||||
await emitDebug(call, 'warn', 'peer_connection_reconnect_offer_failed', `attempt=${call.reconnectAttempts}; error=${toErrorText(error)}`);
|
||||
}
|
||||
|
||||
if (call.pc?.connectionState === 'connected') {
|
||||
stopReconnectFlow(call);
|
||||
return;
|
||||
}
|
||||
if (call.reconnectAttempts >= maxAttempts) {
|
||||
stopReconnectFlow(call);
|
||||
await finalizeCall(call, { localReasonCode: 'error', debugReason: 'reconnect_attempts_exhausted' });
|
||||
return;
|
||||
}
|
||||
call.timers.reconnectStep = setTimeout(() => {
|
||||
void runAttempt();
|
||||
}, attemptDelayMs);
|
||||
};
|
||||
|
||||
void runAttempt();
|
||||
}
|
||||
|
||||
function pushCallSummary(call, summaryCode) {
|
||||
if (!call?.peerLogin) return;
|
||||
const outgoing = call.direction === 'out';
|
||||
@ -227,6 +297,7 @@ async function finalizeCall(call, {
|
||||
} = {}) {
|
||||
if (!call) return;
|
||||
cleanupTimers(call);
|
||||
stopReconnectFlow(call);
|
||||
stopTone();
|
||||
|
||||
if (notifyRemoteHangup && call.remoteSessionId) {
|
||||
@ -331,6 +402,7 @@ async function ensurePeerConnection(call) {
|
||||
pc.onconnectionstatechange = () => {
|
||||
const state = pc.connectionState;
|
||||
if (state === 'connected') {
|
||||
stopReconnectFlow(call);
|
||||
if (!call.connectedAtMs) {
|
||||
call.connectedAtMs = nowMs();
|
||||
}
|
||||
@ -339,15 +411,29 @@ async function ensurePeerConnection(call) {
|
||||
return;
|
||||
}
|
||||
if (state === 'failed') {
|
||||
if (call.connectedAtMs) {
|
||||
startReconnectFlow(call, 'failed');
|
||||
return;
|
||||
}
|
||||
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
||||
void finalizeCall(call, { localReasonCode: call.connectedAtMs ? 'completed' : 'error', debugReason: 'failed' });
|
||||
void finalizeCall(call, { localReasonCode: 'error', debugReason: 'failed' });
|
||||
return;
|
||||
}
|
||||
if ((state === 'closed' || state === 'disconnected') && call.phase !== 'ended') {
|
||||
if (state === 'disconnected' && call.phase !== 'ended') {
|
||||
if (call.connectedAtMs) {
|
||||
startReconnectFlow(call, 'disconnected');
|
||||
return;
|
||||
}
|
||||
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
||||
return;
|
||||
}
|
||||
if (state === 'closed' && call.phase !== 'ended') {
|
||||
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
||||
if (call.connectedAtMs) {
|
||||
void finalizeCall(call, { localReasonCode: 'completed', debugReason: state });
|
||||
void finalizeCall(call, { localReasonCode: 'error', debugReason: state });
|
||||
return;
|
||||
}
|
||||
void finalizeCall(call, { localReasonCode: 'error', debugReason: state });
|
||||
}
|
||||
};
|
||||
|
||||
@ -531,6 +617,8 @@ export async function startOutgoingCall(peerLogin) {
|
||||
pc: null,
|
||||
localStream: null,
|
||||
muted: false,
|
||||
reconnectInProgress: false,
|
||||
reconnectAttempts: 0,
|
||||
debugMode: false,
|
||||
debugRunId: '',
|
||||
debugRole: '',
|
||||
@ -596,6 +684,8 @@ export async function handleIncomingCallInvite(evt) {
|
||||
pc: null,
|
||||
localStream: null,
|
||||
muted: false,
|
||||
reconnectInProgress: false,
|
||||
reconnectAttempts: 0,
|
||||
debugMode: false,
|
||||
debugRunId: '',
|
||||
debugRole: '',
|
||||
|
||||
@ -54,8 +54,8 @@ function ensureUi() {
|
||||
|
||||
declineBtn = document.createElement('button');
|
||||
declineBtn.type = 'button';
|
||||
declineBtn.className = 'ghost-btn';
|
||||
declineBtn.textContent = 'Отклонить';
|
||||
declineBtn.className = 'destructive-btn';
|
||||
declineBtn.textContent = 'Сбросить';
|
||||
declineBtn.addEventListener('click', async () => {
|
||||
await declineIncomingCall();
|
||||
});
|
||||
@ -63,7 +63,7 @@ function ensureUi() {
|
||||
hangupBtn = document.createElement('button');
|
||||
hangupBtn.type = 'button';
|
||||
hangupBtn.className = 'destructive-btn';
|
||||
hangupBtn.textContent = 'Положить';
|
||||
hangupBtn.textContent = 'Положить трубку';
|
||||
hangupBtn.addEventListener('click', async () => {
|
||||
await hangupActiveCall();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user