I am new to Swift and making an audio app using AVAudioPlayer. I am using a remote URL mp3 file for the audio, and this works when it's static.
For my use case, I want to pull a URL for an mp3 file from a JSON array and then pass it into the AVAudioPlayer to run.
If I move the AVAudioPlayer block into the ViewDidLoad and make the mp3 file a static URL, it will run fine.
Then, when I move this code into my block that extracts an mp3 url from JSON, I can print the URL successfully. But when I pass it into my audio player, problems arise. Here's the code.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://www.example.com/example.json")
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
let json: Any?
do{
json = try JSONSerialization.jsonObject(with: data, options: [])
}
catch{
return
}
guard let data_list = json as? [[String:Any]] else {
return
}
if let foo = data_list.first(where: {$0["episode"] as? String == "Example Preview"}) {
self.audiotest = (foo["audio"] as? String)!
print(self.audiotest) // this prints
// where i'm passing it into the audio player
if let audioUrl = URL(string: self.audiotest) {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
//let url = Bundle.main.url(forResource: destinationUrl, withExtension: "mp3")!
do {
audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)
} catch let error {
print(error.localizedDescription)
}
} // end player
// ....
Specifically, I get an error Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when clicking a play button IBAction that is connected to the audio player. Finally, that action function looks like this:
#IBAction func playPod(_ sender: Any) {
audioPlayer.play()
}
Do you know where I'm going wrong? I'm confused as to why I can't print the URL and also get a response that the URL is nil in the same block, but maybe that's an asynchronous thing.
The problem is that you didn't save the mp3 file to documents and trying to play it
this line
audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)
assumes that there is a saved mp3 file in that path , but acutally there is no files you appended the audio extension on the fly
besides for steaming audio from a remote server, use AVPlayer instead of AVAudioPLayer.
AVPlayer Documentation
Also try this with urls parsed from json
var urlStr = (foo["audio"] as? String)!
self.audiotest = urlStr.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
Related
I am working in an audio player application, and also I am having a offline download option, when the user click the download button it should start download and save it into local. I have saved it using file manager URLSession.
I have tried of taking and separating the destination url. I am using a jukebox third party for playing the song. the sample file location is
file:///var/mobile/Containers/Data/Application/BBB9AF1C-D87C-4C40-9F29-AD89062A20E2/Documents/05-KARMA-YOGA.mp3
if let audioUrl = URL(string: audioTobedownloaded) {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
the actual thing is how should I play the audio which I have downloaded in file manager.
func findFilesWith(extensionType: String) -> [URL]{
var matches = [URL]()
let fileManager = FileManager.default
let files = fileManager.enumerator(atPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
// *** this section here adds all files with the chosen extension to an array ***
for item in files!
{
let fileURL = item as! URL
if (fileURL.pathExtension == extensionType)
{
matches.append(fileURL)
}
}
return matches
}
You can use this method to get all kinds of file with respective to the extension type.
In your case use mp3 as extension type
Try the below method to play audio from the fetched url
func play(url:URL) {
print("playing \(url)")
do {
let player = try AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
player.volume = 1.0
player.play()
} catch let error as NSError {
print(error.localizedDescription)
} catch {
print("AVAudioPlayer init failed")
}
}
I am trying to locate a URL which is only pure .m4a sound with my application. I have the URL to the audio and theoretically download it. Then, with the downloaded fileURL to the sound, I try to play it with the AVAudioPlayer, yet it does not play any sound. Here is my code:
In the URL retrieval function, I call: (urls defined as a URL(string: url), url being the retrieve URL string)
downloadSound(url: urls!)
Here is my downloadSound() function:
func downloadSound(url:URL){
var downloadTask:URLSessionDownloadTask
downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { [weak self](URL, response, error) -> Void in
self?.playSound(url: URL!)
})
downloadTask.resume()
}
And lastly the playSound function:
func playSound(url:URL) {
print("The url is \(url)")
let player = try! AVAudioPlayer(contentsOf: url)
player.play()
Everything is being called as the print("The url is \(url)") returns me the path of the file (I am not actually able to track the file, however).
Here is the general path of the sound on the simulator:
file:///Users/[...]/Library/Developer/CoreSimulator/Devices/116C311A-C7F3-44EC-9762-2FAA0F9FE966/data/Containers/Data/Application/60BFCDE7-AC02-4196-8D1A-24EC646C4622/tmp/CFNetworkDownload_7VDpsV.tmp
Whereas running it on a phone returns:
file:///private/var/mobile/Containers/Data/Application/C75C1F1D-77E9-4795-9A38-3F0756D30547/tmp/CFNetworkDownload_T1XlPb.tmp
Thank you in advance.
I had the same problem and I choosed an alternative solution as app doc said:
A file URL for the temporary file. Because the file is temporary, you
must either open the file for reading or move it to a permanent
location in your app’s sandbox container directory before returning
from this delegate method.
The idea is just to copy from tmp directory to document directory and play from document directory.
Create a member variable:
var player = AVAudioPlayer()
Now implement your downloadSound method as below:
func downloadSound(url:URL){
let docUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let desURL = docUrl.appendingPathComponent("tmpsong.m4a")
var downloadTask:URLSessionDownloadTask
downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { [weak self](URLData, response, error) -> Void in
do{
let isFileFound:Bool? = FileManager.default.fileExists(atPath: desURL.path)
if isFileFound == true{
print(desURL) //delete tmpsong.m4a & copy
try FileManager.default.removeItem(atPath: desURL.path)
try FileManager.default.copyItem(at: URLData!, to: desURL)
} else {
try FileManager.default.copyItem(at: URLData!, to: desURL)
}
let sPlayer = try AVAudioPlayer(contentsOf: desURL!)
self?.player = sPlayer
self?.player.prepareToPlay()
self?.player.play()
}catch let err {
print(err.localizedDescription)
}
})
downloadTask.resume()
}
This is just a sample solution.
I have uploaded some songs in firebase Storage directly,I just want to stream the song in AVAudioPlayer.
Below is the code which I am trying:
var mainRef: FIRStorageReference {
return FIRStorage.storage().reference(forURL: "gs://musicapp-d840c.appspot.com")
}
var audioStorageRef: FIRStorageReference{
return mainRef.child("SongsPath")
}
audioStorageRef.downloadURL { url, error in
if let error = error {
print(error.localizedDescription)
} else {
if let url = url {
do {
self.audioPlayer = try AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: String(describing: url)) as URL)
self.audioPlayer.play()
} catch {}
let storyboard = UIStoryboard(name: "AudioPlayer", bundle: nil)
let audioVc = storyboard.instantiateViewController(withIdentifier: "AudioPlayerViewController") as! AudioPlayerViewController
audioVc.playThisSong = String(describing: url)
self.present(audioVc, animated: false, completion: nil)
}
}
}
Here the song url from the firebase is passing but it is skipping the self.audioPlayer.play. ,I just want to stream the audio. Can I get a proper solution for this?
This is not an answer for streaming.
This is an answer for downloading the file, storing it locally, and playing the audio after the file has finished downloading.
Get a Firebase storage reference using a path string with the file extension. Get a file url to store it on the device using the same path string that we use for the Firebase storage reference.
Initiate the download task using write(toFile: URL). Store the download task in a variable to add observers. When the download is successful, play the audio.
In Swift 4:
var player: AVAudioPlayer?
let pathString = "SongsPath.mp3"
let storageReference = Storage.storage().reference().child(pathString)
let fileUrls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
guard let fileUrl = fileUrls.first?.appendingPathComponent(pathString) else {
return
}
let downloadTask = storageReference.write(toFile: fileUrl)
downloadTask.observe(.success) { _ in
do {
self.player = try AVAudioPlayer(contentsOf: fileUrl)
self.player?.prepareToPlay()
self.player?.play()
} catch let error {
print(error.localizedDescription)
}
}
This is minimal code. Implement error handling as you see fit.
Firebase example of downloading locally
I am making a chat room application, so far it is able to send message, image and video.
I am using a very similar method to send video and it works, but it's not working when sending audio.
The audio file and audio url is upload to Firebase successfully, But when I tried to play back the audio, it show this error: The operation couldn’t be completed. (OSStatus error 2003334207.).
The project is getting quite overwhelmingly large, and I have very little experience using AVAudio, so if you guys had similar problems before please teach me how to fix it. Thanks!!!
Here is the code of setting up the audioRecorder, and I get the url here and pass it to other func to put the audio file to Firebase storage.
func startRecording() {
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.low.rawValue
]
do {
let audioFileUrl = getAudiFileURL()
audioRecorder = try AVAudioRecorder(url: audioFileUrl, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
blackView.isHidden = false
} catch {
finishRecording(success: false)
}
}
Here is where I try to upload the audio file to Firebase storage, and it does print out the correct downloadURL. (The URL is pointing to the file's location in the iOS devices.)
func handleAudioSendWith(url: String) {
guard let fileUrl = URL(string: url) else {
return
}
let fileName = NSUUID().uuidString + ".m4a"
FIRStorage.storage().reference().child("message_voice").child(fileName).putFile(fileUrl, metadata: nil) { (metadata, error) in
if error != nil {
print(error ?? "error")
}
if let downloadUrl = metadata?.downloadURL()?.absoluteString {
print(downloadUrl)
let values: [String : Any] = ["audioUrl": downloadUrl]
self.sendMessageWith(properties: values)
}
}
}
Here is how I set up the url for the audioRecorder above.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getAudiFileURL() -> URL {
return getDocumentsDirectory().appendingPathComponent(".m4a")
}
And this is where I play the audio:
func handleAudioPLay() {
if let audioUrl = message?.audioUrl, let url = URL(string: audioUrl) {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.play()
print("Audio ready to play")
} catch let error {
print(error.localizedDescription)
}
}
}
I can actually download the sound file from Firebase using the url and play it on my computer, which means the url is fine.
I have solved the problem by downloading the sound file using URLSession, and play it using AVAudioPlayer(data: data!, fileTypeHint: "aac").
I am trying to play an MP3 file (works when played via VLC/iTunes) when a button is pressed. Here is my code:
var audioPlayer: AVAudioPlayer!
#IBAction func playEpisode(sender: AnyObject) {
println("now playing")
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let data: CDEpisode = fetchedResultsController.objectAtIndexPath(indexPath!) as! CDEpisode
var err: NSError?
let url = NSURL(string: data.localPath)
println("The url is \(url)")
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: &err)
if audioPlayer == nil {
if let e = err {
println(e.localizedDescription)
}
}
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.play()
}
Here is the log:
now playing
The url is Optional(file:///var/mobile/Containers/Data/Application/4747A71E-A63F-4EFC-B2DF-8B361361080B/Documents/serial-s01-e12.mp3)
The operation couldn’t be completed. (OSStatus error 2003334207.)
fatal error: unexpectedly found nil while unwrapping an Optional value
The EXC_BREAKPOINT happens on the audioPlayer.delegate = self.
Other threads on StackoOverflow do not help. Any ideas?
Thanks
Edit: I have tried passing a localURL to contentsOfURL (instead of a CDEpisode object) and it still fails.
This is probably caused by trying to load a file that doesn't exist. If that helps someone adding the call to url.checkResourceIsReachable() will log more descriptive message.
Example code:
do {
let url = URL(fileURLWithPath: dbObject.path)
let isReachable = try url.checkResourceIsReachable()
// ... you can set breaking points after that line, and if you stopped at them it means file exist.
} catch let e {
print("couldnt load file \(e.localizedDescription)")
}
It looks like your trying to unwrap a variable that has a nil value. You should safely unwrap your variables to prevent this.
if let data: CDEpisode = fetchedResultsController.objectAtIndexPath(indexPath!) as! CDEpisode
{
var err: NSError?
let url = NSURL(string: data.localPath)
println("The url is \(url)")
//rest of code
}
You will still need to figure out why it is returning nil but this is a safer way to unwrap variables and prevent crashing as there would need to be more context to resolve that issue.
Some questions to look into:
Are you sure the fetchedResultsController is returning an object at
all?
Are you sure it is of CDEpisode?
try this one
var player: AVAudioPlayer = AVAudioPlayer()
#IBAction func playX(_ sender: Any) {
let urlstring = "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
let url = URL(string: urlstring)
let data = try! Data(contentsOf: url!)
player = try! AVAudioPlayer(data: data)
player.prepareToPlay()
player.volume = 1.0
player.play()
}
You're checking if audioPlayer is nil but then you go on to use it as if it wasn't anyway. You probably want something like:
if audioPlayer == nil {
if let e = err {
println(e.localizedDescription)
}
} else {
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.play()
}
And do something to actually handle the error case rather than just printing the error.
In my case I was having the same issue and I found out that I needed to set this before start recording
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
Hope it helps anyone
I also got this problem, after checking up the audio file url, found that it is stored in Cache directory. so audio player probably couldn't find audio file according to your "url".
please ensure, the url path is under Document directory.
As others have said, the audio player couldn't find the file.
This helped me: Document directory path change when rebuild application
Basically, you cannot load files with an absolute reference, as the sandbox environment regenerates the absolute file url each time. So you will need to add a small bit of code (see above) to get the correct urls to use.