在做一些测试时,我觉得iOS 12 and below 有一个错误,AVAssetDownloadDelegate
在进行一些测试时,我在尝试使用 AVAssetDownloadTask 通过 HLS 下载媒体时注意到以下内容:
iOS 13 及更高版本
- 进入后台时,继续下载
- 当从后台进入前台时,
AVAssetDownloadDelegate 仍然会触发assetDownloadTask didLoad totalTimeRangesLoaded 并且可以更新进度
- 暂停或退出应用后,使用相同的
URLSessionConfiguration identifier 重新初始化AVAssetDownloadURLSession,下载会自动从上次停止的地方继续
iOS 12 及更低版本
除了第 2 点之外,一切几乎都成立,由于某种原因,assetDownloadTask didLoad totalTimeRangesLoaded 从后台进入前台时不再被触发,因此进度不再更新。
我从这个答案https://stackoverflow.com/a/55847387/1619193 得到的一个解决方法是,在过去,必须在应用程序因AVAssetDownloadTask 暂停后手动恢复下载,方法是为其提供磁盘上部分下载文件的位置。
根据文档:
AVAssetDownloadTask 提供恢复先前停止的能力
在某些情况下下载。为此,只需实例化一个
带有用文件实例化的 AVURLAsset 的新 AVAssetDownloadTask
NSURL 指向部分下载的包含所需的捆绑包
下载选项,并且下载将继续恢复任何
以前下载的数据。
有趣的是,您再也无法在官方文档中找到它,而且似乎设置 destinationURL 已被弃用,因此似乎对事情的工作方式进行了一些重构。
我的解决方案:
- 订阅
UIApplication.willEnterForegroundNotification 通知
- 在
UIApplication.willEnterForegroundNotification的回调中,检查设备是否运行iOS 12及以下版本
- 如果是,取消当前的
AVAssetDownloadTask
- 这应该会触发
AVAssetDownloadDelegate 回调assetDownloadTask didFinishDownloadingTo,这将为您提供部分下载文件的位置
- 重新配置
AVAssetDownloadTask,但不要使用 HLS url 配置它,而是使用部分下载资产的 URL 配置它
- 继续下载,
AVAssetDownloadDelegate 的进度似乎又开始触发了
你可以下载这个here的例子
以下是上述步骤的一些小sn-ps:
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