How to play mp3 audio from URL in iOS Swift?
Asked Answered
C

12

45

I am getting mp3 url as a response of an API call.

I want to play that audio, so how can I do that?

here is my response

{
    content = "En este primer programa se tratar\U00e1n asuntos tan importante como este y aquel sin descuidar un poco de todo lo dem\U00e1s";
    file = "http://radio.spainmedia.es/wp-content/uploads/2015/12/ogilvy.mp3";
    image = "http://radio.spainmedia.es/wp-content/uploads/2015/12/tapas.jpg";
    number = 0001;
    subtitle = Titulareando;
    title = "Tapa 1";
}

here is my code:

 @IBAction func btnplayaudio(sender: AnyObject) {
    let urlstring = "http://radio.spainmedia.es/wp-content/uploads/2015/12/tailtoddle_lo4.mp3"
    let url = NSURL(string: urlstring)
    print("the url = \(url!)")
    
    play(url!)

}

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

Where am I going wrong?

Conservation answered 2/1, 2016 at 6:37 Comment(5)
Your code is throwing an exception for me, The operation couldn’t be completed. (OSStatus error 2003334207Katowice
its also throwing exception for me. but how can i avoid this. simply this is my mp3 url radio.spainmedia.es/wp-content/uploads/2015/12/ogilvy.mp3 how can i play it??@satheeshwaranConservation
just a second, looking into it.Katowice
please look at my answer, your audio is playing on my simulator now. Great music.Katowice
its working on simulator but its not working in my iphone device. so is there any solution? its not showing any error. even play function also called but sound is not come on my device @satheeshwaranConservation
K
42

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>
Katowice answered 2/1, 2016 at 7:6 Comment(7)
its working on simulator but its not working on iphone so is there any solution?Conservation
@Conservation Did you try logging the url after downloadTask?? Is it throwing any other exception,Katowice
Watch out the retain cycle on your block! You should use [weak self]Attendance
@satheeshwaran Can you please share right piece of code, where to make correction?Procure
Thanks for the answer, but this means that you have to download the whole file and then play, is it right? could you check my question to understand what am I looking for?Vercelli
@satheeshwaran getting Ambiguous reference to member 'downloadTask(with:completionHandler:)' error in swift 4Sleeve
Use AVPlayer instead of AVAudioPlayer to play remote content.AVAudioPlayer needs mp3 file to play Audio. AVAudioPlayer not provide support for streaming.Grooved
K
47

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>
Kat answered 2/1, 2016 at 7:9 Comment(6)
its working on simulator but its not working in my iphone device. so is there any solution? its not showing any error. even play function also called but sound is not come on my deviceConservation
Playing remote content or local mp3 file ?Kat
Make sure your device in ringing mode rather than silent mode.Kat
its in ringing mode. and main thing is its working with handsfree. but its not working without handsfree.Conservation
I think AVPlayer, first of all, download data content, and then play it. It's not stream. For example, when I am trying to play audio from DropBox using this player, I wait 5-6 seconds before start.... :(Anhedral
Did you try m3u8 file(HTTP Live Streaming) ? It seems not work for m3u8 streaming.Hairdresser
K
42

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>
Katowice answered 2/1, 2016 at 7:6 Comment(7)
its working on simulator but its not working on iphone so is there any solution?Conservation
@Conservation Did you try logging the url after downloadTask?? Is it throwing any other exception,Katowice
Watch out the retain cycle on your block! You should use [weak self]Attendance
@satheeshwaran Can you please share right piece of code, where to make correction?Procure
Thanks for the answer, but this means that you have to download the whole file and then play, is it right? could you check my question to understand what am I looking for?Vercelli
@satheeshwaran getting Ambiguous reference to member 'downloadTask(with:completionHandler:)' error in swift 4Sleeve
Use AVPlayer instead of AVAudioPlayer to play remote content.AVAudioPlayer needs mp3 file to play Audio. AVAudioPlayer not provide support for streaming.Grooved
D
22

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

Dealate answered 22/10, 2020 at 11:46 Comment(2)
Excellent. This works even for macOS (just use AppKit instead of UIKit) Thanks for the example.Cockerham
Just what I was looking for. Thanks :)Trudge
T
16

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()
    }
}
Taber answered 13/3, 2017 at 13:48 Comment(2)
Why you are making AVQueuePlayer instance lazy getter. Isn't it returning each time new queue? What if you want to manage queue of tracks?Phosphorous
It's actually opposite of that. lazy will be created once when you first access it. var playerQueue: AVQueuePlayer { return AVQueuePlayer() } this will create new instance every time. With lazy, there will be only one instance. Further reading: mikebuss.com/2014/06/22/lazy-initialization-swiftTaber
L
13

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.

Laurentian answered 22/6, 2018 at 15:44 Comment(2)
Hey I've implemented the solution and it works fine. But there's a 10s delay after I press the button to play the audio. For testing I've used this audio source: soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3Selfcontrol
Is there a way where the initial delay can be reduced to 2-3s ? ThanksSelfcontrol
S
8
var player : AVPlayer?
func playSound()
    {
      guard  let url = URL(string: "https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3")
     else 
        {
          print("error to get the mp3 file")
          return
        }
     do{
         try AVAudioSession.sharedInstance().setCategory(.playback, mode.default)
         try AVAudioSession.sharedInstance().setActive(true)
         player = try AVPlayer(url: url as URL)
         guard let player = player
               else 
                   { 
                     return 
                   }
         player.play()
      } catch let error {
            print(error.localizedDescription)
               }
   }
Samantha answered 28/11, 2019 at 8:17 Comment(1)
@Cubo78 code is working fine. Actually the audio playing with urls is not worked on iPhones, just working in simulators. in the above code it is solved. after loading with few seconds . its working.Samantha
U
7

After some research, and some disappointment since none of the answers solved my problem, I've come to a solution and here's the final result. The accepted answer is half right, you still need to create an AVPlayerLayer

let url = URL(string: "https://www.myinstants.com/media/sounds/cade-o-respeito.mp3" ?? "")
playerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem)

let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
self.contentView.layoutSublayers(of: playerLayer)

player?.play()
Urissa answered 11/6, 2018 at 19:50 Comment(0)
S
3

Don't forget to unmute silence mode. AvAudioPlayer will not play any sound if silence mode is activated

Sermon answered 27/9, 2021 at 14:27 Comment(0)
D
2

Swift 4

func playSound(soundUrl: String) {
    let sound = URL(fileURLWithPath: soundUrl)
    do {
        let audioPlayer = try AVAudioPlayer(contentsOf: sound)
        audioPlayer.prepareToPlay()
        audioPlayer.play()
    }catch let error {
        print("Error: \(error.localizedDescription)")
    }
}
Danzig answered 26/3, 2018 at 19:7 Comment(0)
F
2

you can create class like:

import AVFoundation

final class Player {
    
    static var share = Player()
    private var player = AVPlayer()
    
    private init() {}
    
    func play(url: URL) {
        player = AVPlayer(url: url)
        player.allowsExternalPlayback = true
        player.appliesMediaSelectionCriteriaAutomatically = true
        player.automaticallyWaitsToMinimizeStalling = true
        player.volume = 1
        player.play()
    }
}

and call it as:

Player.share.play(url: url)

as it is singleton class, it is easy to manage further operation on that player.

Fowle answered 9/8, 2022 at 17:51 Comment(1)
it is working on simulator also, you can increase simulator voice by (cmd + up-arrow), and to decrease (cmd + down-arrow)Fowle
M
1

To play a sound in swift2 is

func playSound(soundUrl: String)
{
   let sound = NSURL(soundUrl)
  do{
      let audioPlayer = try AVAudioPlayer(contentsOfURL: sound)
      audioPlayer.prepareToPlay()
      audioPlayer.play()
  }catch {
      print("Error..")
  }
}

Let me know if it working.

Manualmanubrium answered 2/1, 2016 at 6:51 Comment(2)
its throwing error for me i think i am doing something wrong with mp3 file..here is my response what i got from server... { content = "En este primer programa se tratar\U00e1n asuntos tan importante como este y aquel sin descuidar un poco de todo lo dem\U00e1s"; file = "radio.spainmedia.es/wp-content/uploads/2015/12/ogilvy.mp3"; image = "radio.spainmedia.es/wp-content/uploads/2015/12/tapas.jpg"; number = 0001; subtitle = Titulareando; title = "Tapa 1"; }so how can i play this file @zumry MohamedConservation
It looks like you are almost there but not quite. What you are requesting from the server is not the actual bytes of the mp3 file but rather the metadata about where the file is located. Pull the "file" key out of the json and use that with the AVMediaPlayer instead.Climate
M
0

Plus @Deep Gandhi's answer, If you need to have a cache as well, you may need this below; If any improvement you can give, much appreciated!

final class Player {
    static let shared = Player()

    private var audioPlayer = AVAudioPlayer()

    private init() {
        let cacheSizeMemory = 50 * 1024 * 1024 // 50 MB
        let cacheDiskCapacity = 100 * 1024 * 1024 // 100 MB
        let urlCache = URLCache(memoryCapacity: cacheSizeMemory, diskCapacity: cacheDiskCapacity, diskPath: "audioCache")
        URLCache.shared = urlCache
    }

    func play(url: URL) {
        if let cachedResponse = URLCache.shared.cachedResponse(for: URLRequest(url: url)) {
            playAudio(data: cachedResponse.data)
        } else {
            fetchDataAndPlay(url: url)
        }
    }

    private func fetchDataAndPlay(url: URL) {
        let dataTask = URLSession.shared.dataTask(with: url) { [weak self] data, response, _ in
            guard let data = data else { return }

            // Store the data in the cache
            let cachedResponse = CachedURLResponse(response: response!, data: data)
            URLCache.shared.storeCachedResponse(cachedResponse, for: URLRequest(url: url))

            // Play the audio using the fetched data
            self?.playAudio(data: data)
        }

        dataTask.resume()
    }

    private func playAudio(data: Data) {
        do {
            audioPlayer = try AVAudioPlayer(data: data)
            audioPlayer.play()
        } catch {
            print("Error playing audio: \(error.localizedDescription)")
        }
    }
}
Morphosis answered 16/1 at 5:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.