Search code examples
swiftios8xcode6swift2ios9

AVPlayer Audio Buffering from Live Stream


I am making an App which streams the Live Audio using AVPlayer (single Play/pause button). Application is working fine, however since this is the Live Audio, if there is a slight network problem, the audio stops and player does not continue after, even pressing the Play/Pause button.Only way to resume the app is to kill it and restart each time.

Can someone, please, suggest anything or alternatives to AVPlayer or how can I buffer the Audio so if the connection is lost the player would buffer it in the mean time? I am new to the IOS programming. Appreciated


Solution

  • I was having this same issue. The answer was in creating an error delegate that launched a selector every time the player stopped (The error changes when the network connection is interrupted or the stream didn't load properly):

    Here are my delegates, just outside and above my RadioPlayer class:

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

    Now my class:

    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
        }
    
    }
    

    Now, in the 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
            }
        }
    
    }