应用程序从后台激活后如何跟踪下载进度
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 个部分组成:
- SessionManager(管理所有 sessions)
- 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)
}
- 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 below
和 AVAssetDownloadDelegate
有一个错误
在做一些测试时,我在尝试使用 AVAssetDownloadTask
通过 HLS 下载媒体时注意到以下内容:
iOS 13岁以上
- 进入后台时,下载继续
- 从后台进入前台时,
AVAssetDownloadDelegate
仍然触发assetDownloadTask didLoad totalTimeRangesLoaded
,可以更新进度
- 挂起或退出应用程序后,使用相同的
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
已被弃用,因此似乎对工作方式进行了一些重构。
我的解决方案:
- 订阅
UIApplication.willEnterForegroundNotification
通知
- 在
UIApplication.willEnterForegroundNotification
的回调中,检查设备是否 运行 iOS 12 岁及以下
- 如果是,取消当前
AVAssetDownloadTask
- 这应该会触发
AVAssetDownloadDelegate
回调 assetDownloadTask didFinishDownloadingTo
,它将为您提供部分下载文件的位置
- 重新配置
AVAssetDownloadTask
但不使用 HLS 配置它 url,而是使用 URL 配置到部分下载的资产
- 继续下载,进度
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
我遇到了标题中描述的问题。您可能会在我的存储库 (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 个部分组成:
- SessionManager(管理所有 sessions)
- 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)
}
- 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 below
和 AVAssetDownloadDelegate
在做一些测试时,我在尝试使用 AVAssetDownloadTask
通过 HLS 下载媒体时注意到以下内容:
iOS 13岁以上
- 进入后台时,下载继续
- 从后台进入前台时,
AVAssetDownloadDelegate
仍然触发assetDownloadTask didLoad totalTimeRangesLoaded
,可以更新进度 - 挂起或退出应用程序后,使用相同的
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
已被弃用,因此似乎对工作方式进行了一些重构。
我的解决方案:
- 订阅
UIApplication.willEnterForegroundNotification
通知 - 在
UIApplication.willEnterForegroundNotification
的回调中,检查设备是否 运行 iOS 12 岁及以下 - 如果是,取消当前
AVAssetDownloadTask
- 这应该会触发
AVAssetDownloadDelegate
回调assetDownloadTask didFinishDownloadingTo
,它将为您提供部分下载文件的位置 - 重新配置
AVAssetDownloadTask
但不使用 HLS 配置它 url,而是使用 URL 配置到部分下载的资产 - 继续下载,进度
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