I save mp3 file into Core Data as NSData binary data. Then I load this file with help of NSFetchedResultsController. Then I wrote following code but I got the error. How can I fix it?
fatal error: unexpectedly found nil while unwrapping an Optional value
var audioPlayer = AVAudioPlayer()
func play() {
if musicData != nil {
audioPlayer = AVAudioPlayer(data: musicData, error: nil)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
UPDATE
I checked it and I saw that AVAudioPlayer is nil but I init this player in beginning of this class.
Still I changed code but I get the same error
The second method
var av = AVAudioPlayer()
func play() {
let object = fetchedResultsController.fetchedObjects?.first as! Entity
let songData = object.mp3
var urlData: NSURL!
songData.writeToURL(urlData, atomically: true)
av = AVAudioPlayer(contentsOfURL: urlData, error: nil)
av.prepareToPlay()
av.play()
}
I added the file type hint and it works for me.
func playMusic() {
let url = NSBundle.mainBundle().URLForResource("Music", withExtension: "mp3")!
let data = NSData(contentsOfURL: url)!
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(data: data, fileTypeHint: AVFileTypeMPEGLayer3, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
Had the same problem with playing mp3 file from Core Data. There is another solution, which don't require you to make resources, but only file manager . Works in Swift 5:
final class AudioPlayer: AVPlayer, ObservableObject {
private func changeSong(with newSong: Song) { // struct Song has function findInDatabase, which can return saved data from CoreData (or error)
// trying to find song in CoreData
newSong.findInDatabase() { (result: Result<SavedSong, CoreDataError>) in
switch result {
case .success(let savedSong):
// SavedSong struct has variable 'file' which is of type Data
guard let songFile = savedSong.file, !songFile.isEmpty else {
// your error handling
return
}
guard var localURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { return } // or error handling
localURL.appendPathComponent("song.mp3")
try! songFile.write(to: localURL) // saving song data in url
// now just playing from created URL
let nextPlayerItem = AVPlayerItem(url: localURL)
self.replaceCurrentItem(with: nextPlayerItem) // 'self' - is AVPlayer instance
self.play()
case .failure( _):
// error handling, for example try to play from URL
}
}
}
}
Related
I finally started learning to play sounds but I'm not succeeding in playing sounds in my app. I'm still on Xcode 10.1/Swift4 (not 4.2) as I'm not able to upgrade to Mojave/Catalina yet.
I read quite a few posts about .setCategory(.playback, mode: .default) carrying a bug in Xcode 10.1 but solutions found are for Swift 4.2. Also category: and mode:are expected to be of type String,but on docs the function is explained as:
func setCategory(_ category: AVAudioSession.Category, mode:
AVAudioSession.Mode, options: AVAudioSession.CategoryOptions = [])
throws
and they're not of type String. I'm a bit lost here.
My code doesn't throw any compiling error, but at runtime on console I get :
AVAudioSessionUtilities.mm:106:getUInt32: -- Category Value Converter
failed to find a match for string "ambient"
What am I missing here? Can you please point me in the right direction to understand this category problem?
As always thank you very much for your time and help.
This is the function that should play sounds :
static func playOnceSound(soundToPlay: String) {
var player: AVAudioPlayer?
guard let url = Bundle.main.url(forResource: soundToPlay, withExtension: "mp3") else { return }
do {
// try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) // Error: Type 'String' has no member 'playback'
try AVAudioSession.sharedInstance().setCategory("ambient", mode: "default", options: .defaultToSpeaker)
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: url, fileTypeHint: AVFileType.mp3.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.numberOfLoops = 1
player.volume = 1.0
player.play()
} catch let error {
print(error.localizedDescription)
}
}
Have you tried inputting ambient & default this way, instead of using a string? Also, instantiating the audio session object before setCategory:
var audioSession: AVAudioSession?
func setup() {
audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.ambient, mode: .default, options: .defaultToSpeaker)
try audioSession.setActive(true)
}
catch {
print("Failed to set category", error.localizedDescription)
}
}
#IBAction func doPlay1(_ sender: AnyObject) {
do{
self.audioPlayer = try AVAudioPlayer(contentsOf: URL.init(string: url)!)
self.audioPlayer.prepareToPlay()
self.audioPlayer.delegate = self
self.audioPlayer.play()
}catch{
print(error.localizedDescription)
}
}
So I just set it in AppDelegate didFinishLaunchingWithOptions as :
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch let error1 as NSError{
error = error1
print("could not set session. err:\(error!.localizedDescription)")
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch let error1 as NSError{
error = error1
print("could not active session. err:\(error!.localizedDescription)")
}
and rewrote the function as :
static func playOnceSound(soundToPlay: String) {
if Sounds.player!.isPlaying == true {
Sounds.player!.stop()
}
let url = URL(fileURLWithPath: Bundle.main.path(forResource: soundToPlay, ofType: "mp3")!)
var error: NSError?
do {
Sounds.player = try AVAudioPlayer(contentsOf: url)
} catch let error1 as NSError {
error = error1
Sounds.player = nil
}
if let err = error {
print("audioPlayer error \(err.localizedDescription)")
return
} else {
Sounds.player!.prepareToPlay()
}
//negative number means loop infinity
Sounds.player!.numberOfLoops = 0
Sounds.player!.volume = Sounds.volume
Sounds.player!.play()
}
Thanks for your help, and I hope this will be of help to others.
Cheers.
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
Current code:
#IBAction func sound1(sender: UIButton)
{
var sound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Sound1", ofType: "wav")!)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
var error: NSError?
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: sound)
} catch var error1 as NSError {
error = error1
audioPlayer = nil
}
audioPlayer.prepareToPlay()
audioPlayer.volume = 1
audioPlayer.play()
}
This is the current code I had before updating this project to Swift 2.
audioPlayer = nil
The issue I get when trying to run the app is this:
Cannot assign a value of type 'NilLiteralConvertible' to a value of type 'AVAudioPlayer'
It means that your variable audioplayer is not an Optional and so can not be set to nil.
Either make it an Optional then use it with try? (note the ?) without do catch, or do not attempt to set it to nil if you want to continue using try inside do catch.
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.
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)
}
}
}