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
Related
I am having a weird situation and have no clue how to handle this , I am downloading the videos from firestorage and caching into device for future use , meanwhile the background thread is already doing its job , I am passing a video url to the function to play the video. The issue is that sometimes avplayer is playing the right video and sometimes taking some other video url from the cache.
you can find the code in below :
func cacheVideo(for exercise: Exercise) {
print(exercise.imageFileName)
guard let filePath = filePathURL(for: exercise.imageFileName) else { return }
if fileManager.fileExists(atPath: filePath.path) {
// print("already exists")
} else {
exercise.loadRealURL { (url) in
print(url)
self.getFileWith(with: url, saveTo: filePath)
}
}
}
writing file here
func getFileWith(with url: URL, saveTo saveFilePathURL: URL) {
DispatchQueue.global(qos: .background).async {
print(saveFilePathURL.path)
if let videoData = NSData(contentsOf: url) {
videoData.write(to: saveFilePathURL, atomically: true)
DispatchQueue.main.async {
// print("downloaded")
}
} else {
DispatchQueue.main.async {
let error = NSError(domain: "SomeErrorDomain", code: -2001 /* some error code */, userInfo: ["description": "Can't download video"])
print(error.debugDescription)
}
}
}
}
now playing the video using this
func startPlayingVideoOnDemand(url : URL) {
activityIndicatorView.startAnimating()
activityIndicatorView.isHidden = false
print(url)
let cachingPlayerItem = CachingPlayerItem(url: url)
cachingPlayerItem.delegate = self
cachingPlayerItem.download()
// cachingPlayerItem.preferredPeakBitRate = 0
let avasset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: avasset)
let player = AVPlayer(playerItem: playerItem)
player.automaticallyWaitsToMinimizeStalling = false
initializeVideoLayer(for: player)
}
any suggestions would be highly appreciated.
this was solved because the data model which i was using to download bunch of videos files was accessed in background thread and meanwhile i was trying to assign the url to the same data model class in order to fetch the video and play in avplayer. Hence this was the issue and resolved by simply adding a new attribute into data model for assigning the url to play right away.
I was trying to read a file xyz.m4a and trying to play it. But I have an error
2018-04-24 18:11:20.010927+0530 Demo[10807:690526] NSURLConnection finished with error - code -1002
2018-04-24 18:11:20.327008+0530 Demo[10807:688427] CFURLCopyResourcePropertyForKey failed because it was passed an URL which has no scheme
Error: The file “xyz.m4a” couldn’t be opened.
Here is my code:
func audioMethod() {
let fileName = "xyz.m4a",
audioFile = getDocumentsURL().appendingPathComponent(fileName)
if !FileManager.default.fileExists(atPath: audioFile.path) {
print("We have no file !!!!!")
}
do {
let audioData = try Data(contentsOf: audioFile)
if audioPlayer == nil {
audioPlayer = try AVAudioPlayer.init(data: audioData, fileTypeHint: "m4a")
}
audioPlayer?.play()
} catch let error {
print("Error: " + error.localizedDescription)
}
}
func getDocumentsURL() -> URL {
let urlStr = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let url = URL(string: urlStr!)
return url!
}
If I pass that URL direct in AVAudioPlayer as AVAudioPlayer.init(contentsOf: audioFile) then it works fine.
I have tried everything. But nothing works. Please let me know what is wrong with my code.
if let path = Bundle.main.path(forResource: "videoplayback", ofType: "mp4"){
let video = AVPlayer(url: URL(fileURLWithPath:path))
let videoPlayer = AVPlayerViewController()
videoPlayer.player = video
present(videoPlayer,animated: true,completion: {
video.play()
})
}
Add that Video in File Explorer
Then try with above code
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)
I'm working on a project in swift3 and I have a particular UIViewController to download an mp3 to my filemanager and using that path saved I wants to play an mp3 using AVPlayer. My code doesn't work, I think Im missing something. How would I achieve this?. My code to download the file to the filemanager as below
func downloadSong() {
if let audioUrl = URL(string: "https://www.googleapis.com/download/storage/v1/b/feisty-beacon-159305.appspot.com/o/Aal%20Izz%20Well%20-%20Remix(MyMp3Song).mp3?generation=1490097740630162&alt=media") {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!//since it sys first this may only plays the first item
// destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print("destinationUrl is :",destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("The file already exists at path")
self.dest = destinationUrl.path
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
print("file path is :",destinationUrl.path)
print("File moved to documents folder")
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
}
And once I save that file, using its file path which is "destinationUrl.path" I initiate my player as bellow in a different UIViewController. As for now I have hardcoded the path I save. The code as bellow.
override func viewDidLoad() {
super.viewDidLoad()
//path I have saved in file manager is set to the url
let url = NSURL.fileURL(withPath:"/Users/auxenta/Library/Developer/CoreSimulator/Devices/F3840294-04AA-46BE-9E46-4342452AFB69/data/Containers/Data/Application/670C0EA1-B375-498E-8847-8707D391D7BF/Documents/Aal Izz Well - Remix(MyMp3Song).mp3") as NSURL
self.playerItem = AVPlayerItem(url: url as URL)
self.player=AVPlayer(playerItem: self.playerItem!)
let playerLayer=AVPlayerLayer(player: self.player!)
playerLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 50) // actually this player layer is not visible
self.view.layer.addSublayer(playerLayer)
}
#IBAction func playBtnPressed(_ sender: Any) {
if player?.rate == 0 // this means if its not playing
{
player!.play()
print("playing")
playbutton.setImage(UIImage(named: "pausebutton"), for: UIControlState.normal)
//trackTime
trackTime()
} else {
// getFileFromFieManager()
print("pause")
player!.pause()
playbutton.setImage(UIImage(named: "playbutton"), for: UIControlState.normal)
}
}
Your problem is the URL set to the AVPlayer. In fact hardcoding a path in iOS doesn't work, it can change at any time.
You need to use code it the same way as you destinationUrl:
if let audioUrl = URL(string: "https://www.googleapis.com/download/storage/v1/b/feisty-beacon-159305.appspot.com/o/Aal%20Izz%20Well%20-%20Remix(MyMp3Song).mp3?generation=1490097740630162&alt=media") {
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!//since it sys first this may only plays the first item
// destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print("destinationUrl is :",destinationUrl)
self.playerItem = AVPlayerItem(url: destinationUrl)
self.player=AVPlayer(playerItem: self.playerItem!)
let playerLayer=AVPlayerLayer(player: self.player!)
.
.
.
}
Normally it should work. Hope it helps.
You can use codes in the below. I hope I could helped you out. First you need to create path for the source file that you wanted to be play
let path = Bundle.main.path(forResource: "yourFileName", ofType: "mp3")
let soundUrl = URL(fileURLWithPath: path!)
do{
try btnSound = AVAudioPlayer(contentsOf: soundUrl)
btnSound.prepareToPlay()
}
catch let err as NSError{
print(err.debugDescription)
}
After you created these in the viewDidLoad method. You can create function for the playing sound like;
func playSound() {
if btnSound.isPlaying {
btnSound.stop()
}
btnSound.play()
}
after all of these you can able to play mp3 files in swift.
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").