feat(CallScreen, FloatingCallWindow, callStore, wsService): enhance call status handling and UI updates
- Updated call status messages to include 'calling', 'reconnecting', and 'failed' states for better user feedback. - Improved video handling logic in CallScreen to use currentCall.isVideoEnabled for local video display. - Enhanced callStore to manage new call statuses and ensure proper timeout handling during 'calling' state. - Added a method in wsService to retrieve the active call state after WebSocket reconnections. - Refactored FloatingCallWindow to reflect updated call status messages for consistency across components.
This commit is contained in:
@@ -12,7 +12,15 @@ import { useAuthStore } from './authStore';
|
||||
import { useUserStore } from './userStore';
|
||||
import { userManager } from './userManager';
|
||||
|
||||
export type CallStatus = 'idle' | 'ringing' | 'connecting' | 'connected' | 'renegotiating' | 'ending';
|
||||
export type CallStatus =
|
||||
| 'idle' // 空闲状态
|
||||
| 'calling' // 正在呼出(已发送邀请,等待对方响应)
|
||||
| 'ringing' // 来电响铃中
|
||||
| 'connecting' // 正在建立连接(WebRTC 协商中)
|
||||
| 'connected' // 已接通
|
||||
| 'reconnecting' // 网络断开,正在重连
|
||||
| 'ended' // 已结束
|
||||
| 'failed'; // 连接失败
|
||||
|
||||
export type CallType = 'voice' | 'video';
|
||||
|
||||
@@ -160,35 +168,60 @@ function handleNegotiationNeeded(callId: string, offer: RTCSessionDescriptionIni
|
||||
wsService.sendCallSDP(callId, 'offer', offer.sdp || '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle connection state change
|
||||
*/
|
||||
function handleConnectionStateChange(state: string): void {
|
||||
console.log('[CallStore] Connection state changed:', state);
|
||||
/**
|
||||
* Handle connection state change with enhanced state machine
|
||||
*/
|
||||
function handleConnectionStateChange(state: string): void {
|
||||
console.log('[CallStore] Connection state changed:', state);
|
||||
|
||||
if (state === 'connected') {
|
||||
const now = Date.now();
|
||||
callStore.setState((s) => ({
|
||||
currentCall: s.currentCall
|
||||
? { ...s.currentCall, status: 'connected', startedAt: now }
|
||||
: null,
|
||||
}));
|
||||
|
||||
if (durationTimer) clearInterval(durationTimer);
|
||||
durationTimer = setInterval(() => {
|
||||
callStore.setState((s) => ({ callDuration: s.callDuration + 1 }));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Only end call if connection failed after being connected
|
||||
// Don't end call during initial connection setup
|
||||
if (state === 'failed') {
|
||||
const { currentCall } = callStore.getState();
|
||||
if (currentCall && currentCall.status === 'connected') {
|
||||
callStore.getState().endCall('connection_lost');
|
||||
if (!currentCall) return;
|
||||
|
||||
switch (state) {
|
||||
case 'connected':
|
||||
// 连接成功,开始计时
|
||||
const now = Date.now();
|
||||
callStore.setState((s) => ({
|
||||
currentCall: s.currentCall
|
||||
? { ...s.currentCall, status: 'connected', startedAt: now }
|
||||
: null,
|
||||
}));
|
||||
|
||||
if (durationTimer) clearInterval(durationTimer);
|
||||
durationTimer = setInterval(() => {
|
||||
callStore.setState((s) => ({ callDuration: s.callDuration + 1 }));
|
||||
}, 1000);
|
||||
break;
|
||||
|
||||
case 'disconnected':
|
||||
// 临时断开,进入重连状态
|
||||
if (currentCall.status === 'connected') {
|
||||
callStore.setState((s) => ({
|
||||
currentCall: s.currentCall
|
||||
? { ...s.currentCall, status: 'reconnecting' }
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
// 连接失败
|
||||
if (currentCall.status === 'connected' || currentCall.status === 'reconnecting') {
|
||||
callStore.getState().endCall('connection_failed');
|
||||
} else if (currentCall.status === 'connecting') {
|
||||
// 初始连接失败
|
||||
callStore.getState().endCall('connection_failed');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closed':
|
||||
// 连接关闭
|
||||
if (currentCall.status !== 'ended' && currentCall.status !== 'failed') {
|
||||
callStore.getState().endCall('connection_closed');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming SDP offer with Glare handling
|
||||
@@ -600,7 +633,7 @@ export const callStore = create<CallState>((set, get) => ({
|
||||
peerName: calleeName,
|
||||
peerAvatar: callee?.avatar,
|
||||
callType,
|
||||
status: 'ringing',
|
||||
status: 'calling', // 改为 'calling' 表示正在呼出
|
||||
duration: 0,
|
||||
isMuted: false,
|
||||
isSpeakerOn: false,
|
||||
@@ -626,7 +659,8 @@ export const callStore = create<CallState>((set, get) => ({
|
||||
if (callTimeoutTimer) clearTimeout(callTimeoutTimer);
|
||||
callTimeoutTimer = setTimeout(() => {
|
||||
const { currentCall: cc } = get();
|
||||
if (cc && cc.status === 'ringing') {
|
||||
// 只有在 'calling' 状态(呼出中)才超时
|
||||
if (cc && cc.status === 'calling') {
|
||||
console.warn('[CallStore] Call timeout');
|
||||
get().endCall('timeout');
|
||||
}
|
||||
@@ -637,12 +671,15 @@ export const callStore = create<CallState>((set, get) => ({
|
||||
const { incomingCall } = get();
|
||||
if (!incomingCall) return;
|
||||
|
||||
console.log('[CallStore] acceptCall, incomingCall.callType:', incomingCall.callType);
|
||||
|
||||
if (callTimeoutTimer) {
|
||||
clearTimeout(callTimeoutTimer);
|
||||
callTimeoutTimer = null;
|
||||
}
|
||||
|
||||
const isVideoCall = incomingCall.callType === 'video';
|
||||
console.log('[CallStore] acceptCall, isVideoCall:', isVideoCall);
|
||||
const myUserId = useAuthStore.getState().currentUser?.id || '';
|
||||
|
||||
set({
|
||||
|
||||
Reference in New Issue
Block a user