I am trying to prefetch video files with format m3u8 URL ending.
I run this code:
private func prefetch(for url: URL?) {
guard let url = url else { return nil }
let asset = assets[url] ?? AVAsset(url: url)
asset.loadValuesAsynchronously(forKeys: ["isPlayable", "tracks", "hasProtectedContent"], completionHandler: {
print("asset: \(url.relativeString) tracks: \(asset.tracks.count)")
})
assets[url] = asset // save to memory cache
}
Basically, it prints 0 tracks always in the asset.track.count print.
I would expect it to print 1-3 as it is the amount of tracks these videos have, and I do not see the performance in video playing is improved using this code.
Is there anything else I am missing to get those tracks prefetched?
Related
I have an array of image data(in bytes), and I want to upload them to storage and get the download URL for all of them. But after uploading lots of pictures, there is only one file appears in the storage under the reference. I do get the download URLs, but only the last one works, the rest all say access denied.
I suspect it is because I am putting all the pictures under the same storage reference and the picture substitutes every time. But what can I do to upload multiple images?
My upload code:
var picturesURL: [String] = [] // I want to put the download URL in this array
if self.imageButton.isSelected{
picturesURL = []
for photoData in self.pictures{ // pictures is the array that stores all the photo data
self.storage.child("images/file.png").putData(photoData, metadata: nil) { _, error in
guard error == nil else{
print("Failed to upload")
return
}
self.storage.child("images/file.png").downloadURL (completion: { url, error in
guard let url = url, error == nil else{
print("problems getting url")
return
}
let urlString = url.absoluteString
picturesURL.append(urlString)
print(urlString)
//UserDefaults.standard.set(urlString, forKey: "url")
})
}
}
Following the official documentation by Apple link
self.playerStatusObserver = player.observe(\.currentItem?.status, options: [.new,.old]{ (player, change) in
switch (player.status) {
case .readyToPlay:
player.appliesMediaSelectionCriteriaAutomatically = false
for characteristic in player.currentItem!.asset.availableMediaCharacteristicsWithMediaSelectionOptions {
print("\(characteristic)")
// Retrieve the AVMediaSelectionGroup for the specified characteristic.
if let group = player.currentItem!.asset.mediaSelectionGroup(forMediaCharacteristic: characteristic) {
// Print its options.
for option in group.options {
print(" Option: \(option.displayName)")
}
}
}
player.currentItem!.asset.availableMediaCharacteristicsWithMediaSelectionOptions has no items. Tried many cases, but player.currentItem?.tracks has only two tracks (video and audio), even if stream has more audio tracks.
How to select audio tracks and subtitles from a m3u8 stream, using standard AVPlayer and AVFoundation framework?
Your code relies on the current item being ready to play to access availableMediaCharacteristics. It could happen that availableMediaCharacteristics are actually still not available at that moment unless you created the player item with the property automaticallyLoadedAssetKeys parameter:
AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: ["availableMediaCharacteristicsWithMediaSelectionOptions"])
You can find further info about this on the following page:
https://developer.apple.com/documentation/avfoundation/avplayeritem/1388633-automaticallyloadedassetkeys
Depending on your use case you could also prefer loading the media characteristics asynchronously, you could do this with the following code:
func preloadAssetKeys() {
let key = "availableMediaCharacteristicsWithMediaSelectionOptions"
asset.loadValuesAsynchronously(forKeys: [key]) {
[weak self] in
guard let self = self else { return }
var error: NSError? = nil
switch self.asset.statusOfValue(forKey: key, error: &error) {
case .loaded:
// ℹ️ At this point is when availableMediaCharacteristics are actually available
()
default:
()
}
}
}
There is a line in multiple files via the Internet. I want to avoid download the entire file. Each file may be long or short. The required line is usually about line 15 - always different, but always within the first 500 bytes.
Is there a way I can get just the top part of a remote file?
I can then use a regex pattern to find the required line.
Although I know how to download a file in a temp. location and copy it to a proper location, I think that process is too much and wasteful.
This is an example:
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
PS. It would also be helpful if you could include a method to find a line beginning with, say "abc=xyz", where I want "xyz".
To only download partial content, you need a server that supports sending ranges. To test this, make a HEAD request and check if the server responds with an Accept-Range: bytes header. If so, you can request partial content by adding a header like Range: bytes=0-499 to your GET requests to only receive the initial 500 bytes.
EDIT: Additional information added at the bottom
I have a sandboxed, document based application that loads a user selected quicktime movie into an AVPlayer, and everything was working perfectly.
Now I am upgrading the code so that it will use Security Scoped bookmarks to get the URL rather than just storing a URL string so that the persistent store will allow the movie to be loaded upon relaunch of the application. When the bookmark is created it is stored in a Data variable of a managed object.
For some reason, this has broken the AVPlayer. While I have created a bookmark from the user selected URL, and can resolving the URL from the bookmark when the application is relaunched, the movie is not getting loaded into the AVPlayer correctly and I can't figure out why... I have confirmed that the URL being resolved from the bookmark does point to the movie file.
I have also added the appropriate entitlements to the project.
Here is my code:
Function Where User Selects a Movie To Load and Bookmark is Created
#IBAction func loadMovie(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.title = "Select Video File To Import"
openPanel.allowedFileTypes = ["mov", "avi", "mp4"]
openPanel.begin { (result: NSApplication.ModalResponse) -> Void in
if result == NSApplication.ModalResponse.OK {
self.movieURL = openPanel.url
self.player = AVPlayer.init(url: self.movieURL!)
self.setupMovie()
if self.loadedMovieDatabase.count > 0 {
print("Movie Object Exists. Adding URL String")
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
} else {
print("No Movie Object Exists Yet. Creating one and adding URL String")
let document = NSDocumentController.shared.currentDocument as! NSPersistentDocument
let myManagedObjectContext = document.managedObjectContext!
let newMovie = NSEntityDescription.insertNewObject(forEntityName: "Movie", into: myManagedObjectContext) as! MovieMO
self.loadedMovieDatabase.append(newMovie)
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
}
// create Security-Scoped bookmark - Added 2/1/18
do {
try self.loadedMovieDatabase[0].bookmark = (self.movieURL?.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil))!
} catch {
print("Can't create security bookmark!")
}
}
}
}
Function where Bookmark is Resolved into URL and Movie is Loaded
// initialize AVPlayer with URL stored in coreData movie object if it exists and is a valid path
if loadedMovieDatabase.count > 0 {
// initialize with saved movie path if it is valid (from security bookmark data)
// let myURL = URL(string: loadedMovieDatabase[0].urlString!) <- replaced with new code below
print("Loading URL from Bookmark")
var urlResult = false
var myURL : URL
do {
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStale: &urlResult)!
print("URL Loaded from Bookmark")
print("URL is", myURL)
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
print("IsSecured = ", isSecuredURL)
player = AVPlayer.init(url: myURL)
print("Setting Up Movie")
setupMovie()
} catch {
// No Data in bookmark so load default ColorBars movie instead
print("No Security Bookmark Available. Reverting to Default Color Bars")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
} else {
// load default ColorBars movie instead
print("Nothing was loaded so just set up a new document.")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
I am new to Security-Scoped Bookmarks, so I'm hoping that this may be obvious to anyone who has worked with them before.
I'm wondering if it's a problem with:
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
Perhaps I'm calling this incorrectly? Sometimes I find Apple's documentation to be vague and confusing... Any insight would be appreciated!
EDIT:
I believe I know why this is happening, but I'm not sure how to fix it...
myURL.startAccessingSecurityScopedResource()
always returns FALSE... per the documentation that would mean that it's not working. Additionally, while the movie file is located on my Desktop, the Resolved URL comes up as the following (this may be normal, I don't know.):
file:///Users/me/Library/Containers/myapp/Data/Desktop/sample_on_desktop.mov
The apple docs make reference to the fact that a Document Scope can not use files in the system (aka "/Library"), but my entitlements are setup to use application-scope bookmarks, and my bookmark was created using the nil flag for relativeURL: so this shouldn't be an issue.
I just stumbled upon the answer accidentally...
For starters, when I was resolving the URL, I was not using the method which allows you to include OPTIONS, so my URL was resolved WITHOUT the security-scope. My original code to resolve was:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStable: &urlResult)!
When I should have been using the version with options here:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, Options: URL.bookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStable: &urlResult)!
Basically, I used the first init option Xcode presented in the predictive list with the words "resolvingBookmarkData:" when I should have looked further down the list. (This is how I found my error.)
NOTE also that it's important to use...
URL.bookmarkResolutionOptions.withSecurityScope
and not
URL.bookmarkCreationOptions.withSecurityScope
...when you're resolving your URL or it doesn't appear to work correctly.
Thus ends my frustration with this problem :) I hope this explanation might help others facing this problem!
how can i keep a track or record of already downloaded files in alamofire swift 2.1 so that i don t have to download the same file again ? do we have any native method for that provided by alamofire or we have to do a check before downloading any file on our directory if we already have file with that name there ???? i'm confused on how to accomplish this with a proper approach
if anybody would clear my confusion about this then it'll be so helpful for me thanks
UPDATE:
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileUrl = documentsURL.URLByAppendingPathComponent(suggestedFileName)
print(fileUrl)
if !(NSFileManager.defaultManager().fileExistsAtPath(fileUrl.path!)){
self.suggestedFileName = (self.request?.response?.suggestedFilename)! // here how can i get the suggested download name before starting the download preocess ???
print("\(destination)")
request = Alamofire.download(.GET, "http://contentserver.adobe.com/store/books/GeographyofBliss_oneChapter.epub", destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes read on main queue: \(totalBytesRead)")
self.progressView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
}
}
.response { _, _, _, error in
if let error = error {
print("Failed with error: \(error)")
} else {
print("Downloaded file successfully")
}
}
}else {
print("file already exists")
}
in the above update am trying to get the suggestedFileName which is generated by alamofire but there's one problem when am trying to get sugestedFileName like this : suggestedFileName = (request?.response?.suggestedFilename)! in viewdidload am getting a null exception off course because there's no suggestedFileName because download not yet started so my question is that how can i get the suggestedFileName from response before starting the download ??
According to the docs https://github.com/Alamofire/Alamofire#downloading, you can download to a file. If your file destinations names are predictable, you could simply check to see if the contents of the file exists. For example if your are downloading data:
if let data = NSData(contentsOfURL: yourDestinationURL) {
//Do your stuff here
}
else {
//Download it
}
If you want consistency between names I suggest you avoid the Alamofire suggested destination and do this instead:
let path = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0] as NSURL
let newPath = path.URLByAppendingPathComponent(fileName)
Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: { _ in
newPath //You have to give the destination in this closure. We could say 'return newPath' instead, they're the same thing.
})
.progress({ _ in
//progress stuff
})
.response { _, _, data, _ in
//Handle response once it's all over
}