feat(livekit): improve native audio/video handling and stability
All checks were successful
Frontend CI / ota-ios (push) Successful in 1m55s
Frontend CI / ota-android (push) Successful in 1m57s
Frontend CI / build-and-push-web (push) Successful in 4m8s
Frontend CI / build-android-apk (push) Successful in 12m20s

Implement robust WebRTC polyfills and native audio session management for
LiveKit on mobile platforms. This includes configuring iOS audio sessions
to prevent issues during calls and implementing a delayed video
activation strategy to avoid main thread deadlocks on iOS.

- Add WebRTC and environment polyfills in `src/polyfills.ts`
- Implement iOS-specific `AudioSession` configuration in `LiveKitService`
- Add support for toggling speaker output on iOS
- Update `callStore` to handle video enablement after connection settles
- Fix `useUnreadCountQuery` to correctly handle async fetching
This commit is contained in:
2026-06-02 13:28:16 +08:00
parent 8ee6e77cb4
commit d6a94c7b4d
4 changed files with 118 additions and 12 deletions

View File

@@ -1,4 +1,5 @@
import '../../polyfills';
import { Platform } from 'react-native';
import { Room, RoomEvent, Track, ConnectionState, LocalParticipant, RemoteParticipant, RemoteTrackPublication, TrackPublication, Participant as LKParticipant } from 'livekit-client';
export interface LiveKitServiceConfig {
@@ -44,11 +45,26 @@ class LiveKitServiceImpl {
return this.room?.remoteParticipants ?? new Map();
}
async connect(url: string, token: string): Promise<void> {
async connect(url: string, token: string, isVideoCall: boolean = false): Promise<void> {
if (this.room) {
await this.disconnect();
}
// Configure native audio session before connecting (iOS only)
if (Platform.OS === 'ios') {
try {
const { AudioSession } = require('@livekit/react-native');
await AudioSession.configureAudio({
ios: {
defaultOutput: isVideoCall ? 'speaker' : 'default',
},
});
await AudioSession.startAudioSession();
} catch (err) {
console.warn('[LiveKit] Audio session configuration failed:', err);
}
}
this.room = new Room({
adaptiveStream: true,
dynacast: true,
@@ -87,6 +103,14 @@ class LiveKitServiceImpl {
await this.room.disconnect();
this.room = null;
this._connectionStatus = 'disconnected';
if (Platform.OS === 'ios') {
try {
const { AudioSession } = require('@livekit/react-native');
await AudioSession.stopAudioSession();
} catch {}
}
this.emit('connectionStatusChanged', 'disconnected');
this.emit('disconnected', undefined);
}
@@ -98,7 +122,20 @@ class LiveKitServiceImpl {
async setVideoEnabled(enabled: boolean): Promise<void> {
if (!this.room?.localParticipant) return;
await this.room.localParticipant.setCameraEnabled(enabled);
console.log('[LiveKit] setVideoEnabled called:', enabled);
try {
await this.room.localParticipant.setCameraEnabled(enabled);
console.log('[LiveKit] setVideoEnabled succeeded:', enabled);
} catch (err) {
console.error('[LiveKit] setVideoEnabled failed:', err);
throw err;
}
}
async setSpeakerOn(speakerOn: boolean): Promise<void> {
if (Platform.OS === 'web' || Platform.OS === 'android') return;
const AudioSession = require('@livekit/react-native').AudioSession;
await AudioSession.selectAudioOutput(speakerOn ? 'force_speaker' : 'default');
}
isMuted(): boolean {