phResourceManager.writeData(for... wont run on iOS 14 - ios

Before I updated to iOS 14 on my iPhone, this code was working perfectly. After, iOS 14 this is weirdly not running... it is very odd and I have not seen any solution online, additionally from my investigation, I have not been able to see any change.
This code is used in order to retrieve a videoURL for this video from the imported Camera Roll (I use import Photos...).
phResourceManager.writeData(for: resource.last!, toFile: newURL!, options: resourceRequestOptions) { (error) in
if error != nil {
print(error, "not c67omplted error?")
} else {
print("woah completedd 345?")
newUserTakenVideo.videoURL = newURL
print(newUserTakenVideo.videoURL, "<--?")
}
}
EDIT:
To be clear, it "does not run" means the compleition block never runs... as in it never even runs and gives an error, the compleition block simply never is called (nothing prints at least..)
And here is a print statement printing out all the values I pass in to the parameters:
phResourceManager:
<PHAssetResourceManager: 0x282d352c0>
resource.last:
Optional(<PHAssetResource: 0x28128bc00> {
type: video
uti: public.mpeg-4
filename: v07044090000bu6n1nhlp4leque7r720.mp4
asset: C97B45D3-7039-4626-BA3E-BCA67912A2A9/L0/001
locallyAvailable: YES
fileURL: file:///var/mobile/Media/DCIM/113APPLE/IMG_3404.MP4
width: 576
height: 1024
fileSize: 4664955
analysisType: unavailable
cplResourceType: Original
isCurrent: YES
})
newURL:
Optional(file:///var/mobile/Containers/Data/Application/E2792F47-142E-4601-8D5B-F549D03C9AFE/Documents/Untitled%2027228354.MP4)
resourceRequestOptions:
<PHAssetResourceRequestOptions: 0x28230d480>
Note: this is the decleration for the resource variable:
let resource = PHAssetResource.assetResources(for: (cell?.assetPH)!)

I have a solution to this! Swift 4+, tested on iOS 14!
I looked through using a PHAssetResourceRequest, but the file names were messed with in the process, and it generally didn't work with my sandbox. Then I also tried requesting a AVPlayerItem from the PHAsset but this too, did not work with sandboxing...
But then, I tried simply using PHAssetResourceManager.default().writeData(... and seemingly started working!
I tested a bit more and seemed to work, here is the full code:
let resource = PHAssetResource.assetResources(for: (cell?.assetPH)!)
let resourceRequestOptions = PHAssetResourceRequestOptions()
let newURL = ExistingMediaVC.newFileUrl
PHAssetResourceManager.default().writeData(for: resource.last!, toFile: newURL!, options: resourceRequestOptions) { (error) in
if error != nil {
print(error, "error")
} else {
print("good")
newUserTakenVideo.videoURL = newURL
}
}
It is quite simple!! Tell me if anything is not working, and note I still use the ExisitingMedia.fileURL variable you used in your original code as well :)

Related

AVPlayer / AVAudioPlayer - Play audio fileURL from iCloud Drive (work on simulator but not iPhone) + (OSStatus error -54.)

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.

Why is url.bookmarkData returning nil?

We have an implementation with the UIDocumentPickerViewController that looks something like this:
case .openInitialization:
// Setup UIDocumentPicker.
if #available(iOS 14, *) {
documentsPicker = UIDocumentPickerViewController(forOpeningContentTypes: [
UTType.text,
UTType.utf8PlainText,
UTType.flatRTFD,
UTType.pdf])
} else {
documentsPicker = UIDocumentPickerViewController(documentTypes: [
String(kUTTypeText),
String(kUTTypeUTF8PlainText),
String(kUTTypeFlatRTFD),
String(kUTTypePDF)], in: .open)
}
Everything works great and we can select a document. When we select a document we get a document url but in some cases (especially with one drive) we get issues when we want to turn the url into a bookmark. Following code returns nil:
guard let bookmark = try? url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) else { return }
Do anyone have an idea to why this is happening? Or what we can do to get it to work without returning nil?
Edit:
We've tryed to add try catch and we got following error which doesn't quite help much: Error Domain=NSCocoaErrorDomain Code=260 (file doesn't exist).
Edit 2:
So if I open from archive directly into our app it works no issues at all. But we still need to work from UIDocumentPickerViewController.
Also for some reasons files unlocked this way will just work from UIDocumentPickerViewController afterward.
Files can also be opened from onedrive and from there be opened in another app (ours). But this does't and gives a file does not exist error as well.
Edit 3:
So I've tested and read a ton. I can tell that following will return false for some files picked by documentpicker:
var exist = FileManager.default.fileExists(atPath: url.path)
But again if I open the file just once from iOS archive app it will work perfectly fine afterward. If there just were some way to tell it to update/download like apples does.
Edit 4:
I've made a sample project demonstrating the problem at github .
I answered a similar question here: PDFKit doesn’t work on iPads while works fine on a simulator [iOS, Swift]
Can you check if wrapping your url in a security access scope helps?:
url.startAccessingSecurityScopedResource()
print(FileManager.default.fileExists(atPath: url.path))
url.stopAccessingSecurityScopedResource()
The above should print true. This is because these API's access files outside of the applications sandbox.
See: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
Used a technical ticket for apple and they came with a solution :D
NSFileCoordinator().coordinate(readingItemAt: url, options: .withoutChanges, error:&err, byAccessor: { (newURL: URL) -> Void in
do {
let bookmark = try newURL.bookmarkData()
} catch let error {
print("\(error)")
}
})
if let err = err {
print(err.localizedDescription)
}

QLThumbnailGenerator starts failing when called multiple times (on actual device) iOS 13

I am trying to create thumbnail images of multiple Wallet Passes (.pkpass) by running a loop over all (around 200) passes in a specific folder and calling generateBestRepresentation(for:) for each of them.
This is the code:
let passesDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Passes")
let size = CGSize(width: 1600, height: 1600)
let scale = UIScreen.main.scale
if let passURLs = try? FileManager.default.contentsOfDirectory(
at: self.passesDirURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
),
!passURLs.isEmpty {
for passURL in passURLs {
// Create the thumbnail request.
let request = QLThumbnailGenerator.Request(
fileAt: passURL,
size: size,
scale: scale,
representationTypes: .thumbnail
)
// Retrieve the singleton instance of the thumbnail generator and generate the thumbnails.
let generator = QLThumbnailGenerator.shared
generator.generateBestRepresentation(for: request) { thumbnail, error in
if let error = error as? QLThumbnailError {
print ("Thumbnail generation error: \(error)")
print ("Thumbnail generation localizedDescription: \(error.localizedDescription)")
print ("Thumbnail generation errorUserInfo: \(error.errorUserInfo)")
print ("Thumbnail generation errorCode: \(error.errorCode)")
} else {
print ("Thumbnail generation OK")
//do something with thumbnail here
}
}
}
}
This works fine on the simulator, but on an actual device (iPhone Xs Max) sooner or later I start getting errors and the thumbnail generation fails for a big fraction of the passes. The output looks as follows:
Thumbnail generation error: related decl 'e' for QLThumbnailError(_nsError: Error Domain=QLThumbnailErrorDomain Code=3 "No thumbnail in the cloud for file:///private/var/mobile/Containers/Data/Application/DCF703F7-9A1A-4340-86EB-42579D678EEF/Documents/Passes/pass123.pkpass" UserInfo={NSErrorFailingURLKey=file:///private/var/mobile/Containers/Data/Application/DCF703F7-9A1A-4340-86EB-42579D678EEF/Documents/Passes/pass123.pkpass})
Thumbnail generation localizedDescription: The operation couldn’t be completed. (QLThumbnailErrorDomain error 3.)
Thumbnail generation errorUserInfo: ["NSErrorFailingURLKey": file:///private/var/mobile/Containers/Data/Application/DCF703F7-9A1A-4340-86EB-42579D678EEF/Documents/Passes/pass123.pkpass]
Thumbnail generation errorCode: 3
The error description sounds confusing ("No thumbnail in the cloud for file") as these are not iCloud files.
As the error does not occur when calling the thumbnail generation individually, this seems to be some memory/performance issue. I tried to workaround in many ways, including using a semaphore in the for loop waiting for the completion of one call of generateBestRepresentation to start the next call, which reduced but not eliminated the issue. The only way it worked without error was adding a very long sleep (5 seconds) after the semaphore.wait() statement, but this is no acceptable solution.
Another way I tried was using saveBestRepresentation (as suggested in Apple's documentation), but this did not solve the issue.
Has anyone faced a similar issue and was able to find an acceptable solution?

Audio downloaded from Firebase doesn't make noise Swift 4

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)
}
}
}
})
}

AVAudioPlayer sound not playing

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.

Resources