凹凸,因为这是一个高度评价的问题,在线类似的问题要么有过时的答案,要么不是很好。AVKit
整个想法与and非常直接AVFoundation
,这意味着不再依赖第三方库。唯一的问题是它需要一些修补才能将各个部分组合在一起。
AVFoundation
的Player()
初始化url
显然不是线程安全的,或者说它不应该是。这意味着,无论您如何在后台线程中对其进行初始化,播放器属性都将加载到主队列中,从而导致 UI 冻结,尤其是在UITableView
s 和UICollectionViews
. 为了解决这个问题,Apple 提供了AVAsset
它接受一个 URL 并协助加载媒体属性,如轨道、播放、持续时间等,并且可以异步执行,最好的部分是此加载过程是可取消的(与其他调度队列后台线程不同,后者可能无法结束任务直截了当)。这意味着,当您在表格视图或集合视图上快速滚动时,无需担心在后台挥之不去的僵尸线程,最终会在内存中堆积一大堆未使用的对象。这个功能很棒,如果它正在进行中cancellable
,我们可以取消任何延迟的异步加载,但仅限于单元出队期间。AVAsset
异步加载过程可以由该loadValuesAsynchronously
方法调用,并且可以在以后的任何时间(如果仍在进行中)取消(随意)。
不要忘记使用loadValuesAsynchronously
. 在Swift (3/4)中,如果异步过程失败(由于网络速度慢等),您将如何异步加载视频并处理情况 -
TL;博士
播放视频
let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING))
let keys: [String] = ["playable"]
var player: AVPlayer!
asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: "playable", error: &error)
switch status {
case .loaded:
DispatchQueue.main.async {
let item = AVPlayerItem(asset: asset)
self.player = AVPlayer(playerItem: item)
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds
self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer)
self.player.isMuted = true
self.player.play()
}
break
case .failed:
DispatchQueue.main.async {
//do something, show alert, put a placeholder image etc.
}
break
case .cancelled:
DispatchQueue.main.async {
//do something, show alert, put a placeholder image etc.
}
break
default:
break
}
})
注意:
根据您的应用程序想要实现的目标,您可能仍需要进行一些修改来调整它以在 aUITableView
或UICollectionView
. 您可能还需要在属性上实现一些 KVOAVPlayerItem
才能使其工作,并且 SO 中有很多帖子AVPlayerItem
详细讨论了 KVO。
循环播放资产(视频循环/GIF)
要循环播放视频,您可以使用与上述相同的方法并引入AVPlayerLooper
. 这是一个循环播放视频(或者可能是 GIF 风格的短视频)的示例代码。请注意duration
我们的视频循环所需的密钥的使用。
let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING))
let keys: [String] = ["playable","duration"]
var player: AVPlayer!
var playerLooper: AVPlayerLooper!
asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: "duration", error: &error)
switch status {
case .loaded:
DispatchQueue.main.async {
let playerItem = AVPlayerItem(asset: asset)
self.player = AVQueuePlayer()
let playerLayer = AVPlayerLayer(player: self.player)
//define Timerange for the loop using asset.duration
let duration = playerItem.asset.duration
let start = CMTime(seconds: duration.seconds * 0, preferredTimescale: duration.timescale)
let end = CMTime(seconds: duration.seconds * 1, preferredTimescale: duration.timescale)
let timeRange = CMTimeRange(start: start, end: end)
self.playerLooper = AVPlayerLooper(player: self.player as! AVQueuePlayer, templateItem: playerItem, timeRange: timeRange)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds
self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer)
self.player.isMuted = true
self.player.play()
}
break
case .failed:
DispatchQueue.main.async {
//do something, show alert, put a placeholder image etc.
}
break
case .cancelled:
DispatchQueue.main.async {
//do something, show alert, put a placeholder image etc.
}
break
default:
break
}
})
编辑:根据文档,AVPlayerLooper
需要duration
完全加载资产的属性才能循环播放视频。此外,如果您想要无限循环,初始化timeRange: timeRange
中的开始和结束时间范围确实是可选的。AVPlayerLooper
自从我发布这个答案后,我也意识到AVPlayerLooper
循环播放视频的准确率只有 70-80%,尤其是当您AVAsset
需要从 URL 流式传输视频时。为了解决这个问题,有一种完全不同(但很简单)的方法来循环播放视频-
//this will loop the video since this is a Gif
let interval = CMTime(value: 1, timescale: 2)
self.timeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in
if let totalDuration = self.player?.currentItem?.duration{
if progressTime == totalDuration{
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
}
})