// // PQStuckPointEditerController.swift // PQSpeed // // Created by ak on 2021/4/26. // Copyright © 2021 BytesFlow. All rights reserved. // 功能:卡点音乐编辑界面 import Foundation import ObjectMapper import RealmSwift import UIKit class PQStuckPointEditerController: PQBaseViewController { // 是否导出视频成功 var isExportVideosSuccess: Bool = false // 是否请求卡点数据成功 var isStuckPointDataSuccess: Bool = false // 是否同步音乐成功 var isSynchroMusicInfoSuccess: Bool = false /// 当前所有的filter var filters: Array = Array.init() // 选中的总时长 var selectedTotalDuration: Float64 = 0 // 选择的总数 var selectedDataCount: Int = 0 // 选择的图片总数 var selectedImageDataCount: Int = 0 // 选中的素材数据 var selectedPhotoData: [PQEditVisionTrackMaterialsModel]? // 选中的音乐数据 var stuckPointMusicData: PQVoiceModel? // 保存所有段的所有贴纸,音频信息,用于播放器的渲染使用 var projectModel: PQEditProjectModel = PQEditProjectModel() // 从草稿箱进入的项目数据 var draftProjectModel: PQEditProjectModel? var mStickers: [PQEditVisionTrackMaterialsModel]? // 播放器的开始和结束时间,1,刚进界面使用推荐的开始结束时间,2,用户修改起结点时修改 var playeTimeRange: CMTimeRange = CMTimeRange() // 下一步 lazy var nextBtn: UIButton = { let nextBtn = UIButton(type: .custom) nextBtn.frame = CGRect(x: cScreenWidth - 16 - cDefaultMargin * 6, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: cDefaultMargin * 6, height: cDefaultMargin * 3) nextBtn.setTitle("去合成", for: .normal) nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13) nextBtn.addTarget(self, action: #selector(nextBtnClick(sender:)), for: .touchUpInside) nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue) nextBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#FFFFFF"), for: .normal) nextBtn.uxy_acceptEventInterval = 0.5 nextBtn.addCorner(corner: 3) return nextBtn }() // 播放器显示 view lazy var playerView: PQGPUImagePlayerView = { let playerHeight = cScreenWidth let playerView = PQGPUImagePlayerView(frame: CGRect(x: 0, y: navHeadImageView?.frame.maxY ?? 0, width: playerHeight, height: playerHeight)) playerView.backgroundColor = PQBFConfig.shared.styleBackGroundColor playerView.isShowLine = false return playerView }() /// 节奏选择视图 lazy var sustomSwitchView: PQCustomSwitchView = { let sustomSwitchView = PQCustomSwitchView(frame: CGRect(x: (view.frame.width - cDefaultMargin * 28) / 2, y: view.frame.height - cSafeAreaHeight - cDefaultMargin * 3 - cDefaultMargin * 3, width: cDefaultMargin * 28, height: 35), titles: ["快节奏", "适中", "慢节奏"], defaultIndex: stuckPointMusicData?.speed ?? 2) sustomSwitchView.switchChangeHandle = { [weak self] sender in // 改变速率 self?.stuckPointMusicData?.speed = sender.tag self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.bgmInfo?.rhythmMusicSpeed = sender.tag // 播放前先暂停 self?.playerView.stop() // 开始播放 self?.settingPlayerView() // 点击上报:选择节奏 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_selectRhythm, pageSource: .sp_stuck_previewSyncedUp, extParams: ["rhythmNumber": sender.tag], remindmsg: "点击上报:选择节奏") } return sustomSwitchView }() /// 裁剪视图 lazy var stuckPointCuttingView: PQStuckPointCuttingView = { let stuckPointCuttingView = PQStuckPointCuttingView(frame: CGRect(x: 0, y: sustomSwitchView.frame.minY - cDefaultMargin * 14 - cDefaultMargin * 2, width: view.frame.width, height: cDefaultMargin * 14), duration: CGFloat(Float(stuckPointMusicData?.duration ?? "0") ?? 0), startTime: CGFloat(stuckPointMusicData?.startTime ?? 0), endTime: CGFloat(stuckPointMusicData?.endTime ?? 0)) /// 裁剪进度回调 stuckPointCuttingView.videoRangeDidChanged = { [weak self] startTime, endTime in BFLog(message: "裁剪返回--startTime = \(startTime),endTime = \(endTime)") } /// 播放进度回调 stuckPointCuttingView.videoProgressDidChanged = { [weak self] progress in BFLog(message: "进度更新返回--progress = \(progress) \(String(describing: self?.playerView.mPlayeTimeRange))") } /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束 stuckPointCuttingView.videoDidEndDragging = { [weak self] type, startTime, endTime, progress in BFLog(message: "拖拽结束返回--type = \(type),startTime = \(startTime),endTime = \(endTime),progress = \(progress)") self?.playerView.pause() // 修改最新值 self?.stuckPointMusicData?.startTime = Float64(startTime) self?.stuckPointMusicData?.endTime = Float64(endTime) // 红的指针完成 if type == 3 { if CMTimeGetSeconds(self?.playerView.mPlayeTimeRange?.end ?? .zero) == 0 { BFLog(message: "mPlayeTimeRange is error") return } let newBeginSconds = (Double(startTime) + (Double(endTime) - Double(startTime)) * Double(progress)) * 600 BFLog(message: " newBeginSconds is \(newBeginSconds)") let seekTimeRange: CMTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int64(newBeginSconds)), timescale: 600), end: CMTime(value: CMTimeValue(Int64(endTime * 600)), timescale: 600)) BFLog(message: "修改的开始 \(CMTimeGetSeconds(seekTimeRange.start)) 结束 \(CMTimeGetSeconds(seekTimeRange.end))") self?.playerView.play(pauseFirstFrame: false, playeTimeRange: seekTimeRange) } else { // 更改素材开始时间及结束时间 self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.out = Float64(endTime) self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.model_in = Float64(startTime) self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineIn = Float64(startTime) self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineOut = Float64(endTime) // 初始化音频的开始和结束时间 self?.playeTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int64(startTime * 600)), timescale: 600), end: CMTime(value: CMTimeValue(Int64(endTime * 600)), timescale: 600)) DispatchQueue.global().async { // 并行、异步 self?.mStickers = self?.createStickers(sections: self?.projectModel.sData?.sections ?? List(), inputSize: CGSize(width: CGFloat(self?.projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(self?.projectModel.sData?.videoMetaData?.videoHeight ?? 0))) self?.playerView.mStickers = self?.mStickers DispatchQueue.main.async { // 串行、异步 self?.playerView.play(pauseFirstFrame: false, playeTimeRange: self!.playeTimeRange) } } } // 埋点上报 if type == 1 || type == 2 { // 点击上报:拖动拖拽条(左/右部分) PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: type == 1 ? .ot_click_dragFront : .ot_click_dragBehind, pageSource: .sp_stuck_previewSyncedUp, extParams: ["targetTime": type == 1 ? startTime * 1000 : endTime * 1000], remindmsg: "点击上报:拖动拖拽条(左/右部分)") } } return stuckPointCuttingView }() /// 卡点时长显示视图 lazy var timeRemindLab: UILabel = { let timeRemindLab = UILabel(frame: CGRect(x: 0, y: stuckPointCuttingView.frame.minY - cDefaultMargin * 4 - cDefaultMargin * 3, width: view.frame.width, height: cDefaultMargin * 4)) timeRemindLab.backgroundColor = UIColor.hexColor(hexadecimal: "#262626") timeRemindLab.textAlignment = .center timeRemindLab.font = UIFont.systemFont(ofSize: 12) timeRemindLab.textColor = UIColor.hexColor(hexadecimal: "#999999") let total: Float64 = ((stuckPointMusicData?.endTime ?? Float64(stuckPointMusicData?.duration ?? "0") ?? 0) - (stuckPointMusicData?.startTime ?? 0)) timeRemindLab.text = "现卡点时长\(total.formatDurationToHMS()) / 原视频总时长\(selectedTotalDuration.formatDurationToHMS())" return timeRemindLab }() /// 音乐标题 lazy var musicNameView: UIView = { let musicNameView = UIView() musicNameView.addSubview(musicNameLab) let nameWidth: CGFloat = musicNameLab.frame.width + (25 + cDefaultMargin * 3) musicNameView.frame = CGRect(x: (view.frame.width - nameWidth) / 2, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: nameWidth, height: cDefaultMargin * 3) // musicNameView.backgroundColor = UIColor.hexColor(hexadecimal: "#333333") musicNameView.addCorner(corner: musicNameView.frame.height / 2) let musicImageView = UIImageView() musicImageView.tintColor = PQBFConfig.shared.styleTitleColor musicImageView.image = UIImage().BF_Image(named: "stuckPoint_reCreate_music").withRenderingMode(.alwaysTemplate) musicImageView.frame = CGRect(x: musicNameView.frame.height / 2 - 5, y: (musicNameView.frame.height - 22) / 2, width: 22, height: 22) musicNameView.addSubview(musicImageView) musicNameLab.frame.origin.x = musicImageView.frame.maxX + 5 let ges = UITapGestureRecognizer(target: self, action: #selector(musicNameClick)) musicNameView.addGestureRecognizer(ges) return musicNameView }() /// 音乐歌曲名称 lazy var musicNameLab: LMJHorizontalScrollText = { let nameWidth: CGFloat = sizeWithText(text: "\(stuckPointMusicData?.musicName ?? "")", font: UIFont.systemFont(ofSize: 13), size: CGSize(width: view.frame.width - ((cDefaultMargin * 6 + 16 * 2) * 2) - (25 + cDefaultMargin * 3), height: cDefaultMargin * 3)).width let musicNameLab = LMJHorizontalScrollText(frame: CGRect(x: 0, y: 0, width: nameWidth < cDefaultMargin * 4 ? cDefaultMargin * 4 : nameWidth, height: cDefaultMargin * 3)) musicNameLab.textColor = PQBFConfig.shared.styleTitleColor musicNameLab.textFont = UIFont.systemFont(ofSize: 13) musicNameLab.speed = 0.03 musicNameLab.moveDirection = LMJTextScrollMoveLeft musicNameLab.moveMode = LMJTextScrollContinuous if nameWidth < cDefaultMargin * 4 { musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") " } else { musicNameLab.text = "\(stuckPointMusicData?.musicName ?? "")" } return musicNameLab }() /// 同步进度显示 lazy var synchroMarskView: PQStuckPointLoadingView = { var synchroMarskView = PQStuckPointLoadingView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth)) synchroMarskView.cancelHandle = { [weak self] _ in self?.navigationController?.popViewController(animated: true) } return synchroMarskView }() override func viewWillAppear(_ animated: Bool) { super.viewDidAppear(animated) lineView?.isHidden = true UIApplication.shared.isIdleTimerDisabled = true musicNameLab.move() PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) } @objc func enterBackground() { BFLog(message: "进入到后台") // 取消导出 playerView.pause() } override func backBtnClick() { super.backBtnClick() playerView.pause() // 点击上报:返回按钮 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_previewSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)") } @objc func musicNameClick() { // let musicVc = navigationController?.viewControllers.first(where: { vc in // vc is PQStuckPointMusicController // }) // if musicVc != nil { // navigationController?.popToViewController(musicVc!, animated: true) // } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) UIApplication.shared.isIdleTimerDisabled = false musicNameLab.stop() playerView.pause() } override func viewDidLoad() { super.viewDidLoad() leftButton(image: "icon_detail_back", tintColor: PQBFConfig.shared.styleTitleColor) navHeadImageView?.addSubview(nextBtn) navHeadImageView?.addSubview(musicNameView) // 添加子视图 addSubViews() // 导出相册视频 exportPhotoData() // 同步音乐数据 synchroMusicInfoData() // 曝光上报:预览页面曝光上报 PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_previewSyncedUp, pageSource: .sp_stuck_previewSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:预览页面曝光上报)") } /// 添加子视图 /// - Returns: <#description#> func addSubViews() { if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 { return } view.addSubview(playerView) view.addSubview(sustomSwitchView) view.addSubview(stuckPointCuttingView) // view.addSubview(timeRemindLab) // 添加一个背景区分不同色值 let backView: UIView = UIView() backView.backgroundColor = UIColor.hexColor(hexadecimal: "#262626") view.insertSubview(backView, aboveSubview: navHeadImageView!) backView.frame = CGRect(x: 0, y: navHeadImageView?.frame.height ?? 0, width: cScreenWidth, height: (stuckPointCuttingView.frame.minY - cDefaultMargin * 3) - (navHeadImageView?.frame.height ?? 0)) } @objc func nextBtnClick(sender _: UIButton) { BFLog(message: "去发布") playerView.pause() let videoExporter = PQStuckPointPublicController() videoExporter.selectedTotalDuration = selectedTotalDuration videoExporter.selectedDataCount = selectedDataCount videoExporter.selectedImageDataCount = selectedImageDataCount // 使用深 copy let json = projectModel.toJSONString(prettyPrint: false) if json == nil { BFLog(message: "数据转换有问题 跳转") return } let tempModel: PQEditProjectModel? = Mapper().map(JSONString: json!) videoExporter.mStickers = mStickers videoExporter.audioMixModel = stuckPointMusicData videoExporter.editProjectModel = tempModel navigationController?.pushViewController(videoExporter, animated: true) // 点击上报:去合成 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_commit, pageSource: .sp_stuck_previewSyncedUp, extParams: ["musicName": stuckPointMusicData?.musicName ?? "", "musicId": stuckPointMusicData?.musicId ?? "", "rhythmNumber": stuckPointMusicData?.speed ?? 2, "duration": ((stuckPointMusicData?.endTime ?? 0) - (stuckPointMusicData?.startTime ?? 0)) * 1000], remindmsg: "点击上报:去合成") } // MARK: - 播放器相关操作 /// seek 播放器 /// - Parameter playeTimeRange: 开始和结束时间 func seekPlayer(playeTimeRange: CMTimeRange) { playerView.setEnableSeek(isSeek: true) playerView.play(pauseFirstFrame: false, playeTimeRange: playeTimeRange) } /// 通过传入的 selectedPhotoData 、 stuckPointMusicData 创建 projectModel 模型 后面都使用 projectModel 参数 func createPorjectData() { // 1,添加选择的视觉素材 let section: PQEditSectionModel = PQEditSectionModel() selectedPhotoData?.forEach { model in let json = model.toJSONString(prettyPrint: false) if json == nil { BFLog(message: "数据转换有问题 跳转") return } let tempModel: PQEditVisionTrackMaterialsModel = Mapper().map(JSONString: json!)! section.sectionTimeline?.visionTrack?.visionTrackMaterials.append(tempModel) } projectModel.sData?.sections.append(section) // 2,添加背景音乐 projectModel.sData?.addBGM(audioMix: stuckPointMusicData!) } // 设置播放器 func settingPlayerView() { // 1,设置播放器的显示区域 和画布大小 // - 按第一个素材尺寸自适应 let playerShowHeight = (stuckPointCuttingView.frame.minY - cDefaultMargin * 3) - (navHeadImageView?.frame.maxY ?? 0) var showRect: CGRect = PQPlayerViewModel.getShowCanvasRect(editProjectModel: projectModel, showType: 1, playerViewHeight: playerShowHeight) if showRect.size.width == showRect.size.height { if cScreenWidth > playerShowHeight { showRect.origin.x = (cScreenWidth - playerShowHeight) / 2 showRect.size.width = playerShowHeight showRect.size.height = playerShowHeight } else { showRect.origin.x = 0 showRect.size.width = cScreenWidth showRect.size.height = cScreenWidth } } showRect.origin.y = (playerShowHeight - showRect.size.height) / 2.0 + (navHeadImageView?.frame.maxY ?? 0) if showRect.size.width != 0, showRect.size.height != 0 { playerView.resetCanvasFrame(frame: showRect) } var firstModel: PQEditVisionTrackMaterialsModel? for part in projectModel.sData!.sections { if part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0 > 0 { firstModel = part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first break } } var videoSize: CGSize = CGSize(width: Int(firstModel?.width ?? 0), height: Int(firstModel?.height ?? 0)) var minSlider = min(videoSize.width, videoSize.height) var maxSlider = max(videoSize.width, videoSize.height) let ration = 1080 / minSlider minSlider = minSlider * ration maxSlider = maxSlider * ration if videoSize.width > videoSize.height { // 宽屏 videoSize = CGSize(width: maxSlider, height: minSlider) } else { videoSize = CGSize(width: minSlider, height: maxSlider) } let maxValue = max(firstModel?.width ?? 0, firstModel?.height ?? 0) if maxValue > 1920 { let maxRation = 1920 / maxValue videoSize = CGSize(width: videoSize.width * CGFloat(maxRation), height: videoSize.height * CGFloat(maxRation)) BFLog(message: "最长边已经超过 1920 要等比缩小 缩放后\(videoSize)") } if (Int(videoSize.width) % 2) != 0 { videoSize.width = videoSize.width - 1 } if (Int(videoSize.height) % 2) != 0 { videoSize.height = videoSize.height - 1 } projectModel.sData?.videoMetaData?.videoWidth = Int(videoSize.width) projectModel.sData?.videoMetaData?.videoHeight = Int(videoSize.height) // 2,创建滤镜 mStickers = createStickers(sections: projectModel.sData?.sections ?? List(), inputSize: CGSize(width: CGFloat(projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(projectModel.sData?.videoMetaData?.videoHeight ?? 0))) playerView.mStickers = mStickers // 3,设置音频 let audioPath = stuckPointMusicData?.localPath ?? "" BFLog(message: "初始化音频播放器的音频地址为:\(audioPath)") playerView.updateAsset(URL(fileURLWithPath: documensDirectory + audioPath), videoComposition: nil, audioMixModel: nil) // 4, 设置播放器的输出画布大小 playerView.movie?.mShowVidoSize = CGSize(width: CGFloat(projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(projectModel.sData?.videoMetaData?.videoHeight ?? 0)) // 5,开始播放 playerView.isLoop = false playerView.showProgressLab = false // 初始化音频的开始和结束时间 BFLog(message: "播放的器 开始\(String(describing: CMTimeGetSeconds(playeTimeRange.start))) 结束 \(String(describing: CMTimeGetSeconds(playeTimeRange.end)))") playerView.play(pauseFirstFrame: false, playeTimeRange: CMTimeRange(start: playeTimeRange.start, end: playeTimeRange.end)) // 6,进度回调 playerView.progress = { [weak self] currentTime, tatolTime, _ in // 更新进度 let progress = (currentTime - CMTimeGetSeconds(self?.playeTimeRange.start ?? .zero )) / CMTimeGetSeconds(self?.playeTimeRange.duration ?? .zero ) BFLog(message: "\(currentTime) \(tatolTime) 显示播放器进度为: \(progress)") self?.stuckPointCuttingView.videoCropView.updateProgress(progress: CGFloat(progress)) } } deinit { BFLog(message: "卡点视频预览界面销毁") musicNameLab.stop() playerView.pause() // 取消所有的导出 PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in exportSession.cancelExport() } } } // MARK: - 视频渲染相关逻辑方法 extension PQStuckPointEditerController { /// 分割视频 这里只设置视频类型的 in 和 out 并不设置显示的开始和结束时间 mp4 ,png ,png ,mp4 /// - Parameter section: 当前段 /// - Parameter stuckPoints: 用户选择的,或推荐的卡点数 /// - Returns: 返回分割后的所有 stickers 和卡点数是一致的 func clipVideoMerage(section: PQEditSectionModel, stuckPoints: [Float]) -> [PQEditVisionTrackMaterialsModel] { var stickers: Array = Array.init() // 第二种情况:有视频要进行分割 /* 1, 确定每个视频素材需要切的段数p 2, 将所有视频时长相加,得到总视频素材时长L = l1 + l2 + ... + ln 3, 视频素材a1需要切分的个数clipNum = max (round (kongduan * a1 / L) , 1) */ // 要补的空位数 let kongduan: Int = Int(stuckPoints.count) - 1 - Int(section.sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials().count ?? 0) // 所有视频总时长 var videoTotalDuration: Float64 = 0.0 for video in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video") { let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + video.locationPath), options: avAssertOptions) videoTotalDuration = videoTotalDuration + Float64(CMTimeGetSeconds(asset.duration)) } if videoTotalDuration == 0 { BFLog(message: "视频总时长出现错误!!!!这里应该有视频素材的") return stickers } for sticker in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() { if sticker.type == StickerType.VIDEO.rawValue { let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + sticker.locationPath), options: avAssertOptions) // 要分割的段落 let clipNum = Int(max(round(Double(kongduan) * CMTimeGetSeconds(asset.duration) / videoTotalDuration), 1)) sticker.duration = CMTimeGetSeconds(asset.duration) BFLog(message: "单个视频\(sticker.locationPath)时长::\(CMTimeGetSeconds(asset.duration)) ,clipNum is:\(clipNum)") for clipindex in 0 ... clipNum { // deep copy sticker model 防止只有一个对象 let stickerjson = sticker.toJSONString(prettyPrint: false) let deepCopySticker = Mapper().map(JSONString: stickerjson!) // 设置循环模式和适配模式 deepCopySticker?.generateDefaultValues() deepCopySticker?.model_in = clipindex == 0 ? 0 : CMTimeGetSeconds(asset.duration) / Double(clipNum) * Double(clipindex) deepCopySticker?.out = (deepCopySticker?.model_in ?? 0) + CMTimeGetSeconds(asset.duration) / Double(clipNum) if (deepCopySticker?.model_in ?? 0) >= CMTimeGetSeconds(asset.duration) || (deepCopySticker?.out ?? 0) >= CMTimeGetSeconds(asset.duration) { deepCopySticker?.model_in = CMTimeGetSeconds(asset.duration) - CMTimeGetSeconds(asset.duration) / Double(clipNum) deepCopySticker?.out = CMTimeGetSeconds(asset.duration) } BFLog(message: " crilp is in \(deepCopySticker?.model_in ?? 0) out \(deepCopySticker?.out ?? 0) 总时长\(CMTimeGetSeconds(asset.duration))") if deepCopySticker != nil { stickers.append(deepCopySticker!) } } } else if sticker.type == StickerType.IMAGE.rawValue { sticker.generateDefaultValues() stickers.append(sticker) } } return stickers } /// 创建sticker /// - Parameters: /// - sections: 项目所有段落数据信息 /// - inputSize: 画布大小 /// - Returns: filters 数据 播放器可直接使用 func createStickers(sections: List, inputSize _: CGSize = .zero) -> [PQEditVisionTrackMaterialsModel] { // 保存滤镜对象数据 var stickers: Array = Array.init() for section in sections { if section.sectionType == "normal" { // 推荐卡点数 var stuckPoints: Array = Array.init() var stuckPointsTemp = Array.init() for (index, dunshu) in stuckPointMusicData!.rhythmSdata[0].pointTimes.enumerated() { BFLog(message: "所有卡点数:\(Float64(dunshu) / 1_000_000.0)") if Float64(dunshu) / 1_000_000.0 > CMTimeGetSeconds(playeTimeRange.start), Float64(dunshu) / 1_000_000.0 < CMTimeGetSeconds(playeTimeRange.end) { stuckPointsTemp.append(Float(dunshu) / 1_000_000.0) } } // 根据不同速度 取卡点 1,2,3 /* - 快节奏为选中区域的所有点位,即0,1,2,3,4…… - 适中为每两个点位取一个,即0,2,4,6…… - 慢节奏为每三个点位取一个,即0,3,6,9…… */ BFLog(message: "stuckPointMusicData?.speed is \(String(describing: stuckPointMusicData?.speed))") for (index, point) in stuckPointsTemp.enumerated() { if stuckPointMusicData?.speed == 1 { stuckPoints.append(point) } else if stuckPointMusicData?.speed == 2 { if index % 2 == 0 { stuckPoints.append(point) } } else if stuckPointMusicData?.speed == 3 { if index % 3 == 0 { stuckPoints.append(point) } } } for point in stuckPoints { BFLog(message: "有 start end 计算后的卡点数\(point)") } if stuckPoints.first != nil { stuckPoints.removeFirst() } if stuckPoints.last != nil { stuckPoints.removeLast() } stuckPoints.insert(Float(CMTimeGetSeconds(playeTimeRange.start)), at: 0) stuckPoints.insert(Float(CMTimeGetSeconds(playeTimeRange.end)), at: stuckPoints.count) BFLog(message: "stuckPoints count is \(stuckPoints.count)") // 当用户上传视觉素材个数大于等于音乐选择区域节拍分割个数时,无需进行视频分割,只显示卡点数-1 个素材 if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count >= stuckPointMusicData!.rhythmSdata[0].pointTimes.count { for (index, sticker) in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().enumerated() { if index == stuckPointMusicData!.rhythmSdata[0].pointTimes.count { BFLog(message: "到达卡点数量") break } BFLog(message: "创建 filter start :\(sticker.timelineIn) end :\(sticker.timelineOut) type is \(sticker.type) \(sticker.locationPath)") sticker.timelineIn = Float64(stuckPoints[index]) sticker.timelineOut = Float64(stuckPoints[index + 1]) BFLog(message: "卡点 间隔 \(sticker.timelineIn - sticker.timelineOut)") sticker.generateDefaultValues() stickers.append(sticker) } } else { // 卡点数 > 选择素材数 // 第一种情况:全是图片,图片回环播放 if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video").count == 0, section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "image").count > 0 { for (index, point) in stuckPoints.enumerated() { let sticker: PQEditVisionTrackMaterialsModel = section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials()[index % section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count] BFLog(message: "stickerlocationPath sticker : \(sticker.locationPath)") let stickerjson = sticker.toJSONString(prettyPrint: false) let deepCopySticker = Mapper().map(JSONString: stickerjson!) if deepCopySticker!.type == StickerType.IMAGE.rawValue { if index + 1 < stuckPoints.count { deepCopySticker!.timelineIn = Float64(stuckPoints[index]) deepCopySticker!.timelineOut = Float64(stuckPoints[index + 1]) if deepCopySticker != nil { deepCopySticker?.generateDefaultValues() stickers.append(deepCopySticker!) } } } } } else { // 第二种情况:有视频要进行分割 let clipFilters = clipVideoMerage(section: section, stuckPoints: stuckPoints) for (index, point) in stuckPoints.enumerated() { if index + 1 < stuckPoints.count, index < clipFilters.count { let sticker: PQEditVisionTrackMaterialsModel = clipFilters[index] sticker.timelineIn = Float64(stuckPoints[index]) sticker.timelineOut = Float64(stuckPoints[index + 1]) // 卡点的时间 > in out 值 let timelineInterval = sticker.timelineOut - sticker.timelineIn let inOutInterval = sticker.out - sticker.model_in if timelineInterval > inOutInterval { sticker.out = sticker.model_in + timelineInterval } // out > 素材的总时长in out 进行前移操作 let offsetAssetDuration = sticker.out - sticker.duration if offsetAssetDuration > 0 { sticker.model_in = sticker.model_in - offsetAssetDuration sticker.out = sticker.out - offsetAssetDuration } BFLog(message: "分割后 创建 filter start :\(sticker.timelineIn) end :\(sticker.timelineOut) type is \(sticker.type)") stickers.append(sticker) } } } } } } return stickers } } // MARK: - 同步/下载素材相关 /// 同步/下载素材相关 extension PQStuckPointEditerController { /// 同步音乐相关数据 /// - Returns: <#description#> func synchroMusicInfoData() { if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 { if synchroMarskView.superview == nil { UIApplication.shared.keyWindow?.addSubview(synchroMarskView) } PQStuckPointViewModel.stuckPointMusicDetailData(musicId: stuckPointMusicData?.musicId ?? "", originType: stuckPointMusicData?.originType ?? 1) { [weak self] newMusicData, _ in if newMusicData != nil, (newMusicData?.rhythmSdata.count ?? 0) > 0 { self?.isStuckPointDataSuccess = true self?.stuckPointMusicData?.rhythmSdata = newMusicData?.rhythmSdata ?? [] self?.stuckPointMusicData?.startTime = newMusicData?.startTime ?? 0 self?.stuckPointMusicData?.endTime = newMusicData?.endTime ?? 0 if newMusicData?.speed != nil { self?.stuckPointMusicData?.speed = newMusicData?.speed ?? 2 } if (self?.stuckPointMusicData?.rhythmSdata.count ?? 0) > 0 && (((self?.selectedDataCount ?? 0) - (self?.selectedImageDataCount ?? 0)) > 0 || (self?.selectedImageDataCount ?? 0) > 0 || (self?.selectedTotalDuration ?? 0) > 0) { self?.stuckPointMusicData?.endTime = (self?.stuckPointMusicData?.startTime ?? 0) + (self?.stuckPointMusicData?.stuckPointCuttingTime(videoCount: (self?.selectedDataCount ?? 0) - (self?.selectedImageDataCount ?? 0), imageCount: self?.selectedImageDataCount ?? 0, totalDuration: self?.selectedTotalDuration ?? 0) ?? 0) } self?.stuckPointCuttingView.updateEndTime(startTime: CGFloat(self?.stuckPointMusicData?.startTime ?? 0), endTime: CGFloat(self?.stuckPointMusicData?.endTime ?? 0)) if self?.stuckPointMusicData?.localPath == nil || (self?.stuckPointMusicData?.localPath?.count ?? 0) > 0 { PQDownloadManager.downLoadFile(url: self?.stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in if error == nil, filePath != nil { self?.isSynchroMusicInfoSuccess = true self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "") // 处理所有数据完成 self?.dealWithDataSuccess() } else { if self?.synchroMarskView.superview != nil { self?.synchroMarskView.removeMarskView() } // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in // self?.navigationController?.popViewController(animated: true) // } } } } else { self?.isSynchroMusicInfoSuccess = true // 处理所有数据完成 self?.dealWithDataSuccess() } // 添加子视图 self?.addSubViews() } else { if self?.synchroMarskView.superview != nil { self?.synchroMarskView.removeMarskView() } // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in // self?.navigationController?.popViewController(animated: true) // } } } } else if stuckPointMusicData?.localPath == nil || (stuckPointMusicData?.localPath?.count ?? 0) > 0 { if synchroMarskView.superview == nil { UIApplication.shared.keyWindow?.addSubview(synchroMarskView) } isStuckPointDataSuccess = true PQDownloadManager.downLoadFile(url: stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in if error == nil, filePath != nil { self?.isSynchroMusicInfoSuccess = true self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "") // 处理所有数据完成 self?.dealWithDataSuccess() } else { if self?.synchroMarskView.superview != nil { self?.synchroMarskView.removeMarskView() } // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in // self?.navigationController?.popViewController(animated: true) // } } } } else { isStuckPointDataSuccess = true // 处理所有数据完成 dealWithDataSuccess() } } /// 导出相册数据 /// - Returns: <#description#> func exportPhotoData() { // 取消所有的导出 PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in exportSession.cancelExport() } var isHaveVideo: Bool = false if selectedPhotoData != nil, (selectedPhotoData?.count ?? 0) > 0 { if synchroMarskView.superview == nil { UIApplication.shared.keyWindow?.addSubview(synchroMarskView) } let dispatchGroup = DispatchGroup() for photo in selectedPhotoData! { if photo.asset != nil, photo.asset?.mediaType == .video, photo.locationPath.count <= 0 { if !isHaveVideo { isHaveVideo = true } dispatchGroup.enter() PQPHAssetVideoParaseUtil.exportPHAssetToMP4(phAsset: photo.asset!, deliveryMode: .highQualityFormat) { [weak self] phAsset, _, filePath, _ in let tempPhoto = self?.selectedPhotoData?.first(where: { material in material.asset == phAsset }) if tempPhoto != nil { if filePath != nil, (filePath?.count ?? 0) > 0 { tempPhoto?.locationPath = filePath?.replacingOccurrences(of: documensDirectory, with: "") ?? "" BFLog(message: "导出视频相册地址为") } dispatchGroup.leave() } } } } dispatchGroup.notify(queue: DispatchQueue.main) { [weak self] in self?.isExportVideosSuccess = true BFLog(message: "所有相册视频导出成功") // 处理所有数据完成 if isHaveVideo { self?.dealWithDataSuccess() } } } if !isHaveVideo { isExportVideosSuccess = true // 处理所有数据完成 dealWithDataSuccess() } } /// 处理所有数据完成 /// - Returns: <#description#> func dealWithDataSuccess() { if !isSynchroMusicInfoSuccess || !isExportVideosSuccess || !isStuckPointDataSuccess { return } playeTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int64((stuckPointMusicData?.startTime ?? 0) * 600)), timescale: 600), end: CMTime(value: CMTimeValue(Int64((stuckPointMusicData?.endTime ?? 0) * 600)), timescale: 600)) createPorjectData() settingPlayerView() if synchroMarskView.superview != nil { synchroMarskView.removeMarskView() } } }