PQStuckPointEditerController.swift 56 KB

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