fix(call): кнопки входящего и автопереподключение после обрыва
This commit is contained in:
parent
97a2bee81a
commit
d7c7bb3c23
@ -135,8 +135,8 @@ function getCallStateSnapshot() {
|
|||||||
muted: Boolean(call.muted),
|
muted: Boolean(call.muted),
|
||||||
canAnswer: callPhase === 'incoming',
|
canAnswer: callPhase === 'incoming',
|
||||||
canDecline: callPhase === 'incoming',
|
canDecline: callPhase === 'incoming',
|
||||||
canHangup: callPhase !== 'ended',
|
canHangup: callPhase !== 'ended' && callPhase !== 'incoming',
|
||||||
canMute: callPhase === 'active' || callPhase === 'connecting' || callPhase === 'ringing',
|
canMute: callPhase === 'active' || callPhase === 'connecting' || callPhase === 'ringing' || callPhase === 'reconnecting',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +177,76 @@ async function closeMedia(call) {
|
|||||||
call.localStream = null;
|
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) {
|
function pushCallSummary(call, summaryCode) {
|
||||||
if (!call?.peerLogin) return;
|
if (!call?.peerLogin) return;
|
||||||
const outgoing = call.direction === 'out';
|
const outgoing = call.direction === 'out';
|
||||||
@ -227,6 +297,7 @@ async function finalizeCall(call, {
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
if (!call) return;
|
if (!call) return;
|
||||||
cleanupTimers(call);
|
cleanupTimers(call);
|
||||||
|
stopReconnectFlow(call);
|
||||||
stopTone();
|
stopTone();
|
||||||
|
|
||||||
if (notifyRemoteHangup && call.remoteSessionId) {
|
if (notifyRemoteHangup && call.remoteSessionId) {
|
||||||
@ -331,6 +402,7 @@ async function ensurePeerConnection(call) {
|
|||||||
pc.onconnectionstatechange = () => {
|
pc.onconnectionstatechange = () => {
|
||||||
const state = pc.connectionState;
|
const state = pc.connectionState;
|
||||||
if (state === 'connected') {
|
if (state === 'connected') {
|
||||||
|
stopReconnectFlow(call);
|
||||||
if (!call.connectedAtMs) {
|
if (!call.connectedAtMs) {
|
||||||
call.connectedAtMs = nowMs();
|
call.connectedAtMs = nowMs();
|
||||||
}
|
}
|
||||||
@ -339,15 +411,29 @@ async function ensurePeerConnection(call) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state === 'failed') {
|
if (state === 'failed') {
|
||||||
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
if (call.connectedAtMs) {
|
||||||
void finalizeCall(call, { localReasonCode: call.connectedAtMs ? 'completed' : 'error', debugReason: 'failed' });
|
startReconnectFlow(call, 'failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((state === 'closed' || state === 'disconnected') && call.phase !== 'ended') {
|
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
||||||
|
void finalizeCall(call, { localReasonCode: 'error', debugReason: 'failed' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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}`);
|
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
|
||||||
if (call.connectedAtMs) {
|
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,
|
pc: null,
|
||||||
localStream: null,
|
localStream: null,
|
||||||
muted: false,
|
muted: false,
|
||||||
|
reconnectInProgress: false,
|
||||||
|
reconnectAttempts: 0,
|
||||||
debugMode: false,
|
debugMode: false,
|
||||||
debugRunId: '',
|
debugRunId: '',
|
||||||
debugRole: '',
|
debugRole: '',
|
||||||
@ -596,6 +684,8 @@ export async function handleIncomingCallInvite(evt) {
|
|||||||
pc: null,
|
pc: null,
|
||||||
localStream: null,
|
localStream: null,
|
||||||
muted: false,
|
muted: false,
|
||||||
|
reconnectInProgress: false,
|
||||||
|
reconnectAttempts: 0,
|
||||||
debugMode: false,
|
debugMode: false,
|
||||||
debugRunId: '',
|
debugRunId: '',
|
||||||
debugRole: '',
|
debugRole: '',
|
||||||
|
|||||||
@ -54,8 +54,8 @@ function ensureUi() {
|
|||||||
|
|
||||||
declineBtn = document.createElement('button');
|
declineBtn = document.createElement('button');
|
||||||
declineBtn.type = 'button';
|
declineBtn.type = 'button';
|
||||||
declineBtn.className = 'ghost-btn';
|
declineBtn.className = 'destructive-btn';
|
||||||
declineBtn.textContent = 'Отклонить';
|
declineBtn.textContent = 'Сбросить';
|
||||||
declineBtn.addEventListener('click', async () => {
|
declineBtn.addEventListener('click', async () => {
|
||||||
await declineIncomingCall();
|
await declineIncomingCall();
|
||||||
});
|
});
|
||||||
@ -63,7 +63,7 @@ function ensureUi() {
|
|||||||
hangupBtn = document.createElement('button');
|
hangupBtn = document.createElement('button');
|
||||||
hangupBtn.type = 'button';
|
hangupBtn.type = 'button';
|
||||||
hangupBtn.className = 'destructive-btn';
|
hangupBtn.className = 'destructive-btn';
|
||||||
hangupBtn.textContent = 'Положить';
|
hangupBtn.textContent = 'Положить трубку';
|
||||||
hangupBtn.addEventListener('click', async () => {
|
hangupBtn.addEventListener('click', async () => {
|
||||||
await hangupActiveCall();
|
await hangupActiveCall();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user