PQGPUImagePlayerView.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. //
  2. // PQGPUImagePlayer.swift
  3. // GPUImage_iOS
  4. //
  5. // Created by ak on 2020/8/27.
  6. // Copyright © 2020 Sunset Lake Software LLC. All rights reserved.
  7. // 功能:滤镜播放器 支持音频 https://juejin.im/post/6844904024760664078 这个有用
  8. import AVFoundation
  9. import AVKit
  10. import UIKit
  11. import ObjectMapper
  12. // import GPUImage
  13. struct AVAssetKey {
  14. static let tracks = "tracks"
  15. static let duration = "duration"
  16. static let metadata = "commonMetadata"
  17. }
  18. // 播放器状态
  19. public enum PQGPUImagePlayerViewStatus: Int {
  20. case playing = 10
  21. case pause = 20
  22. case stop = 30
  23. case error = 0
  24. case unknow = -1000
  25. }
  26. public class PQGPUImagePlayerView: UIView, RenderViewDelegate {
  27. public func willDisplayFramebuffer(renderView _: RenderView, framebuffer _: Framebuffer) {}
  28. public func didDisplayFramebuffer(renderView _: RenderView, framebuffer: Framebuffer) {
  29. // if(renderView.bounds.size.width = framebuffer.size.width && renderView.bounds.size.height = framebuffer.size.height){
  30. if GLint(mCanverSize.width) == framebuffer.size.width, GLint(mCanverSize.height) == framebuffer.size.height {
  31. // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  32. // DispatchQueue.main.async {
  33. // renderView.isHidden = false
  34. }
  35. // }
  36. }
  37. public func shouldDisplayNextFramebufferAfterMainThreadLoop() -> Bool {
  38. return false
  39. }
  40. public private(set) var playbackTime: TimeInterval = 0 {
  41. willSet {
  42. playbackTimeChangeClosure?(newValue)
  43. }
  44. }
  45. var mCanverSize: Size = Size(width: 0, height: 0)
  46. // 自动隐藏边框
  47. var isAutoHiden: Bool = false
  48. // 是否显示边框
  49. var isShowLine: Bool = true
  50. // 播放进度
  51. public var playbackTimeChangeClosure: ((_ time: TimeInterval) -> Void)?
  52. // 参数说明:1,当前时间 2,总时长 3,进度
  53. public var progress: ((Double, Double, Double) -> Void)?
  54. /// 预览区域点击回调
  55. var renderViewOnClickHandle: (() -> Void)?
  56. public private(set) var asset: AVAsset?
  57. public var duration: TimeInterval {
  58. return asset?.duration.seconds ?? 0
  59. }
  60. public private(set) var status: PQGPUImagePlayerViewStatus = .unknow {
  61. willSet {
  62. statusChangeClosure?(newValue)
  63. }
  64. }
  65. public var statusChangeClosure: ((_ status: PQGPUImagePlayerViewStatus) -> Void)?
  66. public private(set) var isReadyToPlay = false {
  67. willSet {
  68. assetLoadClosure?(newValue)
  69. }
  70. }
  71. public var assetLoadClosure: ((_ isReadyToPlay: Bool) -> Void)?
  72. /// Called when video finished
  73. /// This closure will not called if isLoop is true
  74. public var finishedClosure: (() -> Void)?
  75. /// Set this attribute to true will print debug info
  76. public var enableDebug = false {
  77. willSet {
  78. movie?.runBenchmark = newValue
  79. }
  80. }
  81. /// Setting this attribute before the end of the video works
  82. public var isLoop = false {
  83. willSet {
  84. movie?.loop = newValue
  85. }
  86. }
  87. /// The player will control the animationLayer of animation with the property `timeOffset`
  88. /// You can set up some animations in this layer like caption
  89. public var animationLayer: CALayer? {
  90. willSet {
  91. // Set speed to 0, use timeOffset to control the animation
  92. newValue?.speed = 0
  93. newValue?.timeOffset = playbackTime
  94. }
  95. didSet {
  96. oldValue?.removeFromSuperlayer()
  97. }
  98. }
  99. /// Add filters to this array and call updateAsset(_:) method
  100. public var filters: [ImageProcessingOperation] = []
  101. var movie: PQMoveInput?
  102. var speaker: SpeakerOutput?
  103. /// Volumn of original sounds in AVAsset
  104. public var originVolumn: Float = 1.0 {
  105. didSet {}
  106. }
  107. var playerLayer: AVPlayerLayer?
  108. var player: AVPlayer?
  109. var playerEmptyView: UIImageView!
  110. var borderLayer: CAShapeLayer?
  111. var mPlayeTimeRange: CMTimeRange?
  112. var mStickers: [PQEditVisionTrackMaterialsModel]?
  113. // 最后一次显示的sticker
  114. var lastshowSticker: PQEditVisionTrackMaterialsModel?
  115. // 是否显示时间条
  116. var showProgressLab: Bool = true
  117. var cacheFilters: [PQBaseFilter] = Array()
  118. //是否显示高斯
  119. var showGaussianBlur:Bool = false
  120. // 渲染区view
  121. private lazy var renderView: RenderView = {
  122. let view = RenderView()
  123. view.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  124. view.frame = self.bounds
  125. view.delegate = self
  126. let tap = UITapGestureRecognizer(target: self, action: #selector(RenderViewOnclick))
  127. view.addGestureRecognizer(tap)
  128. return view
  129. }()
  130. // 暂停播放view
  131. lazy var playView: UIImageView = {
  132. let view = UIImageView(frame: CGRect(x: (self.frame.size.width - 52) / 2, y: (self.frame.size.height - 52) / 2, width: 52, height: 52))
  133. view.image = UIImage().BF_Image(named: "gpuplayBtn")
  134. view.isHidden = true
  135. return view
  136. }()
  137. // 播放进度/总时长
  138. lazy var progressLab: UILabel = {
  139. let titleLab = UILabel(frame: CGRect(x: (self.frame.size.width - 140) / 2, y: 10, width: 140, height: 14))
  140. titleLab.font = UIFont.systemFont(ofSize: 14, weight: .medium)
  141. titleLab.textColor = UIColor.white
  142. titleLab.textAlignment = .center
  143. titleLab.text = ""
  144. titleLab.layer.shadowColor = UIColor.black.cgColor
  145. titleLab.layer.shadowOpacity = 0.3
  146. titleLab.layer.shadowOffset = .zero
  147. titleLab.layer.shadowRadius = 1
  148. return titleLab
  149. }()
  150. lazy var tipLab: UILabel = {
  151. let tipLab = UILabel(frame: CGRect(x: (self.frame.size.width - 100) / 2, y: (self.frame.size.height - 14) / 2, width: 100, height: 14))
  152. tipLab.font = UIFont.systemFont(ofSize: 14, weight: .medium)
  153. tipLab.textColor = UIColor.white
  154. tipLab.textAlignment = .center
  155. tipLab.text = "资源加载中..."
  156. tipLab.layer.shadowColor = UIColor.white.cgColor
  157. tipLab.layer.shadowOpacity = 0.5
  158. tipLab.layer.shadowOffset = .zero
  159. tipLab.layer.shadowRadius = 1
  160. tipLab.isHidden = true
  161. return tipLab
  162. }()
  163. required init?(coder _: NSCoder) {
  164. fatalError("init(coder:) has not been implemented")
  165. }
  166. override init(frame: CGRect) {
  167. super.init(frame: frame)
  168. addSubview(renderView)
  169. addSubview(playView)
  170. addSubview(progressLab)
  171. backgroundColor = PQBFConfig.shared.styleBackGroundColor
  172. playerEmptyView = UIImageView(frame: bounds)
  173. playerEmptyView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  174. playerEmptyView.image = UIImage().BF_Image(named: "playEmpty")
  175. playerEmptyView.contentMode = .center
  176. addSubview(playerEmptyView)
  177. addSubview(tipLab)
  178. }
  179. func showBorderLayer() {
  180. if borderLayer != nil {
  181. borderLayer?.removeFromSuperlayer()
  182. }
  183. // 线条颜色
  184. borderLayer = CAShapeLayer()
  185. borderLayer?.strokeColor = UIColor.hexColor(hexadecimal: "#FFFFFF").cgColor
  186. borderLayer?.fillColor = nil
  187. borderLayer?.path = UIBezierPath(rect: CGRect(x: 1, y: 1, width: bounds.width - 2, height: bounds.height - 2)).cgPath
  188. borderLayer?.frame = bounds
  189. borderLayer?.lineWidth = 2.0
  190. borderLayer?.lineCap = .round
  191. // 第一位是 线条长度 第二位是间距 nil时为实线
  192. if borderLayer != nil {
  193. renderView.layer.addSublayer(borderLayer!)
  194. }
  195. if isAutoHiden {
  196. borderLayer?.opacity = 0
  197. let groupAnimation = CAAnimationGroup()
  198. groupAnimation.beginTime = CACurrentMediaTime()
  199. groupAnimation.duration = 1
  200. groupAnimation.fillMode = .forwards
  201. groupAnimation.isRemovedOnCompletion = true
  202. groupAnimation.repeatCount = 3
  203. let opacity = CABasicAnimation(keyPath: "opacity")
  204. opacity.fromValue = 0
  205. opacity.toValue = 1
  206. opacity.isRemovedOnCompletion = true
  207. let opacity2 = CABasicAnimation(keyPath: "opacity")
  208. opacity2.fromValue = 1
  209. opacity2.toValue = 0
  210. opacity2.isRemovedOnCompletion = false
  211. groupAnimation.animations = [opacity, opacity2]
  212. borderLayer?.add(groupAnimation, forKey: nil)
  213. }
  214. }
  215. // 设置画布比例
  216. func resetCanvasFrame(frame: CGRect) {
  217. if self.frame.equalTo(frame) {
  218. BFLog(message: "新老值一样,不重置")
  219. return
  220. }
  221. self.frame = frame
  222. if isShowLine {
  223. showBorderLayer()
  224. }
  225. BFLog(message: "new frame is \(frame)")
  226. renderView.isHidden = true
  227. renderView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
  228. renderView.resatSize()
  229. playerEmptyView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
  230. tipLab.frame = CGRect(x: (self.frame.size.width - 100) / 2, y: (self.frame.size.height - 14) / 2, width: 100, height: 14)
  231. progressLab.frame = CGRect(x: (self.frame.size.width - 140) / 2, y: 10, width: 140, height: 14)
  232. playView.frame = CGRect(x: (self.frame.size.width - 52) / 2, y: (self.frame.size.height - 52) / 2, width: 52, height: 52)
  233. }
  234. override public func layoutSubviews() {
  235. super.layoutSubviews()
  236. }
  237. @objc func RenderViewOnclick() {
  238. if status == .playing {
  239. playView.isHidden = false
  240. pause()
  241. } else if status == .stop || status == .pause {
  242. playView.isHidden = true
  243. movie?.resume()
  244. speaker?.start()
  245. status = .playing
  246. }
  247. if renderViewOnClickHandle != nil {
  248. renderViewOnClickHandle!()
  249. }
  250. }
  251. func showPlayBtn(isHidden: Bool) {
  252. playView.isHidden = isHidden
  253. }
  254. deinit {
  255. stop()
  256. movie = nil
  257. speaker = nil
  258. }
  259. /// XXXX 这里的 URL 使用的是全路径 ,如果不是全的会 crash ,方便复用 (不用处理业务的文件放在哪里)
  260. func updateAsset(_ url: URL, videoComposition: AVVideoComposition? = nil, audioMixModel: PQVoiceModel? = nil, videoStickers: [PQEditVisionTrackMaterialsModel]? = nil) {
  261. // 每次初始化的时候设置初始值 为 nIl
  262. var audioMix: AVMutableAudioMix?
  263. var composition: AVMutableComposition?
  264. let asset = AVURLAsset(url: url, options: avAssertOptions)
  265. BFLog(message: "播放器初始化的音频时长\(asset.duration.seconds) url is \(url)")
  266. self.asset = asset
  267. if (audioMixModel != nil && audioMixModel?.localPath != nil) || (videoStickers != nil && (videoStickers?.count ?? 0) > 0) {
  268. BFLog(message: "有参加混音的数据。")
  269. (audioMix, composition) = PQPlayerViewModel.setupAudioMix(originAsset: asset, bgmData: audioMixModel, videoStickers: videoStickers)
  270. } else {
  271. audioMix = nil
  272. }
  273. isReadyToPlay = false
  274. asset.loadValuesAsynchronously(forKeys: ["tracks", "duration", "commonMetadata"]) { [weak self] in
  275. guard let strongSelf = self else { return }
  276. let tracksStatus = strongSelf.asset?.statusOfValue(forKey: AVAssetKey.tracks, error: nil) ?? .unknown
  277. let durationStatus = strongSelf.asset?.statusOfValue(forKey: AVAssetKey.duration, error: nil) ?? .unknown
  278. strongSelf.isReadyToPlay = tracksStatus == .loaded && durationStatus == .loaded
  279. }
  280. var audioSettings: [String: Any] = [
  281. AVFormatIDKey: kAudioFormatLinearPCM,
  282. ]
  283. if #available(iOS 14.0, *) {
  284. audioSettings[AVLinearPCMIsFloatKey] = false
  285. audioSettings[AVLinearPCMBitDepthKey] = 16
  286. }
  287. do {
  288. if composition != nil {
  289. BFLog(message: "composition 方式初始化")
  290. movie = try PQMoveInput(asset: composition!, videoComposition: videoComposition, audioMix: audioMix, playAtActualSpeed: true, loop: isLoop, audioSettings: audioSettings)
  291. } else {
  292. movie = try PQMoveInput(url: url, playAtActualSpeed: true, loop: isLoop, audioSettings: audioSettings)
  293. /* 测试代码
  294. let audioDecodeSettings = [AVFormatIDKey:kAudioFormatLinearPCM]
  295. let bundleURL = Bundle.main.resourceURL!
  296. let movieURL = URL(string:"11111.mp4", relativeTo:bundleURL)!
  297. movie = try MovieInput(url:movieURL, playAtActualSpeed:true, loop:true, audioSettings:audioDecodeSettings)
  298. */
  299. }
  300. movie!.runBenchmark = false
  301. movie!.synchronizedEncodingDebug = false
  302. } catch {
  303. status = .error
  304. if enableDebug {
  305. debugPrint(error)
  306. }
  307. }
  308. guard let movie = movie else { return }
  309. movie.progress = { [weak self, movie] currTime, duration, prgressValue in
  310. guard let strongSelf = self else { return }
  311. self?.findShowStikcer(currTime: movie.currentTime.seconds)
  312. self?.progress?(currTime, duration, prgressValue)
  313. DispatchQueue.main.async {
  314. strongSelf.playbackTime = movie.currentTime.seconds
  315. // Non-main thread change this property is not valid
  316. strongSelf.animationLayer?.timeOffset = strongSelf.playbackTime
  317. if strongSelf.showProgressLab {
  318. if duration < 1 {
  319. strongSelf.progressLab.text = "\(currTime.formatDurationToHMS()) / 00:01"
  320. } else {
  321. strongSelf.progressLab.text = "\(currTime.formatDurationToHMS()) / \(duration.formatDurationToHMS())"
  322. }
  323. }
  324. }
  325. }
  326. movie.completion = { [weak self] in
  327. guard let strongSelf = self else { return }
  328. DispatchQueue.main.async {
  329. strongSelf.status = .stop
  330. strongSelf.finishedClosure?()
  331. strongSelf.showPlayBtn(isHidden: false)
  332. }
  333. }
  334. speaker = SpeakerOutput()
  335. movie.audioEncodingTarget = speaker
  336. applyFilters()
  337. }
  338. func findShowStikcer(currTime: Float64) {
  339. if mStickers?.count ?? 0 == 0 {
  340. BFLog(message: "mStickers data is error")
  341. return
  342. }
  343. var currentSticker: PQEditVisionTrackMaterialsModel?
  344. var currentIdenx: Int = 0
  345. for (index, sticker) in mStickers!.enumerated() {
  346. if sticker.timelineIn <= currTime, sticker.timelineOut >= currTime {
  347. currentSticker = sticker
  348. currentIdenx = index
  349. break
  350. }
  351. }
  352. // 创建不同的filter
  353. if currentSticker == nil {
  354. BFLog(message: "sticker data is error")
  355. return
  356. }
  357. //
  358. if movie != nil && currentSticker != lastshowSticker {
  359. BFLog(message: "sticker timelineIn is: \(currentSticker!.timelineIn) timelineOut \(currentSticker!.timelineOut) in is :\(currentSticker!.model_in) in out is :\(currentSticker!.out) sticker location::: \(String(describing: currentSticker?.locationPath))")
  360. var showFitler: PQBaseFilter?
  361. if currentSticker!.type == StickerType.VIDEO.rawValue {
  362. showFitler = PQMoveFilter(movieSticker: currentSticker!)
  363. } else if currentSticker!.type == StickerType.IMAGE.rawValue {
  364. showFitler = PQImageFilter(sticker: currentSticker!)
  365. }
  366. movie!.removeAllTargets()
  367. let currentTarget: ImageSource = movie!
  368. if(currentSticker?.type == StickerType.IMAGE.rawValue && showGaussianBlur){
  369. //高斯层
  370. let json = currentSticker?.toJSONString(prettyPrint: false)
  371. if json == nil {
  372. BFLog(message: "数据转换有问题 跳转")
  373. return
  374. }
  375. let blurStickerModel: PQEditVisionTrackMaterialsModel? = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: json!)
  376. blurStickerModel?.canvasFillType = stickerContentMode.aspectFillStr.rawValue
  377. let showGaussianFitler:PQBaseFilter = PQImageFilter(sticker: blurStickerModel!)
  378. let iosb:GaussianBlur = GaussianBlur.init()
  379. iosb.blurRadiusInPixels = 20
  380. showGaussianFitler.addTarget(iosb)
  381. currentTarget.addTarget(showGaussianFitler, atTargetIndex: 0)
  382. iosb.addTarget(showFitler!)
  383. showFitler?.addTarget(renderView, atTargetIndex: 0)
  384. }else{
  385. currentTarget.addTarget(showFitler!, atTargetIndex: 0)
  386. showFitler?.addTarget(renderView, atTargetIndex: 0)
  387. }
  388. lastshowSticker = currentSticker
  389. }
  390. }
  391. /// 设置 filter 是否为 seek 状态
  392. func setEnableSeek(isSeek: Bool) {
  393. for filter in filters {
  394. (filter as? PQBaseFilter)?.enableSeek = isSeek
  395. }
  396. }
  397. private func applyFilters() {
  398. guard let movie = movie else { return }
  399. movie.removeAllTargets()
  400. var currentTarget: ImageSource = movie
  401. filters.forEach {
  402. let f = $0
  403. currentTarget.addTarget(f, atTargetIndex: 0)
  404. currentTarget = f
  405. }
  406. currentTarget.addTarget(renderView, atTargetIndex: 0)
  407. }
  408. }
  409. // MARK: Player control
  410. public extension PQGPUImagePlayerView {
  411. /// 开始播放
  412. /// - Parameter pauseFirstFrame: 是否暂停到第一帧
  413. func play(pauseFirstFrame: Bool = false, playeTimeRange: CMTimeRange = CMTimeRange()) {
  414. DispatchQueue.main.async {
  415. self.playerEmptyView.isHidden = true
  416. self.playView.isHidden = !pauseFirstFrame
  417. self.renderView.isHidden = false
  418. self.progressLab.isHidden = false
  419. }
  420. // guard status != .playing else {
  421. // BFLog(message: "已经是播放状态")
  422. // return
  423. // }
  424. // 如果没有设置开始结束时长 使用默认音频总时长(创作工具就不会传值)
  425. if CMTIMERANGE_IS_INVALID(playeTimeRange) {
  426. let endTime = CMTime(value: CMTimeValue(CMTimeGetSeconds(asset?.duration ?? .zero) * 600), timescale: 600)
  427. mPlayeTimeRange = CMTimeRange(start: .zero, end: endTime)
  428. } else {
  429. mPlayeTimeRange = playeTimeRange
  430. }
  431. // 清空音频缓存
  432. speaker?.clearBuffer()
  433. movie?.start(timeRange: mPlayeTimeRange ?? CMTimeRange())
  434. speaker?.start()
  435. status = pauseFirstFrame ? .pause : .playing
  436. }
  437. // 快进
  438. func seek(to time: CMTime) {
  439. mPlayeTimeRange?.start = time
  440. play(pauseFirstFrame: false, playeTimeRange: mPlayeTimeRange ?? .zero)
  441. }
  442. // 暂停
  443. func pause() {
  444. guard status != .pause else {
  445. return
  446. }
  447. movie?.pause()
  448. speaker?.pause()
  449. status = .pause
  450. showPlayBtn(isHidden: false)
  451. }
  452. // 停止f解码状态
  453. func stop() {
  454. // guard status != .stop else {
  455. // return
  456. // }
  457. movie?.cancel()
  458. speaker?.cancel()
  459. status = .stop
  460. }
  461. // 清空播放器状态,到空状态
  462. func clearPlayerView() {
  463. playerEmptyView.isHidden = false
  464. renderView.isHidden = true
  465. progressLab.isHidden = true
  466. }
  467. // 显示提示文字
  468. func showTip(show: Bool) {
  469. BFLog(message: "showTip \(show)")
  470. tipLab.isHidden = !show
  471. if show {
  472. playerEmptyView.isHidden = true
  473. renderView.isHidden = true
  474. progressLab.isHidden = true
  475. }
  476. }
  477. }
  478. // MARK: Filter 操作
  479. public extension PQGPUImagePlayerView {
  480. // 添加 filter
  481. func appendFilter(_ filter: ImageProcessingOperation) {
  482. filters.append(filter)
  483. }
  484. // 添加一组filters
  485. func appendFilters(_ newFilters: [ImageProcessingOperation]) {
  486. filters = filters + newFilters
  487. }
  488. // 移除所有filter
  489. func removeAllFilters() {
  490. filters.removeAll()
  491. }
  492. // 重置所有 filer
  493. func appendFiltersClearOldFilter(_ newFilters: [ImageProcessingOperation]) {
  494. filters.removeAll()
  495. filters = newFilters
  496. }
  497. // // 重置所有 filer
  498. // func appendStickers(stickers: [PQEditVisionTrackMaterialsModel]) {
  499. //
  500. // mStickers = stickers
  501. // }
  502. }