iOS音量調節(jié)那些事

因為項目需求需要通過調節(jié)手機音量鍵調節(jié)遠程硬件設備音量,所以對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
}

這個方法的缺點在于當音量調到最小或者最大之后再向下或向上調節(jié)音量是不會產(chǎn)生事件的,不過也有work around的方法,也就是當音量到最小或最大時設置把系統(tǒng)音量再設置為一個相對大一點(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)
        }
        //做音量的比較處理
    }
}

設置系統(tǒng)音量的方式不同版本也不太一樣:

//iOS7以前可以通過
MPMusicPlayerController.applicationMusicPlayer.setVolume()
//iOS7之后可以通過MPVolumeView里的音量Slider來設置
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
        }
    }
}

//通過類似一下方式設置
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下又會有問題,拿音量調小做說明:iOS11下調小音量的事件周期結束前是不允許設置的新值大于或等于舊值(0除外)。iOS音量鍵每觸發(fā)一下音量調節(jié)0.0625,音量為0.0625時調低音量,按照上面的方法在observeValue的方法里會觸發(fā)修改systemVolume的值到0.0001,這個時候修改會成功,音量會設置為0.0001,此時再往下調節(jié)音量音量會為0,而不會再被修改到0.0001,這個時候再調小就不會再產(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表示調大音量,(0,1)時需要保存上一次的值做比較
    }
}

這種方式在iOS11下也表現(xià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)

關于系統(tǒng)音量的恢復問題

還有一個問題在于我們在APP內調節(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)大家可以仁者見仁智者見智。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 目錄 需求 總結 實現(xiàn) Demo 需求 最近在優(yōu)化項目中的播放器界面,設計師新出了一套UI,其中有一個音量的控制的...
    Jerry_Lee閱讀 10,890評論 4 2
  • 這是lion寫的第35篇原創(chuàng)微商創(chuàng)業(yè)文章。 凌晨3點起床,送小孩醫(yī)院檢查,初步診斷感冒引起的急性哮喘,昨天下午在玩...
    CrazyLion閱讀 373評論 0 0
  • 當夢醒時 當夢醒時,我們會干嘛呢? 若是美夢,便肯定倒頭繼續(xù);若是噩夢,必然少不了一聲冷汗。抿抿嘴唇,吞口唾沫...
    毛果蝶閱讀 643評論 0 1
  • Javascript 一般情況下,直接使用原生 Javascript 的代碼是通用的,所以備個份(部分庫的代碼在某...
    大鶸閱讀 368評論 0 1
  • 如我所說,智能手機的中端設備已經(jīng)死了。這情形和PC時代別無二致。 當一個領域被統(tǒng)一模版壟斷以后,惡性競爭下只會剩下...
    趙博思閱讀 1,142評論 6 10

友情鏈接更多精彩內容