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

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?

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.

AVAudioPlayer does not play audio file - swift

I know it seems quite straightforward, but in a weird way AVAudioPlayer does not play audio file. I have this code. It is not go through catch and still sound does not play. I've checked on physical device, simulator and also checked sound volume. But still I can't hear sound.
//in ViewController as a property
let soundEffect: AVAudioPlayer!
//in viewDidLoad
let path = Bundle.main.path(forResource: "success.mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
soundEffect = try AVAudioPlayer(contentsOf: url)
soundEffect.prepareToPlay()
soundEffect.play()
} catch let error as NSError {
print(error.description)
}
I also tried let path = Bundle.main.path(forResource: "success", ofType:"mp3")! but it didn't make any difference.
Try making your AVAudioPlayer an instance variable. otherwise soundEffect is deallocated at the end of the do loop.

How do you rotate a video from a URL in Swift?

I have my AVAssetWriter's completion handler create an exporter after it has finished writing so that the video can present itself in a player so the user can save it if they wish. That is all well and good, but I'd like to alter the written video and simply rotate it after it has finished writing.
The answers I've found to this varies from rotating the video as it's recording or deprecated old Objective-C ways that don't apply anymore.
My completion handler and exporter looks something like this. The exporter knows the outputFileType so I thought I'd be able to rotate the video there:
assetWriter?.finishWriting(completionHandler: {[unowned self] () -> Void in
let firstAsset = AVURLAsset(url: self.movieURL() as URL)
guard let exporter = AVAssetExportSession(asset: firstAsset, presetName: AVAssetExportPresetHighestQuality) else { return }
exporter.outputURL = self.movieURL() as URL
exporter.outputFileType = AVFileType.mov
exporter.exportAsynchronously() {
DispatchQueue.main.async(){
// below here is where another class is presented with the
// video URL to display in a player with the option to save

Play audio file using AVAudioPlayer 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)
}

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