前言
PAG 是腾讯多媒体技术委员会下 AVGenerator Oteam开源协同小组自主研发的一套完整的动效工作流解决方案,和业界常用的动效工作流解决方案相比,PAG支持的 AE 特性更多,覆盖的平台更广(Android、iOS、Web、macOS、Windows和Linux),性能方面也做了深层次的优化,支持文本和占位图编辑替换,可以与视频编辑场景紧密结合。
PAG的优势
这里简单总结一下PAG的优势
1. 相同动效相对Lottie体积更小
2. 支持文本和占位图的编辑和替换
目前,绝大部分的视频编辑App,在贴纸字幕这块的实现都分为两种类型,要么贴纸有丰富的动效,但不可编辑;要么可以编辑文本,但只有静态或者简单的动效,大大束缚了设计师,降低了视频的整体观感。而PAG方案让贴纸有精美动效的前提下,还可以保持强大的编辑性,让使用者的个性化元素得到更好的呈现。
3. 支持播放声音
4. 性能更好
基于 C++ 和 OpenGL 硬件加速渲染,除了能做到两端渲染完全一致外,应用了游戏渲染里的大量的优化经验,从中间渲染数据到局部位图的多级缓存架构,每帧渲染耗时平均可以做到Lottie的50%左右。
已应用的部分软件
问题和解决方案
目前官方的libpag库只支持播放本地PAG资源,不支持网络PAG资源,为此这里提供一个可播放网络PAG资源的方案LXYPAGPlayerSDK
核心代码
1. 使用
1.1 创建LXYPAGView
lazy var pagView :LXYPAGView = {
let pagView = LXYPAGView()
return pagView
}()
1.2 播放线上资源
//MARK: - 播放线上资源
private func playOnlineResource(){
let config = LXYPAGConfig()
config.resourceStr = "http://xxx.pag"
pagView.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
self.pagView.playAnim(config)
}
2. LXPAGView内部实现
2.1 public func playAnim(_ config: LXYPAGConfig)
方法中通过判断config.resourceStr
是否以http开头判断是否是网络资源,如果是本地资源则调用 playLocalPathAnim(config)
播放。,如果没有则进入网络下载环节
if config.resourceStr.hasPrefix("http") == false {
//本地数据
playLocalPathAnim(config)
return
}
2.2 如果传入的config.resourceStr
是网络地址,则通过缓存单例LXYPAGCacheManager
查询本地是否已经有缓存数据
let cacheKey = LXYPAGCacheManager.shareInstance.cacheKey(config.resourceStr)
let cachePath = LXYPAGCacheManager.shareInstance.filepath(cacheKey)
if cachePath.count > 0 && FileManager.default.fileExists(atPath: cachePath) {
//本地有则拿本地数据进行播放
config.resourceStr = cachePath
playLocalPathAnim(config)
return
}
2.3 本地查询没有缓存怎进入下载环节
LXYPAGManager.shareInstance.downloader.loadData(config.resourceStr) { locationPath, err in
if err != nil {
self.delegate?.playErroronView(self, err)
return
}
guard let locationPath = locationPath else{
return
}
do{
let targetPath = try LXYPAGCacheManager.shareInstance.moveTempData((locationPath as NSURL).path, cacheKey)
if let targetPath = targetPath{
config.resourceStr = targetPath
self.playLocalPathAnim(config)
}
}catch let error{
self.delegate?.playErroronView(self, error as NSError)
}
}
}
2.4 playLocalPathAnim(_ config: LXYPAGConfig)
实现
//MARK: - 播放PAG
private func playLocalPathAnim(_ config: LXYPAGConfig){
if isPlaying() {
stopPlay()
}
let path = config.resourceStr
if path.count == 0 || FileManager.default.fileExists(atPath: path) == false {
let error = NSError.localResourceNotExist(path)
delegate?.playErroronView(self, error)
return
}
pagFile = PAGFile.load(path)
//动态替换资源,未实现
pagView.setProgress(0)
customizePlay()
//播放音效,未实现
pagView.play()
//第一次播放时检查缓存大小,如果超出则删除
if checkLocalCaches {
LXYPAGCacheManager.shareInstance.removeFolderIfExceedsSize()
checkLocalCaches = true
}
}
3. LXYDownloader
里的下载逻辑
3.1 根据传入的url去队列中查询该下载路径下的下载任务是否已经存在队列中,如果存在再不重新发起网络请求,只是把这次请求的回调downloadCallback
存放到队列queue中
guard let url = URL(string: urlStr) else {
let error = NSError.pathNotFound(urlStr)
downloadCallback?(nil,error)
return nil
}
let questTask = queue.queryTask(urlStr)
if let questTask = questTask {
//在下载队列中找到对应的下载请求
if let downloadCallback = downloadCallback {
queue.updateQueueItemCallback(urlStr, callback: downloadCallback)
}
return questTask
}
3.2 下载完成后通过url去查询callbacks
并让对应的quesTastTask出列
let requestTask = self.session.downloadTask(with: url) {
[weak self] (localPath, respon, error ) in
guard let self = self else {
return
}
var err: NSError?
if let respon = respon as? HTTPURLResponse{
let statusCode = respon.statusCode
if statusCode 300 {
err = NSError.downloadStatueError(urlStr, statueCode: statusCode)
}
}
let callbacks = queue.dequeueCallback(urlStr)
guard let callbacks = callbacks else {
return
}
for itemCallback in callbacks{
itemCallback(localPath,err)
}
}
常用使用场景介绍
创建LXYPAGView
lazy var pagView :LXYPAGView = {
let pagView = LXYPAGView()
return pagView
}()
//MARK: - 播放线上资源
private func playOnlineResource(){
let config = LXYPAGConfig()
config.resourceStr = "http://xxx.pag"
pagView.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
self.pagView.playAnim(config)
}
//MARK: - 区间播放
private func playInFrame(){
let config = LXYPAGConfig()
if let path = Bundle.main.path(forResource: "like", ofType: ".pag") {
config.resourceStr = path
config.InFramePlayRange = NSRange(location: 30, length: 100)
}
pagView.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
self.pagView.playAnim(config)
}
private func playInFrame(){
let config = LXYPAGConfig()
if let path = Bundle.main.path(forResource: "login_page_animation", ofType: ".pag") {
config.resourceStr = path
config.speed = 2
config.loop = 0
}
pagView.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
self.pagView.playAnim(config)
}
主要流程
githut地址
github.com/liang152091…
引用文章
baijiahao.baidu.com/s?id=167924…
baijiahao.baidu.com/s?id=167843…