業務需求要提供一些ringtone供用戶選擇而且設置爲來電鈴聲.這樣就會涉及到預覽ringtone.這邊預覽ringtone選擇用AVAudioPlayer
去播放.選擇AVAudioPlayer
的緣由是AVAudioPlayer
可控性比較大,能夠播放,暫停,恢復播放等.使用AudioToolbox
提供的api也能播放ringtone當是不能知足操做需求,暫停,恢復,故排除AudioToolbox
.
要使用AVAudioPlayer播放ringtone必然會涉及到
AVAudioSession.
AVAudioSession簡而言之: 音頻會話,主要用來管理音頻設置與硬件交互.關於
AVAudioSession`詳細的信息讀者可自行去查.播放一個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()
}
複製代碼
看似一切正常,可是當你頻繁的點擊切換ringtone,變成開始,結束,暫停。這樣會致使播放失敗又有多線程競爭音頻資源的問題,而且在iOS10.x上會crash,由於音頻資源不可能切換太快.而且快速切換也會開啓多個子線程,這是不必的.通常來講一個設計合理的app同一時刻只會有一個音頻在播放.因此咱們只須要一個線程專門控制音頻播放便可,這裏推薦Queue來控制,由於Queue api比較友好,而且比較可控.
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({
})
複製代碼
根據業務需求我這邊是把包裝AVAudioPlayer成一個單例,不會屢次建立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
internal func configAudioPlayer(with audioFilePath: String, configAudioPlayerClosure: ((Bool, Error?) -> 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({
if !AudioPlayerManager.shared.isPlaying {
AudioPlayerManager.play(audioFilePath: selectedTonePath)
} else {
if let path = AudioPlayerManager.audioFilePath, path == selectedTonePath {
AudioPlayerManager.stop()
} else {
AudioPlayerManager.play(audioFilePath: selectedTonePath)
}
}
})
複製代碼