FileManager cannot find audio file - ios

Helloo! My ultimate goal is to create a UITableViewController with recordings made in-app, using FileManager to traverse the /Documents/ directory and list all the recordings found there.
The recording and playback are functioning just fine with one recording, with the following setup:
// In VC#1
func setupRecorder(){
let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
print("Audio Setup Error: \(error)")
}
do {
try audioSession.setActive(true)
} catch {
print("Audio Setup Error: \(error)")
}
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFileName = "test.caf"
audioFileUrl = documentsDirectory.appendingPathComponent(audioFileName)
print("\n\nrecording url :\(audioFileUrl.absoluteString)\n\n")
let recordSettings = [
AVFormatIDKey: Int(kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
] as [String : Any]
do {
audioRecorder = try AVAudioRecorder(url: audioFileUrl, settings: recordSettings)
} catch let error as NSError{
print("Audio Setup Error: \(error)")
audioRecorder = nil
}
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == GraphViewController.getEntrySegue() {
let navController = segue.destination as! UINavigationController
let destination = navController.topViewController as! GraphViewController
destination.audioFileName = audioFileName
destination.audioFileUrl = audioFileUrl
}
// In VC#2
func setupAudioPlayer() {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
print(error)
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFileUrl)
} catch {
print("Audio Player Setup Error: \(error)")
}
audioPlayer.prepareToPlay()
}
This is working all fine and dandy; it plays the recorded audio file with no problems whatsoever. However, when I try to find the file via FileManager, FileManager cannot find it. Listed below are my attempts to find this file.
print("\(FileManager.default.fileExists(atPath: audioFileUrl.absoluteString))") // -> False
print("\(FileManager.default.contents(atPath: audioFileUrl.absoluteString))") // -> nil
Clearly I must be missing something... especially given AVAudioPlayer is successfully reading the audio file with the exact same url. The path prints as:
file:///var/mobile/Containers/Data/Application/1DA6BD0F-5A23-4228-92A9-A083E55ACE21/Documents/test.caf
Any ideas? šŸ§šŸ¤”
Many thanks!
EDIT: Before storing the url as a property, I was doing the following, recalculating the documentsDirectory in VC#2 however was also not succesfull:
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let results = try? FileManager.default.contentsOfDirectory(atPath: documentsDirectory.absoluteString)

You cannot save a sandboxed file URL or path string for later use, because the sandbox can move. You need to do what you were doing before, successfully: calculate the documents directory URL and then examine it. You should work entirely with URLs and never call absoluteString at all; it is the wrong call entirely and is letting you down here.
In this example, I check the documents directory for any files whatever:
let documentsDirectory =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let results =
try? FileManager.default.contentsOfDirectory(at: documentsDirectory,
includingPropertiesForKeys: nil)

Related

Best Implementation of .wav Audio Recording in Swift

Thanks in advance for your help,
I have been able to record in .m4a format for a while. Unfortunately, this project I'm working on needs to be recorded in .wav. I have been searching for a way of recording in .wav but I've only been able to find resources on converting to .wav after recording in .m4a. Is there a way of recording specifically in .wav?
The code I've pasted is my recording service. In settings specifically, I pass an audio format type. I haven't been able to have this type be of '.wav'.
import Foundation
import Combine
import AVFoundation
class AudioRecorder: NSObject, ObservableObject {
override init() {
super.init()
fetchRecordings()
}
let objectWillChange = PassthroughSubject<AudioRecorder, Never>()
var audioRecorder: AVAudioRecorder!
var recordings = [Recording]()
var recording = false {
didSet {
objectWillChange.send(self)
}
}
func startRecording(taskNum: Int) {
let recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
} catch {
print("Failed to set up recording session")
}
let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let audioFilename = documentPath.appendingPathComponent("\(Date().toString(dateFormat: "dd-MM-YY_'at'_HH:mm:ss"))-task_\(taskNum).m4a")
let settings = [
// Change to kAudioFileWAVEType from kAudioFormatMPEG4AAC for .wav files?
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.record()
recording = true
} catch {
print("Could not start recording")
}
}
func stopRecording() {
audioRecorder.stop()
recording = false
fetchRecordings()
}
func fetchRecordings() {
recordings.removeAll()
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryContents = try! fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil)
for audio in directoryContents {
let recording = Recording(fileURL: audio, createdAt: getCreationDate(for: audio))
recordings.append(recording)
}
recordings.sort(by: { $0.createdAt.compare($1.createdAt) == .orderedAscending})
objectWillChange.send(self)
}
func getCreationDate(for file: URL) -> Date {
if let attributes = try? FileManager.default.attributesOfItem(atPath: file.path) as [FileAttributeKey: Any],
let creationDate = attributes[FileAttributeKey.creationDate] as? Date {
return creationDate
} else {
return Date()
}
}
func deleteRecording(urlsToDelete: [URL]) {
for url in urlsToDelete {
print(url)
do {
try FileManager.default.removeItem(at: url)
} catch {
print("File could not be deleted!")
}
}
fetchRecordings()
}
}

Saving video in locally (directory) in Swift?

I try to save given video locally after then I need those saved videos for playing video in my app. I can't handle the saving video. Here is my saving try :
func saveVideoDocumentDirectory(url : URL){
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(".MOV")
do{
let videoData = try Data(contentsOf: url)
fileManager.createFile(atPath: paths as String, contents: videoData, attributes: nil)
}catch{
//
}
}
here is the get file try
func getVideo(){
let fileManager = FileManager.default
let videoPAth = (self.getDirectoryPath() as NSString).appendingPathComponent(".MOV")
if fileManager.fileExists(atPath: videoPAth){
print(videoPAth)
play(url: URL(string: videoPAth)!)
}else{
print("No Video")
}
}
here is my play video func :
func play(url : URL)
{
let player = AVPlayer(url: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true)
{
playerViewController.player!.play()
}
}
Instead of Filemanager.createFile(), try using write instead.
let videoData = try Data(contentsOf: url)
try videoData.write(to: paths, options: .atomic)
Also, I recommend creating a folder first (from this answer).
extension URL {
static func createFolder(folderName: String) -> URL? {
let fileManager = FileManager.default
// Get document directory for device, this should succeed
if let documentDirectory = fileManager.urls(for: .documentDirectory,
in: .userDomainMask).first {
// Construct a URL with desired folder name
let folderURL = documentDirectory.appendingPathComponent(folderName)
// If folder URL does not exist, create it
if !fileManager.fileExists(atPath: folderURL.path) {
do {
// Attempt to create folder
try fileManager.createDirectory(atPath: folderURL.path,
withIntermediateDirectories: true,
attributes: nil)
} catch {
// Creation failed. Print error & return nil
print(error.localizedDescription)
return nil
}
}
// Folder either exists, or was created. Return URL
return folderURL
}
// Will only be called if document directory not found
return nil
}
}
Then, you can save like this:
guard let folderURL = URL.createFolder(folderName: "StoredVideos") else {
print("Can't create url")
return
}
let permanentFileURL = folderURL.appendingPathComponent(nameOfYourFile).appendingPathExtension("MOV")
let videoData = try Data(contentsOf: url)
try videoData.write(to: permanentFileURL, options: .atomic)
This will save you the hassle of NSSearchPathForDirectoriesInDomains.

Audio Stream via firebase

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

Swift 3 Record audio and upload to Firebase storage and play back

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").

Xcode 8.0 beta3 AVAudioRecorder(url,setting) error catch - "Error Domain=NSOSStatusErrorDomain Code=-50 "(null)""

I'm trying to record the voice of the user and change it to a text data. I used AVAudioRecorder to record the sound and SpeechKit to change it to text which is included in iOS 10. When user touches down the button record starts and stops when button is touched up. But when I initialize the AVAudioRecorder using do catch syntax, error occurs and fails.
I added the appropriate frameworks(Speech, AVFoundation).
import UIKit
import Speech
import AVFoundation
class SearchViewController: UIViewController, CLLocationManagerDelegate, AVAudioRecorderDelegate {
var audioRecorder = AVAudioRecorder()
let recordSettings = [AVSampleRateKey : String(NSNumber(value: Float(44100.0))),
AVFormatIDKey : String(kAudioFileCAFType),
AVNumberOfChannelsKey : String(NSNumber(value: 2))]
#IBAction func recordButtonDown(_ sender: AnyObject) {
print("recordButtonDown")
self.audioPlayer.play()
sleep(1)
let fileManager = FileManager.default
let paths = fileManager.urlsForDirectory(.documentDirectory, inDomains: .userDomainMask)
var audioURL = paths[0] as NSURL
audioURL = audioURL.appendingPathComponent("soundForMapSearch.caf", isDirectory: false)!
do {
self.audioRecorder = try AVAudioRecorder(url: soundFileURL as URL, settings: self.recordSettings)
self.audioRecorder.delegate = self
self.audioRecorder.prepareToRecord()
self.audioRecorder.record()
} catch (let error) {
print("Error: \(error)")
}
}
#IBAction func recordButtonUp(_ sender: AnyObject) {
self.audioRecorder.stop()
}
override func viewDidLoad() {
super.viewDidLoad()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setActive(true)
audioSession.requestRecordPermission({ (recordPermission) in
})
} catch {
print("record initiallizing failed")
}
And on the line
self.audioRecorder = try AVAudioRecorder(url: soundFileURL as URL, settings: self.recordSettings)
error occurs at catch
Error: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"
is printed. I searched for this error and code=-50 means the NSURL object is invalid. How can I solve this error?
my working code
let recordingName = "recording1" + ".m4a"
let pathArray = [dirPath, recordingName]
print(pathArray)
let filePath = NSURL.fileURL(withPathComponents: pathArray)
print(filePath)
do{
let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
assertionFailure("AVAudioSession setup error: \(error)")
}
let recordSettings: [String: AnyObject] = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 1,
]
try! audioRecorder = AVAudioRecorder(url: filePath!, settings: recordSettings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()

Resources