Object-C & Swift 自定義音樂(lè)播放器詳解(基于 Avplayer)

記得剛寫(xiě)音樂(lè)播放器那會(huì)兒,和大多數(shù)人一樣。都會(huì)想自己寫(xiě)的也許不夠完善,還可能會(huì)出各種問(wèn)題,而且現(xiàn)在有很多開(kāi)源的比較完善的播放器,找個(gè)好用的就好了呀,之前也確實(shí)是這樣做的。但是隨著自己的App 在音頻播放上的業(yè)務(wù)以及要求變更,你會(huì)發(fā)現(xiàn)第三方的完全不夠用,那么就會(huì)想著去修改第三方的東西,這時(shí)候你會(huì)發(fā)現(xiàn),第三方的東西雖好是很多大牛寫(xiě)的(基于底層)比較好用,但改起來(lái)卻苦不堪言。所以最終還是回歸本源,自己定義音頻播放。最近在整理以前的東西,順便在此分享一下,希望可以給剛寫(xiě)播放器的兄弟一些幫助.

自定義播放用選擇的是ios 新的播放Api AVPlayer

優(yōu)點(diǎn):AVPlayer屬于AVFoundation框架既可以播放本地音頻也可以網(wǎng)絡(luò)音頻,更接近底層也會(huì)更加靈活,定制性比較高

用AVPlayer 播放音視頻你會(huì)發(fā)現(xiàn),它在設(shè)計(jì)上各個(gè)部分相對(duì)獨(dú)立,這樣更有利拆分使用,更加靈活。(比如 用AVPlayer 播放視頻你會(huì)發(fā)現(xiàn) 和生活中看視頻套路差不多 它也需要 播放器 顯示屏 磁盤(pán))

1 AVPlayer:播放器
2 AVPlayerLayer: 顯示屏(如果要播放視頻則要加上畫(huà)布,音頻則不用)
3 AVPlayerItem:一個(gè)媒體資源管理對(duì)象,管理者視頻的一些基本信息和狀態(tài),一個(gè)AVPlayerItem對(duì)應(yīng)著一個(gè)視頻資源**
相關(guān)對(duì)象的意義:

AVAsset:AVAsset類(lèi)專(zhuān)門(mén)用于獲取多媒體的相關(guān)信息,包括獲取多媒體的畫(huà)面、聲音等信息,屬于一個(gè)抽象類(lèi),不能直接使用。
AVURLAsset:AVAsset的子類(lèi),可以根據(jù)一個(gè)URL路徑創(chuàng)建一個(gè)包含媒體信息的AVURLAsset對(duì)象
CMTime:是一個(gè)結(jié)構(gòu)體,里面存儲(chǔ)著當(dāng)前的播放進(jìn)度,總的播放時(shí)長(zhǎng)

你會(huì)發(fā)現(xiàn) AVPlayer 雖然是一個(gè)整體的音頻播放器但是,它內(nèi)部把各個(gè)功能分成了單獨(dú)的對(duì)象,定義時(shí)就相對(duì)獨(dú)立,又可以組合完成功能。這樣做耦合性會(huì)降低,這也能給我們啟發(fā),我們?cè)趯?xiě)一些比較大的功能的時(shí)候,要把它們細(xì)化成小的功能(查錯(cuò)方便,其他地方也可以用)。接下來(lái)的自定義播放器也會(huì)用到這種思想。

接下來(lái),就詳細(xì)梳理一下自定義的AVPlayer 以swift 代碼為例(為了緊跟時(shí)代步伐 - _ -)

oc 和 swift 版本
帶緩沖進(jìn)度的自定義進(jìn)度條
友好的圖片高斯模糊處理
全部在這里了。
WPYPlayer

不知道為什么圖片模糊處理后錄出來(lái)成這樣了,項(xiàng)目比這個(gè)好看多了。


Untitled2.gif

Swift 自定義音樂(lè)播放器 主要分為

1 自定義Avplayer 的基本內(nèi)容
2 一些附加功能
3 需要注意的問(wèn)題

一 自定義Avplayer 的基本內(nèi)容

1 單例初始化

    static let playManager = WPY_AVPlayer()
    var player : AVPlayer = {
        let _player = AVPlayer()
        _player.volume = 2.0 //默認(rèn)最大音量
        
        return _player
    }()

播放器初始化

func  initPlayer() {  
        //APP進(jìn)入后臺(tái)通知
        NotificationCenter.default.addObserver(self, selector: #selector(configLockScreenPlay) , name:UIApplication.didEnterBackgroundNotification, object: nil)
        
        let session = AVAudioSession.sharedInstance()
        try? session.setActive(true)
        //后臺(tái)播放
        Util_OC.setAVAudioSessionCategory(.playback)
    }

播放前需要配置一些監(jiān)聽(tīng)事件
例如:
1 監(jiān)聽(tīng)播放狀態(tài) (對(duì)于音頻的不同狀態(tài),給與不懂操作)
2 緩沖加載情況(便于有加載播放進(jìn)度條需求)
3 播放進(jìn)度(就不用自己用定時(shí)器來(lái)表示播放時(shí)間,那樣也不準(zhǔn)確,直接用系統(tǒng)的就好)
4 播放結(jié)束通知(便于音頻播放結(jié)束做相關(guān)操作)
5 監(jiān)聽(tīng)打斷處理(播放期間被 電話 短信 微信 等打斷后的處理)

// 播放前增加配置 監(jiān)測(cè)
    func currentItemAddObserver(){
        
        //監(jiān)聽(tīng)是否靠近耳朵
        NotificationCenter.default.addObserver(self, selector: #selector(sensorStateChange), name:UIDevice.proximityStateDidChangeNotification, object: nil)
        
        //播放期間被 電話 短信 微信 等打斷后的處理
        NotificationCenter.default.addObserver(self, selector: #selector(handleInterreption(sender:)), name:AVAudioSession.interruptionNotification, object:AVAudioSession.sharedInstance())
        
        // 監(jiān)控播放結(jié)束通知
        NotificationCenter.default.addObserver(self, selector: #selector(playMusicFinished), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player.currentItem)
        //監(jiān)聽(tīng)狀態(tài)屬性 ,注意AVPlayer也有一個(gè)status屬性 通過(guò)監(jiān)控它的status也可以獲得播放狀態(tài)
        
        self.player.currentItem?.addObserver(self, forKeyPath: "status", options:[.new,.old], context: nil)
        
        //監(jiān)控緩沖加載情況屬性
        self.player.currentItem?.addObserver(self, forKeyPath:"loadedTimeRanges", options: [.new,.old], context: nil)
        
        self.timeObserVer = self.player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { [weak self] (time) in
            
            guard let `self` = self else { return }
            
            let currentTime = CMTimeGetSeconds(time)
            self.progress = Float(currentTime)
            if self.isSeekingToTime {
                return
            }
        
            let total = self.durantion
            if total > 0 {
                self.delegate?.updateProgressWith(progress:Float(currentTime)  / Float(total))
            }
            
            
        }
    }

相應(yīng)的當(dāng)該 playItem 播放結(jié)束時(shí) 移除相關(guān)監(jiān)測(cè),觀察

 // 播放后   刪除配置 監(jiān)測(cè)
    
    func currentItemRemoveObserver(){
        self.player.currentItem?.removeObserver(self, forKeyPath:"status")
        self.player.currentItem?.removeObserver(self, forKeyPath:"loadedTimeRanges")
        
        NotificationCenter.default.removeObserver(self, name:UIDevice.proximityStateDidChangeNotification, object: nil)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
        NotificationCenter.default.removeObserver(self, name:AVAudioSession.interruptionNotification, object: nil)
        
        if(self.timeObserVer != nil){
            self.player.removeTimeObserver(self.timeObserVer!)
        }
        
    }

一些監(jiān)測(cè)的相關(guān)處理

1 app進(jìn)入后臺(tái)的 進(jìn)行后臺(tái)播放
注意:記得在工程中打開(kāi)發(fā)后臺(tái)播放功能 否則不會(huì)后臺(tái)播放

1545300365959.jpg

//鎖屏 或 退入后臺(tái) 保持音頻繼續(xù)播放
    
    @objc func configLockScreenPlay() {
        //設(shè)置并激活音頻會(huì)話類(lèi)別
        let session = AVAudioSession.sharedInstance()
        
        Util_OC.setAVAudioSessionCategory(.playback)
        try? session.setActive(true)
        //允許應(yīng)用接收遠(yuǎn)程控制
        
        //設(shè)置后臺(tái)任務(wù)ID
        var  newTaskID = UIBackgroundTaskIdentifier.invalid
        newTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
        if (newTaskID != UIBackgroundTaskIdentifier.invalid) && (self.bgTaskId != UIBackgroundTaskIdentifier.invalid)  {
            UIApplication.shared.endBackgroundTask(self.bgTaskId)
        }
        
        self.bgTaskId = newTaskID
        
    }

監(jiān)測(cè)和耳朵的距離 來(lái)判斷是聽(tīng)筒 還是 外音 播放

 @objc func sensorStateChange() {
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
            
            if UIDevice.current.proximityState == true {
                
                //靠近耳朵
             /*  AVAudioSession *session = [AVAudioSession sharedInstance];
    
    [session setCategory:category withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil]; */
    
    //swift 4.2 后ios 10 以下不兼容 所以用了oc 的方式寫(xiě)的**
    
    Util_OC.setAVAudioSessionCategory(.playAndRecord)
            }else {
                //遠(yuǎn)離耳朵
                Util_OC.setAVAudioSessionCategory(.playback)
            }
        }
    }

處理播放音頻是被來(lái)電 或者 其他 打斷音頻的處理

@objc func handleInterreption(sender:NSNotification) {
        
        let info = sender.userInfo
        guard let type : AVAudioSession.InterruptionType =  info?[AVAudioSessionInterruptionTypeKey] as? AVAudioSession.InterruptionType else { return }
        
        if type == AVAudioSession.InterruptionType.began {
            
            self.pause()
        }else {
            guard  let options = info![AVAudioSessionInterruptionOptionKey] as? AVAudioSession.InterruptionOptions else {return}
            
            if(options == AVAudioSession.InterruptionOptions.shouldResume){
                self.pause()
            }
        }
    }

單個(gè)音頻播放結(jié)束后的邏輯處理

@objc func playMusicFinished(){
        
        UIDevice.current.isProximityMonitoringEnabled = true
        self.seekToZeroBeforePlay = true
        self.isPlay = false
        self.updateCurrentPlayState(state: AVPlayerPlayState.AVPlayerPlayStateEnd)
        
        //在這里進(jìn)邏輯處理
        
        if (self.playType == WPY_AVPlayerType.PlayTypeSpecial) {
            
            self.next()
        }
    }

播放單個(gè)音頻的方法 如播放 音效, 試聽(tīng), 問(wèn)題回答, 即無(wú)關(guān)聯(lián)性只有url的

func playMusic(url : String,type:WPY_AVPlayerType){
        
        self.playType = type // 記錄播放類(lèi)型 以便做出不同處理
        self.setPlaySpeed(playSpeed: 1.0) //播放前初始化倍速 1.0
        
        self.currentItemRemoveObserver() //移除上一首的通知 觀察
        
        let playUrl = self.loadAudioWithPlaypath(playpath: url)
        let playerItem = AVPlayerItem(url: playUrl)
        
        self.playerItem = playerItem
        self.currentUrl = url
        self.isImmediately = true
        
        self.player.replaceCurrentItem(with: playerItem)
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
        self .currentItemAddObserver()
    }

播放多個(gè)連續(xù)音頻的方法 例如 音樂(lè)播放器,或者多個(gè)連續(xù)的音頻

/// 用于播放多個(gè)音頻的列表  播放方法
    ///
    /// - Parameters:
    ///   - index: 播放列表中的第幾個(gè)音頻
    ///   - isImmediately: 是否立即播放
    
    func playTheLine(index :Int,isImmediately :Bool){
        
        self.currentItemRemoveObserver()
        self.playType = .PlayTypeLine // 記錄播放類(lèi)型 以便做出不同處理
        
        let record = self.musicArray[index]
        
        guard let url = record.playpath else { return }
        let playUrl = self.loadAudioWithPlaypath(playpath:url )
        
        let playerItem = AVPlayerItem(url: playUrl)
        
        self.playerItem = playerItem
        self.currentUrl = url
        self.isImmediately = isImmediately
        self.currentScenicPoint = record
        self.currentIndex = index
        if !isImmediately {
            self.pause()
        }
        self.player.replaceCurrentItem(with: playerItem)
        
        self.currentItemAddObserver()
    }

實(shí)現(xiàn)觀察者方法 根據(jù) playitem 的播放狀態(tài)做相應(yīng)操作 以及 及時(shí)更新緩沖進(jìn)度

/// 觀察者   播放狀態(tài)  和  緩沖進(jìn)度
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        let item = object as! AVPlayerItem
        if keyPath == "status" {
            switch item.status {
            case AVPlayerItem.Status.readyToPlay:
                
                if isImmediately {
                    self.play()
                }else{
                    self.setNowPlayingInfo()
                }
            case AVPlayerItem.Status.failed,AVPlayerItem.Status.unknown:
                self.updateCurrentPlayState(state: AVPlayerPlayState.AVPlayerPlayStateNotPlay)
            }
        }else if keyPath == "loadedTimeRanges" {
            
            let array = item.loadedTimeRanges
            
            let timeRange = array.first?.timeRangeValue
            
            guard let start = timeRange?.start , let end = timeRange?.end else {
                return
            }
            
            let startSeconds = CMTimeGetSeconds(start)
            let durationSeconds = CMTimeGetSeconds(end)
            
            let totalBuffer = startSeconds + durationSeconds
            
            let total = self.durantion
            if totalBuffer != 0  && total != 0{
                
                delegate?.updateBufferProgress(progress: Float(totalBuffer) / Float(total))
                print("\(Float(totalBuffer) / Float(total))")
            }
        }
    }
}

這樣 一個(gè)url 或者 數(shù)組 + 播放序列 就可以實(shí)現(xiàn)基本的播放音頻了
接下可以寫(xiě)一下 播放器的四個(gè)基本操作

暫停

func pause(){....... player.pause() ..........}

播放

func play(){ .......   self.player.play() ......}

上一首

 func next(){ ...... changeTheMusicByIndex .......}

下一首

func previous(){ .......... changeTheMusicByIndex .........}

因?yàn)橐话阋纛l的切換會(huì)有很多相應(yīng)的操作需要 比如界面的圖片,文字的替換等等
所以我們統(tǒng)一下載了一個(gè)方法里

 func changeTheMusicByIndex(index : Int){
        self.playTheLine(index: index, isImmediately: true)
        
        delegate?.changeMusicToIndex(index: index)
        //
    }

那么作為一個(gè)成熟的自定義播放器我們應(yīng)該給使用的地方提供哪些回調(diào)操作呢?

1 音頻混緩沖進(jìn)度

2 音頻播放進(jìn)度

3 音頻切換的相應(yīng)操作

這三個(gè)回調(diào)我們采用代理方式 因?yàn)檫@三個(gè)操作一般設(shè)計(jì)到了 播放界面的單獨(dú)操作一般為 一對(duì)一的

protocol WPY_AVPlayerDelegate : class {
    
    func updateProgressWith(progress : Float)
    func changeMusicToIndex(index : Int)
    func updateBufferProgress(progress : Float)
}

4 音頻播放狀態(tài)
相對(duì)于音頻播放狀態(tài)而言,就不一定是一對(duì)一了,
例如: 有可能tableView 上的每個(gè)cell中都有試聽(tīng) 操作

而且個(gè)能有不同類(lèi)型的各種播放形式,然后最基本的播放狀態(tài)都是要的。所以 我們對(duì)播放狀態(tài)的回調(diào)采用全局通知的形式

里面最好帶參數(shù)
1 播放類(lèi)型
2 播放鏈接 這樣可以在一個(gè)界面有多個(gè)播放時(shí)來(lái)準(zhǔn)確改變補(bǔ)個(gè)view 的狀態(tài)
3 播放相應(yīng)的狀態(tài)類(lèi)型 (統(tǒng)一管理播放狀態(tài))

如: 暫停, 播放, 結(jié)束, 緩沖準(zhǔn)備, 播放出錯(cuò)
case AVPlayerPlayStatePreparing // 準(zhǔn)備播放
case AVPlayerPlayStateBeigin // 開(kāi)始播放
case AVPlayerPlayStatePlaying // 正在播放
case AVPlayerPlayStatePause // 播放暫停
case AVPlayerPlayStateEnd // 播放結(jié)束
case AVPlayerPlayStateBufferEmpty // 沒(méi)有緩存的數(shù)據(jù)供播放了
case AVPlayerPlayStateBufferToKeepUp //有緩存的數(shù)據(jù)可以供播放
case AVPlayerPlayStateseekToZeroBeforePlay
case AVPlayerPlayStateNotPlay // 不能播放
case AVPlayerPlayStateNotKnow // 未知情況

/// 實(shí)時(shí)更新播放狀態(tài)  全局通知(便于多個(gè)地方都用到音頻播放,改變播放狀態(tài))
    ///
    /// - Parameter state: 播放狀態(tài)
    
    func updateCurrentPlayState(state : AVPlayerPlayState){
        
        if self.currentUrl != nil {
            
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: WPY_PlayerState), object: nil, userInfo: [WPY_PlayerState : state,CurrentPlayUrl : self.currentUrl!,PlayType : self.playType])
            
        }else {
            
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: WPY_PlayerState), object: nil, userInfo: [WPY_PlayerState : state,CurrentPlayUrl : "",PlayType : self.playType])
        }
    }

至此,一個(gè)基本的自定義播放器就宣布完成了

二 附加功能

1 根據(jù)靠近耳朵距離 自由切換外音 和 聽(tīng)筒 模式

監(jiān)聽(tīng)

//監(jiān)聽(tīng)是否靠近耳朵
        NotificationCenter.default.addObserver(self, selector: #selector(sensorStateChange), name:UIDevice.proximityStateDidChangeNotification, object: nil)

相應(yīng)操作

 //監(jiān)測(cè)是否靠近耳朵  轉(zhuǎn)換聲音播放模式
    
    @objc func sensorStateChange() {
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
            
            if UIDevice.current.proximityState == true {
                
                //靠近耳朵
                Util_OC.setAVAudioSessionCategory(.playAndRecord)
            }else {
                //遠(yuǎn)離耳朵
                Util_OC.setAVAudioSessionCategory(.playback)
            }
        }
    }

因?yàn)?swift 4.2 對(duì)于ios 10.0 以下 不兼容,所以用了調(diào)oc的方法解決

有更好處理方式的歡迎交流

+ (void)setAVAudioSessionCategory:(AVAudioSessionCategory) category {
    
    AVAudioSession *session = [AVAudioSession sharedInstance];
    
    [session setCategory:category withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
}

注意:其實(shí)在音頻停止播放后 就不應(yīng)該有這種操作 (聽(tīng)筒轉(zhuǎn)換 屏幕息屏)

所以 我們應(yīng)該在 自己的暫停 函數(shù)中關(guān)掉紅外感應(yīng)

UIDevice.current.isProximityMonitoringEnabled = false

在播放函數(shù)中子打開(kāi)

UIDevice.current.isProximityMonitoringEnabled = true

2 鎖屏 顯示播放信息

鎖屏顯示播放信息 包括到了狀態(tài)
所以 我們先進(jìn)行狀態(tài)相關(guān)操作的時(shí)候 ,都應(yīng)該調(diào)用信息設(shè)置操作

如 : 暫停 播放 改變進(jìn)度等

/// 設(shè)置鎖屏?xí)r 播放中心的播放信息、
    
    func setNowPlayingInfo(){
        
        if (self.playType == .PlayTypeLine || self.playType == .PlayTypeSpecial) && self.currentScenicPoint != nil {
            
           // 1  名字
            var info = Dictionary<String,Any>()
            info[MPMediaItemPropertyTitle] = self.currentScenicPoint?.name ?? ""   
            
            // 2 圖片
            
            if let image = UIImage(named: "AppIcon"){
                info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image:image)//顯示的圖片
            }
            
//            if  let url = self.currentScenicPoint?.pictureArray?.first ,let image = UIImage(named: "AppIcon"){
//                imageView.kf.setImage(with: URL(string:url), placeholder: image, options: nil, progressBlock: nil) { (img, _, _, _) in
//
//                    if
//                    info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image:img)//顯示的圖片
//                }
//            }else{
//
//            }
            
            //3  總時(shí)長(zhǎng)
            info[MPMediaItemPropertyPlaybackDuration] = self.durantion 
            
            if let duration = self.player.currentItem?.currentTime() {
               info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(duration)
            }
            
            //4 播放速率
            info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
            
            //最后 設(shè)置
            MPNowPlayingInfoCenter.default().nowPlayingInfo = info
        }
    }
3 遠(yuǎn)程事件操作

記得進(jìn)入后臺(tái)后開(kāi)啟接收遠(yuǎn)程事件

UIApplication.shared.beginReceivingRemoteControlEvents()

在某些不需要遠(yuǎn)程事件是要關(guān)掉

UIApplication.shared.endReceivingRemoteControlEvents()

 //    //后臺(tái)操作   在delegate 或者 某個(gè)VC 中初始化
    
    //    override func remoteControlReceived(with event: UIEvent?) {
    //        guard let event = event else {
    //            print("no event\n")
    //            return
    //        }
    //
    //        if event.type == UIEventType.remoteControl {
    //            switch event.subtype {
    //            case .remoteControlTogglePlayPause:
    //                print("暫停/播放")
    //
    //            case .remoteControlPreviousTrack:
    //                print("上一首")
    //                self.previous()
    //            case .remoteControlNextTrack:
    //                print("下一首")
    //                self.next()
    //            case .remoteControlPlay:
    //                print("播放")
    //               self.play()
    //            case .remoteControlPause:
    //                print("暫停")
    //                self.pause()
    //            default:
    //                break
    //            }
    //        }
    //    }
    //
4 改變播放速度

設(shè)置 playSpeed 屬性用于記錄 改變的播放速率
因?yàn)橛锌赡苁菚和顟B(tài)下改的播放速率,不能及時(shí)生效。所以要記錄一下

也真因?yàn)槿绱?,所以播放時(shí)要及時(shí)更新下播放速率

self.enableAudioTracks(enable: true, playerItem: self.playerItem!)

注意: 暫停是調(diào)用此方法會(huì)直接播放,所以要放在播放時(shí)再調(diào)用

 //設(shè)置播放速率
    func setPlaySpeed(playSpeed:Float) {
        
        if self.isPlay{
            self.enableAudioTracks(enable: true, playerItem: self.playerItem!)
            self.player.rate = playSpeed;
        }
        self.playSpeed = playSpeed
    }

/// 改變播放速率 必實(shí)現(xiàn)的方法

    ///
    /// - Parameters:
    ///   - enable:
    ///   - playerItem: 當(dāng)前播放
    func enableAudioTracks(enable:Bool,playerItem : AVPlayerItem){
        
        for track : AVPlayerItemTrack in playerItem.tracks {
            
            if track.assetTrack?.mediaType == AVMediaType.audio {
                
                track.isEnabled = enable
            }
        }
    }
5 判斷網(wǎng)絡(luò)狀態(tài) 詢(xún)問(wèn)是否播放

這個(gè)一般的網(wǎng)絡(luò)庫(kù)中都有網(wǎng)絡(luò)狀態(tài)的判斷,那么應(yīng)該在哪里進(jìn)行此操作呢

最合理的地方應(yīng)該是 播放 方法里面。因?yàn)樵诖丝梢宰畲笙薅鹊目刂屏髁浚词共サ揭话霑和?網(wǎng)絡(luò)變化后也可以及時(shí)終止

三 需要注意的問(wèn)題

1 進(jìn)度條問(wèn)題

進(jìn)度條有兩個(gè)改變

1 隨著音頻播放,逐漸改變。
2 手動(dòng)調(diào)整位置,調(diào)整播放進(jìn)度

但是這個(gè)連個(gè)問(wèn)題會(huì)存在交叉問(wèn)題,即在手動(dòng)調(diào)整進(jìn)度是如果音頻播放不停,而且進(jìn)度回調(diào)也一直在走,那么你會(huì)發(fā)現(xiàn)進(jìn)度條在拉的時(shí)候是在跳動(dòng)。

解決方案:
所以 在手動(dòng)拉進(jìn)度時(shí),應(yīng)該停掉音頻播放的進(jìn)度回調(diào),在手動(dòng)進(jìn)度結(jié)束時(shí),根據(jù)進(jìn)度播放器把音頻跳到指定位置播放,同時(shí)恢復(fù)音頻進(jìn)度回調(diào)

2 時(shí)間進(jìn)度問(wèn)題
- (NSString *)timeFormatted:(int)totalSeconds {
    int seconds = totalSeconds % 60;
    int minutes = (totalSeconds / 60);
    return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
}

時(shí)間一般為兩個(gè) 一個(gè)是當(dāng)前時(shí)間 另一個(gè)是剩余時(shí)間或者總時(shí)間

這里就需要將avplayer 的時(shí)間 CTime 轉(zhuǎn)換為字符串

object-C 的相對(duì)來(lái)說(shuō)比較好處理

//視頻的總長(zhǎng)度
NSTimeInterval total = CMTimeGetSeconds(self.player.currentItem.duration);
直接取值轉(zhuǎn)化字符串就好

問(wèn)題是 swift

因?yàn)閟wift對(duì)類(lèi)型要求比較嚴(yán)格,所以要進(jìn)行類(lèi)型轉(zhuǎn)換。這時(shí)候你會(huì)發(fā)現(xiàn)在進(jìn)行時(shí)間賦值是會(huì)崩潰

原因:因?yàn)?swift 是不會(huì)有默認(rèn)值的。有時(shí)音頻數(shù)據(jù)沒(méi)取回,有可能就已經(jīng)有賦值操作。

所以 我們?cè)谶M(jìn)行賦值操作前 要進(jìn)行判斷

 if self.isNaN || self.isInfinite {
            
            return "00:00"
        }
目前想到的差不多就這些,歡迎指正交流。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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