Play audio file using AVAudioPlayer swift - swift

I am downloading the audio message first then play it using Avaudioplayer
downloadTask = NSURLSession.sharedSession().downloadTaskWithURL(urlStr, completionHandler: { (URL, response, error) -> Void in
self.play(URL!)
})
downloadTask
This works fine, but if I call play(URL) method after saving the response URL that I have stored in NSUserDefaults. The URL is same in both cases. I have checked it. its something like:
file:///Users/mymac/Library/Developer/CoreSimulator/Devices/X-CD91-XXXXXXX-XXX-XXXXXX/data/Containers/Data/Application/XXXXXXX-XXXX-XXXXXXXXXX/tmp/CFNetworkDownload_InUTA3.tmp
Passing this file url to:
func play(url : NSURL) {
do {
player = try AVAudioPlayer(contentsOfURL: url)
player.prepareToPlay()
player.play()
} catch let error as NSError {
//self.player = nil
print(error.localizedDescription)
} catch {
print("AVAudioPlayer init failed")
}
}
Url path is same, but the audioPlayer is unable to play the sound, it shows the following error:
The operation couldn’t be completed. (OSStatus error 2003334207.)
Can anyone guide me in this regard?
Thanks.

As can be seen from the comments above the problem was that the downloaded file was not moved before completing the completionHandler of the downloadTask method.
As it says in the description of the location parameter
The location of a temporary file where the server’s response is stored. You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.
Therefore, in your completionHandler you must move the file to another location that you control before ending the completionHandler.
This probably also explains the crash. If the "temp" URL is saved to UserDefaults in the completionHandler then that URL is no longer valid when the completionHandler has completed. So when you then later call
play(URL!)
with a force unwrapped URL that is now invalid, you get a crash.
Therefore, as an extra safety measure, try unwrapping the URL before trying to use it:
if let url = urlFromUserDefaults {
play(url)
}
Hope that helps you.

You can simply write this...
do
{
self.player = try AVAudioPlayer(contentsOf:Url)
self.player?.numberOfLoops = 0
self.player?.prepareToPlay()
self.player?.volume = 1.0
self.player?.play()
self.player?.delegate=self
}
catch let error as NSError
{
print(error)
}

Related

On-Demand Resources download but not accessible. Swif 4

In my iOS app I have a bunch of mp4 videos that I download at a certain time on the app using On Demand Resources. Using this tutorial:
https://www.raywenderlich.com/520-on-demand-resources-in-ios-tutorial
I download the resources like this at the start of the app, in a previous view controller:
func requestSceneWith(tag: String,
onSuccess: #escaping () -> Void,
onFailure: #escaping (NSError) -> Void) {
// 2
currentRequest = NSBundleResourceRequest(tags: [tag])
// 3
guard let request = currentRequest else { return }
request.beginAccessingResources { (error: Error?) in
// 4
if let error = error {
onFailure(error as NSError)
return
}
// 5
onSuccess()
}
The resource seem to download fine, and I know that they have been downloaded, by looking in the disk report in xcode.
However, when the videos are supposed to be played in the app, the app just shows a black screen. Here is my code to play the videos:
let videoURL = Bundle.main.url(forResource: "cow2", withExtension: "mp4", subdirectory: "Videos/Animals")
self.player = AVPlayer(url: videoURL!)
self.myPlayerController.player = self.player
self.myPlayerController.player?.play()
Now, when the resources are not tagged, and they come with the app and not downloaded later, they work fine. And the console prints the file name, like I did (print(videoURL.absoluteString). But after they are tagged and downloaded later, they dont work, and nothing prints in the console. Just a black screen appears in the app.
I've been stuck on this for ages, and help with really help.
Thanks
I think you didn't request the on-demand resources before accessing the video file. Maybe you can try to declare the NSBundleResourceRequest instance as a global variable in AppDelegate.

How to go back to DispatchQueue.main from URLSession.shared.dataTask (macOS framework)

I'm building a macOS framework and at some point, I need to make a request to some API
When I got the response I want to update the UI. I'm using URLSession.shared.dataTask to make the call and as I know the call is made in the background thread
For some reason when I try to go back to the main thread nothing happens
I'm using a virtual machine to run my framework
Any help?
Thanks
Here how I doing the request:
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
DispatchQueue.main.async {
//Display error message on the UI
//This never happens
//Never go back to the main thread
//Framework stop working
}
}
}.resume()
Are you sure that your task is called?
let dataTask = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
....
DispatchQueue.main.async {
completion(nil, nil)
}
}
dataTask.resume() // You should add this.

Swift 3/4 AVAudioPlayer:contentsOf: fails when loading same URL many times

I am working on a simple game in Swift 4, Xcode 9 Beta 6, and I get an error when attempting to load an Audio File from the same URL many times. Let me show you what I mean; I have a code that looks a little like this.
guard let url = Bundle.main.url(forResource: soundName, withExtension: ext) else {
return nil
}
do {
player = try AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
} catch let error {
print("Could not create AVAudioPlayer with url: \(url.absoluteString)")
return nil
print("Error: \(error.localizedDescription")
}
Note: soundName and ext are String objects composing the name of the local audio file. player is a property of the type AVAudioPlayer.
The game creates a sound using this code, which works perfectly at first, but after a while, it kind of gets lazy and the player = try AVAudioPlayer(contentsOf: url) fails and prints:
Could not create AVAudioPlayer with url: url here
Error: The operation couldn't be completed. (NSOSStatusErrorDomain error -42.)
Anyone has any ideas?
EDIT 1:
I loaded the content of the URL into a Data object by using let data = try Data(contentsOf: url) and that seems to have fix the issue. I am not making it as resolved because I still wish to know what is wrong with using just a URL and why it stops working after loading the same URL several times. Additionally, loading the contents into a binary Data object first might take more resources and time?

Completely delete SKSpriteNode variable

I have a subclass of SKSpriteNode called 'backgroundMusic' that consists of a SKSpriteNode and a AVAudioPlayer file. The objective is to completely delete 'backgroundMusic' after i instantiate it. I try to do :
backgroundMusic.removeFromParent()
but it only removes the SKSpriteNode and not the AVAudioPlayer.
The main issue is that inside of this subclass, I call a bunch of functions within other functions, and when i try to empty the subclass by:
backgroundMusic = nil
the process of calling all the functions still is occurring and causes issues when i re-instantiate it. What i believe will work is if I delete 'backgroundMusic' completely, which will stop the function calling process, and then later re-instantiate it when i need to, it should work fine with no issues. How can I do this?
EDIT I tried:
self.delete(backgroundMusic)
and it crashed the application. Should I use this? If so how?
This happened because you havent configure Audio Session
Some code for playing:
import AVFoundation
var audioPlayer = AVAudioPlayer()
func playAudio() {
// Set the sound file name & extension
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Flip 02", ofType: "wav")!)
// Preperation
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers)
try! AVAudioSession.sharedInstance().setActive(true)
// Play the sound
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: alertSound)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("there is \(error)")
}
}
Details from the docs:
AVAudioSessionCategoryOptionMixWithOthers
Mixes audio from this session with audio from other active sessions.
Valid only if the session category is
AVAudioSessionCategoryPlayAndRecord or AVAudioSessionCategoryPlayback.
(Implicit if the session category is AVAudioSessionCategoryAmbient.)
If you activate your session while using this option, your app’s audio
will not interrupt audio from other apps (such as the Music app). If
not using this option (or a category that is implicitly mixable),
activating your session will interrupt other nonmixable sessions.
To stop you can do:
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch let error as NSError {
print(error.localizedDescription)
}

How to throw and handle an error in swift?

here is my code (Swift):
import UIKit
import AVFoundation
class PlaySoundViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if var filePath = NSBundle.mainBundle().pathForResource("movie_quote",ofType: "mp3"){
var filePathUrl = NSURL.fileURLWithPath(filePath)
AVAUdioPlayer audioPlayer = AVAudioPlayer(contentsOfURL:filePathUrl) throws
}
else{
print("filePath is empty")
}
}
#IBAction func playSlowAudio(sender: UIButton) {
}
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
this is the method I found on my "Documentation and API References" to play audio:
``
initWithContentsOfURL:error:
init(contentsOfURL url: NSURL) throws
So, I return a String as source path, then conver it to NSURL. Now i want to play the audio but the method I am using needs to throw the error and handle it. How should I throw and handle the error ?
Swift 2.0
AVAudioPlayer will throw an exception if its initializer fails. Catch the error by wrapping its initialization in a do/catch clause.
do {
let audioPlayer = try AVAudioPlayer(contentsOfURL: filePathUrl)
// use audioPlayer
} catch {
// handle error
}
As you can see, the keyword try is inserted before any method call that can throw exceptions. As long as the try statement doesn't throw, you can continue your code as normal. If the try statement does throw, you program will jump to the catch clause.
Examining the error
If you'd like to examine the error, you can convert it to an NSError by writing your catch statement like so (as seen in Apple's Objective-C/Swift Interoperability Docs):
do {
let audioPlayer = try AVAudioPlayer(contentsOfURL: filePathUrl)
// use audioPlayer
} catch let error as NSError {
// error is now an NSError instance; do what you will
}
Converting to NSError is only necessary if you want to examine an error thrown by one of Apple's Cocoa objects. Native Swift code, throwing native ErrorType errors, require no conversion.
I recommend you read Apple's new docs on error handling in Swift.
Swift 1.2
If you are using Swift 1.2, then there is no error handling available. Instead, AVAudioPlayer's initialization method will fail and return nil.
If you are using Swift 1.2, I would recommend initializing the audio player like this:
var initError: NSError?
if let audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: &initError) {
// use audioPlayer
} else {
println(initError) // handle error
}
Since Swift 1.2, you cannot throw/handle exceptions. While it is available in swift2 (need XCode7 support) which is still in beta. See this article for detail (https://www.hackingwithswift.com/new-syntax-swift-2-error-handling-try-catch).