forked from eatvio/eatvio_app
Merge pull request 'feat(video_calls): ios calle mic fix' (#1093) from misha/eatvio_app:issue-1089 into main
Reviewed-on: eatvio/eatvio_app#1093 Reviewed-by: Анатолий Полоз <t0ly.od@ya.ru>
This commit is contained in:
@@ -20,9 +20,8 @@ import flutter_foreground_task
|
||||
private let voipDiagnosticsChannelName = "com.eatvioapp/voip_diagnostics"
|
||||
private let callAudioSessionChannelName = "com.eatvioapp/call_audio_session"
|
||||
private let appUrlScheme = "eatvio"
|
||||
/// If CallKit ends while the in-app call must continue, take the audio
|
||||
/// session back after iOS releases it. Incoming accept keeps CallKit up,
|
||||
/// so this is only for an unexpected teardown.
|
||||
/// True only if CallKit ended while the in-app call must keep audio.
|
||||
/// Incoming accept keeps CallKit up, so this stays false after answer.
|
||||
private var resumeAudioAfterCallKitEnds = false
|
||||
/// The push handler and the Flutter channel touch this map on different
|
||||
/// queues. The push writes the id before CallKit has the call, so the
|
||||
@@ -1077,11 +1076,13 @@ import flutter_foreground_task
|
||||
|
||||
|
||||
func onAccept(_ call: Call, _ action: CXAnswerCallAction) {
|
||||
resumeAudioAfterCallKitEnds = true
|
||||
// Dart may call activate before didActivate. Claim the session now so
|
||||
// that path does not setActive or start the audio unit without a track.
|
||||
// CallKit stays up for the whole incoming call. Do not take the session
|
||||
// back after a later deactivate — that path is hang-up.
|
||||
resumeAudioAfterCallKitEnds = false
|
||||
callKitOwnsAudioSession = true
|
||||
prepareCallAudioCategory()
|
||||
// Category only. RTCAudioSession.setConfiguration here races CallKit's
|
||||
// activation and can start I/O before didActivate.
|
||||
prepareAvAudioSessionCategory()
|
||||
storePendingAcceptedCall(call)
|
||||
recordVoipDiagnostic(
|
||||
"callkit_accept_native",
|
||||
@@ -1122,11 +1123,10 @@ import flutter_foreground_task
|
||||
let rtcSession = RTCAudioSession.sharedInstance()
|
||||
rtcSession.useManualAudio = true
|
||||
rtcSession.audioSessionDidActivate(audioSession)
|
||||
applyCallAudioConfiguration(active: false)
|
||||
// Incoming accept has no LiveKit microphone track yet. Starting the
|
||||
// unit here attaches capture to nothing; a later isAudioEnabled=true
|
||||
// is a no-op and the callee microphone stays silent. Dart starts the
|
||||
// unit via restart after setMicrophoneEnabled.
|
||||
// WebRTC must start I/O inside this CallKit callback. Enabling later
|
||||
// misses the session CallKit just activated; toggling after that
|
||||
// does not restart the ADM, so the incoming microphone stays silent.
|
||||
rtcSession.isAudioEnabled = true
|
||||
}
|
||||
|
||||
func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
|
||||
@@ -1154,7 +1154,7 @@ import flutter_foreground_task
|
||||
RTCAudioSessionConfiguration.setWebRTC(configuration)
|
||||
}
|
||||
|
||||
private func prepareCallAudioCategory() {
|
||||
private func prepareAvAudioSessionCategory() {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
try session.setCategory(
|
||||
@@ -1165,36 +1165,37 @@ import flutter_foreground_task
|
||||
} catch {
|
||||
NSLog("[EatvioVoIP] prepare call audio category failed: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func prepareCallAudioCategory() {
|
||||
prepareAvAudioSessionCategory()
|
||||
applyCallAudioConfiguration(active: false)
|
||||
}
|
||||
|
||||
private func activateInAppCallAudioSession() {
|
||||
let rtcSession = RTCAudioSession.sharedInstance()
|
||||
rtcSession.useManualAudio = true
|
||||
// CallKit already activated the session on answer. A second setActive
|
||||
// fails or replaces that session, and the incoming microphone stays dead.
|
||||
applyCallAudioConfiguration(active: !callKitOwnsAudioSession)
|
||||
if callKitOwnsAudioSession {
|
||||
// The unit starts in restartInAppCallAudioUnit after the microphone
|
||||
// track exists. Enabling it here is the incoming-only silent-mic bug.
|
||||
rtcSession.isAudioEnabled = true
|
||||
return
|
||||
}
|
||||
applyCallAudioConfiguration(active: true)
|
||||
rtcSession.isAudioEnabled = true
|
||||
}
|
||||
|
||||
/// Starts capture after the microphone track exists. Incoming CallKit
|
||||
/// must not have started the unit earlier, or this assignment is a no-op.
|
||||
/// Outgoing (no CallKit) may need a teardown to attach capture. Incoming
|
||||
/// already started the unit in didActivate — toggling it here kills ADM.
|
||||
private func restartInAppCallAudioUnit() {
|
||||
audioRestartGeneration += 1
|
||||
let generation = audioRestartGeneration
|
||||
let rtcSession = RTCAudioSession.sharedInstance()
|
||||
rtcSession.useManualAudio = true
|
||||
let needsTeardown = rtcSession.isAudioEnabled
|
||||
if needsTeardown {
|
||||
rtcSession.isAudioEnabled = false
|
||||
if callKitOwnsAudioSession {
|
||||
rtcSession.isAudioEnabled = true
|
||||
return
|
||||
}
|
||||
let delay: TimeInterval = needsTeardown ? 0.25 : 0
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
audioRestartGeneration += 1
|
||||
let generation = audioRestartGeneration
|
||||
rtcSession.isAudioEnabled = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in
|
||||
guard let self, generation == self.audioRestartGeneration else { return }
|
||||
self.enableInAppCallAudioUnit()
|
||||
}
|
||||
@@ -1203,7 +1204,9 @@ import flutter_foreground_task
|
||||
private func enableInAppCallAudioUnit() {
|
||||
let rtcSession = RTCAudioSession.sharedInstance()
|
||||
rtcSession.useManualAudio = true
|
||||
applyCallAudioConfiguration(active: !callKitOwnsAudioSession)
|
||||
if !callKitOwnsAudioSession {
|
||||
applyCallAudioConfiguration(active: true)
|
||||
}
|
||||
rtcSession.isAudioEnabled = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1187,6 +1187,11 @@ class VideoCallManager {
|
||||
if (status.isGranted) return true;
|
||||
status = await Permission.microphone.request();
|
||||
if (status.isGranted) return true;
|
||||
if (Platform.isIOS) {
|
||||
// CallKit already opened a recording session. A stale permission
|
||||
// status must not block publishing the incoming microphone.
|
||||
return true;
|
||||
}
|
||||
final ctx2 = _navigatorContext;
|
||||
if (ctx2 == null || !ctx2.mounted) return false;
|
||||
final s = S.of(ctx2);
|
||||
|
||||
@@ -246,7 +246,7 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
counterpartName: counterpartName,
|
||||
localParticipantName: localParticipantName,
|
||||
counterpartIsConsultant: counterpartIsConsultant ?? false,
|
||||
micEnabled: false,
|
||||
micEnabled: isIncomingAccepted,
|
||||
videoEnabled: false,
|
||||
callStageEntered: false,
|
||||
connectError: null,
|
||||
@@ -259,7 +259,7 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
conversationUuid: conversationUuid,
|
||||
counterpartName: counterpartName,
|
||||
localParticipantName: localParticipantName,
|
||||
micEnabled: false,
|
||||
micEnabled: isIncomingAccepted,
|
||||
videoEnabled: false,
|
||||
);
|
||||
if (!started) {
|
||||
@@ -371,10 +371,11 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
Future<void> _requestMicBeforeIncomingEnter() async {
|
||||
if (state.entryMode != VideoCallEntryMode.incomingAccepted) return;
|
||||
|
||||
final granted = await _requestMicPermissionForCall();
|
||||
if (!granted || isClosed) return;
|
||||
await _requestMicPermissionForCall();
|
||||
if (isClosed) return;
|
||||
if (state.isMicActiveForUi) return;
|
||||
|
||||
await _videoCallManager.toggleMic(state.isMicActiveForUi);
|
||||
await _videoCallManager.toggleMic(false);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.3.62+151
|
||||
version: 1.3.63+152
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
|
||||
@@ -257,7 +257,7 @@ void main() {
|
||||
conversationUuid: 'uuid',
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: false,
|
||||
micEnabled: true,
|
||||
videoEnabled: false,
|
||||
),
|
||||
).called(1);
|
||||
@@ -357,7 +357,7 @@ void main() {
|
||||
conversationUuid: 'uuid',
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: false,
|
||||
micEnabled: true,
|
||||
videoEnabled: false,
|
||||
),
|
||||
).called(1);
|
||||
|
||||
Reference in New Issue
Block a user