瀏覽代碼

开始缩略图

harry 3 年之前
父節點
當前提交
45d1522fbe

+ 4 - 4
BFRecordScreenKit/Classes/BFRecordItemModel.swift

@@ -100,13 +100,13 @@ public class BFRecordItemModel: NSObject {
 
     public func fetchAVUrlAsset(_ phasset: PHAsset) {
         PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phasset) { [weak self] asset, _, _, _ in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
             if let videoAsset = (asset as? AVURLAsset) {
-                sself.localPath = (videoAsset.url.absoluteString.removingPercentEncoding)?.replacingOccurrences(of: "file://", with: "")
-                sself.videoAsset = videoAsset
-                sself.fetchAVUrlAssetCallBack?()
+                wself.localPath = (videoAsset.url.absoluteString.removingPercentEncoding)?.replacingOccurrences(of: "file://", with: "")
+                wself.videoAsset = videoAsset
+                wself.fetchAVUrlAssetCallBack?()
             }
         }
     }

+ 1 - 1
BFRecordScreenKit/Classes/BFVoiceRecordManager.swift

@@ -118,7 +118,7 @@ extension BFVoiceRecordManager: BFRecorderManagerDelegate {
             // 2处理降噪
             beginRecordTime1 = Date()
             let noiseFilePath = wavFilePath.replacingOccurrences(of: ".wav", with: "_noise.wav")
-            BFLog(1, message: "\(debugHeader)降噪后地址:\(noiseFilePath) 原地址:\(wavFilePath)")
+//            BFLog(1, message: "\(debugHeader)降噪后地址:\(noiseFilePath) 原地址:\(wavFilePath)")
             NXNoiseReduction().denoise(wavFilePath, outFile: noiseFilePath)
             if let model = voiceModel {
                 model.wavFilePath = noiseFilePath

+ 86 - 84
BFRecordScreenKit/Classes/RecordScreen/Controller/BFRecordScreenCameraManager.swift

@@ -5,6 +5,16 @@
 //  Created by 胡志强 on 2022/1/13.
 //
 
+/*
+ 
+ 时长是根据摄像头文件时长相加得来的,预览/合成时需要考虑录音的时长问题
+ 
+ videosticker 已经存储好了,在合成时,需要更新下进入的时间就可以了,不需要再切割了
+ 
+ 
+ 
+ */
+
 import Foundation
 import GPUImage
 import BFCommonKit
@@ -20,36 +30,45 @@ let videoSize = CGSize(width: cScreenWidth, height: cScreenWidth * 16 / 9.0)
 class BFRecordScreenCameraManager : BFRecordScreenBaseManager{
     //
     var rendView = GPUImageView()
+    var cameraProgressV: BFCameraProgressView?
+    
     // 录制完成回调
     var recordEndCallBack: recordEndCallBack?
     var recordProgressCallBack : ((CMTime) -> Void)?
+    
     var startTime = Date()
-    let videoModel = PQEditVisionTrackMaterialsModel()
+    var videoModel = PQEditVisionTrackMaterialsModel()
+    
+    // 用于打开摄像头初始流程
     var firstOpenCamera : Bool = true
+    //当前录制的缩略图数量
+    var currVideoThumImagesNum = 0
+    //当前摄像的序号
+    var currVideoIndex = 0
     
-    fileprivate var timer:Timer?
-    // 定时器时间间隔
-    var timeDur: TimeInterval = 1
-
+    fileprivate var timerr:Timer?
+   
+    var movieWrite : GPUImageMovieWriter?
     
     lazy var camera : GPUImageStillCamera? = {
         let camera = GPUImageStillCamera(sessionPreset: AVCaptureSession.Preset.hd1920x1080.rawValue, cameraPosition: AVCaptureDevice.Position.back)
         camera?.outputImageOrientation = UIInterfaceOrientation.portrait
-        camera?.addAudioInputsAndOutputs()
+//        camera?.addAudioInputsAndOutputs()
         camera?.delegate = self
         return camera
         
     }()
     
-    var movieWrite : GPUImageMovieWriter?
     
     override func resetEnv(){
         guard let camera = camera else {
             cShowHUB(superView: nil, msg: "摄像头开启失败!")
             return
         }
-        camera.startCapture()
+        cameraProgressV?.recordItem = recordItem
+        currVideoIndex = 0
 
+        camera.startCapture()
         initerlizeWriter()
 
         camera.addTarget(filter)
@@ -59,7 +78,7 @@ class BFRecordScreenCameraManager : BFRecordScreenBaseManager{
             startRecord()
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: { [weak self] in
                 guard let wself = self else { return }
-                
+
                 wself.endRecord()
                 wself.firstOpenCamera = false
             })
@@ -74,88 +93,89 @@ class BFRecordScreenCameraManager : BFRecordScreenBaseManager{
         
 
         super.startRecord()
-        if !firstOpenCamera{
-            progreddL?.isHidden = false
-            progressThumV?.isHidden = false
-        }
-        
+
+        currVideoThumImagesNum = 0
         startTime = Date()
+        
+        videoModel = PQEditVisionTrackMaterialsModel()
+        videoModel.thumImgs = [UIImage]()
         videoModel.timelineIn = currentAssetProgress.seconds
+        currVideoIndex = recordItem?.videoStickers.count ?? 0
         
+        
+        // 为了第一时间能更新collection view数据
+        if !firstOpenCamera{
+            recordItem?.videoStickers.append(videoModel)
+            progreddL?.isHidden = false
+            cameraProgressV?.isHidden = false
+        }
         movieWrite.startRecording()
         
-        var thumbCount = -1
-        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: {[weak self] timer in
+        timerr = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: {[weak self] _ in
             guard let wself = self else { return }
             if wself.firstOpenCamera {
                 return
             }
             //MARK: 进度计时器
-//            let dur = Date().timeIntervalSince(sself.startTime)
-            let dur = CMTime(seconds: movieWrite.duration.seconds, preferredTimescale: 1000)
-            // 获取缩略图
-            let coculationCount = Int(dur.seconds / 5.0)
-            if coculationCount > thumbCount{
-                BFLog(1, message: "取一张缩略图出来 \(dur.seconds)")
-                wself.camera?.capturePhotoAsImageProcessedUp(toFilter: wself.filter, withCompletionHandler: {[weak self] image, err in
-                    guard let wself = self else { return }
-                    
-                    if let img = image{
-                        wself.recordItem?.thumbImgs.append(img)
-                        wself.updateThumb()
-                    }
-                })
-                thumbCount = coculationCount
-            }
 
-            let currDur = CMTime(seconds: wself.videoModel.timelineIn, preferredTimescale: 1000) + dur
+            let currDur = CMTime(seconds: wself.videoModel.timelineIn + movieWrite.duration.seconds, preferredTimescale: 1000)
             wself.currentAssetProgress = currDur
+            wself.videoModel.timelineOut = currDur.seconds
             wself.recordProgressCallBack?(currDur)
             wself.updateUI(progress: currDur)
-//            BFLog(1, message: "currTime \(dur.seconds), \(sself.currentAssetProgress.seconds)")
+//            BFLog(1, message: "currTime \(dur.seconds), \(wself.currentAssetProgress.seconds)")
         })
-        RunLoop.current.add(timer!, forMode: .common)
+        RunLoop.current.add(timerr!, forMode: .common)
     }
     
     override func endRecord(){
         super.endRecord()
         
-        timer?.invalidate()
-        timer = nil
-        BFLog(1, message: " currTime fini 1 \(Date().timeIntervalSince(startTime))")
+        timerr?.invalidate()
+        timerr = nil
+        BFLog(1, message: " 拍摄时长 \(Date().timeIntervalSince(startTime))")
         let su = firstOpenCamera
         movieWrite?.finishRecording(completionHandler: {[weak self] in
-            guard let sself = self else { return }
+            guard let wself = self else { return }
             
             if FileManager.default.fileExists(atPath: vpath) {
                 if !su {
-                    let finalPath = PQBridgeObject.p_setupFileRename(vpath)
-                    let dur = AVURLAsset(url: URL(fileURLWithPath: finalPath))
-                    BFLog(1, message: "currTime fini 2 \(dur.duration.seconds)")
-                    sself.videoModel.timelineOut += dur.duration.seconds
-                    sself.videoModel.locationPath = finalPath
-                    sself.recordEndCallBack?(true, sself.videoModel)
+                    if let finalPath = PQBridgeObject.p_setupFileRename(vpath), finalPath.count > 0 {
+                        let dur = AVURLAsset(url: URL(fileURLWithPath: finalPath))
+                        BFLog(1, message: "拍摄文件时长: \(dur.duration.seconds)")
+                        wself.videoModel.timelineOut += dur.duration.seconds
+                        wself.videoModel.locationPath = finalPath
+                        wself.recordEndCallBack?(true, wself.videoModel)
+                    }else{
+                        cShowHUB(superView: nil, msg: "录制失败,未能生成文件")
+                    }
                 }
             }
             
             // 重置writer,提前为下次录制做准备
-            sself.initerlizeWriter()
+            wself.initerlizeWriter()
         })
     }
     
     override func clean() {
+        filter.removeTarget(movieWrite)
+        filter.removeTarget(rendView)
+        camera?.removeTarget(filter)
         super.clean()
     }
     
     // MARK: - 私有方法
     func updateUI(progress:CMTime){
         progreddL?.text = CMTimeGetSeconds(progress).formatDurationToHMS()
+//        cameraProgressV?.collectionV.contentOffset = CGPoint(x: progress.seconds * 70 / 5.0, y: 0)
+        cameraProgressV?.addThumbImages()
     }
     
     func updateThumb(){
-        progressThumV!.addThumbImages(images: recordItem!.thumbImgs)
-        let size = CGSize(width: recordItem!.materialDuraion.seconds * 70.0 / 5.0, height: progressThumV!.height)
-        progressThumV!.progressView.contentSize = size
+        if let progressThumV = cameraProgressV {
+//            progressThumV.collectionV.reloadData()
+            progressThumV.addThumbImages()
+        }
     }
     
     func beginRecord(startTime:CMTime) {
@@ -168,15 +188,10 @@ class BFRecordScreenCameraManager : BFRecordScreenBaseManager{
         }
         if movieWrite != nil {
             filter.removeTarget(movieWrite)
-//            camera?.audioEncodingTarget = nil
             movieWrite = nil
         }
         
         movieWrite = GPUImageMovieWriter(movieURL: URL(fileURLWithPath: vpath), size: videoSize)
-
-//        movieWrite?.shouldPassthroughAudio = true
-//        movieWrite?.hasAudioTrack = true
-//        camera?.audioEncodingTarget = movieWrite
         filter.addTarget(movieWrite)
 
     }
@@ -185,47 +200,34 @@ class BFRecordScreenCameraManager : BFRecordScreenBaseManager{
 
 extension BFRecordScreenCameraManager : GPUImageVideoCameraDelegate {
     func willOutputSampleBuffer(_ sampleBuffer: CMSampleBuffer!) {
+        if !recording || firstOpenCamera {
+            return
+        }
         let dur = CMTime(seconds: movieWrite?.duration.seconds ?? 0, preferredTimescale: 1000)
         // 获取缩略图
         let coculationCount = Int(dur.seconds / 5.0)
-        if coculationCount >= recordItem?.thumbImgs.count ?? -1{
-            BFLog(1, message: "取一张缩略图出来 \(dur.seconds)")
-            if let img = sampleBufferToImage(sampleBuffer){
+        if coculationCount >= currVideoThumImagesNum{
+            if let img = imageFromBuffer(sampleBuffer){
+                BFLog(1, message: "取一张缩略图出来 \(dur.seconds)")
                 recordItem?.thumbImgs.append(img)
+                videoModel.thumImgs?.append(img)
+                currVideoThumImagesNum += 1
                 updateThumb()
             }
         }
     }
     
-    func sampleBufferToImage(_ sampleBuffer: CMSampleBuffer) -> UIImage? {
-        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
-            return nil
-        }
+    func imageFromBuffer(_ buff: CMSampleBuffer) -> UIImage? {
+        if let buffer = CMSampleBufferGetImageBuffer(buff) {
+            let ciImage = CIImage(cvPixelBuffer: buffer)
 
-        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))
-
-        let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
-        let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
-        let width = CVPixelBufferGetWidth(imageBuffer)
-        let height = CVPixelBufferGetHeight(imageBuffer)
-        let colorSpace = CGColorSpaceCreateDeviceRGB()
-        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
-
-        guard let context = CGContext(data: baseAddress,
-                                      width: width,
-                                      height: height,
-                                      bitsPerComponent:8,
-                                      bytesPerRow: bytesPerRow,
-                                      space: colorSpace,
-                                      bitmapInfo: bitmapInfo.rawValue) else { return nil }
-
-        guard let cgImage = context.makeImage() else {
-            return nil
-        }
-
-        CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0));
+            return UIImage(ciImage: ciImage.oriented(forExifOrientation: 6),
+                           scale: cScreenWidth / 50.0,
+                           orientation: .up)
 
-        return UIImage(cgImage: cgImage)
+        }
 
+        return nil
     }
+    
 }

+ 143 - 116
BFRecordScreenKit/Classes/RecordScreen/Controller/BFRecordScreenController.swift

@@ -54,7 +54,7 @@ public class BFRecordScreenController: BFBaseViewController {
         let m = BFRecordScreenCameraManager()
         m.recordProgressCallBack = {[weak self] progress in
             guard let wself = self else { return }
-            
+            wself.currentAssetProgress = progress
         }
         return m
     }()
@@ -75,19 +75,25 @@ public class BFRecordScreenController: BFBaseViewController {
                 rscurrentManager = rsimanager
                 recordBtn.setTitle("按住 录制", for: .normal)
                 progressThumV.isHidden = false
+                cameraProgressThumV.isHidden = true
                 progreddL.isHidden = false
+                rscurrentManager.progressThumV = progressThumV
 
             case .Video:
                 rscurrentManager = rsvmanager
                 recordBtn.setTitle("按住 录音", for: .normal)
                 progressThumV.isHidden = false
+                cameraProgressThumV.isHidden = true
                 progreddL.isHidden = false
-                
+                rscurrentManager.progressThumV = progressThumV
+
             case .Camera:
                 rscurrentManager = rscmanager
                 recordBtn.setTitle("按住 录音", for: .normal)
                 progressThumV.isHidden = true
+                cameraProgressThumV.isHidden = false
                 progreddL.isHidden = true
+                rscmanager.cameraProgressV = cameraProgressThumV
 
             default:
                 break
@@ -105,10 +111,13 @@ public class BFRecordScreenController: BFBaseViewController {
                         }
                         
                         // MARK: 摄像头结束回调
-                        (rscurrentManager as? BFRecordScreenCameraManager)?.recordEndCallBack = { isSuccess, sticker in
-                            if isSuccess, let sticker = sticker {
-                                itemModel.videoStickers.append(sticker)
-                            }
+                        (rscurrentManager as? BFRecordScreenCameraManager)?.recordEndCallBack = { [weak self] _, sticker in 
+                            guard let wself = self, let sticker = sticker else { return }
+                            
+                            wself.currentAssetProgress = CMTime(seconds: sticker.timelineOut , preferredTimescale: 1000)
+//                            if isSuccess, let sticker = sticker {
+//                                itemModel.videoStickers.append(sticker)
+//                            }
                         }
                     }
                     
@@ -120,7 +129,6 @@ public class BFRecordScreenController: BFBaseViewController {
                 }
                 rscurrentManager.playView = cell.playView
                 rscurrentManager.progreddL = progreddL
-                rscurrentManager.progressThumV = progressThumV
                 
                 rscurrentManager.recordItem = itemModel
                 rscurrentManager.resetEnv()
@@ -321,11 +329,11 @@ public class BFRecordScreenController: BFBaseViewController {
         toolV.centerY = view.centerY
 
         toolV.choosedToolHandle = { [weak self] tool in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
-            tool.center = sself.view.center
-            sself.view.addSubview(tool)
+            tool.center = wself.view.center
+            wself.view.addSubview(tool)
         }
 
         return toolV
@@ -424,32 +432,14 @@ public class BFRecordScreenController: BFBaseViewController {
 
     lazy var progressThumV: BFVideoThumbProgressView = {
         let vv = BFVideoThumbProgressView(frame: CGRect(x: 0, y: 18, width: cScreenWidth, height: 50))
-        vv.dragStartHandle = { [weak self] in
-            self?.isDragingProgressSlder = true
-            self?.pause()
-        }
-        vv.dragScrollProgressHandle = { [weak self] isStart, process in
-            DispatchQueue.main.async { [weak self] in
-                guard let sself = self else {
-                    return
-                }
-                if isStart {
-                    sself.events.append(WithDrawModel(type: 0, timestamp: sself.currentAssetProgress))
-                }
-
-                sself.isDragingProgressSlder = true
-
-                sself.changeProgress(progress: process)
-            }
-        }
-        vv.dragEndHandle = { [weak self] process in
-            DispatchQueue.main.async { [weak self] in
-                guard let sself = self else {
-                    return
-                }
-                sself.thumbViewEnded(progress: process)
-            }
-        }
+        self.thumViewCallBack(vv)
+        vv.isHidden = true
+        return vv
+    }()
+    
+    lazy var cameraProgressThumV: BFCameraProgressView = {
+        let vv = BFCameraProgressView(frame: CGRect(x: 0, y: 18, width: cScreenWidth, height: 50))
+        self.thumViewCallBack(vv)
         vv.isHidden = true
         return vv
     }()
@@ -655,15 +645,15 @@ public class BFRecordScreenController: BFBaseViewController {
 
         // MARK: -  录音结束
         recorderManager?.endRecordHandle = { [weak self, weak recorderManager] voideModel, _ in
-            if let sself = self, let model = voideModel, FileManager.default.fileExists(atPath: model.wavFilePath ?? "") {
+            if let wself = self, let model = voideModel, FileManager.default.fileExists(atPath: model.wavFilePath ?? "") {
                 // 加入到语音数组里
 
-                model.endCMTime = sself.currentAssetProgress
+                model.endCMTime = wself.currentAssetProgress
                 BFLog(1, message: "录音地址:\((model.wavFilePath ?? "").replacingOccurrences(of: documensDirectory, with: "")) -  \(model.startCMTime.seconds)~\(model.endCMTime.seconds), dur: \(model.endCMTime.seconds - model.startCMTime.seconds) / \(model.duration)")
                 /// 注:录音机回调的录音时长大于一秒,而业务逻辑计算的会小于一秒
                 if (model.endCMTime.seconds - model.startCMTime.seconds) < 1 {
                     // 取消录制
-                    sself.recordManagerCancelRecord(voiceModel: model)
+                    wself.recordManagerCancelRecord(voiceModel: model)
                     return
                 }
                 // ********** 开始处理冲突的录制部分
@@ -674,38 +664,38 @@ public class BFRecordScreenController: BFBaseViewController {
                 var deletedTitlesTemp = [PQEditSubTitleModel]()
 
                 // 查找要删除的音频和字幕数据
-                for (i, m) in sself.itemModels[sself.currItemModelIndex].voiceStickers.enumerated() {
+                for (i, m) in wself.itemModels[wself.currItemModelIndex].voiceStickers.enumerated() {
                     let originRange = CMTimeRange(start: m.startCMTime, end: CMTime(seconds: m.endCMTime.seconds - 0.02, preferredTimescale: 1000))
                     if CMTimeRangeGetIntersection(originRange, otherRange: newRange).duration.seconds > 0 {
                         deletedVoices.append((m, i))
-                        deletedTitlesTemp += sself.deleteTitles(voiceModel: m)
+                        deletedTitlesTemp += wself.deleteTitles(voiceModel: m)
                         continue
                     }
                 }
                 // 删除冲突的音频
                 deletedVoices.forEach { m, _ in
-                    sself.itemModels[sself.currItemModelIndex].voiceStickers.removeAll { tempM in
+                    wself.itemModels[wself.currItemModelIndex].voiceStickers.removeAll { tempM in
                         m.wavFilePath == tempM.wavFilePath
                     }
                 }
 
                 BFLog(1, message: "添加录音文件:\(model.startCMTime.seconds) -- \(model.endCMTime.seconds)")
-                sself.itemModels[sself.currItemModelIndex].voiceStickers.append(model)
+                wself.itemModels[wself.currItemModelIndex].voiceStickers.append(model)
                 // 录制结束回调
                 self?.recordEndHandle?(model)
                 // 如果是图片素材同时有需要删除的录音时需要调整录音文件开始结束时间
                 // warning: 图片录制的时候应该只能在结尾处录制
-                if sself.itemModels[sself.currItemModelIndex].mediaType == .Image {
+                if wself.itemModels[wself.currItemModelIndex].mediaType == .Image {
                     if deletedVoices.count > 0 {
                         // 如果是图片先排序在计算区间
-                        sself.itemModels[sself.currItemModelIndex].voiceStickers = sself.itemModels[sself.currItemModelIndex].voiceStickers.sorted { voice1, voice2 in
+                        wself.itemModels[wself.currItemModelIndex].voiceStickers = wself.itemModels[wself.currItemModelIndex].voiceStickers.sorted { voice1, voice2 in
                             voice1.startCMTime.seconds < voice2.startCMTime.seconds
                         }
-                        for (index, item) in sself.itemModels[sself.currItemModelIndex].voiceStickers.enumerated() {
+                        for (index, item) in wself.itemModels[wself.currItemModelIndex].voiceStickers.enumerated() {
                             if index > 0 {
                                 // 注:开始时间减去duration or 等一前一段录音的结束时间
                                 let tempDuration = item.endCMTime.seconds - item.startCMTime.seconds
-                                item.startCMTime = CMTime(seconds: sself.itemModels[sself.currItemModelIndex].voiceStickers[index - 1].endCMTime.seconds, preferredTimescale: 1000)
+                                item.startCMTime = CMTime(seconds: wself.itemModels[wself.currItemModelIndex].voiceStickers[index - 1].endCMTime.seconds, preferredTimescale: 1000)
                                 item.endCMTime = CMTime(seconds: item.startCMTime.seconds + tempDuration, preferredTimescale: 1000)
                             }
                             BFLog(message: "录制结束重新排序录音文件:\(index)-\(item.wavFilePath ?? "")-\(item.startCMTime.seconds)-\(item.endCMTime.seconds)-\(item.endCMTime.seconds - item.startCMTime.seconds)")
@@ -718,20 +708,20 @@ public class BFRecordScreenController: BFBaseViewController {
                 var event = WithDrawModel(type: 2, timestamp: model.startCMTime)
                 event.deletedVoices = deletedVoices
                 event.deletedTittles = deletedTitlesTemp
-                sself.events.append(event)
+                wself.events.append(event)
 
-                if sself.itemModels[sself.currItemModelIndex].mediaType == .Image {
+                if wself.itemModels[wself.currItemModelIndex].mediaType == .Image {
                     var duration: CMTime = .zero
-                    sself.itemModels[sself.currItemModelIndex].voiceStickers.forEach { temp in
+                    wself.itemModels[wself.currItemModelIndex].voiceStickers.forEach { temp in
                         BFLog(1, message: "录制结束-最终:\(temp.wavFilePath ?? "")-\(temp.startCMTime.seconds)-\(temp.endCMTime.seconds)-\(temp.endCMTime.seconds - temp.startCMTime.seconds)")
                         temp.duration = "\(temp.endCMTime.seconds - temp.startCMTime.seconds)"
                         duration = duration + temp.endCMTime - temp.startCMTime
                     }
-                    sself.itemModels[sself.currItemModelIndex].materialDuraion = duration
+                    wself.itemModels[wself.currItemModelIndex].materialDuraion = duration
                     self?.isEndPlay = true
                     // 录制结束显示播放按钮
-                    sself.playBtn.isSelected = sself.itemModels[sself.currItemModelIndex].voiceStickers.count <= 0
-                    sself.playBtn.isHidden = sself.playBtn.isSelected
+                    wself.playBtn.isSelected = wself.itemModels[wself.currItemModelIndex].voiceStickers.count <= 0
+                    wself.playBtn.isHidden = wself.playBtn.isSelected
                 }
                 DispatchQueue.main.async { [weak self] in
                     // 录音完,重绘撤销按钮,更新录音按钮,
@@ -747,8 +737,8 @@ public class BFRecordScreenController: BFBaseViewController {
                     self?.deleteRecordBtn.isHidden = true
                     self?.recordBtn.isHidden = (self?.itemModels[self?.currItemModelIndex ?? 0].mediaType == .Image && (self?.isEndPlay ?? false)) ? false : (self?.isEndPlay ?? false)
                 }
-                sself.currentPlayRecordIndex = -3 // 刚录音完,不需要播放录音
-                BFLog(3, message: "重置播放index-\(#function) = \(sself.currentPlayRecordIndex)")
+                wself.currentPlayRecordIndex = -3 // 刚录音完,不需要播放录音
+                BFLog(3, message: "重置播放index-\(#function) = \(wself.currentPlayRecordIndex)")
                 recorderManager?.voiceModel = nil
             } else {
                 BFLog(2, message: "数据出错!!!!\(voideModel?.wavFilePath ?? "")")
@@ -798,6 +788,7 @@ public class BFRecordScreenController: BFBaseViewController {
         bottomeView.addSubview(withDrawBtn)
         //        bottomeView.addSubview(changeVoiceBtn)
         bottomeView.addSubview(progressThumV)
+        bottomeView.addSubview(cameraProgressThumV)
 
         avatarView.recordEndCallBack = { _, materialsModel in
             BFLog(message: "新录制完成::::\(materialsModel?.locationPath ?? "")")
@@ -1176,18 +1167,18 @@ public class BFRecordScreenController: BFBaseViewController {
 
         DispatchQueue.global().async {[weak self] in
             
-            guard let sself = self else { return }
+            guard let wself = self else { return }
             
-            sself.recorderManager?.cancelTitleService()
+            wself.recorderManager?.cancelTitleService()
             let model = PQVoiceModel()
-            model.startCMTime = sself.currentAssetProgress
-            model.currIndex = sself.currItemModelIndex
+            model.startCMTime = wself.currentAssetProgress
+            model.currIndex = wself.currItemModelIndex
     //        model.recordId = getUniqueId(desc: "recordId")
             model.volume = 100
-            sself.recorderManager?.voiceModel = model
-            BFLog(3, message: "开始录制-开始:currentAssetProgress=\(sself.currentAssetProgress.seconds),cuInde=\(sself.currItemModelIndex),\(model)")
-            sself.recorderManager?.startRecord()
-//            sself.recorderManager?.audioRecorder?.startNeoNui(sself.NeoNuiToken ?? "", appid: sself.NeoNuiAPPID ?? "")
+            wself.recorderManager?.voiceModel = model
+            BFLog(3, message: "开始录制-开始:currentAssetProgress=\(wself.currentAssetProgress.seconds),cuInde=\(wself.currItemModelIndex),\(model)")
+            wself.recorderManager?.startRecord()
+//            wself.recorderManager?.audioRecorder?.startNeoNui(wself.NeoNuiToken ?? "", appid: wself.NeoNuiAPPID ?? "")
 
         }
         // 开始时间
@@ -1287,16 +1278,22 @@ public class BFRecordScreenController: BFBaseViewController {
         subtitleCount = itemModels[currItemModelIndex].titleStickers.count
         BFLog(2, message: "删除\(voiceModel?.wavFilePath ?? "")对应的字幕  后 count\(subtitleCount)")
         /// 重置进度
-        if voiceModel?.currIndex == currItemModelIndex {
-            if itemModels[currItemModelIndex].mediaType == .Image {
-                currentAssetProgress = CMTime(seconds: recorderManager?.voiceModel?.startCMTime.seconds ?? 0, preferredTimescale: 1000)
-                if currentAssetProgress.seconds >= itemModels[currItemModelIndex].materialDuraion.seconds {
-                    currentAssetProgress = CMTime(seconds: itemModels[currItemModelIndex].materialDuraion.seconds, preferredTimescale: 1000)
+        ///
+        DispatchQueue.main.async {[weak self] in
+            guard let wself = self else { return }
+            
+            if voiceModel?.currIndex == wself.currItemModelIndex {
+                if wself.itemModels[wself.currItemModelIndex].mediaType == .Image {
+                    wself.currentAssetProgress = CMTime(seconds: wself.recorderManager?.voiceModel?.startCMTime.seconds ?? 0, preferredTimescale: 1000)
+                    if wself.currentAssetProgress.seconds >= wself.itemModels[wself.currItemModelIndex].materialDuraion.seconds {
+                        wself.currentAssetProgress = CMTime(seconds: wself.itemModels[wself.currItemModelIndex].materialDuraion.seconds, preferredTimescale: 1000)
+                    }
+                    wself.resetCurrentProgress()
                 }
-                resetCurrentProgress()
-            }
             // 移除
-            resetAllIndirectionView()
+                
+                wself.resetAllIndirectionView()
+            }
         }
         recorderManager?.voiceModel = nil
     }
@@ -1498,7 +1495,37 @@ public class BFRecordScreenController: BFBaseViewController {
             }
         }
     }
+    
+    func thumViewCallBack(_ vv: BFProgressBaseView) {
+        vv.dragStartHandle = { [weak self] in
+            guard let wself = self else { return }
+            
+            wself.isDragingProgressSlder = true
+            wself.pause()
+        }
+        vv.dragScrollProgressHandle = { [weak self] isStart, process in
+            DispatchQueue.main.async { [weak self] in
+                guard let wself = self else { return }
+                
+                if isStart {
+                    wself.events.append(WithDrawModel(type: 0, timestamp: wself.currentAssetProgress))
+                }
+
+                wself.isDragingProgressSlder = true
+
+                wself.changeProgress(progress: process)
+            }
+        }
+        vv.dragEndHandle = { [weak self] process in
+            DispatchQueue.main.async { [weak self] in
+                guard let wself = self else { return }
+
+                wself.thumbViewEnded(progress: process)
+            }
+        }
+    }
 
+    // MARK: - 逻辑处理
     // 是否吸附在录音首尾处
     func searchStopAtRecordRange(needAdsorb: Bool = false) {
         // TODO: 滑动,播放暂停,撤销时,判断是否停止录音区间,是则删除相关录音,画笔,头像,字幕
@@ -1669,29 +1696,29 @@ public class BFRecordScreenController: BFBaseViewController {
             BFLog(1, message: "录音播放器初始化(有时候不准)")
             BFLog(3, message: "重置播放index-\(#function) = \(currentPlayRecordIndex)")
             NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: newItem, queue: .main) { [weak self] _ in
-                guard let sself = self else {
-                    BFLog(3, message: "sself为空AVPlayerItemDidPlayToEndTime")
+                guard let wself = self else {
+                    BFLog(3, message: "wself为空AVPlayerItemDidPlayToEndTime")
                     return
                 }
-                sself.hadPrepareToPlayRecord = false
-                sself.currentPlayRecordIndex = -1
-//                sself.recordPlayer.volume = 0
-//                sself.assetPlayer.volume = sself.noSpeakVolume
+                wself.hadPrepareToPlayRecord = false
+                wself.currentPlayRecordIndex = -1
+//                wself.recordPlayer.volume = 0
+//                wself.assetPlayer.volume = wself.noSpeakVolume
                 BFLog(3, message: "播放结束")
                 didPlayToEndTime((shouldPlayRecordIndex, recordedAudio), newItem)
             }
             recordPlayerTimeObserver?.invalidate()
             recordPlayerTimeObserver = recordPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1000), queue: DispatchQueue.global()) { [weak self, weak recordPlayer] time in
-                guard let sself = self, let rPlay = recordPlayer else {
-                    BFLog(3, message: "sself为空")
+                guard let wself = self, let rPlay = recordPlayer else {
+                    BFLog(3, message: "wself为空")
                     return
                 }
                 BFLog(3, message: "当前播放---\(time),\(time.seconds),\(rPlay.currentItem?.currentTime().seconds ?? 0),\(rPlay.currentItem?.duration.seconds ?? 0)")
-                if CMTimeGetSeconds(sself.currenStartPlayTime) <= 0 {
-                    BFLog(message: "重新更新开始播放进度\(#function)-\(sself.currenStartPlayTime.seconds)")
-                    sself.currenStartPlayTime = time
+                if CMTimeGetSeconds(wself.currenStartPlayTime) <= 0 {
+                    BFLog(message: "重新更新开始播放进度\(#function)-\(wself.currenStartPlayTime.seconds)")
+                    wself.currenStartPlayTime = time
                 }
-                let progressTime = CMTime(seconds: CMTimeGetSeconds(time) - CMTimeGetSeconds(sself.currenStartPlayTime), preferredTimescale: 1000)
+                let progressTime = CMTime(seconds: CMTimeGetSeconds(time) - CMTimeGetSeconds(wself.currenStartPlayTime), preferredTimescale: 1000)
                 BFLog(message: "progressTime进度\(#function)-\(progressTime.seconds)")
                 periodicTimeObserver(progressTime, newItem)
 //                if (rPlay.currentItem?.currentTime().seconds ?? 0) > (rPlay.currentItem?.duration.seconds ?? 0) - 0.1 {
@@ -1719,10 +1746,10 @@ public class BFRecordScreenController: BFBaseViewController {
         //            return
         //        }
         synced(currentPlayRecordIndex) { [weak self] in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
-//            BFLog(1, message: "判断是否开始录音播放** hadPrepareToPlayRecord:\(hadPrepareToPlayRecord), currentPlayRecordIndex:\(currentPlayRecordIndex), isNormalPlaying :\(sself.isNormalPlaying),\(recordPlayer.currentItem?.duration.timescale ?? 0),\(CMTimeGetSeconds(currentT) >= recordedAudio.startCMTime.seconds),\(CMTimeGetSeconds(currentT) <= recordedAudio.endCMTime.seconds - 0.2)")
+//            BFLog(1, message: "判断是否开始录音播放** hadPrepareToPlayRecord:\(hadPrepareToPlayRecord), currentPlayRecordIndex:\(currentPlayRecordIndex), isNormalPlaying :\(wself.isNormalPlaying),\(recordPlayer.currentItem?.duration.timescale ?? 0),\(CMTimeGetSeconds(currentT) >= recordedAudio.startCMTime.seconds),\(CMTimeGetSeconds(currentT) <= recordedAudio.endCMTime.seconds - 0.2)")
 
             if !hadPrepareToPlayRecord,
                CMTimeGetSeconds(currentT) >= (recordedAudio.startCMTime.seconds - 0.1),
@@ -1733,7 +1760,7 @@ public class BFRecordScreenController: BFBaseViewController {
                 }
                 // 应当开始播放了
                 // 两个逻辑:如果在播,则跳过;如果暂停拖动到中间,则seek
-                if currentPlayRecordIndex == -1, sself.isNormalPlaying {
+                if currentPlayRecordIndex == -1, wself.isNormalPlaying {
                     let second = currentT - recordedAudio.startCMTime
                     DispatchQueue.main.async { [weak self] in
                         self?.recordPlayer.seek(to: second, toleranceBefore: CMTime(value: 1, timescale: 1_000_000), toleranceAfter: CMTime(value: 1, timescale: 1_000_000), completionHandler: { [weak self] finished in
@@ -1761,21 +1788,21 @@ public class BFRecordScreenController: BFBaseViewController {
     ///   - recordedAudio: <#recordedAudio description#>
     func imageMaterialRecordPlay(at currentT: CMTime, shouldPlayRecordIndex _: Int, recordedAudio: PQVoiceModel) {
         synced(currentPlayRecordIndex) { [weak self] in
-            guard let sself = self, sself.isNormalPlaying else {
+            guard let wself = self, wself.isNormalPlaying else {
                 return
             }
-//            BFLog(1, message: "判断是否开始录音播放** hadPrepareToPlayRecord:\(hadPrepareToPlayRecord), currentPlayRecordIndex:\(currentPlayRecordIndex), isNormalPlaying :\(sself.isNormalPlaying),\(recordPlayer.currentItem?.duration.timescale ?? 0),\(CMTimeGetSeconds(currentT) >= recordedAudio.startCMTime.seconds),\(CMTimeGetSeconds(currentT) <= recordedAudio.endCMTime.seconds - 0.2)")
+//            BFLog(1, message: "判断是否开始录音播放** hadPrepareToPlayRecord:\(hadPrepareToPlayRecord), currentPlayRecordIndex:\(currentPlayRecordIndex), isNormalPlaying :\(wself.isNormalPlaying),\(recordPlayer.currentItem?.duration.timescale ?? 0),\(CMTimeGetSeconds(currentT) >= recordedAudio.startCMTime.seconds),\(CMTimeGetSeconds(currentT) <= recordedAudio.endCMTime.seconds - 0.2)")
 
             if CMTimeGetSeconds(currentT) >= (recordedAudio.startCMTime.seconds - 0.1),
                CMTimeCompare(currentT, recordedAudio.endCMTime) <= 0 // 这个条件是避免录音结束后有小幅度回退导致播放最新录音
             {
                 // 应当开始播放了
                 // 两个逻辑:如果在播,则跳过;如果暂停拖动到中间,则seek
-                if sself.isNormalPlaying {
+                if wself.isNormalPlaying {
                     let second = currentT - recordedAudio.startCMTime
-                    sself.recordPlayer.seek(to: second, toleranceBefore: CMTime(seconds: 1, preferredTimescale: 1000), toleranceAfter: CMTime(seconds: 1, preferredTimescale: 1000), completionHandler: { isFinished in
+                    wself.recordPlayer.seek(to: second, toleranceBefore: CMTime(seconds: 1, preferredTimescale: 1000), toleranceAfter: CMTime(seconds: 1, preferredTimescale: 1000), completionHandler: { isFinished in
                     })
-                    sself.recordPlayer.play()
+                    wself.recordPlayer.play()
                     BFLog(3, message: "录音开始播放2, \(second), \(CMTimeGetSeconds(recordPlayer.currentItem?.duration ?? .zero)),index = \(currentPlayRecordIndex)")
                 }
             }
@@ -1828,7 +1855,7 @@ public class BFRecordScreenController: BFBaseViewController {
         subtitleBtn.isHidden = false
         soundSettingBtn.isHidden = false
         withDrawBtn.isHidden = false
-        recordBtn.isHidden = (itemModels[currItemModelIndex].mediaType == .Image && isEndPlay) ? false : isEndPlay
+        recordBtn.isHidden = (rscurrentManager.recordItem?.mediaType == .Image && isEndPlay) ? false : isEndPlay
 
         assetPlayer.pause()
         recordPlayer.pause()
@@ -1838,7 +1865,7 @@ public class BFRecordScreenController: BFBaseViewController {
         hadPrepareToPlayRecord = false
 
         // 暂停状态
-        playBtn.isSelected = (itemModels[currItemModelIndex].mediaType == .Image && itemModels[currItemModelIndex].voiceStickers.count <= 0)
+        playBtn.isSelected = (rscurrentManager.recordItem?.mediaType == .Image && rscurrentManager.recordItem?.voiceStickers.count ?? -1 <= 0)
         playBtn.isHidden = playBtn.isSelected
     }
 
@@ -1915,28 +1942,28 @@ public class BFRecordScreenController: BFBaseViewController {
         }
 
         NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: assetPlayer.currentItem, queue: .main) { [weak self] _ in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
 
             // 非录音的播放状态,结束时自动跳下一段落
-            if !sself.isRecording, sself.currItemModelIndex < (sself.itemModels.count - 1) {
+            if !wself.isRecording, wself.currItemModelIndex < (wself.itemModels.count - 1) {
 //                self?.collectionView.setContentOffset(CGPoint(x: CGFloat((self?.currItemModelIndex ?? 0) + 1) * (self?.collectionView.frame.width ?? 0), y: 0), animated: true)
             } else {} //
 
-            sself.isNormalPlaying = false
-            sself.isEndPlay = true
-            sself.recordBtn.isHidden = (sself.itemModels[sself.currItemModelIndex].mediaType == .Image) ? false : true
+            wself.isNormalPlaying = false
+            wself.isEndPlay = true
+            wself.recordBtn.isHidden = (wself.itemModels[wself.currItemModelIndex].mediaType == .Image) ? false : true
 
-            sself.subtitleBtn.isHidden = false
-            sself.soundSettingBtn.isHidden = false
+            wself.subtitleBtn.isHidden = false
+            wself.soundSettingBtn.isHidden = false
 
-            if sself.isRecording {
-                sself.endRecord()
+            if wself.isRecording {
+                wself.endRecord()
                 cShowHUB(superView: nil, msg: "此视频已录制到头了哦")
             }
-            sself.playBtn.isSelected = false
-            sself.playBtn.isHidden = sself.playBtn.isSelected
+            wself.playBtn.isSelected = false
+            wself.playBtn.isHidden = wself.playBtn.isSelected
         }
     }
 
@@ -1977,6 +2004,7 @@ public class BFRecordScreenController: BFBaseViewController {
     }
 
     // MARK: - 录音对应图像绘制
+    
 
     // 撤销按钮修改title,重绘
     func changeWithDrawBtnLayout(_ type: Int) {
@@ -2103,30 +2131,30 @@ extension BFRecordScreenController: UICollectionViewDelegate, UICollectionViewDa
         }
         
         recordItem.fetchCoverImgCallBack = { [weak self, weak cell, weak recordItem] _ in
-            guard let sself = self, let item = recordItem else {
+            guard let wself = self, let item = recordItem else {
                 return
             }
             cell?.addData()
             if item.mediaType != .Camera {
-                sself.progressThumV.recordItem = item
-                sself.progressThumV.isHidden = false
+                wself.progressThumV.recordItem = item
+                wself.progressThumV.isHidden = false
             }
         }
         recordItem.fetchPlayItemCallBack = { [weak self, weak recordItem] item in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
             guard item != nil else {
                 cShowHUB(superView: nil, msg: "视频获取失败:\(recordItem?.index ?? 0)")
                 return
             }
-            sself.rscurrentManager.resetEnv()
+            wself.rscurrentManager.resetEnv()
         }
         cell.btnClickHandle = { [weak self] _, _ in
-            guard let sself = self else {
+            guard let wself = self else {
                 return
             }
-            sself.playVideo(btn: sself.playBtn)
+            wself.playVideo(btn: wself.playBtn)
         }
         cell.recordItem = recordItem
         return cell
@@ -2164,13 +2192,9 @@ extension BFRecordScreenController: UICollectionViewDelegate, UICollectionViewDa
                 endRecord()
             }
             recorderManager?.cancelTitleService()
-//            events.append(WithDrawModel(type: 0, timestamp: currentAssetProgress))
-//            // 重设撤销栈
-//            itemModels[currItemModelIndex].events = events
-//            events = itemModels[page].events
+            
             events = [WithDrawModel]()
 
-
             let recordItem = itemModels[currItemModelIndex]
             // 暂停状态--如果是图片素材同时没有录音文件时不显示播放按钮
             playBtn.isSelected = (recordItem.mediaType == .Image && recordItem.voiceStickers.count <= 0)
@@ -2308,6 +2332,9 @@ public extension BFRecordScreenController {
 
     /// 重绘录音进度视图
     func resetAllIndirectionView() {
+        if itemModels[currItemModelIndex].mediaType == .Camera {
+            return
+        }
         // 重绘录音进度视图
         var percenWidth: CGFloat = 0
         if itemModels[currItemModelIndex].mediaType == .Image {

+ 2 - 0
BFRecordScreenKit/Classes/RecordScreen/Controller/BFRecordScreenImageManager.swift

@@ -23,5 +23,7 @@ class BFRecordScreenImageManager : BFRecordScreenBaseManager{
     
     override func clean() {
         super.clean()
+        filter.removeTarget(playView)
+        
     }
 }

+ 5 - 1
BFRecordScreenKit/Classes/RecordScreen/View/BFIndirectionProgressView.swift

@@ -158,7 +158,11 @@ class BFIndirectionProgressView: UIView {
     ///   - indirec: <#indirec description#>
     /// - Returns: <#description#>
     func createItemView(minX: CGFloat, width: CGFloat = 0, indirec: Bool = false) -> UIView {
-        let lineV = UIView(frame: CGRect(x: minX < 0 ? 0 : minX, y: 0, width: width, height: progressHeight))
+        var newW = width
+        if width.isNaN || width.isInfinite {
+            newW = 0
+        }
+        let lineV = UIView(frame: CGRect(x: minX < 0 ? 0 : minX, y: 0, width: newW, height: progressHeight))
         lineV.backgroundColor = indirec ? indirecColor : themeColor
         addSubview(lineV)
         return lineV

+ 10 - 0
BFRecordScreenKit/Classes/RecordScreen/View/Cell/BFCameraCoverViewCell.swift

@@ -23,4 +23,14 @@ open class BFCameraCoverViewCell: BFImageCoverViewCell {
             initCameraAfterData?()
         }
     }
+    
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        
+    }
+    
+    public required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 }

+ 126 - 0
BFRecordScreenKit/Classes/RecordScreen/View/ProgressView/BFCameraProgressView.swift

@@ -0,0 +1,126 @@
+//
+//  BFCameraProgressView.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/1/17.
+//
+
+import Foundation
+import BFCommonKit
+
+class BFCameraProgressView: BFProgressBaseView {
+    
+    lazy var collectionV : UICollectionView = {
+        let flowLayout = UICollectionViewFlowLayout()
+        flowLayout.minimumLineSpacing = 0
+        flowLayout.minimumInteritemSpacing = 0
+        flowLayout.scrollDirection = .horizontal
+        flowLayout.itemSize = CGSize(width: self.frame.width, height: self.frame.height)
+        
+        let cc = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.width, height: self.height), collectionViewLayout: flowLayout)
+        cc.delegate = self
+        cc.dataSource = self
+        cc.bounces = false
+        cc.register(UICollectionViewCell.self, forCellWithReuseIdentifier: String(describing: UICollectionViewCell.self))
+        cc.showsVerticalScrollIndicator = false
+        cc.showsHorizontalScrollIndicator = false
+        cc.backgroundColor = UIColor.clear
+        return cc
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+
+        addSubview(collectionV)
+        
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    /// 添加缩略图
+    /// - Parameter images:
+    func addThumbImages() {
+        DispatchQueue.main.async {
+            self.collectionV.reloadData()
+        }
+    }
+}
+
+extension BFCameraProgressView: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return recordItem?.thumbImgs.count ?? 0
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        var size : CGSize = .zero
+        if let img = recordItem?.thumbImgs[indexPath.row] {
+            if let sticker = recordItem?.videoStickers.first(where: { m in
+                m.thumImgs?.contains(img) as? Bool ?? false
+            }) {
+                if img == sticker.thumImgs!.last {
+                    size = CGSize(width: (sticker.timelineOut - sticker.timelineIn) * 70.0 / 5.0, height: 50)
+                }else{
+                    size = CGSize(width: 70.0, height: 50)
+                }
+            }
+        }
+        BFLog(1, message: "size \(size), \(indexPath.row)")
+        return size
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: UICollectionViewCell.self), for: indexPath)
+        var imgview : UIImageView? = cell.contentView.viewWithTag(13000) as? UIImageView
+        if  imgview == nil {
+            imgview = UIImageView(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
+            imgview?.contentMode = .scaleAspectFit
+            imgview?.tag = 13000
+            cell.contentView.addSubview(imgview!)
+        }
+        
+        imgview?.image = recordItem?.thumbImgs[indexPath.row]
+        
+        return cell
+    }
+}
+
+extension BFCameraProgressView {
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        let totalW = recordItem?.mediaType == .Video ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
+        if recordItem?.mediaType == .Video {
+            if isDrag {
+                dragScrollProgressHandle?(false, totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
+            }
+        } else if recordItem?.mediaType == .Image {
+            if isDrag {
+                if scrollView.contentOffset.x > ((CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0) + 0.5) {
+                    scrollView.contentOffset = CGPoint(x: (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0) + 0.5, y: 0)
+                }
+                dragScrollProgressHandle?(false, totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
+            }
+        }
+    }
+
+    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+        isDrag = true
+        let totalW = recordItem?.mediaType == .Video ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
+        dragStartHandle?()
+        dragScrollProgressHandle?(true, totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
+    }
+
+    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
+        if !decelerate {
+            let totalW = recordItem?.mediaType == .Video ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
+            isDrag = false
+            dragEndHandle?(totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
+        }
+    }
+
+    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
+        let totalW = recordItem?.mediaType == .Video ? (scrollView.contentSize.width - width) : (CGFloat(recordItem?.materialDuraion.seconds ?? 0) * thumbImageWidth / 2.0)
+        isDrag = false
+        dragEndHandle?(totalW > 0 ? min(Float(scrollView.contentOffset.x / totalW), 1) : 0)
+    }
+}

+ 42 - 0
BFRecordScreenKit/Classes/RecordScreen/View/ProgressView/BFProgressBaseView.swift

@@ -0,0 +1,42 @@
+//
+//  BFProgressBaseView.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/1/17.
+//
+
+import Foundation
+
+
+class BFProgressBaseView: UIView {
+    
+    var recordItem: BFRecordItemModel?
+    
+    var dragScrollProgressHandle: ((Bool, Float) -> Void)?
+    var dragEndHandle: ((Float) -> Void)?
+    var dragStartHandle: (() -> Void)?
+    var isDrag = false
+
+    let thumbImageWidth: CGFloat = 70.0
+    
+    func addThumbImages(images: [UIImage]){}
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+
+        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()
+        }
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 12 - 0
BFRecordScreenKit/Classes/RecordScreen/View/ProgressView/BFThumCollectionViewFlowLayout.swift

@@ -0,0 +1,12 @@
+//
+//  BFThumCollectionViewFlowLayout.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/1/17.
+//
+
+import Foundation
+
+class BFThumCollectionViewFlowLayout: UICollectionViewLayout {
+    
+}

+ 16 - 22
BFRecordScreenKit/Classes/RecordScreen/View/BFVideoThumbProgressView.swift → BFRecordScreenKit/Classes/RecordScreen/View/ProgressView/BFVideoThumbProgressView.swift

@@ -12,8 +12,8 @@ import Foundation
 import SnapKit
 import UIKit
 
-class BFVideoThumbProgressView: UIView {
-    var recordItem: BFRecordItemModel? {
+class BFVideoThumbProgressView: BFProgressBaseView {
+    override var recordItem: BFRecordItemModel? {
         didSet {
             // 指针回归
             BFLog(1, message: "new recorditem")
@@ -26,12 +26,6 @@ class BFVideoThumbProgressView: UIView {
         }
     }
 
-    var dragScrollProgressHandle: ((Bool, Float) -> Void)?
-    var dragEndHandle: ((Float) -> Void)?
-    var dragStartHandle: (() -> Void)?
-    var isDrag = false
-
-    let thumbImageWidth: CGFloat = 70.0
 
     let fetchThumbStrategy: BFVideoThumbProgressStrategyProtocol = BFVideoThumbProgressStrategy()
 
@@ -133,7 +127,7 @@ class BFVideoThumbProgressView: UIView {
             }
 
             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 {
+                guard let wself = self, let sitem = recordItem else {
                     return
                 }
 
@@ -145,8 +139,8 @@ class BFVideoThumbProgressView: UIView {
                 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)
+                if sitem.localPath == wself.recordItem!.localPath {
+                    wself.addThumbImages(images: sitem.thumbImgs)
                 } else {
                     BFLog(1, message: "thumbImgs.count:\(sitem.thumbImgs.count)")
                 }
@@ -165,7 +159,7 @@ class BFVideoThumbProgressView: UIView {
 
     /// 添加缩略图
     /// - Parameter images: <#images description#>
-    func addThumbImages(images: [UIImage]) {
+    override func addThumbImages(images: [UIImage]) {
         DispatchQueue.main.async { [weak self] in
             self?.progressView.contentView.subviews.forEach { subview in
                 if subview is UIImageView {
@@ -173,27 +167,27 @@ class BFVideoThumbProgressView: UIView {
                 }
             }
             if images.count > 0 {
-                if let sself = self {
-                    if sself.lastImg != nil, sself.lastImg?.superview != nil {
-                        sself.lastImg?.removeFromSuperview()
+                if let wself = self {
+                    if wself.lastImg != nil, wself.lastImg?.superview != nil {
+                        wself.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)
+                        wself.progressView.contentView.insertSubview(iv, belowSubview: wself.progessIndicateBackV)
                         iv.snp.makeConstraints { make in
-                            make.left.equalTo(CGFloat(i) * CGFloat(sself.thumbImageWidth) + sself.width * 0.5)
+                            make.left.equalTo(CGFloat(i) * CGFloat(wself.thumbImageWidth) + wself.width * 0.5)
                             make.top.bottom.equalToSuperview()
                             make.height.equalTo(50)
-                            make.width.equalTo(sself.thumbImageWidth)
+                            make.width.equalTo(wself.thumbImageWidth)
                         }
-                        sself.lastImg = iv
+                        wself.lastImg = iv
                     }
-                    if sself.recordItem?.mediaType == .Image {
+                    if wself.recordItem?.mediaType == .Image {
                         // 图片需要动态修改宽度
-                        sself.lastImg?.snp.makeConstraints { make in
-                            make.right.equalTo(sself.width * -0.5)
+                        wself.lastImg?.snp.makeConstraints { make in
+                            make.right.equalTo(wself.width * -0.5)
                         }
                     }
                 }

+ 23 - 0
BFRecordScreenKit/Classes/RecordScreen/ViewModel/PQEditVisionTrackMaterialsModel+ext.swift

@@ -0,0 +1,23 @@
+//
+//  PQEditVisionTrackMaterialsModel+ext.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/1/17.
+//
+
+import Foundation
+import BFMediaKit
+
+public extension PQEditVisionTrackMaterialsModel {
+    
+    static var thumImgs_key: UnsafeRawPointer = UnsafeRawPointer("thumImgs_key")
+
+    var thumImgs: [UIImage]? {
+        set {
+            objc_setAssociatedObject(self, &PQEditVisionTrackMaterialsModel.thumImgs_key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        }
+        get {
+            return objc_getAssociatedObject(self, &PQEditVisionTrackMaterialsModel.thumImgs_key) as? [UIImage]
+        }
+    }
+}