PQGPUImagePlayerView.swift 19 KB

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