ZLEditVideoViewController.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. //
  2. // ZLEditVideoViewController.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/8/30.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. import Photos
  28. public class ZLEditVideoViewController: UIViewController {
  29. static let frameImageSize = CGSize(width: 50.0 * 2.0 / 3.0, height: 50.0)
  30. let avAsset: AVAsset
  31. let animateDismiss: Bool
  32. var cancelBtn: UIButton!
  33. var doneBtn: UIButton!
  34. var timer: Timer?
  35. var playerLayer: AVPlayerLayer!
  36. var collectionView: UICollectionView!
  37. var frameImageBorderView: ZLEditVideoFrameImageBorderView!
  38. var leftSideView: UIImageView!
  39. var rightSideView: UIImageView!
  40. var leftSidePan: UIPanGestureRecognizer!
  41. var rightSidePan: UIPanGestureRecognizer!
  42. var indicator: UIView!
  43. var measureCount = 0
  44. lazy var interval: TimeInterval = {
  45. let assetDuration = round(self.avAsset.duration.seconds)
  46. return min(assetDuration, TimeInterval(ZLPhotoConfiguration.default().maxEditVideoTime)) / 10
  47. }()
  48. var requestFrameImageQueue: OperationQueue!
  49. var avAssetRequestID = PHInvalidImageRequestID
  50. var videoRequestID = PHInvalidImageRequestID
  51. var frameImageCache: [Int: UIImage] = [:]
  52. var requestFailedFrameImageIndex: [Int] = []
  53. var shouldLayout = true
  54. lazy var generator: AVAssetImageGenerator = {
  55. let g = AVAssetImageGenerator(asset: self.avAsset)
  56. g.maximumSize = CGSize(width: ZLEditVideoViewController.frameImageSize.width * 3, height: ZLEditVideoViewController.frameImageSize.height * 3)
  57. g.appliesPreferredTrackTransform = true
  58. g.requestedTimeToleranceBefore = .zero
  59. g.requestedTimeToleranceAfter = .zero
  60. g.apertureMode = .productionAperture
  61. return g
  62. }()
  63. @objc public var editFinishBlock: ( (URL?) -> Void )?
  64. public override var prefersStatusBarHidden: Bool {
  65. return true
  66. }
  67. public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  68. return .portrait
  69. }
  70. deinit {
  71. zl_debugPrint("ZLEditVideoViewController deinit")
  72. self.cleanTimer()
  73. self.requestFrameImageQueue.cancelAllOperations()
  74. if self.avAssetRequestID > PHInvalidImageRequestID {
  75. PHImageManager.default().cancelImageRequest(self.avAssetRequestID)
  76. }
  77. if self.videoRequestID > PHInvalidImageRequestID {
  78. PHImageManager.default().cancelImageRequest(self.videoRequestID)
  79. }
  80. }
  81. /// initialize
  82. /// - Parameters:
  83. /// - avAsset: AVAsset对象,需要传入本地视频,网络视频不支持
  84. /// - animateDismiss: 退出界面时是否显示dismiss动画
  85. @objc public init(avAsset: AVAsset, animateDismiss: Bool = false) {
  86. self.avAsset = avAsset
  87. self.animateDismiss = animateDismiss
  88. super.init(nibName: nil, bundle: nil)
  89. }
  90. required init?(coder: NSCoder) {
  91. fatalError("init(coder:) has not been implemented")
  92. }
  93. public override func viewDidLoad() {
  94. super.viewDidLoad()
  95. self.setupUI()
  96. self.requestFrameImageQueue = OperationQueue()
  97. self.requestFrameImageQueue.maxConcurrentOperationCount = 10
  98. NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
  99. NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
  100. }
  101. public override func viewDidAppear(_ animated: Bool) {
  102. super.viewDidAppear(animated)
  103. self.analysisAssetImages()
  104. }
  105. public override func viewDidLayoutSubviews() {
  106. super.viewDidLayoutSubviews()
  107. guard self.shouldLayout else {
  108. return
  109. }
  110. self.shouldLayout = false
  111. zl_debugPrint("edit video layout subviews")
  112. var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
  113. if #available(iOS 11.0, *) {
  114. insets = self.view.safeAreaInsets
  115. }
  116. let btnH = ZLLayout.bottomToolBtnH
  117. let bottomBtnAndColSpacing: CGFloat = 20
  118. let playerLayerY = insets.top + 20
  119. let diffBottom = btnH + ZLEditVideoViewController.frameImageSize.height + bottomBtnAndColSpacing + insets.bottom + 30
  120. self.playerLayer.frame = CGRect(x: 15, y: insets.top + 20, width: self.view.bounds.width - 30, height: self.view.bounds.height - playerLayerY - diffBottom)
  121. let cancelBtnW = localLanguageTextValue(.cancel).boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: btnH)).width
  122. self.cancelBtn.frame = CGRect(x: 20, y: self.view.bounds.height - insets.bottom - btnH, width: cancelBtnW, height: btnH)
  123. let doneBtnW = localLanguageTextValue(.done).boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: btnH)).width + 20
  124. self.doneBtn.frame = CGRect(x: self.view.bounds.width-doneBtnW-20, y: self.view.bounds.height - insets.bottom - btnH, width: doneBtnW, height: btnH)
  125. self.collectionView.frame = CGRect(x: 0, y: self.doneBtn.frame.minY - bottomBtnAndColSpacing - ZLEditVideoViewController.frameImageSize.height, width: self.view.bounds.width, height: ZLEditVideoViewController.frameImageSize.height)
  126. let frameViewW = ZLEditVideoViewController.frameImageSize.width * 10
  127. self.frameImageBorderView.frame = CGRect(x: (self.view.bounds.width - frameViewW)/2, y: self.collectionView.frame.minY, width: frameViewW, height: ZLEditVideoViewController.frameImageSize.height)
  128. // 左右拖动view
  129. let leftRightSideViewW = ZLEditVideoViewController.frameImageSize.width/2
  130. self.leftSideView.frame = CGRect(x: self.frameImageBorderView.frame.minX, y: self.collectionView.frame.minY, width: leftRightSideViewW, height: ZLEditVideoViewController.frameImageSize.height)
  131. let rightSideViewX = self.view.bounds.width - self.frameImageBorderView.frame.minX - leftRightSideViewW
  132. self.rightSideView.frame = CGRect(x: rightSideViewX, y: self.collectionView.frame.minY, width: leftRightSideViewW, height: ZLEditVideoViewController.frameImageSize.height)
  133. self.frameImageBorderView.validRect = self.frameImageBorderView.convert(self.clipRect(), from: self.view)
  134. }
  135. func setupUI() {
  136. self.view.backgroundColor = .black
  137. self.playerLayer = AVPlayerLayer()
  138. self.playerLayer.videoGravity = .resizeAspect
  139. self.view.layer.addSublayer(self.playerLayer)
  140. let layout = UICollectionViewFlowLayout()
  141. layout.itemSize = ZLEditVideoViewController.frameImageSize
  142. layout.minimumLineSpacing = 0
  143. layout.minimumInteritemSpacing = 0
  144. layout.scrollDirection = .horizontal
  145. self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
  146. self.collectionView.delegate = self
  147. self.collectionView.dataSource = self
  148. self.collectionView.showsHorizontalScrollIndicator = false
  149. self.view.addSubview(self.collectionView)
  150. ZLEditVideoFrameImageCell.zl_register(self.collectionView)
  151. self.frameImageBorderView = ZLEditVideoFrameImageBorderView()
  152. self.frameImageBorderView.isUserInteractionEnabled = false
  153. self.view.addSubview(self.frameImageBorderView)
  154. self.indicator = UIView()
  155. self.indicator.backgroundColor = UIColor.white.withAlphaComponent(0.7)
  156. self.view.addSubview(self.indicator)
  157. self.leftSideView = UIImageView(image: getImage("zl_ic_left"))
  158. self.leftSideView.isUserInteractionEnabled = true
  159. self.view.addSubview(self.leftSideView)
  160. self.leftSidePan = UIPanGestureRecognizer(target: self, action: #selector(leftSidePanAction(_:)))
  161. self.leftSidePan.delegate = self
  162. self.view.addGestureRecognizer(self.leftSidePan)
  163. self.rightSideView = UIImageView(image: getImage("zl_ic_right"))
  164. self.rightSideView.isUserInteractionEnabled = true
  165. self.view.addSubview(self.rightSideView)
  166. self.rightSidePan = UIPanGestureRecognizer(target: self, action: #selector(rightSidePanAction(_:)))
  167. self.rightSidePan.delegate = self
  168. self.view.addGestureRecognizer(self.rightSidePan)
  169. self.collectionView.panGestureRecognizer.require(toFail: self.leftSidePan)
  170. self.collectionView.panGestureRecognizer.require(toFail: self.rightSidePan)
  171. self.rightSidePan.require(toFail: self.leftSidePan)
  172. self.cancelBtn = UIButton(type: .custom)
  173. self.cancelBtn.setTitle(localLanguageTextValue(.cancel), for: .normal)
  174. self.cancelBtn.setTitleColor(.bottomToolViewBtnNormalTitleColor, for: .normal)
  175. self.cancelBtn.titleLabel?.font = ZLLayout.bottomToolTitleFont
  176. self.cancelBtn.addTarget(self, action: #selector(cancelBtnClick), for: .touchUpInside)
  177. self.view.addSubview(self.cancelBtn)
  178. self.doneBtn = UIButton(type: .custom)
  179. self.doneBtn.setTitle(localLanguageTextValue(.done), for: .normal)
  180. self.doneBtn.setTitleColor(.bottomToolViewBtnNormalTitleColor, for: .normal)
  181. self.doneBtn.titleLabel?.font = ZLLayout.bottomToolTitleFont
  182. self.doneBtn.addTarget(self, action: #selector(doneBtnClick), for: .touchUpInside)
  183. self.doneBtn.backgroundColor = .bottomToolViewBtnNormalBgColor
  184. self.doneBtn.layer.masksToBounds = true
  185. self.doneBtn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
  186. self.view.addSubview(self.doneBtn)
  187. }
  188. @objc func cancelBtnClick() {
  189. self.dismiss(animated: self.animateDismiss, completion: nil)
  190. }
  191. @objc func doneBtnClick() {
  192. self.cleanTimer()
  193. let d = CGFloat(self.interval) * self.clipRect().width / ZLEditVideoViewController.frameImageSize.width
  194. if Second(round(d)) < ZLPhotoConfiguration.default().minSelectVideoDuration {
  195. let message = String(format: localLanguageTextValue(.shorterThanMaxVideoDuration), ZLPhotoConfiguration.default().minSelectVideoDuration)
  196. showAlertView(message, self)
  197. return
  198. }
  199. if Second(round(d)) > ZLPhotoConfiguration.default().maxSelectVideoDuration {
  200. let message = String(format: localLanguageTextValue(.longerThanMaxVideoDuration), ZLPhotoConfiguration.default().maxSelectVideoDuration)
  201. showAlertView(message, self)
  202. return
  203. }
  204. if d == round(CGFloat(self.avAsset.duration.seconds)) {
  205. self.dismiss(animated: self.animateDismiss) {
  206. self.editFinishBlock?(nil)
  207. }
  208. return
  209. }
  210. let hud = ZLProgressHUD(style: ZLPhotoConfiguration.default().hudStyle)
  211. hud.show()
  212. ZLVideoManager.exportEditVideo(for: avAsset, range: self.getTimeRange()) { [weak self] (url, error) in
  213. hud.hide()
  214. if let er = error {
  215. showAlertView(er.localizedDescription, self)
  216. } else if url != nil {
  217. self?.dismiss(animated: self?.animateDismiss ?? false) {
  218. self?.editFinishBlock?(url)
  219. }
  220. }
  221. }
  222. }
  223. @objc func leftSidePanAction(_ pan: UIPanGestureRecognizer) {
  224. let point = pan.location(in: self.view)
  225. if pan.state == .began {
  226. self.frameImageBorderView.layer.borderColor = UIColor(white: 1, alpha: 0.4).cgColor
  227. self.cleanTimer()
  228. } else if pan.state == .changed {
  229. let minX = self.frameImageBorderView.frame.minX
  230. let maxX = self.rightSideView.frame.minX - self.leftSideView.frame.width
  231. var frame = self.leftSideView.frame
  232. frame.origin.x = min(maxX, max(minX, point.x))
  233. self.leftSideView.frame = frame
  234. self.frameImageBorderView.validRect = self.frameImageBorderView.convert(self.clipRect(), from: self.view)
  235. self.playerLayer.player?.seek(to: self.getStartTime(), toleranceBefore: .zero, toleranceAfter: .zero)
  236. } else if pan.state == .ended || pan.state == .cancelled {
  237. self.frameImageBorderView.layer.borderColor = UIColor.clear.cgColor
  238. self.startTimer()
  239. }
  240. }
  241. @objc func rightSidePanAction(_ pan: UIPanGestureRecognizer) {
  242. let point = pan.location(in: self.view)
  243. if pan.state == .began {
  244. self.frameImageBorderView.layer.borderColor = UIColor(white: 1, alpha: 0.4).cgColor
  245. self.cleanTimer()
  246. } else if pan.state == .changed {
  247. let minX = self.leftSideView.frame.maxX
  248. let maxX = self.frameImageBorderView.frame.maxX - self.rightSideView.frame.width
  249. var frame = self.rightSideView.frame
  250. frame.origin.x = min(maxX, max(minX, point.x))
  251. self.rightSideView.frame = frame
  252. self.frameImageBorderView.validRect = self.frameImageBorderView.convert(self.clipRect(), from: self.view)
  253. self.playerLayer.player?.seek(to: self.getStartTime(), toleranceBefore: .zero, toleranceAfter: .zero)
  254. } else if pan.state == .ended || pan.state == .cancelled {
  255. self.frameImageBorderView.layer.borderColor = UIColor.clear.cgColor
  256. self.startTimer()
  257. }
  258. }
  259. @objc func appWillResignActive() {
  260. self.cleanTimer()
  261. self.indicator.layer.removeAllAnimations()
  262. }
  263. @objc func appDidBecomeActive() {
  264. self.startTimer()
  265. }
  266. func analysisAssetImages() {
  267. let duration = round(self.avAsset.duration.seconds)
  268. guard duration > 0 else {
  269. self.showFetchFailedAlert()
  270. return
  271. }
  272. let item = AVPlayerItem(asset: self.avAsset)
  273. let player = AVPlayer(playerItem: item)
  274. self.playerLayer.player = player
  275. self.startTimer()
  276. self.measureCount = Int(duration / self.interval)
  277. self.collectionView.reloadData()
  278. self.requestVideoMeasureFrameImage()
  279. }
  280. func requestVideoMeasureFrameImage() {
  281. for i in 0..<self.measureCount {
  282. let mes = TimeInterval(i) * self.interval
  283. let time = CMTimeMakeWithSeconds(Float64(mes), preferredTimescale: self.avAsset.duration.timescale)
  284. let operation = ZLEditVideoFetchFrameImageOperation(generator: self.generator, time: time) { [weak self] (image, time) in
  285. self?.frameImageCache[Int(i)] = image
  286. let cell = self?.collectionView.cellForItem(at: IndexPath(row: Int(i), section: 0)) as? ZLEditVideoFrameImageCell
  287. cell?.imageView.image = image
  288. if image == nil {
  289. self?.requestFailedFrameImageIndex.append(i)
  290. }
  291. }
  292. self.requestFrameImageQueue.addOperation(operation)
  293. }
  294. }
  295. @objc func playPartVideo() {
  296. self.playerLayer.player?.seek(to: self.getStartTime(), toleranceBefore: .zero, toleranceAfter: .zero)
  297. if (self.playerLayer.player?.rate ?? 0) == 0 {
  298. self.playerLayer.player?.play()
  299. }
  300. }
  301. func startTimer() {
  302. self.cleanTimer()
  303. let duration = self.interval * TimeInterval(self.clipRect().width / ZLEditVideoViewController.frameImageSize.width)
  304. self.timer = Timer.scheduledTimer(timeInterval: duration, target: ZLWeakProxy(target: self), selector: #selector(playPartVideo), userInfo: nil, repeats: true)
  305. self.timer?.fire()
  306. RunLoop.main.add(self.timer!, forMode: .common)
  307. self.indicator.isHidden = false
  308. self.indicator.frame = CGRect(x: self.leftSideView.frame.minX, y: self.leftSideView.frame.minY, width: 2, height: self.leftSideView.frame.height)
  309. self.indicator.layer.removeAllAnimations()
  310. UIView.animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveLinear, .repeat], animations: {
  311. self.indicator.frame = CGRect(x: self.rightSideView.frame.maxX-2, y: self.rightSideView.frame.minY, width: 2, height: self.rightSideView.frame.height)
  312. }, completion: nil)
  313. }
  314. func cleanTimer() {
  315. self.timer?.invalidate()
  316. self.timer = nil
  317. self.indicator.layer.removeAllAnimations()
  318. self.indicator.isHidden = true
  319. self.playerLayer.player?.pause()
  320. }
  321. func getStartTime() -> CMTime {
  322. var rect = self.collectionView.convert(self.clipRect(), from: self.view)
  323. rect.origin.x -= self.frameImageBorderView.frame.minX
  324. let second = max(0, CGFloat(self.interval) * rect.minX / ZLEditVideoViewController.frameImageSize.width)
  325. return CMTimeMakeWithSeconds(Float64(second), preferredTimescale: self.avAsset.duration.timescale)
  326. }
  327. func getTimeRange() -> CMTimeRange {
  328. let start = self.getStartTime()
  329. let d = CGFloat(self.interval) * self.clipRect().width / ZLEditVideoViewController.frameImageSize.width
  330. let duration = CMTimeMakeWithSeconds(Float64(d), preferredTimescale: self.avAsset.duration.timescale)
  331. return CMTimeRangeMake(start: start, duration: duration)
  332. }
  333. func clipRect() -> CGRect {
  334. var frame = CGRect.zero
  335. frame.origin.x = self.leftSideView.frame.minX
  336. frame.origin.y = self.leftSideView.frame.minY
  337. frame.size.width = self.rightSideView.frame.maxX - frame.minX
  338. frame.size.height = self.leftSideView.frame.height
  339. return frame
  340. }
  341. func showFetchFailedAlert() {
  342. let alert = UIAlertController(title: nil, message: localLanguageTextValue(.iCloudVideoLoadFaild), preferredStyle: .alert)
  343. let action = UIAlertAction(title: localLanguageTextValue(.ok), style: .default) { (_) in
  344. self.dismiss(animated: false, completion: nil)
  345. }
  346. alert.addAction(action)
  347. self.showDetailViewController(alert, sender: nil)
  348. }
  349. }
  350. extension ZLEditVideoViewController: UIGestureRecognizerDelegate {
  351. public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  352. if gestureRecognizer == self.leftSidePan {
  353. let point = gestureRecognizer.location(in: self.view)
  354. let frame = self.leftSideView.frame
  355. let outerFrame = frame.inset(by: UIEdgeInsets(top: -20, left: -40, bottom: -20, right: -20))
  356. return outerFrame.contains(point)
  357. } else if gestureRecognizer == self.rightSidePan {
  358. let point = gestureRecognizer.location(in: self.view)
  359. let frame = self.rightSideView.frame
  360. let outerFrame = frame.inset(by: UIEdgeInsets(top: -20, left: -20, bottom: -20, right: -40))
  361. return outerFrame.contains(point)
  362. }
  363. return true
  364. }
  365. }
  366. extension ZLEditVideoViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  367. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  368. self.cleanTimer()
  369. self.playerLayer.player?.seek(to: self.getStartTime(), toleranceBefore: .zero, toleranceAfter: .zero)
  370. }
  371. public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  372. if !decelerate {
  373. self.startTimer()
  374. }
  375. }
  376. public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  377. self.startTimer()
  378. }
  379. public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
  380. let w = ZLEditVideoViewController.frameImageSize.width * 10
  381. let leftRight = (collectionView.frame.width - w) / 2
  382. return UIEdgeInsets(top: 0, left: leftRight, bottom: 0, right: leftRight)
  383. }
  384. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  385. return self.measureCount
  386. }
  387. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  388. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLEditVideoFrameImageCell.zl_identifier(), for: indexPath) as! ZLEditVideoFrameImageCell
  389. if let image = self.frameImageCache[indexPath.row] {
  390. cell.imageView.image = image
  391. }
  392. return cell
  393. }
  394. public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  395. if self.requestFailedFrameImageIndex.contains(indexPath.row) {
  396. let mes = TimeInterval(indexPath.row) * self.interval
  397. let time = CMTimeMakeWithSeconds(Float64(mes), preferredTimescale: self.avAsset.duration.timescale)
  398. let operation = ZLEditVideoFetchFrameImageOperation(generator: self.generator, time: time) { [weak self] (image, time) in
  399. self?.frameImageCache[indexPath.row] = image
  400. let cell = self?.collectionView.cellForItem(at: IndexPath(row: indexPath.row, section: 0)) as? ZLEditVideoFrameImageCell
  401. cell?.imageView.image = image
  402. if image != nil {
  403. self?.requestFailedFrameImageIndex.removeAll { $0 == indexPath.row }
  404. }
  405. }
  406. self.requestFrameImageQueue.addOperation(operation)
  407. }
  408. }
  409. }
  410. class ZLEditVideoFrameImageBorderView: UIView {
  411. var validRect: CGRect = .zero {
  412. didSet {
  413. self.setNeedsDisplay()
  414. }
  415. }
  416. override init(frame: CGRect) {
  417. super.init(frame: frame)
  418. self.layer.borderWidth = 2
  419. self.layer.borderColor = UIColor.clear.cgColor
  420. self.backgroundColor = .clear
  421. self.isOpaque = false
  422. }
  423. required init?(coder: NSCoder) {
  424. fatalError("init(coder:) has not been implemented")
  425. }
  426. override func draw(_ rect: CGRect) {
  427. let context = UIGraphicsGetCurrentContext()
  428. context?.setStrokeColor(UIColor.white.cgColor)
  429. context?.setLineWidth(4)
  430. context?.move(to: CGPoint(x: self.validRect.minX, y: 0))
  431. context?.addLine(to: CGPoint(x: self.validRect.minX+self.validRect.width, y: 0))
  432. context?.move(to: CGPoint(x: self.validRect.minX, y: rect.height))
  433. context?.addLine(to: CGPoint(x: self.validRect.minX+self.validRect.width, y: rect.height))
  434. context?.strokePath()
  435. }
  436. }
  437. class ZLEditVideoFrameImageCell: UICollectionViewCell {
  438. var imageView: UIImageView!
  439. override init(frame: CGRect) {
  440. super.init(frame: frame)
  441. self.imageView = UIImageView()
  442. self.imageView.contentMode = .scaleAspectFill
  443. self.imageView.clipsToBounds = true
  444. self.contentView.addSubview(self.imageView)
  445. }
  446. required init?(coder: NSCoder) {
  447. fatalError("init(coder:) has not been implemented")
  448. }
  449. override func layoutSubviews() {
  450. super.layoutSubviews()
  451. self.imageView.frame = self.bounds
  452. }
  453. }
  454. class ZLEditVideoFetchFrameImageOperation: Operation {
  455. let generator: AVAssetImageGenerator
  456. let time: CMTime
  457. let completion: ( (UIImage?, CMTime) -> Void )
  458. var pri_isExecuting = false {
  459. willSet {
  460. self.willChangeValue(forKey: "isExecuting")
  461. }
  462. didSet {
  463. self.didChangeValue(forKey: "isExecuting")
  464. }
  465. }
  466. override var isExecuting: Bool {
  467. return self.pri_isExecuting
  468. }
  469. var pri_isFinished = false {
  470. willSet {
  471. self.willChangeValue(forKey: "isFinished")
  472. }
  473. didSet {
  474. self.didChangeValue(forKey: "isFinished")
  475. }
  476. }
  477. override var isFinished: Bool {
  478. return self.pri_isFinished
  479. }
  480. var pri_isCancelled = false {
  481. willSet {
  482. self.willChangeValue(forKey: "isCancelled")
  483. }
  484. didSet {
  485. self.didChangeValue(forKey: "isCancelled")
  486. }
  487. }
  488. override var isCancelled: Bool {
  489. return self.pri_isCancelled
  490. }
  491. init(generator: AVAssetImageGenerator, time: CMTime, completion: @escaping ( (UIImage?, CMTime) -> Void )) {
  492. self.generator = generator
  493. self.time = time
  494. self.completion = completion
  495. super.init()
  496. }
  497. override func start() {
  498. if self.isCancelled {
  499. self.fetchFinish()
  500. return
  501. }
  502. self.pri_isExecuting = true
  503. self.generator.generateCGImagesAsynchronously(forTimes: [NSValue(time: self.time)]) { (_, cgImage, _, result, error) in
  504. if result == .succeeded, let cg = cgImage {
  505. let image = UIImage(cgImage: cg)
  506. DispatchQueue.main.async {
  507. self.completion(image, self.time)
  508. }
  509. self.fetchFinish()
  510. } else {
  511. self.fetchFinish()
  512. }
  513. }
  514. }
  515. func fetchFinish() {
  516. self.pri_isExecuting = false
  517. self.pri_isFinished = true
  518. }
  519. override func cancel() {
  520. super.cancel()
  521. self.pri_isCancelled = true
  522. }
  523. }