Я пытаюсь реализовать автономный режим для потокового приложения. Цель состоит в том, чтобы иметь возможность загружать поток HLS на устройство пользователя, чтобы можно было смотреть поток, даже когда пользователь находится в автономном режиме.
Недавно я наткнулся на это руководство. Кажется, он отвечает точным требованиям того, что я пытался реализовать, но столкнулся с проблемой, пытаясь заставить его работать.
Я создал небольшой 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
. Каждый раз, когда я пытаюсь возобновить загрузку, я получаю сообщение об ошибке в функции делегата urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
.
Журнал сообщения:
Задача выполнена: ‹__NSCFBackgroundAVAssetDownloadTask: 0x7ff57fc024a0>{ taskIdentifier: 1}, ошибка: необязательно (домен ошибки = код AVFoundationErrorDomain = -11800 \"Операция не может быть завершена\" UserInfo={NSLocalizedFailureReason=Произошла неизвестная ошибка (-12780), NSLocalizedDescription=Операция не может быть завершена})
Чтобы предоставить вам всю необходимую информацию, URL-адрес, который я передал моей функции setupAssetDownload
, имеет тип URL(string: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8")!
.
Я искал причину и решение этой ошибки, но пока не могу найти. Я был бы очень признателен за любые советы или подсказки о том, как решить эту проблему, или за любые указания на ошибки в моей реализации синглтона, которые могли бы объяснить такое поведение.
Заранее спасибо.
Мартин
ИЗМЕНИТЬ:
Похоже, эта ошибка возникает на симуляторе. Я запускаю свое приложение на реальном устройстве, и загрузка началась без проблем. Надеюсь это поможет. До сих пор не понимаю, почему я не могу попробовать это поведение на симуляторе.
AVAssetDownloadTask
не работают на симуляторе. Я не смог найти это явно упомянутым в документации, но образец кода HLSCatalog от Apple печатает Загрузка потоков HLS не поддерживается в симуляторе. за ошибку, которую вы упомянули. - person fruitcoder   schedule 24.02.2018