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

因為項目需求需要通過調(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)大家可以仁者見仁智者見智席楚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市税稼,隨后出現(xiàn)的幾起案子烦秩,更是在濱河造成了極大的恐慌,老刑警劉巖郎仆,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件只祠,死亡現(xiàn)場離奇詭異,居然都是意外死亡扰肌,警方通過查閱死者的電腦和手機抛寝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曙旭,“玉大人墩剖,你說我怎么就攤上這事∫恼” “怎么了岭皂?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沼头。 經(jīng)常有香客問我爷绘,道長书劝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任土至,我火速辦了婚禮购对,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陶因。我一直安慰自己骡苞,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布楷扬。 她就那樣靜靜地躺著解幽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烘苹。 梳的紋絲不亂的頭發(fā)上躲株,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音镣衡,去河邊找鬼霜定。 笑死,一個胖子當著我的面吹牛廊鸥,可吹牛的內(nèi)容都是我干的望浩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼惰说,長吁一口氣:“原來是場噩夢啊……” “哼曾雕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起助被,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤剖张,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后揩环,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔弄,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年丰滑,在試婚紗的時候發(fā)現(xiàn)自己被綠了顾犹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡褒墨,死狀恐怖炫刷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郁妈,我是刑警寧澤浑玛,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站噩咪,受9級特大地震影響顾彰,放射性物質(zhì)發(fā)生泄漏极阅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一涨享、第九天 我趴在偏房一處隱蔽的房頂上張望筋搏。 院中可真熱鬧,春花似錦厕隧、人聲如沸奔脐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓迎。三九已至,卻和暖如春挡爵,著一層夾襖步出監(jiān)牢的瞬間竖般,已是汗流浹背甚垦。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工茶鹃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人艰亮。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓闭翩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迄埃。 傳聞我的和親對象是個殘疾皇子疗韵,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

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