I am facing a weird problem with an .m4a sound file on an iOS app written in Swift.
Here is what happens:
I record a sound file on the device using the app. Let us call it xyz.m4a.
I check that I can play this sound file, and it is working.
I then upload the sound file to an AWS S3 bucket, from my app.
I check on the AWS console that the file is uploaded as expected and I download it using a browser (on my computer), checking again that I can still play the sound as expected. Up to this point nothing is wrong, I can play the sound.
I then use my app to download the file to my iOS device (iPhone 6). All seems OK here too.
Finally I try to play the downloaded file from within my app and this is where things go wrong. I cannot play it.
Here is what I think is the relevant code, please let me know if you think I should add more.
Code to download the file from the AWS S3 bucket:
let transferUtility = AWSS3TransferUtility.default()
transferUtility.downloadData(
fromBucket: "mybucket",
key: "xyz.m4a",
expression: expression,
completionHandler: completionHandler
).continueWith {
(task) -> AnyObject! in if let error = task.error {
print("Error: \(error.localizedDescription)")
}
if let _ = task.result {
// Do something with downloadTask.
print("task.result -- OK!")
let downloadOutput = task.result
print("downloadOutput:\(String(describing: downloadOutput))")
}
return nil;
}
Here is the completionHandler part:
var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?
completionHandler = {
[weak self] (task, URL, data, error) -> Void in
DispatchQueue.main.async(execute: {
print("transfer completion OK!")
let localFileName = "xyz.m4a",
playFileURL = self?.getDocumentsURL().appendingPathComponent(localFileName)
FileManager.default.createFile(atPath: (playFileURL?.path)!,
contents: data,
attributes: nil)
if FileManager.default.fileExists(atPath: (playFileURL?.path)!) {
print("playFileURL present!") // Confirm that the file is here!
}
})
}
Code to play the downloaded sound file:
let fileName = "xyz.m4a",
audioFile = getDocumentsURL().appendingPathComponent(fileName)
if !FileManager.default.fileExists(atPath: audioFile.path) {
print("We have no file !!!!!") // Just in case the file was not found!
}
// This code assumes the file exists.
do {try soundSession.setCategory(AVAudioSessionCategoryPlayback)
try soundSession.setActive(true)
if audioPlayer == nil {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile,
fileTypeHint: AVFileType.m4a.rawValue)
guard let _ = audioPlayer else {return}
audioPlayer.delegate = self
}
audioPlayer.play()
} catch let error {
print("Error:\n" + error.localizedDescription)
}
Finally here is the error I get:
Error:
The operation couldn’t be completed. (OSStatus error 1685348671.)
I hope someone can see something wrong an tell me where the problem is.
Related
I tried to create AVAudioPlayer like this
let url = URL(string: "https://storage.googleapis.com/preview-public/project/e45d3194bb7f4768984fd53acc833600/fa13293c27314b448a815ebd42176684/audio-gnvY.m4a")!
do {
let player = try AVAudioPlayer(contentsOf: url)
} catch (let err) {
print("err", err)
}
I get this error: Error Domain=NSOSStatusErrorDomain Code=2003334207 "(null)".
I also tried
do {
let _ = try url.checkResourceIsReachable()
} catch (let err) {
print("err", err)
}
and I get this error: Domain=NSCocoaErrorDomain Code=262 "The file couldn’t be opened because the specified URL type isn’t supported.".
I can't figure out what could possibly be wrong with the url.
Images load normally from same bucket although if I check checkResourceIsReachable() on image URL it returns same error. (But I load image with CGImageSourceCreateWithURL(url as CFURL which might not have same checks as AVAudioPlayer.) Anyway I'm lost. Any ideas?
AVAudioPlayer is for local file: URLs only. It doesn’t accept remote URLs, which would need to be streamed. For that, use AVPlayer.
i have demo in try to run this code audio is working perfect
let audiourl = URL(string: "https://storage.googleapis.com/preview-public/project/e45d3194bb7f4768984fd53acc833600/fa13293c27314b448a815ebd42176684/audio-gnvY.m4a")
let player = AVPlayer(url: audiourl!)
let playViewController = AVPlayerViewController()
playViewController.player = player
self.present(playViewController, animated: true){
playViewController.player!.play()
}
In AVAudioPlayer, URL is the path to a local file.
https://developer.apple.com/documentation/foundation/url
Works perfectly fine on iOS 12.
Simple boilerplate code:
let storageRef = storage.reference().child("\(profile.studioCode)/\(selected.classId)/\(uploadDate)")
//Upload file and metadata
let uploadTask = storageRef.putFile(from: videoURL, metadata: metadata)
//Listen for state changes and, errors, and completion of the upload
uploadTask.observe(.resume) { (snapshot) in
//upload resumed or started
}
uploadTask.observe(.pause) { (snapshot) in
//upload paused
}
uploadTask.observe(.progress) { (snapshot) in
//upload progress
}
uploadTask.observe(.success) { (snapshot) in
//upload successful
}
uploadTask.observe(.failure) { (snapshot) in
//upload failed
}
Gives me:
Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response."
I've updated Cocoapods and Firebase to the newest versions, tried allowing arbitrary loads, and tried signing out and back into the app to reset my auth token. In iOS 13 it throws that error immediately on upload, but on iOS 12 it uploads perfectly fine. Any help or insight would be greatly appreciated. Thanks!
I had a similar issue but here is an easy workaround: You need to use '.putData' instead of '.putFile' and specify the MIME type on upload.
let metadata = StorageMetadata()
//specify MIME type
metadata.contentType = "video/quicktime"
//convert video url to data
if let videoData = NSData(contentsOf: videoURL) as Data? {
//use 'putData' instead
let uploadTask = storageRef.putData(videoData, metadata: metadata)
}
How I ended up fixing it:
It turns out that file paths are different in iOS 13 than iOS 12:
iOS12 path:
file:///private/var/mobile/Containers/Data/Application/DF9C58AB-8DCE-401B-B0C9-2CCAC69DC0F9/tmp/12FD0C43-F9A0-4DCB-96C3-18ED83FED424.MOV
iOS13 path:
file:///private/var/mobile/Containers/Data/PluginKitPlugin/5DFD037B-AC84-463B-84BD-D0C1BEC00E4C/tmp/trim.7C8C6CD1-97E7-44D4-9552-431D90B525EA.MOV
Note the extra '.' in the iOS13 path. My solution was to, inside of my imagePickerController didFinishPickingMediaWithInfo function, copy the file into another temp directory, upload it from there, and then delete the copy.
do {
if #available(iOS 13, *) {
//If on iOS13 slice the URL to get the name of the file
let urlString = videoURL.relativeString
let urlSlices = urlString.split(separator: ".")
//Create a temp directory using the file name
let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let targetURL = tempDirectoryURL.appendingPathComponent(String(urlSlices[1])).appendingPathExtension(String(urlSlices[2]))
//Copy the video over
try FileManager.default.copyItem(at: videoURL, to: targetURL)
picker.dismiss(animated: true) {
self.videoRecorded = false
self.showUpload(targetURL)
}
}
else {
//If on iOS12 just use the original URL
picker.dismiss(animated: true) {
self.videoRecorded = false
self.showUpload(videoURL)
}
}
}
catch let error {
//Handle errors
}
I want to implement rich media in Notification Service Extension, my code works fine for images. but when I pass an url for some 4 mb mp4 file it crashes. I've tested several methods for downloading and saving that file and realized that I get crash when trying to store downloaded mp4 as data. this is my code for downloading media:
private func downloadMedia(mediaURL: String, completionHandler: #escaping (URL) -> ()) {
guard let url = URL(string: mediaURL) else {
return
}
let mediaExtension = url.pathExtension
// mp4
if mediaExtension.isEmpty {
fatalError()
}
let supportedRichMediaTypes = ["aiff", "wav", "mp3", "mp4", "jpg", "jpeg", "png", "gif", "mpeg", "mpg", "avi", "m4a", "m4v"]
guard supportedRichMediaTypes.contains(mediaExtension) else {
fatalError()
}
let fileName = self.randomString(length: 10) + ".\(mediaExtension)"
guard let cashesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError()
}
let localURL = cashesURL.appendingPathComponent(fileName)
// approach 1 for downloading mp4 file
let dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlResponse, error) in
print("\(#line)") // can't reach this line and I get crash without printing this
guard let data = data else {
fatalError()
}
do {
try data.write(to: localURL)
DispatchQueue.main.async {
completionHandler(localURL)
}
} catch {
fatalError()
}
})
dataTask.resume()
// approach 2 for downloading mp4 file
let downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { (downloadedURL, urlResponse, error) in
if let downloadedURL = downloadedURL {
do {
let data = try Data(contentsOf: downloadedURL) // in this approach this line crashes the code and the bottom print not executes
print("\(#line):<\(data.base64EncodedString())>")
} catch {
print("\(#line):<\(error.localizedDescription)>")
}
}
})
downloadTask.resume()
}
in both cases the crash message in console is:
Message from debugger: Terminated due to memory issue
Program ended with exit code: 0
and the fatal error is this:
Thread 5: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=12 MB, unused=0x0)
I will appreciate any idea or suggestion about what I'm doing wrong.
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.
Now that AssetsLibrary has been deprecated, we're supposed to use the photos framework, specifically PHPhotoLibrary to save images and videos to a users camera roll.
Using ReactiveCocoa, such a request would look like:
func saveImageAsAsset(url: NSURL) -> SignalProducer<String, NSError> {
return SignalProducer { observer, disposable in
var imageIdentifier: String?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let changeRequest = PHAssetChangeRequest.creationRequestForAssetFromImageAtFileURL(url)
let placeholder = changeRequest?.placeholderForCreatedAsset
imageIdentifier = placeholder?.localIdentifier
}, completionHandler: { success, error in
if let identifier = imageIdentifier where success {
observer.sendNext(identifier)
} else if let error = error {
observer.sendFailed(error)
return
}
observer.sendCompleted()
})
}
}
I created a gif from a video using Regift and I can verify that the gif exists inside my temporary directory. However when I go save that gif to the camera roll, I get a mysterious error: NSCocoaErrorDomain -1 (null), which is really super helpful.
Has anyone ever experienced this issue?
You can try this.
let data = try? Data(contentsOf: /*Your-File-URL-Path*/)
PHPhotoLibrary.shared().performChanges({
PHAssetCreationRequest.forAsset().addResource(with: .photo, data: data!, options: nil)
})