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:
@@ -40,10 +40,27 @@ const CallScreen: React.FC = () => {
|
||||
const [remoteVideoTrack, setRemoteVideoTrack] = useState<VideoTrack | null>(null);
|
||||
const [localVideoTrack, setLocalVideoTrack] = useState<VideoTrack | null>(null);
|
||||
|
||||
// Subscribe to LiveKit track events
|
||||
// Event-driven video track subscription (no polling)
|
||||
useEffect(() => {
|
||||
const unsubs: (() => void)[] = [];
|
||||
|
||||
// Sync current tracks immediately
|
||||
const syncTracks = () => {
|
||||
const lp = liveKitService.localParticipant;
|
||||
if (lp) {
|
||||
const videoPub = lp.getTrackPublication(Track.Source.Camera);
|
||||
setLocalVideoTrack((videoPub?.track as VideoTrack | undefined) ?? null);
|
||||
}
|
||||
liveKitService.remoteParticipants.forEach((participant) => {
|
||||
const videoPub = participant.getTrackPublication(Track.Source.Camera);
|
||||
if (videoPub?.track) {
|
||||
setRemoteVideoTrack(videoPub.track as VideoTrack);
|
||||
}
|
||||
});
|
||||
};
|
||||
syncTracks();
|
||||
|
||||
// Remote video track events
|
||||
unsubs.push(
|
||||
liveKitService.on('trackSubscribed', ({ track }) => {
|
||||
if (track.kind === Track.Kind.Video) {
|
||||
@@ -69,28 +86,32 @@ const CallScreen: React.FC = () => {
|
||||
);
|
||||
|
||||
unsubs.push(
|
||||
liveKitService.on('trackUnmuted', ({ publication, participant }) => {
|
||||
liveKitService.on('trackUnmuted', ({ publication }) => {
|
||||
if (publication.source === Track.Source.Camera && publication.track) {
|
||||
setRemoteVideoTrack(publication.track as VideoTrack);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Poll local video track from localParticipant
|
||||
const localPollInterval = setInterval(() => {
|
||||
const lp = liveKitService.localParticipant;
|
||||
if (lp) {
|
||||
const videoPub = lp.getTrackPublication(Track.Source.Camera);
|
||||
const track = (videoPub?.track ?? null) as VideoTrack | null;
|
||||
setLocalVideoTrack((prev) => (prev === track ? prev : track));
|
||||
} else {
|
||||
setLocalVideoTrack((prev) => (prev === null ? prev : null));
|
||||
}
|
||||
}, 500);
|
||||
// Local video track events (replace 500ms polling)
|
||||
unsubs.push(
|
||||
liveKitService.on('localTrackPublished', ({ publication }) => {
|
||||
if (publication.source === Track.Source.Camera && publication.track) {
|
||||
setLocalVideoTrack(publication.track as VideoTrack);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
unsubs.push(
|
||||
liveKitService.on('localTrackUnpublished', ({ publication }) => {
|
||||
if (publication.source === Track.Source.Camera) {
|
||||
setLocalVideoTrack(null);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubs.forEach((unsub) => unsub());
|
||||
clearInterval(localPollInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user