Use AVAudioPlayer in OperationQueue

業(yè)務(wù)需求要提供一些ringtone供用戶選擇并且設(shè)置為來電鈴聲.這樣就會涉及到預(yù)覽ringtone.這邊預(yù)覽ringtone選擇用AVAudioPlayer去播放.選擇AVAudioPlayer的原因是AVAudioPlayer可控性比較大,可以播放,暫停,恢復(fù)播放等.使用AudioToolbox提供的api也能播放ringtone當(dāng)是不能滿足操作需求,暫停,恢復(fù),故排除AudioToolbox.

要使用AVAudioPlayer播放ringtone必然會涉及到AVAudioSession.AVAudioSession簡而言之: 音頻會話,主要用來管理音頻設(shè)置與硬件交互.關(guān)于AVAudioSession`詳細(xì)的信息讀者可自行去查.播放一個ringtone的簡化流程代碼大概如下:

try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord)

try? AVAudioSession.sharedInstance().setActive(true, options: [])

let audioPlayer = try? AVAudioPlayer(contentsOf: ringtoneUrl)

audioPlayer?.play()
為了不卡UI一般會放到子線程中去播放:

DispatchQueue.global().async {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord)
            
try? AVAudioSession.sharedInstance().setActive(true, options: [])
            
let audioPlayer = try? AVAudioPlayer(contentsOf: ringtoneUrl)
            
audioPlayer?.play()
}

看似一切正常,但是當(dāng)你頻繁的點擊切換ringtone,變成開始,結(jié)束,暫停。這樣會導(dǎo)致播放失敗又有多線程競爭音頻資源的問題,并且在iOS10.x上會crash,因為音頻資源不可能切換太快.并且快速切換也會開啟多個子線程,這是沒必要的.一般來說一個設(shè)計合理的app同一時刻只會有一個音頻在播放.所以我們只需要一個線程專門控制音頻播放即可,這里推薦Queue來控制,因為Queue api比較友好,并且比較可控.
//作為一個對象的變量比較好,因為會多次點擊播放,這樣用同一個queue管理.
lazy var ringToneQueue: OperationQueue = {
    let ringToneQueue = OperationQueue()
    ringToneQueue.name = "rc.ringtone.queue"
    ringToneQueue.maxConcurrentOperationCount = 1
    return ringToneQueue
}()


//回收資源
if self.ringToneQueue.operationCount > 0 {
   self.ringToneQueue.cancelAllOperations()
}

self.ringToneQueue.addOperation({
    //播放ringtone
})


根據(jù)業(yè)務(wù)需求我這邊是把包裝AVAudioPlayer成一個單例,不會多次創(chuàng)建AVAudioPlayer。

internal class AudioPlayerManager : NSObject {

    internal static let shared: AudioPlayerManager

    internal var currentTime: TimeInterval? { get set }

    internal var audioFilePath: String? { get }

    internal var isPlaying: Bool { get }

    internal var delegates: <<error type>>

    override internal init()

    internal func setCategory(category: AVAudioSession.Category, options: AVAudioSession.CategoryOptions, shouldSetPlayBack: Bool = true) throws

    /**
     * Prepare some configuration for AudioPlayer.
     * Call this method to configurate AudioPlayer and than call `play(atTime time: TimeInterval? = nil)` to play audio.
     *
     */
    internal func configAudioPlayer(with audioFilePath: String, configAudioPlayerClosure: ((Bool, Error?) -> Void)?)

    /**
     *
     * Call this method after call `configAudioPlayer(with audioFilePath: String, configAudioPlayerClosure: ((AVAudioPlayer?) -> Void)?)`.
     *
     */
    internal func play(category: AVAudioSession.Category = .playAndRecord, options: AVAudioSession.CategoryOptions = [.defaultToSpeaker], portOverride: AVAudioSession.PortOverride = .none) throws

    internal func play(audioFilePath: String, category: AVAudioSession.Category = .playAndRecord, options: AVAudioSession.CategoryOptions = [.defaultToSpeaker], atTime time: TimeInterval? = nil, portOverride: AVAudioSession.PortOverride = .none) throws

    internal func stop(options: AVAudioSession.SetActiveOptions = [.notifyOthersOnDeactivation])

    internal func pause(options: AVAudioSession.SetActiveOptions = [.notifyOthersOnDeactivation])

    internal func resume()

    internal func setAudioSessionActive(isActive: Bool, options: AVAudioSession.SetActiveOptions = [])
}

這樣播放的代碼大概是這樣:

//回收資源
if self.ringToneQueue.operationCount > 0 {
   self.ringToneQueue.cancelAllOperations()
}

self.ringToneQueue.addOperation({
    //播放ringtone
    if !AudioPlayerManager.shared.isPlaying {
       AudioPlayerManager.play(audioFilePath: selectedTonePath)
    } else {
       if let path = AudioPlayerManager.audioFilePath, path == selectedTonePath {
          AudioPlayerManager.stop()
        } else {
          AudioPlayerManager.play(audioFilePath: selectedTonePath)
        }
    }
})

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容