How to play mp3 audio from URL in ios swift

Use AVPlayer instead of AVAudioPlayer to play remote content. As per documentation AVAudioPlayer needs mp3 file to play Audio. AVAudioPlayer not provide support for streaming.

Try this code , its working fine for me

func play(url:NSURL) {
    print("playing \(url)")

    do {

        let playerItem = AVPlayerItem(URL: url)

        self.player = try AVPlayer(playerItem:playerItem)
        player!.volume = 1.0
        player!.play()
    } catch let error as NSError {
        self.player = nil
        print(error.localizedDescription)
    } catch {
        print("AVAudioPlayer init failed")
    }
}

Please keep in mind to set App Transport Security(ATS) in info.plist file.

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

I tried the following,

let urlstring = "http://radio.spainmedia.es/wp-content/uploads/2015/12/tailtoddle_lo4.mp3"
let url = NSURL(string: urlstring)
print("the url = \(url!)")
downloadFileFromURL(url!)

Add the below methods,

func downloadFileFromURL(url:NSURL){

    var downloadTask:NSURLSessionDownloadTask
    downloadTask = NSURLSession.sharedSession().downloadTaskWithURL(url, completionHandler: { [weak self](URL, response, error) -> Void in
        self?.play(URL)
    })
        
    downloadTask.resume()
    
}

And your play method as it is,

func play(url:NSURL) {
    print("playing \(url)")
    
    do {
        self.player = try AVAudioPlayer(contentsOfURL: url)
        player.prepareToPlay()
        player.volume = 1.0
        player.play()
    } catch let error as NSError {
        //self.player = nil
        print(error.localizedDescription)
    } catch {
        print("AVAudioPlayer init failed")
    }
    
}

Download the mp3 file and then try to play it, somehow AVAudioPlayer does not download your mp3 file for you. I am able to download the audio file and player plays it.

Remember to add this in your info.plist since you are loading from a http source and you need the below to be set for iOS 9+

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
</plist>

Since AVPlayer is very limited (for example you cannot change url there without regenerate whole AVPlayer I think you should use AVQueuePlayer:

From the docs:

AVQueuePlayer is a subclass of AVPlayer used to play a number of items in sequence. Using this class you can create and manage a queue of player items comprised of local or progressively downloaded file-based media, such as QuickTime movies or MP3 audio files, as well as media served using HTTP Live Streaming.

So in Swift 3 it will work like this:

lazy var playerQueue : AVQueuePlayer = {
    return AVQueuePlayer()
}()

...

func playTrackOrPlaylist{
    if let url = track.streamURL() { //this is an URL fetch from somewhere. In this if you make sure that URL is valid
        let playerItem = AVPlayerItem.init(url: url)
        self.playerQueue.insert(playerItem, after: nil)
        self.playerQueue.play()
    }
}

Create a complete Audio player with a progress bar and other stuff ... swift 5 and Xcode 12.1

var player: AVPlayer?
var playerItem:AVPlayerItem?
fileprivate let seekDuration: Float64 = 10


@IBOutlet weak var labelOverallDuration: UILabel!
@IBOutlet weak var labelCurrentTime: UILabel!
@IBOutlet weak var playbackSlider: UISlider!
@IBOutlet weak var ButtonPlay: UIButton!

//call this mehtod to init audio player 
func initAudioPlayer{
    let url = URL(string: "https://argaamplus.s3.amazonaws.com/eb2fa654-bcf9-41de-829c-4d47c5648352.mp3")
    let playerItem:AVPlayerItem = AVPlayerItem(url: url!)
    player = AVPlayer(playerItem: playerItem)
    
    playbackSlider.minimumValue = 0
    
    //To get overAll duration of the audio
    let duration : CMTime = playerItem.asset.duration
    let seconds : Float64 = CMTimeGetSeconds(duration)
    labelOverallDuration.text = self.stringFromTimeInterval(interval: seconds)
    
    //To get the current duration of the audio
    let currentDuration : CMTime = playerItem.currentTime()
    let currentSeconds : Float64 = CMTimeGetSeconds(currentDuration)
    labelCurrentTime.text = self.stringFromTimeInterval(interval: currentSeconds)
    
    playbackSlider.maximumValue = Float(seconds)
    playbackSlider.isContinuous = true
    
    
    
    player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
        if self.player!.currentItem?.status == .readyToPlay {
            let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
            self.playbackSlider.value = Float ( time );
            self.labelCurrentTime.text = self.stringFromTimeInterval(interval: time)
        }
        let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
        if playbackLikelyToKeepUp == false{
            print("IsBuffering")
            self.ButtonPlay.isHidden = true
            //        self.loadingView.isHidden = false
        } else {
            //stop the activity indicator
            print("Buffering completed")
            self.ButtonPlay.isHidden = false
            //        self.loadingView.isHidden = true
        }
    }
   
   //change the progress value
    playbackSlider.addTarget(self, action: #selector(playbackSliderValueChanged(_:)), for: .valueChanged)
    
    //check player has completed playing audio
    NotificationCenter.default.addObserver(self, selector: #selector(self.finishedPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)}


@objc func playbackSliderValueChanged(_ playbackSlider:UISlider) {
    let seconds : Int64 = Int64(playbackSlider.value)
    let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1)
    player!.seek(to: targetTime)
    if player!.rate == 0 {
        player?.play()
    }
}

@objc func finishedPlaying( _ myNotification:NSNotification) {
    ButtonPlay.setImage(UIImage(named: "play"), for: UIControl.State.normal)
    //reset player when finish   
    playbackSlider.value = 0
    let targetTime:CMTime = CMTimeMake(value: 0, timescale: 1)
    player!.seek(to: targetTime)
}

@IBAction func playButton(_ sender: Any) {
    print("play Button")
    if player?.rate == 0
    {
        player!.play()
        self.ButtonPlay.isHidden = true
        //        self.loadingView.isHidden = false
        ButtonPlay.setImage(UIImage(systemName: "pause"), for: UIControl.State.normal)
    } else {
        player!.pause()
        ButtonPlay.setImage(UIImage(systemName: "play"), for: UIControl.State.normal)
    }
    
}


func stringFromTimeInterval(interval: TimeInterval) -> String {
    let interval = Int(interval)
    let seconds = interval % 60
    let minutes = (interval / 60) % 60
    let hours = (interval / 3600)
    return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}



@IBAction func seekBackWards(_ sender: Any) {
    if player == nil { return }
    let playerCurrenTime = CMTimeGetSeconds(player!.currentTime())
    var newTime = playerCurrenTime - seekDuration
    if newTime < 0 { newTime = 0 }
    player?.pause()
    let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
    player?.seek(to: selectedTime)
    player?.play()

}


@IBAction func seekForward(_ sender: Any) {
    if player == nil { return }
    if let duration = player!.currentItem?.duration {
       let playerCurrentTime = CMTimeGetSeconds(player!.currentTime())
       let newTime = playerCurrentTime + seekDuration
       if newTime < CMTimeGetSeconds(duration)
       {
          let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as 
       Float64), timescale: 1000)
          player!.seek(to: selectedTime)
       }
       player?.pause()
       player?.play()
      }
}

The end result looks like this ...

enter image description here


SWIFT 4

1 - import AVFoundation

2 - declare player

var player : AVPlayer?

3 - in ViewDidLoad call function and pass the streaming url like String

loadRadio(radioURL: (radioStation?.streamURL)!)

4 - function to play

func loadRadio(radioURL: String) {

        guard let url = URL.init(string: radioURL) else { return }
        let playerItem = AVPlayerItem.init(url: url)
        player = AVPlayer.init(playerItem: playerItem)
        player?.play()
        startNowPlayingAnimation(true)
        played = true
    }

For me is the best and simply way to streaming audio in swift.