I am currently working on a code that downloads .m4a audio format files from Firebase and allows the user to hear the music play within the same View Controller. Through lots of research on SO and other websites, I managed to get the audio downloaded, but when the audio is supposed to be being played, I only hear static. In fact, it actually plays the entire duration of the audio clip (e.g. the audio is 6s long, so a user would hear static for exactly 6s), so I know for a fact it is actually downloading something, just nothing is playing.
Below is my source code for the download function and play music function. I tried searching this topic on SO, but there are very few articles pertaining to audio download to iOS from Firebase (mostly it seems to be "how to download images", etc.)
Thank you very much in advance!
Sam
//Function to start playing music
func playMusic(){
do {
self.audioPlay = try AVAudioPlayer(contentsOf: self.localSongURL)
self.audioPlay.delegate = self
self.audioPlay.prepareToPlay()
self.audioPlay.play()
self.audioPlay.volume = 1.0 //I tried adjusting volumes to see if it would make a difference
} catch {
createAlert(title: "File Not Found", message: "Audio downloaded cannot be interpereted.")
}
}
//Function to download music
//In the Firebase storage, songs are listed underneath an ID, so the "tillAt"s help to parse through
//the appropriate strings to get the right path for collection
func downloadMusic(){
let tillAt1 = self.songPath.components(separatedBy: ID + "/")
let tillAt2 = tillAt1[1].components(separatedBy: ".")
store = Storage.storage().reference().child(patientID).child(tillAt1[1])
//From here is where I think the issue is within
self.localSongURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(tillAt2[0] + ".m4a")
store.getData(maxSize: 128 * 1024 * 1024, completion: {(data, error) in
if let error = error {
print("Error Here _______________ Level 1")
print(error)
} else {
if let d = data {
do {
try d.write(to: self.localSongURL)
} catch {
print("Error Here _______________ Level 2")
print(error)
}
}
}
})
}
Related
I'm making Audio Player.
Importing file from iCloud Drive using .fileImporter.
I get file URL that looks like this: file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/_Storage/Audio-books/%D0%91%D1%80%D0%B5%D0%BD%D0%B4%D1%8F%D1%82%D0%B8%D0%BD%D0%B0/Audiobook.mp3"
Then I pass it to audio player (tried AVPlayer and AVAudioPlayer). Both works on iOS simulator.
On the device after import I get error: The operation couldn’t be completed. (OSStatus error -54.)
I know it's possible, app called Evermusic does quite the same with on device files.
Is there permissions I need to be granted to play audio that stored on device?
How can I access Container for com~apple~CloudDocs?
Thank you very much for help, any suggestions greatly appreciated, I'm seriously stuck.
For future references repo of the project: https://github.com/yaosamo/AudioPlayer
You need to be using startAccessingSecurityScopedResource in order to get read access to those files. See documentation:
https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso
https://developer.apple.com/documentation/corefoundation/1543318-cfurlstartaccessingsecurityscope
In addition to #jnpdx answer want to add some details, and my solution example.
Few core things
✅ for my app if you need to access secured audio you need to use startAccessingSecurityScopedResource()
❌ you can't simple store URL and use it later, in fact you don't store fileURL at all. You need to use what's called bookmarkData() on your URL and store it. So later you can restore URL
✅ Watch Apple pres here
Here's how I import file:
.fileImporter(isPresented: $presentImporter, allowedContentTypes: [.mp3]) { result in
switch result {
case .success(let url):
// Start accessing secured url
let StartAccess = url.startAccessingSecurityScopedResource()
defer {
// Must stop accessing once stop using
if StartAccess {
url.stopAccessingSecurityScopedResource()
}
}
// Creating new book
let newBook = Book(context: viewContext)
let _ = print("---- Access Granted?", url.startAccessingSecurityScopedResource())
// Getting bookmarkData of the URL
let bookmarkData = try? url.bookmarkData()
newBook.name = "\(url.lastPathComponent)"
// Save bookmarkURL into CoreData
newBook.urldata = bookmarkData
// Specifiying parent item in CoreData
newBook.origin = playlist.self
try? viewContext.save()
case .failure(let error):
print(error)
}
}
Player retrieving URL:
func Audioplayer(bookmarkData: Data) {
// Restore security scoped bookmark
var bookmarkDataIsStale = false
let playNow = try? URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &bookmarkDataIsStale)
do {
player = try AVAudioPlayer(contentsOf: playNow!)
// Delegate listen when audio is finished
player?.delegate = del
NotificationCenter.default.addObserver(forName: NSNotification.Name("ended"), object: nil, queue: .main) { (_) in
player?.stop()
ended = true
let _ = print("---- Book has ended ----")
}
} catch let error {
print("Player Error", error.localizedDescription)
}
player?.prepareToPlay()
player?.play()
}
Thank you and again here's repo on git.
I'm trying to download a youtube video on the phone so the user can later play it offline. I hooked a button to where the user can download. The code of how I'm currently downloading the video is here below.
#objc func downloadSelectedVideo() {
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
print(destination)
// check if it exists before downloading it
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
}
as you can see I have hooked up the URL to a free music which is in mp3 and it works fine I can download the music and everything works just fine, however when I try to hook up a YouTube video then it never gets to the print statement file saved I tried this as the URL if let audioUrl = URL(string: "https://www.youtube.com/watch?v=3WSgJCYIewM")
but the print statement file saved never ran, but when I tried with the other I mentioned earlier it print file saved.
What URL should use to download the youtube videos, and do I have to use an mp3 or mp4 source to download the videos. I'm trying not to use any third-party sites if you can come up with any solution it would be great and helpful. Thanks
YouTube spells this out pretty specifically that you cannot do this.
YouTube API Services - Developer Policies
Found this under E. Handling YouTube Data and Content
You and your API Clients must not, and must not encourage, enable, or
require others to:
download, import, backup, cache, or store copies of YouTube
audiovisual content without YouTube's prior written approval,
IANAL, but I'd rather not go up against their legal team.
YouTube does not provide a simple URL that taps into the video or audio directly. Instead, you would have to extract thier link using some algorithm. If you'd like to achieve that I would recommend you use XCDYouTubeKit as it is well maintained and easy to use. However, If you'd like a ready made app, I have developed an open source application - YouTag - that you can simply install on your iOS device and use directly without having to go through developing one.
I have made one demo in which i have to do download multiple video and audio so i found one library from github. But i do not know how to save donwloading progress when i quite app(means when downloading done 50% and i want to save 50% video data in Document directory but when i open app downloading start from initial)
Code which i had used in my demo.
self.progressView.setProgress(0, animated: false)
self.progressLabel.text = "0.0 %"
self.finalUrlLabel.text = ""
let request = URLRequest.init(url: URL.init(string: "http://techslides.com/demos/samples/sample.mp4")!)
let downloadKey = SDDownloadManager.shared.dowloadFile(withRequest: request,
inDirectory: directoryName,
withName: nil,
onProgress: { [weak self] (progress) in
let percentage = String(format: "%.1f %", (progress * 100))
self?.progressView.setProgress(Float(progress), animated: true)
self?.progressLabel.text = "\(percentage) %"
print("percentage",percentage)
}) { [weak self] (error, url) in
if let error = error {
print("Error is \(error as NSError)")
} else {
if let url = url {
print("Downloaded file's url is \(url.path)")
self?.finalUrlLabel.text = url.path
}
}
}
print("The key is \(downloadKey!)")
let dasd = SDDownloadManager.shared.ongoingDownloads
print("dasd",dasd.count)
If you know any other solution please help me via your best experience in downloading progress.
TIA
Edit:
I have used this Link for download For downloading video using url session. But don't know how to resume download when application terminated and open again.
first thing SDDownloadManager says Resumable Downloads not implemented yet in the framework. Its mentioned there as below
Future Enhancements
I'm planning to integrate the following features in upcoming releases :
Background Downloads.
Resumable Downloads.
so you cant achieve this using this. Instead you can use NSUrlSession to
achieve this.
I am using AudioKit to mix WAV files together with MIDI files.
I also need to save the result in a separate file.
To mix the WAVs and MIDIs I am using an AKMIDISampler with an AKSequencer like this:
func add(track: MixerTrack) -> Bool {
do{
let trackSampler = AKMIDISampler()
try trackSampler.loadWav(track.instrument.fileName)
trackSampler.connect(to: mixer)
let sequencer = AKSequencer(filename: track.midi.fileName)
sequencer.setTempo(Double(tempo))
sequencer.setRate(rate)
sequencer.setGlobalMIDIOutput(trackSampler.midiIn)
sequencer.enableLooping()
sequencer.enableLooping()
sequencers.append(sequencer)
tracks.append(track)
return true
} catch {
return false
}
}
I am using the SongProcessor example from AudioKit's examples for ideas on how to use AKOfflineRenderNode.
The thing is the example works with AKAudioPlayer instances and not sequencers as I am using. I believe I cannot use players because I need to mix the WAV and MIDI files, and I was only able to achieve that using sequencers.
My first question is: Is it possible to create files from sequencers the same way it is done in SongProcessor with players?
I was able to save an m4a file but the result is weird. First, if I don't set the rate manually to a number like 40, it is veeery slow to play all the notes. And when I set ti to a value like that,I can hear the sequence playing but at wrong rates. At some moments the beats play correctly but they often start playing too slow or too fast at different times.
Is there something I am doing wrong? Is this a bug with AKOfflineRenderNode or is it just not mean to be used like this?
Here is the code I use to save the mix to disk:
func saveMixToDisk() -> URL? {
do {
let fileManager = FileManager.default
let name = UUID().uuidString.appending(".m4a")
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = documentDirectory.appendingPathComponent(name)
offlineRender.internalRenderEnabled = false
let duration = sequencers.first!.length.seconds
for sequencer in sequencers {
sequencer.stop()
sequencer.setTime(AKDuration(seconds: 0).musicTimeStamp)
sequencer.rewind()
}
for sequencer in sequencers {
sequencer.setRate(40) // I would like to find a way to avoid having to set this, since this value is hardcoded and I don't know how to find the correct one. (When I only play through the sequencer inside the app the rate is perfect, but it gets messed up when rendering to URL)
sequencer.play()
}
try offlineRender.renderToURL(fileURL, seconds: duration * 10)
for sequencer in sequencers {
sequencer.stop()
sequencer.setTime(AKDuration(seconds: 0).musicTimeStamp)
sequencer.rewind()
}
offlineRender.internalRenderEnabled = true
return fileURL
} catch let error {
print(error)
return nil
}
}
Any help is very much appreciated. I can't seem to be able to get this to work, and sadly I don't know of any other options in iOS to achieve what I need.
Instead of using AKOfflineRender, try the new AudioKit.renderToFile in AudioKit 4.0.4: https://github.com/AudioKit/AudioKit/commit/09aedf7c119a399ab00026ddfb91ae6778570176
I think you need to use this method in iOS11
[AudioKit renderToFile:file duration:self->_audioDurationSeconds error:&error prerender:^{
[self.voicePlayer start];
}];
In iOS 8/Xcode 6 I had a function that included a sound effect. It no longer works in iOS 9 after changing the code multiple times. This is what I've tried:
Original:
let bangSoundEffect = SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false)
runAction(bangSoundEffect)
Other attempt:
self.runAction(SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false))
Also:
func playRocketExplosionSound(filename: String) {
let url = NSBundle.mainBundle().URLForResource(
filename, withExtension: nil)
if (url == nil) {
print("Could not find file: \(filename)")
return }
var error: NSError? = nil
do {
backgroundMusicPlayer =
try AVAudioPlayer(contentsOfURL: url!)
} catch let error1 as NSError {
error = error1
backgroundMusicPlayer = nil
}
if backgroundMusicPlayer == nil {
print("Could not create audio player: \(error!)")
return}
backgroundMusicPlayer.numberOfLoops = 1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play() }
playRocketExplosionSound("Bang.mp3")
I'm pulling my hair out. I'm using the same code in a different scene for another sound effect and it works fine!! What's going wrong?
I've noticed that the sound effect begins to play sometimes in the simulator, however it doesn't complete and throws this error:
2015-09-24 19:12:14.554 APPNAME[4982:270835] 19:12:14.553 ERROR: 177: timed out after 0.012s (735 736); mMajorChangePending=0
It doesn't work at all on actual devices.
What is the problem? :'(
Possible problem with MP3 file
The problem is most likely connected with the MP3 file you're using. The code works for other sounds, this suggests that the MP3 file might be corrupted and AVAudioPlayer fails with decoding it. You can try re-encode this file and see if the problem persists. Or, even better, converting it to WAV.
Using WAVs
General rule of the thumb when creating short sound effects for games, is to use WAV unless you really feel you need the trim the fat.
Top-notch games are going for top-of-the-line production quality, so they record and produce assets uncompressed 24bit/48kHz. Titles with slightly lesser ambitions might record and produce in 16/44.1, which is the official standard for CD quality audio.
This has at least two benefits. One is that the sound has a better quality. Second one, the CPU does not have to decode the file to play it.
Corrupt data file | AVAudioPlayer out of scope
1. Corrupt data file
This will ensure you have found the file:
var backgroundMusicPlayer: AVAudioPlayer? = nil
if let url = Bundle.main.url(
forResource: "Bang", withExtension: "mp3") {
do {
try backgroundMusicPlayer = AVAudioPlayer(contentsOf: url)
backgroundMusicPlayer!.play()
} catch {}
}
return nil
2. AVAudioPlayer out of scope
The variable retaining backgroundMusicPlayer must not go out of scope before play() has completed and returns. This is generally achieved by using a class variable:
var backgroundMusicPlayer: AVAudioPlayer? = nil
Don't do this: the following sound will play for, at best, outOfScopeDelay due to the local scope of var audioPlayer.
let outOfScopeDelay = 0.5
do {
var audioPlayer:AVAudioPlayer! // Incorrectly scoped variable
try audioPlayer = AVAudioPlayer(contentsOf: audioRecorder.url)
audioPlayer.play()
Thread.sleep(forTimeInterval: outOfScopeDelay)
} catch {}
► Find this solution on GitHub and additional details on Swift Recipes.
try this:
dispatch_async(dispatch_get_main_queue(), {
(self.playRocketExplosionSound("Bang.mp3")
})
it's no longer safe to play audio in child thread under iOS 9.