PQStuckPointEditerController.swift 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232
  1. //
  2. // PQStuckPointEditerController.swift
  3. // PQSpeed
  4. //
  5. // Created by ak on 2021/4/26.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. // 功能:卡点音乐编辑界面
  8. // 创建不同玩法的类型
  9. enum createStickersModel {
  10. case createStickersModelPoint // 卡点
  11. case createStickersModelSpeed // 快慢速
  12. }
  13. import Foundation
  14. import ObjectMapper
  15. import RealmSwift
  16. import UIKit
  17. class PQStuckPointEditerController: PQBaseViewController {
  18. // 是否导出视频成功
  19. var isExportVideosSuccess: Bool = false
  20. // 是否请求卡点数据成功
  21. var isStuckPointDataSuccess: Bool = false
  22. // 是否同步音乐成功
  23. var isSynchroMusicInfoSuccess: Bool = false
  24. /// 当前所有的filter
  25. var filters: Array = Array<ImageProcessingOperation>.init()
  26. // 选中的总时长
  27. var selectedTotalDuration: Float64 = 0
  28. // 选择的总数
  29. var selectedDataCount: Int = 0
  30. // 选择的图片总数
  31. var selectedImageDataCount: Int = 0
  32. // 选中的素材数据
  33. var selectedPhotoData: [PQEditVisionTrackMaterialsModel]?
  34. // 选中的音乐数据
  35. var stuckPointMusicData: PQVoiceModel?
  36. // 保存所有段的所有贴纸,音频信息,用于播放器的渲染使用
  37. var projectModel: PQEditProjectModel = PQEditProjectModel()
  38. // 从草稿箱进入的项目数据
  39. var draftProjectModel: PQEditProjectModel?
  40. var mStickers: [PQEditVisionTrackMaterialsModel]?
  41. // 播放器的开始和结束时间,1,刚进界面使用推荐的开始结束时间,2,用户修改起结点时修改
  42. var playeTimeRange: CMTimeRange = CMTimeRange()
  43. //add by ak 是否是再创作模式
  44. var isReCreate:Bool = false
  45. //最后一个选择的模式 BTN 用于还原选中状态
  46. var lastEditModelBtn:UIButton?
  47. // 下一步
  48. lazy var nextBtn: UIButton = {
  49. let nextBtn = UIButton(type: .custom)
  50. nextBtn.frame = CGRect(x: cScreenWidth - 16 - cDefaultMargin * 6, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: cDefaultMargin * 6, height: cDefaultMargin * 3)
  51. nextBtn.setTitle("合成", for: .normal)
  52. nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
  53. nextBtn.addTarget(self, action: #selector(nextBtnClick(sender:)), for: .touchUpInside)
  54. nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  55. nextBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#FFFFFF"), for: .normal)
  56. nextBtn.uxy_acceptEventInterval = 0.5
  57. nextBtn.addCorner(corner: 3)
  58. return nextBtn
  59. }()
  60. // 播放器显示 view
  61. lazy var playerView: PQGPUImagePlayerView = {
  62. let playerHeight = cScreenWidth
  63. let playerView = PQGPUImagePlayerView(frame: CGRect(x: 0, y: navHeadImageView?.frame.maxY ?? 0, width: playerHeight, height: playerHeight))
  64. playerView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  65. playerView.isShowLine = false
  66. playerView.showGaussianBlur = true
  67. playerView.playerEmptyView.isHidden = true
  68. return playerView
  69. }()
  70. /// 节奏选择视图
  71. lazy var sustomSwitchView: PQCustomSwitchView = {
  72. let sustomSwitchView = PQCustomSwitchView(frame: CGRect(x:16, y: 0, width:180, height: 30), titles: ["快节奏", "适中", "慢节奏"], defaultIndex: stuckPointMusicData?.speed ?? 2)
  73. sustomSwitchView.switchChangeHandle = { [weak self] sender in
  74. // 改变速率
  75. self?.stuckPointMusicData?.speed = sender.tag
  76. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.bgmInfo?.rhythmMusicSpeed = sender.tag
  77. // 播放前先暂停
  78. // self?.playerView.stop()
  79. // 开始播放
  80. self?.settingPlayerView()
  81. // 点击上报:选择节奏
  82. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_selectRhythm, pageSource: .sp_stuck_previewSyncedUp, extParams: ["rhythmNumber": sender.tag], remindmsg: "点击上报:选择节奏")
  83. }
  84. return sustomSwitchView
  85. }()
  86. /// 裁剪视图
  87. lazy var stuckPointCuttingView: PQStuckPointCuttingView = {
  88. 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))
  89. /// 裁剪进度回调
  90. stuckPointCuttingView.videoRangeDidChanged = { [weak self] startTime, endTime in
  91. BFLog(message: "裁剪返回--startTime = \(startTime),endTime = \(endTime)")
  92. }
  93. /// 播放进度回调
  94. stuckPointCuttingView.videoProgressDidChanged = { [weak self] progress in
  95. BFLog(message: "进度更新返回--progress = \(progress) \(String(describing: self?.playerView.mPlayeTimeRange))")
  96. }
  97. /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束
  98. stuckPointCuttingView.videoDidEndDragging = { [weak self] type, startTime, endTime, progress in
  99. BFLog(message: "拖拽结束返回--type = \(type),startTime = \(startTime),endTime = \(endTime),progress = \(progress)")
  100. self?.playerView.pause()
  101. // 修改最新值
  102. self?.stuckPointMusicData?.startTime = Float64(startTime)
  103. self?.stuckPointMusicData?.endTime = Float64(endTime)
  104. // 红的指针完成
  105. if type == 3 {
  106. if CMTimeGetSeconds(self?.playerView.mPlayeTimeRange?.end ?? .zero) == 0 {
  107. BFLog(message: "mPlayeTimeRange is error")
  108. return
  109. }
  110. let newBeginSconds = (Double(startTime) + (Double(endTime) - Double(startTime)) * Double(progress)) * 600
  111. BFLog(message: " newBeginSconds is \(newBeginSconds)")
  112. let seekTimeRange: CMTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int64(newBeginSconds)), timescale: 600), end:
  113. CMTime(value: CMTimeValue(Int64(endTime * 600)), timescale: 600))
  114. BFLog(message: "修改的开始 \(CMTimeGetSeconds(seekTimeRange.start)) 结束 \(CMTimeGetSeconds(seekTimeRange.end))")
  115. // 重新设置有效缓存
  116. self?.playerView.configCache(beginTime: CMTimeGetSeconds(seekTimeRange.start))
  117. self?.playerView.play(pauseFirstFrame: false, playeTimeRange: seekTimeRange)
  118. } else {
  119. // 更改素材开始时间及结束时间
  120. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.out = Float64(endTime)
  121. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.model_in = Float64(startTime)
  122. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineIn = Float64(startTime)
  123. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineOut = Float64(endTime)
  124. BFLog(message: "调整后总时长: \(endTime - startTime) startTime:\(startTime) endTime:\(endTime)")
  125. // 初始化音频的开始和结束时间
  126. self?.playeTimeRange = CMTimeRange(start: CMTimeMakeWithSeconds(Float64(startTime), preferredTimescale: BASE_FILTER_TIMESCALE), end: CMTimeMakeWithSeconds(Float64(endTime), preferredTimescale: BASE_FILTER_TIMESCALE))
  127. DispatchQueue.global().async { // 并行、异步
  128. let beginTime: TimeInterval = Date().timeIntervalSince1970
  129. 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)))
  130. self?.playerView.mStickers = self?.mStickers
  131. DispatchQueue.main.async { // 串行、异步
  132. var endTime: TimeInterval = Date().timeIntervalSince1970
  133. BFLog(message: "endTime is endTimeendTime \(endTime = beginTime)")
  134. self?.playerView.play(pauseFirstFrame: false, playeTimeRange: self!.playeTimeRange)
  135. }
  136. }
  137. }
  138. // 埋点上报
  139. if type == 1 || type == 2 {
  140. // 点击上报:拖动拖拽条(左/右部分)r
  141. 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: "点击上报:拖动拖拽条(左/右部分)")
  142. }
  143. }
  144. return stuckPointCuttingView
  145. }()
  146. /// 卡点模式标题
  147. lazy var pointEditRemindLab: UILabel = {
  148. let pointEditRemindLab = UILabel()
  149. pointEditRemindLab.backgroundColor = .clear
  150. pointEditRemindLab.textAlignment = .left
  151. pointEditRemindLab.font = UIFont.boldSystemFont(ofSize: 14)
  152. pointEditRemindLab.textColor = .black
  153. pointEditRemindLab.text = "卡点模式"
  154. return pointEditRemindLab
  155. }()
  156. /// 卡点模式标题
  157. lazy var speedTitleLab: UILabel = {
  158. let speedTitleLab = UILabel()
  159. speedTitleLab.backgroundColor = .clear
  160. speedTitleLab.textAlignment = .left
  161. speedTitleLab.font = UIFont.boldSystemFont(ofSize: 14)
  162. speedTitleLab.textColor = .black
  163. speedTitleLab.text = "节奏变化"
  164. return speedTitleLab
  165. }()
  166. /// 卡点模式下方操作区背景
  167. lazy var pointEditBGView: UIView = {
  168. let pointEditBGView = UIView.init()
  169. pointEditBGView.backgroundColor = .clear
  170. return pointEditBGView
  171. }()
  172. //卡点编辑 btn
  173. lazy var pointEditerBtn: UIButton = {
  174. let pointEdterBtn = UIButton(type: .custom)
  175. pointEdterBtn.setImage(UIImage().BF_Image(named: "pointEditerBtn_n"), for: .normal)
  176. pointEdterBtn.setImage(UIImage().BF_Image(named: "pointEditerBtn_h"), for: .selected)
  177. pointEdterBtn.addTarget(self, action: #selector(pointEditerBtnClick(sender:)), for: .touchUpInside)
  178. pointEdterBtn.isSelected = true
  179. pointEdterBtn.adjustsImageWhenHighlighted = false
  180. return pointEdterBtn
  181. }()
  182. //音乐编辑 btn
  183. lazy var musicEditerBtn: UIButton = {
  184. let musicEditerBtn = UIButton(type: .custom)
  185. musicEditerBtn.setImage(UIImage().BF_Image(named: "musicEditerBtn_n"), for: .normal)
  186. musicEditerBtn.setImage(UIImage().BF_Image(named: "musicEditerBtn_h"), for: .selected)
  187. musicEditerBtn.addTarget(self, action: #selector(musicEditerBtnClick(sender:)), for: .touchUpInside)
  188. musicEditerBtn.adjustsImageWhenHighlighted = false
  189. return musicEditerBtn
  190. }()
  191. //快慢速卡点模式 btn
  192. lazy var speedStuckBtn: UIButton = {
  193. let speedStuckBtn = UIButton(type: .custom)
  194. speedStuckBtn.setBackgroundImage(UIImage().BF_Image(named: "speedstuck_n"), for: .normal)
  195. speedStuckBtn.setBackgroundImage(UIImage().BF_Image(named: "speedstuck_h"), for: .selected)
  196. speedStuckBtn.addTarget(self, action: #selector(editModelClick(sender:)), for: .touchUpInside)
  197. speedStuckBtn.isSelected = true
  198. lastEditModelBtn = speedStuckBtn
  199. speedStuckBtn.tag = 1
  200. speedStuckBtn.adjustsImageWhenHighlighted = false
  201. return speedStuckBtn
  202. }()
  203. //跳转卡点模式 btn
  204. lazy var jumpPointBtn: UIButton = {
  205. let jumpPointBtn = UIButton(type: .custom)
  206. jumpPointBtn.setImage(UIImage().BF_Image(named: "jumpPoint_n"), for: .normal)
  207. jumpPointBtn.setImage(UIImage().BF_Image(named: "jumpPoint_h"), for: .selected)
  208. jumpPointBtn.tag = 2
  209. jumpPointBtn.addTarget(self, action: #selector(editModelClick(sender:)), for: .touchUpInside)
  210. jumpPointBtn.adjustsImageWhenHighlighted = false
  211. return jumpPointBtn
  212. }()
  213. //仅配乐模式 btn
  214. lazy var onlyMusicBtn: UIButton = {
  215. let onlyMusicBtn = UIButton(type: .custom)
  216. onlyMusicBtn.setImage(UIImage().BF_Image(named: "onlyMusic_n"), for: .normal)
  217. onlyMusicBtn.setImage(UIImage().BF_Image(named: "onlyMusic_h"), for: .selected)
  218. onlyMusicBtn.tag = 3
  219. onlyMusicBtn.addTarget(self, action: #selector(editModelClick(sender:)), for: .touchUpInside)
  220. onlyMusicBtn.adjustsImageWhenHighlighted = false
  221. return onlyMusicBtn
  222. }()
  223. //编辑模式的指示线
  224. lazy var editModelLineView: UIView = {
  225. let editModelLineView = UIView.init()
  226. editModelLineView.backgroundColor = UIColor.hexColor(hexadecimal: "#EFEFEF")
  227. return editModelLineView
  228. }()
  229. //操作面板上的分割线
  230. lazy var optionlineView: UIView = {
  231. let optionlineView = UIView.init()
  232. optionlineView.backgroundColor = UIColor.hexColor(hexadecimal: "#EFEFEF")
  233. return optionlineView
  234. }()
  235. //固定速度 UI
  236. lazy var speedSettingView:PQSpeedSettingView = {
  237. let speedSettingView = PQSpeedSettingView.init()
  238. speedSettingView.backgroundColor = .clear
  239. speedSettingView.viewType = 1
  240. speedSettingView.selectSpeedCallBack = { [weak self] maxSpeed,minSpeed in
  241. BFLog(message: "maxSpeed is\(maxSpeed) minSpeed \(minSpeed)")
  242. if(maxSpeed == 0 && minSpeed == 0){
  243. self?.customSpeedSettingView.isHidden = false
  244. self?.customSpeedSettingView.isJumpSpeedModel = speedSettingView.viewType == 2
  245. }
  246. }
  247. return speedSettingView
  248. }()
  249. //自定义速度
  250. lazy var customSpeedSettingView:PQCustomSpeedSettingView = {
  251. let customSpeedSettingView = PQCustomSpeedSettingView.init(frame: CGRect(x: 0, y: cScreenHeigth - 354, width: cScreenWidth, height: 354))
  252. customSpeedSettingView.isHidden = true
  253. customSpeedSettingView.isJumpSpeedModel = false
  254. return customSpeedSettingView
  255. }()
  256. /// 音乐标题
  257. lazy var musicNameView: UIView = {
  258. let musicNameView = UIView()
  259. musicNameView.addSubview(musicNameLab)
  260. let nameWidth: CGFloat = musicNameLab.frame.width + (25 + cDefaultMargin * 3)
  261. musicNameView.frame = CGRect(x: (view.frame.width - nameWidth) / 2, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: nameWidth, height: cDefaultMargin * 3)
  262. // musicNameView.backgroundColor = UIColor.hexColor(hexadecimal: "#333333")
  263. musicNameView.addCorner(corner: musicNameView.frame.height / 2)
  264. let musicImageView = UIImageView()
  265. musicImageView.tintColor = PQBFConfig.shared.styleTitleColor
  266. musicImageView.image = UIImage().BF_Image(named: "stuckPoint_reCreate_music").withRenderingMode(.alwaysTemplate)
  267. musicImageView.frame = CGRect(x: musicNameView.frame.height / 2 - 5, y: (musicNameView.frame.height - 22) / 2, width: 22, height: 22)
  268. musicNameView.addSubview(musicImageView)
  269. musicNameLab.frame.origin.x = musicImageView.frame.maxX + 5
  270. return musicNameView
  271. }()
  272. /// 音乐歌曲名称
  273. lazy var musicNameLab: LMJHorizontalScrollText = {
  274. 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
  275. let musicNameLab = LMJHorizontalScrollText(frame: CGRect(x: 0, y: 0, width: nameWidth < cDefaultMargin * 4 ? cDefaultMargin * 4 : nameWidth, height: cDefaultMargin * 3))
  276. musicNameLab.textColor = PQBFConfig.shared.styleTitleColor
  277. musicNameLab.textFont = UIFont.systemFont(ofSize: 13)
  278. musicNameLab.speed = 0.03
  279. musicNameLab.moveDirection = LMJTextScrollMoveLeft
  280. musicNameLab.moveMode = LMJTextScrollContinuous
  281. if nameWidth < cDefaultMargin * 4 {
  282. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  283. } else {
  284. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  285. }
  286. return musicNameLab
  287. }()
  288. /// 同步进度显示
  289. lazy var synchroMarskView: PQStuckPointLoadingView = {
  290. var synchroMarskView = PQStuckPointLoadingView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  291. synchroMarskView.cancelHandle = { [weak self] _ in
  292. self?.navigationController?.popViewController(animated: true)
  293. }
  294. return synchroMarskView
  295. }()
  296. override func viewWillAppear(_ animated: Bool) {
  297. super.viewDidAppear(animated)
  298. lineView?.isHidden = true
  299. UIApplication.shared.isIdleTimerDisabled = true
  300. musicNameLab.move()
  301. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  302. }
  303. @objc func enterBackground() {
  304. BFLog(message: "进入到后台")
  305. // 取消导出
  306. playerView.pause()
  307. }
  308. override func backBtnClick() {
  309. super.backBtnClick()
  310. playerView.pause()
  311. // 点击上报:返回按钮
  312. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_previewSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)")
  313. }
  314. //卡点编辑
  315. @objc func pointEditerBtnClick(sender: UIButton) {
  316. if(sender.isSelected){return}
  317. sender.isSelected = !sender.isSelected
  318. musicEditerBtn.isSelected = false
  319. }
  320. //音乐编辑
  321. @objc func musicEditerBtnClick(sender: UIButton) {
  322. if(sender.isSelected){return}
  323. sender.isSelected = !sender.isSelected
  324. pointEditerBtn.isSelected = false
  325. }
  326. //三种模式修改
  327. @objc func editModelClick(sender:UIButton){
  328. if(sender.isSelected){
  329. BFLog(message:"已经是选中状态")
  330. return
  331. }
  332. lastEditModelBtn?.isSelected = false
  333. sender.isSelected = !sender.isSelected
  334. lastEditModelBtn = sender
  335. BFLog(message: "sender tag is \(sender.tag)")
  336. //1 ui 调整
  337. if(sender.tag == 1 || sender.tag == 2){
  338. speedSettingView.viewType = sender.tag
  339. customSpeedSettingView.isJumpSpeedModel = speedSettingView.viewType == 2
  340. speedSettingView.snp.remakeConstraints { make in
  341. make.left.equalToSuperview().offset(16)
  342. make.right.equalToSuperview()
  343. make.top.equalTo(editModelLineView.snp_bottom).offset(8)
  344. make.height.equalTo(sender.tag == 1 ? 44 : 30)
  345. }
  346. speedSettingView.isHidden = false
  347. speedTitleLab.isHidden = false
  348. sustomSwitchView.isHidden = false
  349. editModelLineView.isHidden = false
  350. }else{
  351. speedSettingView.isHidden = true
  352. speedTitleLab.isHidden = true
  353. sustomSwitchView.isHidden = true
  354. editModelLineView.isHidden = true
  355. }
  356. //2 不同状的业务逻辑
  357. if(sender.tag == 1){// 快慢速
  358. }else if(sender.tag == 2){//跳跃卡点
  359. }else if(sender.tag == 3){//仅音乐
  360. }
  361. }
  362. override func viewWillDisappear(_ animated: Bool) {
  363. super.viewWillDisappear(animated)
  364. UIApplication.shared.isIdleTimerDisabled = false
  365. musicNameLab.stop()
  366. playerView.pause()
  367. }
  368. override func viewDidLoad() {
  369. super.viewDidLoad()
  370. leftButton(image: "icon_detail_back", tintColor: PQBFConfig.shared.styleTitleColor)
  371. navHeadImageView?.addSubview(nextBtn)
  372. navHeadImageView?.addSubview(musicNameView)
  373. // 添加子视图
  374. addSubViews()
  375. // 导出相册视频
  376. exportPhotoData()
  377. // 同步音乐数据
  378. synchroMusicInfoData()
  379. // 曝光上报:预览页面曝光上报
  380. PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_previewSyncedUp, pageSource: .sp_stuck_previewSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:预览页面曝光上报)")
  381. }
  382. /// 添加子视图
  383. /// - Returns: <#description#>
  384. func addSubViews() {
  385. if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 {
  386. return
  387. }
  388. view.addSubview(playerView)
  389. view.addSubview(pointEditBGView)
  390. view.addSubview(optionlineView)
  391. view.addSubview(pointEditerBtn)
  392. view.addSubview(musicEditerBtn)
  393. view.addSubview(customSpeedSettingView)
  394. pointEditBGView.addSubview(pointEditRemindLab)
  395. pointEditBGView.addSubview(speedTitleLab)
  396. pointEditBGView.addSubview(speedStuckBtn)
  397. pointEditBGView.addSubview(jumpPointBtn)
  398. pointEditBGView.addSubview(onlyMusicBtn)
  399. pointEditBGView.addSubview(editModelLineView)
  400. pointEditBGView.addSubview(speedSettingView)
  401. pointEditBGView.addSubview(sustomSwitchView)
  402. // view.addSubview(stuckPointCuttingView)
  403. pointEditerBtn.snp.makeConstraints { make in
  404. make.left.equalToSuperview().offset(100)
  405. make.bottom.equalToSuperview().offset(-(14 + cAKSafeAreaHeight))
  406. make.height.equalTo(24)
  407. make.width.equalTo(24)
  408. }
  409. musicEditerBtn.snp.makeConstraints { make in
  410. make.right.equalToSuperview().offset(-100)
  411. make.top.equalTo(pointEditerBtn.snp_top)
  412. make.height.equalTo(24)
  413. make.width.equalTo(24)
  414. }
  415. pointEditBGView.snp.makeConstraints { make in
  416. make.left.right.equalTo(view)
  417. make.bottom.equalTo(pointEditerBtn.snp_top).offset(-7)
  418. make.height.equalTo(290)
  419. }
  420. optionlineView.snp.makeConstraints { make in
  421. make.left.right.equalToSuperview()
  422. make.bottom.equalTo(pointEditBGView.snp_bottom).offset(-1)
  423. make.height.equalTo(1)
  424. }
  425. pointEditRemindLab.snp.makeConstraints { make in
  426. make.left.equalToSuperview().offset(16)
  427. make.top.equalToSuperview().offset(16)
  428. make.height.equalTo(20)
  429. make.width.equalTo(80)
  430. }
  431. speedStuckBtn.snp.makeConstraints { make in
  432. make.left.equalToSuperview().offset(16)
  433. make.top.equalTo(pointEditRemindLab.snp_bottom).offset(8)
  434. make.height.equalTo(80)
  435. make.width.equalTo(80)
  436. }
  437. jumpPointBtn.snp.makeConstraints { make in
  438. make.left.equalTo(speedStuckBtn.snp_right).offset(10)
  439. make.top.equalTo(speedStuckBtn.snp_top)
  440. make.height.equalTo(80)
  441. make.width.equalTo(80)
  442. }
  443. onlyMusicBtn.snp.makeConstraints { make in
  444. make.left.equalTo(jumpPointBtn.snp_right).offset(10)
  445. make.top.equalTo(speedStuckBtn.snp_top)
  446. make.height.equalTo(80)
  447. make.width.equalTo(64)
  448. }
  449. editModelLineView.snp.makeConstraints { make in
  450. make.left.equalToSuperview().offset(16)
  451. make.right.equalToSuperview()
  452. make.bottom.equalTo(onlyMusicBtn.snp_bottom).offset(17)
  453. make.height.equalTo(1)
  454. }
  455. speedSettingView.snp.makeConstraints { make in
  456. make.left.equalToSuperview().offset(16)
  457. make.right.equalToSuperview()
  458. make.top.equalTo(editModelLineView.snp_bottom).offset(8)
  459. make.height.equalTo(44)
  460. }
  461. speedTitleLab.snp.makeConstraints { make in
  462. make.left.equalToSuperview().offset(16)
  463. make.top.equalToSuperview().offset(213)
  464. make.height.equalTo(20)
  465. make.width.equalTo(80)
  466. }
  467. sustomSwitchView.snp.makeConstraints { make in
  468. make.left.equalToSuperview().offset(16)
  469. make.top.equalTo(speedTitleLab.snp_bottom).offset(8)
  470. make.height.equalTo(30)
  471. make.width.equalTo(180)
  472. }
  473. }
  474. @objc func nextBtnClick(sender _: UIButton) {
  475. BFLog(message: "去发布")
  476. playerView.pause()
  477. let videoExporter = PQStuckPointPublicController()
  478. videoExporter.isReCreate = isReCreate
  479. videoExporter.selectedTotalDuration = selectedTotalDuration
  480. videoExporter.selectedDataCount = selectedDataCount
  481. videoExporter.selectedImageDataCount = selectedImageDataCount
  482. // 使用深 copy
  483. let json = projectModel.toJSONString(prettyPrint: false)
  484. if json == nil {
  485. BFLog(message: "数据转换有问题 跳转")
  486. return
  487. }
  488. let tempModel: PQEditProjectModel? = Mapper<PQEditProjectModel>().map(JSONString: json!)
  489. videoExporter.mStickers = mStickers
  490. videoExporter.audioMixModel = stuckPointMusicData
  491. videoExporter.editProjectModel = tempModel
  492. navigationController?.pushViewController(videoExporter, animated: true)
  493. // 点击上报:去合成
  494. 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: "点击上报:去合成")
  495. }
  496. // MARK: - 播放器相关操作
  497. /// seek 播放器
  498. /// - Parameter playeTimeRange: 开始和结束时间
  499. func seekPlayer(playeTimeRange: CMTimeRange) {
  500. playerView.setEnableSeek(isSeek: true)
  501. playerView.play(pauseFirstFrame: false, playeTimeRange: playeTimeRange)
  502. }
  503. /// 通过传入的 selectedPhotoData 、 stuckPointMusicData 创建 projectModel 模型 后面都使用 projectModel 参数
  504. func createPorjectData() {
  505. // 1,添加选择的视觉素材
  506. let section: PQEditSectionModel = PQEditSectionModel()
  507. selectedPhotoData?.forEach { model in
  508. let json = model.toJSONString(prettyPrint: false)
  509. if json == nil {
  510. BFLog(message: "数据转换有问题 跳转")
  511. return
  512. }
  513. let tempModel: PQEditVisionTrackMaterialsModel = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: json!)!
  514. section.sectionTimeline?.visionTrack?.visionTrackMaterials.append(tempModel)
  515. }
  516. projectModel.sData?.sections.append(section)
  517. // 2,添加背景音乐
  518. projectModel.sData?.addBGM(audioMix: stuckPointMusicData!)
  519. }
  520. // 设置播放器
  521. func settingPlayerView() {
  522. // 1,设置播放器的显示区域 和画布大小
  523. // - 按第一个素材尺寸自适应
  524. let playerShowHeight = pointEditBGView.frame.minY - (navHeadImageView?.frame.maxY ?? 0)
  525. var showRect: CGRect = PQPlayerViewModel.getShowCanvasRect(editProjectModel: projectModel, showType: 1, playerViewHeight: playerShowHeight)
  526. if showRect.size.width == showRect.size.height {
  527. if cScreenWidth > playerShowHeight {
  528. showRect.origin.x = (cScreenWidth - playerShowHeight) / 2
  529. showRect.size.width = playerShowHeight
  530. showRect.size.height = playerShowHeight
  531. } else {
  532. showRect.origin.x = 0
  533. showRect.size.width = cScreenWidth
  534. showRect.size.height = cScreenWidth
  535. }
  536. }
  537. showRect.origin.y = (playerShowHeight - showRect.size.height) / 2.0 + (navHeadImageView?.frame.maxY ?? 0)
  538. if showRect.size.width != 0, showRect.size.height != 0 {
  539. playerView.resetCanvasFrame(frame: showRect)
  540. }
  541. var firstModel: PQEditVisionTrackMaterialsModel?
  542. for part in projectModel.sData!.sections {
  543. if part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0 > 0 {
  544. firstModel = part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first
  545. break
  546. }
  547. }
  548. var videoSize: CGSize = CGSize(width: Int(firstModel?.width ?? 0), height: Int(firstModel?.height ?? 0))
  549. var minSlider = min(videoSize.width, videoSize.height)
  550. var maxSlider = max(videoSize.width, videoSize.height)
  551. let ration = 1080 / minSlider
  552. minSlider = minSlider * ration
  553. maxSlider = maxSlider * ration
  554. if videoSize.width > videoSize.height { // 宽屏
  555. videoSize = CGSize(width: maxSlider, height: minSlider)
  556. } else {
  557. videoSize = CGSize(width: minSlider, height: maxSlider)
  558. }
  559. let maxValue = max(videoSize.width , videoSize.height ?? 0)
  560. if maxValue > 1920 {
  561. let maxRation = 1920 / maxValue
  562. videoSize = CGSize(width: videoSize.width * CGFloat(maxRation), height: videoSize.height * CGFloat(maxRation))
  563. BFLog(message: "最长边已经超过 1920 要等比缩小 缩放后\(videoSize)")
  564. }
  565. if (Int(videoSize.width) % 2) != 0 {
  566. videoSize.width = videoSize.width - 1
  567. }
  568. if (Int(videoSize.height) % 2) != 0 {
  569. videoSize.height = videoSize.height - 1
  570. }
  571. projectModel.sData?.videoMetaData?.videoWidth = Int(videoSize.width)
  572. projectModel.sData?.videoMetaData?.videoHeight = Int(videoSize.height)
  573. // 2,创建滤镜
  574. let beginTime: TimeInterval = Date().timeIntervalSince1970
  575. mStickers = createStickers(sections: projectModel.sData?.sections ?? List(), inputSize: CGSize(width: CGFloat(projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(projectModel.sData?.videoMetaData?.videoHeight ?? 0)), model: .createStickersModelPoint)
  576. playerView.mStickers = mStickers
  577. let end: TimeInterval = Date().timeIntervalSince1970
  578. BFLog(message: "createStickers tiskskskskme \(end - beginTime)")
  579. // 3,设置音频
  580. let audioPath = stuckPointMusicData?.localPath ?? ""
  581. BFLog(message: "初始化音频播放器的音频地址为:\(audioPath)")
  582. playerView.stop()
  583. // 这里的测试这个音乐播放有问题
  584. // self.playerView.updateAsset(URL(fileURLWithPath: "63930549652d74e477141e3b79c8d29a9ef8af81625053214516.mp3", relativeTo:Bundle.main.resourceURL!), videoComposition: nil, audioMixModel: nil)
  585. playerView.updateAsset(URL(fileURLWithPath: documensDirectory + audioPath), videoComposition: nil, audioMixModel: nil)
  586. let end2: TimeInterval = Date().timeIntervalSince1970
  587. BFLog(message: "updateAsset tiskskskskme \(end2 - end)")
  588. // 4, 设置播放器的输出画布大小
  589. playerView.movie?.mShowVidoSize = CGSize(width: CGFloat(projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(projectModel.sData?.videoMetaData?.videoHeight ?? 0))
  590. // 5,开始播放
  591. playerView.isLoop = false
  592. playerView.showProgressLab = true
  593. // 初始化音频的开始和结束时间
  594. BFLog(message: "播放的器 开始\(String(describing: CMTimeGetSeconds(playeTimeRange.start))) 结束 \(String(describing: CMTimeGetSeconds(playeTimeRange.end)))")
  595. let end3: TimeInterval = Date().timeIntervalSince1970
  596. playerView.play(pauseFirstFrame: false, playeTimeRange: CMTimeRange(start: playeTimeRange.start, end: playeTimeRange.end))
  597. let end4: TimeInterval = Date().timeIntervalSince1970
  598. BFLog(message: " playerView.play tiskskskskme \(end4 - end3)")
  599. // 6,进度回调
  600. playerView.progress = { [weak self] currentTime, tatolTime, _ in
  601. // 更新进度
  602. let progress = (currentTime - CMTimeGetSeconds(self?.playeTimeRange.start ?? .zero)) / CMTimeGetSeconds(self?.playeTimeRange.duration ?? .zero)
  603. BFLog(message: "\(currentTime) \(tatolTime) 显示播放器进度为: \(progress)")
  604. self?.stuckPointCuttingView.videoCropView.updateProgress(progress: CGFloat(progress))
  605. if self?.synchroMarskView.superview != nil {
  606. self?.synchroMarskView.removeMarskView()
  607. }
  608. }
  609. }
  610. deinit {
  611. BFLog(message: "卡点视频预览界面销毁")
  612. musicNameLab.stop()
  613. playerView.pause()
  614. // 取消所有的导出
  615. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  616. exportSession.cancelExport()
  617. }
  618. }
  619. }
  620. // MARK: - 视频渲染相关逻辑方法
  621. extension PQStuckPointEditerController {
  622. /// 分割视频 这里只设置视频类型的 in 和 out 并不设置显示的开始和结束时间 mp4 ,png ,png ,mp4
  623. /// - Parameter section: 当前段
  624. /// - Parameter stuckPoints: 用户选择的,或推荐的卡点数
  625. /// - Returns: 返回分割后的所有 stickers 和卡点数是一致的
  626. func clipVideoMerage(section: PQEditSectionModel, stuckPoints: [Float]) -> [PQEditVisionTrackMaterialsModel] {
  627. var stickers: Array = Array<PQEditVisionTrackMaterialsModel>.init()
  628. // 第二种情况:有视频要进行分割
  629. /*
  630. 1, 确定每个视频素材需要切的段数p
  631. 2, 将所有视频时长相加,得到总视频素材时长L = l1 + l2 + ... + ln
  632. 3, 视频素材a1需要切分的个数clipNum = max (round (kongduan * a1 / L) , 1)
  633. */
  634. // 要补的空位数
  635. let kongduan: Int = Int(stuckPoints.count) - Int(section.sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials().count ?? 0)
  636. // 所有视频总时长
  637. var videoTotalDuration: Float64 = 0.0
  638. for video in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video") {
  639. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + video.locationPath), options: nil)
  640. videoTotalDuration = videoTotalDuration + Float64(CMTimeGetSeconds(asset.duration))
  641. }
  642. if videoTotalDuration == 0 {
  643. BFLog(message: "视频总时长出现错误!!!!这里应该有视频素材的")
  644. return stickers
  645. }
  646. for sticker in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() {
  647. if sticker.type == StickerType.VIDEO.rawValue {
  648. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + sticker.locationPath), options: nil)
  649. // 要分割的段落
  650. let clipNum = Int(max(round(Double(kongduan) * CMTimeGetSeconds(asset.duration) / videoTotalDuration), 1))
  651. sticker.duration = CMTimeGetSeconds(asset.duration)
  652. BFLog(message: "单个视频\(sticker.locationPath)时长::\(CMTimeGetSeconds(asset.duration)) ,clipNum is:\(clipNum)")
  653. for clipindex in 0 ... clipNum - 1 {
  654. // deep copy sticker model 防止只有一个对象
  655. let stickerjson = sticker.toJSONString(prettyPrint: false)
  656. let deepCopySticker = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: stickerjson!)
  657. // 设置循环模式和适配模式
  658. deepCopySticker?.generateDefaultValues()
  659. deepCopySticker?.model_in = clipindex == 0 ? 0 : CMTimeGetSeconds(asset.duration) / Double(clipNum) * Double(clipindex)
  660. deepCopySticker?.out = (deepCopySticker?.model_in ?? 0) + CMTimeGetSeconds(asset.duration) / Double(clipNum)
  661. if (deepCopySticker?.model_in ?? 0) >= CMTimeGetSeconds(asset.duration) || (deepCopySticker?.out ?? 0) >= CMTimeGetSeconds(asset.duration) {
  662. deepCopySticker?.model_in = CMTimeGetSeconds(asset.duration) - CMTimeGetSeconds(asset.duration) / Double(clipNum)
  663. deepCopySticker?.out = CMTimeGetSeconds(asset.duration)
  664. }
  665. BFLog(message: " crilp is in \(deepCopySticker?.model_in ?? 0) out \(deepCopySticker?.out ?? 0) 总时长\(CMTimeGetSeconds(asset.duration))")
  666. if deepCopySticker != nil {
  667. stickers.append(deepCopySticker!)
  668. }
  669. }
  670. } else if sticker.type == StickerType.IMAGE.rawValue {
  671. sticker.generateDefaultValues()
  672. stickers.append(sticker)
  673. }
  674. }
  675. return stickers
  676. }
  677. /// 创建sticker
  678. /// - Parameters:
  679. /// - sections: 项目所有段落数据信息
  680. /// - inputSize: 画布大小
  681. /// - Returns: filters 数据 播放器可直接使用
  682. func createStickers(sections: List<PQEditSectionModel>, inputSize _: CGSize = .zero, model: createStickersModel = .createStickersModelPoint) -> [PQEditVisionTrackMaterialsModel] {
  683. // 保存滤镜对象数据
  684. var stickers: Array = Array<PQEditVisionTrackMaterialsModel>.init()
  685. if model == .createStickersModelPoint {
  686. for section in sections {
  687. if section.sectionType == "normal" {
  688. // 推荐卡点数
  689. var stuckPoints: Array = Array<Float>.init()
  690. var stuckPointsTemp = Array<Float>.init()
  691. for (index, dunshu) in stuckPointMusicData!.rhythmSdata[0].pointTimes.enumerated() {
  692. BFLog(message: "原所有卡点数:\(index) \(Float(dunshu) / Float(BASE_FILTER_TIMESCALE))")
  693. if Float64(dunshu) / Float64(BASE_FILTER_TIMESCALE) > CMTimeGetSeconds(playeTimeRange.start), Float64(dunshu) / Float64(BASE_FILTER_TIMESCALE) < CMTimeGetSeconds(playeTimeRange.end) {
  694. stuckPointsTemp.append(Float(dunshu) / Float(BASE_FILTER_TIMESCALE))
  695. }
  696. }
  697. /* 快慢速模式下取卡点
  698. - 快节奏为选中区域的所有点位,即0,1,2,3,4 5 6 7 8 9 10 ……
  699. - 适中为每两个点位取一个,即0,3,6,9 12
  700. - 慢节奏为每三个点位取一个,即0 5 10 15
  701. 慢节奏要做特殊处理
  702. 5d or L/1.23
  703. (*当输入素材为L ∈(0-10.5]s 时,判断与5d之间的关系,若L/1.2≥5d,则取5d;若L/1.2<5d,则取L/1.2)
  704. */
  705. // 跳跃卡点模式下根据不同速度 取卡点 1,2,3
  706. /*
  707. - 快节奏为选中区域的所有点位,即0,1,2,3,4……
  708. - 适中为每两个点位取一个,即0,2,4,6……
  709. - 慢节奏为每三个点位取一个,即0,3,6,9……
  710. */
  711. BFLog(message: "stuckPointMusicData?.speed is \(String(describing: stuckPointMusicData?.speed))")
  712. for (index, point) in stuckPointsTemp.enumerated() {
  713. if stuckPointMusicData?.speed == 1 {
  714. stuckPoints.append(Float(point))
  715. } else if stuckPointMusicData?.speed == 2 {
  716. if index % 2 == 0 {
  717. stuckPoints.append(point)
  718. }
  719. } else if stuckPointMusicData?.speed == 3 {
  720. if index % 3 == 0 {
  721. stuckPoints.append(point)
  722. }
  723. }
  724. }
  725. for point in stuckPoints {
  726. BFLog(message: "没有 start end 计算后的卡点数\(point)")
  727. }
  728. if stuckPoints.first != nil {
  729. stuckPoints.removeFirst()
  730. }
  731. if stuckPoints.last != nil {
  732. stuckPoints.removeLast()
  733. }
  734. // 开始时间是服务器返回, 结果时间根据策略计算的
  735. stuckPoints.insert(Float(CMTimeGetSeconds(playeTimeRange.start)), at: 0)
  736. stuckPoints.insert((Float(CMTimeGetSeconds(playeTimeRange.end))), at: stuckPoints.count)
  737. for point in stuckPoints {
  738. BFLog(message: "有 start end 计算后的卡点数\(point)")
  739. }
  740. BFLog(message: "stuckPoints count is \(stuckPoints.count)")
  741. // 当用户上传视觉素材个数大于等于音乐选择区域节拍分割个数时,无需进行视频分割,只显示卡点数-1 个素材
  742. if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count >= stuckPointMusicData!.rhythmSdata[0].pointTimes.count {
  743. for (index, sticker) in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().enumerated() {
  744. if index == stuckPointMusicData!.rhythmSdata[0].pointTimes.count {
  745. BFLog(message: "到达卡点数量")
  746. break
  747. }
  748. BFLog(message: "创建 filter start :\(sticker.timelineIn) end :\(sticker.timelineOut) type is \(sticker.type) \(sticker.locationPath)")
  749. sticker.timelineIn = Float64("\(stuckPoints[index])") ?? 0.0
  750. sticker.timelineOut = Float64("\(stuckPoints[index + 1])") ?? 0.0
  751. BFLog(message: "卡点 间隔 \(sticker.timelineIn - sticker.timelineOut)")
  752. sticker.generateDefaultValues()
  753. stickers.append(sticker)
  754. }
  755. } else {
  756. // 卡点数 > 选择素材数
  757. // 第一种情况:全是图片,图片回环播放
  758. if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video").count == 0, section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "image").count > 0 {
  759. for (index, point) in stuckPoints.enumerated() {
  760. let sticker: PQEditVisionTrackMaterialsModel = section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials()[index % section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count]
  761. BFLog(message: "stickerlocationPath sticker : \(sticker.locationPath)")
  762. let stickerjson = sticker.toJSONString(prettyPrint: false)
  763. let deepCopySticker = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: stickerjson!)
  764. if deepCopySticker!.type == StickerType.IMAGE.rawValue {
  765. if index + 1 < stuckPoints.count {
  766. deepCopySticker!.timelineIn = Float64("\(stuckPoints[index])") ?? 0.0
  767. deepCopySticker!.timelineOut = Float64("\(stuckPoints[index + 1])") ?? 0.0
  768. if deepCopySticker != nil {
  769. deepCopySticker?.generateDefaultValues()
  770. stickers.append(deepCopySticker!)
  771. }
  772. }
  773. }
  774. }
  775. } else {
  776. // 第二种情况:有视频要进行分割
  777. let clipFilters = clipVideoMerage(section: section, stuckPoints: stuckPoints)
  778. for (index, point) in stuckPoints.enumerated() {
  779. BFLog(message: "aaaaaindexindeindexxindexindexindex \(index) \(point)")
  780. if index + 1 < stuckPoints.count, index < clipFilters.count {
  781. BFLog(message: "bbbbbindexindeindexxindexindexindex \(index) \(point)")
  782. let sticker: PQEditVisionTrackMaterialsModel = clipFilters[index]
  783. sticker.timelineIn = Float64("\(stuckPoints[index])") ?? 0.0
  784. // TODO: 不是最好方案
  785. sticker.timelineOut = Float64("\(stuckPoints[index + 1])") ?? 0.0
  786. // 卡点的时间 > in out 值 这里就会出现鬼畜效果
  787. let timelineInterval = sticker.timelineOut - sticker.timelineIn
  788. let inOutInterval = sticker.out - sticker.model_in
  789. if timelineInterval > inOutInterval {
  790. BFLog(message: "实际要显示卡点时长\(timelineInterval) 素材裁剪时长:\(inOutInterval)")
  791. sticker.out = sticker.model_in + timelineInterval
  792. // 下面只是 LOG 方便查问题
  793. let stickerInOut = sticker.out - sticker.model_in
  794. let stickerTimelineInOut = sticker.timelineOut - sticker.timelineIn
  795. if stickerInOut != stickerTimelineInOut {
  796. BFLog(message: "sticker.timelineIn \(sticker.timelineIn) stickerTimelineInOut is\(stickerTimelineInOut) stickerInOut is\(stickerInOut) 相差\(stickerTimelineInOut - stickerInOut)")
  797. }
  798. }
  799. // out > 素材的总时长in out 进行前移操作
  800. let offsetAssetDuration = sticker.out - sticker.duration
  801. if offsetAssetDuration > 0 {
  802. sticker.model_in = sticker.model_in - offsetAssetDuration
  803. sticker.out = sticker.out - offsetAssetDuration
  804. }
  805. BFLog(message: "index is \(index)分割后 创建 filter timelineIn :\(sticker.timelineIn) timelineOut :\(sticker.timelineOut) in :\(sticker.model_in) out:\(sticker.out) type is \(sticker.type) 显示总时长为:\(sticker.timelineOut - sticker.timelineIn) 裁剪总时长\(sticker.out - sticker.model_in)")
  806. stickers.append(sticker)
  807. }
  808. }
  809. }
  810. }
  811. }
  812. }
  813. } else {
  814. for section in sections {
  815. if section.sectionType == "normal" {
  816. // 推荐卡点数
  817. var stuckPoints: Array = Array<Float>.init()
  818. var stuckPointsTemp = Array<Float>.init()
  819. for (index, dunshu) in stuckPointMusicData!.rhythmSdata[0].pointTimes.enumerated() {
  820. BFLog(message: "原所有卡点数:\(index) \(Float(dunshu) / Float(BASE_FILTER_TIMESCALE))")
  821. if Float64(dunshu) / Float64(BASE_FILTER_TIMESCALE) > CMTimeGetSeconds(playeTimeRange.start), Float64(dunshu) / Float64(BASE_FILTER_TIMESCALE) < CMTimeGetSeconds(playeTimeRange.end) {
  822. stuckPointsTemp.append(Float(dunshu) / Float(BASE_FILTER_TIMESCALE))
  823. }
  824. }
  825. // 根据不同速度 取卡点 1,2,3
  826. /*
  827. - 快节奏为选中区域的所有点位,即0,1,2,3,4……
  828. - 适中为每两个点位取一个,即0,2,4,6……
  829. - 慢节奏为每三个点位取一个,即0,3,6,9……
  830. */
  831. BFLog(message: "stuckPointMusicData?.speed is \(String(describing: stuckPointMusicData?.speed))")
  832. for (index, point) in stuckPointsTemp.enumerated() {
  833. if stuckPointMusicData?.speed == 1 {
  834. stuckPoints.append(Float(point))
  835. } else if stuckPointMusicData?.speed == 2 {
  836. if index % 2 == 0 {
  837. stuckPoints.append(point)
  838. }
  839. } else if stuckPointMusicData?.speed == 3 {
  840. if index % 3 == 0 {
  841. stuckPoints.append(point)
  842. }
  843. }
  844. }
  845. for point in stuckPoints {
  846. BFLog(message: "没有 start end 计算后的卡点数\(point)")
  847. }
  848. if stuckPoints.first != nil {
  849. stuckPoints.removeFirst()
  850. }
  851. if stuckPoints.last != nil {
  852. stuckPoints.removeLast()
  853. }
  854. // 开始时间是服务器返回, 结果时间根据策略计算的
  855. stuckPoints.insert(Float(CMTimeGetSeconds(playeTimeRange.start)), at: 0)
  856. stuckPoints.insert((Float(CMTimeGetSeconds(playeTimeRange.end))), at: stuckPoints.count)
  857. for point in stuckPoints {
  858. BFLog(message: "有 start end 计算后的卡点数\(point)")
  859. }
  860. BFLog(message: "stuckPoints count is \(stuckPoints.count)")
  861. // 当用户上传视觉素材个数大于等于音乐选择区域节拍分割个数时,无需进行视频分割,只显示卡点数-1 个素材
  862. if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count >= stuckPointMusicData!.rhythmSdata[0].pointTimes.count {
  863. for (index, sticker) in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().enumerated() {
  864. if index == stuckPointMusicData!.rhythmSdata[0].pointTimes.count {
  865. BFLog(message: "到达卡点数量")
  866. break
  867. }
  868. BFLog(message: "创建 filter start :\(sticker.timelineIn) end :\(sticker.timelineOut) type is \(sticker.type) \(sticker.locationPath)")
  869. sticker.timelineIn = Float64("\(stuckPoints[index])") ?? 0.0
  870. sticker.timelineOut = Float64("\(stuckPoints[index + 1])") ?? 0.0
  871. BFLog(message: "卡点 间隔 \(sticker.timelineIn - sticker.timelineOut)")
  872. sticker.generateDefaultValues()
  873. stickers.append(sticker)
  874. }
  875. } else {
  876. // 卡点数 > 选择素材数
  877. // 第一种情况:全是图片,图片回环播放
  878. if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video").count == 0, section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "image").count > 0 {
  879. for (index, point) in stuckPoints.enumerated() {
  880. let sticker: PQEditVisionTrackMaterialsModel = section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials()[index % section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count]
  881. BFLog(message: "stickerlocationPath sticker : \(sticker.locationPath)")
  882. let stickerjson = sticker.toJSONString(prettyPrint: false)
  883. let deepCopySticker = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: stickerjson!)
  884. if deepCopySticker!.type == StickerType.IMAGE.rawValue {
  885. if index + 1 < stuckPoints.count {
  886. deepCopySticker!.timelineIn = Float64("\(stuckPoints[index])") ?? 0.0
  887. deepCopySticker!.timelineOut = Float64("\(stuckPoints[index + 1])") ?? 0.0
  888. if deepCopySticker != nil {
  889. deepCopySticker?.generateDefaultValues()
  890. stickers.append(deepCopySticker!)
  891. }
  892. }
  893. }
  894. }
  895. } else {
  896. // 第二种情况:有视频要进行分割
  897. let clipFilters = clipVideoMerage(section: section, stuckPoints: stuckPoints)
  898. for (index, point) in stuckPoints.enumerated() {
  899. BFLog(message: "aaaaaindexindeindexxindexindexindex \(index) \(point)")
  900. if index + 1 < stuckPoints.count, index < clipFilters.count {
  901. BFLog(message: "bbbbbindexindeindexxindexindexindex \(index) \(point)")
  902. let sticker: PQEditVisionTrackMaterialsModel = clipFilters[index]
  903. let spit: Int = 2
  904. sticker.timelineIn = 57.5 + Double(index * spit)
  905. sticker.timelineOut = sticker.timelineIn + Double(spit)
  906. sticker.speedRate = index % 2 == 0 ? 0.1 : 2
  907. sticker.model_in = Double(index * spit)
  908. sticker.out = sticker.model_in + Double(spit)
  909. // let spit: Int = 2
  910. // sticker.timelineIn = 57.5
  911. // sticker.timelineOut = sticker.timelineIn + 24
  912. // sticker.speedRate = index % 2 == 0 ? 0.1 : 2
  913. //// sticker.speedRate = 0.1
  914. // sticker.model_in = 0
  915. // sticker.out = sticker.model_in + 24
  916. if stickers.count < 12 {
  917. BFLog(message: "index is \(index)分割后 创建 filter timelineIn :\(sticker.timelineIn) timelineOut :\(sticker.timelineOut) in :\(sticker.model_in) out:\(sticker.out) type is \(sticker.type) 显示总时长为:\(sticker.timelineOut - sticker.timelineIn) 裁剪总时长\(sticker.out - sticker.model_in)")
  918. stickers.append(sticker)
  919. }
  920. }
  921. }
  922. }
  923. }
  924. }
  925. }
  926. }
  927. return stickers
  928. }
  929. }
  930. // MARK: - 同步/下载素材相关
  931. /// 同步/下载素材相关
  932. extension PQStuckPointEditerController {
  933. /// 同步音乐相关数据
  934. /// - Returns: <#description#>
  935. func synchroMusicInfoData() {
  936. if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 {
  937. if synchroMarskView.superview == nil {
  938. UIApplication.shared.keyWindow?.addSubview(synchroMarskView)
  939. }
  940. PQStuckPointViewModel.stuckPointMusicDetailData(musicId: stuckPointMusicData?.musicId ?? "", originType: stuckPointMusicData?.originType ?? 1) { [weak self] newMusicData, _ in
  941. if newMusicData != nil, (newMusicData?.rhythmSdata.count ?? 0) > 0 {
  942. self?.isStuckPointDataSuccess = true
  943. self?.stuckPointMusicData?.rhythmSdata = newMusicData?.rhythmSdata ?? []
  944. self?.stuckPointMusicData?.startTime = newMusicData?.startTime ?? 0
  945. self?.stuckPointMusicData?.endTime = newMusicData?.endTime ?? 0
  946. if newMusicData?.speed != nil {
  947. self?.stuckPointMusicData?.speed = newMusicData?.speed ?? 2
  948. }
  949. if (self?.stuckPointMusicData?.rhythmSdata.count ?? 0) > 0 && (((self?.selectedDataCount ?? 0) - (self?.selectedImageDataCount ?? 0)) > 0 || (self?.selectedImageDataCount ?? 0) > 0 || (self?.selectedTotalDuration ?? 0) > 0) {
  950. 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)
  951. }
  952. self?.stuckPointCuttingView.updateEndTime(startTime: CGFloat(self?.stuckPointMusicData?.startTime ?? 0), endTime: CGFloat(self?.stuckPointMusicData?.endTime ?? 0))
  953. if self?.stuckPointMusicData?.localPath == nil || (self?.stuckPointMusicData?.localPath?.count ?? 0) > 0 {
  954. PQDownloadManager.downLoadFile(url: self?.stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in
  955. if error == nil, filePath != nil {
  956. self?.isSynchroMusicInfoSuccess = true
  957. self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "")
  958. // 处理所有数据完成
  959. self?.dealWithDataSuccess()
  960. } else {
  961. if self?.synchroMarskView.superview != nil {
  962. self?.synchroMarskView.removeMarskView()
  963. }
  964. // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in
  965. // self?.navigationController?.popViewController(animated: true)
  966. // }
  967. }
  968. }
  969. } else {
  970. self?.isSynchroMusicInfoSuccess = true
  971. // 处理所有数据完成
  972. self?.dealWithDataSuccess()
  973. }
  974. // 添加子视图
  975. self?.addSubViews()
  976. } else {
  977. if self?.synchroMarskView.superview != nil {
  978. self?.synchroMarskView.removeMarskView()
  979. }
  980. // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in
  981. // self?.navigationController?.popViewController(animated: true)
  982. // }
  983. }
  984. }
  985. } else if stuckPointMusicData?.localPath == nil || (stuckPointMusicData?.localPath?.count ?? 0) > 0 {
  986. if synchroMarskView.superview == nil {
  987. UIApplication.shared.keyWindow?.addSubview(synchroMarskView)
  988. }
  989. isStuckPointDataSuccess = true
  990. PQDownloadManager.downLoadFile(url: stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in
  991. if error == nil, filePath != nil {
  992. self?.isSynchroMusicInfoSuccess = true
  993. self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "")
  994. // 处理所有数据完成
  995. self?.dealWithDataSuccess()
  996. } else {
  997. if self?.synchroMarskView.superview != nil {
  998. self?.synchroMarskView.removeMarskView()
  999. }
  1000. // PQUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in
  1001. // self?.navigationController?.popViewController(animated: true)
  1002. // }
  1003. }
  1004. }
  1005. } else {
  1006. isStuckPointDataSuccess = true
  1007. // 处理所有数据完成
  1008. dealWithDataSuccess()
  1009. }
  1010. }
  1011. /// 导出相册数据
  1012. /// - Returns: <#description#>
  1013. func exportPhotoData() {
  1014. // 取消所有的导出
  1015. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  1016. exportSession.cancelExport()
  1017. }
  1018. var isHaveVideo: Bool = false
  1019. if selectedPhotoData != nil, (selectedPhotoData?.count ?? 0) > 0 {
  1020. if synchroMarskView.superview == nil {
  1021. UIApplication.shared.keyWindow?.addSubview(synchroMarskView)
  1022. }
  1023. let dispatchGroup = DispatchGroup()
  1024. for photo in selectedPhotoData! {
  1025. if photo.asset != nil, photo.asset?.mediaType == .video {
  1026. if !isHaveVideo {
  1027. isHaveVideo = true
  1028. }
  1029. dispatchGroup.enter()
  1030. PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: photo.asset!) { avAsset, _, _, _ in
  1031. if avAsset is AVURLAsset {
  1032. // 创建目录
  1033. let fileName = (avAsset as! AVURLAsset).url.absoluteString
  1034. BFLog(message: "video fileName is\(fileName)")
  1035. let tempPhoto = self.selectedPhotoData?.first(where: { material in
  1036. material.asset == photo.asset
  1037. })
  1038. if (fileName.count) > 0 {
  1039. createDirectory(path: photoLibraryDirectory)
  1040. let outFilePath = photoLibraryDirectory + fileName.md5 + ".mp4"
  1041. // 文件存在先删除老文件
  1042. if FileManager.default.fileExists(atPath: outFilePath) {
  1043. do {
  1044. try FileManager.default.removeItem(at: NSURL.fileURL(withPath: outFilePath))
  1045. } catch {
  1046. BFLog(message: "导出相册视频-error == \(error)")
  1047. }
  1048. }
  1049. do {
  1050. try FileManager.default.copyItem(atPath: fileName.replacingOccurrences(of: "file:///", with: ""), toPath: outFilePath)
  1051. print("Success to copy file.")
  1052. } catch {
  1053. print("Failed to copy file.")
  1054. }
  1055. tempPhoto?.locationPath = outFilePath.replacingOccurrences(of: documensDirectory, with: "")
  1056. BFLog(message: "导出视频相册地址为 \(String(describing: tempPhoto?.locationPath))")
  1057. dispatchGroup.leave()
  1058. }
  1059. }
  1060. }
  1061. }
  1062. }
  1063. dispatchGroup.notify(queue: DispatchQueue.main) { [weak self] in
  1064. self?.isExportVideosSuccess = true
  1065. BFLog(message: "所有相册视频导出成功")
  1066. // 处理所有数据完成
  1067. if isHaveVideo {
  1068. self?.dealWithDataSuccess()
  1069. }
  1070. }
  1071. // 只有图片
  1072. if !isHaveVideo {
  1073. isExportVideosSuccess = true
  1074. BFLog(message: "所有相册视频导出成功")
  1075. dealWithDataSuccess()
  1076. }
  1077. }
  1078. }
  1079. /// 处理所有数据完成
  1080. /// - Returns: <#description#>
  1081. func dealWithDataSuccess() {
  1082. BFLog(message: "三组参数:\(isSynchroMusicInfoSuccess) \(isStuckPointDataSuccess) \(isExportVideosSuccess)")
  1083. if !isSynchroMusicInfoSuccess || !isStuckPointDataSuccess || !isExportVideosSuccess {
  1084. return
  1085. }
  1086. playeTimeRange = CMTimeRange(start: CMTimeMakeWithSeconds(stuckPointMusicData?.startTime ?? 0, preferredTimescale: BASE_FILTER_TIMESCALE), end: CMTimeMakeWithSeconds(stuckPointMusicData?.endTime ?? 0, preferredTimescale: BASE_FILTER_TIMESCALE))
  1087. createPorjectData()
  1088. settingPlayerView()
  1089. }
  1090. }