BFVideoThumbProgressView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. //
  2. // BFVideoThumbProgressView.swift
  3. // BFRecordScreenKit
  4. //
  5. // Created by 胡志强 on 2021/12/3.
  6. //
  7. import AVFoundation
  8. import BFCommonKit
  9. import BFUIKit
  10. import Foundation
  11. import SnapKit
  12. import UIKit
  13. class BFVideoThumbProgressView: UIView {
  14. var recordItem: BFRecordItemModel? {
  15. didSet {
  16. // 指针回归
  17. BFLog(1, message: "new recorditem")
  18. progress = 0
  19. if recordItem?.mediaType == .VIDEO {
  20. dealWithVideoThumb()
  21. } else if recordItem?.mediaType == .IMAGE {
  22. dealWithImageThumb()
  23. }
  24. }
  25. }
  26. var dragScrollProgressHandle: ((Bool, Float) -> Void)?
  27. var dragEndHandle: ((Float) -> Void)?
  28. var dragStartHandle: (() -> Void)?
  29. var isDrag = false
  30. let thumbImageWidth: CGFloat = 70.0
  31. let fetchThumbStrategy: BFVideoThumbProgressStrategyProtocol = BFVideoThumbProgressStrategy()
  32. var progress: Double = 0 { // 进度,秒为单位
  33. didSet {
  34. updateProgress(progress: progress)
  35. }
  36. }
  37. var thumbImgs = [UIImage]()
  38. var lastImg: UIImageView?
  39. lazy var progressView: BFAutolayoutScrollView = {
  40. let sv = BFAutolayoutScrollView()
  41. sv.bounces = false
  42. // sv.backgroundColor = .clear
  43. // sv.backgroundColor = UIColor.hexColor(hexadecimal: "#888888",alpha:0.3)
  44. sv.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
  45. sv.decelerationRate = .fast
  46. sv.showsHorizontalScrollIndicator = false
  47. sv.delegate = self
  48. return sv
  49. }()
  50. lazy var progessIndicateBackV: UIView = {
  51. let vv = UIView()
  52. return vv
  53. }()
  54. // MARK: - 生命周期
  55. override init(frame: CGRect) {
  56. super.init(frame: frame)
  57. addSubview(progressView)
  58. let line = UIView()
  59. line.backgroundColor = .white
  60. line.layer.borderColor = UIColor.black.cgColor
  61. line.layer.borderWidth = 0.3
  62. line.layer.cornerRadius = 1.5
  63. addSubview(line)
  64. line.snp.makeConstraints { make in
  65. make.width.equalTo(3)
  66. make.center.height.equalToSuperview()
  67. }
  68. progressView.contentView.addSubview(progessIndicateBackV)
  69. progessIndicateBackV.snp.makeConstraints { make in
  70. make.left.equalTo(width * 0.5)
  71. make.right.equalTo(width * -0.5)
  72. make.bottom.equalToSuperview()
  73. make.height.equalTo(6)
  74. make.width.equalTo(1).priority(.high)
  75. }
  76. }
  77. required init?(coder _: NSCoder) {
  78. fatalError("init(coder:) has not been implemented")
  79. }
  80. override func layoutSubviews() {
  81. super.layoutSubviews()
  82. progressView.snp.makeConstraints { make in
  83. make.edges.equalToSuperview()
  84. }
  85. }
  86. /// 处理视频缩略图
  87. func dealWithVideoThumb() {
  88. guard let videoAsset = recordItem?.videoAsset else {
  89. // 可能icloud资源没回来,清空原有内容
  90. addThumbImages(images: [UIImage]())
  91. return
  92. }
  93. addThumbImages(images: recordItem!.thumbImgs)
  94. if recordItem!.thumbImgs.count > 0 {
  95. // 代表已经获取过了,不用在重新去获得
  96. progessIndicateBackV.snp.remakeConstraints { make in
  97. make.left.equalTo(width * 0.5)
  98. make.right.equalTo(width * -0.5)
  99. make.bottom.equalToSuperview()
  100. make.height.equalTo(6)
  101. make.width.equalTo(CGFloat(recordItem!.thumbImgs.count) * thumbImageWidth).priority(.high)
  102. }
  103. return
  104. }
  105. let date = Date()
  106. let dur = videoAsset.duration.seconds
  107. if dur > 0 {
  108. let count = fetchThumbStrategy.frameNumberOfVideo(assetDuration: dur)
  109. progessIndicateBackV.snp.remakeConstraints { make in
  110. make.left.equalTo(width * 0.5)
  111. make.right.equalTo(width * -0.5)
  112. make.bottom.equalToSuperview()
  113. make.height.equalTo(6)
  114. make.width.equalTo(CGFloat(count) * thumbImageWidth).priority(.high)
  115. }
  116. recordItem!.splitVideoFileUrlFps(frames: count, firstImagesCount: Int(ceil(width / 2.0 / thumbImageWidth))) { [weak self, weak recordItem] hadGetAll, images in
  117. guard let sself = self, let sitem = recordItem else {
  118. return
  119. }
  120. BFLog(1, message: "获取缩略图:\(hadGetAll), \(Date().timeIntervalSince(date)), \(sitem.localPath ?? "aa")")
  121. sitem.thumbImgs.removeAll()
  122. sitem.thumbImgs.append(contentsOf: images)
  123. // 不足数则补充足够帧数
  124. while hadGetAll, sitem.thumbImgs.count < count, images.count > 0 {
  125. sitem.thumbImgs.append(images.last!)
  126. }
  127. if sitem.localPath == sself.recordItem!.localPath {
  128. sself.addThumbImages(images: sitem.thumbImgs)
  129. } else {
  130. BFLog(1, message: "thumbImgs.count:\(sitem.thumbImgs.count)")
  131. }
  132. }
  133. }
  134. }
  135. /// 处理图片缩略图
  136. func dealWithImageThumb() {
  137. guard let image = (recordItem?.thumbImgs.first ?? recordItem?.coverImg) else {
  138. addThumbImages(images: [UIImage]())
  139. return
  140. }
  141. addThumbImages(images: Array(repeating: image, count: 10))
  142. }
  143. /// 添加缩略图
  144. /// - Parameter images: <#images description#>
  145. func addThumbImages(images: [UIImage]) {
  146. DispatchQueue.main.async { [weak self] in
  147. self?.progressView.contentView.subviews.forEach { subview in
  148. if subview is UIImageView {
  149. subview.removeFromSuperview()
  150. }
  151. }
  152. if images.count > 0 {
  153. if let sself = self {
  154. if sself.lastImg != nil, sself.lastImg?.superview != nil {
  155. sself.lastImg?.removeFromSuperview()
  156. }
  157. for (i, img) in images.enumerated() {
  158. let iv = UIImageView(image: img)
  159. iv.contentMode = .scaleAspectFill
  160. iv.clipsToBounds = true
  161. sself.progressView.contentView.insertSubview(iv, belowSubview: sself.progessIndicateBackV)
  162. iv.snp.makeConstraints { make in
  163. make.left.equalTo(CGFloat(i) * CGFloat(sself.thumbImageWidth) + sself.width * 0.5)
  164. make.top.bottom.equalToSuperview()
  165. make.height.equalTo(50)
  166. make.width.equalTo(sself.thumbImageWidth)
  167. }
  168. sself.lastImg = iv
  169. }
  170. if sself.recordItem?.mediaType == .IMAGE {
  171. // 图片需要动态修改宽度
  172. sself.lastImg?.snp.makeConstraints { make in
  173. make.right.equalTo(sself.width * -0.5)
  174. }
  175. }
  176. }
  177. }
  178. }
  179. }
  180. func appendThumb(progress: Double = 0) {
  181. DispatchQueue.main.async { [weak self] in
  182. guard let sslf = self else { return }
  183. let count: Int = Int(progress / 2)
  184. // BFLog(message: "需要的图片个数:progress=\(progress),count=\(count)")
  185. if sslf.recordItem?.mediaType == .IMAGE, (sslf.progressView.contentView.subviews.count - 6) < count {
  186. guard let image = (sslf.recordItem?.thumbImgs.first ?? sslf.recordItem?.coverImg) else {
  187. return
  188. }
  189. if sslf.lastImg != nil, sslf.lastImg?.superview != nil {
  190. sslf.lastImg?.removeFromSuperview()
  191. }
  192. let lastIndex = sslf.progressView.contentView.subviews.count - 1
  193. for i in lastIndex ... lastIndex + 10 {
  194. let iv = UIImageView(image: image)
  195. iv.contentMode = .scaleAspectFill
  196. iv.clipsToBounds = true
  197. sslf.progressView.contentView.addSubview(iv)
  198. iv.snp.makeConstraints { make in
  199. make.left.equalTo(CGFloat(i) * CGFloat(sslf.thumbImageWidth) + sslf.width * 0.5)
  200. make.top.bottom.equalToSuperview()
  201. make.height.equalTo(50)
  202. make.width.equalTo(sslf.thumbImageWidth)
  203. }
  204. sslf.lastImg = iv
  205. }
  206. sslf.lastImg?.snp.makeConstraints { make in
  207. make.right.equalTo(sslf.width * -0.5)
  208. }
  209. }
  210. }
  211. }
  212. /// 更新进度
  213. /// - Parameter progress: <#progress description#>
  214. func updateProgress(progress: Double = 0) {
  215. if progressView.contentSize.width <= 0 {
  216. return
  217. }
  218. if recordItem?.mediaType == .VIDEO {
  219. if let second = recordItem?.materialDuraion.seconds, second > 0 {
  220. let w = progressView.contentSize.width - width
  221. BFLog(message: "录音进度--指示器:progress=\(progress),duration=\(second),w=\(w),perW=\(Double(w) / second),totalW:\(progress * Double(w) / second)")
  222. progressView.contentOffset = CGPoint(x: progress * Double(w) / second, y: 0)
  223. }
  224. } else if recordItem?.mediaType == .IMAGE {
  225. // if (recordItem?.materialDuraion ?? 0) > progress {
  226. BFLog(message: "updateProgress:\(progress)")
  227. progressView.contentOffset = CGPoint(x: progress * thumbImageWidth / 2.0, y: 0)
  228. appendThumb(progress: progress)
  229. // }
  230. }
  231. }
  232. }
  233. extension BFVideoThumbProgressView: UIScrollViewDelegate {
  234. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  235. let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
  236. if recordItem?.mediaType == .VIDEO {
  237. if isDrag {
  238. dragScrollProgressHandle?(false, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0)
  239. }
  240. } else if recordItem?.mediaType == .IMAGE {
  241. if isDrag {
  242. if scrollView.contentOffset.x > ((CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0) + 0.34) {
  243. scrollView.contentOffset = CGPoint(x: (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0) + 0.34, y: 0)
  244. }
  245. dragScrollProgressHandle?(false, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0)
  246. }
  247. }
  248. }
  249. func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  250. isDrag = true
  251. let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
  252. dragStartHandle?()
  253. dragScrollProgressHandle?(true, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0)
  254. }
  255. func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  256. if !decelerate {
  257. let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
  258. isDrag = false
  259. dragEndHandle?(totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0)
  260. }
  261. }
  262. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  263. let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
  264. isDrag = false
  265. dragEndHandle?(totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
  266. }
  267. }