Explorar o código

fix: 优化炫声、卡点、创意模板、最终合成页 取消逻辑处理 #trello(【卡点】保存的加载用白色弹窗的样式)

Melody %!s(int64=5) %!d(string=hai) anos
pai
achega
2e9621419a

+ 6 - 5
SuperShow/OJAGPUImageDecoder/OJADecoder.swift

@@ -275,14 +275,14 @@ extension OJADecoder {
     }
     
 
-    func mixAudio(videoURL: URL ,finish: @escaping (_ exportAsset: AVURLAsset?) -> Void) throws {
+    func mixAudio(videoURL: URL ,finish: @escaping (_ exportAsset: AVURLAsset?) -> Void) throws -> AVAssetExportSession? {
         /// 渲染完成视频资源
         let videoAsset = AVURLAsset(url: videoURL)
         
         /// 获取视频轨道
         guard let videoTrack = videoAsset.tracks(withMediaType: AVMediaType.video).first else {
             finish(videoAsset)
-            return
+            return nil
         }
         
         /// 获取用户资源视频数组
@@ -303,7 +303,7 @@ extension OJADecoder {
         /// 无音轨,无需进行音频合成
         guard emptyAudioCount != movieInputArray.count else {
             finish(videoAsset)
-            return
+            return nil
         }
         
         let frameRate = round(self.templateDataModel.frameRate)
@@ -315,7 +315,7 @@ extension OJADecoder {
         /// 插入视频
         guard ((try? targetVideoCompositionTrack?.insertTimeRange(videoTrack.timeRange, of: videoTrack, at: CMTime.zero)) != nil) else {
             finish(videoAsset)
-            return
+            return nil
         }
     
         /// 插入音频
@@ -385,7 +385,7 @@ extension OJADecoder {
         
         /// 导出视频,完成回调
         let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: composition)
-        _ = WSSMediaOperationTool.exportVideo(withVideoAsset: composition,
+        let exportSession = WSSMediaOperationTool.exportVideo(withVideoAsset: composition,
                                           exportPath: outputURL,
                                           presetName: presetName,
                                           fileType: AVFileType.mp4,
@@ -398,6 +398,7 @@ extension OJADecoder {
                                                 finish(videoAsset)
                                             }
         }
+            return exportSession
     }
 }
 

+ 10 - 8
SuperShow/Tool/WSSMediaOperationTool.swift

@@ -442,11 +442,13 @@ extension WSSMediaOperationTool {
         } else if _audioVolume > 1.0 {
             _audioVolume = 1.0
         }
-        if _audioVolume == 0.0 {
-            // 如果需要混入的音频音量为0,即表示不混音,直接返回原视频资源
-            finish(videoAsset)
-            return nil
-        }
+        
+        /// 目前先注释掉,目前可以调节原生与配乐都为0.0
+//        if _audioVolume == 0.0 {
+//            // 如果需要混入的音频音量为0,即表示不混音,直接返回原视频资源
+//            finish(videoAsset)
+//            return nil
+//        }
 
         var _videoVolume = videoVolume
         if _videoVolume < 0.0 {
@@ -962,7 +964,7 @@ extension WSSMediaOperationTool {
         }
     }
 
-    class func insertWatermark(withVideoAsset videoAsset: AVAsset, watermark: UIImage, watermarkSize: CGSize, finish: @escaping (URL?) -> Void) throws {
+    class func insertWatermark(withVideoAsset videoAsset: AVAsset, watermark: UIImage, watermarkSize: CGSize, finish: @escaping (URL?) -> Void) throws -> AVAssetExportSession? {
         do {
             guard videoAsset.tracks(withMediaType: AVMediaType.video).count > 0 else {
                 throw WSSMediaOperationTool.MediaToolError.sourceError
@@ -1028,14 +1030,14 @@ extension WSSMediaOperationTool {
                 // 导出视频
                 let outputPath: String = kOJSUserCacheDirectory + "/" + "tmp_export_watermark_video" + ".mp4"
                 let outputUrl: URL = URL(fileURLWithPath: outputPath)
-                _ = exportVideo(withVideoAsset: composition, exportPath: outputUrl, presetName: AVAssetExportPresetHighestQuality, fileType: AVFileType.mp4, videoComposition: videoComposition, audioMix: nil) { isSuccess in
+                let exportSession = exportVideo(withVideoAsset: composition, exportPath: outputUrl, presetName: AVAssetExportPresetHighestQuality, fileType: AVFileType.mp4, videoComposition: videoComposition, audioMix: nil) { isSuccess in
                     if isSuccess {
                         finish(outputUrl)
                     } else {
                         finish(nil)
                     }
                 }
-
+                return exportSession
             } else {
                 throw WSSMediaOperationTool.MediaToolError.sourceError
             }

+ 7 - 8
SuperShow/UI/CoolVoice/WSSCoolVoiceSubtitleController.swift

@@ -263,16 +263,18 @@ class WSSCoolVoiceSubtitleController: MTViewController {
                 }
             }
 
-            if let effectExport = self?.effectExport {
-                effectExport.endProcessing()
-            }
-
             if let mixAudioExport = self?.mixAudioExportSeesion {
                 if mixAudioExport.status == .exporting {
                     self?.stopMixAuiodExportDisplayLink()
                     mixAudioExport.cancelExport()
                 }
             }
+
+            if let effectExport = self?.effectExport {
+                self?.effectExport?.perFrameProcessingCallback = nil
+                effectExport.endProcessing()
+            }
+
             hideHud()
         }
     }
@@ -758,6 +760,7 @@ class WSSCoolVoiceSubtitleController: MTViewController {
                             self?.videoSize = vs
                         }
                         // 2. 字幕合成
+                        self?.stopExportDisplayLink()
                         self?.startCombineSubtitle(resultVideoAsset: videoUrlAsset, audioAsset: combineAudio)
                     }
 
@@ -1038,11 +1041,7 @@ fileprivate extension WSSCoolVoiceSubtitleController {
 
         effectExport?.didEndProcessingHandle = { [weak self] isUserCancel in
             guard !isUserCancel else {
-                // PS:因为effectExport的movieFile 回调了endFrame 走到这里,但是还会走一次preFrameProgress,会导致hideHud失败,所以延迟0.3秒进行hideHub
-                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
                     hideHud()
-                }
-
                 return
             }
             self?.movieWriter?.finishRecording(completionHandler: { [weak self] in

+ 128 - 51
SuperShow/UI/EditVideo/WSSEditVideoViewController.swift

@@ -104,13 +104,17 @@ class WSSEditVideoViewController: MTViewController {
     fileprivate var templateModel: WSSCommonTemplateModel?
 
     fileprivate var exportSession: AVAssetExportSession?
-
     fileprivate var updateDisplayLink: CADisplayLink?
 
     fileprivate var mixAudioExportSession: AVAssetExportSession?
-
     fileprivate var mixAudioUpdateDisplayLink: CADisplayLink?
 
+    fileprivate var waterMarkSession: AVAssetExportSession?
+    fileprivate var waterMarkSessionDisplayLink: CADisplayLink?
+
+    /// 用于waterMark主动Cancel 没有条件判断
+    fileprivate var isUserCancel = false
+
     fileprivate var isTwoExportSessionMode = false
 
     fileprivate var pasterInputs: [OJAPasterFrameInput] = []
@@ -219,6 +223,7 @@ extension WSSEditVideoViewController {
 
         // 视频导出取消
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
 
             if let exportEfftect = self?.effectExport {
                 exportEfftect.endProcessing()
@@ -242,6 +247,11 @@ extension WSSEditVideoViewController {
                 }
             }
 
+            if let waterMartExport = self?.waterMarkSession {
+                waterMartExport.cancelExport()
+                self?.waterMarkSession = nil
+            }
+
             hideHud()
             self?.videoPlayer?.play()
         }
@@ -630,6 +640,7 @@ private extension WSSEditVideoViewController {
             movieAsset = AVURLAsset(url: url)
         }
         isTwoExportSessionMode = false
+        isUserCancel = false
 
         /// 当用户添加新贴纸或者设置滤镜,需要进行重新渲染
         if pasterOverlayView.pasterArray.count > 0 || lookupFilter != nil {
@@ -694,26 +705,19 @@ private extension WSSEditVideoViewController {
                             volume = controlMusicView.currentMusicVolume()
                         }
 
+                        let combineVideoAsset = AVURLAsset(url: outputUrl)
+                        let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: combineVideoAsset)
                         if let musicFile = self?.cutMusicFileUrl, let originalVideo = self?.movieAsset, volume.1 > 0.0 {
                             // 有选择音乐,并且音乐音量大于0.0,视频混入音乐
-                            let videoAsset = AVURLAsset(url: outputUrl)
                             let musicAsset = AVURLAsset(url: musicFile)
-                            let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: videoAsset)
                             if let mixAudioExportSession = try? WSSMediaOperationTool.mixAudio(withAudioAsset: musicAsset,
-                                                                                               forVideo: videoAsset,
+                                                                                               forVideo: combineVideoAsset,
                                                                                                originalAudioAsset: originalVideo,
                                                                                                audioVolume: volume.1,
                                                                                                videoVolume: volume.0,
                                                                                                presetName: presetName,
                                                                                                finish: { [weak self] result in
-                                                                                                   DispatchQueue.main.async {
-                                                                                                       self?.stopMixAudioDisplayLink()
-                                                                                                       if result != nil, let path = (result as? AVURLAsset)?.url {
-                                                                                                           self?.combineVideoSuccess(path.absoluteString.replacingOccurrences(of: "file://", with: ""), needAddWaterMark: false)
-                                                                                                       } else {
-                                                                                                           hideHud()
-                                                                                                       }
-                                                                                                   }
+                                                                                                   self?.mixAudioFinishHandle(resultAsset: result)
                             }) {
                                 DispatchQueue.main.async {
                                     self?.setupMixAudioDisplaLink()
@@ -724,24 +728,20 @@ private extension WSSEditVideoViewController {
                         } else {
                             // 没有选音乐或者配乐音量为0,保留视频原声
                             if let originalVideo = self?.movieAsset {
-                                let combineVideo = AVURLAsset(url: outputUrl)
-                                let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: combineVideo)
-                                _ = try? WSSMediaOperationTool.mixAudio(withAudioAsset: originalVideo,
-                                                                        forVideo: combineVideo,
-                                                                        originalAudioAsset: nil,
-                                                                        audioVolume: volume.0,
-                                                                        videoVolume: 0.0,
-                                                                        presetName: presetName,
-                                                                        finish: { [weak self] mixResult in
-                                                                            DispatchQueue.main.async {
-                                                                                self?.stopMixAudioDisplayLink()
-                                                                                if mixResult != nil, let path = (mixResult as? AVURLAsset)?.url {
-                                                                                    self?.combineVideoSuccess(path.absoluteString.replacingOccurrences(of: "file://", with: ""), needAddWaterMark: false)
-                                                                                } else {
-                                                                                    hideHud()
-                                                                                }
-                                                                            }
-                                })
+                                if let mixAudioExportSession = try? WSSMediaOperationTool.mixAudio(withAudioAsset: originalVideo,
+                                                                                                   forVideo: combineVideoAsset,
+                                                                                                   originalAudioAsset: nil,
+                                                                                                   audioVolume: volume.0,
+                                                                                                   videoVolume: 0.0,
+                                                                                                   presetName: presetName,
+                                                                                                   finish: { [weak self] mixResult in
+                                                                                                       self?.mixAudioFinishHandle(resultAsset: mixResult, needAddWaterMark: false)
+                                }) {
+                                    DispatchQueue.main.async {
+                                        self?.setupMixAudioDisplaLink()
+                                        self?.mixAudioExportSession = mixAudioExportSession
+                                    }
+                                }
                             }
                         }
 
@@ -782,27 +782,39 @@ private extension WSSEditVideoViewController {
                                                                                    audioVolume: volume.1,
                                                                                    videoVolume: volume.0,
                                                                                    presetName: presetName,
-                                                                                   finish: { result in
-                                                                                       if result != nil, let path = (result as? AVURLAsset)?.url {
-                                                                                           self.combineVideoSuccess(path.absoluteString.replacingOccurrences(of: "file://", with: ""))
-                                                                                       }
+                                                                                   finish: { [weak self] result in
+                                                                                       self?.mixAudioFinishHandle(resultAsset: result)
                 }) {
                     setupMixAudioDisplaLink()
                     self.mixAudioExportSession = mixAudioExportSession
                 }
             } else {
-                // 没有选音乐或者配乐音量为0,保留视频原声
+                // 无选择音乐,但是用户可调控源视频音量大小
                 let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: movieAsset)
-                exportSession = WSSMediaOperationTool.exportVideo(withVideoAsset: movieAsset,
-                                                                  exportPath: URL(fileURLWithPath: kFinalOutputURLString),
-                                                                  presetName: presetName,
-                                                                  fileType: AVFileType.mp4,
-                                                                  videoComposition: nil,
-                                                                  audioMix: nil) { [weak self] isSuccess in
-                    if isSuccess {
-                        self?.combineVideoSuccess(kFinalOutputURLString)
-                    }
+                if let mixAudioExportSession = try? WSSMediaOperationTool.mixAudio(withAudioAsset: movieAsset,
+                                                                                   forVideo: movieAsset,
+                                                                                   originalAudioAsset: nil,
+                                                                                   audioVolume: volume.0,
+                                                                                   videoVolume: 0.0,
+                                                                                   presetName: presetName,
+                                                                                   finish: { [weak self] result in
+                                                                                       self?.mixAudioFinishHandle(resultAsset: result)
+                }) {
+                    setupMixAudioDisplaLink()
+                    self.mixAudioExportSession = mixAudioExportSession
                 }
+//                // 没有选音乐或者配乐音量为0,保留视频原声
+//                let presetName = WSSCreativeTemplateConstant.getAdjustNormalPreset(videoAsset: movieAsset)
+//                exportSession = WSSMediaOperationTool.exportVideo(withVideoAsset: movieAsset,
+//                                                                  exportPath: URL(fileURLWithPath: kFinalOutputURLString),
+//                                                                  presetName: presetName,
+//                                                                  fileType: AVFileType.mp4,
+//                                                                  videoComposition: nil,
+//                                                                  audioMix: nil) { [weak self] isSuccess in
+//                    if isSuccess {
+//                        self?.combineVideoSuccess(kFinalOutputURLString)
+//                    }
+//                }
             }
         }
 
@@ -815,6 +827,17 @@ private extension WSSEditVideoViewController {
         }
     }
 
+    func mixAudioFinishHandle(resultAsset: AVAsset?, needAddWaterMark: Bool = true) {
+        DispatchQueue.main.async {
+            self.stopMixAudioDisplayLink()
+            if resultAsset != nil, let path = (resultAsset as? AVURLAsset)?.url {
+                self.combineVideoSuccess(path.absoluteString.replacingOccurrences(of: "file://", with: ""), needAddWaterMark: needAddWaterMark)
+            } else {
+                hideHud()
+            }
+        }
+    }
+
     func combineVideoSuccess(_ outputPath: String, needAddWaterMark: Bool = true) {
         if needAddWaterMark && WSSProfileItemModel.isWatermarkOn() {
             // 加水印
@@ -829,16 +852,25 @@ private extension WSSEditVideoViewController {
             let scale = 2
             let watermarkSize = CGSize(width: 35 * scale, height: 35 * scale)
 
-            try? WSSMediaOperationTool.insertWatermark(withVideoAsset: videoAsset, watermark: watermark, watermarkSize: watermarkSize, finish: { [weak self] tmpUrl in
+            let waterMarkSession = try? WSSMediaOperationTool.insertWatermark(withVideoAsset: videoAsset, watermark: watermark, watermarkSize: watermarkSize, finish: { [weak self] tmpUrl in
                 DispatchQueue.main.async {
+                    guard let userCancel = self?.isUserCancel,
+                        !userCancel
+                    else {
+                        hideHud()
+                        self?.stopWaterMarkDisplay()
+                        return
+                    }
                     if let finishUrl = tmpUrl {
+                        self?.stopWaterMarkDisplay()
                         self?.saveFinalVideoToAlbum(withOutputPath: finishUrl.path)
                     } else {
                         showHud(withOnlyText: "保存失败,稍后重试")
                     }
                 }
-
             })
+            self.waterMarkSession = waterMarkSession
+            setupWaterMarkDisplay()
         } else {
             saveFinalVideoToAlbum(withOutputPath: outputPath)
         }
@@ -865,7 +897,6 @@ private extension WSSEditVideoViewController {
 
             DispatchQueue.main.async { [weak self] in
                 if isSuccess {
-                    showProgressCancelHub(progress: 1.0, status: "正在合成...")
                     hideHud()
                     OJAJumpManager.jumpToEditFinishViewController(filePath: outputPath, videoLocalId: videoID, template: self?.templateModel, nav: nav)
                 }
@@ -881,6 +912,7 @@ private extension WSSEditVideoViewController {
     func cleanDisplayLink() {
         stopUpdateDisplayLink()
         stopMixAudioDisplayLink()
+        stopWaterMarkDisplay()
         hideHud()
     }
 
@@ -892,6 +924,8 @@ private extension WSSEditVideoViewController {
         }
     }
 
+    // MARK: mixAudioExportSession
+
     func stopMixAudioDisplayLink() {
         if let mixAudioDisplayLink = self.mixAudioUpdateDisplayLink {
             mixAudioDisplayLink.isPaused = true
@@ -928,12 +962,19 @@ private extension WSSEditVideoViewController {
         if let exportSession = self.mixAudioExportSession {
             let progress = exportSession.progress
             if isTwoExportSessionMode {
-                showProgressCancelHub(progress: 0.7 + 0.3 * progress, status: "正在合成...")
+                var realProgress = simd_clamp(0.7 + 0.3 * progress, 0.7, 1.0)
+                if WSSProfileItemModel.isWatermarkOn() {
+                    realProgress = simd_clamp(0.7 + 0.2 * progress, 0.7, 0.9)
+                }
+                showProgressCancelHub(progress: realProgress, status: "正在合成...")
             } else {
-                showProgressCancelHub(progress: progress, status: "正在合成...")
+                var realProgress = progress
+                if WSSProfileItemModel.isWatermarkOn() {
+                    realProgress = simd_clamp(0.5 * realProgress, 0.0, 0.5)
+                }
+                showProgressCancelHub(progress: realProgress, status: "正在合成...")
             }
             if progress >= 1.0 {
-//                hideHud()
                 displayLink.invalidate()
                 mixAudioUpdateDisplayLink = nil
             }
@@ -943,6 +984,42 @@ private extension WSSEditVideoViewController {
         }
     }
 
+    // MARK: waterMarkExportSession
+
+    func setupWaterMarkDisplay() {
+        if waterMarkSessionDisplayLink != nil {
+            waterMarkSessionDisplayLink?.isPaused = true
+            waterMarkSessionDisplayLink?.invalidate()
+            waterMarkSessionDisplayLink = nil
+        }
+        let displayLink = CADisplayLink(target: self, selector: #selector(updateWaterMarkProgress))
+        DispatchQueue.main.async {
+            displayLink.add(to: .current, forMode: .common)
+            self.waterMarkSessionDisplayLink = displayLink
+        }
+    }
+
+    func stopWaterMarkDisplay() {
+        if let displayLink = waterMarkSessionDisplayLink {
+            displayLink.isPaused = true
+            displayLink.invalidate()
+            waterMarkSessionDisplayLink = nil
+        }
+    }
+
+    @objc func updateWaterMarkProgress() {
+        if let exportSession = waterMarkSession {
+            let progress = exportSession.progress
+            var realProgress = simd_clamp(0.5 + 0.5 * progress, 0.5, 1.0)
+            if isTwoExportSessionMode {
+                realProgress = simd_clamp(0.9 + 0.1 * progress, 0.9, 1.0)
+            }
+            showProgressCancelHub(progress: realProgress, status: "正在合成...")
+        } else {
+            stopWaterMarkDisplay()
+        }
+    }
+
     // MARK: data
 
     func loadTopMuisc() {

+ 78 - 25
SuperShow/UI/Template/WSSCreativeTemplateEditViewController.swift

@@ -32,7 +32,6 @@ class WSSCreativeTemplateEditViewController: WSSMultiVideoEditViewController {
 
         // --
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
-            WSSSimpleLog("WSS 裁剪取消")
             self?.isCropVideoFail = true
             self?.isUserCancel = true
 
@@ -44,6 +43,12 @@ class WSSCreativeTemplateEditViewController: WSSMultiVideoEditViewController {
             if let templateDecoder = self?.templateDecoder {
                 templateDecoder.endProcessing()
             }
+
+            if let mixAudioExport = self?.mixAudioExportSession {
+                mixAudioExport.cancelExport()
+                self?.mixAudioExportSession = nil
+            }
+
             hideHud()
         }
     }
@@ -86,6 +91,9 @@ class WSSCreativeTemplateEditViewController: WSSMultiVideoEditViewController {
     /// 模板合成器
     fileprivate var templateDecoder: OJADecoder?
 
+    fileprivate var mixAudioExportSession: AVAssetExportSession?
+    fileprivate var mixAudioExportSessionDisplayLink: CADisplayLink?
+
     fileprivate var isUserCancel = false
     fileprivate var cropVideoTool: WSSTemplateEditCropVideoTool?
     fileprivate var cropVideoSuccessCount = 0
@@ -117,6 +125,8 @@ class WSSCreativeTemplateEditViewController: WSSMultiVideoEditViewController {
         videoPlayer?.removeFromSuperview()
         videoPlayer = nil
 
+        stopMixAudioExportSeesionDisplay()
+        
         templateDecoder?.endProcessing()
         templateDecoder = nil
     }
@@ -369,7 +379,6 @@ fileprivate extension WSSCreativeTemplateEditViewController {
             }
         }
 
-        // ---
         try? FileManager.default.removeItem(atPath: outputURLString)
         let movieWriter = OJAImageMovieWriter(movieURL: outputURL, size: templateSize)
         movieWriter?.shouldPassthroughAudio = true
@@ -385,31 +394,43 @@ fileprivate extension WSSCreativeTemplateEditViewController {
 
         templateDecoder?.didEndProcessingCallback = { [weak self] finish in
             movieWriter?.finishRecording(completionHandler: {
-                if finish {
-                    DispatchQueue.main.async { [weak self] in
-                        try? self?.templateDecoder?.mixAudio(videoURL: outputURL, finish: { [weak self] exportAsset in
-                            var outputAsset: AVAsset!
-                            if let exportAsset = exportAsset {
-                                outputAsset = exportAsset
-                            } else {
-                                let videoAsset = AVURLAsset(url: outputURL)
-                                outputAsset = videoAsset
-                            }
-                            DispatchQueue.main.async { [weak self] in
-                                showProgressCancelHub(progress: 1.0, status: "正在合成...")
-                                hideHud()
-                                self?.removeAppResignActiveObserver()
-                                OJAJumpManager.jumpToEditViewController(withAsset: outputAsset, mode: .creativeTemplate, template: self?.templateModel, nav: self?.navigationController)
-
-                                self?.templateDecoder?.endProcessing()
-                                self?.templateDecoder = nil
-                            }
-                        })
+                DispatchQueue.main.async { [weak self] in
+                    do {
+                        if finish {
+                            let mixExportSession = try self?.templateDecoder?.mixAudio(videoURL: outputURL, finish: { [weak self] exportAsset in
+                                guard let userCancel = self?.isUserCancel,
+                                    !userCancel
+                                else {
+                                    self?.stopMixAudioExportSeesionDisplay()
+                                    hideHud()
+                                    return
+                                }
+                                var outputAsset: AVAsset!
+                                if let exportAsset = exportAsset {
+                                    outputAsset = exportAsset
+                                } else {
+                                    let videoAsset = AVURLAsset(url: outputURL)
+                                    outputAsset = videoAsset
+                                }
+                                DispatchQueue.main.async { [weak self] in
+                                    self?.stopMixAudioExportSeesionDisplay()
+                                    hideHud()
+                                    self?.removeAppResignActiveObserver()
+                                    OJAJumpManager.jumpToEditViewController(withAsset: outputAsset, mode: .creativeTemplate, template: self?.templateModel, nav: self?.navigationController)
+
+                                    self?.templateDecoder?.endProcessing()
+                                    self?.templateDecoder = nil
+                                }
+                            })
+                            self?.mixAudioExportSession = mixExportSession
+                            self?.setupMixAudioExportSeesionDisplay()
+                        } else {
+                            showHud(withOnlyText: "合成失败", hideAfterDelay: 2.0)
+                        }
+                    } catch {
+                        showHud(withOnlyText: "合成失败", hideAfterDelay: 2.0)
                     }
-                } else {
-                    showHud(withOnlyText: "合成失败", hideAfterDelay: 2.0)
                 }
-
             })
         }
 
@@ -497,6 +518,38 @@ fileprivate extension WSSCreativeTemplateEditViewController {
     func removeAppResignActiveObserver() {
         NotificationCenter.default.lxm_removeObserver(self, name: UIApplication.willResignActiveNotification.rawValue, object: nil)
     }
+
+    // MARK: mixAudioExportSeesionDisPlayLink
+
+    func setupMixAudioExportSeesionDisplay() {
+        if mixAudioExportSessionDisplayLink != nil {
+            mixAudioExportSessionDisplayLink?.isPaused = true
+            mixAudioExportSessionDisplayLink?.invalidate()
+            mixAudioExportSessionDisplayLink = nil
+        }
+        let displayLink = CADisplayLink(target: self, selector: #selector(updateMixAudioExportSeesionProgress))
+        DispatchQueue.main.async {
+            displayLink.add(to: .current, forMode: .common)
+            self.mixAudioExportSessionDisplayLink = displayLink
+        }
+    }
+
+    func stopMixAudioExportSeesionDisplay() {
+        if let displayLink = mixAudioExportSessionDisplayLink {
+            displayLink.isPaused = true
+            displayLink.invalidate()
+            mixAudioExportSessionDisplayLink = nil
+        }
+    }
+
+    @objc func updateMixAudioExportSeesionProgress() {
+        if let exportSession = mixAudioExportSession {
+            let progress = simd_clamp(0.9 + 0.1 * exportSession.progress, 0.9, 1.0)
+            showProgressCancelHub(progress: progress, status: "正在合成...")
+        } else {
+            stopMixAudioExportSeesionDisplay()
+        }
+    }
 }
 
 // MARK: - 视频播放处理

+ 44 - 4
SuperShow/UI/Template/WSSMultiVideoEditViewController.swift

@@ -64,6 +64,10 @@ class WSSMultiVideoEditViewController: MTViewController {
     /// 记录视频是否播放状态 无论是main 还是 sub
     var isPlayerPlaying: Bool = false
 
+    var waterMarkSession: AVAssetExportSession?
+    var waterMarkSessionDisplayLink: CADisplayLink?
+    var waterMarkExportProgressUpdateCallBack: ((_ progress: Float) -> Void)?
+
     /// 子视频长按手势处理
     fileprivate lazy var longPressGesture: UILongPressGestureRecognizer = {
         let gesture = UILongPressGestureRecognizer { [unowned self] sender in
@@ -145,6 +149,8 @@ class WSSMultiVideoEditViewController: MTViewController {
         videoCropView?.removeAllSubViews()
         videoCropView?.removeFromSuperview()
         videoCropView = nil
+        
+        stopWaterMarkDisplay()
     }
 
     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
@@ -206,6 +212,38 @@ class WSSMultiVideoEditViewController: MTViewController {
                                   height: editMaxSize.height)
         return displayFrame
     }
+
+    // MARK: waterMarkSession
+
+    func setupWaterMarkDisplay() {
+        if waterMarkSessionDisplayLink != nil {
+            waterMarkSessionDisplayLink?.isPaused = true
+            waterMarkSessionDisplayLink?.invalidate()
+            waterMarkSessionDisplayLink = nil
+        }
+        let displayLink = CADisplayLink(target: self, selector: #selector(updateWaterMarkProgress))
+        DispatchQueue.main.async {
+            displayLink.add(to: .current, forMode: .common)
+            self.waterMarkSessionDisplayLink = displayLink
+        }
+    }
+
+    func stopWaterMarkDisplay() {
+        if let displayLink = waterMarkSessionDisplayLink {
+            displayLink.isPaused = true
+            displayLink.invalidate()
+            waterMarkSessionDisplayLink = nil
+        }
+    }
+
+    @objc func updateWaterMarkProgress() {
+        if let exportSession = waterMarkSession {
+            let progress = exportSession.progress
+            waterMarkExportProgressUpdateCallBack?(progress)
+        } else {
+            stopWaterMarkDisplay()
+        }
+    }
 }
 
 // MARK: - 子视频容器相关处理
@@ -527,7 +565,7 @@ class WSSMultiVideoEditViewController: MTViewController {
     func combineVideoSuccess(_ outputPath: String) {
         WSSLog("###合成视频成功###: \(outputPath)")
 
-        WSSHub.showHub("正在保存到相册...")
+//        WSSHub.showHub("正在保存到相册...")
 
         if WSSProfileItemModel.isWatermarkOn() {
             // 加水印
@@ -541,16 +579,17 @@ class WSSMultiVideoEditViewController: MTViewController {
             let scale = 2
             let watermarkSize = CGSize(width: 35 * scale, height: 35 * scale)
 
-            try? WSSMediaOperationTool.insertWatermark(withVideoAsset: videoAsset, watermark: watermark, watermarkSize: watermarkSize, finish: { [weak self] tmpUrl in
+            waterMarkSession = try? WSSMediaOperationTool.insertWatermark(withVideoAsset: videoAsset, watermark: watermark, watermarkSize: watermarkSize, finish: { [weak self] tmpUrl in
                 DispatchQueue.main.async {
                     if let finishUrl = tmpUrl {
+                        self?.stopWaterMarkDisplay()
                         self?.saveFinalVideoAssetToAlbum(withOutputPath: finishUrl.path)
                     } else {
                         showHud(withOnlyText: "保存失败,稍后重试")
                     }
                 }
-
             })
+            setupWaterMarkDisplay()
         } else {
             saveFinalVideoAssetToAlbum(withOutputPath: outputPath)
         }
@@ -572,7 +611,8 @@ class WSSMultiVideoEditViewController: MTViewController {
             WSSLog("###: 写入相册结果 \(isSuccess) ++ \(String(describing: videoID)) ++ \(String(describing: err)) ++ \(covertPath)")
 
             DispatchQueue.main.async {
-                WSSHub.dismissHub()
+//                WSSHub.dismissHub()
+                hideHud()
                 if isSuccess {
                     self?.toNextPage(videoPath: outputPath, videoID: videoID)
                 }

+ 57 - 9
SuperShow/UI/Template/WSSTemplateSpotViewController.swift

@@ -84,6 +84,9 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
 
     fileprivate var exportTimer: Timer?
 
+    /// transitionExport 在主动取消后,还是会调用 updateProgress所以用这个属性处理一下
+    fileprivate var isUserCancel = false
+
     fileprivate var isSubPlaying: Bool = false
 
     fileprivate var imageVideoDecoder: OJAImageVideoDecoder?
@@ -115,6 +118,28 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kEditVideoFinish).take(duringLifetimeOf: self).observeValues { [weak self] _ in
             self?.releaseObjs()
         }
+
+        // --
+        NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+
+            self?.isUserCancel = true
+
+            if let transitionExport = self?.transitionExport {
+                transitionExport.endProcessing()
+                self?.transitionExport = nil
+            }
+
+            if let mixAudioExport = self?.exportSession {
+                mixAudioExport.cancelExport()
+                self?.exportSession = nil
+            }
+
+            if let waterMartExport = self?.waterMarkSession {
+                waterMartExport.cancelExport()
+                self?.waterMarkSession = nil
+            }
+            hideHud()
+        }
     }
 
     override func viewDidDisappear(_ animated: Bool) {
@@ -145,6 +170,8 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
                 }
             }
         }
+        
+        hideExportProgress()
 
         if playerContainView.superview == nil {
             return
@@ -295,7 +322,8 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
             WSSLog("###: 写入相册结果 \(isSuccess) ++ \(String(describing: videoID)) ++ \(String(describing: err)) ++ \(covertPath)")
 
             DispatchQueue.main.async {
-                WSSHub.dismissHub()
+//                WSSHub.dismissHub()
+                hideHud()
                 if isSuccess {
                     if let templateID = self?.templateModel?.templateId {
                         /// 卡点制作完成统计,因为卡点完成不走最后编辑页,故在此处理
@@ -916,7 +944,9 @@ extension WSSTemplateSpotViewController {
     }
 
     private func showExportProgress() {
-        hideExportProgress()
+        exportTimer?.invalidate()
+        exportTimer = nil
+        exportSession = nil
 
         if exportSession != nil {
             exportTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(refreshExportProgress), userInfo: nil, repeats: true)
@@ -926,12 +956,15 @@ extension WSSTemplateSpotViewController {
 
     @objc private func refreshExportProgress() {
         if let session = self.exportSession {
-            showProgressHud(progress: session.progress, status: "正在合成...")
-
+            var progress = session.progress
+            if WSSProfileItemModel.isWatermarkOn() {
+                /// 如果要合成水印,把剩余的50 分为两份 2:3
+                progress = simd_clamp(0.5 + 0.2 * progress, 0.5, 0.7)
+            }
+            showProgressCancelHub(progress: progress, status: "正在合成...")
             if session.progress > 0.99 {
                 hideExportProgress()
             }
-
             WSSLog("🌼: \(session.progress)")
         }
     }
@@ -951,12 +984,23 @@ extension WSSTemplateSpotViewController {
             let path = videoUrl.absoluteString
             /// PS: 这里之前异步线程,会导致isVideoNeedCombine不准,所以改为mainTheard
             DispatchQueue.main.async { [weak self] in
+                self?.waterMarkExportProgressUpdateCallBack = { progress in
+                    showProgressCancelHub(progress: progress, status: "正在合成...")
+                }
                 self?.combineVideoSuccess(path)
             }
 
             return
         }
 
+        isUserCancel = false
+
+        waterMarkExportProgressUpdateCallBack = { progress in
+            /// 水印(3), 混音(2)
+            let realProgress = simd_clamp(0.7 + (0.3 * progress), 0.7, 1.0)
+            showProgressCancelHub(progress: realProgress, status: "正在合成...")
+        }
+
         let outputPath = kOJSUserDocumentDirectory + "/spot_tmp_export_video_2.mp4"
         let outputUrl = URL(fileURLWithPath: outputPath)
 
@@ -981,7 +1025,6 @@ extension WSSTemplateSpotViewController {
         transitionExport!.didEndProcessingCallback = { [weak self] isFinish in
             if isFinish {
                 self?.movieWriter?.finishRecording(completionHandler: { [weak self] in
-                    hideHud()
                     WSSLog("$视频合成输出$: \(outputUrl)")
                     self?.combineSpotMusic(withVideoUrl: outputUrl)
                 })
@@ -990,8 +1033,12 @@ extension WSSTemplateSpotViewController {
             }
         }
 
-        transitionExport?.exportProgressCallback = { pg in
-            showProgressHud(progress: pg, status: "正在合成...")
+        transitionExport?.exportProgressCallback = { [weak self] pg in
+            guard let userCancel = self?.isUserCancel,
+                !userCancel
+            else { return }
+            let progress = simd_clamp(0.5 * pg, 0.0, 0.5)
+            showProgressCancelHub(progress: progress, status: "正在合成...")
         }
 
         transitionExport!.outputFilter.addTarget(movieWriter!)
@@ -1029,7 +1076,8 @@ extension WSSTemplateSpotViewController {
             showExportProgress()
 
         } catch {
-            WSSHub.dismissHub()
+//            WSSHub.dismissHub()
+            hideHud()
             showHud(withOnlyText: "合成视频失败,稍后重试")
             WSSLog("导出失败: \(error)")
         }