iOS音视频开发-----流媒体

HTTP Live Streaming (HLS))是向播放应用提供媒体的理想方式。使用HLS,您可以以不同的比特率提供多个媒体流,并且您的播放客户端会随着网络带宽的变化动态选择适当的流。这可确保您始终根据用户当前的网络状况提供最优质的内容。本章介绍如何在播放应用中利用HLS的独特功能。

从iOS 10开始,您可以使用AVFoundation将HTTP Live Streaming资源下载到iOS设备。这项新功能允许用户在可以访问快速,可靠的网络时在其设备上下载和存储HLS电影,并在以后无需网络连接即可观看。通过引入此功能,HLS可以最小化不一致的网络可用性对用户体验的影响。

使用AVAssetDownloadURLSession来实现资源下载功能,他是NSURLSession的子类,主要用来创建和执行资源下载任务。

func setupAssetDownload() {
    // Create new background session configuration.
    configuration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
 
    // Create a new AVAssetDownloadURLSession with background configuration, delegate, and queue
    downloadSession = AVAssetDownloadURLSession(configuration: configuration,
                                                assetDownloadDelegate: self,
                                                delegateQueue: OperationQueue.main)
}

 配置完URLSession以后,创建AVAssetDownloadTask实例来开始下载任务

func setupAssetDownload() {
    ...
    // Previous AVAssetDownloadURLSession configuration
    ...
 
    let url = // HLS Asset URL
    let asset = AVURLAsset(url: url)
 
    // Create new AVAssetDownloadTask for the desired asset
    let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
                                                             assetTitle: assetTitle,
                                                             assetArtworkData: nil,
                                                             options: nil)
    // Start task and begin download
    downloadTask?.resume()
}

 options选型可以传入一个Dictionary用来选择不同的比特和媒体选型。如果为Nil,会默认选择下载最高质量的音视频内容。由于可以在后台实现下载,当App被终止时,下次启动需要恢复上次的下载任务,此时可以通过一个上次任务在 NSURLSessionConfiguration中配置的identifier 来新创建一个NSURLSessionConfiguration对象,重新创建AVAssetDownloadURLSession对象,使用session的getTasksWithCompletionHandler:方法来获取终止的任务,来恢复下载任务。

func restorePendingDownloads() {
    // Create session configuration with ORIGINAL download identifier
    configuration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
 
    // Create a new AVAssetDownloadURLSession
    downloadSession = AVAssetDownloadURLSession(configuration: configuration,
                                                assetDownloadDelegate: self,
                                                delegateQueue: OperationQueue.main)
 
    // 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
        }
    }
}

 可以通过代理方法监控下载的进度:

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
    // Update UI state: post notification, update KVO state, invoke callback, etc.
}

当下载完成时或下载失败了会调用session的代理方法,可以在此方法中设置文件保存位置,与NSURLSessionDownloadDelegate中URLSession:downloadTask:didFinishDownloadingToURL:方法不同的时,用户不应该修该下载资源的位置,它是在系统的控制之下的,传入的URL,代表了下载资源在磁盘上的最终位置。

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")
}

你可以更新下载的资源,例如以前服务器并不提供高清的资源或者一些 资源选项。当服务器有资源更新的时候 session的代理方法URLSession:assetDownloadTask:didResolveMediaSelection:会被调用,保存AVMediaSelection对象,用于随后的下载任务

func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didResolve resolvedMediaSelection: AVMediaSelection) {
    // Store away for later retrieval when main asset download is complete
    // mediaSelectionMap is defined as: [AVAssetDownloadTask : AVMediaSelection]()
    mediaSelectionMap[assetDownloadTask] = resolvedMediaSelection
}

通过以下方法获取未在本地缓存的内容

func nextMediaSelection(_ asset: AVURLAsset) -> (mediaSelectionGroup: AVMediaSelectionGroup?,
                                                 mediaSelectionOption: AVMediaSelectionOption?) {
 
    // If the specified asset has not associated asset cache, return nil tuple
    guard let assetCache = asset.assetCache else {
        return (nil, nil)
    }
 
    // Iterate through audible and legible characteristics to find associated groups for asset
    for characteristic in [AVMediaCharacteristicAudible, AVMediaCharacteristicLegible] {
 
        if let mediaSelectionGroup = asset.mediaSelectionGroup(forMediaCharacteristic: characteristic) {
 
            // Determine which offline media selection options exist for this asset
            let savedOptions = assetCache.mediaSelectionOptions(in: mediaSelectionGroup)
 
            // If there are still media options to download...
            if savedOptions.count < mediaSelectionGroup.options.count {
                for option in mediaSelectionGroup.options {
                    if !savedOptions.contains(option) {
                        // This option hasn't been downloaded. Return it so it can be.
                        return (mediaSelectionGroup, option)
                    }
                }
            }
        }
    }
    // At this point all media options have been downloaded.
    return (nil, nil)
}

下面代码检查并下载所有的可选媒体资源 

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
 
    guard error == nil else { return }
    guard let task = task as? AVAssetDownloadTask else { return }
 
    // Determine the next available AVMediaSelectionOption to download
    let mediaSelectionPair = nextMediaSelection(task.urlAsset)
 
    // If an undownloaded media selection option exists in the group...
    if let group = mediaSelectionPair.mediaSelectionGroup,
           option = mediaSelectionPair.mediaSelectionOption {
 
        // Exit early if no corresponding AVMediaSelection exists for the current task
        guard let originalMediaSelection = mediaSelectionMap[task] else { return }
 
        // Create a mutable copy and select the media selection option in the media selection group
        let mediaSelection = originalMediaSelection.mutableCopy() as! AVMutableMediaSelection
        mediaSelection.select(option, in: group)
 
        // Create a new download task with this media selection in its options
        let options = [AVAssetDownloadTaskMediaSelectionKey: mediaSelection]
        let task = downloadSession.makeAssetDownloadTask(asset: task.urlAsset,
                                                         assetTitle: assetTitle,
                                                         assetArtworkData: nil,
                                                         options: options)
 
        // Start media selection download
        task?.resume()
 
    } else {
        // All media selection downloads complete
    }
}

一旦开始下载,就可以开始同步播放

func downloadAndPlayAsset(_ asset: AVURLAsset) {
    // Create new AVAssetDownloadTask for the desired asset
    // Passing a nil options value indicates the highest available bitrate should be downloaded
    let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
                                                             assetTitle: assetTitle,
                                                             assetArtworkData: nil,
                                                             options: nil)!
    // Start task
    downloadTask.resume()
 
    // Create standard playback items and begin playback
    let playerItem = AVPlayerItem(asset: downloadTask.urlAsset)
    player = AVPlayer(playerItem: playerItem)
    player.play()
}

也可以播放下载完成的离线资源,使用在下载完成后指定为资源位置

func playOfflineAsset() {
    guard let assetPath = UserDefaults.standard.value(forKey: "assetPath") as? String else {
        // Present Error: No offline version of this asset available
        return
    }
    let baseURL = URL(fileURLWithPath: NSHomeDirectory())
    let assetURL = baseURL.appendingPathComponent(assetPath)
    let asset = AVURLAsset(url: assetURL)
    if let cache = asset.assetCache, cache.isPlayableOffline {
        // Set up player item and player and begin playback
    } else {
        // Present Error: No playable version of this asset exists offline
    }
}

 删除文件:

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)")
    }
}

AVPlayerItem的accessLog and errorLog属性用来读取日志文件。 

你可能感兴趣的:(iOS开发)