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

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

使用AVAssetDownloadURLSession
<https://developer.apple.com/documentation/avfoundation/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
<https://developer.apple.com/documentation/avfoundation/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:
<https://developer.apple.com/documentation/foundation/nsurlsession/1411578-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:
<https://developer.apple.com/documentation/foundation/urlsessiondownloaddelegate/1411575-urlsession>
方法不同的时,用户不应该修该下载资源的位置,它是在系统的控制之下的,传入的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:
<https://developer.apple.com/documentation/avfoundation/avassetdownloaddelegate/1621023-urlsession>
会被调用,保存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
<https://developer.apple.com/documentation/avfoundation/avplayeritem/1388499-accesslog>
 and errorLog
<https://developer.apple.com/documentation/avfoundation/avplayeritem/1387573-errorlog>
属性用来读取日志文件。 

<>