我正在尝试将离线模式实施为流式传输应用。 目标是能够在用户的设备上下载HLS流,以便即使在用户离线时也可以观看流。
我最近偶然发现this tutorial。 它似乎回答了我试图实现的确切要求,但我在尝试使其工作时遇到了问题。
我已经创建了一个小的DownloadManager来应用教程的逻辑。 这是我的单身人士课程:
import AVFoundation
class DownloadManager:NSObject {
static var shared = DownloadManager()
private var config: URLSessionConfiguration!
private var downloadSession: AVAssetDownloadURLSession!
override private init() {
super.init()
config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
downloadSession = AVAssetDownloadURLSession(configuration: config, assetDownloadDelegate: self, delegateQueue: OperationQueue.main)
}
func setupAssetDownload(_ url: URL) {
let options = [AVURLAssetAllowsCellularAccessKey: false]
let asset = AVURLAsset(url: url, options: options)
// Create new AVAssetDownloadTask for the desired asset
let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
assetTitle: "Test Download",
assetArtworkData: nil,
options: nil)
// Start task and begin download
downloadTask?.resume()
}
func restorePendingDownloads() {
// Grab all the pending tasks associated with the downloadSession
downloadSession.getAllTasks { tasksArray in
// For each task, restore the state in the app
for task in tasksArray {
guard let downloadTask = task as? AVAssetDownloadTask else { break }
// Restore asset, progress indicators, state, etc...
let asset = downloadTask.urlAsset
downloadTask.resume()
}
}
}
func playOfflineAsset() -> AVURLAsset? {
guard let assetPath = UserDefaults.standard.value(forKey: "assetPath") as? String else {
// Present Error: No offline version of this asset available
return nil
}
let baseURL = URL(fileURLWithPath: NSHomeDirectory())
let assetURL = baseURL.appendingPathComponent(assetPath)
let asset = AVURLAsset(url: assetURL)
if let cache = asset.assetCache, cache.isPlayableOffline {
return asset
// Set up player item and player and begin playback
} else {
return nil
// Present Error: No playable version of this asset exists offline
}
}
func getPath() -> String {
return UserDefaults.standard.value(forKey: "assetPath") as? String ?? ""
}
func deleteOfflineAsset() {
do {
let userDefaults = UserDefaults.standard
if let assetPath = userDefaults.value(forKey: "assetPath") as? String {
let baseURL = URL(fileURLWithPath: NSHomeDirectory())
let assetURL = baseURL.appendingPathComponent(assetPath)
try FileManager.default.removeItem(at: assetURL)
userDefaults.removeObject(forKey: "assetPath")
}
} catch {
print("An error occured deleting offline asset: \(error)")
}
}
}
extension DownloadManager: AVAssetDownloadDelegate {
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) {
var percentComplete = 0.0
// Iterate through the loaded time ranges
for value in loadedTimeRanges {
// Unwrap the CMTimeRange from the NSValue
let loadedTimeRange = value.timeRangeValue
// Calculate the percentage of the total expected asset duration
percentComplete += loadedTimeRange.duration.seconds / timeRangeExpectedToLoad.duration.seconds
}
percentComplete *= 100
debugPrint("Progress \( assetDownloadTask) \(percentComplete)")
let params = ["percent": percentComplete]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "completion"), object: nil, userInfo: params)
// Update UI state: post notification, update KVO state, invoke callback, etc.
}
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
// Do not move the asset from the download location
UserDefaults.standard.set(location.relativePath, forKey: "assetPath")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
debugPrint("Download finished: \(location)")
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: \(task), error: \(String(describing: error))")
guard error == nil else { return }
guard let task = task as? AVAssetDownloadTask else { return }
print("DOWNLOAD: FINISHED")
}
}
当我尝试拨打setupAssetDownload
功能时出现问题。
每次我尝试恢复downloadTask时,我都会在urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
委托函数中收到错误消息。
消息的日志是:
任务完成:< __ NSCFBackgroundAVAssetDownloadTask: 0x7ff57fc024a0> {taskIdentifier:1},错误:可选(错误 Domain = AVFoundationErrorDomain Code = -11800 \"操作不能 完成\" UserInfo = {NSLocalizedFailureReason =未知错误 发生(-12780),NSLocalizedDescription =操作无法进行 完成})
为了向您提供我过去的setupAssetDownload
功能的URL所有相关信息
URL(string: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8")!
我一直在寻找这个错误的原因和解决方案,但我似乎暂时无法找到它。 我将非常感谢有关如何解决此问题的任何提示或任何线索或我的单例实现中可以解释此行为的任何错误迹象。
提前谢谢。
马丁
修改:
似乎这个错误发生在模拟器上。我在真实设备上启动我的应用程序,下载开始没有任何问题。希望这可以帮助。仍然不明白为什么我不能在模拟器上尝试这种行为。