6

I am implementing HLS streaming as per Apple Docs

But the problem that I am facing is for resuming the download when the user kills the app. If a download is in progress and say its 50% done and the user kills the app or app is killed by the system due to any reason and when the app is alive again then the URL session delegate of didCompleteWithError is called

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
}

and here I dont have the partially downloaded file path or ability to resume the task.

The only location for the downloaded file is called when the download is complete via the following delegate call

func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
}

Now doc says to use

downloadSession.getAllTasks { tasksArray in }

but unfortunately, it does not resume the download

So my problem is

  1. How to resume the task from that downloaded state so that the entire download doesn't start all over again from 0% ?
  2. For the task that is not resumable or for a particular scenario where I don't want to resume it how can I delete the partially downloaded file ? How will I get the downloaded path (I don't want to search the entire documents directory)
trungduc
  • 11,926
  • 4
  • 31
  • 55
Girish Nair
  • 5,148
  • 5
  • 40
  • 61

2 Answers2

6

Actually, you can use getAllTasks(completionHandler:)] to get pending tasks which are not completed at the previous launch but somehow these tasks will be cancelled immediately after download session is created which leads to urlSession(_:task:didCompleteWithError:) is called as you see.

Luckily, I found another way to resume AVAssetDownloadTask

AVAssetDownloadTask provides the ability to resume previously stopped downloads under certain circumstances. To do so, simply instantiate a new AVAssetDownloadTask with an AVURLAsset instantiated with a file NSURL pointing to the partially downloaded bundle with the desired download options, and the download will continue restoring any previously downloaded data.

It means that if you want to resume a pending AVAssetDownloadTask, you have to save location from urlSession(_:assetDownloadTask:didFinishDownloadingTo:) when download task is stopped. After that, create another download task base on the partially downloaded file.

func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
    destinationURL = location
}

func resumeDownloadTask() {
    let urlAsset = AVURLAsset(url: destinationURL)
    downloadTask = assetDownloadURLSession.makeAssetDownloadTask(asset: urlAsset, assetTitle: "title", assetArtworkData: nil, options: nil)
    downloadTask.resume()
}

urlSession(_:assetDownloadTask:didFinishDownloadingTo:) will always be called before urlSession(_:task:didCompleteWithError:) so you can get destinationURL in both case when app is terminated and relaunch or download task is cancelled.

Note that you shouldn't create new download task inside urlSession(_:task:didCompleteWithError:), somehow it will lead to an infinity loop.

With your second question, simply delete the file by using destinationURL.

For more detail, I created a sample repo at the below link. There is still some bug but it can run in normal case. Try to start download task, let it runs for a while and terminated the app. Relaunch and resume the task, you will see the result.

https://github.com/trungducc/stackoverflow/tree/hls-download-resuming

trungduc
  • 11,926
  • 4
  • 31
  • 55
  • I had a word with the apple technical support on this, they say 1. It's not possible to resume it 2. The partially downloaded file will be deleted by the system. However, the docs say we have to handle it Still in talks with them for this, will update it once it's done – Girish Nair Apr 26 '19 at 11:31
  • 1. Did you try my sample? It works. 2. Even if partially dowloaded file will be deleted, you can copy it to another folder to resume when you want – trungduc Apr 26 '19 at 12:23
  • @GirishNair Oh. So there's really no way to resume it? – KarenAnne Jul 16 '19 at 07:41
  • @KarenAnne Download my sample and try. You can see it work yourself. – trungduc Jul 16 '19 at 12:25
  • @KarenAnne: It doesn't, the resume that trungduc has given only when you pause and resume in the same session. If you kill the app and try resuming it, it will throw an error – Girish Nair Jul 16 '19 at 14:59
  • @trungduc .. I tried the sample but it restarts to 0 when you restart the app. It doesn't resume to the point you left. – KarenAnne Jul 17 '19 at 09:56
  • @GirishNair Have you found any way to resume it to the point it left off? – KarenAnne Jul 17 '19 at 09:57
  • @KarenAnne As mentioned you here all resume on pending tasks but if it throws an error you simply cannot resume it. An alternative would be to use alamofire network call with a background session for the .ts files, but its not the same – Girish Nair Aug 13 '19 at 12:49
  • Hello anyone find solution ? – kirti Chavda Jul 20 '21 at 18:19
-1

While the accepted answer works well, here is an important addition.

When debugging from Xcode the app install directory changes at each new relaunch, causing the asset destination location to become invalid across restarts.

You can actually persist location.relativeString and reconstitute the updated URL at the next launch as follows :

// get it from UserDefaults most likely
let persistedRelativeString = .... 

// this corresponds to the app installation root path
let baseDownloadDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!.deletingLastPathComponent()

let updatedLocation = URL(string: persistedRelativeString, relativeTo: baseDownloadDirectory)!

On iOS 14+ it seems the session automatically resumes the download task, unless you manually cancel it. Knowing this behaviour we can simply let it continue and once urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) gets called you can retrieve a valid path for this task.

To support multiple task you can persist a single [Int: String] corresponding to :

  • keys: task IDs (stable accross relaunches)
  • values: location.relativeString.

This way you can easily implement downloads across restarts.

dvkch
  • 1,079
  • 1
  • 12
  • 20