应用程序从后台激活后如何跟踪下载进度

how can i track download progress after app is did become active from background

我遇到了标题中描述的问题。您可能会在我的存储库 (https://github.com/Hudayberdyyev/custom_download_manager) . i will try to briefly explain the problem. I am trying to write a download manager based on this repo (https://github.com/r-plus/HLSion) 中看到源代码。基本上它由 3 个部分组成:

  1. SessionManager(管理所有 sessions)
  2. HLSData(初始化的 HLSData 模型与下面的代码相同。它就像 session 管理器之间的中介)
public convenience init(url: URL, options: [String: Any]? = nil, name: String) {
        let urlAsset = AVURLAsset(url: url, options: options)
        self.init(asset: urlAsset, description: name)
}
  1. AssetStore(它被管理 HLSData.plist 文件。其中包含每个下载的名称和路径 session)。

这是开始下载的实现方式:

var sources = [HLSData]()
@objc func startDownloadButtonTapped() {
        print(#function)
        let hlsData = sources[0]
        switch hlsData.state {
        case .notDownloaded:
            hlsData.download { (percent) in
                DispatchQueue.main.async {
                    print("percent = \(percent)")
                    self.percentLabel.text = "\(percent)"
                }
            }.finish { (relativePath) in
                DispatchQueue.main.async {
                    print("download completed relative path = \(relativePath)")
                }
            }.onError { (error) in
                print("Error finish. \(error)")
            }
        case .downloading:
            print("State is downloading")
            break
        case .downloaded:
            print(hlsData.localUrl ?? "localURL is nil")
        }
}

点击前状态为notDownloaded。当点击按钮并且状态更改为 downloading 时,应用程序开始下载。 一切都很好,进展情况也很好。但是当我进入后台并 return 回到应用程序时,状态仍然是下载状态,但进度关闭不再起作用。我如何恢复或重置此关闭以跟踪进度。提前致谢。

在做一些测试时,我觉得 iOS 12 and belowAVAssetDownloadDelegate

有一个错误

在做一些测试时,我在尝试使用 AVAssetDownloadTask 通过 HLS 下载媒体时注意到以下内容:

iOS 13岁以上

  1. 进入后台时,下载继续
  2. 从后台进入前台时,AVAssetDownloadDelegate仍然触发assetDownloadTask didLoad totalTimeRangesLoaded,可以更新进度
  3. 挂起或退出应用程序后,使用相同的 URLSessionConfiguration identifier 重新初始化 AVAssetDownloadURLSession,下载会自动从上次停止的地方恢复

iOS 12岁及以下

除第 2 点外,所有内容几乎都成立,由于某种原因,assetDownloadTask didLoad totalTimeRangesLoaded 在从后台进入前台时不再被触发,因此进度不再更新。

我从这个答案 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.

有趣的是,您再也无法在官方文档中找到它,而且似乎设置 destinationURL 已被弃用,因此似乎对工作方式进行了一些重构。

我的解决方案:

  1. 订阅 UIApplication.willEnterForegroundNotification 通知
  2. UIApplication.willEnterForegroundNotification 的回调中,检查设备是否 运行 iOS 12 岁及以下
  3. 如果是,取消当前AVAssetDownloadTask
  4. 这应该会触发 AVAssetDownloadDelegate 回调 assetDownloadTask didFinishDownloadingTo,它将为您提供部分下载文件的位置
  5. 重新配置 AVAssetDownloadTask 但不使用 HLS 配置它 url,而是使用 URL 配置到部分下载的资产
  6. 继续下载,进度AVAssetDownloadDelegate好像又开始了

您可以下载此示例here

以下是上述步骤的一些小片段:

private let downloadButton = UIButton(type: .system)

private let downloadTaskIdentifier = "com.mindhyve.HLSDOWNLOADER"

private var backgroundConfiguration: URLSessionConfiguration?
private var assetDownloadURLSession: AVAssetDownloadURLSession!
private var downloadTask: AVAssetDownloadTask!

override func viewDidLoad()
{
    super.viewDidLoad()

    // UI configuration left out intentionally
    subscribeToNotifications()
    initializeDownloadSession()
}

private func initializeDownloadSession()
{
    // This will create a new configuration if the identifier does not exist
    // Otherwise, it will reuse the existing identifier which is how a download
    // task resumes
    backgroundConfiguration
        = URLSessionConfiguration.background(withIdentifier: downloadTaskIdentifier)
    
    // Resume will happen automatically when this configuration is made
    assetDownloadURLSession
        = AVAssetDownloadURLSession(configuration: backgroundConfiguration!,
                                    assetDownloadDelegate: self,
                                    delegateQueue: OperationQueue.main)
}

private func resumeDownloadTask()
{
    var sourceURL = getHLSSourceURL(.large)
    
    // Now Check if we have any previous download tasks to resume
    if let destinationURL = destinationURL
    {
        sourceURL = destinationURL
    }
    
    if let sourceURL = sourceURL
    {
        let urlAsset = AVURLAsset(url: sourceURL)
        
        downloadTask = assetDownloadURLSession.makeAssetDownloadTask(asset: urlAsset,
                                                                     assetTitle: "Movie",
                                                                     assetArtworkData: nil,
                                                                     options: nil)
        
        downloadTask.resume()
    }
}

func cancelDownloadTask()
{
    downloadTask.cancel()
}

private func getHLSSourceURL(_ size: HLSSampleSize) -> URL?
{
    if size == .large
    {
        return URL(string: "https://video.film.belet.me/45505/480/ff27c84a-6a13-4429-b830-02385592698b.m3u8")
    }
    
    return URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8")
}

// MARK: INTENTS
@objc
private func downloadButtonTapped()
{
    print("\(downloadButton.titleLabel!.text!) tapped")
    
    if downloadTask != nil,
       downloadTask.state == .running
    {
        cancelDownloadTask()
    }
    else
    {
        resumeDownloadTask()
    }
}

@objc
private func didEnterForeground()
{
    if #available(iOS 13.0, *) { return }
    
    // In iOS 12 and below, there seems to be a bug with AVAssetDownloadDelegate.
    // It will not give you progress when coming from the background so we cancel
    // the task and resume it and you should see the progress in maybe 5-8 seconds
    if let downloadTask = downloadTask
    {
        downloadTask.cancel()
        initializeDownloadSession()
        resumeDownloadTask()
    }
}

private func subscribeToNotifications()
{
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(didEnterForeground),
                                           name: UIApplication.willEnterForegroundNotification,
                                           object: nil)
}

// MARK: AVAssetDownloadDelegate
func urlSession(_ session: URLSession,
                task: URLSessionTask,
                didCompleteWithError error: Error?)
{
    guard error != nil else
    {
        // download complete, do what you want
        return
    }
    
    // something went wrong, handle errors
}

func urlSession(_ session: URLSession,
                assetDownloadTask: AVAssetDownloadTask,
                didFinishDownloadingTo location: URL)
{
    // Save the download path of the task to resume downloads
    destinationURL = location
}

如果有些地方看起来不对劲,我建议您查看完整的工作示例 here