因為項目需求需要通過調(diào)節(jié)手機音量鍵調(diào)節(jié)遠程硬件設(shè)備音量虱饿,所以對iOS系統(tǒng)音量事件做了一些研究鸡典,也嘗試了網(wǎng)上的一些方法炫欺,所以記錄一些所見所得:
1. 通過KVO方式
監(jiān)聽AVAudioSession的OutputVolume屬性,同時需要override observeValue方法
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
try AVAudioSession.sharedInstance().setActive(true)
AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: [.new, .old], context: nil)
} catch {
//處理Error
}
這個方法的缺點在于當音量調(diào)到最小或者最大之后再向下或向上調(diào)節(jié)音量是不會產(chǎn)生事件的佑菩,不過也有work around的方法扁凛,也就是當音量到最小或最大時設(shè)置把系統(tǒng)音量再設(shè)置為一個相對大一點(0.0001)或者小一點(0.9999)的值忍疾。
let maxVolume: Float = 0.9999
let minVolume: Float = 0.0001
var systemVolume: Float?
var systemVolumeView: MPVolumeView?
var systemVolumeSlider: UISlider?
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "outputVolume" {
guard let newVolume = change?[.newKey] as? Float else {
return
}
guard let oldVolume = change?[.oldKey] as? Float else {
return
}
if newVolume == minVolume || newVolume == maxVolume {
return
}
//systemVolumeSlider會在后面有說明
if newVolume == 0 {
systemVolumeSlider?.setValue(minVolume, animated: false)
} else if newVolume == 1 {
systemVolumeSlider?.setValue(maxVolume, animated: false)
}
//做音量的比較處理
}
}
設(shè)置系統(tǒng)音量的方式不同版本也不太一樣:
//iOS7以前可以通過
MPMusicPlayerController.applicationMusicPlayer.setVolume()
//iOS7之后可以通過MPVolumeView里的音量Slider來設(shè)置
systemVolumeView = MPVolumeView(frame: CGRect(x: 0, y: -200, width: 320, height: 100))
for view in systemVolumeView!.subviews {
if view.isKind(of: UISlider.self) {
systemVolumeSlider = view as? UISlider
break
}
}
}
//通過類似一下方式設(shè)置
let maxVolume: Float = 0.9999
let minVolume: Float = 0.0001
if systemVolume == 0 {
systemVolumeSlider?.setValue(minVolume, animated: false)
} else if systemVolume == 1 {
systemVolumeSlider?.setValue(maxVolume, animated: false)
}
不過以上的方法在iOS11下又會有問題,拿音量調(diào)小做說明:iOS11下調(diào)小音量的事件周期結(jié)束前是不允許設(shè)置的新值大于或等于舊值(0除外)谨朝。iOS音量鍵每觸發(fā)一下音量調(diào)節(jié)0.0625卤妒,音量為0.0625時調(diào)低音量甥绿,按照上面的方法在observeValue的方法里會觸發(fā)修改systemVolume的值到0.0001,這個時候修改會成功则披,音量會設(shè)置為0.0001共缕,此時再往下調(diào)節(jié)音量音量會為0,而不會再被修改到0.0001收叶,這個時候再調(diào)小就不會再產(chǎn)生事件了。
2. 通過監(jiān)聽Notification
監(jiān)聽 AVSystemController_SystemVolumeDidChangeNotification 事件
//替換前面AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: [.new, .old], context: nil)為
NotificationCenter.default.addObserver(self, selector: #selector(volumeNotification(_:)), name: NSNotification.Name(rawValue: "AVSystemController_SystemVolumeDidChangeNotification"), object: nil)
//處理Notification
@objc func volumeNotification(_ notification: Notification) {
guard notification.name.rawValue == "AVSystemController_SystemVolumeDidChangeNotification" else {
return
}
guard let userInfo = notification.userInfo else {
return
}
guard let reason = userInfo["AVSystemController_AudioVolumeChangeReasonNotificationParameter"] as? String, reason == "ExplicitVolumeChange" else {
return
}
if let volumeNotification = userInfo["AVSystemController_AudioVolumeNotificationParameter"] as? Float {
//可以根據(jù)該值來做判斷
//==0表示減小音量共苛,==1表示調(diào)大音量判没,(0,1)時需要保存上一次的值做比較
}
}
這種方式在iOS11下也表現(xiàn)正常,不過也得注意代碼中的過濾條件
關(guān)于隱藏系統(tǒng)的音量提示窗口
上面的兩種方式都會顯示系統(tǒng)的音量提示窗口隅茎,而在需求上我們其實是需要屏蔽掉的澄峰,這個時候我們就要借助于MPVolumeView。當window的subviews里存在MPVolumeView時系統(tǒng)音量提示窗口就不會顯示辟犀,需要注意的是MPVolumeView不能是hidden的也不能alpha=0俏竞,所以我們需要讓MPVolumeView在屏幕外
systemVolumeView = MPVolumeView(frame: CGRect(x: 0, y: -200, width: 320, height: 100))
UIApplication.shared.keyWindow?.insertSubview(systemVolumeView!, at: 0)
關(guān)于系統(tǒng)音量的恢復問題
還有一個問題在于我們在APP內(nèi)調(diào)節(jié)了系統(tǒng)的音量,但退出APP時實際上是期望音量恢復到之前的大小堂竟,這個就需要我們注意在APP的生命周期做相應的處理魂毁。
applicationDidBecomeActive 記錄當前音量并監(jiān)聽Notification
applicationWillResignActive 移除監(jiān)聽Notification并恢復之前記錄的音量
func applicationWillResignActive(_ application: UIApplication) {
VolumeHelper.shared.removeObserve()
VolumeHelper.shared.restoreSystemVolume()
}
func applicationDidBecomeActive(_ application: UIApplication) {
VolumeHelper.shared.observeHardwareVolume()
}
//記錄當前音量可以通過如下方式
systemVolume = AVAudioSession.sharedInstance().outputVolume
不過雖然如此還是處理不了APP crash或者人為殺掉情況下的系統(tǒng)音量恢復。
說明
代碼都是示意出嘹,具體實現(xiàn)大家可以仁者見仁智者見智席楚。