Ошибка при реализации AVAssetDownloadURLSession для загрузки потока HLS

Я пытаюсь реализовать автономный режим для потокового приложения. Цель состоит в том, чтобы иметь возможность загружать поток 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")!.

Я искал причину и решение этой ошибки, но пока не могу найти. Я был бы очень признателен за любые советы или подсказки о том, как решить эту проблему, или за любые указания на ошибки в моей реализации синглтона, которые могли бы объяснить такое поведение.

Заранее спасибо.

Мартин

ИЗМЕНИТЬ:

Похоже, эта ошибка возникает на симуляторе. Я запускаю свое приложение на реальном устройстве, и загрузка началась без проблем. Надеюсь это поможет. До сих пор не понимаю, почему я не могу попробовать это поведение на симуляторе.


person Martin    schedule 22.11.2017    source источник
comment
AVAssetDownloadTask не работают на симуляторе. Я не смог найти это явно упомянутым в документации, но образец кода HLSCatalog от Apple печатает Загрузка потоков HLS не поддерживается в симуляторе. за ошибку, которую вы упомянули.   -  person fruitcoder    schedule 24.02.2018
comment
@fruitcoder Спасибо за ваш ответ. Это была именно моя проблема. Определенно не было ясного объяснения в документе.   -  person Martin    schedule 27.02.2018