沒什麼,就是最近摸AVFoundation的紀錄
寫了那麼久的APP最近才有機會接觸影音播放,但又是一陣喜怒哀樂,所以寫成經驗談跟大家分享。
AVPlayer
play、pause、rate 這幾個屬性方法相對好懂,就不介紹了,底部有很多大大的教學文件參考連結,多閱讀有益身心健康。
a.跳轉播放進度
avPlayer.seek(to: time) { (_) in}
b.監聽當前播放進度:
playTimeObserver = avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 2), queue: timeQueue, using: { (_) in //avPlayer.currentTime().seconds})
監聽完要記得釋放掉。
deinit { avPlayer.removeTimeObserver(playTimeObserver as Any)}
c.播放狀態
TimeControlStatus屬性僅適用iOS 10以上且沒有停止的狀態,我為了向下相容所以就自訂播放狀態。
enum PlayStatus { case Default case Playing case Pause case Stop}
d.開啟背景播放權限
let audioSession = AVAudioSession.sharedInstance()do {try audioSession.setActive(true) try audioSession.setCategory(AVAudioSession.Category.playback)} catch { NSLog("Failed to set audio session category.")}
RemoteCommand
遠程控制指的是螢幕鎖屏時顯示的音樂播放器畫面,因為也有很多邏輯所以我就另外封包。
// 顯示音樂詳細資料
private let infoCenter = MPNowPlayingInfoCenter.default()// 播放事件遠程控制
private let commandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared()
a.鎖屏顯示當前播放訊息
var nowPlayingInfo = [String: Any]()nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = albumNamenowPlayingInfo[MPMediaItemPropertyTitle] = mediaNamenowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.audio.rawValuenowPlayingInfo[MPNowPlayingInfoCollectionIdentifier] = mediaIdnowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = mediaDurationvar albumImage = UIImage(named: "none") ?? UIImage()// 顯示網路圖片
if let url = URL(string: albumImagePath), let data = try? Data(contentsOf: url), let image = UIImage(data: data) { albumImage = image}nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumImage.size) { _ in return albumImage }MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
b.開啟遠程控制事件
// 開啟遠程控制事件
UIApplication.shared.beginReceivingRemoteControlEvents()
c.更新狀態
// 更新鎖屏播放進度
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = time// 更新播放速度
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = rate
d.鎖屏遠程控制事件
為了不要所有監聽和事件都混在一起,所以我定義了一個Rx的監聽物件:commandSubject來處理每個遠程控制指令,然後由監聽的對象執行播放。
commandCenter.playCommand.isEnabled = truecommandCenter.playCommand.addTarget { [weak self] _ in self?.commandSubject.onNext(.Play) return .success}
還有其他指令:
commandCenter.pauseCommand.isEnabled = truecommandCenter.nextTrackCommand.isEnabled = truecommandCenter.previousTrackCommand.isEnabled = true
iOS 9.1 以上才有時間選擇滑動條。
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in let seconds = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime ?? 0 let time = CMTime(seconds: seconds, preferredTimescale: 1) self?.commandSubject.onNext(.Seek(time))return .success}
AVPlayerObserver
實在太多事件要監聽了,我也單獨封包成專門監聽的物件,一樣全部都要在不監聽的時候要釋放掉。
a.監聽播放完成
finishNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: observerQueue) { [weak self] (_) in}
b. 監聽播放失敗:NSNotification.Name.AVPlayerItemFailedToPlayToEndTime
c.監聽異常中斷:
NSNotification.Name.AVPlayerItemPlaybackStalled
d.監聽其他APP播放音樂造成中斷
AVAudioSession.interruptionNotification
e.監聽電話造成的中斷和中斷原因結束
NotificationCenter.default.addObserver(self, selector: #selector(handleCaptureSessionInterrupted), name: NSNotification.Name.AVCaptureSessionWasInterrupted, object: nil)NotificationCenter.default.addObserver(self, selector: #selector(handleCaptureSessionInterrupted), name: NSNotification.Name.AVCaptureSessionInterruptionEnded, object: nil)
handleCaptureSessionInterrupted 針對不同事件做不同處理。
guard let userInfo = notification.userInfo else { return }guard let interruptionType = userInfo[AVAudioSessionInterruptionTypeKey] as? AVAudioSession.InterruptionType else { return }switch interruptionType {case .began: NSLog("音頻被中斷")case .ended: NSLog("中斷原因結束") guard let options = userInfo[AVAudioSessionInterruptionOptionKey] as? AVAudioSession.InterruptionOptions else { return } guard options == .shouldResume else { return } // continue playdefault: NSLog("interrupted \(interruptionType)")}
f.設備連線、斷線通知
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: nil)
handleRouteChange判斷連線/斷線。
guard let userInfo = notification.userInfo else { return }guard let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt else { return }switch AVAudioSession.RouteChangeReason(rawValue: reason) {case .newDeviceAvailable: NSLog("設備連線")case .oldDeviceUnavailable: NSLog("設備斷線")case .categoryChange: NSLog("categoryChange")default: NSLog("routeChang \(reason)")}
以下是踩坑紀錄,坑坑洞洞請酌量閱讀。
第一個坑:模擬器無法顯示鎖屏控制畫面
沒錯,為了這件事花了一整天,我真蠢。果然模擬器真的只是模擬……
第二個坑:使用Play開始播放會變回速度1.0
How to change the play speed rate on AVPlayer (swift)
由於還得摸IAP和Apple sign,所以我就土法煉鋼在每次播放都重新設定速度,目前還沒想到更好的方式。
參考
AVAudioPlayer、AVPlayer和AVQueuePlayer的使用
AVPlayerItemDidPlayToEndTimeNotification仅在强制时有效
【iOS】音頻播放之AVAudioPlayer,AVPlayer,AVQueuePlayer
swift — 创建观察者以检查MediaPlayer播放状态是否已暂停
NotificationCenter
Swift — 透過 NotificationCenter 監聽特定的事件同時傳值
iOS AVAudioSessionInterruptionNotification通知接收混乱
MPNowPlayingInfoCenter
ios — 使用MPMusicPlayerApplicationController时如何更新MPNowPlayingInfoCenter
How to properly set up the MPNowPlayingInfoCenter in Swift 3
iOS音频播放 (八):NowPlayingCenter和RemoteControl
ios — 分配给nowPlayingInfo后,MPNowPlayingInfoCenter不更新任何信息
MPNowPlayingInfoCenter nowPlayingInfo通過AirPlay忽略AVPlayer音頻