How to use nsurlsession to download an asset of a hls link at background

Now I want to implement a download manager which can support to download an assert of a hls link. There are some issues:

1.As https://forums.developer.apple.com/message/42352#42352, there is a resume rate limiter. So, it seems we can't create and resume one segment at a time. For this, I do some tests that I create all tasks but just resume the first task at begin, then I resume the next task in "didcompletewitherror":

- (void)beginDownloadWithUrlArray:(NSArray *)downloadURLArray{

    for (NSString *urlStr in downloadURLArray) {
        NSURL *downloadURL = [NSURL URLWithString:urlStr];
        NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
        NSURLSessionTask *task = [self.session downloadTaskWithRequest:request];
        [self.taskArray addObject:task];
    }

    [self runNextTask];
}

- (void)runNextTask{
    if (self.taskIndex < self.taskArray.count) {

        self.currentDownloadTask = self.taskArray[self.taskIndex];
        [self.currentDownloadTask resume];
        NSLog(@"nextrequest--%@",self.currentDownloadTask);
    }else{
        self.currentDownloadTask = nil;
    }

    self.taskIndex ++ ;
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
    NSLog(@"didCompleteWithError");
    if (error) {
        NSLog(@"error -- %@",error);
        if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
            self.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
            NSURLSessionTask *task = [[[self class] sharedManager] downloadTaskWithResumeData:self.resumeData];
            [task resume];
        }
    }else{
        [self runNextTask];
    }
}

I can see it can continue downloading all tasks without delay. But it is strange that I can see didCompleteWithError is called when all tasks are finished. So I wonder whether "[self runNextTask]" in "didCompleteWithError" is called? But if I remove "[self runNextTask]" from "didCompleteWithError", it just can download the first task. Can someone explain what is the mechanism of this? Thanks. Also we capture the pcap packet to analyze and find that the tasks are not called in order.


2. As apple provides an AVAssetDownloadURLSession for downloading an asset and find that it can download the segment in order and have no delay. What is the mechanism of this. Thanks.

Replies

So, it seems we can't create and resume one segment at a time.

Correct.

For this, I do some tests that I create all tasks but just resume the first task at begin, then I resume the next task in "didcompletewitherror":

Splitting the creation and resuming of the tasks won’t help. The limiting factor here is NSURLSession’s resuming (or relaunching) of your process, not of the tasks themselves.

If you’re seeing odd behaviours here, be aware that the debugger has a big impact on background task execution, so make sure you follow the guidelines outlined Testing Background Session Code.

Also we capture the pcap packet to analyze and find that the tasks are not called in order.

This is expected; NSURLSession background sessions do not specify the order in which tasks are run.

As apple provides an AVAssetDownloadURLSession for downloading an asset and find that it can download the segment in order and have no delay. What is the mechanism of this.

I’m not sure what you’re asking here. Are you asking how to use AVAssetDownloadURLSession? Or how AVAssetDownloadURLSession works?

If it’s the latter, that won’t help you because AVAssetDownloadURLSession is part of the system and thus has access to facilities that you don’t have access to.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo

Thank you very much. For second question, I just want to ask how AVAssetDownloadURLSession works and you already answered it. I have one more question, for it needs to create all tasks at begin, is there a limitation on the number of task? i.e. does it work if I create hundreds or thousands of tasks at a time?Thanks again.

… is there a limitation on the number of task? i.e. does it work if I create hundreds or thousands of tasks at a time?

There is no hard limit. In my experience you won’t have problems with a few hundred tasks. Back when NSURLSession was first introduced (iOS 7) I heard reports from developers describing weird problems when the task count got up into the thousands. I’ve not tracked that issue since then, so I don’t know how things currently stand.

My general advice on this front is covered by the Moving to Fewer, Larger Transfers pinned post.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your reply🙂

Since create all tasks and resume them at a time, the tasks are not called in order. Does it have any solution to make the tasks to be processed in order? i.e. download task1, task1 finish then download task2, task2 finish then downlod task3 and so on. Thanks.

Since create all tasks and resume them at a time, the tasks are not called in order.

Right. That’s expected behaviour.

Does it have any solution to make the tasks to be processed in order?

No, alas.

If you'd like to see such support added in the future, I encourage you to file an enhancement request describing your requirements. While we may have seen similar requests before, a fresh bug report will allow you to express your needs in your own terms, and allow iOS engineering to gauge the level of demand.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks. I already file an enhancement request and the bug number is 29194026. Also as we test, the delegate function is awaked till all takes are finished at background . And there is a 30 seconds limitation for handling the remaining work(such as remove the downloaded files from system storage to app sandbox) of all tasks. Is it expected result? If we can't finish the remaining work of all tasks within 30 seconds, then what should we do? Let me describe the situation more clearly:

1. create 500 download tasks at foreground

2. switch to backgroud

3. when all tasks is finished, the delegate function of each task(such as didfinishdownloadingtourl) is called and we can do some needed work in the delegate function.

4. For there is a 30 seconds limitation, assume we can just finish the needed work for 400 tasks, then 100 tasks are left.

5. Does it have any chance to do the needed work of the left 100 tasks?

Thanks again.🙂

In step 3 you should start a UIApplication background task to get the maximum amount of background execution time to process the results of these tasks. As you say, that time is currently capped to around 30 seconds. If that’s not enough, the best you can do is defer the work until your app gets background execution time for other reasons.

What I’d do in your situation is:

  1. Implement

    -URLSession:downloadTask:didFinishDownloadingToURL:
    so that it runs very quickly, moving the file to a ‘to be processed’ directory.
  2. Similarly, implement

    -URLSession:task:didCompleteWithError:
    so that it runs very quickly.
  3. When you get

    -URLSessionDidFinishEventsForBackgroundURLSession:
    , do three things:
  4. Start a background process to process all the files in your ‘to be processed’ directory. When you’re done with a file, move it to a different directory.

  5. Use a UIApplication background to keep your process from being suspended.

  6. Call the completion handler that you got via

    -application:handleEventsForBackgroundURLSession:completionHandler:
    so that the system knows you’re done with the background session work.
  7. In the code that processes these files in the background, do three things:

  8. Monitor the

    backgroundTimeRemaining
    property of UIApplication so that can stop your background work orderly when you start running out of time.
  9. If the expiry handler associated with your UIApplication background task gets called, urgently stop your background work.

  10. In both case, once you’ve stopped your background work, end the background task.

The next time your app gets execution time (either in the background, or because your user has brought the app to the front), you can continue processing any files left over in the ‘to be processed’ directory.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for you detail guidance. We'll try that. Thanks again.🙂