【问题标题】:AVPlayer Audio Buffering from Live Stream来自实时流的 AVPlayer 音频缓冲
【发布时间】:2015-12-13 08:41:54
【问题描述】:

我正在制作一个使用 AVPlayer(单播放/暂停按钮)流式传输实时音频的应用程序。应用程序工作正常,但是由于这是实时音频,如果有轻微的网络问题,音频会停止并且播放器不会继续播放,即使按下播放/暂停按钮也是如此。恢复应用程序的唯一方法是杀死它并每次重启。

请有人建议 AVPlayer 的任何内容或替代方案,或者我如何缓冲音频,以便如果连接丢失,播放器会同时缓冲它?我是IOS编程的新手。赞赏

【问题讨论】:

标签: swift ios8 xcode6 swift2 ios9


【解决方案1】:

为 Swift 5 更新:

import Foundation
import AVFoundation
import UIKit


class RadioViewController: UIViewController, errorMessageDelegate, sharedInstanceDelegate {
    
    // MARK: Properties
    
    var firstErrorSkip = true
    var firstInstanceSkip = true
    
    
    @IBAction func switched(_ sender: UIButton) {
        toggle()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
            print("AVAudioSession Category Playback OK")
            do {
                try AVAudioSession.sharedInstance().setActive(true)
                print("AVAudioSession is Active")
                
            } catch let error as NSError {
                print(error.localizedDescription)
            }
        } catch let error as NSError {
            print(error.localizedDescription)
        }
        
        RadioPlayer.sharedInstance.errorDelegate = self
        RadioPlayer.sharedInstance.instanceDelegate = self
        
        if RadioPlayer.sharedInstance.currentlyPlaying() {
            
        }
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func toggle() {
        if RadioPlayer.sharedInstance.currentlyPlaying() {
            pauseRadio()
        } else {
            playRadio()
        }
    }
    
    func playRadio() {
        firstErrorSkip = false
        firstInstanceSkip = false
        
        if RadioPlayer.sharedInstance.errorMessage != "" || RadioPlayer.sharedInstance.bufferFull() {
            resetStream()
        } else {
            RadioPlayer.sharedInstance.play()
        }
    }
    
    func pauseRadio() {
        RadioPlayer.sharedInstance.pause()
    }
    
    func resetStream() {
        print("Reloading interrupted stream");
        RadioPlayer.sharedInstance.resetPlayer()
        //RadioPlayer.sharedInstance = RadioPlayer();
        RadioPlayer.sharedInstance.errorDelegate = self
        RadioPlayer.sharedInstance.instanceDelegate = self
        if RadioPlayer.sharedInstance.bufferFull() {
            RadioPlayer.sharedInstance.play()
        } else {
            playRadio()
        }
    }
    
    func errorMessageChanged(newVal: String) {
        if !firstErrorSkip {
            print("Error changed to '\(newVal)'")
            if RadioPlayer.sharedInstance.errorMessage != "" {
                print("Showing Error Message")
                let alertController = UIAlertController(title: "Stream Failure", message: RadioPlayer.sharedInstance.errorMessage, preferredStyle: UIAlertController.Style.alert)
                alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default, handler: nil))
                
                self.present(alertController, animated: true, completion: nil)
                
                pauseRadio()
                
            }
        } else {
            print("Skipping first init")
            firstErrorSkip = false
        }
    }
    
    func sharedInstanceChanged(newVal: Bool) {
        if !firstInstanceSkip {
            print("Detected New Instance")
            if newVal {
                RadioPlayer.sharedInstance.play()
            }
        } else {
            firstInstanceSkip = false
        }
    }
    
}

对于 RadioPlayer:

import Foundation
import UIKit
import AVFoundation

protocol errorMessageDelegate {
    func errorMessageChanged(newVal: String)
}

protocol sharedInstanceDelegate {
    func sharedInstanceChanged(newVal: Bool)
}

class  Globals{
    static let radioURL =  "enter url here"
}

class RadioPlayer : NSObject {

    static let sharedInstance = RadioPlayer()
    var instanceDelegate:sharedInstanceDelegate?
    var sharedInstanceBool = false {
        didSet {
            if let delegate = self.instanceDelegate {
                delegate.sharedInstanceChanged(newVal: self.sharedInstanceBool)
            }
        }
    }
    private var player = AVPlayer(url: NSURL(string: Globals.radioURL)! as URL)
    private var playerItem = AVPlayerItem?.self
    private var isPlaying = false

    var errorDelegate:errorMessageDelegate? = nil
    var errorMessage = "" {
        didSet {
            if let delegate = self.errorDelegate {
                delegate.errorMessageChanged(newVal: self.errorMessage)
            }
        }
    }

    override init() {
        super.init()

        errorMessage = ""

        let asset: AVURLAsset = AVURLAsset(url: NSURL(string: Globals.radioURL)! as URL, options: nil)

        let statusKey = "tracks"
        
        asset.loadValuesAsynchronously(forKeys: [statusKey]) {
            
            var error: NSError? = nil
            DispatchQueue.main.async {
                
                let status: AVKeyValueStatus = asset.statusOfValue(forKey: statusKey, error: &error)
                
                if status == AVKeyValueStatus.loaded{
                    
                    let playerItem = AVPlayerItem(asset: asset)
                    
                    self.player = AVPlayer(playerItem: playerItem)
                    self.sharedInstanceBool = true
                    
                } else {
                    self.errorMessage = error!.localizedDescription
                    print(error!)
                }
            }
        }
        
        NotificationCenter.default.addObserver(
            forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
            object: nil,
            queue: nil,
            using: { notification in
                print("Status: Failed to continue")
                self.errorMessage = "Stream was interrupted"
        })

        print("Initializing new player")

    }

    
    
    
    func resetPlayer() {
        errorMessage = ""

        let asset: AVURLAsset = AVURLAsset(url: NSURL(string: Globals.radioURL)! as URL, options: nil)

        let statusKey = "tracks"
        asset.loadValuesAsynchronously(forKeys: [statusKey]) {
            var error: NSError? = nil
            
            DispatchQueue.main.async {
                let status: AVKeyValueStatus = asset.statusOfValue(forKey: statusKey, error: &error)
                
                if status == AVKeyValueStatus.loaded{
                    
                    let playerItem = AVPlayerItem(asset: asset)
                    
                    self.player = AVPlayer(playerItem: playerItem)
                    self.sharedInstanceBool = true
                    
                } else {
                    self.errorMessage = error!.localizedDescription
                    print(error!)
                }
            }
            
        }
    }

    func bufferFull() -> Bool {
        return bufferAvailableSeconds() > 45.0
    }

    func bufferAvailableSeconds() -> TimeInterval {
        // Check if there is a player instance
        if ((player.currentItem) != nil) {

            // Get current AVPlayerItem
            let item: AVPlayerItem = player.currentItem!
            if (item.status == AVPlayerItem.Status.readyToPlay) {

                let timeRangeArray: NSArray = item.loadedTimeRanges as NSArray
                if timeRangeArray.count < 1 { return(CMTimeGetSeconds(CMTime.invalid)) }
                let aTimeRange: CMTimeRange = (timeRangeArray.object(at:0) as AnyObject).timeRangeValue
                //let startTime = CMTimeGetSeconds(aTimeRange.end)
                let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)

                return (TimeInterval)(loadedDuration);
            }
            else {
                return(CMTimeGetSeconds(CMTime.invalid))
            }
        }
        else {
            return(CMTimeGetSeconds(CMTime.invalid))
        }
    }

    func play() {
        player.play()
        isPlaying = true
        print("Radio is \(isPlaying ? "" : "not ")playing")
    }

    func pause() {
        player.pause()
        isPlaying = false
        print("Radio is \(isPlaying ? "" : "not ")playing")
    }

    func currentlyPlaying() -> Bool {
        return isPlaying
    }

}

【讨论】:

    【解决方案2】:

    我遇到了同样的问题。答案是创建一个错误委托,每次播放器停止时都会启动一个选择器(当网络连接中断或流未正确加载时,错误会发生变化):

    这是我的代表,就在我的 RadioPlayer 课程的外面和上方:

    protocol errorMessageDelegate {
        func errorMessageChanged(newVal: String)
    }
    
    protocol sharedInstanceDelegate {
        func sharedInstanceChanged(newVal: Bool)
    }
    

    现在我的课:

    import Foundation
    import AVFoundation
    import UIKit
    
    class RadioPlayer : NSObject {
    
        static let sharedInstance = RadioPlayer()
        var instanceDelegate:sharedInstanceDelegate? = nil
        var sharedInstanceBool = false {
            didSet {
                if let delegate = self.instanceDelegate {
                    delegate.sharedInstanceChanged(self.sharedInstanceBool)
                }
            }
        }
        private var player = AVPlayer(URL: NSURL(string: Globals.radioURL)!)
        private var playerItem = AVPlayerItem?()
        private var isPlaying = false
    
        var errorDelegate:errorMessageDelegate? = nil
        var errorMessage = "" {
            didSet {
                if let delegate = self.errorDelegate {
                    delegate.errorMessageChanged(self.errorMessage)
                }
            }
        }
    
        override init() {
            super.init()
    
            errorMessage = ""
    
            let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
    
            let statusKey = "tracks"
    
            asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler: {
                var error: NSError? = nil
    
                dispatch_async(dispatch_get_main_queue(), {
                    let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
    
                    if status == AVKeyValueStatus.Loaded{
    
                        let playerItem = AVPlayerItem(asset: asset)
    
                        self.player = AVPlayer(playerItem: playerItem)
                        self.sharedInstanceBool = true
    
                    } else {
                        self.errorMessage = error!.localizedDescription
                        print(error!)
                    }
    
                })
    
    
            })
    
            NSNotificationCenter.defaultCenter().addObserverForName(
                AVPlayerItemFailedToPlayToEndTimeNotification,
                object: nil,
                queue: nil,
                usingBlock: { notification in
                    print("Status: Failed to continue")
                    self.errorMessage = "Stream was interrupted"
            })
    
            print("Initializing new player")
    
        }
    
        func resetPlayer() {
            errorMessage = ""
    
            let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
    
            let statusKey = "tracks"
    
            asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler: {
                var error: NSError? = nil
    
                dispatch_async(dispatch_get_main_queue(), {
                    let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
    
                    if status == AVKeyValueStatus.Loaded{
    
                        let playerItem = AVPlayerItem(asset: asset)
                        //playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: &ItemStatusContext)
    
                        self.player = AVPlayer(playerItem: playerItem)
                        self.sharedInstanceBool = true
    
                    } else {
                        self.errorMessage = error!.localizedDescription
                        print(error!)
                    }
    
                })
            })
        }
    
        func bufferFull() -> Bool {
            return bufferAvailableSeconds() > 45.0
        }
    
        func bufferAvailableSeconds() -> NSTimeInterval {
            // Check if there is a player instance
            if ((player.currentItem) != nil) {
    
                // Get current AVPlayerItem
                let item: AVPlayerItem = player.currentItem!
                if (item.status == AVPlayerItemStatus.ReadyToPlay) {
    
                    let timeRangeArray: NSArray = item.loadedTimeRanges
                    if timeRangeArray.count < 1 { return(CMTimeGetSeconds(kCMTimeInvalid)) }
                    let aTimeRange: CMTimeRange = timeRangeArray.objectAtIndex(0).CMTimeRangeValue
                    //let startTime = CMTimeGetSeconds(aTimeRange.end)
                    let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)
    
                    return (NSTimeInterval)(loadedDuration);
                }
                else {
                    return(CMTimeGetSeconds(kCMTimeInvalid))
                }
            } 
            else {
                return(CMTimeGetSeconds(kCMTimeInvalid))
            }
        }
    
        func play() {
            player.play()
            isPlaying = true
            print("Radio is \(isPlaying ? "" : "not ")playing")
        }
    
        func pause() {
            player.pause()
            isPlaying = false
            print("Radio is \(isPlaying ? "" : "not ")playing")
        }
    
        func currentlyPlaying() -> Bool {
            return isPlaying
        }
    
    }
    

    现在,在 RadioViewController 中:

    import UIKit
    import AVFoundation
    
    class RadioViewController: UIViewController, errorMessageDelegate, sharedInstanceDelegate {
    
        // MARK: Properties
    
        var firstErrorSkip = true
        var firstInstanceSkip = true
    
        @IBOutlet weak var listenLabel: UILabel!
        @IBOutlet weak var radioSwitch: UIImageView!
    
        @IBAction func back(sender: AnyObject) {
            print("Dismissing radio view")
            if let navigationController = self.navigationController
            {
                navigationController.popViewControllerAnimated(true)
            }
        }
    
        @IBAction func switched(sender: AnyObject) {
            toggle()
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                print("AVAudioSession Category Playback OK")
                do {
                    try AVAudioSession.sharedInstance().setActive(true)
                    print("AVAudioSession is Active")
    
                } catch let error as NSError {
                    print(error.localizedDescription)
                }
            } catch let error as NSError {
                print(error.localizedDescription)
            }
    
            RadioPlayer.sharedInstance.errorDelegate = self
            RadioPlayer.sharedInstance.instanceDelegate = self
    
            if RadioPlayer.sharedInstance.currentlyPlaying() {
                radioSwitch.image = UIImage(named: "Radio_Switch_Active")
                listenLabel.text = "Click to Pause Radio Stream:"
            }
    
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func toggle() {
            if RadioPlayer.sharedInstance.currentlyPlaying() {
                pauseRadio()
            } else {
                playRadio()
            }
        }
    
        func playRadio() {
            firstErrorSkip = false
            firstInstanceSkip = false
    
            if RadioPlayer.sharedInstance.errorMessage != "" || RadioPlayer.sharedInstance.bufferFull() {
                resetStream()
            } else {
                radioSwitch.image = UIImage(named: "Radio_Switch_Active")
                listenLabel.text = "Click to Pause Radio Stream:"
                RadioPlayer.sharedInstance.play()
            }
        }
    
        func pauseRadio() {
            RadioPlayer.sharedInstance.pause()
            radioSwitch.image = UIImage(named: "Radio_Switch_Inactive")
            listenLabel.text = "Click to Play Radio Stream:"
        }
    
        func resetStream() {
            print("Reloading interrupted stream");
            RadioPlayer.sharedInstance.resetPlayer()
            //RadioPlayer.sharedInstance = RadioPlayer();
            RadioPlayer.sharedInstance.errorDelegate = self
            RadioPlayer.sharedInstance.instanceDelegate = self
            if RadioPlayer.sharedInstance.bufferFull() {
                radioSwitch.image = UIImage(named: "Radio_Switch_Active")
                listenLabel.text = "Click to Pause Radio Stream:"
                RadioPlayer.sharedInstance.play()
            } else {
                playRadio()
            }
        }
    
        func errorMessageChanged(newVal: String) {
            if !firstErrorSkip {
                print("Error changed to '\(newVal)'")
                if RadioPlayer.sharedInstance.errorMessage != "" {
                    print("Showing Error Message")
                    let alertController = UIAlertController(title: "Stream Failure", message: RadioPlayer.sharedInstance.errorMessage, preferredStyle: UIAlertControllerStyle.Alert)
                    alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
    
                    self.presentViewController(alertController, animated: true, completion: nil)
    
                    pauseRadio()
    
                }
            } else {
                print("Skipping first init")
                firstErrorSkip = false
            }
        }
    
        func sharedInstanceChanged(newVal: Bool) {
            if !firstInstanceSkip {
            print("Detected New Instance")
                if newVal {
                    RadioPlayer.sharedInstance.play()
                }
            } else {
                firstInstanceSkip = false
            }
        }
    
    }
    

    【讨论】:

    • 非常感谢!我也试试看。
    猜你喜欢
    • 2011-06-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    • 2016-08-08
    • 1970-01-01
    相关资源
    最近更新 更多