AVAudioSession重新激活后outputVolume无法获取正确设备音量
iOS 18 下 AVAudioSession 重新激活后 outputVolume 不更新的问题
问题描述
使用AVAudioSession.sharedInstance()管理音频会话时,首次激活(调用.setActive(true))后能正确获取设备真实音量,但停用会话(.setActive(false))并调整系统音量后,再次激活会话,outputVolume返回的仍是停用前的旧值,只有通过物理按键或控制中心调整音量后,观察者才能拿到正确值。该问题仅存在于iOS 18.0.1 和 iOS 18.1,iOS 17.6.1 无此问题,且启停音频会话是应用核心逻辑。
复现步骤
- 启用音频会话,初始音量值正确
- 按音量键,文本更新正常
- 停用音频会话,通过物理按键/控制中心调整系统音量
- 重新启用音频会话,
outputVolume未更新,仍为停用前数值
复现代码
import AVFoundation import SwiftUI @main struct AudioSessionTestApp: App { @State private var active = false @State private var volume = 0.0 @State private var volumeObservation: NSKeyValueObservation? var body: some Scene { WindowGroup { ZStack { Color.red.interpolateTo( color: Color.green, fraction: volume ).ignoresSafeArea() VStack(spacing: 24) { Text("Audio session active: \(active)") Button("Toggle") { active = !active if active { enableSession() } else { disableSession() } }.buttonStyle(.borderedProminent) Text("Session volume: \(volume)") } .padding() } } } func enableSession() { let session = AVAudioSession.sharedInstance() try? session.setActive(true) try? session.setCategory(.playback) volume = Double(session.outputVolume) volumeObservation = session.observe(\.outputVolume) { session, _ in NSLog("Observed volume change to \(session.outputVolume)") volume = Double(session.outputVolume) } } func disableSession() { volumeObservation?.invalidate() volumeObservation = nil let session = AVAudioSession.sharedInstance() try? session.setActive(false) } } extension Color { var components: (r: Double, g: Double, b: Double, o: Double)? { let uiColor: UIColor var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 var o: CGFloat = 0 if description.contains("NamedColor") { let lowerBound = description.range(of: "name: \"")!.upperBound let upperBound = description.range(of: "\", bundle")!.lowerBound let assetsName = String(description[lowerBound ..< upperBound]) uiColor = UIColor(named: assetsName)! } else { uiColor = UIColor(self) } guard uiColor.getRed(&r, green: &g, blue: &b, alpha: &o) else { return nil } return (Double(r), Double(g), Double(b), Double(o)) } func interpolateTo(color: Color, fraction: Double) -> Color { let s = components! let t = color.components! let r: Double = s.r + (t.r - s.r) * fraction let g: Double = s.g + (t.g - s.g) * fraction let b: Double = s.b + (t.b - s.b) * fraction let o: Double = s.o + (t.o - s.o) * fraction return Color(red: r, green: g, blue: b, opacity: o) } }
解决方案
针对iOS 18的这个bug,以下是几种可行的规避方法:
方法1:激活会话后延迟读取outputVolume
iOS 18中,会话激活后需要一小段时间才能同步真实音量,延迟读取可以拿到正确值:
func enableSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback) try session.setActive(true) } catch { print("Failed to activate session: \(error)") } // 延迟0.1秒读取音量,确保同步完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { volume = Double(session.outputVolume) } volumeObservation = session.observe(\.outputVolume) { session, _ in NSLog("Observed volume change to \(session.outputVolume)") volume = Double(session.outputVolume) } }
注:延迟时间可根据实际场景调整,0.1秒在多数情况下足够稳定。
方法2:播放静音音频触发会话同步
激活会话后播放一段极短的静音音频,强制会话同步系统音量:
func enableSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback) try session.setActive(true) } catch { print("Failed to activate session: \(error)") } // 播放静音音频触发同步(需提前将静音wav文件加入项目) guard let silenceURL = Bundle.main.url(forResource: "silence", withExtension: "wav") else { volume = Double(session.outputVolume) return } do { let player = try AVAudioPlayer(contentsOf: silenceURL) player.volume = 0 player.play() } catch { print("Failed to play silence audio: \(error)") } // 立即读取真实音量 volume = Double(session.outputVolume) volumeObservation = session.observe(\.outputVolume) { session, _ in NSLog("Observed volume change to \(session.outputVolume)") volume = Double(session.outputVolume) } }
注:需提前准备一个无声音频文件(如1秒静音wav),该方法能确保会话立即同步真实音量,性能开销可忽略。
方法3:监听系统全局音量变化通知
注册系统音量变化的私有通知,捕获会话激活前的音量调整:
@State private var volumeNotificationObserver: NSObjectProtocol? func enableSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback) try session.setActive(true) } catch { print("Failed to activate session: \(error)") } // 初始读取音量 volume = Double(session.outputVolume) // KVO监听会话音量 volumeObservation = session.observe(\.outputVolume) { session, _ in NSLog("Observed volume change to \(session.outputVolume)") volume = Double(session.outputVolume) } // 监听系统全局音量变化 volumeNotificationObserver = NotificationCenter.default.addObserver( forName: NSNotification.Name("AVSystemController_SystemVolumeDidChangeNotification"), object: nil, queue: .main ) { notification in guard let volumeValue = notification.userInfo?["AVSystemController_AudioVolumeNotificationParameter"] as? Float else { return } volume = Double(volumeValue) } } func disableSession() { volumeObservation?.invalidate() volumeObservation = nil if let observer = volumeNotificationObserver { NotificationCenter.default.removeObserver(observer) volumeNotificationObserver = nil } let session = AVAudioSession.sharedInstance() do { try session.setActive(false) } catch { print("Failed to deactivate session: \(error)") } }
注:该通知为私有API,虽在iOS各版本普遍可用,但需注意未来系统版本的兼容性风险。
总结
优先推荐方法1(延迟读取),实现简单无额外依赖;若延迟读取仍不稳定,可尝试方法2;方法3作为备选方案。
内容的提问来源于stack exchange,提问作者Albert221
相关产品推荐
相关产品推荐

