feat(call): implement CallKeep integration and enhance group call support
Integrate `expo-callkit-telecom` to support native system call handling (answering, ending, and muting via system UI). This includes a new `callKeepService` and a `CallKeepBootstrap` component to manage the lifecycle of system call events. Additionally, improves the calling experience by: - Adding support for group calls with participant tracking and UI indicators. - Enhancing LiveKit integration by replacing polling with event-driven video track synchronization. - Updating `WebSocketService` to prevent disconnection when the app enters the background during an active call. - Adding new WebSocket message types for participant join/leave events and group invites. - Refining call UI components (`CallScreen`, `FloatingCallWindow`, `IncomingCallModal`) for better visual feedback and safe area handling. Refactor LiveKit service to use event-driven updates for local and remote tracks, improving performance and reliability.
This commit is contained in:
@@ -44,6 +44,8 @@ export type WSMessageType =
|
||||
| 'call_peer_muted'
|
||||
| 'call_invited'
|
||||
| 'call_answered_elsewhere'
|
||||
| 'call_participant_joined'
|
||||
| 'call_participant_left'
|
||||
| 'sync_required';
|
||||
|
||||
export interface WSCallIncomingMessage {
|
||||
@@ -103,6 +105,19 @@ export interface WSCallAnsweredElsewhereMessage {
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface WSCallParticipantJoinedMessage {
|
||||
type: 'call_participant_joined';
|
||||
call_id: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
export interface WSCallParticipantLeftMessage {
|
||||
type: 'call_participant_left';
|
||||
call_id: string;
|
||||
user_id: string;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface WSErrorMessage {
|
||||
type: 'error';
|
||||
code: string;
|
||||
@@ -257,6 +272,8 @@ export type WSMessage =
|
||||
| WSCallPeerMutedMessage
|
||||
| WSCallInvitedMessage
|
||||
| WSCallAnsweredElsewhereMessage
|
||||
| WSCallParticipantJoinedMessage
|
||||
| WSCallParticipantLeftMessage
|
||||
| WSErrorMessage
|
||||
| WSSyncRequiredMessage;
|
||||
|
||||
@@ -300,6 +317,9 @@ class WebSocketService {
|
||||
private isFallbackMode = false;
|
||||
private fallbackCheckTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// 通话中阻止后台断开(对方请求权限等场景会导致 AppState 切换,不应断开 WebSocket)
|
||||
preventDisconnectOnBackground = false;
|
||||
|
||||
private getWSUrl(token: string | null): string {
|
||||
return `${WS_URL}?token=${encodeURIComponent(token || '')}&compress=1`;
|
||||
}
|
||||
@@ -747,6 +767,14 @@ class WebSocketService {
|
||||
this.sendFireAndForget('call_ready', { call_id: callId });
|
||||
}
|
||||
|
||||
sendCallGroupInvite(groupId: string, conversationId: string, callType: string): void {
|
||||
this.sendFireAndForget('call_group_invite', {
|
||||
group_id: groupId,
|
||||
conversation_id: conversationId,
|
||||
call_type: callType,
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessageSent(payload: any): void {
|
||||
// 找到对应的发送请求并resolve
|
||||
const pendingMsg = this.pendingMessages.find(p =>
|
||||
@@ -1059,9 +1087,14 @@ class WebSocketService {
|
||||
this.lastAppState = AppState.currentState;
|
||||
this.appStateSubscription = AppState.addEventListener('change', (nextState: AppStateStatus) => {
|
||||
// 进入后台时断开 WebSocket,由 JPush 负责后台推送通知
|
||||
// 但如果正在通话中则不断开,避免对方请求权限等场景导致通话中断
|
||||
if (this.lastAppState === 'active' && nextState.match(/inactive|background/)) {
|
||||
console.log('[WebSocket] App entering background, disconnecting');
|
||||
this.disconnect();
|
||||
if (this.preventDisconnectOnBackground) {
|
||||
console.log('[WebSocket] App entering background but call is active, skipping disconnect');
|
||||
} else {
|
||||
console.log('[WebSocket] App entering background, disconnecting');
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
// 回到前台时重连 WebSocket
|
||||
if (this.lastAppState.match(/inactive|background/) && nextState === 'active' && !this.isConnected()) {
|
||||
|
||||
Reference in New Issue
Block a user