Преглед изворни кода

Merge branch 'master' of https://git.yishihui.com/iOS/BFRecordScreenKit
合并代码

# Conflicts:
# BFRecordScreenKit/Classes/BFRecordExport.swift

jsonwang пре 3 година
родитељ
комит
99700b95a0

+ 3 - 3
BFRecordScreenKit/Classes/BFRecordExport.swift

@@ -22,7 +22,7 @@ public enum ExportError: Int {
     case DiskNoSpace = -31006
 }
 
-let testFor = true
+let testFor = false
 
 public class BFRecordExport {
     public var progress: ((Float) -> Void)?
@@ -351,7 +351,7 @@ public class BFRecordExport {
 
             let outputSize: CGSize = CGSize(width: 1080, height: Int(1080 * CGFloat(UIScreen.main.bounds.size.height / UIScreen.main.bounds.size.width)))
             BFLog(message: "输出视频大小:\(outputSize)")
-
+ 
             // add by ak 有字幕数据 & 显示字幕开关打开 添加字幕filter
             var subtitleFilter:PQSubTitleFilter?
             if titleStickers.count > 0,titleStickers.first?.setting.subtitleIsShow ?? true {
@@ -400,7 +400,7 @@ public class BFRecordExport {
 //                    BFLog(1, message: "视频导出完成: \(String(describing: url)) 生成视频时长为:\(outSeconds) 总用时:\(exportEndTime - tempBeginExport)")
                     print("生成视频时长为:\(outSeconds) 总用时:\(exportEndTime - tempBeginExport)")
                     if testFor {
-                        cShowHUB(superView: nil, msg: (outSeconds == 0) ? "合成失败请重试。" : "合成成功,总用时:\(exportEndTime - tempBeginExport)")
+                        cShowHUB(superView: nil, msg: (outSeconds == 0) ? "合成失败请重试。" : "总用时:\(exportEndTime - tempBeginExport)")
                     }
 
                     self?.exportCompletion?(nil, url)

+ 16 - 13
BFRecordScreenKit/Classes/BFRecordItemModel.swift

@@ -28,7 +28,7 @@ public class BFRecordItemModel: NSObject {
     public var videoStickers = [PQEditVisionTrackMaterialsModel]() // 合成导出时计算
     public var imageStickers = [PQEditVisionTrackMaterialsModel]() //
     public var titleStickers = [PQEditSubTitleModel]() // 字幕贴纸
-    
+
     var events = [WithDrawModel]() // 行为记录,方便撤销
 
     public var coverImg: UIImage? // 封面图
@@ -40,16 +40,16 @@ public class BFRecordItemModel: NSObject {
     public var index = 0 // 素材index
     public var width = 0 // 素材宽
     public var height = 0 // 素材高
-    public var videoDegress : UInt = 0  // 视频拍摄角度 90,270为横屏,180,0为竖屏
+    public var videoDegress: UInt = 0 // 视频拍摄角度 90,270为横屏,180,0为竖屏
 
     func initOriginData(phasset: PHAsset) {
         width = phasset.pixelWidth
         height = phasset.pixelHeight
         materialDuraion = phasset.duration
-        
+
         fetchCoverImage(phasset)
         fetchAVUrlAsset(phasset)
-        
+
         if phasset.mediaType == .image {
             mediaType = .IMAGE
             localPath = "image"
@@ -57,7 +57,6 @@ public class BFRecordItemModel: NSObject {
             mediaType = .VIDEO
             fetchPlayItem(phasset)
         }
-
     }
 
     func fetchCoverImage(_ phasset: PHAsset) {
@@ -72,6 +71,11 @@ public class BFRecordItemModel: NSObject {
             // 设置首帧/封面
             if image != nil {
                 self?.coverImg = image
+                if self?.mediaType == .IMAGE {
+                    if let thumImage = image?.nx_scaleToSize(size: CGSize(width: 112, height: 200)) {
+                        self?.thumbImgs.append(thumImage)
+                    }
+                }
                 self?.fetchCoverImgCallBack?(image!)
             }
         }
@@ -89,7 +93,7 @@ public class BFRecordItemModel: NSObject {
     }
 
     public func fetchAVUrlAsset(_ phasset: PHAsset) {
-        PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phasset) {[weak self] asset, size, _, _ in
+        PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phasset) { [weak self] asset, _, _, _ in
             guard let sself = self else {
                 return
             }
@@ -126,12 +130,12 @@ public class BFRecordItemModel: NSObject {
             dealedDurationRanges.append(SplitRecordRange(isRecord: false, range: range, index: -1))
         }
     }
-    
+
     /// 视频分解成帧
     /// - parameter frames                          : 需要取的帧数
     /// - parameter firstImagesCount             : 获取首先N张连续视频帧后先返回给调用方使用作为缓冲
     /// - parameter splitCompleteClosure    : 回调
-    func splitVideoFileUrlFps(frames: Int, firstImagesCount:Int = 0, maxSize:CGSize = CGSize(width: 200, height: 200), splitCompleteClosure: @escaping ((Bool, [UIImage]) -> Void)) {
+    func splitVideoFileUrlFps(frames: Int, firstImagesCount: Int = 0, maxSize _: CGSize = CGSize(width: 200, height: 200), splitCompleteClosure: @escaping ((Bool, [UIImage]) -> Void)) {
         guard let urlAsset = videoAsset, urlAsset.duration.seconds > 0 else {
             return
         }
@@ -144,7 +148,7 @@ public class BFRecordItemModel: NSObject {
         let start = 0
 //        let end = Int(urlAsset.duration.seconds * Float64(fps))
         let fps = Double(frames) / urlAsset.duration.seconds
-        for i in start..<frames {
+        for i in start ..< frames {
             let timeValue = NSValue(time: CMTimeMake(value: Int64(i * 1000), timescale: Int32(fps * 1000)))
 
             times.append(timeValue)
@@ -170,15 +174,15 @@ public class BFRecordItemModel: NSObject {
             case AVAssetImageGenerator.Result.succeeded:
                 let framImg = UIImage(cgImage: image!)
                 splitImages.append(framImg)
-    //            BFLog(1, message: "aaa: \(requestedTime.seconds) - \(actualTime.seconds)")
+            //            BFLog(1, message: "aaa: \(requestedTime.seconds) - \(actualTime.seconds)")
             @unknown default:
                 break
             }
-            
+
             if cocu == firstImagesCount {
                 splitCompleteClosure(false, splitImages)
             }
-            
+
             if cocu == timesCount { // 最后一帧时 回调赋值
                 splitCompleteClosure(true, splitImages)
                 BFLog(1, message: "splitVideo: complete")
@@ -204,5 +208,4 @@ public class BFRecordItemModel: NSObject {
 
         return cgImg == nil ? nil : UIImage(cgImage: cgImg!)
     }
-
 }

+ 4 - 0
BFRecordScreenKit/Classes/BFVoiceRecordManager.swift

@@ -67,6 +67,10 @@ class BFVoiceRecordManager: NSObject {
         BFLog(1, message: "开始录音 \(recorderFilePath)")
         audioRecorder?.startRecord(recorderFilePath)
     }
+    
+    func cancelTitleService() {
+        audioRecorder?.cancelRecord()
+    }
 
     /// 停止录制
     /// - Parameter isCancel: 是否为取消 ,取消操作会把录制的文件删除和字幕删除

+ 86 - 60
BFRecordScreenKit/Classes/RecordScreen/Controller/BFRecordScreenController.swift

@@ -6,6 +6,8 @@
 //  Copyright © 2021 CocoaPods. All rights reserved.
 //
 
+import AVFAudio
+import AVFoundation
 import BFCommonKit
 import BFMediaKit
 import BFNetRequestKit
@@ -20,7 +22,7 @@ struct WithDrawModel {
     var timestamp: Double
     var deletedVoices: [(PQVoiceModel, Int)]?
     // add by ak 保存删除的字幕数据用于恢复
-    var deletedTittles: [(PQEditSubTitleModel, Int)]?
+    var deletedTittles: [PQEditSubTitleModel]?
 }
 
 public class BFRecordScreenController: BFBaseViewController {
@@ -67,7 +69,7 @@ public class BFRecordScreenController: BFBaseViewController {
 
     var currentAssetProgress: CMTime = .zero {
         didSet {
-            BFLog(3,message: "currentAssetProgress=\(currentAssetProgress.seconds)")
+            BFLog(3, message: "currentAssetProgress=\(currentAssetProgress.seconds)")
         }
     } // 当前素材播放的进度
     // 播放器开始播放时间
@@ -132,11 +134,12 @@ public class BFRecordScreenController: BFBaseViewController {
     lazy var progreddL: UILabel = {
         let l = UILabel()
         l.textAlignment = .center
-        l.font = UIFont.systemFont(ofSize: 13)
+        l.font = UIFont.systemFont(ofSize: 13, weight: .medium)
         l.textColor = .white
-        l.shadowColor = .black
-        l.shadowOffset = CGSize(width: 0, height: 1)
-        l.layer.opacity = 0.4
+        l.layer.shadowOpacity = 0.4
+        l.layer.shadowColor = UIColor.black.cgColor
+        l.layer.shadowOffset = CGSize(width: 0, height: 2)
+        l.layer.shadowRadius = 2
         l.text = "00:00"
         return l
     }()
@@ -423,6 +426,19 @@ public class BFRecordScreenController: BFBaseViewController {
         endRecord()
     }
 
+    /// 线路切换
+    /// - Parameter nofify: <#nofify description#>
+    @objc func routeChangeNofify(nofify: Notification) {
+        let routeChangeDic = nofify.userInfo
+        let routeChangeReason: AVAudioSession.RouteChangeReason? = AVAudioSession.RouteChangeReason(rawValue: UInt((routeChangeDic?[AVAudioSessionRouteChangeReasonKey] as? Int) ?? 0))
+        if routeChangeReason == .oldDeviceUnavailable {
+            let previousRoute = routeChangeDic?[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription
+            if previousRoute?.outputs.first?.portType == .headphones {
+                playVideo(btn: playBtn)
+            }
+        }
+    }
+
     override public func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         navigationController?.isNavigationBarHidden = true
@@ -440,7 +456,7 @@ public class BFRecordScreenController: BFBaseViewController {
 
         // 进入活跃状态
         PQNotification.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
-
+        PQNotification.addObserver(self, selector: #selector(routeChangeNofify(nofify:)), name: AVAudioSession.routeChangeNotification, object: nil)
         // 进入非活跃状态
         PQNotification.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
 
@@ -476,14 +492,14 @@ public class BFRecordScreenController: BFBaseViewController {
             let header = dicResult?["header"] as? [String: Any]
             let payload = dicResult?["payload"] as? [String: Any]
 
-            BFLog(1, message: "识别结果:) \((payload)!),taskId:\((header?["task_id"] as? String) ?? "taskId"), 识别时间:\((((payload?["begin_time"]) as? Int) ?? 0)) ~ \((((payload?["time"]) as? Int) ?? 0)) startTime:\(self?.recorderManager?.voiceModel?.startCMTime.seconds ?? 0.0)")
+            BFLog(1, message: "识别结果:) \(payload?["result"]) ,taskId:\((header?["task_id"] as? String) ?? "taskId"), 识别时间:\(((payload?["begin_time"]) as? Int) ?? 0) ~ \(((payload?["time"]) as? Int) ?? 0) startTime:\(self?.recorderManager?.voiceModel?.startCMTime.seconds ?? 0.0)")
+
             DispatchQueue.main.async {
                 // 1,保存字幕数据 begin_time是开始出现文字的时间,time 是结束文字出现的时间 单位都为毫秒,都是相对于录制音频数据整段时间。self.recorderManager.voiceModel?.startCMTime.seconds 为开始的录制的时间,开始和结束都要加上这个时差
 
                 let newSubtitle = PQEditSubTitleModel()
                 // 任务全局唯一ID,请记录该值,便于排查问题。 每次 startRecorder 和 stopRecoder 之间  task_Id都不会变化
                 newSubtitle.taskID = (header?["task_id"] as? String) ?? ""
-                
                 BFLog(1, message: "对应关系:字幕所属地址:\((audioFilePath ?? "b").replacingOccurrences(of: documensDirectory, with: "")), 开始录音输入:\((self?.recorderManager?.voiceModel?.wavFilePath ?? "aa").replacingOccurrences(of: documensDirectory, with: ""))")
                 // 这里加300ms 是因为返回结果为了切到字,时长提前一些时间,具体时间官方没说和原音频有关系。这里我们先延后300ms 单位:毫秒。
                 if let audioUrl = audioFilePath, URL(fileURLWithPath: audioUrl).deletingPathExtension().lastPathComponent.contains(URL(fileURLWithPath: self?.recorderManager?.voiceModel?.wavFilePath ?? "aa").deletingPathExtension().lastPathComponent) {
@@ -508,7 +524,7 @@ public class BFRecordScreenController: BFBaseViewController {
                             BFLog(1, message: "卡在录音尾巴上了1")
                             newSubtitle.timelineIn = newSubtitle.timelineIn - CMTime(seconds: 0.1, preferredTimescale: 1000)
                         }
-                    }else {
+                    } else {
                         BFLog(1, message: "没有对应音频播放记录,出现错误!!!!!!")
                         return
                     }
@@ -530,12 +546,13 @@ 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 ?? "") {
                 // 加入到语音数组里
-               
+
                 model.endCMTime = sself.currentAssetProgress
-                BFLog(1, message: "对应关系:录制结束文件地址:\((model.wavFilePath ?? "") .replacingOccurrences(of: documensDirectory, with: "")) 开始录音前地址:\((self?.recorderManager?.voiceModel?.wavFilePath ?? "aa").replacingOccurrences(of: documensDirectory, with: ""))-  \(model.startCMTime.seconds)-\(model.endCMTime.seconds)-\(model.endCMTime.seconds - model.startCMTime.seconds)")
+                BFLog(1, message: "对应关系:录制结束文件地址:\((model.wavFilePath ?? "").replacingOccurrences(of: documensDirectory, with: "")) 开始录音前地址:\((self?.recorderManager?.voiceModel?.wavFilePath ?? "aa").replacingOccurrences(of: documensDirectory, with: ""))-  \(model.startCMTime.seconds)-\(model.endCMTime.seconds)-\(model.endCMTime.seconds - model.startCMTime.seconds)")
                 /// 注:录音机回调的录音时长大于一秒,而业务逻辑计算的会小于一秒
                 if (model.endCMTime.seconds - model.startCMTime.seconds) < 1 {
                     // 取消录制
@@ -547,7 +564,9 @@ public class BFRecordScreenController: BFBaseViewController {
 
                 var deletedVoices = [(PQVoiceModel, Int)]()
                 // 要删除的字幕
-                var deletedTitlesTemp = [(PQEditSubTitleModel, Int)]()
+                var deletedTitlesTemp = [PQEditSubTitleModel]()
+                
+                // 查找要删除的音频和字幕数据
                 for (i, m) in sself.itemModels[sself.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 {
@@ -562,6 +581,8 @@ public class BFRecordScreenController: BFBaseViewController {
                         m.wavFilePath == tempM.wavFilePath
                     }
                 }
+                
+                
                 BFLog(1, message: "添加录音文件:\(model.startCMTime.seconds) -- \(model.endCMTime.seconds)")
                 sself.itemModels[sself.currItemModelIndex].voiceStickers.append(model)
                 // 如果是图片素材同时有需要删除的录音时需要调整录音文件开始结束时间
@@ -620,8 +641,7 @@ public class BFRecordScreenController: BFBaseViewController {
                 sself.currentPlayRecordIndex = -3 // 刚录音完,不需要播放录音
                 BFLog(3, message: "重置播放index-\(#function) = \(sself.currentPlayRecordIndex)")
                 recorderManager?.voiceModel = nil
-            }
-            else{
+            } else {
                 BFLog(2, message: "数据出错!!!!\(voideModel?.wavFilePath ?? "")")
             }
         }
@@ -781,7 +801,7 @@ public class BFRecordScreenController: BFBaseViewController {
         progreddL.snp.makeConstraints { make in
             make.width.equalTo(100)
             make.centerX.equalToSuperview()
-            make.top.equalToSuperview().offset(-8)
+            make.top.equalToSuperview().offset(-5)
             make.height.equalTo(18)
         }
 
@@ -920,22 +940,19 @@ public class BFRecordScreenController: BFBaseViewController {
 
     /// 删除指定段落的所有字幕 数据
     /// - Parameter voiceModel: 删除的音频数据
-    func deleteTitles(voiceModel: PQVoiceModel) -> [(PQEditSubTitleModel, Int)] {
+    func deleteTitles(voiceModel: PQVoiceModel) -> [PQEditSubTitleModel] {
         BFLog(message: "itemModels[currItemModelIndex].titleStickers  删除前:\(itemModels[currItemModelIndex].titleStickers.count) model.startCMTime.seconds: \(voiceModel.startCMTime.seconds) model.end: \(voiceModel.endCMTime.seconds)")
-        var deleteTemp = [(PQEditSubTitleModel, Int)]()
+        var deleteTemp = [PQEditSubTitleModel]()
 
-        for (ind, sticker) in itemModels[currItemModelIndex].titleStickers.enumerated() {
-            if sticker.audioFilePath == voiceModel.wavFilePath {
-                deleteTemp.append((sticker, ind))
-            }
-        }
         // 从原数组中删除
-        let arr = itemModels[currItemModelIndex].titleStickers.filter { model in
-            !deleteTemp.contains { tuple in
-                tuple.0.audioFilePath == model.audioFilePath
+        itemModels[currItemModelIndex].titleStickers.removeAll { m in
+            let su = (m.audioFilePath == voiceModel.wavFilePath)
+            if su {
+                deleteTemp.append(m)
             }
+            return su
         }
-        itemModels[currItemModelIndex].titleStickers = arr
+  
         BFLog(message: "itemModels[currItemModelIndex].titleStickers  删除后:\(itemModels[currItemModelIndex].titleStickers.count)")
 
         // 清空字幕UI
@@ -953,7 +970,7 @@ public class BFRecordScreenController: BFBaseViewController {
             var event = WithDrawModel(type: 3, timestamp: currentAssetProgress.seconds, deletedVoices: [(model, isStopAtRecordRange)])
             event.deletedTittles = deleteTitles(voiceModel: model)
             events.append(event)
-            
+
             // 注:删除录音后图片素材需要回撤指针进度,同时后面录音往前挪
             if itemModels[currItemModelIndex].mediaType == .IMAGE {
                 let currDuration = model.endCMTime.seconds - model.startCMTime.seconds
@@ -974,6 +991,11 @@ public class BFRecordScreenController: BFBaseViewController {
                 }
                 /// 重绘录音进度视图
                 resetAllIndirectionView()
+                
+                // 判断是否无录音了
+                if itemModels[currItemModelIndex].materialDuraion == 0 {
+                    playBtn.isSelected = true
+                }
             }
             searchStopAtRecordRange()
             changeWithDrawBtnLayout(3)
@@ -1004,6 +1026,7 @@ public class BFRecordScreenController: BFBaseViewController {
             return
         }
 
+        recorderManager?.cancelTitleService()
         // 开始时间
         beginOnStartBtn = true
         isRecording = true
@@ -1015,7 +1038,7 @@ public class BFRecordScreenController: BFBaseViewController {
         model.currIndex = currItemModelIndex
         model.volume = 100
         recorderManager?.voiceModel = model
-        BFLog(3,message: "开始录制-开始:currentAssetProgress=\(currentAssetProgress.seconds),cuInde=\(currItemModelIndex),\(model)")
+        BFLog(3, message: "开始录制-开始:currentAssetProgress=\(currentAssetProgress.seconds),cuInde=\(currItemModelIndex),\(model)")
         recorderManager?.startRecord()
         recorderManager?.audioRecorder?.startNeoNui(NeoNuiToken ?? "", appid: NeoNuiAPPID ?? "")
         isRecording = true
@@ -1049,10 +1072,10 @@ public class BFRecordScreenController: BFBaseViewController {
         beginOnStartBtn = false
 
         recordBtn.isEnabled = false
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {[weak self] in
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in
             self?.recordBtn.isEnabled = true
         }
-        
+
         isRecording = false
 //        progressThumV.progressView.isUserInteractionEnabled = true
 //        collectionView.isScrollEnabled = true
@@ -1078,7 +1101,6 @@ public class BFRecordScreenController: BFBaseViewController {
         if !avatarView.isHidden {
             avatarView.endRecord()
         }
-        
     }
 
     @objc func cancleRecord() {
@@ -1093,7 +1115,7 @@ public class BFRecordScreenController: BFBaseViewController {
     /// 不足一秒,主动取消录制
     /// - Parameter voiceModel: <#voiceModel description#>
     @objc func recordManagerCancelRecord(voiceModel: PQVoiceModel?) {
-        BFLog(3,message: "开始录制-取消:currentAssetProgress=\(currentAssetProgress.seconds),cuInde=\(currItemModelIndex),currIndex=\(voiceModel?.currIndex ?? 0),\(String(describing: voiceModel)),\(String(describing:recorderManager?.voiceModel))")
+        BFLog(3, message: "开始录制-取消:currentAssetProgress=\(currentAssetProgress.seconds),cuInde=\(currItemModelIndex),currIndex=\(voiceModel?.currIndex ?? 0),\(String(describing: voiceModel)),\(String(describing: recorderManager?.voiceModel))")
         if voiceModel != nil, currentAssetProgress.seconds - (recorderManager?.voiceModel?.startCMTime.seconds ?? 0) < 1.0 {
             cShowHUB(superView: nil, msg: "最短录制1秒")
         }
@@ -1138,13 +1160,12 @@ public class BFRecordScreenController: BFBaseViewController {
                     let model = itemModels[currItemModelIndex].voiceStickers[modelIndex]
                     itemModels[currItemModelIndex].voiceStickers.remove(at: modelIndex)
                     indirectionView?.deleteItem(index: modelIndex)
+                    
                     // 删除对应字幕
-                    let deleteTitiles = deleteTitles(voiceModel: model)
-                    for title in deleteTitiles {
-                        itemModels[currItemModelIndex].titleStickers.removeAll { model in
-                            title.0.timelineIn == model.timelineIn
-                        }
+                    itemModels[currItemModelIndex].titleStickers.removeAll { m in
+                        m.audioFilePath == model.wavFilePath
                     }
+                    
                     // 恢复被覆盖的音频
                     var tuples = action.deletedVoices
                     if tuples != nil, tuples!.count > 0 {
@@ -1159,7 +1180,7 @@ public class BFRecordScreenController: BFBaseViewController {
                     let titleTuples = action.deletedTittles
                     if titleTuples != nil, titleTuples!.count > 0 {
                         titleTuples?.forEach { titleTuple in
-                            itemModels[currItemModelIndex].titleStickers.insert(titleTuple.0, at: titleTuple.1)
+                            itemModels[currItemModelIndex].titleStickers.append(titleTuple)
                         }
                     }
                     jumpTime = model.startCMTime.seconds
@@ -1200,7 +1221,7 @@ public class BFRecordScreenController: BFBaseViewController {
                 let titleTuples = action.deletedTittles
                 if titleTuples != nil, titleTuples!.count > 0 {
                     titleTuples?.forEach { titleTuple in
-                        itemModels[currItemModelIndex].titleStickers.insert(titleTuple.0, at: titleTuple.1)
+                        itemModels[currItemModelIndex].titleStickers.append(titleTuple)
                     }
                 }
                 if itemModels[currItemModelIndex].mediaType == .IMAGE {
@@ -1247,6 +1268,7 @@ public class BFRecordScreenController: BFBaseViewController {
     @objc func playVideo(btn: UIButton) {
         if itemModels[currItemModelIndex].mediaType == .IMAGE && itemModels[currItemModelIndex].voiceStickers.count <= 0 {
             BFLog(message: "图片没有录音无法播放")
+            btn.isSelected = true
             return
         }
         btn.isSelected = !btn.isSelected
@@ -1400,7 +1422,6 @@ public class BFRecordScreenController: BFBaseViewController {
     // MARK: - 音视频处理
 
     func playRecord(at currentT: CMTime, periodicTimeObserver: @escaping (_ time: CMTime, _ currentItem: AVPlayerItem) -> Void, didPlayToEndTime: @escaping (_ recordedInfo: (Int, PQVoiceModel)?, _ currentItem: AVPlayerItem?) -> Void, playFailed _: @escaping (_ recordedInfo: (Int, PQVoiceModel)?, _ currentItem: AVPlayerItem?) -> Void) {
-        
 //        if currentPlayRecordIndex >= 0 {
 //            if assetPlayer?.volume != haveSpeakVolume{
 //                assetPlayer?.volume = haveSpeakVolume
@@ -1410,23 +1431,23 @@ public class BFRecordScreenController: BFBaseViewController {
 //                assetPlayer?.volume = noSpeakVolume
 //            }
 //        }
-        
+
         if itemModels[currItemModelIndex].voiceStickers.first(where: { m in
-            return CMTimeCompare(m.startCMTime, currentT) <= 0 && CMTimeCompare(currentT, m.endCMTime) <= 0
-        }) != nil{
-            if assetPlayer?.volume != haveSpeakVolume{
+            CMTimeCompare(m.startCMTime, currentT) <= 0 && CMTimeCompare(currentT, m.endCMTime) <= 0
+        }) != nil {
+            if assetPlayer?.volume != haveSpeakVolume {
                 assetPlayer?.volume = haveSpeakVolume
             }
-        }else{
+        } else {
             if assetPlayer?.volume != noSpeakVolume {
                 assetPlayer?.volume = noSpeakVolume
             }
         }
-        
+
         if currentPlayRecordIndex == -3 { // 刚录音完,不需要播放
             return
         }
-        
+
         // 先排序,再查找下一个需要播放的录音
         let list = itemModels[currItemModelIndex].voiceStickers.sorted { m1, m2 in
             m1.startCMTime.seconds < m2.startCMTime.seconds
@@ -1434,14 +1455,14 @@ public class BFRecordScreenController: BFBaseViewController {
         let (shouldPlayRecordIndex, recordedAudio) = list.enumerated().first { model in
             model.1.endCMTime.seconds > CMTimeGetSeconds(currentT)
         } ?? (-1, nil)
-        
+
         // 没找到,代表后边没有录音需求了
         guard let recordedAudio = recordedAudio, recordedAudio.wavFilePath.count > 0 else {
             BFLog(3, message: "未找到可播放录音")
             return
         }
         BFLog(1, message: "当前时间:\(CMTimeGetSeconds(currentT)), 找到的音频:\(recordedAudio.startCMTime.seconds) ~ \(recordedAudio.endCMTime.seconds), \(recordedAudio.wavFilePath ?? "")")
-        
+
         // 创建播放器
         if recordPlayer == nil || (recordPlayer?.currentItem?.asset as? AVURLAsset)?.url.lastPathComponent != (recordedAudio.wavFilePath as NSString?)?.lastPathComponent {
             let newItem = AVPlayerItem(url: URL(fileURLWithPath: recordedAudio.wavFilePath))
@@ -1475,7 +1496,7 @@ public class BFRecordScreenController: BFBaseViewController {
             }
             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 {
+                guard let sself = self, let rPlay = recordPlayer else {
                     BFLog(3, message: "sself为空")
                     return
                 }
@@ -1593,6 +1614,10 @@ public class BFRecordScreenController: BFBaseViewController {
     func play() {
         BFLog(1, message: "开始播放 \(currentAssetProgress.seconds)")
         recorderManager?.voiceModel = nil
+        
+        itemModels[currItemModelIndex].titleStickers.sort { m1, m2 in
+            m1.timelineIn < m2.timelineIn
+        }
 
         isNormalPlaying = true
         if isEndPlay {
@@ -1611,7 +1636,7 @@ public class BFRecordScreenController: BFBaseViewController {
                 movie?.startProcessing()
                 movieIsProcessing = true
             }
-            //add by ak 切换段落时会有一小段原素材的声音播放时先把视频原音量设置为0
+            // add by ak 切换段落时会有一小段原素材的声音播放时先把视频原音量设置为0
             assetPlayer?.volume = 0
             assetPlayer?.play()
         } else {
@@ -1630,14 +1655,14 @@ public class BFRecordScreenController: BFBaseViewController {
     func pause() {
         BFLog(1, message: "暂停播放")
         isNormalPlaying = false
-        
+
         // ---- 修复暂停播放回退问题
         avplayerTimeObserver?.invalidate()
         avplayerTimeObserver = nil
         recordPlayerTimeObserver?.invalidate()
         recordPlayerTimeObserver = nil
         // ----
-        
+
         subtitleBtn.isHidden = false
         soundSettingBtn.isHidden = false
         withDrawBtn.isHidden = false
@@ -1649,7 +1674,7 @@ public class BFRecordScreenController: BFBaseViewController {
         pauseTime = currentAssetProgress.seconds
         currentPlayRecordIndex = -1
         hadPrepareToPlayRecord = false
-        
+
         // 暂停状态
         playBtn.isSelected = (itemModels[currItemModelIndex].mediaType == .IMAGE && itemModels[currItemModelIndex].voiceStickers.count <= 0)
     }
@@ -1980,10 +2005,11 @@ extension BFRecordScreenController: UICollectionViewDelegate, UICollectionViewDa
 
     public func collectionView(_: UICollectionView, didSelectItemAt _: IndexPath) {}
 
-    public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+    public func scrollViewWillBeginDragging(_: UIScrollView) {
         BFLog(1, message: "开始滚动")
         recordBtn.isEnabled = false
     }
+
     public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
         endScrollItem(scrollView)
     }
@@ -1997,7 +2023,7 @@ extension BFRecordScreenController: UICollectionViewDelegate, UICollectionViewDa
     func endScrollItem(_ scrollView: UIScrollView) {
 //    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
         BFLog(1, message: "滚动结束")
-        
+
         let page = Int((scrollView.contentOffset.x + scrollView.frame.width / 2) / scrollView.frame.width)
         if page != currItemModelIndex {
             // 切换素材时先把录制状态切为不可用,延迟可点,避免在缩略图未加载出来时即可录制
@@ -2038,10 +2064,10 @@ extension BFRecordScreenController: UICollectionViewDelegate, UICollectionViewDa
             // 重置播放器
             assetPlayer?.seek(to: CMTime.zero)
             recordPlayer?.seek(to: CMTime.zero)
-            
+
             if let voice = itemModels[page].voiceStickers.enumerated().first(where: { m in
                 m.1.startTime == 0
-            }){
+            }) {
                 currentPlayRecordIndex = voice.0
             }
 
@@ -2091,7 +2117,7 @@ public extension BFRecordScreenController {
             recordStartPlayTime = currentAssetProgress
             currenStartPlayTime = CMTime.zero
         }
-        playRecord(at: time, periodicTimeObserver: { [weak self] currentT, currentItem in
+        playRecord(at: time, periodicTimeObserver: { [weak self] currentT, _ in
 //            BFLog(1, message: "播放录音进度:\(currentT.seconds),\(currentItem)")
             if self?.itemModels[self?.currItemModelIndex ?? 0].mediaType == .IMAGE, self?.isNormalPlaying ?? false {
                 self?.imageRecordProgress(progress: CMTimeGetSeconds(currentT))

+ 2 - 2
BFRecordScreenKit/Classes/RecordScreen/View/BFVideoThumbProgressView.swift

@@ -156,7 +156,7 @@ class BFVideoThumbProgressView: UIView {
 
     /// 处理图片缩略图
     func dealWithImageThumb() {
-        guard let image = recordItem?.coverImg else {
+        guard let image = (recordItem?.thumbImgs.first ?? recordItem?.coverImg) else {
             addThumbImages(images: [UIImage]())
             return
         }
@@ -207,7 +207,7 @@ class BFVideoThumbProgressView: UIView {
             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?.coverImg else {
+                guard let image = (sslf.recordItem?.thumbImgs.first ?? sslf.recordItem?.coverImg) else {
                     return
                 }
                 if sslf.lastImg != nil, sslf.lastImg?.superview != nil {