feat(video_calls): ios calle mic fix

This commit is contained in:
misha
2026-10-22 12:16:09 +03:00
parent 3122394037
commit 992573aead
4 changed files with 64 additions and 2 deletions

View File

@@ -592,6 +592,7 @@ class VideoCallManager {
);
if (next) {
await _audioSession.restartCapture();
await _bindIosCallKitAudioEngine();
}
} catch (_) {
return;
@@ -791,6 +792,7 @@ class VideoCallManager {
audioCaptureOptions: _micCaptureOptions,
);
await _audioSession.restartCapture();
await _bindIosCallKitAudioEngine();
} catch (_) {}
}
await _resumeVideoAfterForeground();
@@ -1264,6 +1266,7 @@ class VideoCallManager {
audioCaptureOptions: _micCaptureOptions,
);
await _audioSession.restartCapture();
await _bindIosCallKitAudioEngine();
} catch (_) {
effectiveMic = false;
}
@@ -1304,6 +1307,17 @@ class VideoCallManager {
_lastMutedMicTotalEnergy = null;
}
Future<void> _bindIosCallKitAudioEngine() async {
if (!Platform.isIOS) return;
try {
final calls = await FlutterCallkitIncoming.activeCalls();
if (calls.isEmpty) return;
} catch (_) {
return;
}
await _audioSession.startCallKitCapture();
}
Future<void> _startCallAudioSession() async {
if (!_canHandleCallAudio) return;
try {

View File

@@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
typedef VideoCallAudioSessionFactory = Future<AudioSession> Function();
typedef IosCallAudioSessionHandler = Future<void> Function(String method);
typedef IosAdmHandler = Future<void> Function();
/// Configures and owns the platform audio session for an active VoIP call.
class VideoCallAudioSessionCoordinator {
@@ -12,18 +13,27 @@ class VideoCallAudioSessionCoordinator {
VideoCallAudioSessionFactory? audioSessionFactory,
bool? isIos,
IosCallAudioSessionHandler? iosCallAudioSession,
IosAdmHandler? stopLocalRecording,
IosAdmHandler? startLocalRecording,
}) : _audioSessionFactory =
audioSessionFactory ?? (() => AudioSession.instance),
_isIos = isIos ?? Platform.isIOS,
_iosCallAudioSession = iosCallAudioSession ?? _invokeIosCallAudioSession;
_iosCallAudioSession = iosCallAudioSession ?? _invokeIosCallAudioSession,
_stopLocalRecording = stopLocalRecording ?? _invokeStopLocalRecording,
_startLocalRecording = startLocalRecording ?? _invokeStartLocalRecording;
static const MethodChannel _iosChannel = MethodChannel(
'com.eatvioapp/call_audio_session',
);
static const MethodChannel _webrtcChannel = MethodChannel(
'FlutterWebRTC.Method',
);
final VideoCallAudioSessionFactory _audioSessionFactory;
final bool _isIos;
final IosCallAudioSessionHandler _iosCallAudioSession;
final IosAdmHandler _stopLocalRecording;
final IosAdmHandler _startLocalRecording;
static final AudioSessionConfiguration callConfiguration =
AudioSessionConfiguration(
@@ -54,6 +64,14 @@ class VideoCallAudioSessionCoordinator {
return _iosChannel.invokeMethod<void>(method);
}
static Future<void> _invokeStopLocalRecording() {
return _webrtcChannel.invokeMethod<void>('stopLocalRecording');
}
static Future<void> _invokeStartLocalRecording() {
return _webrtcChannel.invokeMethod<void>('startLocalRecording');
}
Future<void> start() async {
if (_isIos) {
await _iosCallAudioSession('activate');
@@ -97,6 +115,21 @@ class VideoCallAudioSessionCoordinator {
} catch (_) {}
}
/// flutter-webrtc 1.2 uses AudioEngine ADM. `isAudioEnabled` does not start
/// that engine. Incoming CallKit therefore needs `initAndStartRecording`
/// after the microphone track exists and CallKit has activated the session.
Future<void> startCallKitCapture() async {
if (!_isIos) return;
try {
await _stopLocalRecording();
} catch (_) {}
await Future<void>.delayed(const Duration(milliseconds: 120));
try {
await _startLocalRecording();
_iosActive = true;
} catch (_) {}
}
Future<void> stop() async {
if (_isIos) {
_iosActive = false;

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.63+152
version: 1.3.64+153
publish_to: 'none'
environment:

View File

@@ -28,6 +28,21 @@ void main() {
expect(calls, ['activate', 'activate', 'restart', 'deactivate']);
});
test('startCallKitCapture restarts the AudioEngine ADM', () async {
final adm = <String>[];
final coordinator = VideoCallAudioSessionCoordinator(
isIos: true,
iosCallAudioSession: (_) async {},
stopLocalRecording: () async => adm.add('stop'),
startLocalRecording: () async => adm.add('start'),
);
await coordinator.startCallKitCapture();
expect(coordinator.isActive, isTrue);
expect(adm, ['stop', 'start']);
});
test('stop clears the active session when deactivate fails', () async {
final coordinator = VideoCallAudioSessionCoordinator(
isIos: true,