Proper way to detect user cancelled downloads using AVAssetDownloadDelegate

544 Views Asked by At

hoping someone has some incite, we've tried posting in the Apple forums, but they were no help. Trying to find the correct way to detect a user cancelled download/extract the error code from the AVAssetDownloadTask object using the AVAssetDownloadDelegate in Swift. We're tried using the following method:

public func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL)
{
    if let errorCode = assetDownloadTask.error?._code,
        errorCode == NSURLErrorCancelled
    {
        do
        {
            try FileManager.default.removeItem(at: location)
        }
        catch
        {
            DDLogError("An error occurred trying to delete the contents on disk for \(error)")
        }

        // if we do have a cancelled download and we have removed it, return before we save the location
        return
    }

    // we need to save the location where the download finished, so when we call our delegate in
    // urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
    // we have a reference to it in order to pass along
    self.locationDict[assetDownloadTask.urlAsset.url] = location
}

As well as

public func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL)  
{  
    if let error = assetDownloadTask.error as NSError?  
    {  
        switch (error.domain, error.code)  
        {  
            case (NSURLErrorDomain, NSURLErrorCancelled):  

                do  
                {  
                    try FileManager.default.removeItem(at: location)  
                }  
                catch  
                {  
                    DDLogError("An error occurred trying to delete the contents on disk for \(error)")  
                }  

                // if we do have a cancelled download and we have removed it, return before we save the location  
                return  
            default:  
                DDLogError("An unexpected error occurred \(error.domain)")  
        }  
    }  

    // we need to save the location where the download finished, so when we call our delegate in  
    // urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)  
    // we have a reference to it in order to pass along  
    self.locationDict[assetDownloadTask.urlAsset.url] = location  
}  

But both show up in Crashlytics with the crash log:

Crashed: com.-.ios.application.AssetDownloadUrlSession  
0  libswiftFoundation.dylib       0x103afa01c specialized static URL._unconditionallyBridgeFromObjectiveC(_:) + 8344  
1  Networking                     0x1027d2178 $S13Networking26AssetDownloadUrlSessionC03urlE0_05assetC4Task22didFinishDownloadingToySo12NSURLSessionC_So07AVAssetcH0C10Foundation3URLVtFTf4dnnn_n + 456  
2  Networking                     0x1027ce3e8 $S13Networking26AssetDownloadUrlSessionC03urlE0_05assetC4Task22didFinishDownloadingToySo12NSURLSessionC_So07AVAssetcH0C10Foundation3URLVtFTo + 88  
3  CFNetwork                      0x1dc049c78 __89-[NSURLSession delegate_AVAssetDownloadTask:didFinishDownloadingToURL:completionHandler:]_block_invoke + 36  
4  Foundation                     0x1dc33c8bc __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16  
5  Foundation                     0x1dc244ab8 -[NSBlockOperation main] + 72  
6  Foundation                     0x1dc243f8c -[__NSOperationInternal _start:] + 740  
7  Foundation                     0x1dc33e790 __NSOQSchedule_f + 272  
8  libdispatch.dylib              0x1db2e56c8 _dispatch_call_block_and_release + 24  
9  libdispatch.dylib              0x1db2e6484 _dispatch_client_callout + 16  
10 libdispatch.dylib              0x1db28982c _dispatch_continuation_pop$VARIANT$mp + 412  
11 libdispatch.dylib              0x1db288ef4 _dispatch_async_redirect_invoke + 600  
12 libdispatch.dylib              0x1db295a18 _dispatch_root_queue_drain + 376  
13 libdispatch.dylib              0x1db2962c0 _dispatch_worker_thread2 + 128  
14 libsystem_pthread.dylib        0x1db4c917c _pthread_wqthread + 472  
15 libsystem_pthread.dylib        0x1db4cbcec start_wqthread + 4 
3

There are 3 best solutions below

0
On

You can implement this option, it should help you avoid an app crash

 func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
        guard assetDownloadTask.urlAsset.assetCache?.isPlayableOffline == true,
            assetDownloadTask.error == nil else { return }
        locationDict[assetDownloadTask.urlAsset.url] = location
    }
4
On

Why you are trying to delete file if it failed load?

self.locationDict[assetDownloadTask.urlAsset.url] = location

I think crash is here [assetDownloadTask.urlAsset.url] This value could be nil in this case

0
On

Facing the same issue, so turns out AVURLAsset's url property can be nil even though it's not defined as optional. Here's an ugly but failry reliable workaround for this:



private extension AVURLAsset {
    var isURLNil: Bool {
        debugDescription.contains("URL = (null)")
    }
}

Obviously this can break in (unlikely) future changes in how Swift prints debug descriptions, but until then they'll probably also fix this bug in the framework.