فهرست منبع

Merge branch 'develop' of http://svn.ouj.com:3000/duowan_iOS/SuperShow into develop

zhongbaojian 5 سال پیش
والد
کامیت
e733ab45ae

+ 36 - 6
SuperShow/OJAGPUImageDecoder/Input/OJAAlphaMovieFrameInput.swift

@@ -21,19 +21,41 @@ class OJAAlphaMovieFrameInput: OJAFrameInput {
 
     fileprivate var blendFilter = GPUImageMaskFilter()
 
-    init(backgroundItem: OJATemplateItemModel, maskItem: OJATemplateItemModel) {
+    /// 创意模板新添加 bottomInput 实现新的效果
+    fileprivate var bottomInput: OJAMovieFrameInput?
+    fileprivate lazy var bottomBlendFilter = GPUImageAddBlendFilter()
+
+    init(backgroundItem: OJATemplateItemModel, maskItem: OJATemplateItemModel, bottomItem: OJATemplateItemModel?) {
         coverInput = OJAMovieFrameInput(maskTemplateItem: backgroundItem)
         maskInput = OJAMovieFrameInput(maskTemplateItem: maskItem)
-        coverInput.addTarget(blendFilter)
-        maskInput.addTarget(blendFilter)
+        if let bottomItemData = bottomItem {
+            bottomInput = OJAMovieFrameInput(maskTemplateItem: bottomItemData)
+        }
         super.init()
+
+        if let bottomInput = self.bottomInput {
+            coverInput.addTarget(blendFilter)
+            maskInput.addTarget(blendFilter)
+
+            bottomInput.addTarget(bottomBlendFilter)
+            blendFilter.addTarget(bottomBlendFilter)
+            zIndex = bottomInput.zIndex
+        } else {
+            coverInput.addTarget(blendFilter)
+            maskInput.addTarget(blendFilter)
+            zIndex = coverInput.zIndex
+        }
+
         startFrame = 0
         endFrame = Int.max
-        zIndex = coverInput.zIndex
     }
 
     override func output() -> GPUImageOutput? {
-        return blendFilter
+        if bottomInput != nil {
+            return bottomBlendFilter
+        } else {
+            return blendFilter
+        }
     }
 
     func readCoverFrameBuffer() -> CMSampleBuffer? {
@@ -44,13 +66,21 @@ class OJAAlphaMovieFrameInput: OJAFrameInput {
         return maskInput.readVideoSampleBuffer()
     }
 
-    func processFrame(atSampleTime sampleTime: CMTime, coverBuffer: CMSampleBuffer, maskBuffer: CMSampleBuffer) {
+    func readBottomFrameButter() -> CMSampleBuffer? {
+        return bottomInput?.readVideoSampleBuffer()
+    }
+
+    func processFrame(atSampleTime sampleTime: CMTime, coverBuffer: CMSampleBuffer, maskBuffer: CMSampleBuffer, bottomBuffer: CMSampleBuffer? = nil) {
         coverInput.processFrame(atSampleTime: sampleTime, frameBuffer: coverBuffer)
         maskInput.processFrame(atSampleTime: sampleTime, frameBuffer: maskBuffer)
+        if let bottonBuffer = bottomBuffer {
+            bottomInput?.processFrame(atSampleTime: sampleTime, frameBuffer: bottonBuffer)
+        }
     }
 
     func reset() {
         coverInput.reset()
         maskInput.reset()
+        bottomInput?.reset()
     }
 }

+ 1 - 0
SuperShow/OJAGPUImageDecoder/Model/OJATemplateDataModel.h

@@ -59,6 +59,7 @@ typedef NS_ENUM(NSInteger, OJATemplateItemFilterType) {
 @property (nonatomic, strong) NSArray<OJATemplateItemModel *> *items;
 @property (nonatomic, strong) OJATemplateItemModel *bg;
 @property (nonatomic, strong) OJATemplateItemModel *mask;
+@property (nonatomic, strong) OJATemplateItemModel *bottom;
 @property (nonatomic, assign) NSInteger layer; //总共的层级数
 
 

+ 1 - 0
SuperShow/OJAGPUImageDecoder/OJACompositionInput.swift

@@ -44,6 +44,7 @@ class OJACompositionInput: NSObject {
         
         self.templateModel.bg.destinationPathString = destinationPathString
         self.templateModel.mask.destinationPathString = destinationPathString
+        self.templateModel.bottom.destinationPathString = destinationPathString
         
         templateModel.items.forEach { [unowned self] (itemModel) in
             self.setupTemplateItem(itemModel: itemModel)

+ 6 - 3
SuperShow/OJAGPUImageDecoder/OJADecoder.swift

@@ -57,7 +57,9 @@ class OJADecoder: NSObject {
     /// 给创意模板用的初始化方法
     init(compositionInput: OJACompositionInput) {
         self.templateDataModel = compositionInput.templateModel
-        self.timeMovieInput = OJAAlphaMovieFrameInput(backgroundItem: compositionInput.templateModel.bg, maskItem: compositionInput.templateModel.mask)
+        self.timeMovieInput = OJAAlphaMovieFrameInput(backgroundItem: compositionInput.templateModel.bg,
+                                                      maskItem: compositionInput.templateModel.mask,
+                                                      bottomItem: compositionInput.templateModel.bottom)
         self.allInputArray = compositionInput.inputArray
         self.backgroundColor = OJSColor(hexRGBValue: compositionInput.templateModel.bgColor)
         super.init()
@@ -81,7 +83,7 @@ class OJADecoder: NSObject {
     
     /// 测试用
     init(backgroundItem: OJATemplateItemModel, maskItem: OJATemplateItemModel, inputArray: [OJAFrameInput]) {
-        self.timeMovieInput = OJAAlphaMovieFrameInput(backgroundItem: backgroundItem, maskItem: maskItem)
+        self.timeMovieInput = OJAAlphaMovieFrameInput(backgroundItem: backgroundItem, maskItem: maskItem, bottomItem: nil)
         self.allInputArray = inputArray
         super.init()
         
@@ -201,6 +203,7 @@ extension OJADecoder {
                     if let alphaMovieInput = self.timeMovieInput as? OJAAlphaMovieFrameInput {
                         let coverBufferTemp = alphaMovieInput.readCoverFrameBuffer()
                         let maskBufferTemp = alphaMovieInput.readMaskFrameBuffer()
+                        let bottomBufferTemp = alphaMovieInput.readBottomFrameButter()
                         guard let coverBuffer = coverBufferTemp, let maskBuffer = maskBufferTemp else {
                             self.processing = false
                             return
@@ -230,7 +233,7 @@ extension OJADecoder {
                             input.processFrame(atSampleTime: sampleBufferTime)
                         }
                         
-                        alphaMovieInput.processFrame(atSampleTime: sampleBufferTime, coverBuffer: coverBuffer, maskBuffer: maskBuffer)
+                        alphaMovieInput.processFrame(atSampleTime: sampleBufferTime, coverBuffer: coverBuffer, maskBuffer: maskBuffer, bottomBuffer: bottomBufferTemp)
                         print(self.currentFrame)
                         self.currentFrame += 1
                     } else if let timeMovieInput = self.timeMovieInput as? OJAMovieFrameInput {

+ 10 - 1
SuperShow/UI/CoolVoice/WSSCoolVoiceCropMediaViewController.swift

@@ -66,6 +66,7 @@ class WSSCoolVoiceCropMediaViewController: MTViewController {
     fileprivate var mainMovieAssetInfo: (AVAsset, AVVideoComposition)?
     fileprivate var combineSession: AVAssetExportSession?
     fileprivate var combineProgressDisplayLink: CADisplayLink?
+    fileprivate var isUserCancel = false
 
     fileprivate var isMainPlayerShowing: Bool {
         guard let mainPlayView = self.mainVideoPlayer else { return false }
@@ -121,6 +122,7 @@ class WSSCoolVoiceCropMediaViewController: MTViewController {
 
         // 监听取消事件
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kAIHubForceCancel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
             if let combineSession = self?.combineSession {
                 if combineSession.status == .exporting {
                     combineSession.cancelExport()
@@ -131,6 +133,7 @@ class WSSCoolVoiceCropMediaViewController: MTViewController {
 
         // 视频导出取消
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
             if let combineSession = self?.combineSession {
                 if combineSession.status == .exporting {
                     combineSession.cancelExport()
@@ -496,6 +499,8 @@ private extension WSSCoolVoiceCropMediaViewController {
             exportAsset = result.0
             exportVideoComposition = result.1
 
+            isUserCancel = false
+
             let exportUrl = kOJSUserDocumentDirectory + "/" + "tmp_edit_video_cuted.mp4"
             if let session = self.combineSession, session.status == .exporting || session.status == .waiting {
                 session.cancelExport()
@@ -530,6 +535,10 @@ private extension WSSCoolVoiceCropMediaViewController {
                                 cancel = combineSession.status == .cancelled
                             }
                             if !cancel {
+                                guard let userCancel = self?.isUserCancel, !userCancel else {
+                                    showHud(withOnlyText: "取消合成")
+                                    return
+                                }
                                 showHud(withOnlyText: "截取视频失败")
                             }
                         }
@@ -603,7 +612,7 @@ private extension WSSCoolVoiceCropMediaViewController {
 
                         } else if state == .stop {
                             /// 手动停止
-                            showHud(withOnlyText: "图片资源加载失败")
+                            showHud(withOnlyText: "取消合成")
                         }
                     }
                 }

+ 9 - 1
SuperShow/UI/CoolVoice/WSSCoolVoiceCropSingleMediaViewController.swift

@@ -40,6 +40,7 @@ class WSSCoolVoiceCropSingleMediaViewController: MTViewController {
 
     fileprivate var combineSession: AVAssetExportSession?
     fileprivate var combineProgressDisplayLink: CADisplayLink?
+    fileprivate var isUserCancel = false
 
     /// 图片生成视频
     fileprivate var imageVideoDecoder: OJAImageVideoDecoder?
@@ -74,6 +75,7 @@ class WSSCoolVoiceCropSingleMediaViewController: MTViewController {
         }
         // 监听取消事件
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kAIHubForceCancel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
             if let combineSession = self?.combineSession {
                 if combineSession.status == .exporting {
                     combineSession.cancelExport()
@@ -84,6 +86,7 @@ class WSSCoolVoiceCropSingleMediaViewController: MTViewController {
 
         // 视频导出取消
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
             if let combineSession = self?.combineSession {
                 if combineSession.status == .exporting {
                     combineSession.cancelExport()
@@ -268,6 +271,7 @@ class WSSCoolVoiceCropSingleMediaViewController: MTViewController {
 
 private extension WSSCoolVoiceCropSingleMediaViewController {
     func nextStepHandle(shouldRecoizerVoice: Bool) {
+        isUserCancel = false
         switch cropMode {
         case .singleVideo, .presetVideo:
             mainVideoPlayer?.pause()
@@ -355,6 +359,10 @@ private extension WSSCoolVoiceCropSingleMediaViewController {
                             cancel = combineSession.status == .cancelled
                         }
                         if !cancel {
+                            guard let userCancel = self?.isUserCancel, !userCancel else {
+                                showHud(withOnlyText: "取消合成")
+                                return
+                            }
                             showHud(withOnlyText: "截取视频失败")
                         }
                     }
@@ -423,7 +431,7 @@ private extension WSSCoolVoiceCropSingleMediaViewController {
 
                         } else if state == .stop {
                             /// 手动停止
-                            showHud(withOnlyText: "图片资源加载失败")
+                            showHud(withOnlyText: "取消合成")
                         }
                     }
                 }

+ 11 - 2
SuperShow/UI/CoolVoice/WSSCoolVoiceSubtitleController.swift

@@ -55,6 +55,7 @@ class WSSCoolVoiceSubtitleController: MTViewController {
 
     fileprivate var mixAudioExportSeesion: AVAssetExportSession?
     fileprivate var mixAudioExportDisplayLink: CADisplayLink?
+    fileprivate var isUserCancel = false
 
     // MARK: CoreTextView
 
@@ -255,7 +256,7 @@ class WSSCoolVoiceSubtitleController: MTViewController {
 
         // --
         NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
-
+            self?.isUserCancel = true
             if let exportSession = self?.exportSession {
                 if exportSession.status == .exporting {
                     self?.stopExportDisplayLink()
@@ -741,7 +742,7 @@ class WSSCoolVoiceSubtitleController: MTViewController {
         // 下一步
         videoPlayer?.pause()
         subtitleView.endEditing(true)
-
+        isUserCancel = false
         // 1. 先导出视频
         if let combineAudio = self.mixTool?.currentMixAudio() {
             let presetName = WSSMediaOperationTool.resolvePresentName(forVideoAsset: videoAsset)
@@ -766,6 +767,10 @@ class WSSCoolVoiceSubtitleController: MTViewController {
 
                 } else {
                     WSSLog("合成视频失败~")
+                    guard let userCancel = self?.isUserCancel, !userCancel else {
+                        showHud(withOnlyText: "取消合成")
+                        return
+                    }
                     showHud(withOnlyText: "合成视频失败,稍后重试")
                 }
             })
@@ -1072,6 +1077,10 @@ fileprivate extension WSSCoolVoiceSubtitleController {
                                                                                               OJAJumpManager.jumpToEditViewController(fileURL: URL(fileURLWithPath: mixOutputPath), mode: .coolVoice, nav: weakNav)
                                                                                           } else {
                                                                                               WSSLog("视频混音步骤失败~")
+                                                                                              guard let userCancel = self?.isUserCancel, !userCancel else {
+                                                                                                  showHud(withOnlyText: "取消合成")
+                                                                                                  return
+                                                                                              }
                                                                                               showHud(withOnlyText: "合成视频失败,稍后重试")
                                                                                           }
                                                                                       }

+ 69 - 10
SuperShow/UI/EditVideo/CutVideo/WSSEditCutView.swift

@@ -25,6 +25,11 @@ class WSSEditCutView: UIView {
     fileprivate var imageGenerator: AVAssetImageGenerator
     fileprivate var imageArray = [UIImage]()
     fileprivate var videoDuration: Float = 0
+
+    fileprivate var exportSession: AVAssetExportSession?
+    fileprivate var exportSessionDisplayLink: CADisplayLink?
+    fileprivate var isUserCancel = false
+
     weak var playerView: LXMAVPlayerView? {
         didSet {
             playerView?.playerTimeDidChangeBlock = { [weak self] current, total in
@@ -95,6 +100,17 @@ class WSSEditCutView: UIView {
             }
 
         })
+
+        // --
+        NotificationCenter.default.reactive.notifications(forName: Notification.Name.kSVProgressHUDCacnel).take(duringLifetimeOf: self).observeValues { [weak self] _ in
+            self?.isUserCancel = true
+
+            if let exportSession = self?.exportSession {
+                exportSession.cancelExport()
+                self?.exportSession = nil
+                hideHud()
+            }
+        }
     }
 
     convenience init(movieURL: URL) {
@@ -105,6 +121,10 @@ class WSSEditCutView: UIView {
     required init?(coder aDecoder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
+    
+    func releaseObjc() {
+        stopExportSessionDisplay()
+    }
 
     func cutedStartSeconds() -> TimeInterval {
         let startTime = TimeInterval(bottomView.sliderView.selectedStart()) * TimeInterval(videoDuration)
@@ -162,21 +182,28 @@ private extension WSSEditCutView {
             let timeRange = CMTimeRange(start: startTime, end: endTime)
 
             do {
-                showHud()
+                self.isUserCancel = false
                 let cutedVideo = try WSSMediaOperationTool.cutVideo(withVideoAsset: self.videoAsset, timeRange: timeRange)
                 let exportUrl = kOJSUserDocumentDirectory + "/" + "tmp_edit_video_cuted.mp4"
-                _ = WSSMediaOperationTool.exportVideo(withVideoAsset: cutedVideo, exportPath: URL(fileURLWithPath: exportUrl), finish: { [weak self] isSucess in
-                    hideHud()
-
-                    if isSucess {
-                        DispatchQueue.main.async(execute: {
+                let exportSession = WSSMediaOperationTool.exportVideo(withVideoAsset: cutedVideo, exportPath: URL(fileURLWithPath: exportUrl), finish: { [weak self] isSucess in
+                    DispatchQueue.main.async(execute: {
+                        self?.stopExportSessionDisplay()
+                        if isSucess {
+                            hideHud()
                             self?.saveActionHandle?(exportUrl, startTime.seconds, timeRange.duration.seconds)
-                        })
-                    } else {
-                        showHud(withOnlyText: "截取视频失败")
-                    }
+                        } else {
+                            guard let userCancel = self?.isUserCancel, !userCancel else {
+                                showHud(withOnlyText: "取消裁剪")
+                                return
+                            }
+                            showHud(withOnlyText: "截取视频失败")
+                        }
+                    })
                 })
 
+                self.exportSession = exportSession
+                self.setupExportSessionDisplayLink()
+
             } catch {
                 showHud(withOnlyText: "截取视频失败")
             }
@@ -218,6 +245,38 @@ extension WSSEditCutView: WSSEditCutSliderViewDelegate {
         let endTime = TimeInterval(bottomView.sliderView.selectedEnd()) * TimeInterval(videoDuration)
         cutTimeRangeChangeHandle?(startTime, endTime - startTime)
     }
+
+    // MARK: exportSession
+
+    func setupExportSessionDisplayLink() {
+        if exportSessionDisplayLink != nil {
+            exportSessionDisplayLink?.isPaused = true
+            exportSessionDisplayLink?.invalidate()
+            exportSessionDisplayLink = nil
+        }
+        let displayLink = CADisplayLink(target: self, selector: #selector(updateExportSessionProgress))
+        DispatchQueue.main.async {
+            displayLink.add(to: .current, forMode: .common)
+            self.exportSessionDisplayLink = displayLink
+        }
+    }
+
+    func stopExportSessionDisplay() {
+        if let displayLink = exportSessionDisplayLink {
+            displayLink.isPaused = true
+            displayLink.invalidate()
+            exportSessionDisplayLink = nil
+        }
+    }
+
+    @objc func updateExportSessionProgress() {
+        if let exportSession = exportSession {
+            let progress = exportSession.progress
+            showProgressCancelHub(progress: progress, status: "正在裁剪...")
+        } else {
+            stopExportSessionDisplay()
+        }
+    }
 }
 
 // MARK: - PublicMethod

+ 3 - 0
SuperShow/UI/EditVideo/WSSEditVideoViewController.swift

@@ -171,6 +171,9 @@ class WSSEditVideoViewController: MTViewController {
         pasterOverlayView.removeAllSubViews()
         pasterOverlayView.removeFromSuperview()
 
+        cleanDisplayLink()
+        cutView.releaseObjc() /// cutView中含有DisplayLink
+
         displayView.endProcessing()
         displayView.removeFromSuperview()
         decoder?.outputFilter.removeAllTargets()

+ 1 - 1
SuperShow/UI/ResourcePicker/WSSResourcePickerController.swift

@@ -398,7 +398,7 @@ class WSSResourcePickerController: MTViewController {
 
             } else if state == .stop {
                 /// 手动停止
-                showHud(withOnlyText: "图片资源加载失败")
+                showHud(withOnlyText: "取消合成")
             }
         }
     }

+ 6 - 2
SuperShow/UI/Template/WSSCreativeTemplateEditViewController.swift

@@ -126,7 +126,7 @@ class WSSCreativeTemplateEditViewController: WSSMultiVideoEditViewController {
         videoPlayer = nil
 
         stopMixAudioExportSeesionDisplay()
-        
+
         templateDecoder?.endProcessing()
         templateDecoder = nil
     }
@@ -337,7 +337,11 @@ fileprivate extension WSSCreativeTemplateEditViewController {
                                               model.mediaModel?.sourceType = .video
 
                                           } else {
-                                              showHud(withOnlyText: "截取视频失败")
+                                              if let userCancel = self?.isUserCancel, userCancel {
+                                                  showHud(withOnlyText: "取消合成")
+                                              } else {
+                                                  showHud(withOnlyText: "截取视频失败")
+                                              }
                                               self?.isCropVideoFail = true
                                           }
                                           self?.cropVideoSuccessCount += 1

+ 2 - 2
SuperShow/UI/Template/WSSMultiVideoEditViewController.swift

@@ -485,7 +485,7 @@ class WSSMultiVideoEditViewController: MTViewController {
                 }
             } else if state == .stop {
                 /// 手动停止
-                showHud(withOnlyText: "图片资源加载失败")
+                showHud(withOnlyText: "取消合成")
             }
         }
     }
@@ -585,7 +585,7 @@ class WSSMultiVideoEditViewController: MTViewController {
                         self?.stopWaterMarkDisplay()
                         self?.saveFinalVideoAssetToAlbum(withOutputPath: finishUrl.path)
                     } else {
-                        showHud(withOnlyText: "保存失败,稍后重试")
+                        showHud(withOnlyText: "取消合成")
                     }
                 }
             })

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

@@ -138,6 +138,7 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
                 waterMartExport.cancelExport()
                 self?.waterMarkSession = nil
             }
+
             hideHud()
         }
     }
@@ -170,7 +171,7 @@ class WSSTemplateSpotViewController: WSSMultiVideoEditViewController {
                 }
             }
         }
-        
+
         hideExportProgress()
 
         if playerContainView.superview == nil {
@@ -1062,15 +1063,20 @@ extension WSSTemplateSpotViewController {
             let outputPath = kOJSUserDocumentDirectory + "/" + "result_combine_spot_music.mp4"
             exportSession = try WSSMediaOperationTool.mixAudio(withAudioAsset: spotMusicAsset, forVideo: videoAsset, originalAudioAsset: nil, audioVolume: 1.0, videoVolume: 1.0, presetName: presentName, exportPath: outputPath, finish: { [weak self] resultAsset in
 
-                self?.hideExportProgress()
-
-                if let asset = resultAsset as? AVURLAsset {
-                    let path = asset.url.absoluteString
-                    self?.combineVideoSuccess(path)
-
-                } else {
-                    showHud(withOnlyText: "合成视频失败,稍后重试")
+                DispatchQueue.main.async {
+                    self?.hideExportProgress()
+                    if let asset = resultAsset as? AVURLAsset {
+                        let path = asset.url.absoluteString
+                        self?.combineVideoSuccess(path)
+                    } else {
+                        guard let userCancel = self?.isUserCancel, !userCancel else {
+                            showHud(withOnlyText: "取消合成")
+                            return
+                        }
+                        showHud(withOnlyText: "合成视频失败,稍后重试")
+                    }
                 }
+
             })
 
             showExportProgress()