iOS 8 一些跟 Audio 相關的改變

iOS 8 裡頭 Audio API 的底層有一些改變,但是蘋果改了這些地方,也沒有在文件裡頭說清楚,一開始也不會注意到這麼微小的改動。所以你原本寫的程式在升到 iOS 8 之後,就會出現一些奇怪的行為。

蘋果改動了 Audio Session 的行為。Audio Session 是一套在 iOS 裝置上用來描述、管理你的 App 使用的是哪一種 Audio 的方式,以及因為使用某種類型(Audio Session Category)的 Audio 所產生的對應行為。

不同種類的 App,會用不同的方式處理 Audio,比方說,如果你寫的是一款遊戲,那麼,當遊戲退到背景的時候,由於在背景中也沒辦法玩這款遊戲,那遊戲的背景音效也該同時停止播放,當靜音按鈕上鎖的時候,遊戲也不該發出音效。這種類型的音效,蘋果定義成 AVAudioSessionCategoryAmbient

而如果你寫的是媒體播放軟體,像是播放音樂或影片,就該把 Audio Session 的種類設成 AVAudioSessionCategoryPlayback,這樣才有辦法在 iOS 裝置上背景播放,聲音能不能播出來,也不會受到靜音鎖的影響。當你把 Audio Session 設成 AVAudioSessionCategoryPlayback 之後,記得要把 Audio Session 設成 active,才會生效。

接下來就要處理 Interruption 的問題。

Interruption 就是指我們的 App 被其他系統事件或是其他 App 打斷的狀況。像是,當你在使用某款音樂軟體播放音樂的時候,中途突然有電話打進來,或是當音樂軟體在背景播放的時候,你在前景使用瀏覽器,開啟了某個 Youtube 影片,那麼原本音樂播放軟體就被打斷了。這種狀況下,音樂播放軟體應該要停止播放,並且將 UI 恢復到沒有播放歌曲的狀態。然後我們要處理 Resume,像是電話打完了,音樂就該繼續播下去,但如果是在別的 App 看了一部影片,就不該在影片結束的時候,恢復播放。

發生 Interrupt 的時候,iOS 會發生兩件事情:其一是,iOS 會發送名為 AVAudioSessionInterruptionNotification 的通知,其二是你的 Audio Graph(如果你使用底層的 Core Audio 播放音樂,而不是比較高階的 API),會被強制終止。這兩件事情的順序在 iOS 8 反過來了,之前是先收到 Interrupt 通知,系統才關閉 Audio Graph,但是在 iOS 8 上,卻是先關閉 Audio Graph,才送出通知。

這兩件事情的順序影響了怎樣判斷 Interrupt 之後,接下來要怎麼 resume。原本你在收到 Interrupt 通知時,會先把現在 Audio Graph 是不是正在播放中記錄下來,Interrupt 結束,就可以參考這個記錄下來的 Flag,知道在 Interrupt 之前是不是有在播放,決定是否繼續播放音樂。這樣的程式拿到 iOS 8 上,就會遇到—在收到 Interrupt 通知的時候,Audio Graph 一定已經關閉了,所以在 Interrupt 結束的時候,就一定不會 resume。你必須要用其他的方式,判斷在收到 Interrupt 之前,你的軟體是否在播放歌曲。

另外,我們可以再 Audio Graph 停止播放的時候,綁上一個 callback function,方法是對 Audio Graph 當中的 output unit 呼叫 AudioUnitAddPropertyListener,要求監聽 kAudioOutputUnitProperty_IsRunning 這個 property 的改變。在 iOS 8 之前,我們的 callback function 被呼叫的時候,一定會在 main thread,但 iOS 8 之後,在發生 Interrupt 的時候,這個 function 卻會在 background thread 呼叫。

由於之前一定會在 main thread,我們可能很放心的在這個 callback function 中更新 UI—UI 更新一定得在 main thread 而不能在 background thread 做,當這個 function 不預期地在 background thread 呼叫時,那就出現沒完沒了的 crash 了。

在 iOS 8 上,我們還得要注意將 Audio Session 設成 active 的時機。

如果我們的程式在還沒有將 Audio Session 設成 AVAudioSessionCategoryPlayback 的狀況下,就在背景呼叫 AUGraphInitialize(),這個 function 就會回傳 -50 錯誤,我們也沒辦法繼續使用 Audio Graph 播放。這件事情在 iOS 8 之前不會發生,因為每次 App 啟動的時候,都會在前景,我們一定是在前景播放了歌曲之後,才將軟體退入背景做背景播放;如此一來,我們可以先讓 Audio Graph 開始播放,才設定 Audio Session。

iOS 8 提供了新的 Push Notification 機制,我們可以在 Notification 上加上 Action,而 Action 可以設定成「背景啟動」模式,也就是將 activationMode 設成 UIUserNotificationActivationModeBackground。如果我們送了一個 Push Notification,詢問用戶是否不要中斷目前前景的工作,而是在背景打開音樂播放軟體、播放某首剛上架的新歌,那麼,這個音樂播放軟體就會在背景啟動,也在背景呼叫了 AUGraphInitialize()。

因此,我們不管怎樣,都得在呼叫 AUGraphInitialize() 之前,就要先設好 Audio Session 的 Category 並設成 active。

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.