fix(ios): output muted with A2DP, session lifecycle, mic input issues
Three bugs causing no audio output and no mic input:
1. .voiceChat mode + A2DP = output muted. The .voiceChat mode uses hardware
AEC/AGC/HPF but requires HFP-compatible routes. A2DP is NOT HFP — iOS
mutes the output because it can't set up the voice processing pipeline on
an A2DP route. Fix: use .default mode for Standard+A2DP (no hardware AEC,
but audio routes correctly). .voiceChat kept for HFP and speaker modes.
Added info warning in Settings UI for A2DP no-AEC.
2. Session lifecycle broken. stopMicStream() called deactivateAfterStreaming()
which deactivated the AVAudioSession — but the AudioEngine keeps running for
remote audio playback, so leaving voice killed all remote audio. And the
session was never activated when a remote user started talking (only on
Join Voice), so you couldn't hear anyone before joining voice. Fix:
- ensureSessionActive() replaces activateForStreaming() — idempotent, called
on Join Voice AND on .streamStarted (remote user starts talking).
- stopMicStream() no longer deactivates the session.
- deactivateSession() called only on disconnect from server.
- isSessionActive flag tracks state, updated by interruption handler.
3. setPreferredInputNumberOfChannels(1) called for mono — unnecessary (1 is
the default) and may put the session in a bad state on some devices. Fix:
only call it when stereo is explicitly selected. Also handle empty input
port ID (selecting 'Default' in the picker) correctly.
Added comprehensive route logging — after activation, logs the current output
and input route names so issues can be diagnosed from Console.app.
This commit is contained in:
@@ -100,6 +100,16 @@ final class SessionState {
|
||||
let who = users.first(where: { $0.id == ev.userId })?.nickname ?? "user \(ev.userId)"
|
||||
addActivity(talking ? "\(who) started talking" : "\(who) stopped talking")
|
||||
case .streamStarted:
|
||||
// A remote user started a stream — ensure the audio session is active so we can
|
||||
// hear them even if we haven't joined voice ourselves. Previously the session was
|
||||
// only activated when the user pressed Join Voice, so remote audio was silent.
|
||||
if ev.userId != selfUserId {
|
||||
do {
|
||||
try AudioSessionManager.shared.ensureSessionActive()
|
||||
} catch {
|
||||
addActivity("Audio session activate failed: \(error)")
|
||||
}
|
||||
}
|
||||
addActivity("Stream started (user \(ev.userId))")
|
||||
case .streamStopped:
|
||||
addActivity("Stream stopped (user \(ev.userId))")
|
||||
@@ -184,7 +194,7 @@ final class SessionState {
|
||||
|
||||
private func doStartMicStream() {
|
||||
do {
|
||||
try AudioSessionManager.shared.activateForStreaming()
|
||||
try AudioSessionManager.shared.ensureSessionActive()
|
||||
} catch {
|
||||
addActivity("AVAudioSession activate failed: \(error)")
|
||||
return
|
||||
@@ -213,7 +223,9 @@ final class SessionState {
|
||||
}
|
||||
voiceState.micActive = false
|
||||
voiceState.level = 0
|
||||
AudioSessionManager.shared.deactivateAfterStreaming()
|
||||
// Do NOT deactivate the AVAudioSession here — the user may still want to hear
|
||||
// remote audio (other people talking). The session is deactivated only when
|
||||
// disconnecting from the server (see AppState.disconnect / .disconnected event).
|
||||
}
|
||||
|
||||
func setMute(_ muted: Bool, deafened: Bool) {
|
||||
|
||||
Reference in New Issue
Block a user