Using AVAudioPlayer fail to play .m4a by Swift 4 - ios

I use Alamofire to download the iTunes search Api's trial music.
And when I download finished, I want to play the music.
I try to fix it, but it also no sounds to play.
How to solve this problem?
Thanks.
import UIKit
import AVFoundation
import Alamofire
import CryptoSwift
class FirstViewController: UIViewController {
let urlString = "https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview18/v4/9c/db/54/9cdb54b3-5c52-3063-b1ad-abe42955edb5/mzaf_520282131402737225.plus.aac.p.m4a"
override func viewDidLoad() {
super.viewDidLoad()
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileName = self.urlString.md5()
let fileURL = documentsURL.appendingPathComponent("\(fileName).m4a")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
if response.error == nil {
var audioPlayer: AVAudioPlayer!
do {
audioPlayer = try AVAudioPlayer(contentsOf: response.destinationURL!)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("Error:", error.localizedDescription)
}
}
}
}
}

The problem is caused by the fact that audioPlayer is a local variable, therefore it gets deallocated when you leave the scope of completion closure. Because audioPlayer isn't retained anywhere else, the moment you leave the closure's scope, audioPlayer reference count is equal to 0, which causes ARC to deallocate it.
Also, you use force unwrapping operator - ! - a lot, which is:
1) incorrect
2) unsafe
Use either if let construct or guard statement
What you need to to is store the player as instance variable of your FirstViewController class.
class FirstViewController: UIViewController {
let urlString = "https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview18/v4/9c/db/54/9cdb54b3-5c52-3063-b1ad-abe42955edb5/mzaf_520282131402737225.plus.aac.p.m4a"
var audioPlayer : AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
// (...)
Alamofire.download(urlString, to: destination).response { [weak self] (response) in
if response.error == nil {
guard let url = response.destinationURL else { return }
do {
self?.audioPlayer = try AVAudioPlayer(contentsOf: url)
self?.audioPlayer?.prepareToPlay()
self?.audioPlayer?.play()
} catch {
print("Error:", error.localizedDescription)
}
}
}
}
}

Just move audioPlayer to controller
class FirstViewController: UIViewController {
let urlString = "https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview18/v4/9c/db/54/9cdb54b3-5c52-3063-b1ad-abe42955edb5/mzaf_520282131402737225.plus.aac.p.m4a"
var audioPlayer: AVAudioPlayer?
//Downloading code......
}

Related

AVQueuePlayer is taking lot of time to play first song

Below is my code to play AVQueueplayer with some web URL, but it takes almost 10-20 seconds to play first song. I have 10 songs, but for reference and to make it small, I have just kept one song here.
arrSongs = ["http://radiotaj.com/music/1/1836.mp3"]
let index = 0
let strSong = (arrSongs.object(at: index)) as? String
playerItem = AVPlayerItem(url: URL(string:strSong!)!
let playerItems = [playerItem]
player = AVQueuePlayer(items : playerItems as! [AVPlayerItem])
player.play()
Below are the solutions I have already tried
Adding CMTime
Making it pause and play again
It works perfectly fine if file is local.
Any help is greatly appreciated.
Thank you!
You can try the following code, it works for me. Here is the version without Alamofire (replace yourURL with the actual link):
override func viewDidLoad() {
super.viewDidLoad()
downloadFileFromURL(url: URL(string: yourURL)!)
}
func play(url: URL) {
do {
let songData = try Data(contentsOf: url, options: NSData.ReadingOptions.mappedIfSafe)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(data: songData, fileTypeHint: AVFileTypeAppleM4A)
player!.prepareToPlay()
player!.play()
} catch {
print(error)
}
}
func downloadFileFromURL(url: URL) {
var downloadTask = URLSessionDownloadTask()
downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: {
customURL, response, error in
self.play(url: customURL!)
})
downloadTask.resume()
}
Using Alamofire is faster, it basically downloads the file into the Documents folder and plays it from here (don't forget to install Alamofire and type outside the class import Alamofire:
func play(url: URL) {
do {
let songData = try Data(contentsOf: url, options: NSData.ReadingOptions.mappedIfSafe)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(data: songData, fileTypeHint: AVFileTypeAppleM4A)
player!.prepareToPlay()
player!.play()
} catch {
print(error)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("song."+"mp3")
return (documentsURL, [.removePreviousFile])
}
Alamofire.download(yourURL, to: destination).response { response in
if response.destinationURL != nil {
songURL = response.destinationURL!
self.play(url: songURL!)
}
}
}

AVAudioPlayer doesn't play sound?

I have a AVAudioPlayer in a seperate class, which I load into a ViewController. Here's the code:
import Foundation
import AVFoundation
class SoundController {
var audioPlayer: AVAudioPlayer!
init() {
}
func playAudio() {
let path = NSBundle.mainBundle().pathForResource("ring-ring", ofType: "aiff", inDirectory: "Media" )!
let url = NSURL(fileURLWithPath: path)
do {
print("Let's play the sound")
let sound = try AVAudioPlayer(contentsOfURL: url)
self.audioPlayer = sound
sound.play()
} catch {
print("Error playing sound file")
}
}
}
And here is the ViewController
override func viewDidLoad() {
super.viewDidLoad()
let sound = SoundController()
sound.playAudio()
}
Everything compiles and I get the console output "Let's play the sound", but no matter which device I test on, the sound doesn't play. Any advice what's wrong?
You assign your audioPlayer the wrong way I think this should work.
import Foundation
import AVFoundation
var audioPlayer: AVAudioPlayer!
class SoundController {
class func playAudio() {
let path = NSBundle.mainBundle().pathForResource("ring-ring", ofType: "aiff", inDirectory: "Media" )!
let url = NSURL(fileURLWithPath: path)
do {
print("Let's play the sound")
audioPlayer = try AVAudioPlayer(contentsOfURL: url)
audioPlayer.prepareToPlay()
} catch {
print("Error playing sound file")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
SoundController.playAudio()
audioPlayer.play()
}

Reusable extension to play sound without memory leak in iOS and Swift?

Playing a sound is so verbose in code and would like to create an extension of AVAudioSession if possible. The way I'm doing it is assigning an object to a variable, but need help/advise on how to set up this function so it's reusable and optimized.
Here's what I have:
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
I think I have to create the audioPlayer variable outside of this function, otherwise I had a hard time playing anything. Maybe it can be self contained? I'm hoping to use it something like this:
AVAudioSession.sharedInstance().play("bebop")
Taking the code straight from your example I see two options:
1) Is there any particular reason why you want to make it as an extension of AVAudioSession? If not, just make your own service!
class AudioPlayerService {
static let sharedInstance = AudioPlayerService()
var audioPlayer: AVAudioPlayer?
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
2) If you do need to make it as an extension of AVAudioSession, then take a look at associated objects
extension AVAudioSession {
private struct AssociatedKeys {
static var AudioPlayerTag = "AudioPlayerTag"
}
var audioPlayer: AVAudioPlayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.AudioPlayerTag) as? AVAudioPlayer
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.AudioPlayerTag,
newValue as AVAudioPlayer?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
This is the best way I have found to add sound
Filename is shootMissile.wav
func shootMissileSound() {
if let soundURL = NSBundle.mainBundle().URLForResource("shootMissile", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}

Playing an embedded sound using swift 2.0 code Xcode 7.1 IOS 9.1

Copied and Pasted this code principally. Compiles and runs, but plays nothing. Using Xcode 7.1 and IOS 9.1. What have I missed... Loaded sound file into main program and AVAssets...
import UIKit
import AVFoundation
class ViewController: UIViewController {
var buttonBeep : AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
buttonBeep = setupAudioPlayerWithFile("hotel_transylvania2", type:"mp3")
//buttonBeep?.volume = 0.9
buttonBeep?.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer? {
//1
let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
let url = NSURL.fileURLWithPath(path!)
//2
var audioPlayer:AVAudioPlayer?
// 3
do {
try audioPlayer? = AVAudioPlayer(contentsOfURL: url)
} catch {
print("Player not available")
}
return audioPlayer
}
}
You've got this line backwards:
try audioPlayer? = AVAudioPlayer(contentsOfURL: url)
It should be:
audioPlayer = try AVAudioPlayer(contentsOfURL: url)
Side note: the conversion to and from NSString is not necessary here, just use String - and you should not force unwrap the result of NSBundle:
func setupAudioPlayerWithFile(file:String, type:String) -> AVAudioPlayer? {
//1
guard let path = NSBundle.mainBundle().pathForResource(file, ofType: type) else {
return nil
}
let url = NSURL.fileURLWithPath(path)
//2
var audioPlayer:AVAudioPlayer?
// 3
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: url)
} catch {
print("Player not available")
}
return audioPlayer
}

AVAudioPlayer not playing audio in Swift

I have this code in a very simple, single view Swift application in my ViewController:
var audioPlayer = AVAudioPlayer()
#IBAction func playMyFile(sender: AnyObject) {
let fileString = NSBundle.mainBundle().pathForResource("audioFile", ofType: "m4a")
let url = NSURL(fileURLWithPath: fileString)
var error : NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: &error)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
if (audioPlayer.isEqual(nil)) {
println("There was an error: (er)")
} else {
audioPlayer.play()
NSLog("working")
}
I have added import AVFoundation and audioPlayer is a global variable. When I execute the code, it does print "working", so it makes it through without errors but no sound is played. The device is not in silent.
There's so much wrong with your code that Socratic method breaks down; it will probably be easiest just to throw it out and show you:
var player : AVAudioPlayer! = nil // will be Optional, must supply initializer
#IBAction func playMyFile(sender: AnyObject?) {
let path = NSBundle.mainBundle().pathForResource("audioFile", ofType:"m4a")
let fileURL = NSURL(fileURLWithPath: path)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.prepareToPlay()
player.delegate = self
player.play()
}
I have not bothered to do any error checking, but the upside is you'll crash if there's a problem.
One final point, which may or may not be relevant: not every m4a file is playable. A highly compressed file, for example, can fail silently (pun intended).
Important that AvPlayer is class member and not in the given function, else it goes out of scope... :)
I had to declare a global player variable
var player: AVAudioPlayer!
and set it in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
player = AVAudioPlayer()
}
Then I could play the audio file wherever like this:
func playAudioFile(){
do {
if audioFileUrl == nil{
return
}
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
/* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
player = try AVAudioPlayer(contentsOf: audioFileUrl, fileTypeHint: AVFileType.m4a.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.play()
print("PLAYING::::: \(audioFileUrl)")
}
catch let error {
print(error.localizedDescription)
}
}
}
Here is a working snippet from my swift project. Replace "audiofile" by your file name.
var audioPlayer = AVAudioPlayer()
let audioPath = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("audiofile", ofType: "mp3"))
audioPlayer = AVAudioPlayer(contentsOfURL: audioPath, error: nil)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.play()
You can download fully functional Swift Audio Player application source code from here https://github.com/bpolat/Music-Player
for some reason (probably a bug) Xcode can't play certain music files in the .m4a and the .mp3 format I would recommend changing them all to .wav files to get it to play
//top of your class
var audioPlayer = AVAudioPlayer
//where you want to play your sound
let Sound = NSURL(fileURLWithPath: Bundle.main.path(forResource: "sound", ofType: "wav")!)
do {
audioPlayer = try AVAudioPlayer(contentsOf: Sound as URL)
audioPlayer.prepareToPlay()
} catch {
print("Problem in getting File")
}
audioPlayer.play()
var audioPlayer = AVAudioPlayer()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("KiepRongBuon", ofType: "mp3")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
I used the below code in my app and it works. Hope that is helpful.
var audioPlayer: AVAudioPlayer!
if var filePath = NSBundle.mainBundle().pathForResource("audioFile", ofType:"mp3"){
var filePathUrl = NSURL.fileURLWithPath(filePath)
audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
audioPlayer.play()
}else {
println("Path for audio file not found")
}
In Swift Coding using Try catch, this issues will solve and play audio for me and my code below,
var playerVal = AVAudioPlayer()
#IBAction func btnPlayAction(sender: AnyObject) {
let fileURL: NSURL = NSURL(string: url)!
let soundData = NSData(contentsOfURL: fileURL)
do {
playerVal = try AVAudioPlayer(data: soundData!)
}
catch {
print("Something bad happened. Try catching specific errors to narrow things down",error)
}
playerVal.delegate = self
playerVal.prepareToPlay()
playerVal.play()
}
Based on #matt answer but little bit detailed 'cause original answer did not completely satisfied me.
import AVFoundation
class YourController: UIViewController {
private var player : AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
prepareAudioPlayer()
}
#IBAction func playAudio() {
player?.play()
}
}
extension YourController: AVAudioPlayerDelegate {}
private extension YourController {
func prepareAudioPlayer() {
guard let path = Bundle.main.path(forResource: "you-audio", ofType:"mp3") else {
return
}
let fileURL = URL(fileURLWithPath: path)
do {
player = try AVAudioPlayer(contentsOf: fileURL)
} catch let ex {
print(ex.localizedDescription)
}
player?.prepareToPlay()
player?.delegate = self
}
}
swift 3.0:
import UIKit
import AVFoundation
class ViewController: UIViewController
{
var audioplayer = AVAudioPlayer()
#IBAction func Play(_ sender: Any)
{
audioplayer.play()
}
#IBAction func Pause(_ sender: Any)
{
if audioplayer.isPlaying
{
audioplayer.pause()
}
else
{
}
}
#IBAction func Restart(_ sender: Any)
{
if audioplayer.isPlaying
{
audioplayer.currentTime = 0
audioplayer.play()
}
else
{
audioplayer.play()
}
}
override func viewDidLoad()
{
super.viewDidLoad()
do
{
audioplayer = try AVAudioPlayer(contentsOf:URL.init(fileURLWithPath:Bundle.main.path(forResource:"bahubali", ofType: "mp3")!))
audioplayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
do
{
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch
{
}
}
catch
{
print (error)
}
}
}

Resources