Need HELP with Download Manager for offline video playback - Code=-11800, An unknown error occurred (-12782)

Hi guys,


I have implemented download manager to support multiple iOS versions and 2 types of videos - individual files (eg. MP4) and HLS streams (based on m3u8 manifests) for offline playback. I used following solution:


1. iOS 9 - URLSessionDownloadTask for MP4

2. iOS 10 - URLSessionDownloadTask for MP4

3. iOS 10 - AVAssetDownloadTask for HLS

4. iOS 11, 12 - URLSessionDownloadTask for MP4

5. iOS 11, 12 - AVAggregateAssetDownloadTask for HLS


Solution 5 supports also downloading all audio and subtitle tracks, but I need to implement the same for iOS 10, but iOS 10 SDK doesn't support AVAggregateAssetDownloadTask.


I used AVAssetDownloadTask and approach described here:

https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html


However downloading additional assets doesn't work and I'm getting an error:


(String) $R2 = "Optional(Error Domain=AVFoundationErrorDomain Code=-11800 \"The operation could not be completed\" UserInfo={NSLocalizedDescription=The operation could not be completed, NSLocalizedFailureReason=An unknown error occurred (-12782)})"


I tried following approaches:


SOLUTION 1:

- The main download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, destinationURL: destinationURL, options: nil)

- Asset download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, assetTitle: option.displayName, assetArtworkData: nil, options: options)


In this case I download the main part (video and default audio) into destination folder.

Then I try to download other tracks, hoping that AVURLAsset knows it needs to add downloaded content into destination folder.



SOLUTION 2:

- The main download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, assetTitle: option.displayName, assetArtworkData: nil, options: nil)

- Asset download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, assetTitle: option.displayName, assetArtworkData: nil, options: options)


Here I leave asset download task to handle destination and just take care of setting options.



SOLUTION 3:

- The main download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, assetTitle: option.displayName, assetArtworkData: nil, options: nil)

- Asset download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, destinationURL: destinationURL, options: options)


In this case I download the main part (video and default audio) and leave the download task to inform me of the destination.

Then I try to download other tracks to the same destination.



SOLUTION 4:

- The main download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, destinationURL: destinationURL, options: nil)

- Asset download: makeAssetDownloadTask(asset: assetDownloadTask.urlAsset, destinationURL: destinationURL, options: options)


In this case I download the main part (video and default audio) into destination folder.

Then I try to download other tracks to the same destination (it's a folder, so it could just add additional bits and pieces).



No matter what I do, when trying to download additional assets using existing AVURLAsset I get the error mentioned above. The only way I get additional assets downloaded is creating new instance of AVURLAsset, but in that case video gets downloaded as well, so if there are 4 subtitle tracks, I get video download 4 times. I could merge all downloads and it would probably work, but it's waste of bandwidth and takes too long. I tried setting AVAssetDownloadTaskMinimumRequiredMediaBitrateKey when downloading additional tracks, but it still downloads video as well.


I guess when I create new instance of AVURLAsset I would also need to set property 'assetCache' to the instance held by previous AVURLAsset, but it's read only property.

Having cache info in AVURLAsset makes it download only what's missing - similar to playback, when switching audio track or subtitle track.


Do you think I should create derived class based on AVURLAsset, so I could update the cache reference?

I have already wasted too much time on this and don't want to start another round of experiments using trial & error approach.


I was also thinking about using AVPlayerItem and loading assets into cache this way, but that's awkward and really bad way to implement download of HLS streams.


Another approach would be parsing m3u8 and use URLSessionDownloadTask for all fragments, but that's overkill.


Last resort would be telling clients that additional audio and subtitle tracks are not supported on iOS 10 (well it really isn't, since Apple introduced Aggregate Downloads on iOS 11), but they may still have quite a few subscribers on iOS 10.


Any idea how to resolve this?


Thanks for any advice on iOS 10 HLS downloads.

Replies

Do I perahps need to use resourceLoader (AVAssetResourceLoader) property of AVURLAsset to handle additional audi and subtitle tracks?

Resolved it myself.


Downloads of additional media selections requires some very specific hack to make it work using AVAssetDownloadTask and it could have NEVER worked as described in

https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html


But I guess from now on very few developers will require HLS downloads with all Audio and Subtitle tracks on iOS 10, so there is no reason to describe it here. It took me a week to figure this out by trial and error approach.


I recommend other developers to persuade clients to drop iOS 10 support of HLS downloads with all Audio and Subtitle tracks and ship it only on iOS 11 and higher (using AVAggregateAssetDownloadTask). Downloads of default/preffered media selection still works on iOS 10 without much effort.

In my case I had to implement downlaod of all Audio and Subtitle tracks for iOS 10, because it was required for high profile clients, but generally it's not worth the effort.

Hi Robert, I am currently troubleshooting this weird behavior as well with iOS 10. What was the hack you ended up implementing? I'm evaluating what road to take based on the complexity/technical debt of the hack. Thank you for any info you can shoot my way!