PQStuckPointEditerController.swift 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066
  1. //
  2. // PQStuckPointEditerController.swift
  3. // PQSpeed
  4. //
  5. // Created by ak on 2021/4/26.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. // 功能:卡点音乐编辑界面
  8. import BFAnalyzeKit
  9. import BFCommonKit
  10. import BFUIKit
  11. import BFMediaKit
  12. import Foundation
  13. import ObjectMapper
  14. import Photos
  15. import RealmSwift
  16. import UIKit
  17. class PQStuckPointEditerController: BFBaseViewController {
  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: [PHAsset]? {
  34. didSet {
  35. if selectedPhotoData != nil, (selectedPhotoData?.count ?? 0) > 0 {
  36. selectedMetarialData = Array<PQEditVisionTrackMaterialsModel>.init()
  37. selectedPhotoData?.forEach { phAsset in
  38. let metarialData = PQEditVisionTrackMaterialsModel()
  39. metarialData.asset = phAsset
  40. metarialData.width = Float(phAsset.pixelWidth)
  41. metarialData.itemWidth = Float(phAsset.pixelWidth)
  42. metarialData.height = Float(phAsset.pixelHeight)
  43. metarialData.itemHeight = Float(phAsset.pixelHeight)
  44. if phAsset.mediaType == .image {
  45. metarialData.type = "image"
  46. } else if phAsset.mediaType == .video {
  47. metarialData.type = "video"
  48. metarialData.duration = Float64(phAsset.duration)
  49. }
  50. metarialData.canvasFillType = phAsset.canvasFillType ?? ""
  51. metarialData.locationPath = phAsset.localPath ?? ""
  52. metarialData.selectedIndex = phAsset.selectedIndex ?? 1
  53. metarialData.originalData = phAsset.originalData
  54. metarialData.coverImageUI = phAsset.image
  55. selectedMetarialData?.append(metarialData)
  56. }
  57. }
  58. }
  59. }
  60. var selectedMetarialData: [PQEditVisionTrackMaterialsModel]?
  61. // 选中的音乐数据
  62. var stuckPointMusicData: PQVoiceModel?
  63. // 保存所有段的所有贴纸,音频信息,用于播放器的渲染使用
  64. var projectModel: PQEditProjectModel = PQEditProjectModel()
  65. // 从草稿箱进入的项目数据
  66. var draftProjectModel: PQEditProjectModel?
  67. var mStickers: [PQEditVisionTrackMaterialsModel]?
  68. // 播放器的开始和结束时间,1,刚进界面使用推荐的开始结束时间,2,用户修改起结点时修改
  69. var playeTimeRange: CMTimeRange = CMTimeRange()
  70. // 首帧图片
  71. var firstFrameImage: UIImage? {
  72. didSet {
  73. if firstFrameImage != nil {
  74. playerView.layer.contents = firstFrameImage?.cgImage
  75. }
  76. }
  77. }
  78. // add by ak 是否是再创作模式
  79. var isReCreate: Bool = false
  80. public var reCreateVideoData: PQReCreateModel? // 再创作数据
  81. // 最后一个选择的模式 BTN 用于还原选中状态
  82. var lastEditModelBtn: UIButton?
  83. // add by ak 最大、最小速度 有固定值和自定义,当快慢速下两个值都有效,当跳跃卡点只有maxSpeed有效
  84. // 快慢速模式的 速度设置,快/慢速
  85. var modelSpeed_maxSpeed: Float = 1.0
  86. var modelSpeed_minSpeed: Float = 1.0
  87. // 跳跃模式的速度
  88. var modelPoint_speed: Float = 1.0
  89. // 快慢速最后一次选择的速度位置
  90. var lastSpeedSelectIndex: Int = 0
  91. // 跳跃卡点最后一次选择的速度位置
  92. var lastJumpSpeedSelectIndex: Int = 0
  93. // 循环次数设置最后一次选择的位置
  94. var lastCyclesSelectIndex: Int = -1
  95. // 当前选择的玩法模式
  96. var currentCreateStickersModel: createStickersModel = .createStickersModelSpeed
  97. // 最终使用的卡点数据
  98. var finallyStuckPoints: Array = Array<Float>.init()
  99. var finallyStuckPointsInt64: Array = Array<Int64>.init()
  100. // 最终使用的音频时长
  101. var finallyUserAudioTime: Float = 0.0
  102. // 注意推荐时间位置和后面最近的卡点时间与0.3的关系
  103. // 保存丢卡点处理后的卡点信息推荐开始到最后倒数第二个
  104. // 经过档位处理后的卡点信息
  105. var stuckPointsTemp: Array = Array<Float>.init()
  106. var stuckPointsTempInt64: Array = Array<Int64>.init()
  107. // 是否点击了下一步去合成
  108. var isClickNextBtn: Bool = false
  109. // 下一步
  110. lazy var nextBtn: UIButton = {
  111. let nextBtn = UIButton(type: .custom)
  112. nextBtn.frame = CGRect(x: cScreenWidth - 16 - cDefaultMargin * 6, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: cDefaultMargin * 6, height: cDefaultMargin * 3)
  113. nextBtn.setTitle("合成", for: .normal)
  114. nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
  115. nextBtn.addTarget(self, action: #selector(nextBtnClick(sender:)), for: .touchUpInside)
  116. nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  117. nextBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#FFFFFF"), for: .normal)
  118. nextBtn.uxy_acceptEventInterval = 0.5
  119. nextBtn.addCorner(corner: 3)
  120. return nextBtn
  121. }()
  122. // 播放器显示 view
  123. lazy var playerView: PQGPUImagePlayerView = {
  124. let playerView = PQGPUImagePlayerView(frame: .zero)
  125. playerView.backgroundColor = BFConfig.shared.styleBackGroundColor
  126. playerView.isShowLine = false
  127. playerView.showGaussianBlur = true
  128. playerView.pause()
  129. playerView.renderViewOnClickHandle = { [weak self] in
  130. self?.musicEditBGView.pausePlayer()
  131. }
  132. playerView.playerEmptyView.isHidden = true
  133. return playerView
  134. }()
  135. /// 节奏选择视图
  136. lazy var sustomSwitchView: PQCustomSwitchView = {
  137. let sustomSwitchView = PQCustomSwitchView(frame: CGRect(x: 16, y: 0, width: 180, height: 30), titles: ["慢节奏", "适中", "快节奏"], defaultIndex: stuckPointMusicData?.speed ?? 2)
  138. sustomSwitchView.switchChangeHandle = { [weak self] sender in
  139. // 改变速率,.只有快慢速且非只有图片素材时自动+1处理
  140. self?.stuckPointMusicData?.speed = sender.tag
  141. self?.musicEditBGView.pausePlayer()
  142. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.bgmInfo?.rhythmMusicSpeed = sender.tag
  143. // 播放前先暂停
  144. // self?.playerView.stop()
  145. // 开始播放
  146. self?.settingPlayerView()
  147. // 下面都是统计
  148. if self?.currentCreateStickersModel == .createStickersModelPoint {
  149. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectMusicVideoRhythm, pageSource: .sp_stuck_previewSyncedUp, commonParams: commonParams())
  150. } else if self?.currentCreateStickersModel == .createStickersModelSpeed {
  151. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectSpeedRhythm, pageSource: .sp_stuck_previewSyncedUp, commonParams: commonParams())
  152. } else if self?.currentCreateStickersModel == .createStickersModelOnlyMusic {
  153. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectMusicVideoRepeatRhythm, pageSource: .sp_stuck_previewSyncedUp, commonParams: commonParams())
  154. }
  155. }
  156. return sustomSwitchView
  157. }()
  158. /// 裁剪视图
  159. lazy var stuckPointCuttingView: PQStuckPointCuttingView = {
  160. let stuckPointCuttingView = PQStuckPointCuttingView(frame: CGRect(x: 0, y: optionlineView.frame.minY - 85 - 28, width: view.frame.width, height: 80), duration: CGFloat(Float(stuckPointMusicData?.duration ?? "0") ?? 0), suggestRhythmStartTime: CGFloat(stuckPointMusicData?.suggestRhythmStartTime ?? 0))
  161. /// 裁剪进度回调
  162. stuckPointCuttingView.videoDidBeginDrag = { [weak self] in
  163. BFLog(message: "开始划动")
  164. self?.playerView.pause()
  165. }
  166. /// 播放进度回调
  167. stuckPointCuttingView.videoProgressDidChanged = { [weak self] progress in
  168. BFLog(message: "进度更新返回--progress = \(progress) \(String(describing: self?.playerView.mPlayeTimeRange))")
  169. }
  170. /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束
  171. stuckPointCuttingView.videoDidEndDragging = { [weak self] type, startTime, endTime, progress in
  172. BFLog(1, message: "拖拽结束返回--type = \(type),startTime = \(startTime),endTime = \(endTime),progress = \(progress)")
  173. self?.playerView.pause()
  174. self?.musicEditBGView.pausePlayer()
  175. // 修改最新值
  176. self?.stuckPointMusicData?.startTime = Float64(startTime)
  177. self?.stuckPointMusicData?.endTime = Float64(endTime)
  178. // 红的指针完成
  179. if type == 3 {
  180. if CMTimeGetSeconds(self?.playerView.mPlayeTimeRange?.end ?? .zero) == 0 {
  181. BFLog(message: "mPlayeTimeRange is error")
  182. return
  183. }
  184. let newBeginSconds = (Double(startTime) + (Double(endTime) - Double(startTime)) * Double(progress)) * 600
  185. BFLog(message: " newBeginSconds is \(newBeginSconds)")
  186. let seekTimeRange: CMTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int64(newBeginSconds)), timescale: 600), end:
  187. CMTime(value: CMTimeValue(Int64(endTime * 600)), timescale: 600))
  188. BFLog(message: "修改的开始 \(CMTimeGetSeconds(seekTimeRange.start)) 结束 \(CMTimeGetSeconds(seekTimeRange.end))")
  189. // 重新设置有效缓存
  190. self?.playerView.configCache(beginTime: CMTimeGetSeconds(seekTimeRange.start))
  191. self?.playerView.play(pauseFirstFrame: false, playeTimeRange: seekTimeRange)
  192. } else {
  193. // 更改素材开始时间及结束时间
  194. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.out = Float64(endTime)
  195. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.model_in = Float64(startTime)
  196. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineIn = Float64(startTime)
  197. self?.projectModel.sData?.getBGMSession()?.sectionTimeline?.audioTrack?.audioTrackMaterials.first?.timelineOut = Float64(endTime)
  198. BFLog(message: "调整后总时长: \(endTime - startTime) startTime:\(startTime) endTime:\(endTime)")
  199. // 初始化音频的开始和结束时间
  200. self?.playeTimeRange = CMTimeRange(start: CMTimeMakeWithSeconds(Float64(startTime), preferredTimescale: BASE_FILTER_TIMESCALE), end: CMTimeMakeWithSeconds(Float64(endTime), preferredTimescale: BASE_FILTER_TIMESCALE))
  201. self?.dealParameter(model: self?.currentCreateStickersModel ?? .createStickersModelSpeed)
  202. if (self?.finallyStuckPoints.count ?? 0) < 1 {
  203. BFLog(message: "finallyStuckPoints data is error!!!!")
  204. return
  205. }
  206. DispatchQueue.global().async { // 并行、异步
  207. let beginTime = Date()
  208. 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)), model: self?.currentCreateStickersModel ?? .createStickersModelSpeed)
  209. DispatchQueue.main.async { // 串行、异步
  210. self?.playerView.mStickers = self?.mStickers
  211. BFLog(message: "endTime is endTimeendTime \(Date().timeIntervalSince(beginTime))")
  212. self?.playerView.play(pauseFirstFrame: false, playeTimeRange: self!.playeTimeRange)
  213. // 更新一下时间条的UI总时间 及数据
  214. self?.stuckPointCuttingView.videoDuration = CGFloat(self?.finallyUserAudioTime ?? 0)
  215. self?.stuckPointCuttingView.stuckPointStartTime = CGFloat(CMTimeGetSeconds(self?.playeTimeRange.start ?? .zero))
  216. self?.stuckPointCuttingView.stuckPointEndTime = CGFloat(CMTimeGetSeconds(self?.playeTimeRange.end ?? .zero))
  217. self?.stuckPointCuttingView.tatalTimeLabel.text = "\(Float64(CMTimeGetSeconds(self?.playeTimeRange.end ?? .zero) - CMTimeGetSeconds(self?.playeTimeRange.start ?? .zero)).formatDurationToHMS())"
  218. }
  219. }
  220. }
  221. }
  222. return stuckPointCuttingView
  223. }()
  224. /// 卡点模式标题
  225. lazy var pointEditRemindLab: UILabel = {
  226. let pointEditRemindLab = UILabel()
  227. pointEditRemindLab.backgroundColor = .clear
  228. pointEditRemindLab.textAlignment = .left
  229. pointEditRemindLab.font = UIFont.boldSystemFont(ofSize: 14)
  230. pointEditRemindLab.textColor = BFConfig.shared.styleTitleColor
  231. pointEditRemindLab.text = "卡点模式"
  232. return pointEditRemindLab
  233. }()
  234. /// 卡点模式标题
  235. lazy var speedTitleLab: UILabel = {
  236. let speedTitleLab = UILabel()
  237. speedTitleLab.backgroundColor = .clear
  238. speedTitleLab.textAlignment = .left
  239. speedTitleLab.font = UIFont.boldSystemFont(ofSize: 14)
  240. speedTitleLab.textColor = BFConfig.shared.styleTitleColor
  241. speedTitleLab.text = "节奏变化"
  242. return speedTitleLab
  243. }()
  244. /// 卡点模式下方操作区背景
  245. lazy var pointEditBGView: UIView = {
  246. let pointEditBGView = UIView()
  247. pointEditBGView.backgroundColor = .clear
  248. return pointEditBGView
  249. }()
  250. /// 下方音乐编辑操作区背景
  251. lazy var musicEditBGView: PQSelecteMusicView = {
  252. let musicEditBGView = PQSelecteMusicView()
  253. musicEditBGView.backgroundColor = .clear
  254. musicEditBGView.isUserInteractionEnabled = true
  255. musicEditBGView.isHidden = true
  256. musicEditBGView.musicSearchBtn.addTarget(self, action: #selector(musicSearchBtnClick(sender:)), for: .touchUpInside)
  257. musicEditBGView.didSelectItemHandle = { [weak self] status in
  258. if status == .isSelected {
  259. if self?.playerView.status != .playing {
  260. self?.playerView.RenderViewOnclick()
  261. }
  262. } else {
  263. self?.playerView.pause()
  264. }
  265. }
  266. musicEditBGView.btnClickHandle = { [weak self] _, bgmData in
  267. // 使用音乐
  268. self?.userstuckPointMusic(musicData: bgmData as? PQVoiceModel)
  269. }
  270. return musicEditBGView
  271. }()
  272. // 卡点编辑 btn
  273. lazy var pointEditerBtn: UIButton = {
  274. let pointEdterBtn = UIButton(type: .custom)
  275. pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_n", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
  276. pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_h", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
  277. pointEdterBtn.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  278. pointEdterBtn.addTarget(self, action: #selector(pointEditerBtnClick(sender:)), for: .touchUpInside)
  279. pointEdterBtn.isSelected = true
  280. pointEdterBtn.adjustsImageWhenHighlighted = false
  281. return pointEdterBtn
  282. }()
  283. // 音乐编辑 btn
  284. lazy var musicEditerBtn: UIButton = {
  285. let musicEditerBtn = UIButton(type: .custom)
  286. musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_n", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
  287. musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_h", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
  288. musicEditerBtn.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  289. musicEditerBtn.addTarget(self, action: #selector(musicEditerBtnClick(sender:)), for: .touchUpInside)
  290. musicEditerBtn.adjustsImageWhenHighlighted = false
  291. return musicEditerBtn
  292. }()
  293. // 快慢速卡点模式 btn
  294. lazy var speedStuckBtn: UIButton = {
  295. let speedStuckBtn = UIButton(type: .custom)
  296. speedStuckBtn.addTarget(self, action: #selector(editModelClick1(sender:)), for: .touchUpInside)
  297. speedStuckBtn.setTitle("快慢速卡点", for: .normal)
  298. speedStuckBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .regular)
  299. jumpPointBtn.backgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  300. speedStuckBtn.setTitleColor(UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue), for: .selected)
  301. speedStuckBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#959595"), for: .normal)
  302. speedStuckBtn.addCorner(corner: 5)
  303. speedStuckBtn.imagePosition(at: .top, space: 8)
  304. speedStuckBtn.tag = 1
  305. speedStuckBtn.adjustsImageWhenHighlighted = false
  306. speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
  307. speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
  308. return speedStuckBtn
  309. }()
  310. // //
  311. // lazy var speedStuckBtnGif: UIImageView = {
  312. // let speedStuckBtnGif = UIImageView()
  313. // speedStuckBtnGif.kf.setImage(with: URL(fileURLWithPath: currentBundlePath()!.path(forResource: "speedstuck_h", ofType: "gif")!))
  314. // speedStuckBtnGif.isHidden = true
  315. // return speedStuckBtnGif
  316. //
  317. // }()
  318. // 跳转卡点模式 btn
  319. lazy var jumpPointBtn: UIButton = {
  320. let jumpPointBtn = UIButton(type: .custom)
  321. jumpPointBtn.setTitle("跳跃卡点", for: .normal)
  322. jumpPointBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .regular)
  323. jumpPointBtn.backgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  324. jumpPointBtn.setTitleColor(UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue), for: .selected)
  325. jumpPointBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#959595"), for: .normal)
  326. jumpPointBtn.imagePosition(at: .top, space: 8)
  327. jumpPointBtn.addCorner(corner: 5)
  328. jumpPointBtn.tag = 2
  329. jumpPointBtn.addTarget(self, action: #selector(editModelClick1(sender:)), for: .touchUpInside)
  330. jumpPointBtn.adjustsImageWhenHighlighted = false
  331. jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
  332. jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
  333. return jumpPointBtn
  334. }()
  335. // lazy var jumpPointBtnGif: UIImageView = {
  336. // let jumpPointBtnGif = UIImageView()
  337. // jumpPointBtnGif.kf.setImage(with: URL(fileURLWithPath: currentBundlePath()!.path(forResource: "jumpPoint_n", ofType: "gif")!))
  338. // jumpPointBtnGif.isHidden = true
  339. // return jumpPointBtnGif
  340. //
  341. // }()
  342. // 仅配乐模式 btn
  343. lazy var onlyMusicBtn: UIButton = {
  344. let onlyMusicBtn = UIButton(type: .custom)
  345. onlyMusicBtn.setTitle("仅配乐", for: .normal)
  346. onlyMusicBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .regular)
  347. onlyMusicBtn.backgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  348. onlyMusicBtn.setTitleColor(UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue), for: .selected)
  349. onlyMusicBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#959595"), for: .normal)
  350. onlyMusicBtn.addCorner(corner: 5)
  351. onlyMusicBtn.tag = 3
  352. onlyMusicBtn.addTarget(self, action: #selector(editModelClick1(sender:)), for: .touchUpInside)
  353. onlyMusicBtn.adjustsImageWhenHighlighted = false
  354. onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
  355. onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
  356. return onlyMusicBtn
  357. }()
  358. // 操作面板上的分割线
  359. lazy var optionlineView: UIView = {
  360. let optionlineView = UIView()
  361. optionlineView.backgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  362. return optionlineView
  363. }()
  364. // 固定速度 UI
  365. lazy var speedSettingView: PQSpeedSettingView = {
  366. let speedSetView = PQSpeedSettingView()
  367. speedSetView.backgroundColor = .clear
  368. speedSetView.selectSpeedCallBack = { [weak self] maxSpeed, minSpeed, selectIndex, isSettingPlayer in
  369. BFLog(message: "固定maxSpeed is\(maxSpeed) minSpeed \(minSpeed)")
  370. self?.musicEditBGView.pausePlayer()
  371. if maxSpeed == -1.0, minSpeed == -1.0 {
  372. self?.customSpeedSettingView.isHidden = false
  373. self?.customSpeedSettingView.viewType = self?.speedSettingView.viewType ?? 2
  374. } else {
  375. if maxSpeed != 0.0 {
  376. // 更新最后一次选择的位置恢复时使用
  377. if self?.speedSettingView.viewType == 1 {
  378. self?.lastSpeedSelectIndex = selectIndex
  379. self?.modelSpeed_maxSpeed = maxSpeed
  380. self?.modelSpeed_minSpeed = minSpeed
  381. } else if self?.speedSettingView.viewType == 2 {
  382. self?.lastJumpSpeedSelectIndex = selectIndex
  383. self?.modelPoint_speed = maxSpeed
  384. } else {
  385. self?.lastCyclesSelectIndex = selectIndex
  386. }
  387. } else {
  388. BFLog(message: "设置速度无效")
  389. }
  390. }
  391. if isSettingPlayer {
  392. self?.settingPlayerView()
  393. }
  394. }
  395. return speedSetView
  396. }()
  397. // 自定义速度
  398. lazy var customSpeedSettingView: PQCustomSpeedSettingView = {
  399. let customSpeedSetView = PQCustomSpeedSettingView(frame: CGRect(x: 0, y: cScreenHeigth - 354, width: cScreenWidth, height: 354))
  400. customSpeedSetView.isHidden = true
  401. customSpeedSetView.selectSpeedCallBack = { [weak self, weak customSpeedSetView] maxSpeed, minSpeed, isJumpSpeedModel, isCancle in
  402. if !isCancle {
  403. BFLog(message: "自定义速度maxSpeed is\(maxSpeed) minSpeed \(minSpeed) \(isJumpSpeedModel)")
  404. self?.musicEditBGView.pausePlayer()
  405. // 自定定义的更新一下最后的选择位置
  406. if self?.speedSettingView.viewType == 1 {
  407. self?.lastSpeedSelectIndex = -1
  408. self?.modelSpeed_maxSpeed = maxSpeed
  409. self?.modelSpeed_minSpeed = minSpeed
  410. } else if self?.speedSettingView.viewType == 2 {
  411. self?.lastJumpSpeedSelectIndex = -1
  412. self?.modelPoint_speed = maxSpeed
  413. } else {
  414. self?.lastCyclesSelectIndex = Int(maxSpeed - 1)
  415. }
  416. self?.settingPlayerView()
  417. // 确认后 选中自定义
  418. self?.speedSettingView.selectCustom()
  419. } else {
  420. // 取消后恢复上一次选择的位置
  421. if self?.speedSettingView.viewType == 1 {
  422. self?.speedSettingView.setSelectItem(index: self?.lastSpeedSelectIndex ?? 0, isSettingPlayer: false)
  423. } else if self?.speedSettingView.viewType == 2 {
  424. self?.speedSettingView.setSelectItem(index: self?.lastJumpSpeedSelectIndex ?? 0, isSettingPlayer: false)
  425. } else {
  426. self?.speedSettingView.setSelectItem(index: self?.lastCyclesSelectIndex ?? 0, isSettingPlayer: false)
  427. }
  428. customSpeedSetView?.isHidden = true
  429. }
  430. }
  431. return customSpeedSetView
  432. }()
  433. /// 音乐标题
  434. lazy var musicNameView: UIView = {
  435. let musicNameView = UIView()
  436. musicNameView.addSubview(musicNameLab)
  437. let nameWidth: CGFloat = musicNameLab.frame.width + (25 + cDefaultMargin * 3)
  438. musicNameView.frame = CGRect(x: (view.frame.width - nameWidth) / 2, y: cDevice_iPhoneStatusBarHei + (cDevice_iPhoneNavBarHei - cDefaultMargin * 3) / 2, width: nameWidth, height: cDefaultMargin * 3)
  439. // musicNameView.backgroundColor = UIColor.hexColor(hexadecimal: "#333333")
  440. musicNameView.addCorner(corner: musicNameView.frame.height / 2)
  441. let musicImageView = UIImageView()
  442. musicImageView.tintColor = BFConfig.shared.styleTitleColor
  443. musicImageView.image = UIImage.moduleImage(named: "stuckPoint_reCreate_music", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate)
  444. musicImageView.frame = CGRect(x: musicNameView.frame.height / 2 - 5, y: (musicNameView.frame.height - 22) / 2, width: 22, height: 22)
  445. musicNameView.addSubview(musicImageView)
  446. musicNameLab.frame.origin.x = musicImageView.frame.maxX + 5
  447. return musicNameView
  448. }()
  449. /// 音乐歌曲名称
  450. lazy var musicNameLab: LMJHorizontalScrollText = {
  451. 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
  452. let musicNameLab = LMJHorizontalScrollText(frame: CGRect(x: 0, y: 0, width: nameWidth < cDefaultMargin * 4 ? cDefaultMargin * 4 : nameWidth, height: cDefaultMargin * 3))
  453. musicNameLab.textColor = BFConfig.shared.styleTitleColor
  454. musicNameLab.textFont = UIFont.systemFont(ofSize: 13)
  455. musicNameLab.speed = 0.03
  456. musicNameLab.moveDirection = LMJTextScrollMoveLeft
  457. musicNameLab.moveMode = LMJTextScrollContinuous
  458. if nameWidth < cDefaultMargin * 4 {
  459. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  460. } else {
  461. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  462. }
  463. return musicNameLab
  464. }()
  465. /// 同步进度显示
  466. lazy var synchroMarskView: PQStuckPointLoadingView = {
  467. var synchroMarskView = PQStuckPointLoadingView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  468. synchroMarskView.cancelHandle = { [weak self] _ in
  469. self?.navigationController?.popViewController(animated: true)
  470. }
  471. return synchroMarskView
  472. }()
  473. @objc func willEnterForeground() {
  474. BFLog(message: "进入到前台")
  475. if navigationController?.topViewController == self {
  476. if projectModel.sData!.sections.count > 0 {
  477. settingPlayerView()
  478. } else {
  479. prepareMeta()
  480. }
  481. }
  482. }
  483. @objc func enterBackground() {
  484. BFLog(message: "进入到后台")
  485. // 取消导出
  486. if navigationController?.topViewController == self {
  487. playerView.pause()
  488. }
  489. }
  490. override func backBtnClick() {
  491. super.backBtnClick()
  492. // playerView.pause()
  493. // 点击上报:返回按钮
  494. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_previewSyncedUp, commonParams: commonParams())
  495. }
  496. // 使用选择音乐 调用情况:1,操作面板直接选择 ,2 搜索界面点击使用
  497. func userstuckPointMusic(musicData: PQVoiceModel?) {
  498. // 1,音乐面板点击了使用
  499. stuckPointMusicData = musicData
  500. // 2,同步最新音乐数据
  501. synchroMusicInfoData(resetSelectIndex: false)
  502. // 3,更新音乐标题UI
  503. 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
  504. if nameWidth < cDefaultMargin * 4 {
  505. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  506. } else {
  507. musicNameLab.text = " \(stuckPointMusicData?.musicName ?? "") "
  508. }
  509. // 更新一下节奏的 UI
  510. sustomSwitchView.selectOneBtn(Index: stuckPointMusicData?.speed ?? 2)
  511. }
  512. // 点击搜索音乐
  513. @objc func musicSearchBtnClick(sender _: UIButton) {
  514. let searchVC = PQEditMusicSearchController()
  515. searchVC.btnClickHandle = { [weak self] _, bgmData in
  516. // 使用音乐
  517. BFLog(message: "搜索音乐点击了使用")
  518. self?.musicEditBGView.insertSearchMusic(model: bgmData as! PQVoiceModel)
  519. self?.userstuckPointMusic(musicData: bgmData as? PQVoiceModel)
  520. }
  521. let navigationController: UINavigationController = UINavigationController(rootViewController: searchVC)
  522. navigationController.modalPresentationStyle = .fullScreen
  523. present(navigationController, animated: true, completion: nil)
  524. }
  525. // 卡点编辑
  526. @objc func pointEditerBtnClick(sender: UIButton) {
  527. if sender.isSelected { return }
  528. sender.isSelected = !sender.isSelected
  529. musicEditerBtn.isSelected = false
  530. pointEditBGView.isHidden = false
  531. musicEditBGView.isHidden = true
  532. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_videoTab, pageSource: .sp_shanyinApp_main, commonParams: commonParams())
  533. }
  534. // 音乐编辑
  535. @objc func musicEditerBtnClick(sender: UIButton) {
  536. if sender.isSelected { return }
  537. sender.isSelected = !sender.isSelected
  538. pointEditerBtn.isSelected = false
  539. pointEditBGView.isHidden = true
  540. musicEditBGView.showData()
  541. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_musicTab, pageSource: .sp_shanyinApp_main, commonParams: commonParams())
  542. }
  543. @objc func editModelClick1(sender: UIButton) {
  544. editModelClick(sender: sender)
  545. }
  546. // 三种模式修改
  547. @objc func editModelClick(sender: UIButton, reportLog: Bool = true) {
  548. // if sender.isSelected {
  549. // BFLog(message: "已经是选中状态")
  550. // return “”
  551. // }
  552. musicEditBGView.pausePlayer()
  553. sharedImageProcessingContext.framebufferCache.purgeAllUnassignedFramebuffers()
  554. if sender == jumpPointBtn, selectedTotalDuration < 6, selectedDataCount != selectedImageDataCount, reCreateVideoData == nil {
  555. cShowHUB(superView: view, msg: "素材时长需要大于6秒才\n可选择“跳跃卡点”模式")
  556. return
  557. }
  558. lastEditModelBtn?.isSelected = false
  559. // 设置取消选中的背景色
  560. lastEditModelBtn?.backgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  561. sender.isSelected = !sender.isSelected
  562. lastEditModelBtn = sender
  563. // 设置选中的背景色
  564. let styleColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  565. lastEditModelBtn?.backgroundColor = UIColor(red: styleColor.rgbaf[0], green: styleColor.rgbaf[1], blue: styleColor.rgbaf[2], alpha: 0.15)
  566. BFLog(message: "sender tag is \(sender.tag)")
  567. // 2素材全是图片的时候三个模式都显示循环设置 UI
  568. if selectedDataCount == selectedImageDataCount {
  569. speedSettingView.viewType = 3
  570. customSpeedSettingView.viewType = speedSettingView.viewType
  571. speedSettingView.snp.remakeConstraints { make in
  572. make.left.equalToSuperview().offset(16)
  573. make.right.equalToSuperview()
  574. make.top.equalTo(onlyMusicBtn.snp.bottom).offset(10)
  575. make.height.equalTo(30)
  576. }
  577. speedSettingView.isHidden = false
  578. speedTitleLab.isHidden = false
  579. sustomSwitchView.isHidden = false
  580. if lastCyclesSelectIndex != -1 {
  581. speedSettingView.setSelectItem(index: lastCyclesSelectIndex, isSettingPlayer: false)
  582. }
  583. } else {
  584. // 1 ui 调整
  585. if sender.tag == 1 || sender.tag == 2 {
  586. speedSettingView.viewType = sender.tag
  587. customSpeedSettingView.viewType = speedSettingView.viewType
  588. speedSettingView.snp.remakeConstraints { make in
  589. make.left.equalToSuperview().offset(16)
  590. make.right.equalToSuperview()
  591. make.top.equalTo(onlyMusicBtn.snp.bottom).offset(10)
  592. make.height.equalTo(sender.tag == 1 ? 44 : 30)
  593. }
  594. speedSettingView.isHidden = false
  595. speedTitleLab.isHidden = false
  596. sustomSwitchView.isHidden = false
  597. if sender.tag == 1 { // 快慢速
  598. speedSettingView.setSelectItem(index: lastSpeedSelectIndex, isSettingPlayer: false, setDisable: (selectedTotalDuration < 6 && selectedDataCount != selectedImageDataCount) ? true : false)
  599. } else if sender.tag == 2 { // 跳跃卡点
  600. speedSettingView.setSelectItem(index: lastJumpSpeedSelectIndex, isSettingPlayer: false)
  601. }
  602. } else {
  603. speedTitleLab.isHidden = true
  604. speedSettingView.isHidden = true
  605. sustomSwitchView.isHidden = true
  606. }
  607. }
  608. // 3 设置 btn 不同显示状态
  609. var speedStuckBtnGifName = ""
  610. var jumpPointBtnGifName = ""
  611. if sender.tag == 1 { // 快慢速
  612. speedStuckBtnGifName = "speedstuck_h_pq"
  613. jumpPointBtnGifName = "jumpPoint_n_pq"
  614. currentCreateStickersModel = .createStickersModelSpeed
  615. if reportLog {
  616. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectPatternSpeed, pageSource: .sp_shanyinApp_main, commonParams: commonParams())
  617. }
  618. } else if sender.tag == 2 { // 跳跃卡点
  619. speedStuckBtnGifName = "speedstuck_n_pq"
  620. jumpPointBtnGifName = "jumpPoint_h_pq"
  621. currentCreateStickersModel = .createStickersModelPoint
  622. if reportLog {
  623. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectPatternMusicVideo, pageSource: .sp_shanyinApp_main, commonParams: commonParams())
  624. }
  625. } else if sender.tag == 3 { // 仅音乐
  626. speedStuckBtnGifName = "speedstuck_n_pq"
  627. jumpPointBtnGifName = "jumpPoint_n_pq"
  628. currentCreateStickersModel = .createStickersModelOnlyMusic
  629. if reportLog {
  630. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_selectPatternBgm, pageSource: .sp_shanyinApp_main, commonParams: commonParams())
  631. }
  632. }
  633. settingPlayerView()
  634. }
  635. override func viewWillAppear(_ animated: Bool) {
  636. super.viewWillAppear(animated)
  637. lineView?.isHidden = true
  638. DispatchQueue.main.async {
  639. UIApplication.shared.isIdleTimerDisabled = true
  640. }
  641. musicNameLab.move()
  642. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  643. PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  644. // 从分享返回后从重初始化播放器
  645. if isClickNextBtn {
  646. isClickNextBtn = false
  647. settingPlayerView()
  648. }
  649. }
  650. override func viewWillDisappear(_ animated: Bool) {
  651. super.viewWillDisappear(animated)
  652. DispatchQueue.main.async {
  653. UIApplication.shared.isIdleTimerDisabled = false
  654. }
  655. musicNameLab.stop()
  656. playerView.stop()
  657. musicEditBGView.pausePlayer()
  658. PQNotification.removeObserver(self)
  659. synchroMarskView.removeMarskView()
  660. }
  661. override func viewDidLoad() {
  662. super.viewDidLoad()
  663. leftButton(image: nil, tintColor: BFConfig.shared.styleTitleColor)
  664. navHeadImageView?.addSubview(nextBtn)
  665. navHeadImageView?.addSubview(musicNameView)
  666. // 添加子视图
  667. addSubViews()
  668. prepareMeta()
  669. // 曝光上报:预览页面曝光上报
  670. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_previewSyncedUp, pageSource: .sp_stuck_previewSyncedUp, commonParams: commonParams())
  671. // 从选择的素材中 第一个素材设置封面
  672. if selectedPhotoData != nil, selectedPhotoData!.count > 0 {
  673. let photo = selectedPhotoData!.first!
  674. let option = PHImageRequestOptions()
  675. option.isNetworkAccessAllowed = true // 允许下载iCloud的图片
  676. option.resizeMode = .none
  677. option.deliveryMode = .highQualityFormat
  678. PHImageManager.default().requestImage(for: photo, targetSize: CGSize(width: 1920, height: 1920), contentMode: .aspectFill, options: option) { [weak self] image, _ in
  679. // image就是图片
  680. if image != nil {
  681. self?.firstFrameImage = image
  682. }
  683. }
  684. }
  685. }
  686. override func viewDidLayoutSubviews() {
  687. super.viewDidLayoutSubviews()
  688. playerView.resetCanvasFrame(frame: coculationPlayViewRect())
  689. }
  690. func prepareMeta() {
  691. // 导出相册视频
  692. exportPhotoData()
  693. // 同步音乐数据
  694. synchroMusicInfoData()
  695. // 插入选择的音乐信息
  696. musicEditBGView.firstInsertVoiceModel = stuckPointMusicData!
  697. }
  698. /// 添加子视图
  699. /// - Returns: <#description#>
  700. func addSubViews() {
  701. if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 {
  702. return
  703. }
  704. view.addSubview(playerView)
  705. view.addSubview(pointEditBGView)
  706. view.addSubview(musicEditBGView)
  707. view.addSubview(optionlineView)
  708. view.addSubview(pointEditerBtn)
  709. view.addSubview(musicEditerBtn)
  710. view.addSubview(customSpeedSettingView)
  711. // 卡点
  712. pointEditBGView.addSubview(pointEditRemindLab)
  713. pointEditBGView.addSubview(speedTitleLab)
  714. pointEditBGView.addSubview(speedStuckBtn)
  715. pointEditBGView.addSubview(jumpPointBtn)
  716. pointEditBGView.addSubview(onlyMusicBtn)
  717. pointEditBGView.addSubview(speedSettingView)
  718. pointEditBGView.addSubview(sustomSwitchView)
  719. // 音乐
  720. musicEditBGView.addSubview(stuckPointCuttingView)
  721. pointEditerBtn.snp.makeConstraints { make in
  722. make.left.equalToSuperview().offset(100)
  723. make.bottom.equalToSuperview().offset(-(14 + cAKSafeAreaHeight))
  724. make.height.equalTo(24)
  725. make.width.equalTo(24)
  726. }
  727. musicEditerBtn.snp.makeConstraints { make in
  728. make.right.equalToSuperview().offset(-100)
  729. make.top.equalTo(pointEditerBtn.snp.top)
  730. make.height.equalTo(24)
  731. make.width.equalTo(24)
  732. }
  733. pointEditBGView.snp.makeConstraints { make in
  734. make.left.right.equalTo(view)
  735. make.bottom.equalTo(pointEditerBtn.snp.top).offset(-7)
  736. make.height.equalTo(290)
  737. }
  738. musicEditBGView.snp.makeConstraints { make in
  739. make.left.right.equalToSuperview()
  740. make.bottom.equalTo(pointEditerBtn.snp.top).offset(-7)
  741. make.height.equalTo(290)
  742. }
  743. optionlineView.snp.makeConstraints { make in
  744. make.left.right.equalToSuperview()
  745. make.bottom.equalTo(pointEditBGView.snp.bottom).offset(-1)
  746. make.height.equalTo(1)
  747. }
  748. stuckPointCuttingView.snp.makeConstraints { make in
  749. make.left.right.equalToSuperview()
  750. make.bottom.equalTo(musicEditBGView.snp.bottom).offset(-1)
  751. make.height.equalTo(85)
  752. }
  753. pointEditRemindLab.snp.makeConstraints { make in
  754. make.left.equalToSuperview().offset(16)
  755. make.top.equalToSuperview().offset(16)
  756. make.height.equalTo(20)
  757. make.width.equalTo(80)
  758. }
  759. speedStuckBtn.snp.makeConstraints { make in
  760. make.left.equalToSuperview().offset(16)
  761. make.top.equalTo(pointEditRemindLab.snp.bottom).offset(8)
  762. make.height.equalTo(80)
  763. make.width.equalTo(80)
  764. }
  765. jumpPointBtn.snp.makeConstraints { make in
  766. make.left.equalTo(speedStuckBtn.snp.right).offset(10)
  767. make.top.equalTo(speedStuckBtn.snp.top)
  768. make.height.equalTo(80)
  769. make.width.equalTo(80)
  770. }
  771. onlyMusicBtn.snp.makeConstraints { make in
  772. make.left.equalTo(jumpPointBtn.snp.right).offset(10)
  773. make.top.equalTo(speedStuckBtn.snp.top)
  774. make.height.equalTo(80)
  775. make.width.equalTo(64)
  776. }
  777. // 重新设置三个模式 btn 图片和title的位置
  778. speedStuckBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(speedStuckBtn.imageView?.frame.size.width ?? 0), bottom: -(speedStuckBtn.imageView?.frame.size.height ?? 0), right: 0)
  779. speedStuckBtn.imageEdgeInsets = UIEdgeInsets(top: -(speedStuckBtn.titleLabel?.intrinsicContentSize.height ?? 0), left: 0, bottom: 0, right: -(speedStuckBtn.titleLabel?.intrinsicContentSize.width ?? 0))
  780. jumpPointBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(jumpPointBtn.imageView?.frame.size.width ?? 0), bottom: -(jumpPointBtn.imageView?.frame.size.height ?? 0), right: 0)
  781. jumpPointBtn.imageEdgeInsets = UIEdgeInsets(top: -(jumpPointBtn.titleLabel?.intrinsicContentSize.height ?? 0), left: 0, bottom: 0, right: -(jumpPointBtn.titleLabel?.intrinsicContentSize.width ?? 0))
  782. onlyMusicBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(onlyMusicBtn.imageView?.frame.size.width ?? 0), bottom: -(onlyMusicBtn.imageView?.frame.size.height ?? 0), right: 0)
  783. onlyMusicBtn.imageEdgeInsets = UIEdgeInsets(top: -(onlyMusicBtn.titleLabel?.intrinsicContentSize.height ?? 0), left: 0, bottom: 0, right: -(onlyMusicBtn.titleLabel?.intrinsicContentSize.width ?? 0))
  784. speedSettingView.snp.makeConstraints { make in
  785. make.left.equalToSuperview().offset(16)
  786. make.right.equalToSuperview()
  787. make.top.equalTo(onlyMusicBtn.snp.bottom).offset(10)
  788. make.height.equalTo(44)
  789. }
  790. speedTitleLab.snp.makeConstraints { make in
  791. make.left.equalToSuperview().offset(16)
  792. make.top.equalToSuperview().offset(190)
  793. make.height.equalTo(20)
  794. make.width.equalTo(80)
  795. }
  796. sustomSwitchView.snp.makeConstraints { make in
  797. make.left.equalToSuperview().offset(16)
  798. make.top.equalTo(speedTitleLab.snp.bottom).offset(8)
  799. make.height.equalTo(30)
  800. make.width.equalTo(180)
  801. }
  802. }
  803. @objc func nextBtnClick(sender _: UIButton) {
  804. BFLog(message: "去发布")
  805. isClickNextBtn = true
  806. playerView.pause()
  807. // 使用深 copy
  808. let json = projectModel.toJSONString(prettyPrint: false)
  809. if json == nil {
  810. BFLog(message: "数据转换有问题 跳转")
  811. return
  812. }
  813. let tempModel: PQEditProjectModel? = Mapper<PQEditProjectModel>().map(JSONString: json!)
  814. let materialVC: PQStuckPointMaterialController? = navigationController?.viewControllers.first(where: { (vc) -> Bool in
  815. vc is PQStuckPointMaterialController
  816. }) as? PQStuckPointMaterialController
  817. if materialVC != nil, materialVC?.isToPublicHandle != nil {
  818. materialVC?.isToPublicHandle!(isReCreate, selectedTotalDuration, selectedDataCount, selectedImageDataCount, mStickers, stuckPointMusicData, tempModel, currentCreateStickersModel, modelSpeed_maxSpeed, modelSpeed_minSpeed, Float(finallyStuckPoints.last ?? 0) - Float(finallyStuckPoints.first ?? 0), getClipAudioRange(), playeTimeRange)
  819. } else {
  820. if finallyStuckPoints.count == 0 {
  821. cShowHUB(superView: nil, msg: "无卡点信息,返回重新选择音乐")
  822. return
  823. }
  824. let videoExporter = PQStuckPointPublicController()
  825. videoExporter.rhythmMode = currentCreateStickersModel
  826. videoExporter.syncedUpVideoSpeedMin = modelSpeed_maxSpeed
  827. videoExporter.syncedUpVideoSpeedMax = modelSpeed_minSpeed
  828. videoExporter.isReCreate = isReCreate
  829. videoExporter.selectedTotalDuration = selectedTotalDuration
  830. videoExporter.selectedDataCount = selectedDataCount
  831. videoExporter.selectedImageDataCount = selectedImageDataCount
  832. videoExporter.finallyUserAudioTime = Float(finallyStuckPoints.last ?? 0) - Float(finallyStuckPoints.first ?? 0)
  833. videoExporter.clipAudioRange = getClipAudioRange()
  834. videoExporter.playeTimeRange = playeTimeRange
  835. videoExporter.mStickers = mStickers
  836. videoExporter.audioMixModel = stuckPointMusicData
  837. videoExporter.coverImage = selectedMetarialData?.first?.coverImageUI ?? UIImage()
  838. videoExporter.editProjectModel = tempModel
  839. navigationController?.pushViewController(videoExporter, animated: true)
  840. }
  841. // 点击上报:去合成
  842. BFEventTrackAdaptor.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], commonParams: commonParams())
  843. }
  844. override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  845. super.touchesBegan(touches, with: event)
  846. if touches.first?.view != customSpeedSettingView {
  847. if !customSpeedSettingView.isHidden {
  848. customSpeedSettingView.isHidden = true
  849. }
  850. }
  851. }
  852. // MARK: - 播放器相关操作
  853. /// seek 播放器
  854. /// - Parameter playeTimeRange: 开始和结束时间
  855. func seekPlayer(playeTimeRange: CMTimeRange) {
  856. playerView.setEnableSeek(isSeek: true)
  857. playerView.play(pauseFirstFrame: false, playeTimeRange: playeTimeRange)
  858. }
  859. /// 通过传入的 selectedPhotoData 、 stuckPointMusicData 创建 projectModel 模型 后面都使用 projectModel 参数
  860. func createPorjectData() {
  861. // 1,添加选择的视觉素材
  862. if projectModel.sData?.sections.count == 0 {
  863. let section: PQEditSectionModel = PQEditSectionModel()
  864. selectedMetarialData?.forEach { model in
  865. // let json = model.toJSONString(prettyPrint: false)
  866. // if json == nil {
  867. // BFLog(message: "数据转换有问题 跳转")
  868. // return
  869. // }
  870. // let tempModel: PQEditVisionTrackMaterialsModel = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: json!)!
  871. section.sectionTimeline?.visionTrack?.visionTrackMaterials.append(model)
  872. }
  873. projectModel.sData?.sections.append(section)
  874. }
  875. // 2,添加背景音乐
  876. projectModel.sData?.addBGM(audioMix: stuckPointMusicData!)
  877. }
  878. // 计算拼接音乐的开始和结束点
  879. func getClipAudioRange() -> CMTimeRange {
  880. // 设置音乐的拼接范围,开始:推荐的卡点 结束:点到的倒数第二位
  881. if stuckPointMusicData!.rhythmSdata.count > 0, stuckPointMusicData?.rhythmSdata[0].pointTimes.count ?? 0 > 2 {
  882. let lastSecondPoint = Float(stuckPointMusicData!.rhythmSdata[0].pointTimes[stuckPointMusicData!.rhythmSdata[0].pointTimes.count - 2]) / Float(BASE_FILTER_TIMESCALE)
  883. let clipAudioRange =
  884. CMTimeRange(start: CMTime(value: CMTimeValue(Float(stuckPointMusicData?.startTime ?? 0) * Float(BASE_FILTER_TIMESCALE)), timescale: BASE_FILTER_TIMESCALE), end: CMTime(value: CMTimeValue(Float(lastSecondPoint) * Float(BASE_FILTER_TIMESCALE)), timescale: BASE_FILTER_TIMESCALE))
  885. return clipAudioRange
  886. }
  887. return CMTimeRange(start: CMTime(value: 0, timescale: 1), duration: CMTime(value: 1, timescale: 1))
  888. }
  889. // 设置播放器
  890. func coculationPlayViewRect() -> CGRect {
  891. let playerShowHeight = pointEditBGView.frame.minY - (navHeadImageView?.frame.maxY ?? 0)
  892. var showRect: CGRect = CGRect(x: (cScreenWidth - playerShowHeight) / 2, y: 0, width: playerShowHeight, height: playerShowHeight)
  893. if firstFrameImage != nil {
  894. let w = firstFrameImage!.size.width
  895. let h = firstFrameImage!.size.height
  896. let ratioMaterial: Float = Float(w / max(h, 1))
  897. if ratioMaterial > 1 {
  898. // 横屏
  899. let tempPlayerHeight = min(cScreenWidth * CGFloat(h / w), playerShowHeight)
  900. showRect = CGRect(x: (cScreenWidth - tempPlayerHeight * CGFloat(ratioMaterial)) / 2, y: (playerShowHeight - tempPlayerHeight) / 2, width: tempPlayerHeight * CGFloat(ratioMaterial), height: tempPlayerHeight)
  901. } else {
  902. // 竖屏
  903. let playerViewWidth = (CGFloat(w) / CGFloat(h)) * playerShowHeight
  904. showRect = CGRect(x: (cScreenWidth - playerViewWidth) / 2, y: 0, width: playerViewWidth, height: playerShowHeight)
  905. }
  906. }
  907. if showRect.size.width == showRect.size.height {
  908. if cScreenWidth > playerShowHeight {
  909. showRect.origin.x = (cScreenWidth - playerShowHeight) / 2
  910. showRect.size.width = playerShowHeight
  911. showRect.size.height = playerShowHeight
  912. } else {
  913. showRect.origin.x = 0
  914. showRect.size.width = cScreenWidth
  915. showRect.size.height = cScreenWidth
  916. }
  917. }
  918. if showRect.size.width != 0, showRect.size.height != 0 {
  919. if Int(showRect.height) % 2 != 0 {
  920. showRect.size.height = showRect.size.height + 1.0
  921. }
  922. if Int(showRect.width) % 2 != 0 {
  923. showRect.size.width = showRect.size.width + 1.0
  924. }
  925. }
  926. showRect.origin.y = (playerShowHeight - showRect.size.height) / 2.0 + (navHeadImageView?.frame.maxY ?? 0)
  927. return showRect
  928. }
  929. func settingPlayerView() {
  930. stuckPointCuttingView.resetDefaultsColor()
  931. synchroMarskView.show()
  932. // 1,设置播放器的显示区域 和画布大小
  933. // - 按第一个素材尺寸自适应
  934. playerView.pause()
  935. var firstModel: PQEditVisionTrackMaterialsModel?
  936. for part in projectModel.sData!.sections {
  937. if part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0 > 0 {
  938. firstModel = part.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first
  939. break
  940. }
  941. }
  942. var videoSize: CGSize = CGSize(width: Int(firstModel?.width ?? 0), height: Int(firstModel?.height ?? 0))
  943. var minSlider = min(videoSize.width, videoSize.height)
  944. var maxSlider = max(videoSize.width, videoSize.height)
  945. let ration = 1080 / minSlider
  946. minSlider = minSlider * ration
  947. maxSlider = maxSlider * ration
  948. if videoSize.width > videoSize.height { // 宽屏
  949. videoSize = CGSize(width: maxSlider, height: minSlider)
  950. } else {
  951. videoSize = CGSize(width: minSlider, height: maxSlider)
  952. }
  953. if videoSize.width.isNaN || videoSize.height.isNaN {
  954. BFLog(1, message: "宽高无效NaN")
  955. return
  956. }
  957. let maxValue = max(videoSize.width, videoSize.height)
  958. if maxValue > 1920 {
  959. let maxRation = 1920 / maxValue
  960. videoSize = CGSize(width: videoSize.width * CGFloat(maxRation), height: videoSize.height * CGFloat(maxRation))
  961. BFLog(message: "最长边已经超过 1920 要等比缩小 缩放后\(videoSize)")
  962. }
  963. if (Int(videoSize.width) % 2) != 0 {
  964. videoSize.width = videoSize.width - 1
  965. }
  966. if (Int(videoSize.height) % 2) != 0 {
  967. videoSize.height = videoSize.height - 1
  968. }
  969. projectModel.sData?.videoMetaData?.videoWidth = Int(videoSize.width)
  970. projectModel.sData?.videoMetaData?.videoHeight = Int(videoSize.height)
  971. let beginTime = Date()
  972. dealParameter(model: currentCreateStickersModel)
  973. // 更新裁剪时间条的的ui数据
  974. stuckPointCuttingView.videoDuration = max(CGFloat(finallyUserAudioTime), CGFloat(finallyStuckPoints.last!))
  975. let counn = (stuckPointMusicData?.rhythmSdata[0].pointTimes.count)! - 2
  976. let suggestRhythmStartTime = CGFloat(stuckPointMusicData?.suggestRhythmStartTime ?? 0)
  977. let suggestRhythmEndTime = max(suggestRhythmStartTime, CGFloat(stuckPointMusicData?.rhythmSdata[0].pointTimes[max(counn, 0)] ?? 0) / CGFloat(BASE_FILTER_TIMESCALE))
  978. stuckPointCuttingView.updateEndTime(
  979. startTime: CGFloat(CMTimeGetSeconds(playeTimeRange.start)),
  980. endTime: CGFloat(CMTimeGetSeconds(playeTimeRange.end)),
  981. suggestRhythmStartTime: suggestRhythmStartTime,
  982. suggestRhythmEndTime: suggestRhythmEndTime
  983. )
  984. // 2,创建滤镜
  985. DispatchQueue.global().async {
  986. 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)), model: self.currentCreateStickersModel)
  987. DispatchQueue.main.async { // 串行、异步
  988. self.playerView.mStickers = self.mStickers
  989. BFLog(message: "createStickers tiskskskskme \(Date().timeIntervalSince(beginTime))")
  990. // 3,设置音频
  991. let audioPath = self.stuckPointMusicData?.localPath ?? ""
  992. BFLog(message: "初始化音频播放器的音频地址为:\(audioPath)")
  993. self.playerView.stop()
  994. // 这里的测试这个音乐播放有问题
  995. // self.playerView.updateAsset(URL(fileURLWithPath: "63930549652d74e477141e3b79c8d29a9ef8af81625053214516.mp3", relativeTo:Bundle.main.resourceURL!), videoComposition: nil, audioMixModel: nil)
  996. self.playerView.updateAsset(URL(fileURLWithPath: documensDirectory + audioPath), videoComposition: nil, audioMixModel: nil, originMusicDuration: self.finallyUserAudioTime, clipAudioRange: self.getClipAudioRange(), isUsedAVPlayer: true)
  997. // 4, 设置播放器的输出画布大小
  998. self.playerView.movie?.mShowVidoSize = CGSize(width: CGFloat(self.projectModel.sData?.videoMetaData?.videoWidth ?? 0), height: CGFloat(self.projectModel.sData?.videoMetaData?.videoHeight ?? 0))
  999. // 传给movie 音频的原始卡点
  1000. let fir = Int64(self.stuckPointsTemp.first ?? 0)
  1001. let endd = Int64(self.stuckPointsTemp.last ?? 0)
  1002. self.playerView.movie?.orginStuckRange = CMTimeRange(start: CMTime(value: Int64(fir) * Int64(BASE_FILTER_TIMESCALE), timescale: BASE_FILTER_TIMESCALE), end: CMTime(value: Int64(endd) * Int64(BASE_FILTER_TIMESCALE), timescale: BASE_FILTER_TIMESCALE))
  1003. // 5,开始播放
  1004. self.playerView.isLoop = false
  1005. self.playerView.showProgressLab = true
  1006. // 初始化音频的开始和结束时间
  1007. BFLog(message: "播放的器 开始\(String(describing: CMTimeGetSeconds(self.playeTimeRange.start))) 结束 \(String(describing: CMTimeGetSeconds(self.playeTimeRange.end)))")
  1008. let end3: TimeInterval = Date().timeIntervalSince1970
  1009. self.playerView.play(pauseFirstFrame: false, playeTimeRange: CMTimeRange(start: self.playeTimeRange.start, end: self.playeTimeRange.end))
  1010. self.stuckPointCuttingView.updateProgress(progress: 0)
  1011. self.synchroMarskView.removeMarskView()
  1012. let end4: TimeInterval = Date().timeIntervalSince1970
  1013. BFLog(message: " playerView.play tiskskskskme \(end4 - end3)")
  1014. // 6,进度回调
  1015. self.playerView.progress = { [weak self] currentTime, tatolTime, percent in
  1016. if percent == 1 {
  1017. self?.stuckPointCuttingView.resetDefaultsColor(clearData: false)
  1018. sharedImageProcessingContext.framebufferCache.purgeAllUnassignedFramebuffers()
  1019. return
  1020. }
  1021. if CMTimeGetSeconds(self?.playeTimeRange.duration ?? .zero) <= 0.0 {
  1022. BFLog(message: "时长错误!!!!")
  1023. return
  1024. }
  1025. // 更新进度
  1026. let progress = (currentTime - CMTimeGetSeconds(self?.playeTimeRange.start ?? .zero)) / CMTimeGetSeconds(self?.playeTimeRange.duration ?? .zero)
  1027. BFLog(message: "\(currentTime) \(tatolTime) 显示播放器进度为: \(progress)")
  1028. self?.stuckPointCuttingView.updateProgress(progress: CGFloat(progress))
  1029. }
  1030. }
  1031. }
  1032. }
  1033. deinit {
  1034. musicNameLab.stop()
  1035. // playerView.pause()
  1036. // 取消所有的导出
  1037. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  1038. exportSession.cancelExport()
  1039. }
  1040. self.synchroMarskView.removeMarskView()
  1041. sharedImageProcessingContext.framebufferCache.purgeAllUnassignedFramebuffers()
  1042. BFLog(1, message: "卡点视频预览界面release")
  1043. }
  1044. }
  1045. // MARK: - 视频渲染相关逻辑方法
  1046. extension PQStuckPointEditerController {
  1047. /// 分割视频 这里只设置视频类型的 in 和 out 并不设置显示的开始和结束时间 mp4 ,png ,png ,mp4
  1048. /// - Parameter section: 当前段
  1049. /// - Parameter stuckPoints: 用户选择的,或推荐的卡点数
  1050. /// - Returns: 返回分割后的所有 stickers 和卡点数是一致的
  1051. func clipVideoMerage(section: PQEditSectionModel, stuckPoints: [Float]) -> [PQEditVisionTrackMaterialsModel] {
  1052. var stickers: Array = Array<PQEditVisionTrackMaterialsModel>.init()
  1053. // 第二种情况:有视频要进行分割
  1054. /*
  1055. 1, 确定每个视频素材需要切的段数p
  1056. 2, 将所有视频时长相加,得到总视频素材时长L = l1 + l2 + ... + ln
  1057. 3, 视频素材a1需要切分的个数clipNum = max (round (kongduan * a1 / L) , 1)
  1058. */
  1059. // 要补的空位数
  1060. let kongduan: Int = Int(stuckPoints.count - 1) - selectedImageDataCount
  1061. // 所有视频总时长
  1062. var videoTotalDuration: Float64 = 0.0
  1063. for video in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video") {
  1064. // MARK: SanW-2021.11.15-不再导出到沙盒,直接使用本地相册地址
  1065. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: video.locationPath), options: nil)
  1066. // let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + video.locationPath), options: nil)
  1067. videoTotalDuration = videoTotalDuration + Float64(CMTimeGetSeconds(asset.duration))
  1068. }
  1069. if videoTotalDuration == 0 {
  1070. BFLog(message: "视频总时长出现错误!!!!这里应该有视频素材的")
  1071. return stickers
  1072. }
  1073. for sticker in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() {
  1074. if sticker.type == StickerType.VIDEO.rawValue {
  1075. // MARK: SanW-2021.11.15-不再导出到沙盒,直接使用本地相册地址
  1076. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: sticker.locationPath), options: nil)
  1077. // let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + sticker.locationPath), options: nil)
  1078. // 要分割的段落
  1079. let clipNum = Int(max(round(Double(kongduan) * CMTimeGetSeconds(asset.duration) / videoTotalDuration), 1))
  1080. sticker.duration = CMTimeGetSeconds(asset.duration)
  1081. BFLog(message: "单个视频\(sticker.locationPath)时长::\(CMTimeGetSeconds(asset.duration)) ,clipNum is:\(clipNum)")
  1082. for clipindex in 0 ..< clipNum {
  1083. // deep copy sticker model 防止只有一个对象
  1084. let deepCopySticker: PQEditVisionTrackMaterialsModel? = sticker.copy() as? PQEditVisionTrackMaterialsModel
  1085. // 设置循环模式和适配模式
  1086. deepCopySticker?.generateDefaultValues()
  1087. deepCopySticker?.model_in = clipindex == 0 ? 0 : CMTimeGetSeconds(asset.duration) / Double(clipNum) * Double(clipindex)
  1088. deepCopySticker?.out = (deepCopySticker?.model_in ?? 0) + CMTimeGetSeconds(asset.duration) / Double(clipNum)
  1089. if (deepCopySticker?.model_in ?? 0) >= CMTimeGetSeconds(asset.duration) || (deepCopySticker?.out ?? 0) >= CMTimeGetSeconds(asset.duration) {
  1090. deepCopySticker?.model_in = CMTimeGetSeconds(asset.duration) - CMTimeGetSeconds(asset.duration) / Double(clipNum)
  1091. deepCopySticker?.out = CMTimeGetSeconds(asset.duration)
  1092. }
  1093. BFLog(message: " crilp is in \(deepCopySticker?.model_in ?? 0) out \(deepCopySticker?.out ?? 0) 总时长\(CMTimeGetSeconds(asset.duration))")
  1094. if deepCopySticker != nil {
  1095. stickers.append(deepCopySticker!)
  1096. }
  1097. }
  1098. } else if sticker.type == StickerType.IMAGE.rawValue {
  1099. sticker.generateDefaultValues()
  1100. stickers.append(sticker)
  1101. }
  1102. }
  1103. // kongduan = clipNumTep
  1104. return stickers
  1105. }
  1106. // 更新 playeTimeRange & finallyUserAudioTime
  1107. func updateTimeInfomation() {
  1108. // 四,背景音乐时长处理)计算最后使用的音频时长, 如果不用拼接音频时长度是卡点的倒数第二位时间
  1109. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (stuckPointMusicData?.localPath ?? "")), options: nil)
  1110. // 原推荐卡点的倒数第二位时间
  1111. let lastSecondPoint = Float(stuckPointMusicData!.rhythmSdata[0].pointTimes[stuckPointMusicData!.rhythmSdata[0].pointTimes.count - 2]) / Float(BASE_FILTER_TIMESCALE)
  1112. finallyUserAudioTime = Float(lastSecondPoint)
  1113. if (finallyStuckPoints.last ?? 0) > Float(CMTimeGetSeconds(asset.duration)) {
  1114. finallyUserAudioTime = Float(finallyStuckPoints.last ?? 0) + (Float(CMTimeGetSeconds(asset.duration)) - Float(lastSecondPoint))
  1115. }
  1116. playeTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Float64(finallyStuckPoints.first ?? 0) * Float64(BASE_FILTER_TIMESCALE)), timescale: BASE_FILTER_TIMESCALE), end: CMTime(value: CMTimeValue(Float64(finallyStuckPoints.last ?? 0) * Float64(BASE_FILTER_TIMESCALE)), timescale: BASE_FILTER_TIMESCALE))
  1117. for (index, usePoint) in finallyStuckPoints.enumerated() {
  1118. BFLog(message: "测试人员最后使用的卡点信息 \(index) : \(usePoint)")
  1119. }
  1120. BFLog(message: "计算后给播放器使用的开始:\(CMTimeGetSeconds(playeTimeRange.start)) 结束时间\(CMTimeGetSeconds(playeTimeRange.end)) 播放总时长:\(CMTimeGetSeconds(playeTimeRange.end) - CMTimeGetSeconds(playeTimeRange.start))")
  1121. }
  1122. /// 创建sticker
  1123. /// - Parameters:
  1124. /// - sections: 项目所有段落数据信息
  1125. /// - inputSize: 画布大小
  1126. /// - Returns: filters 数据 播放器可直接使用
  1127. func createStickers(sections: List<PQEditSectionModel>, inputSize _: CGSize = .zero, model: createStickersModel = .createStickersModelPoint) -> [PQEditVisionTrackMaterialsModel] {
  1128. // 推荐卡点数
  1129. let beginDecoderTime: TimeInterval = Date().timeIntervalSince1970
  1130. // 保存滤镜对象数据
  1131. var stickers: Array = Array<PQEditVisionTrackMaterialsModel>.init()
  1132. for section in sections {
  1133. if section.sectionType == "normal" {
  1134. // 第一种情况:全是图片,三种模式都进行图片回环播放
  1135. if section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "video").count == 0, section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials(type: "image").count > 0 {
  1136. for (index, _) in finallyStuckPoints.enumerated() {
  1137. let sticker: PQEditVisionTrackMaterialsModel = section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials()[index % section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials().count]
  1138. BFLog(message: "stickerlocationPath sticker : \(sticker.locationPath)")
  1139. //
  1140. let deepCopySticker: PQEditVisionTrackMaterialsModel? = sticker.copy() as? PQEditVisionTrackMaterialsModel
  1141. if deepCopySticker!.type == StickerType.IMAGE.rawValue {
  1142. if index + 1 < finallyStuckPoints.count {
  1143. deepCopySticker!.timelineIn = Float64(String(format: "%.6f", finallyStuckPoints[index])) ?? 0.0
  1144. deepCopySticker!.timelineOut = Float64(String(format: "%.6f", finallyStuckPoints[index + 1])) ?? 0.0
  1145. if deepCopySticker != nil {
  1146. deepCopySticker?.generateDefaultValues()
  1147. stickers.append(deepCopySticker!)
  1148. BFLog(1, message: "测试人员index is 纯图片 timelineOut:\(deepCopySticker!.timelineIn) timelineOut :\(deepCopySticker!.timelineOut)")
  1149. }
  1150. }
  1151. }
  1152. }
  1153. } else { // 第二种情况:视频 + 图片情况、视频要分段+图片按卡点时长创建
  1154. if model == .createStickersModelPoint { // 跳跃卡点
  1155. // 第二种情况:有视频要进行分割
  1156. let clipFilters = clipVideoMerage(section: section, stuckPoints: finallyStuckPoints)
  1157. // 数据不一致时要对数据进行二次处理,不是最好方案
  1158. if clipFilters.count > finallyStuckPoints.count - 1 {
  1159. clipPoint(clipNum: clipFilters.count - finallyStuckPoints.count, oldPoints: finallyStuckPoints)
  1160. } else if clipFilters.count < finallyStuckPoints.count - 1 {
  1161. while clipFilters.count < finallyStuckPoints.count - 1 {
  1162. finallyStuckPoints.removeLast()
  1163. }
  1164. }
  1165. // 更新最终使用值
  1166. updateTimeInfomation()
  1167. // stikcer段数比clipFilters 数 大于 1才是正确的
  1168. BFLog(message: "stikcer count is\(clipFilters.count) finallyStuckPoints count is\(finallyStuckPoints.count)")
  1169. for (index, point) in finallyStuckPoints.enumerated() {
  1170. BFLog(message: "aaaaaindexindeindexxindexindexindex \(index) \(point)")
  1171. if index + 1 < finallyStuckPoints.count, index < clipFilters.count {
  1172. BFLog(message: "bbbbbindexindeindexxindexindexindex \(index) \(point)")
  1173. let sticker: PQEditVisionTrackMaterialsModel = clipFilters[index]
  1174. if sticker.type == StickerType.IMAGE.rawValue {
  1175. BFLog(message: "当前是image filter !!!!!")
  1176. }
  1177. sticker.timelineIn = Float64(String(format: "%.6f", finallyStuckPoints[index])) ?? 0.0
  1178. sticker.timelineOut = Float64(String(format: "%.6f", finallyStuckPoints[index + 1])) ?? 0.0
  1179. // 卡点的时间 > in out 值 这里就会出现鬼畜效果
  1180. let timelineInterval = sticker.timelineOut - sticker.timelineIn
  1181. let inOutInterval = sticker.out - sticker.model_in
  1182. if timelineInterval > inOutInterval {
  1183. BFLog(message: "实际要显示卡点时长\(timelineInterval) 素材裁剪时长:\(inOutInterval)")
  1184. sticker.out = sticker.model_in + timelineInterval
  1185. // 下面只是 LOG 方便查问题
  1186. let stickerInOut = sticker.out - sticker.model_in
  1187. let stickerTimelineInOut = sticker.timelineOut - sticker.timelineIn
  1188. if stickerInOut != stickerTimelineInOut {
  1189. BFLog(message: "sticker.timelineIn \(sticker.timelineIn) stickerTimelineInOut is\(stickerTimelineInOut) stickerInOut is\(stickerInOut) 相差\(stickerTimelineInOut - stickerInOut)")
  1190. }
  1191. }
  1192. // out > 素材的总时长in out 进行前移操作
  1193. let offsetAssetDuration = sticker.out - sticker.duration
  1194. if offsetAssetDuration > 0 {
  1195. sticker.model_in = sticker.model_in - offsetAssetDuration
  1196. sticker.out = sticker.out - offsetAssetDuration
  1197. }
  1198. print("跳跃卡点测试人员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)")
  1199. stickers.append(sticker)
  1200. }
  1201. }
  1202. } else if model == .createStickersModelOnlyMusic || model == .createStickersModelSpeed { // 仅音乐 和 快慢速卡点
  1203. BFLog(message: "stuckPoints count is \(finallyStuckPoints.count)")
  1204. for sticker in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() {
  1205. if sticker.type == StickerType.VIDEO.rawValue {
  1206. // MARK: SanW-2021.11.15-不再导出到沙盒,直接使用本地相册地址
  1207. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: sticker.locationPath), options: nil)
  1208. // let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + sticker.locationPath), options: nil)
  1209. BFLog(message: "单个视频\(sticker.locationPath)时长::\(CMTimeGetSeconds(asset.duration)) ,clipNum is:\(sticker.clipCount)")
  1210. var lastOutTime: Float64 = 0.0
  1211. for _ in 1 ... sticker.clipCount {
  1212. // deep copy sticker model 防止只有一个对象
  1213. let deepCopyStickerDecoderTime: TimeInterval = Date().timeIntervalSince1970
  1214. let deepCopySticker: PQEditVisionTrackMaterialsModel? = sticker.copy() as? PQEditVisionTrackMaterialsModel
  1215. BFLog(message: "生成stickers 总时长为 aaa\(Date().timeIntervalSince1970 - deepCopyStickerDecoderTime)")
  1216. // 设置循环模式和适配模式
  1217. deepCopySticker?.generateDefaultValues()
  1218. deepCopySticker?.materialDurationFit?.fitType = adapterMode.staticFrame.rawValue
  1219. // 当前分段的速度
  1220. var tempSpeed: Float = 1.0
  1221. if model == .createStickersModelSpeed {
  1222. tempSpeed = (stickers.count % 2) == 0 ? modelSpeed_maxSpeed : modelSpeed_minSpeed
  1223. }
  1224. if stickers.count + 1 < finallyStuckPoints.count {
  1225. deepCopySticker?.speedRate = tempSpeed
  1226. // 定义临时使用的变量
  1227. // 素材显示的开始时间和结束时间
  1228. let tempTimelineIn: Float64 = Float64(String(format: "%.6f", finallyStuckPoints[stickers.count])) ?? 0.0
  1229. let timelineOut: Float64 = Float64(String(format: "%.6f", finallyStuckPoints[stickers.count + 1])) ?? 0.0
  1230. // 素材分割的开始时间和结束时间
  1231. let tempModel_In = lastOutTime
  1232. var tempOut = lastOutTime + Float64(tempSpeed) * (timelineOut - tempTimelineIn)
  1233. // 处理最后一点视频素材不够卡点时长 e.g. 0.3 卡点时长0.5
  1234. if tempOut > CMTimeGetSeconds(asset.duration) {
  1235. // 最后一点素材时长
  1236. let lastAssetDuration = CMTimeGetSeconds(asset.duration) - lastOutTime
  1237. let pointDuration = timelineOut - tempTimelineIn
  1238. // 要适应到卡点内要使用的C速度
  1239. let needSpeed = lastAssetDuration / pointDuration
  1240. // 当前卡点段为快速 快速都用 C 速处理
  1241. BFLog(message: "最后一点视频素材不够卡点时长要做变速C处理 差\(tempOut - CMTimeGetSeconds(asset.duration)) \(needSpeed)")
  1242. deepCopySticker?.speedRate = Float(needSpeed)
  1243. tempOut = CMTimeGetSeconds(asset.duration)
  1244. if needSpeed == 0 {
  1245. BFLog(message: "needSpeed is 0 出现在时长和卡点正好相等")
  1246. continue
  1247. }
  1248. }
  1249. deepCopySticker?.model_in = tempModel_In
  1250. deepCopySticker?.out = tempOut
  1251. deepCopySticker?.timelineIn = tempTimelineIn
  1252. deepCopySticker?.timelineOut = timelineOut
  1253. lastOutTime = deepCopySticker?.out ?? 0
  1254. }
  1255. BFLog(message: "测试人员创建 sticker crilp is in 视频 \(String(format: "%.6f", deepCopySticker?.model_in ?? 0)) out \(String(format: "%.6f", deepCopySticker?.out ?? 0)) ,分段素材时长:\(String(format: "%.6f", (deepCopySticker?.out ?? 0) - (deepCopySticker?.model_in ?? 0))) ,分段显示时长:\(String(format: "%.6f", (deepCopySticker?.timelineOut ?? 0) - (deepCopySticker?.timelineIn ?? 0))), 视频素材原时长\(CMTimeGetSeconds(asset.duration)) clipNum \(sticker.clipCount) timelineIN: \(String(format: "%.6f", deepCopySticker?.timelineIn ?? 0)) timelineOUT:\(String(format: "%.6f", deepCopySticker?.timelineOut ?? 0)) speedRate:\(deepCopySticker?.speedRate ?? 0.0)")
  1256. if deepCopySticker != nil {
  1257. if deepCopySticker?.timelineIn == 0 {
  1258. BFLog(message: "timelineIn data is error!!!")
  1259. }
  1260. stickers.append(deepCopySticker!)
  1261. }
  1262. }
  1263. } else if sticker.type == StickerType.IMAGE.rawValue {
  1264. if stickers.count + 1 >= finallyStuckPoints.count {
  1265. BFLog(message: "数据出现错误!!!查正") // 155.318253
  1266. break
  1267. }
  1268. sticker.generateDefaultValues()
  1269. sticker.timelineIn = Float64(String(format: "%.6f", finallyStuckPoints[stickers.count])) ?? 0.0
  1270. sticker.timelineOut = Float64(String(format: "%.6f", finallyStuckPoints[stickers.count + 1])) ?? 0.0
  1271. stickers.append(sticker)
  1272. BFLog(message: "测试人员创建 sticker crilp is in 图片 \(String(format: "%.6f", sticker.model_in)) out \(String(format: "%.6f", sticker.out)) ,分段素材时长:\(String(format: "%.6f", sticker.out - sticker.model_in)) ,分段显示时长:\(String(format: "%.6f", sticker.timelineOut - sticker.timelineIn)), timelineIN: \(String(format: "%.6f", sticker.timelineIn)) timelineOUT:\(String(format: "%.6f", sticker.timelineOut)) speedRate:\(sticker.speedRate)")
  1273. }
  1274. }
  1275. }
  1276. }
  1277. }
  1278. }
  1279. BFLog(message: "生成stickers 总时长为:\(Date().timeIntervalSince1970 - beginDecoderTime)")
  1280. return stickers
  1281. }
  1282. /// 根据档位取最后使用的卡点数据
  1283. /// - Parameter seed: 档位速度
  1284. /// - Returns: 最后使用的卡点
  1285. func getUsedStuckPoint(seed: Int) -> [Float] {
  1286. if !(stuckPointMusicData!.rhythmSdata.count > 0 && stuckPointMusicData!.rhythmSdata[0].pointTimes.count > 1) {
  1287. return []
  1288. }
  1289. // 推荐卡点数
  1290. var stuckPoints: Array = Array<Float>.init()
  1291. var pointsTemp = Array<Float>.init()
  1292. //
  1293. // 最后一个卡点时间(原推荐卡点的倒数第二位时间)
  1294. let lastPoint = stuckPointMusicData!.rhythmSdata[0].pointTimes[stuckPointMusicData!.rhythmSdata[0].pointTimes.count - 2]
  1295. for (index, dunshu) in stuckPointMusicData!.rhythmSdata[0].pointTimes.enumerated() {
  1296. if dunshu >= Int64((stuckPointMusicData?.startTime ?? 0) * Float64(BASE_FILTER_TIMESCALE)), dunshu < lastPoint {
  1297. let savePointStr = String(format: "%.6f", Float(dunshu) / Float(BASE_FILTER_TIMESCALE))
  1298. BFLog(message: "原所有卡点数:\(index) \(savePointStr)")
  1299. pointsTemp.append(Float(savePointStr) ?? 0.0)
  1300. }
  1301. }
  1302. /*
  1303. 一,快慢速模式下取卡点 2 3 4
  1304. 二,跳跃卡点模式下根据不同速度 取卡点 1,2,3
  1305. 快节奏为选中区域的所有点位,即0,1,2,3,4 5 6 7 8 9 10 ……
  1306. 适中为每两个点位取一个,即0,2,4,6……
  1307. 慢节奏为每三个点位取一个,即0,3,6,9……
  1308. */
  1309. // 不丢
  1310. if seed == 1 {
  1311. stuckPoints = pointsTemp
  1312. } else {
  1313. // 根据档位要丢
  1314. for (index, point) in pointsTemp.enumerated() {
  1315. if index % seed == 0 {
  1316. stuckPoints.append(point)
  1317. }
  1318. }
  1319. }
  1320. // for point in stuckPoints {
  1321. // BFLog(message: "没有 start end 计算后的卡点数\(point)")
  1322. // }
  1323. // 若音乐起点至第一个卡点点位之间时长t0<0.3时,此段时长与下一个点位时长合并,故第一段卡点部分时长为t0+d
  1324. while (stuckPoints.first ?? 0.0) - Float(stuckPointMusicData?.startTime ?? 0) < 0.3 {
  1325. if stuckPoints.first != nil {
  1326. stuckPoints.removeFirst()
  1327. }
  1328. }
  1329. stuckPoints.insert(Float(stuckPointMusicData?.startTime ?? 0), at: 0)
  1330. // for point in stuckPoints {
  1331. // BFLog(message: "有 start end 计算后的卡点数\(point)")
  1332. // }
  1333. BFLog(message: "处理节奏后 stuckPoints count is \(stuckPoints.count) seed \(seed), start time:\(stuckPoints.first ?? 0.0),end time:\(stuckPoints.last ?? 0.0) 总时长为:\((stuckPoints.last ?? 0.0) - (stuckPoints.first ?? 0.0))")
  1334. return stuckPoints
  1335. }
  1336. func clipPoint(clipNum: Int, oldPoints: [Float]) {
  1337. BFLog(message: "拼接卡点数:\(clipNum)")
  1338. if clipNum < 0 || oldPoints.count < 2 {
  1339. BFLog(message: "clipNum is error!!!! \(clipNum)")
  1340. return
  1341. }
  1342. // 如果是第一次拼接先补第0位
  1343. if finallyStuckPoints.count == 0 {
  1344. finallyStuckPoints.append(stuckPointsTemp.first ?? 0.0)
  1345. }
  1346. for i in finallyStuckPoints.count ... clipNum + finallyStuckPoints.count {
  1347. if (i % (oldPoints.count - 1)) != 1 {
  1348. let value = String(format: "%.6f", finallyStuckPoints[i - 1] + oldPoints[((i - 1) % (oldPoints.count - 1)) + 1] - oldPoints[((i - 2) % (oldPoints.count - 1)) + 1])
  1349. finallyStuckPoints.append(Float(value) ?? 0.0)
  1350. } else {
  1351. let value = String(format: "%.6f", finallyStuckPoints[i - 1] + oldPoints[1] - oldPoints[0])
  1352. finallyStuckPoints.append(Float(value) ?? 0.0)
  1353. }
  1354. }
  1355. }
  1356. /// 根据不同模式model, maxSpeed ,minSpeed, self?.stuckPointMusicData?.speed 档位,生成音乐时长和最终使用的卡点信息
  1357. func dealParameter(model: createStickersModel) {
  1358. // 清空上一次使用的卡点数据
  1359. finallyStuckPoints.removeAll()
  1360. // 已经取到的视频素材总长度,用于和原视频素材时长做对比,不够多加一个点
  1361. var useAssestDuration: Float = 0.0
  1362. switch model {
  1363. case .createStickersModelPoint: // 跳跃卡点
  1364. stuckPointsTemp = getUsedStuckPoint(seed: stuckPointMusicData?.speed ?? 0)
  1365. // 要拼接的段数
  1366. var clipNum: Int = 0
  1367. var i: Int = 0
  1368. // L/(n+1) L -原视觉素材总时长 n-抛留倍数 lastJumpSpeedSelectIndex 是位置 对应的值要+1
  1369. // 根据公式计划出的总时长
  1370. let jumpTime = Float(selectedTotalDuration) / Float(modelPoint_speed + 1)
  1371. // 只有图片素材时会为0
  1372. if jumpTime > 0 {
  1373. while useAssestDuration < Float(jumpTime) {
  1374. // 回环从头取\
  1375. if i + 1 >= stuckPointsTemp.count {
  1376. i = 0
  1377. }
  1378. // 快速段
  1379. let LA = (stuckPointsTemp[i + 1] - stuckPointsTemp[i])
  1380. useAssestDuration = useAssestDuration + Float(LA)
  1381. i = i + 1
  1382. clipNum = clipNum + 1
  1383. }
  1384. // 拼接要使用的卡点信息
  1385. clipPoint(clipNum: clipNum, oldPoints: stuckPointsTemp)
  1386. }
  1387. case .createStickersModelSpeed, .createStickersModelOnlyMusic: // 快慢速
  1388. // 快慢速 (2:快节奏,3:适中,4:慢节奏)
  1389. var tempMaxSpeed: Float = 1
  1390. var tempMinSpeed: Float = 1
  1391. if model == .createStickersModelSpeed {
  1392. // 改变速率,.只有快慢速且非只有图片素材时自动+1处理
  1393. if model == .createStickersModelSpeed, selectedDataCount != selectedImageDataCount {
  1394. stuckPointsTemp = getUsedStuckPoint(seed: (stuckPointMusicData?.speed ?? 0) + 1)
  1395. } else {
  1396. stuckPointsTemp = getUsedStuckPoint(seed: stuckPointMusicData?.speed ?? 0)
  1397. }
  1398. tempMaxSpeed = modelSpeed_maxSpeed
  1399. tempMinSpeed = modelSpeed_minSpeed
  1400. } else {
  1401. stuckPointsTemp = getUsedStuckPoint(seed: stuckPointMusicData?.speed ?? 0)
  1402. }
  1403. /*
  1404. - A-视频中的快速片段
  1405. - B-视频中的慢速片段
  1406. - d-在一档下音乐每个点位时长
  1407. - n-不同音乐档位对应的d倍数,快节奏时,n=1;适中时,n=3;慢节奏时,n=5
  1408. - L-原视觉素材时长
  1409. - x-视频在A片段的播放倍速
  1410. - y-视频在B片段的播放倍速
  1411. */
  1412. // LA=x*n*d,LB=y*n*d (n=1/3/5) 注:视频经过快慢速处理后的总时长约=L*2/(x+y)
  1413. BFLog(message: "Ax快速为:\(tempMaxSpeed) By慢速为:\(tempMinSpeed) 档位 N为:\(stuckPointMusicData?.speed ?? 0) 使用的卡点总数:\(stuckPointsTemp.count)")
  1414. // 使用新方法取使用的卡点数据
  1415. for section in projectModel.sData?.sections ?? List() {
  1416. if section.sectionType == "normal" {
  1417. for sticker in section.sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() {
  1418. if sticker.type == StickerType.VIDEO.rawValue {
  1419. // MARK: SanW-2021.11.15-不再导出到沙盒,直接使用本地相册地址
  1420. let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: sticker.locationPath), options: nil)
  1421. // let asset: AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + sticker.locationPath), options: nil)
  1422. let assetDuration = Float(CMTimeGetSeconds(asset.duration))
  1423. BFLog(message: "输入素材时长 \(assetDuration)")
  1424. if finallyStuckPoints.count == 0 {
  1425. finallyStuckPoints.append(stuckPointsTemp[0])
  1426. }
  1427. var j = finallyStuckPoints.count
  1428. // 添加卡点的段数 用于判断当前卡点是快/慢速
  1429. var pointCount: Int = 1
  1430. // 已经取到的视频素材总长度,用于和原视频素材时长做对比,不够多加一个卡点
  1431. var useAssestDurationTemp: Float = 0.0
  1432. while useAssestDurationTemp < assetDuration {
  1433. // 当前段的应该使用的速度 A/B
  1434. let useSpeed = (pointCount % 2 != 0) ? tempMaxSpeed : tempMinSpeed
  1435. // 计算卡点
  1436. // 要添加的卡点数值
  1437. var sub: Float = 0.0
  1438. if stuckPointsTemp.count > 2 {
  1439. if ((j - 1) % (stuckPointsTemp.count - 1)) != 0 {
  1440. sub = stuckPointsTemp[((j - 1) % (stuckPointsTemp.count - 1)) + 1] - stuckPointsTemp[(j - 1) % (stuckPointsTemp.count - 1)]
  1441. } else {
  1442. sub = stuckPointsTemp[1] - stuckPointsTemp[0]
  1443. }
  1444. finallyStuckPoints.append(finallyStuckPoints[j - 1] + sub)
  1445. j += 1
  1446. }
  1447. useAssestDurationTemp += sub * useSpeed
  1448. if useAssestDurationTemp > assetDuration {
  1449. useAssestDurationTemp -= sub * useSpeed
  1450. break
  1451. }
  1452. pointCount += 1
  1453. BFLog(2, message: "finallyStuckPoints last ;\((finallyStuckPoints.last ?? 0.0) - (finallyStuckPoints.first ?? 0.0)), tmp:\(useAssestDurationTemp)")
  1454. }
  1455. // 2拼接要使用的卡点信息
  1456. sticker.clipCount = pointCount
  1457. if stuckPointsTemp.count < 1 {
  1458. // todo 和产品沟通提示
  1459. BFLog(message: "卡点数据有错误!!!")
  1460. return
  1461. }
  1462. BFLog(message: "finallyStuckPoints\(finallyStuckPoints)")
  1463. // 3,多补一个卡点 做 C级 速处理,要根据条件不满足 要删除最后一位,
  1464. if useAssestDurationTemp < assetDuration {
  1465. /*
  1466. // 下一个卡的的速度性质快、慢,e.g. sticker.clipCount = 5时 tempSpeed 应该是慢速。下面计算正确。
  1467. var tempSpeed: Float = 1.0
  1468. if model == .createStickersModelSpeed {
  1469. tempSpeed = (sticker.clipCount) % 2 != 0 ? modelSpeed_maxSpeed : modelSpeed_minSpeed
  1470. }
  1471. // 最后一点素材时长
  1472. let lastAssetDuration = Float(CMTimeGetSeconds(asset.duration)) - useAssestDurationTemp
  1473. let lastPointIndex = (sticker.clipCount % stuckPointsTemp.count)
  1474. // 两个卡点
  1475. let a: Float = stuckPointsTemp[lastPointIndex]
  1476. var b: Float = 0.0
  1477. if lastPointIndex + 1 < stuckPointsTemp.count {
  1478. b = stuckPointsTemp[lastPointIndex + 1]
  1479. let pointDuration = b - a
  1480. // 要适应到卡点内要使用的C速度
  1481. let needSpeed = lastAssetDuration / pointDuration
  1482. // 当前卡点段为快速
  1483. if tempSpeed >= 1 {
  1484. // if needSpeed < 0.4 * tempSpeed {
  1485. // BFLog(message: "条件不满足不用补位 删除多加的一位")
  1486. // finallyStuckPoints.removeLast()
  1487. // }
  1488. } else { // 当前卡点段为慢速
  1489. if needSpeed >= 0.4 * tempSpeed && needSpeed >= 0.2 {
  1490. // 查找使用的最后一个卡点在原数组中的位置
  1491. } else {
  1492. BFLog(message: "条件不满足不用补位 删除多加的一位")
  1493. finallyStuckPoints.removeLast()
  1494. }
  1495. }
  1496. }
  1497. */
  1498. } else {
  1499. // 出现在第一个卡点X 倍速 > 原素材
  1500. finallyStuckPoints.removeLast()
  1501. }
  1502. }
  1503. }
  1504. }
  1505. }
  1506. }
  1507. // 拼接图片所使用的时长.选择一组图片 按图片数量计算卡点的总时长
  1508. if selectedImageDataCount > 0 {
  1509. clipPoint(clipNum: selectedImageDataCount - 1, oldPoints: stuckPointsTemp)
  1510. }
  1511. // 全是图片时数组里放着的一定都是图片的使用的卡点
  1512. // 定义一次循环的总时长
  1513. var oneSelectImageDuration: Float = 0.0
  1514. if selectedDataCount == selectedImageDataCount {
  1515. oneSelectImageDuration = (finallyStuckPoints.last ?? 0) - (finallyStuckPoints.first ?? 0)
  1516. }
  1517. // 3)素材全是图片处理
  1518. if selectedDataCount == selectedImageDataCount {
  1519. // lastCyclesSelectIndex != -1 已经设置过循环次数 应该是手动设置的值
  1520. if lastCyclesSelectIndex != -1 {
  1521. // 纯图片时 已经默认添加一次循环 所以要用lastCyclesSelectIndex - 1
  1522. if lastCyclesSelectIndex != 0 {
  1523. clipPoint(clipNum: selectedImageDataCount * lastCyclesSelectIndex - 1, oldPoints: stuckPointsTemp)
  1524. }
  1525. } else {
  1526. if oneSelectImageDuration < 10 {
  1527. lastCyclesSelectIndex = 0
  1528. while oneSelectImageDuration < 10 {
  1529. // 不够10S 时 一次加图片数量的卡点数
  1530. clipPoint(clipNum: selectedImageDataCount - 1, oldPoints: stuckPointsTemp)
  1531. oneSelectImageDuration = Float((finallyStuckPoints.last ?? 0) - (finallyStuckPoints.first ?? 0))
  1532. lastCyclesSelectIndex = lastCyclesSelectIndex + 1
  1533. }
  1534. speedSettingView.setSelectItem(index: lastCyclesSelectIndex, isSettingPlayer: false, enableInsert: true)
  1535. } else {
  1536. lastCyclesSelectIndex = 0
  1537. }
  1538. }
  1539. }
  1540. if finallyStuckPoints.count < 2 {
  1541. cShowHUB(superView: nil, msg: "视频资源导入失败,请重新选择!!")
  1542. exportResourceFailed()
  1543. return
  1544. }
  1545. // 设置速度选择的位置
  1546. if speedSettingView.viewType == 1 {
  1547. speedSettingView.setSelectItem(index: lastSpeedSelectIndex, isSettingPlayer: false)
  1548. } else if speedSettingView.viewType == 2 {
  1549. speedSettingView.setSelectItem(index: lastJumpSpeedSelectIndex, isSettingPlayer: false)
  1550. } else if speedSettingView.viewType == 3 {
  1551. if lastCyclesSelectIndex != -1 {
  1552. speedSettingView.setSelectItem(index: lastCyclesSelectIndex, isSettingPlayer: false)
  1553. } else {
  1554. speedSettingView.setSelectItem(index: 0, isSettingPlayer: false)
  1555. }
  1556. }
  1557. updateTimeInfomation()
  1558. }
  1559. }
  1560. // MARK: - 同步/下载素材相关
  1561. /// 同步/下载素材相关
  1562. extension PQStuckPointEditerController {
  1563. /// 同步音乐相关数据
  1564. /// - Returns: <#description#>
  1565. func synchroMusicInfoData(resetSelectIndex: Bool = true) {
  1566. if (stuckPointMusicData?.rhythmSdata.count ?? 0) <= 0 {
  1567. synchroMarskView.show()
  1568. PQStuckPointViewModel.stuckPointMusicDetailData(musicId: stuckPointMusicData?.musicId ?? "", originType: stuckPointMusicData?.originType ?? 1) { [weak self] newMusicData, _ in
  1569. if newMusicData != nil, (newMusicData?.rhythmSdata.count ?? 0) > 0 {
  1570. self?.isStuckPointDataSuccess = true
  1571. self?.stuckPointMusicData?.rhythmSdata = newMusicData?.rhythmSdata ?? []
  1572. self?.stuckPointMusicData?.startTime = newMusicData?.startTime ?? 0
  1573. self?.stuckPointMusicData?.endTime = newMusicData?.endTime ?? 0
  1574. if newMusicData?.speed != nil {
  1575. self?.stuckPointMusicData?.speed = newMusicData?.speed ?? 2
  1576. }
  1577. if self?.stuckPointMusicData?.localPath == nil || (self?.stuckPointMusicData?.localPath?.count ?? 0) > 0 {
  1578. PQDownloadManager.downLoadFile(url: self?.stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in
  1579. if error == nil, filePath != nil {
  1580. self?.isSynchroMusicInfoSuccess = true
  1581. self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "")
  1582. // 处理所有数据完成
  1583. self?.dealWithDataSuccess(resetSelectIndex: resetSelectIndex)
  1584. }
  1585. self?.synchroMarskView.removeMarskView()
  1586. }
  1587. } else {
  1588. self?.isSynchroMusicInfoSuccess = true
  1589. // 处理所有数据完成
  1590. self?.dealWithDataSuccess(resetSelectIndex: resetSelectIndex)
  1591. }
  1592. // 添加子视图
  1593. self?.addSubViews()
  1594. } else {
  1595. self?.synchroMarskView.removeMarskView()
  1596. cShowHUB(superView: nil, msg: "音乐信息加载失败,请重新选择音乐")
  1597. self?.navigationController?.popViewController(animated: true)
  1598. }
  1599. }
  1600. } else if stuckPointMusicData?.localPath == nil || (stuckPointMusicData?.localPath?.count ?? 0) > 0 {
  1601. synchroMarskView.show()
  1602. isStuckPointDataSuccess = true
  1603. PQDownloadManager.downLoadFile(url: stuckPointMusicData?.musicPath ?? "") { [weak self] filePath, error in
  1604. if error == nil, filePath != nil {
  1605. self?.isSynchroMusicInfoSuccess = true
  1606. self?.stuckPointMusicData?.localPath = filePath?.replacingOccurrences(of: documensDirectory, with: "")
  1607. // 处理所有数据完成
  1608. self?.dealWithDataSuccess(resetSelectIndex: resetSelectIndex)
  1609. } else {
  1610. self?.synchroMarskView.removeMarskView()
  1611. cShowHUB(superView: nil, msg: "音乐信息加载失败,请重新选择音乐")
  1612. // BFUploadRemindView.showUploadRemindView(title: nil, attributedTitle: NSAttributedString(string: "加载音乐失败,请重新选择音乐"), summary: "", confirmTitle: nil) { [weak self] _, _ in
  1613. self?.navigationController?.popViewController(animated: true)
  1614. }
  1615. }
  1616. } else {
  1617. isStuckPointDataSuccess = true
  1618. // 处理所有数据完成
  1619. dealWithDataSuccess(resetSelectIndex: resetSelectIndex)
  1620. }
  1621. }
  1622. /// 导出相册数据
  1623. /// - Returns: <#description#>
  1624. func exportPhotoData() {
  1625. // 取消所有的导出
  1626. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  1627. exportSession.cancelExport()
  1628. }
  1629. var isHaveVideo: Bool = false
  1630. var failedExprot: Bool = false
  1631. if selectedMetarialData != nil, (selectedMetarialData?.count ?? 0) > 0 {
  1632. if synchroMarskView.superview == nil {
  1633. UIApplication.shared.keyWindow?.addSubview(synchroMarskView)
  1634. }
  1635. let dispatchGroup = DispatchGroup()
  1636. for photo in selectedMetarialData! {
  1637. if photo.asset != nil, photo.asset?.mediaType == .video {
  1638. if !isHaveVideo {
  1639. isHaveVideo = true
  1640. }
  1641. dispatchGroup.enter()
  1642. PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: photo.asset!) { avAsset, _, _, _ in
  1643. if avAsset is AVURLAsset {
  1644. // 创建目录
  1645. let fileName = (avAsset as! AVURLAsset).url.absoluteString
  1646. BFLog(message: "video fileName is\(fileName)")
  1647. let tempPhoto = self.selectedMetarialData?.first(where: { material in
  1648. material.asset == photo.asset
  1649. })
  1650. // MARK: SanW-2021.11.15-不在导出到沙盒,直接用本地地址
  1651. tempPhoto?.locationPath = fileName.replacingOccurrences(of: "file://", with: "")
  1652. dispatchGroup.leave()
  1653. // if fileName.count > 0 {
  1654. // createDirectory(path: photoLibraryDirectory)
  1655. // let outFilePath = photoLibraryDirectory + fileName.md5 + ".mp4"
  1656. // // 文件存在先删除老文件
  1657. // if FileManager.default.fileExists(atPath: outFilePath) {
  1658. // do {
  1659. // try FileManager.default.removeItem(at: NSURL.fileURL(withPath: outFilePath))
  1660. // } catch {
  1661. // BFLog(message: "导出相册视频-error == \(error)")
  1662. // }
  1663. // }
  1664. // let curr = Date()
  1665. // let assetResources = PHAssetResource.assetResources(for: photo.asset!)
  1666. // if let rsc = assetResources.first(where: { res in
  1667. // res.type == .video || res.type == .pairedVideo
  1668. // }) {
  1669. // PHAssetResourceManager.default().writeData(for: rsc, toFile: URL(fileURLWithPath: outFilePath), options: nil) { error in
  1670. // if error == nil {
  1671. // BFLog(message: "导出视频相exportAsynchronously \(String(describing: outFilePath)) \(Date().timeIntervalSince(curr))")
  1672. // tempPhoto?.locationPath = outFilePath.replacingOccurrences(of: documensDirectory, with: "")
  1673. // }else{
  1674. // failedExprot = true
  1675. // BFLog(message: "导出视频相exportAsynchro faile")
  1676. // }
  1677. // dispatchGroup.leave()
  1678. // }
  1679. //
  1680. // }else {
  1681. // BFLog(message: "导出视频相exportAsynchro faile")
  1682. // dispatchGroup.leave()
  1683. // }
  1684. // }
  1685. }
  1686. }
  1687. }
  1688. }
  1689. dispatchGroup.notify(queue: DispatchQueue.main) { [weak self] in
  1690. if failedExprot {
  1691. cShowHUB(superView: nil, msg: "视频导入失败,请返回重试")
  1692. self?.exportResourceFailed()
  1693. return
  1694. }
  1695. self?.isExportVideosSuccess = true
  1696. BFLog(message: "所有相册视频导出成功")
  1697. // 处理所有数据完成
  1698. if isHaveVideo {
  1699. self?.dealWithDataSuccess()
  1700. }
  1701. }
  1702. // 只有图片
  1703. if !isHaveVideo {
  1704. isExportVideosSuccess = true
  1705. BFLog(message: "所有相册视频导出成功")
  1706. dealWithDataSuccess()
  1707. }
  1708. }
  1709. }
  1710. func exportResourceFailed() {
  1711. DispatchQueue.main.async {
  1712. self.synchroMarskView.removeMarskView()
  1713. self.navigationController?.popViewController(animated: true)
  1714. }
  1715. }
  1716. /// 处理所有数据完成
  1717. /// - Returns: <#description#>
  1718. /// resetSelectIndex : 是否重置用户选择的速度设置,在编辑界面切换音乐时不重置
  1719. func dealWithDataSuccess(resetSelectIndex: Bool = true) {
  1720. BFLog(message: "三组参数:\(isSynchroMusicInfoSuccess) \(isStuckPointDataSuccess) \(isExportVideosSuccess)")
  1721. if !isSynchroMusicInfoSuccess || !isStuckPointDataSuccess || !isExportVideosSuccess {
  1722. return
  1723. }
  1724. createPorjectData()
  1725. BFLog(1, message: "界面编辑界面时参数 选择素材时长:\(selectedTotalDuration) 选择素材总数:\(selectedDataCount) 选择图片总数\(selectedImageDataCount) 再创建类型:\(String(describing: reCreateVideoData?.rhythmMode))")
  1726. if resetSelectIndex {
  1727. // 1 生成默认参数值
  1728. /*
  1729. - 当素材总时长∈[0-6)s 时,提示推荐仅配乐模式 or 快慢速模式
  1730. - 当素材总时长∈[6-80)s 时,卡点抛留倍数为1x
  1731. - 当素材总时长∈[80,120)s 时,卡点抛留倍数为2x
  1732. - 当素材总时长∈[120,160)s 时,卡点抛留倍数为3x
  1733. - 当素材总时长∈[160,200)s 时,卡点抛留倍数为4x
  1734. - 当素材总时长∈[200,240)s 时,卡点抛留倍数为5x
  1735. - 当素材总时长∈[240,∞)s 时,卡点抛留倍数为5x
  1736. */
  1737. // 1.1) 生成跳跃卡点的默认值
  1738. if selectedTotalDuration >= 6 && selectedTotalDuration < 80 {
  1739. lastJumpSpeedSelectIndex = 0
  1740. } else if selectedTotalDuration >= 80 && selectedTotalDuration < 120 {
  1741. lastJumpSpeedSelectIndex = 1
  1742. } else if selectedTotalDuration >= 120 && selectedTotalDuration < 160 {
  1743. lastJumpSpeedSelectIndex = 2
  1744. } else if selectedTotalDuration >= 160 && selectedTotalDuration < 200 {
  1745. lastJumpSpeedSelectIndex = 3
  1746. } else if (selectedTotalDuration >= 200 && selectedTotalDuration < 240) || selectedTotalDuration >= 240 {
  1747. lastJumpSpeedSelectIndex = 4
  1748. }
  1749. /* 默认进入快慢速模式
  1750. - 当素材总时长∈[120,144]s 时,快慢速处理方式:快速为 6x,慢速为 1.2x,效果是快&快
  1751. - 当素材总时长∈[70,120)s 时,快慢速处理方式:快速为5x,慢速为 1x,效果是快&正常
  1752. - 当素材总时长∈[56,70)s 时,快慢速处理方式:快速为3x,慢速为 0.5x,效果是快&慢
  1753. - 当素材总时长∈[17.5,56)s 时,快慢速处理方式:快速为 2.4x,慢速为 0.4x,效果是快&慢
  1754. - 当素材总时长∈[10.5,17.5)s 时,快慢速处理方式:快速为 1.8x,慢速为 0.3x,效果是快&慢
  1755. - 当素材总时长∈(0,10.5)s 时,快慢速处理方式:快速为 1x,慢速为 0.2x,效果是正常&慢
  1756. */
  1757. // 1.2)生成快慢速的默认值
  1758. if selectedTotalDuration >= 120 && selectedTotalDuration <= 144 || selectedTotalDuration > 144 {
  1759. lastSpeedSelectIndex = 5
  1760. } else if selectedTotalDuration >= 70, selectedTotalDuration < 120 {
  1761. lastSpeedSelectIndex = 4
  1762. } else if selectedTotalDuration >= 56, selectedTotalDuration < 70 {
  1763. lastSpeedSelectIndex = 3
  1764. } else if selectedTotalDuration >= 17.5, selectedTotalDuration < 56 {
  1765. lastSpeedSelectIndex = 2
  1766. } else if selectedTotalDuration >= 10.5, selectedTotalDuration < 17.5 {
  1767. lastSpeedSelectIndex = 1
  1768. } else if selectedTotalDuration > 0, selectedTotalDuration < 10.5 {
  1769. lastSpeedSelectIndex = 0
  1770. }
  1771. // 如果是再创作进来的按原视频的模式
  1772. if reCreateVideoData != nil {
  1773. BFLog(message: "是再创作进来的 \(reCreateVideoData!.rhythmMode)")
  1774. switch reCreateVideoData!.rhythmMode {
  1775. case 1:
  1776. editModelClick(sender: jumpPointBtn, reportLog: false)
  1777. case 2:
  1778. editModelClick(sender: speedStuckBtn, reportLog: false)
  1779. case 3:
  1780. editModelClick(sender: onlyMusicBtn, reportLog: false)
  1781. default: break
  1782. }
  1783. return
  1784. }
  1785. // 跳跃卡点不可用
  1786. if selectedTotalDuration < 6, selectedDataCount != selectedImageDataCount {
  1787. let styleColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  1788. jumpPointBtn.setTitleColor(UIColor(red: styleColor.rgbaf[0], green: styleColor.rgbaf[1], blue: styleColor.rgbaf[2], alpha: 0.3), for: .selected)
  1789. let pointEditNamalBackgroundColor = BFConfig.shared.pointEditNamalBackgroundColor
  1790. jumpPointBtn.backgroundColor = UIColor(red: pointEditNamalBackgroundColor.rgbaf[0], green: pointEditNamalBackgroundColor.rgbaf[1], blue: pointEditNamalBackgroundColor.rgbaf[2], alpha: 0.3)
  1791. }
  1792. /*
  1793. 文档规则 https://w42nne6hzg.feishu.cn/docs/doccnQZm1uCfkU4QtJb5fLxYk4d#
  1794. */
  1795. // 2,根据所选择所有素材时长进入默认模式
  1796. // 全是图片
  1797. if selectedDataCount == selectedImageDataCount {
  1798. BFLog(message: "全是图片 \(selectedDataCount) \(selectedImageDataCount)")
  1799. // 默认进入跳跃卡点模式
  1800. editModelClick(sender: jumpPointBtn, reportLog: false)
  1801. } else {
  1802. // 默认进入快慢速模式
  1803. if selectedTotalDuration > 0, selectedTotalDuration <= 144 {
  1804. editModelClick(sender: speedStuckBtn, reportLog: false)
  1805. } else {
  1806. // 默认进入跳跃卡点模式
  1807. editModelClick(sender: jumpPointBtn, reportLog: false)
  1808. }
  1809. }
  1810. } else {
  1811. editModelClick(sender: lastEditModelBtn ?? jumpPointBtn, reportLog: false)
  1812. }
  1813. }
  1814. }