Hello all, I am playing with MapKit for SwiftUI, so far so good. There is one thing I have not seen any documentations, or sample codes around and that's elevation data, e.g.
My questions are:
Is there a way to get this information from an MKRoute?
Is it possible to get the elevation gain/drop at a given point in the route?
Many thank in advance for your help.
Post
Replies
Boosts
Views
Activity
Hi all,
I was wondering if it is possible to calculate the bandwidth of a download in progress? I am using AVAggregateAssetDownloadTask From Apple's sample code I know the best way to show "download progress" is by doing something like this:
func urlSession(_ session: URLSession, aggregateAssetDownloadTask: AVAggregateAssetDownloadTask,
didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue],
timeRangeExpectedToLoad: CMTimeRange, for mediaSelection: AVMediaSelection) {
...
var percentComplete = 0.0
for value in loadedTimeRanges {
let loadedTimeRange: CMTimeRange = value.timeRangeValue
percentComplete +=
loadedTimeRange.duration.seconds / timeRangeExpectedToLoad.duration.seconds
...
// The following values are wrong when comparing actual size of files on disk.
let fetchedBytes = counter.string(fromByteCount: aggregateAssetDownloadTask.countOfBytesReceived)
let expectedBytes = counter.string(fromByteCount: aggregateAssetDownloadTask.countOfBytesExpectedToReceive)
}
}
AVAggregateAssetDownloadTask also has URLSessionTasks field countOfBytesReceived and countOfBytesExpectedToReceive
Trouble is if I try to read either of these two, as per the code above, the count is incorrect. I.e. if I check the size of the movpkg on disk, it is quite off from what countOfBytesReceived states.
My questions are:
Is a time range calculation still the best way to depict download progress
Is there a a way to get correct updates of the movpkg size as the download progresses, so that I can calculate speed?
Hello,
I have a strange issue when deleting partially downloaded HLS streams. I'm using the HLSCatalog sample as a reference and the code is confusing.
Here's a snippet:
/// Tells the delegate that the task finished transferring data.
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let userDefaults = UserDefaults.standard
/*
This is the ideal place to begin downloading additional media selections
once the asset itself has finished downloading.
*/
guard let task = task as? AVAggregateAssetDownloadTask,
let asset = activeDownloadsMap.removeValue(forKey: task) else { return }
guard let downloadURL = willDownloadToUrlMap.removeValue(forKey: task) else { return }
// Prepare the basic userInfo dictionary that will be posted as part of our notification.
var userInfo = [String: Any]()
userInfo[Asset.Keys.name] = asset.stream.name
if let error = error as NSError? {
switch (error.domain, error.code) {
case (NSURLErrorDomain, NSURLErrorCancelled):
/*
This task was canceled, you should perform cleanup using the
URL saved from AVAssetDownloadDelegate.urlSession(_:assetDownloadTask:didFinishDownloadingTo:).
*/
guard let localFileLocation = localAssetForStream(withName: asset.stream.name)?.urlAsset.url else { return }
do {
try FileManager.default.removeItem(at: localFileLocation)
userDefaults.removeObject(forKey: asset.stream.name)
} catch {
print("An error occured trying to delete the contents on disk for \(asset.stream.name): \(error)")
}
userInfo[Asset.Keys.downloadState] = Asset.DownloadState.notDownloaded.rawValue
case (NSURLErrorDomain, NSURLErrorUnknown):
fatalError("Downloading HLS streams is not supported in the simulator.")
default:
fatalError("An unexpected error occured \(error.domain)")
}
} else {
do {
let bookmark = try downloadURL.bookmarkData()
userDefaults.set(bookmark, forKey: asset.stream.name)
} catch {
print("Failed to create bookmarkData for download URL.")
}
userInfo[Asset.Keys.downloadState] = Asset.DownloadState.downloaded.rawValue
userInfo[Asset.Keys.downloadSelectionDisplayName] = ""
}
NotificationCenter.default.post(name: .AssetDownloadStateChanged, object: nil, userInfo: userInfo)
}
If the task completes, we generate a bookmark:
let bookmark = try downloadURL.bookmarkData()
and we save it.
if I was to delete the stream then, it all works fine. I can go to my device's settings and I can't see it iPhone Storage. So all good.
However.... if I try to cancel the download
task?.cancel()
Is invoked and the didCompleteWithError function is called with a cancellation error. At this point, we haven't generated a bookmark for the file, so this will always fail
guard let localFileLocation = localAssetForStream(withName: asset.stream.name)?.urlAsset.url else { return }
do {
try FileManager.default.removeItem(at: localFileLocation)
But strangely, even if I try to generate a bookmark from the url location where the asset, I will get a bookmark, FileManager won't throw any errors on delete, but the partial download still remains on the device.
let url = try URL(resolvingBookmarkData: localFileLocation,
bookmarkDataIsStale: &bookmarkDataIsStale)
try FileManager.default.removeItem(at: url)
Any ideas?
Hello,
I am queueing up download task on a AVAssetDownloadURLSession - if I queue up 6-7 downloads, not all of them start straight away in the foreground, some are actually queued up and will begin, run and complete while the app is in background mode.
The HLS streams I am downloading are FairPlay protected, so I use AVContentKeySession to fetch the keys for each download
I pass the AVURLAsset to the key session via addContentKeyRecipient since I don't have an skd. To my understanding, the key session won't actually kick in before I start the download , i.e. so it can download the manifest and find the skd and then I can go through the whole DRM flow - i.e. the asset download task needs to actually begin for all this to work.
Problem is, if my app is in the background, the part of my code that calls addContentKeyRecipient is not executed, since the background session only notifies you when it has completed, or failed.
My question is - how are we supposed to fetch licenses while in the background? Does content key session provide some background support?
Apple's advice is to always preload keys, but it seems this is not always possible. Am I missing something? Thanks in advance for any assistance.
Hello all,
I recently encountered a problem similar to what has been asked here (sadly no answer): https://developer.apple.com/forums/thread/127808?login=true
When downloading an HLS stream via an aggregate task (regardless if I set it to use preferred, or all media selections) I get an error on offline playback. If my device is connected, all seems to work ok.
Upon inspection of the boot.xml in the .movpkg file, I can see some streams report
<Complete>NO</Complete></Stream>
I cannot quite work out why this is. Before playback the resultant AVURLAsset's status is not ready.
The movie has multiple audio and a single subtitle and video tracks. It is important to mention, playback seems to work for some of my test videos. I also use a VPN.
Any assistance would be much appreciated.
Hello, I have an architectural question. Imagine this sample
https://developer.apple.com/documentation/swift/asyncstream
slightly modified so it doesn't use a static var
extension QuakeMonitor {
var quakes: AsyncStream<Quake> {
AsyncStream { continuation in
let monitor = QuakeMonitor()
monitor.quakeHandler = { quake in
continuation.yield(quake)
}
continuation.onTermination = { @Sendable _ in
monitor.stopMonitoring()
}
monitor.startMonitoring()
}
}
}
Suppose multiple receivers are interested in getting updates via the quakes stream, how would one architect such solution? E.g. let's say we have two views which exist at the same time such as below. Is there a way both can get updates simultaneously?
Alternatively, is it possible to "hand off" a stream from one object to another?
class MyFirstView: UIView {
private var quakeMonitor: QuakeMonitor
init(quakeMonitor: QuakeMonitor) {
self.quakeMonitor = quakeMonitor
}
func readQuakeData() {
for await quake in quakeMonitor.quakes {
print ("Quakes in first view: \(quake.date)")
}
}
}
class MySecondView: UIView {
private var quakeMonitor: QuakeMonitor
init(quakeMonitor: QuakeMonitor) {
self.quakeMonitor = quakeMonitor
}
func readQuakeData() {
for await quake in quakeMonitor.quakes {
print ("Quakes in second view: \(quake.date)")
}
}
}
Hello all,
I am using a AVAggregateAssetDownloadTask to download a stream. In the session's init, the delegateQueue is set to main.
I create and resume session tasks from a separate thread:
func doSomething async {
Task {
//Task setup here
mySessionTask.resume()
}
}
Now, in the delegate, I have:
public func urlSession(
_ session: URLSession,
aggregateAssetDownloadTask: AVAggregateAssetDownloadTask,
willDownloadTo location: URL
) {
print("Location: \(location.path)")
do {
let data = (try location.bookmarkData())
print("Great! We generated bookmark data with length \(data.count)")
} catch {
print("Ooops, an error occurred: \(error)")
}
The problem is, when I run the code I get an error:
Error Domain=NSCocoaErrorDomain Code=260 "The file couldn’t be opened because it doesn’t exist."
I am not sure why this is the case. My code is basically exactly the same as a sample from Apple: https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/using_avfoundation_to_play_and_persist_http_live_streams . I can reproduce my error here if I generate the task from a different thread
This leads me to believe, there is a problem (probably with security scoping, but doesn't make sense to me) when trying to access a URL from a different thread in order to generate a bookmark. Has anyone experience this? Any ideas how it can be fixed?
Thanks in advance.
Hi all,
I have seen this discussed a number of times, but I'm afraid I haven't found a solution which works for me.
Gven an AVAggregateAssetDownloadTask, when the user force quits the application, is there a way on launch to resume from where the app left off?
I have tried a number of things:
I have tried to point the application to the locally stored HLS download (seems illogical, but the internet suggested it.
I have implemented
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?
)
I can see the cancelled task, but there is no resume data associated with it.
Any help would be appreciated.
Hi all,
I would like to unit test a piece of code which has logic related to the operation of an AVAggregateAssetDownloadTask
I cannot separate concerns more than I have, so can't really go around the problem. I know I can use URLProtocol to mock data tasks, but it seems AVAssetDownloadURLSession is a bit different.
So far I have tried:
To create an AVURLAsset by pointing it to a stream in my test bundle - no joy (I downloaded the streams from here: https://developer.apple.com/streaming/fps/)
I can't quite figure out how to load the stream via URLProxy in a way that won't result in an error being returned by the task.
Any help would be greatly appreciated!