// // BFVideoThumbProgressView.swift // BFRecordScreenKit // // Created by 胡志强 on 2021/12/3. // import AVFoundation import BFCommonKit import BFUIKit import Foundation import SnapKit import UIKit class BFVideoThumbProgressView: UIView { var recordItem: BFRecordItemModel? { didSet { // 指针回归 BFLog(1, message: "new recorditem") progress = 0 if recordItem?.mediaType == .VIDEO { dealWithVideoThumb() } else if recordItem?.mediaType == .IMAGE { dealWithImageThumb() } } } var dragScrollProgressHandle: ((Bool, Float) -> Void)? var dragEndHandle: ((Float) -> Void)? var dragStartHandle: (() -> Void)? var isDrag = false let thumbImageWidth: CGFloat = 70.0 let fetchThumbStrategy: BFVideoThumbProgressStrategyProtocol = BFVideoThumbProgressStrategy() var progress: Double = 0 { // 进度,秒为单位 didSet { updateProgress(progress: progress) } } var thumbImgs = [UIImage]() var lastImg: UIImageView? lazy var progressView: BFAutolayoutScrollView = { let sv = BFAutolayoutScrollView() sv.bounces = false // sv.backgroundColor = .clear // sv.backgroundColor = UIColor.hexColor(hexadecimal: "#888888",alpha:0.3) sv.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3) sv.decelerationRate = .fast sv.showsHorizontalScrollIndicator = false sv.delegate = self return sv }() lazy var progessIndicateBackV: UIView = { let vv = UIView() return vv }() // MARK: - 生命周期 override init(frame: CGRect) { super.init(frame: frame) addSubview(progressView) let line = UIView() line.backgroundColor = .white line.layer.borderColor = UIColor.black.cgColor line.layer.borderWidth = 0.3 line.layer.cornerRadius = 1.5 addSubview(line) line.snp.makeConstraints { make in make.width.equalTo(3) make.center.height.equalToSuperview() } progressView.contentView.addSubview(progessIndicateBackV) progessIndicateBackV.snp.makeConstraints { make in make.left.equalTo(width * 0.5) make.right.equalTo(width * -0.5) make.bottom.equalToSuperview() make.height.equalTo(6) make.width.equalTo(1).priority(.high) } } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() progressView.snp.makeConstraints { make in make.edges.equalToSuperview() } } /// 处理视频缩略图 func dealWithVideoThumb() { guard let videoAsset = recordItem?.videoAsset else { // 可能icloud资源没回来,清空原有内容 addThumbImages(images: [UIImage]()) return } addThumbImages(images: recordItem!.thumbImgs) if recordItem!.thumbImgs.count > 0 { // 代表已经获取过了,不用在重新去获得 progessIndicateBackV.snp.remakeConstraints { make in make.left.equalTo(width * 0.5) make.right.equalTo(width * -0.5) make.bottom.equalToSuperview() make.height.equalTo(6) make.width.equalTo(CGFloat(recordItem!.thumbImgs.count) * thumbImageWidth).priority(.high) } return } let date = Date() let dur = videoAsset.duration.seconds if dur > 0 { let count = fetchThumbStrategy.frameNumberOfVideo(assetDuration: dur) progessIndicateBackV.snp.remakeConstraints { make in make.left.equalTo(width * 0.5) make.right.equalTo(width * -0.5) make.bottom.equalToSuperview() make.height.equalTo(6) make.width.equalTo(CGFloat(count) * thumbImageWidth).priority(.high) } recordItem!.splitVideoFileUrlFps(frames: count, firstImagesCount: Int(ceil(width / 2.0 / thumbImageWidth))) { [weak self, weak recordItem] hadGetAll, images in guard let sself = self, let sitem = recordItem else { return } BFLog(1, message: "获取缩略图:\(hadGetAll), \(Date().timeIntervalSince(date)), \(sitem.localPath ?? "aa")") sitem.thumbImgs.removeAll() sitem.thumbImgs.append(contentsOf: images) // 不足数则补充足够帧数 while hadGetAll, sitem.thumbImgs.count < count, images.count > 0 { sitem.thumbImgs.append(images.last!) } if sitem.localPath == sself.recordItem!.localPath { sself.addThumbImages(images: sitem.thumbImgs) } else { BFLog(1, message: "thumbImgs.count:\(sitem.thumbImgs.count)") } } } } /// 处理图片缩略图 func dealWithImageThumb() { guard let image = (recordItem?.thumbImgs.first ?? recordItem?.coverImg) else { addThumbImages(images: [UIImage]()) return } addThumbImages(images: Array(repeating: image, count: 10)) } /// 添加缩略图 /// - Parameter images: <#images description#> func addThumbImages(images: [UIImage]) { DispatchQueue.main.async { [weak self] in self?.progressView.contentView.subviews.forEach { subview in if subview is UIImageView { subview.removeFromSuperview() } } if images.count > 0 { if let sself = self { if sself.lastImg != nil, sself.lastImg?.superview != nil { sself.lastImg?.removeFromSuperview() } for (i, img) in images.enumerated() { let iv = UIImageView(image: img) iv.contentMode = .scaleAspectFill iv.clipsToBounds = true sself.progressView.contentView.insertSubview(iv, belowSubview: sself.progessIndicateBackV) iv.snp.makeConstraints { make in make.left.equalTo(CGFloat(i) * CGFloat(sself.thumbImageWidth) + sself.width * 0.5) make.top.bottom.equalToSuperview() make.height.equalTo(50) make.width.equalTo(sself.thumbImageWidth) } sself.lastImg = iv } if sself.recordItem?.mediaType == .IMAGE { // 图片需要动态修改宽度 sself.lastImg?.snp.makeConstraints { make in make.right.equalTo(sself.width * -0.5) } } } } } } func appendThumb(progress: Double = 0) { DispatchQueue.main.async { [weak self] in guard let sslf = self else { return } let count: Int = Int(progress / 2) BFLog(message: "需要的图片个数:progress=\(progress),count=\(count)") if sslf.recordItem?.mediaType == .IMAGE, (sslf.progressView.contentView.subviews.count - 6) < count { guard let image = (sslf.recordItem?.thumbImgs.first ?? sslf.recordItem?.coverImg) else { return } if sslf.lastImg != nil, sslf.lastImg?.superview != nil { sslf.lastImg?.removeFromSuperview() } let lastIndex = sslf.progressView.contentView.subviews.count - 1 for i in lastIndex ... lastIndex + 10 { let iv = UIImageView(image: image) iv.contentMode = .scaleAspectFill iv.clipsToBounds = true sslf.progressView.contentView.addSubview(iv) iv.snp.makeConstraints { make in make.left.equalTo(CGFloat(i) * CGFloat(sslf.thumbImageWidth) + sslf.width * 0.5) make.top.bottom.equalToSuperview() make.height.equalTo(50) make.width.equalTo(sslf.thumbImageWidth) } sslf.lastImg = iv } sslf.lastImg?.snp.makeConstraints { make in make.right.equalTo(sslf.width * -0.5) } } } } /// 更新进度 /// - Parameter progress: <#progress description#> func updateProgress(progress: Double = 0) { if progressView.contentSize.width <= 0 { return } if recordItem?.mediaType == .VIDEO { if let second = recordItem?.materialDuraion, second > 0 { let w = progressView.contentSize.width - width BFLog(message: "录音进度--指示器:progress=\(progress),duration=\(second),w=\(w),perW=\(Double(w) / second),totalW:\(progress * Double(w) / second)") progressView.contentOffset = CGPoint(x: progress * Double(w) / second, y: 0) } } else if recordItem?.mediaType == .IMAGE { // if (recordItem?.materialDuraion ?? 0) > progress { BFLog(message: "updateProgress:\(progress)") progressView.contentOffset = CGPoint(x: progress * thumbImageWidth / 2.0, y: 0) appendThumb(progress: progress) // } } } } extension BFVideoThumbProgressView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0) if recordItem?.mediaType == .VIDEO { if isDrag { dragScrollProgressHandle?(false, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0) } } else if recordItem?.mediaType == .IMAGE { if isDrag { if scrollView.contentOffset.x > CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0 { scrollView.contentOffset = CGPoint(x: CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0, y: 0) } dragScrollProgressHandle?(false, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0) } } } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { isDrag = true let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0) dragStartHandle?() dragScrollProgressHandle?(true, totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0) isDrag = false dragEndHandle?(totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0) } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let totalW = recordItem?.mediaType == .VIDEO ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion ?? 0) * thumbImageWidth / 2.0) isDrag = false dragEndHandle?(totalW > 0 ? Float(scrollView.contentOffset.x / totalW) : 0) } }