feat(CallScreen, FloatingCallWindow, callStore, wsService): enhance call status handling and UI updates
All checks were successful
Frontend CI / ota-android (push) Successful in 11m58s
Frontend CI / build-and-push-web (push) Successful in 15m49s
Frontend CI / build-android-apk (push) Successful in 1h27m7s

- 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:
lafay
2026-03-28 04:35:05 +08:00
parent fa10ef5116
commit 3c071957ce
5 changed files with 381 additions and 309 deletions

View File

@@ -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({