PQStuckPointPublicController.swift 93 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926
  1. //
  2. // PQStuckPointPublicController.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2021/5/6.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. //
  8. import ObjectMapper
  9. import Photos
  10. import UIKit
  11. import WechatOpenSDK
  12. import Kingfisher
  13. import BFCommonKit
  14. import Alamofire
  15. class PQStuckPointPublicController: PQBaseViewController {
  16. private var isShared: Bool = false // 是否在分享
  17. private var isExportSuccess: Bool = false // 是否导出完成
  18. private var isSaveDraftSuccess: Bool = false // 是否保存草稿完成
  19. private var isSaveProjectSuccess: Bool = false // 是否保存项目完成
  20. private var isUploadSuccess: Bool = false // 是否上传完成
  21. private var isPublicSuccess: Bool = false // 是否发布完成
  22. private var exportLocalURL: URL? // 导出的地址
  23. // 再创作数据
  24. private var reCreateData: PQReCreateModel?
  25. // 确定上传的数据
  26. private var uploadData: PQUploadModel?
  27. // 发布成功的视频数据
  28. private var videoData: PQVideoListModel?
  29. // 视频创作埋点数据
  30. private var eventTrackData: PQVideoMakeEventTrackModel?
  31. // 选中的总时长-统计使用
  32. var selectedTotalDuration: Float64 = 0
  33. // 选择的总数-统计使用
  34. var selectedDataCount: Int = 0
  35. // 选择的图片总数-统计使用
  36. var selectedImageDataCount: Int = 0
  37. // 最大的宽度
  38. private var maxWidth: CGFloat = cScreenWidth
  39. // 最大的高度
  40. private var maxHeight: CGFloat = cScreenHeigth > 568 ? 385 : 385*cScreenHeigth/818
  41. // 开始导出的时间
  42. private let startExportDate: Float64 = Date().timeIntervalSince1970
  43. // 导出结束的时间
  44. private var exportEndDate: Float64 = Date().timeIntervalSince1970
  45. // 取到的封面 给发布界面使用
  46. private var coverImage: UIImage?
  47. // 导出视频工具类
  48. private var exporter: PQCompositionExporter!
  49. // 导出进度
  50. private var exportProgrss = 0
  51. var mStickers: [PQEditVisionTrackMaterialsModel]?
  52. var remindView: PQRemindView?
  53. // 已经选择标题内容,加一个属性接收 使用有不在主线不能直接使用 titleLabel text
  54. var selectTitle: String = ""
  55. // add by ak 玩法类型 调用 producevideo/saveProject 时使用
  56. var rhythmMode: createStickersModel = .createStickersModelPoint
  57. // add by ak 设置的速度
  58. var syncedUpVideoSpeedMax: Float = 0.0
  59. var syncedUpVideoSpeedMin: Float = 0.0
  60. // add by ak 是否是再创作模式
  61. var isReCreate: Bool = false
  62. // 最终使用的音频时长,用于拼接音乐使用
  63. var finallyUserAudioTime: Float = 0.0
  64. // 拼接音乐的开始和结束位置
  65. var clipAudioRange: CMTimeRange = CMTimeRange.zero
  66. // 导出的开始的开始和结束时间
  67. var playeTimeRange: CMTimeRange = CMTimeRange()
  68. //---------------------------add by ak 保存系统相册使用的变量
  69. // 导出有水印的正片
  70. private var watermarkMovieExporter: PQCompositionExporter!
  71. // 带水印 MP4 导出地址
  72. private var watermarkMovieLocalURL: URL?
  73. // 导出片尾
  74. private var endMovieExporter: PQCompositionExporter!
  75. // 导出片尾 MP4 地址
  76. private var endMovieLocalURL: URL?
  77. // 保存相册的合成视频地址 水印+片尾 MP4 地址
  78. private var saveMovieLocalURL: URL?
  79. //----------------------------
  80. // 预览大小
  81. private var preViewSize: CGSize {
  82. switch aspectRatio {
  83. case let .origin(width, height):
  84. var tempHeight: CGFloat = 0
  85. var tempWidth: CGFloat = 0
  86. if width > height {
  87. tempWidth = maxWidth
  88. tempHeight = (maxWidth * height / width)
  89. if tempHeight > maxHeight {
  90. tempHeight = maxHeight
  91. tempWidth = (maxHeight * width / height)
  92. }
  93. } else {
  94. tempHeight = maxHeight
  95. tempWidth = (maxHeight * width / height)
  96. if tempWidth > maxWidth {
  97. tempWidth = maxWidth
  98. tempHeight = (maxWidth * height / width)
  99. }
  100. }
  101. if tempHeight.isNaN || tempWidth.isNaN {
  102. return CGSize.zero
  103. } else {
  104. return CGSize(width: tempWidth, height: tempHeight)
  105. }
  106. case .oneToOne:
  107. if maxWidth > maxHeight {
  108. return CGSize(width: maxHeight, height: maxHeight)
  109. } else {
  110. return CGSize(width: maxWidth, height: maxWidth)
  111. }
  112. case .sixteenToNine:
  113. return CGSize(width: maxWidth, height: maxWidth * 9.0 / 16.0)
  114. case .nineToSixteen:
  115. return CGSize(width: maxHeight * 9.0 / 16.0, height: maxHeight)
  116. default:
  117. break
  118. }
  119. return CGSize(width: maxHeight, height: maxHeight)
  120. }
  121. // 背景音乐
  122. var audioMixModel: PQVoiceModel?
  123. // 画面比例
  124. var aspectRatio: aspectRatio?
  125. // 导出的项目数据
  126. var editProjectModel: PQEditProjectModel? {
  127. didSet {
  128. aspectRatio = PQPlayerViewModel.videoCanvasTypeToAspectRatio(projectModel: editProjectModel)
  129. var totalDuration: Float64 = 0
  130. if editProjectModel?.sData?.sections.count ?? 0 > 0 {
  131. for section in (editProjectModel?.sData?.sections)! {
  132. totalDuration = totalDuration + section.sectionDuration
  133. }
  134. }
  135. editProjectModel?.sData?.videoMetaData?.duration = totalDuration
  136. if editProjectModel?.sData?.sections != nil, (editProjectModel?.sData?.sections.count ?? 0) > 0 {
  137. // 查找出背景图并设置
  138. var coverImageMaterialsModel: PQEditVisionTrackMaterialsModel?
  139. for section in (editProjectModel?.sData?.sections)! {
  140. if coverImageMaterialsModel != nil {
  141. break
  142. }
  143. coverImageMaterialsModel = section.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first
  144. }
  145. if coverImageMaterialsModel != nil {
  146. coverImage = coverImageMaterialsModel?.getCoverImage()
  147. playerHeaderView.image = coverImage
  148. playerHeaderView.contentMode = coverImageMaterialsModel!.canvasFillType == stickerContentMode.aspectFitStr.rawValue ? .scaleAspectFill : .scaleAspectFit
  149. }
  150. }
  151. }
  152. }
  153. /// 所有需要导出的filter
  154. var filters: Array = Array<ImageProcessingOperation>.init()
  155. /// 预览背景页
  156. lazy var bgTopView: UIView = {
  157. let bgTopView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: maxHeight))
  158. bgTopView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  159. return bgTopView
  160. }()
  161. // 预览界面
  162. var playerHeaderView: UIImageView = {
  163. let playerHeaderView = UIImageView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 0))
  164. playerHeaderView.isUserInteractionEnabled = true
  165. playerHeaderView.contentMode = .scaleAspectFit
  166. playerHeaderView.clipsToBounds = true
  167. return playerHeaderView
  168. }()
  169. // add by ak 播放器的封面 为了不和原有的播放器层级单独添加一个 view
  170. lazy var playerHeaderCoverImageView: UIImageView = {
  171. let playerHeaderCoverImageView = UIImageView()
  172. playerHeaderCoverImageView.isUserInteractionEnabled = true
  173. playerHeaderCoverImageView.contentMode = .scaleAspectFit
  174. playerHeaderCoverImageView.clipsToBounds = true
  175. let playBtn = UIButton(type: .custom)
  176. playBtn.setImage(UIImage.moduleImage(named: "icon_video_play", moduleName: "BFFramework",isAssets: false), for: .normal)
  177. playBtn.tag = 4
  178. playBtn.isUserInteractionEnabled = false
  179. playerHeaderCoverImageView.addSubview(playBtn)
  180. playerHeaderCoverImageView.isHidden = true
  181. return playerHeaderCoverImageView
  182. }()
  183. /// 播放器
  184. lazy var avPlayer: AVPlayer = {
  185. let avPlayer = AVPlayer()
  186. NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem, queue: .main) { [weak self] notify in
  187. BFLog(message: "AVPlayerItemDidPlayToEndTime = \(notify)")
  188. avPlayer.seek(to: CMTime.zero)
  189. if self?.playerHeaderCoverImageView.image != nil {
  190. self?.playerHeaderCoverImageView.isHidden = false
  191. }
  192. self?.playBtn.isHidden = false
  193. }
  194. NotificationCenter.default.addObserver(forName: .AVPlayerItemNewErrorLogEntry, object: avPlayer.currentItem, queue: .main) { notify in
  195. BFLog(message: "AVPlayerItemNewErrorLogEntry = \(notify)")
  196. }
  197. NotificationCenter.default.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: avPlayer.currentItem, queue: .main) { notify in
  198. BFLog(message: "AVPlayerItemFailedToPlayToEndTime = \(notify)")
  199. }
  200. NotificationCenter.default.addObserver(forName: .AVPlayerItemPlaybackStalled, object: avPlayer.currentItem, queue: .main) { notify in
  201. BFLog(message: "AVPlayerItemPlaybackStalled = \(notify)")
  202. }
  203. avPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1000), queue: .main) { [weak self] _ in
  204. let progress = CMTimeGetSeconds(avPlayer.currentItem?.currentTime() ?? CMTime.zero) / CMTimeGetSeconds(avPlayer.currentItem?.duration ?? CMTime.zero)
  205. if progress >= 1 {
  206. self?.playBtn.isHidden = false
  207. }
  208. }
  209. return avPlayer
  210. }()
  211. /// 预览layer
  212. lazy var playerLayer: AVPlayerLayer = {
  213. let playerLayer = AVPlayerLayer(player: avPlayer)
  214. playerLayer.frame = playerHeaderView.bounds
  215. return playerLayer
  216. }()
  217. /// 播放按钮
  218. lazy var playBtn: UIButton = {
  219. let playBtn = UIButton(type: .custom)
  220. playBtn.frame = CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
  221. playBtn.setImage(UIImage.moduleImage(named: "icon_video_play", moduleName: "BFFramework",isAssets: false), for: .normal)
  222. playBtn.tag = 4
  223. playBtn.isHidden = true
  224. playBtn.isUserInteractionEnabled = false
  225. return playBtn
  226. }()
  227. // progressTipsLab
  228. lazy var progressTipsLab: UILabel = {
  229. let progressTipsLab = UILabel()
  230. progressTipsLab.textAlignment = .center
  231. progressTipsLab.font = UIFont.systemFont(ofSize: 14, weight: .medium)
  232. progressTipsLab.numberOfLines = 2
  233. progressTipsLab.textColor = UIColor.white
  234. let attributedText = NSMutableAttributedString(string: "0%\n视频正在处理中,请勿离开")
  235. attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: 2))
  236. progressTipsLab.attributedText = attributedText
  237. progressTipsLab.addShadow()
  238. return progressTipsLab
  239. }()
  240. // 进度条
  241. lazy var progressView: UIProgressView = {
  242. let progressView = UIProgressView(progressViewStyle: .default)
  243. progressView.trackTintColor = UIColor(white: 0, alpha: 0.5)
  244. progressView.progressTintColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  245. progressView.transform = CGAffineTransform(scaleX: 1.0, y: playerHeaderView.frame.height)
  246. return progressView
  247. }()
  248. // 提示
  249. lazy var remindLab: UILabel = {
  250. let remindLab = UILabel()
  251. remindLab.font = UIFont.boldSystemFont(ofSize: 18)
  252. remindLab.textColor = PQBFConfig.shared.styleTitleColor
  253. remindLab.textAlignment = .center
  254. remindLab.numberOfLines = 2
  255. remindLab.backgroundColor = .clear
  256. remindLab.text = "为你的大作起个响亮的标题\n分享秀一下🎉"
  257. return remindLab
  258. }()
  259. // 输入框背景
  260. lazy var inputBackView: UIView = {
  261. let inputBackView = UIView()
  262. inputBackView.backgroundColor = .clear
  263. inputBackView.layer.cornerRadius = 7
  264. inputBackView.layer.borderWidth = 2
  265. inputBackView.layer.borderColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue).cgColor
  266. return inputBackView
  267. }()
  268. // 手势提示
  269. lazy var pinView: UIImageView = {
  270. let pinView = UIImageView()
  271. pinView.kf.setImage(with: URL(fileURLWithPath: (currentBundlePath()!.path(forResource: "editCoverPin", ofType: ".gif")!)))
  272. return pinView
  273. }()
  274. // 封面
  275. lazy var coverImageView: UIImageView = {
  276. let coverImageView = UIImageView()
  277. coverImageView.isUserInteractionEnabled = true
  278. coverImageView.backgroundColor = .clear
  279. coverImageView.contentMode = .scaleToFill
  280. return coverImageView
  281. }()
  282. // 封面标题
  283. lazy var coverImageTitle: UILabel = {
  284. let coverImageTitle = UILabel()
  285. coverImageTitle.text = "换封面"
  286. coverImageTitle.textAlignment = .center
  287. coverImageTitle.backgroundColor = UIColor(red: 0.22, green: 0.26, blue: 0.35, alpha: 0.5)
  288. coverImageTitle.isUserInteractionEnabled = true
  289. coverImageTitle.textColor = .white
  290. coverImageTitle.font = UIFont.boldSystemFont(ofSize: 12)
  291. return coverImageTitle
  292. }()
  293. // 标题
  294. lazy var titleLabel: UILabel = {
  295. let titleLabel = UILabel()
  296. titleLabel.numberOfLines = 2
  297. titleLabel.isUserInteractionEnabled = true
  298. titleLabel.textColor = UIColor.hexColor(hexadecimal: "#ABABAB")
  299. titleLabel.textAlignment = .left
  300. titleLabel.font = UIFont.systemFont(ofSize: 17)
  301. let ges = UITapGestureRecognizer(target: self, action: #selector(titleLabelClick))
  302. titleLabel.addGestureRecognizer(ges)
  303. return titleLabel
  304. }()
  305. // 编辑发布标题
  306. lazy var publicTitleView: PQEditPublicTitleView = {
  307. let publicTitleView = PQEditPublicTitleView()
  308. publicTitleView.isHidden = true
  309. publicTitleView.confirmBtnClock = { [weak self] title in
  310. BFLog(message: "传出的 title is :\(String(describing: title))")
  311. if title?.count != 0 && title != self?.titleLabel.text {
  312. self?.changPlayerIsPause(isPause: false)
  313. // 判断文字是否有效
  314. var inputText = ""
  315. inputText = title?.replacingOccurrences(of: "\n", with: "") ?? ""
  316. inputText = inputText.replacingOccurrences(of: " ", with: "")
  317. if inputText.count > 0 {
  318. self?.setTitleText(text: title ?? "", textColor: .black)
  319. // 更新数据
  320. self?.videoData?.title = title
  321. self?.updateCoverImagegOrTitle()
  322. }
  323. }
  324. }
  325. publicTitleView.viewIsHiddenCallBack = { [weak self] in
  326. self?.changPlayerIsPause(isPause: false)
  327. }
  328. return publicTitleView
  329. }()
  330. // 编辑发布封面
  331. lazy var publicEditCoverView: PQEditPublicCoverImageView = {
  332. let publicEditCoverView = PQEditPublicCoverImageView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  333. publicEditCoverView.isHidden = true
  334. return publicEditCoverView
  335. }()
  336. // 分享到朋友圈
  337. lazy var shareWechatBtn: UIButton = {
  338. let shareWechatBtn = UIButton(type: .custom)
  339. shareWechatBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
  340. shareWechatBtn.setImage(UIImage.moduleImage(named: "reCreate_opration_wechat", moduleName: "BFFramework",isAssets: false), for: .normal)
  341. shareWechatBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  342. shareWechatBtn.addCorner(corner: 6)
  343. shareWechatBtn.tag = 2
  344. shareWechatBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  345. return shareWechatBtn
  346. }()
  347. // 分享到好友
  348. lazy var shareFriendBtn: UIButton = {
  349. let shareFriendBtn = UIButton(type: .custom)
  350. shareFriendBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
  351. shareFriendBtn.setImage(UIImage.moduleImage(named: "reCreate_opration_friend", moduleName: "BFFramework",isAssets: false), for: .normal)
  352. shareFriendBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  353. shareFriendBtn.addCorner(corner: 6)
  354. shareFriendBtn.tag = 1
  355. shareFriendBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  356. return shareFriendBtn
  357. }()
  358. // 关闭
  359. lazy var finishedBtn: UIButton = {
  360. let finishedBtn = UIButton(type: .custom)
  361. finishedBtn.setTitle("完成", for: .normal)
  362. finishedBtn.setTitleColor(UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue), for: .normal)
  363. finishedBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  364. finishedBtn.backgroundColor = .clear
  365. finishedBtn.tag = 3
  366. finishedBtn.addCorner(corner: 3)
  367. finishedBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  368. return finishedBtn
  369. }()
  370. /// 背景View
  371. lazy var oprationBgView: UIView = {
  372. let oprationBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei))
  373. oprationBgView.backgroundColor = .clear
  374. return oprationBgView
  375. }()
  376. // 除了播放器以外的 下半部分操作区
  377. lazy var bottomOprationBgView: UIView = {
  378. let bottomOprationBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei + maxHeight, width: cScreenWidth, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei - maxHeight))
  379. bottomOprationBgView.backgroundColor = .clear
  380. bottomOprationBgView.isHidden = true
  381. return bottomOprationBgView
  382. }()
  383. ///保存视频到相册提示
  384. lazy var saveVideoTipsBgView: UIView = {
  385. let saveVideoTipsBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 40))
  386. saveVideoTipsBgView.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.4)
  387. saveVideoTipsBgView.isHidden = true
  388. saveVideoTipsBgView.alpha = 1
  389. return saveVideoTipsBgView
  390. }()
  391. lazy var saveVideoTipsLabel: UILabel = {
  392. let saveVideoTipsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: 40))
  393. saveVideoTipsLabel.textColor = .white
  394. saveVideoTipsLabel.textAlignment = .center
  395. saveVideoTipsLabel.font = UIFont.boldSystemFont(ofSize: 17)
  396. saveVideoTipsLabel.text = "视频保存中..."
  397. saveVideoTipsLabel.sizeToFit()
  398. return saveVideoTipsLabel
  399. }()
  400. // 保存重试
  401. lazy var saveRetryBtn: UIButton = {
  402. let finishedBtn = UIButton(type: .custom)
  403. finishedBtn.setTitle("重试", for: .normal)
  404. finishedBtn.setTitleColor(UIColor.white, for: .normal)
  405. finishedBtn.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
  406. finishedBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  407. finishedBtn.tag = 97
  408. finishedBtn.isHidden = true
  409. finishedBtn.addCorner(corner: 5)
  410. finishedBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  411. return finishedBtn
  412. }()
  413. override func backBtnClick() {
  414. if isExportSuccess {
  415. navigationController?.popViewController(animated: true)
  416. } else {
  417. view.endEditing(true)
  418. let remindData = PQBaseModel()
  419. remindData.title = "编辑的内容,将不会被保存"
  420. remindView = PQRemindView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  421. remindView?.isBanned = true
  422. remindView?.confirmBtn.setTitle("确认", for: .normal)
  423. remindView?.cancelBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#333333"), for: .normal)
  424. remindView?.confirmBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#EE0051"), for: .normal)
  425. UIApplication.shared.keyWindow?.addSubview(remindView!)
  426. remindView?.remindData = remindData
  427. remindView?.remindBlock = { [weak self] item, _ in
  428. if item.tag == 2 {
  429. // 取消导出
  430. if self?.exporter != nil {
  431. self?.exporter.cancel()
  432. }
  433. self?.navigationController?.popViewController(animated: true)
  434. }
  435. }
  436. }
  437. }
  438. override func viewDidLoad() {
  439. super.viewDidLoad()
  440. // 注册上传成功的通知
  441. addNotification(self, selector: #selector(uploadSuccess(notify:)), name: cUploadSuccessKey, object: nil)
  442. PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
  443. leftButton(image: nil, tintColor: PQBFConfig.shared.styleTitleColor)
  444. navHeadImageView?.backgroundColor = UIColor.clear
  445. lineView?.removeFromSuperview()
  446. view.addSubview(bgTopView)
  447. playerHeaderView.frame = CGRect(origin: CGPoint(x: (cScreenWidth - preViewSize.width) / 2, y: (maxHeight - preViewSize.height) / 2), size: preViewSize)
  448. let ges = UITapGestureRecognizer(target: self, action: #selector(playVideo))
  449. playerHeaderView.addGestureRecognizer(ges)
  450. if playerLayer.superlayer == nil {
  451. playerHeaderView.layer.insertSublayer(playerLayer, at: 0)
  452. }
  453. playerHeaderView.addSubview(playBtn)
  454. playerHeaderView.addSubview(progressView)
  455. view.addSubview(oprationBgView)
  456. oprationBgView.addSubview(progressTipsLab)
  457. // 添加导出view
  458. bgTopView.addSubview(playerHeaderView)
  459. playerHeaderCoverImageView.frame = playerHeaderView.frame
  460. playerHeaderCoverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(playVideo)))
  461. (playerHeaderCoverImageView.viewWithTag(4))?.frame =
  462. CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
  463. bgTopView.addSubview(playerHeaderCoverImageView)
  464. view.addSubview(bottomOprationBgView)
  465. bottomOprationBgView.addSubview(remindLab)
  466. bottomOprationBgView.addSubview(shareWechatBtn)
  467. bottomOprationBgView.addSubview(shareFriendBtn)
  468. bottomOprationBgView.addSubview(finishedBtn)
  469. bottomOprationBgView.addSubview(inputBackView)
  470. bottomOprationBgView.addSubview(pinView)
  471. inputBackView.addSubview(coverImageView)
  472. coverImageView.addSubview(coverImageTitle)
  473. inputBackView.addSubview(titleLabel)
  474. view.addSubview(publicTitleView)
  475. view.addSubview(publicEditCoverView)
  476. view.addSubview(saveVideoTipsBgView)
  477. saveVideoTipsBgView.addSubview(saveVideoTipsLabel)
  478. saveVideoTipsBgView.addSubview(saveRetryBtn)
  479. saveVideoTipsLabel.snp.makeConstraints { make in
  480. make.top.height.equalToSuperview()
  481. make.centerX.equalToSuperview()
  482. }
  483. saveRetryBtn.snp.makeConstraints { make in
  484. make.left.equalTo(saveVideoTipsLabel.snp.right).offset(10)
  485. make.top.equalTo(6)
  486. make.bottom.equalTo(-6)
  487. make.width.equalTo(50)
  488. }
  489. coverImageTitle.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  490. coverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  491. progressView.snp.makeConstraints { make in
  492. make.left.right.centerY.equalTo(playerHeaderView)
  493. make.height.equalTo(3)
  494. }
  495. progressTipsLab.snp.makeConstraints { make in
  496. make.centerX.equalToSuperview()
  497. make.top.equalToSuperview().offset(((preViewSize.height - 90) / 2) + ((maxHeight - preViewSize.height) / 2))
  498. make.width.equalToSuperview()
  499. make.height.equalTo(90)
  500. }
  501. finishedBtn.snp.makeConstraints { make in
  502. make.centerX.equalToSuperview()
  503. make.bottom.equalToSuperview().offset(-cSafeAreaHeight)
  504. make.width.equalTo(100)
  505. make.height.equalTo(22)
  506. }
  507. shareWechatBtn.snp.makeConstraints { make in
  508. make.right.equalTo(view.snp.centerX).offset(-cDefaultMargin)
  509. make.width.equalTo(164)
  510. make.height.equalTo(52)
  511. make.bottom.equalTo(finishedBtn.snp.top).offset(-32)
  512. }
  513. shareFriendBtn.snp.makeConstraints { make in
  514. make.left.equalTo(view.snp.centerX).offset(cDefaultMargin)
  515. make.width.bottom.height.equalTo(shareWechatBtn)
  516. }
  517. inputBackView.snp.makeConstraints { make in
  518. make.centerX.equalToSuperview()
  519. make.bottom.equalTo(shareWechatBtn.snp.top).offset(-16)
  520. make.width.equalTo(343)
  521. make.height.equalTo(109)
  522. }
  523. // 根据横竖屏设置不同的 UI
  524. let isWidth: Bool = (Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)) >= 1
  525. var coverImageViewHeight = 50.0 * Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  526. if coverImageViewHeight > 89 {
  527. coverImageViewHeight = 89
  528. }
  529. coverImageView.snp.makeConstraints { make in
  530. make.left.equalToSuperview().offset(12)
  531. make.width.equalTo(50)
  532. make.top.equalToSuperview().offset(10)
  533. make.height.equalTo(coverImageViewHeight)
  534. }
  535. coverImageTitle.snp.makeConstraints { make in
  536. make.left.equalToSuperview()
  537. make.width.equalTo(50)
  538. make.top.equalTo(coverImageView.snp.bottom).offset(isWidth ? 0 : -23)
  539. make.height.equalTo(23)
  540. }
  541. remindLab.snp.makeConstraints { make in
  542. make.centerX.equalToSuperview()
  543. make.bottom.equalTo(inputBackView.snp.top).offset(-16).priorityHigh()
  544. make.height.equalTo(44)
  545. make.top.greaterThanOrEqualTo(5)
  546. make.bottom.lessThanOrEqualTo(inputBackView.snp.top).offset(-5)
  547. }
  548. titleLabel.snp.makeConstraints { make in
  549. make.height.equalTo(48)
  550. make.left.equalTo(coverImageView.snp.right).offset(12)
  551. make.right.equalToSuperview().offset(-14)
  552. make.top.equalToSuperview().offset(10)
  553. }
  554. pinView.snp.makeConstraints { make in
  555. make.height.width.equalTo(72)
  556. make.right.equalToSuperview()
  557. make.bottom.equalTo(inputBackView.snp.bottom)
  558. }
  559. publicTitleView.snp.makeConstraints { make in
  560. make.height.equalTo(cScreenHeigth)
  561. make.width.equalTo(cScreenWidth)
  562. make.bottom.equalToSuperview()
  563. }
  564. // 取消所有的导出
  565. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  566. exportSession.cancelExport()
  567. }
  568. // 开始导出
  569. appendAudio()
  570. /// 保存草稿
  571. saveDraftbox()
  572. // 曝光上报:窗口曝光
  573. PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_publishSyncedUp, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:窗口曝光)")
  574. // 取推荐标题
  575. getTitles()
  576. networkStausListen()
  577. }
  578. override func viewWillAppear(_ animated: Bool) {
  579. super.viewWillAppear(animated)
  580. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  581. PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  582. DispatchQueue.main.async {
  583. UIApplication.shared.isIdleTimerDisabled = true
  584. }
  585. //从相册选择一个照片后回调
  586. addNotification(self, selector: #selector(imageSelectedImage(notify:)), name: cSelectedImageSuccessKey, object: nil)
  587. #if swift(>=4.2)
  588. let memoryNotification = UIApplication.didReceiveMemoryWarningNotification
  589. _ = UIApplication.willTerminateNotification
  590. _ = UIApplication.didEnterBackgroundNotification
  591. #else
  592. let memoryNotification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning
  593. let terminateNotification = NSNotification.Name.UIApplicationWillTerminate
  594. let enterbackgroundNotification = NSNotification.Name.UIApplicationDidEnterBackground
  595. #endif
  596. NotificationCenter.default.addObserver(
  597. self, selector: #selector(clearMemoryCache), name: memoryNotification, object: nil
  598. )
  599. }
  600. @objc public func clearMemoryCache() {
  601. BFLog(message: "收到内存警告")
  602. }
  603. override func viewWillDisappear(_ animated: Bool) {
  604. super.viewWillDisappear(animated)
  605. DispatchQueue.main.async {
  606. UIApplication.shared.isIdleTimerDisabled = false
  607. }
  608. }
  609. deinit {
  610. BFLog(1, message: "发布界面析构release")
  611. view.endEditing(true)
  612. PQNotification.removeObserver(self)
  613. // 取消导出
  614. if exporter != nil {
  615. exporter.cancel()
  616. }
  617. if watermarkMovieExporter != nil{
  618. watermarkMovieExporter.cancel()
  619. }
  620. if endMovieExporter != nil{
  621. endMovieExporter.cancel()
  622. }
  623. exporter?.input?.removeAllTargets()
  624. watermarkMovieExporter?.input?.removeAllTargets()
  625. endMovieExporter?.input?.removeAllTargets()
  626. avPlayer.pause()
  627. avPlayer.replaceCurrentItem(with: nil)
  628. // 点击上报:返回按钮
  629. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)")
  630. }
  631. // MARK: - 网络监控
  632. func networkStausListen(){
  633. manager?.startListening(onUpdatePerforming: { status in
  634. if status == .reachable(.cellular) || status == .reachable(.ethernetOrWiFi) {
  635. cHiddenHUB(superView: nil)
  636. } else {
  637. cShowHUB(superView: nil, msg: "当前网络不佳,请尝试重新连接")
  638. }
  639. })
  640. }
  641. }
  642. // MARK: - 导出/上传/下载及其他方法
  643. /// 导出/上传/下载及其他方法
  644. extension PQStuckPointPublicController {
  645. /// fp1 - 导出视频
  646. /// 开始导出视频
  647. /// 合并声音
  648. /// - Parameter urls: 所有音频的URL 是全路径方便复用
  649. /// - Parameter completeHander: 返回的 URL 全路径的 URL 如果要保存替换掉前缀
  650. func mergeAudios(originAsset: AVURLAsset, mTotalDuration: Float, clipAudioRange: CMTimeRange = CMTimeRange.zero, mStartTime _: CMTime = .zero, completeHander: @escaping (_ fileURL: URL?) -> Void) {
  651. let timeInterval: TimeInterval = Date().timeIntervalSince1970
  652. let composition = AVMutableComposition()
  653. let originaDuration = CMTimeGetSeconds(clipAudioRange.duration)
  654. BFLog(message: "处理主音频 原始时长startTime = \(originaDuration) 要显示时长totalDuration = \(mTotalDuration)")
  655. //originaDuration = 37.616768 mTotalDuration = 37.616776 TODO 都用 INT 微秒级
  656. if Float64(String(format: "%.3f",mTotalDuration)) ?? 0.0 <= Float64(String(format: "%.3f",originaDuration)) ?? 0.0 {
  657. BFLog(message: "不用拼接音频文件 \(originAsset.url) 时长is \(CMTimeGetSeconds(originAsset.duration))")
  658. completeHander(originAsset.url)
  659. return
  660. }
  661. // 整倍数
  662. let count = Int(mTotalDuration) / Int(originaDuration)
  663. // 有余数多 clip 一整段
  664. let row = mTotalDuration - Float(count) * Float(originaDuration)
  665. // 已经拼接的总时长
  666. var totalDuration: CMTime = .zero
  667. // 第一段的时长
  668. var duration: CMTime = .zero
  669. // 第一段的区间
  670. var timeRange: CMTimeRange = CMTimeRange.zero
  671. if count > 0 {
  672. for index in 0 ..< count {
  673. // 第0段从0开始到推荐的结束,播放器的开始时间不是从0开始的
  674. duration = CMTime(value: CMTimeValue((CMTimeGetSeconds(clipAudioRange.end)) * Double(playerTimescaleInt)), timescale: playerTimescaleInt)
  675. BFLog(message: "每一个文件的 duration \(CMTimeGetSeconds(duration))")
  676. var timeRange = CMTimeRangeMake(start: .zero, duration: duration)
  677. if index != 0 {
  678. // (CMTimeGetSeconds(clipAudioRange.end) - CMTimeGetSeconds(mStartTime))为用户选择的第一段时长
  679. timeRange = clipAudioRange
  680. }
  681. BFLog(message: "合并的文件地址: \(originAsset.url)")
  682. let audioAsset = originAsset
  683. let tracks = audioAsset.tracks(withMediaType: .audio)
  684. if tracks.count == 0 {
  685. BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
  686. completeHander(URL(string: ""))
  687. return
  688. }
  689. let assetTrack: AVAssetTrack = tracks[0]
  690. let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
  691. do {
  692. //
  693. try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
  694. } catch {
  695. BFLog(message: "error is \(error)")
  696. completeHander(URL(string: ""))
  697. return
  698. }
  699. totalDuration = CMTimeAdd(totalDuration, timeRange.duration)
  700. }
  701. }
  702. if row > 0 {
  703. duration = CMTime(value: CMTimeValue(Float(CMTimeGetSeconds(totalDuration)) * Float(playerTimescaleInt)), timescale: playerTimescaleInt)
  704. timeRange = CMTimeRange(start: clipAudioRange.start, duration: CMTime(value: Int64(Double(row) * Double(playerTimescaleInt)), timescale: playerTimescaleInt))
  705. BFLog(message: "合并的文件地址: \(originAsset.url)")
  706. let audioAsset = originAsset
  707. let tracks = audioAsset.tracks(withMediaType: .audio)
  708. if tracks.count == 0 {
  709. BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
  710. completeHander(URL(string: ""))
  711. return
  712. }
  713. let assetTrack: AVAssetTrack = tracks[0]
  714. let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
  715. do {
  716. //
  717. try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
  718. } catch {
  719. BFLog(message: "合并音频 error is \(error)")
  720. completeHander(URL(string: ""))
  721. return
  722. }
  723. totalDuration = CMTimeAdd(totalDuration, timeRange.duration)
  724. }
  725. let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
  726. BFLog(message: "assetExport.supportedFileTypes is \(String(describing: assetExport?.supportedFileTypes))")
  727. assetExport?.outputFileType = .m4a
  728. // XXXX 注意文件名的后缀要和outputFileType 一致 否则会导出失败
  729. var audioFilePath = exportAudiosDirectory
  730. if !directoryIsExists(dicPath: audioFilePath) {
  731. BFLog(message: "文件夹不存在")
  732. createDirectory(path: audioFilePath)
  733. }
  734. audioFilePath.append("merge_\(timeInterval).m4a")
  735. let fileUrl = URL(fileURLWithPath: audioFilePath)
  736. assetExport?.outputURL = fileUrl
  737. assetExport?.exportAsynchronously {
  738. if assetExport!.status == .completed {
  739. let audioAsset = AVURLAsset(url: fileUrl, options: avAssertOptions)
  740. BFLog(1, message: "拼接声音文件 完成 \(fileUrl) 时长is \(CMTimeGetSeconds(audioAsset.duration))")
  741. completeHander(fileUrl)
  742. } else {
  743. print("拼接出错 \(String(describing: assetExport?.error))")
  744. completeHander(URL(string: ""))
  745. }
  746. }
  747. }
  748. func appendAudio() {
  749. //更新一下假进度
  750. updatePublicCurrentProgress(useProgress: 0.01)
  751. let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
  752. let startMergeTime = CFAbsoluteTimeGetCurrent()
  753. mergeAudios(originAsset: inputAsset, mTotalDuration: finallyUserAudioTime, clipAudioRange: clipAudioRange, mStartTime: CMTime(value: CMTimeValue((mStickers?.first?.timelineIn ?? 0.0) * Float64(playerTimescaleInt)), timescale: playerTimescaleInt)) { [weak self] completURL in
  754. if completURL != nil {
  755. let asset = AVURLAsset(url: completURL!, options: nil)
  756. BFLog(message: "拼接后音频时长\(asset.duration.seconds) url is \(String(describing: completURL)) 用时\(CFAbsoluteTimeGetCurrent() - startMergeTime)")
  757. //导出不带水印的正片
  758. self?.beginExport(inputAsset: asset)
  759. //导出带水印的正片
  760. self?.beginExportWatermarkMovie(inputAsset:asset)
  761. }else{
  762. cShowHUB(superView: self?.view, msg: "合成失败请重试。")
  763. }
  764. }
  765. }
  766. func beginExport(inputAsset: AVURLAsset!) {
  767. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  768. BFLog(message: "项目段落错误❌")
  769. return
  770. }
  771. // 输出视频地址
  772. var outPutMP4Path = exportVideosDirectory
  773. if !directoryIsExists(dicPath: outPutMP4Path) {
  774. BFLog(message: "文件夹不存在")
  775. createDirectory(path: outPutMP4Path)
  776. }
  777. outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
  778. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  779. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  780. exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
  781. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  782. if mStickers != nil {
  783. for stick in mStickers! {
  784. if stick.type == StickerType.VIDEO.rawValue {
  785. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
  786. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  787. if Int(cbr ?? 0) > orgeBitRate {
  788. orgeBitRate = Int(cbr ?? 0)
  789. }
  790. }
  791. }
  792. }
  793. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  794. exporter.showGaussianBlur = true
  795. if exporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
  796. BFLog(message: "开始导出 \(String(describing: playeTimeRange.start)) 结束 \(String(describing: playeTimeRange.end))")
  797. exporter.start(playeTimeRange: playeTimeRange)
  798. BFLog(message: "开始导出")
  799. }
  800. exporter.progressClosure = { [weak self] _, _, progress in
  801. BFLog(message: "正片合成进度 \(progress)")
  802. let useProgress = progress > 1 ? 1 : progress
  803. if progress > 0, Int(useProgress * 100) > (self?.exportProgrss ?? 0) {
  804. // 更新进度
  805. self?.updatePublicCurrentProgress(useProgress: useProgress * 0.88)
  806. }
  807. }
  808. exporter.completion = { [weak self] url in
  809. BFLog(message: "无水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: (url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)).duration))")
  810. // 导出完成后取消导出
  811. if self?.exporter != nil {
  812. self?.exporter.cancel()
  813. }
  814. self?.remindView?.removeFromSuperview()
  815. if !(self?.isExportSuccess ?? false) {
  816. self?.isExportSuccess = true
  817. self?.exportEndDate = Date().timeIntervalSince1970
  818. BFLog(message: "视频导出完成-开始去发布视频 总时长为\((self?.exportEndDate ?? 0) - (self?.startExportDate ?? 0) * 1000)")
  819. self?.exportLocalURL = url
  820. /// fp2-1-1 - 请求权限
  821. // self?.authorizationStatus()
  822. /// fp2-2 - 保存草稿
  823. // self?.saveDraftbox()
  824. /// fp2 - 处理视频数据
  825. self?.dealWithVideoData()
  826. }
  827. }
  828. }
  829. /// fp2-1-1 - 请求权限
  830. func authorizationStatus() {
  831. let authStatus = PHPhotoLibrary.authorizationStatus()
  832. if authStatus == .notDetermined {
  833. // 第一次触发授权 alert
  834. PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in
  835. if status != .authorized {
  836. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  837. } else {
  838. /// fp2-1-2 - 保存视频到相册
  839. self?.saveStuckPointVideo()
  840. }
  841. }
  842. } else if authStatus == .authorized {
  843. /// fp2-1-2 - 保存视频到相册
  844. saveStuckPointVideo()
  845. } else {
  846. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  847. }
  848. }
  849. /// fp2-1-2 - 保存视频到相册
  850. /// - Parameter localPath: localPath description
  851. /// - Returns: <#description#>
  852. func saveStuckPointVideo() {
  853. if(saveMovieLocalURL == nil){
  854. BFLog(message: "保存相册的视频导出地址无效!!!")
  855. saveVideoTipsLabel.text = "视频保存失败"
  856. saveRetryBtn.isHidden = false
  857. saveVideoTipsBgView.isHidden = false
  858. // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  859. // self?.saveVideoTipsBgView.isHidden = true
  860. // }
  861. return
  862. }
  863. let authStatus = PHPhotoLibrary.authorizationStatus()
  864. if authStatus == .authorized {
  865. let photoLibrary = PHPhotoLibrary.shared()
  866. photoLibrary.performChanges({ [weak self] in
  867. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.saveMovieLocalURL)!)
  868. }) { [weak self] isFinished, _ in
  869. DispatchQueue.main.async { [weak self] in
  870. if self?.view != nil {
  871. if isFinished {
  872. self?.saveVideoTipsLabel.text = "视频已保存到相册"
  873. self?.saveRetryBtn.isHidden = true
  874. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  875. self?.saveVideoTipsBgView.isHidden = true
  876. }
  877. } else {
  878. self?.saveVideoTipsLabel.text = "视频保存失败"
  879. self?.saveRetryBtn.isHidden = false
  880. // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  881. // self?.saveVideoTipsBgView.isHidden = true
  882. // }
  883. }
  884. }
  885. }
  886. }
  887. } else {
  888. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  889. }
  890. }
  891. /// fp2-2 - 保存草稿
  892. /// - Returns: <#description#>
  893. @objc func saveDraftbox() {
  894. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false)
  895. if sdata != nil, (sdata?.count ?? 0) > 0 {
  896. DispatchQueue.global().async { [weak self] in
  897. PQBaseViewModel.saveDraftbox(draftboxId: self?.editProjectModel?.draftboxId, title: self?.editProjectModel?.sData?.videoMetaData?.title, coverUrl: self?.editProjectModel?.sData?.videoMetaData?.coverUrl, sdata: sdata!, videoFromScene: .stuckPoint, copyType: (self?.audioMixModel != nil && self?.audioMixModel?.originProjectId != nil && (self?.audioMixModel?.originProjectId?.count ?? 0) > 0) ? 3 : nil, originProjectId: self?.audioMixModel?.originProjectId) { [weak self] draftboxInfo, _ in
  898. if draftboxInfo != nil {
  899. self?.editProjectModel?.draftboxId = draftboxInfo?["draftboxId"] as? String ?? ""
  900. self?.editProjectModel?.sData?.videoMetaData?.title = draftboxInfo?["title"] as? String ?? ""
  901. self?.editProjectModel?.sData?.videoMetaData?.coverUrl = draftboxInfo?["coverUrl"] as? String ?? ""
  902. self?.editProjectModel?.dataVersionCode = draftboxInfo?["dataVersionCode"] as? Int ?? 0
  903. BFLog(message: "保存远程的草稿成功")
  904. self?.isSaveDraftSuccess = true
  905. /// fp3 - 保存项目
  906. self?.saveProject()
  907. } else {
  908. // 保存草稿失败-播放视频
  909. // self?.publicEnd(isError: true)
  910. }
  911. }
  912. }
  913. } else {
  914. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  915. // 保存草稿失败-播放视频
  916. publicEnd(isError: true)
  917. }
  918. }
  919. /// fp3 - 保存项目
  920. /// - Returns: description
  921. func saveProject() {
  922. if isSaveDraftSuccess {
  923. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false) ?? ""
  924. let draftboxId: String? = editProjectModel?.draftboxId
  925. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint, rhythmMode: rhythmMode) { [weak self] projectId, msg in
  926. BFLog(message: "生成的项目id1111 :\(projectId ?? ""),msg = \(msg ?? "")")
  927. if projectId == nil || (projectId?.count ?? 0) <= 0 {
  928. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint, rhythmMode: self?.rhythmMode ?? .createStickersModelPoint) { [weak self] projectId, msg in
  929. BFLog(message: "生成的项目id222 :\(projectId ?? ""),msg = \(msg ?? "")")
  930. if projectId == nil || (projectId?.count ?? 0) <= 0 {
  931. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint, rhythmMode: self?.rhythmMode ?? .createStickersModelPoint) { [weak self] projectId, msg in
  932. BFLog(message: "生成的项目id 3333:\(projectId ?? ""),msg = \(msg ?? "")")
  933. if projectId != nil, (projectId?.count ?? 0) > 0 {
  934. self?.editProjectModel?.projectId = projectId ?? ""
  935. }
  936. /// fp4 - 处理视频数据
  937. // self?.dealWithVideoData()
  938. }
  939. } else {
  940. self?.editProjectModel?.projectId = projectId ?? ""
  941. /// fp4 - 处理视频数据
  942. // self?.dealWithVideoData()
  943. }
  944. }
  945. } else {
  946. self?.editProjectModel?.projectId = projectId ?? ""
  947. /// fp4 - 处理视频数据
  948. // self?.dealWithVideoData()
  949. }
  950. }
  951. }
  952. }
  953. /// fp4 - 处理视频数据
  954. /// - Returns: description
  955. @objc func dealWithVideoData() {
  956. BFLog(message: "开始去发布视频12")
  957. isSaveProjectSuccess = true
  958. if isExportSuccess && exportLocalURL != nil {
  959. BFLog(message: "素材上传完成同时视频导出完成开始发布视频")
  960. // 更新项目
  961. PQBaseViewModel.updateProject(projectId: editProjectModel?.projectId ?? "", produceStatus: "5") { repseon, _ in
  962. BFLog(message: "updateProject 结果 is \(String(describing: repseon))")
  963. }
  964. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  965. let tempUploadData = PQUploadModel()
  966. tempUploadData.duration = CMTimeGetSeconds(asset.duration)
  967. tempUploadData.localPath = exportLocalURL?.absoluteString
  968. tempUploadData.videoWidth = CGFloat(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  969. tempUploadData.videoHeight = CGFloat(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  970. tempUploadData.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  971. if tempUploadData.image == nil {
  972. tempUploadData.image = coverImage
  973. }
  974. tempUploadData.videoFromScene = .stuckPoint
  975. eventTrackData = getExportEventTrackData()
  976. eventTrackData?.projectId = editProjectModel?.projectId ?? ""
  977. uploadData = tempUploadData
  978. if uploadData?.image == nil {
  979. uploadData?.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  980. }
  981. if uploadData?.image != nil {
  982. playerHeaderView.image = uploadData?.image
  983. coverImageView.image = uploadData?.image
  984. }
  985. if isExportSuccess, exportLocalURL != nil {
  986. let size = try! exportLocalURL?.resourceValues(forKeys: [.fileSizeKey])
  987. BFLog(message: "size = \(String(describing: size))")
  988. if Float64(size?.fileSize ?? 0) <= maxUploadSize {
  989. /// fp5 - 上传视频
  990. reUploadVideo()
  991. }
  992. }
  993. }
  994. }
  995. /// fp5 - 上传视频
  996. /// - Returns: <#description#>
  997. @objc func reUploadVideo() {
  998. if uploadData?.stsToken != nil {
  999. multipartUpload(response: uploadData?.stsToken)
  1000. } else {
  1001. uploadVideo()
  1002. }
  1003. }
  1004. /// fp5-1 - 开始上传视频
  1005. /// - Returns: <#description#>
  1006. func uploadVideo() {
  1007. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1008. if uploadRequest != nil, "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1" {
  1009. return
  1010. }
  1011. // 更新进度
  1012. updatePublicCurrentProgress(useProgress: 0.89)
  1013. DispatchQueue.global().async {
  1014. PQBaseViewModel.getStsToken { [weak self] response, _ in
  1015. if response == nil {
  1016. self?.showUploadRemindView(isNetCollected: false, msg: "token获取数据失败了哦~")
  1017. return
  1018. }
  1019. // 更新进度
  1020. self?.updatePublicCurrentProgress(useProgress: 0.90)
  1021. BFLog(message: "取我方服务器STS 返回数据 \(String(describing: response))")
  1022. self?.multipartUpload(response: response)
  1023. }
  1024. }
  1025. }
  1026. /// fp5-2 - 继续上传视频
  1027. /// - Parameter response: <#response description#>
  1028. func multipartUpload(response: [String: Any]?) {
  1029. let FileName: String = "\(response?["FileName"] ?? "")"
  1030. let uploadID: String = "\(response?["Upload"] ?? "")"
  1031. uploadData?.stsToken = response
  1032. uploadData?.videoBucketKey = FileName
  1033. uploadData?.uploadID = uploadID
  1034. if uploadData?.asset != nil && isValidURL(url: uploadData?.localPath) {
  1035. PQPHAssetVideoParaseUtil.exportPHAssetToMP4(phAsset: (uploadData?.asset)!, isCancelCurrentExport: true) { [weak self] _, _, filePath, _ in
  1036. if filePath != nil, (filePath?.count ?? 0) > 0 {
  1037. self?.uploadData?.localPath = filePath
  1038. PQAliOssUtil.multipartUpload(localPath: self?.uploadData?.localPath ?? "", response: response)
  1039. }
  1040. }
  1041. } else {
  1042. PQAliOssUtil.multipartUpload(localPath: uploadData?.localPath ?? "", response: response)
  1043. }
  1044. PQAliOssUtil.shared.aliOssHander = { [weak self] isMatarialUpload, materialType, _, code, objectkey, _, _, _, _, _, _, _, _, _ in
  1045. if !isMatarialUpload, materialType == .VIDEO, self?.uploadData?.videoBucketKey == objectkey {
  1046. if code == 6 { // 无网
  1047. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[self?.uploadData?.videoBucketKey ?? ""]
  1048. if !(uploadRequest != nil && "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1") {
  1049. self?.showUploadRemindView(msg:"aliOss")
  1050. }
  1051. } else if code == 260 {
  1052. self?.showUploadRemindView(isNetCollected: false, msg:"aliOss")
  1053. } else if code != 1 {
  1054. // 上传失败-播放视频
  1055. self?.publicEnd(isError: true)
  1056. }
  1057. }
  1058. }
  1059. PQAliOssUtil.shared.aliOssProgressHander = { [weak self] bytesSent, totalBytesSent, totalBytesExpectedToSend, _, _ in
  1060. let progress: Float = 0.90 + Float(Float(totalBytesSent) / Float(totalBytesExpectedToSend)) * 0.09
  1061. BFLog(message: "卡点视频上传:bytesSent = \(bytesSent),totalBytesSent = \(totalBytesSent),totalBytesExpectedToSend = \(totalBytesExpectedToSend),progress = \(progress)")
  1062. if progress >= 0.90, progress <= 0.99 {
  1063. // 更新进度
  1064. self?.updatePublicCurrentProgress(useProgress: progress)
  1065. }
  1066. }
  1067. }
  1068. /// fp6 - 视频上传成功,处理要发布视频数据
  1069. /// - Parameter notify: <#notify description#>
  1070. @objc func uploadSuccess(notify: NSNotification) {
  1071. let objectKey: String = "\(notify.userInfo?["objectKey"] ?? "")"
  1072. BFLog(message: "收到上传成功请求==\(notify.userInfo ?? [:])")
  1073. if uploadData?.videoBucketKey == objectKey {
  1074. // 上传成功
  1075. isUploadSuccess = true
  1076. /// fp7 - 处理要发布视频数据
  1077. dealWithPublicData()
  1078. }
  1079. }
  1080. /// fp7 - 处理要发布视频数据
  1081. /// - Returns: <#description#>
  1082. func dealWithPublicData() {
  1083. if uploadData?.localPath != nil {
  1084. let size = try! URL(string: uploadData?.localPath ?? "")?.resourceValues(forKeys: [.fileSizeKey])
  1085. BFLog(message: "size = \(String(describing: size))")
  1086. if Float64(size?.fileSize ?? 0) > maxUploadSize {
  1087. cShowHUB(superView: nil, msg: "无法发布大于10G的视频,请重新选择/合成发布")
  1088. // 上传失败-播放视频
  1089. publicEnd(isError: true)
  1090. return
  1091. }
  1092. }
  1093. let projectId: String? = editProjectModel?.projectId
  1094. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1095. if uploadRequest == nil {
  1096. reUploadVideo()
  1097. return
  1098. }
  1099. let tempModel = PQVideoListModel()
  1100. tempModel.title = selectTitle
  1101. tempModel.summary = ""
  1102. tempModel.duration = Float64(uploadData?.duration ?? 0)
  1103. tempModel.uplpadImage = uploadData?.image
  1104. tempModel.uplpadBucketKey = uploadRequest?.objectKey
  1105. tempModel.localPath = uploadData?.localPath
  1106. tempModel.reCreateVideoData = reCreateData
  1107. tempModel.eventTrackData = eventTrackData
  1108. tempModel.uplpadStatus = 1
  1109. tempModel.videoFromScene = .stuckPoint
  1110. tempModel.uid = Int(BFLoginUserInfo.shared.uid) ?? 0
  1111. tempModel.uplpadRequest = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1112. tempModel.stsToken = uploadData?.stsToken
  1113. tempModel.projectId = projectId
  1114. /// fp8 - 发布视频
  1115. publicVideo(videoData: tempModel)
  1116. }
  1117. /// fp8 - 发布视频
  1118. /// - Parameter videoData: <#videoData description#>
  1119. func publicVideo(videoData: PQVideoListModel) {
  1120. if videoData.uplpadBucketKey == nil {
  1121. BFLog(message: "发布视频:视频uplpadBucketKey为空-\(String(describing: videoData.uplpadBucketKey))")
  1122. // 上传失败-播放视频
  1123. publicEnd(isError: true)
  1124. return
  1125. }
  1126. BFLog(message: "开始发布")
  1127. if (videoData.eventTrackData?.endUploadDate ?? 0) <= 0 {
  1128. // 结束上传时间
  1129. videoData.eventTrackData?.endUploadDate = Date().timeIntervalSince1970
  1130. }
  1131. DispatchQueue.global().async {
  1132. // PQBaseViewModel.ossTempToken { [weak self] response, _ in
  1133. // let image: UIImage = videoData.uplpadImage ?? UIImage()
  1134. // let data = image.jpegData(compressionQuality: 1)
  1135. // let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  1136. // let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  1137. // let securityToken: String = "\(response?["securityToken"] ?? "")"
  1138. // let endpoint: String = "\(response?["uploadDomain"] ?? "")"
  1139. // let bucketName: String = "\(response?["bucketName"] ?? "")"
  1140. // let objectKey: String = "\(response?["objectKey"] ?? "")"
  1141. // BFLog(message: "开始上传视频图片==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey)")
  1142. // PQAliOssUtil.shared
  1143. // .startClient(
  1144. // accessKeyId: accessKeyId,
  1145. // secretKeyId: secretKeyId,
  1146. // securityToken: securityToken,
  1147. // endpoint: endpoint
  1148. // )
  1149. // .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  1150. // BFLog(message: "图片上传完成==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  1151. // if code == 1 && ossObjectKey == objectKey && objectKey.count > 0 {
  1152. // BFLog(message: "开始发布==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  1153. PQUploadViewModel.publishVideo(projectId: videoData.projectId, fileExtensions: videoData.localPath?.pathExtension, title: videoData.title ?? "", videoPath: videoData.uplpadBucketKey ?? "", coverImgPath: nil, descr: videoData.summary ?? "", videoFromScene: .stuckPoint, reCreateData: videoData.reCreateVideoData, eventTrackData: videoData.eventTrackData) { [weak self] newVideoData, _, _ in
  1154. self?.videoData = newVideoData
  1155. self?.videoData?.title = self?.titleLabel.text
  1156. if self?.videoData?.reCreateVideoData == nil {
  1157. let reCreateVideo = PQReCreateModel()
  1158. reCreateVideo.reProduceVideoFlag = 1
  1159. self?.videoData?.reCreateVideoData = reCreateVideo
  1160. }
  1161. postNotification(name: cPublishStuckPointSuccessKey, userInfo: ["newVideoData": self?.videoData ?? PQVideoListModel()])
  1162. BFLog(message: "发布成功==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? "")")
  1163. // cShowHUB(superView: nil, msg: "视频发布成功")
  1164. // 发布成功后续操作
  1165. self?.publicEnd()
  1166. PQEventTrackViewModel.publishReportUpload(projectId: videoData.projectId, businessType: .bt_publish_success, ossInfo: videoData.stsToken ?? [:], params: ["title": videoData.title ?? "", "videoPath": videoData.uplpadBucketKey ?? "", "descr": videoData.summary ?? ""])
  1167. }
  1168. // } else {
  1169. // // 图片上传失败
  1170. // BFLog(message: "图片上传失败重新发布视频==\(videoData.title ?? ""),\(videoData.uplpadBucketKey ?? "")")
  1171. // self?.publicVideo(videoData: videoData)
  1172. // }
  1173. // })
  1174. // }
  1175. }
  1176. }
  1177. /// 发布结束操作
  1178. /// - Parameter isError: <#isError description#>
  1179. /// - Returns: <#description#>
  1180. func publicEnd(isError: Bool = false) {
  1181. UIApplication.shared.keyWindow?.viewWithTag(100_100)?.removeFromSuperview()
  1182. isPublicSuccess = true
  1183. progressView.removeFromSuperview()
  1184. progressTipsLab.removeFromSuperview()
  1185. oprationBgView.removeFromSuperview()
  1186. playBtn.isHidden = true
  1187. avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: (exportLocalURL?.absoluteString ?? "").replacingOccurrences(of: "file:///", with: ""))))
  1188. avPlayer.play()
  1189. if isError {
  1190. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1191. } else {
  1192. bottomOprationBgView.isHidden = false
  1193. //add by ak 发布成功后如果带片尾的视频还没有生成成功时,出提示
  1194. saveVideoTipsBgView.isHidden = false
  1195. self.saveRetryBtn.isHidden = true
  1196. if(saveMovieLocalURL == nil){
  1197. saveVideoTipsLabel.text = "视频保存中..."
  1198. }else{
  1199. self.saveVideoTipsLabel.text = "视频已保存到相册"
  1200. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  1201. self?.saveVideoTipsBgView.isHidden = true
  1202. }
  1203. }
  1204. }
  1205. }
  1206. /// 生成创作工具埋点数据
  1207. /// - Returns: <#description#>
  1208. func getExportEventTrackData() -> PQVideoMakeEventTrackModel? {
  1209. let eventTrackData = PQVideoMakeEventTrackModel(projectModel: editProjectModel, reCreateData: reCreateData)
  1210. eventTrackData.entrance = .entranceStuckPointPublic
  1211. eventTrackData.editTimeCost = 0
  1212. eventTrackData.composeTimeCost = (exportEndDate - startExportDate) * 1000
  1213. eventTrackData.musicName = audioMixModel?.musicName ?? ""
  1214. eventTrackData.syncedUpMusicName = audioMixModel?.musicName ?? ""
  1215. eventTrackData.musicId = audioMixModel?.musicId ?? ""
  1216. eventTrackData.syncedUpMusicId = audioMixModel?.musicId ?? ""
  1217. eventTrackData.musicUrl = audioMixModel?.selectVoiceType == 1 ? (audioMixModel?.musicPath ?? "") : (audioMixModel?.accompanimentPath ?? "")
  1218. eventTrackData.musicType = audioMixModel != nil ? (audioMixModel?.selectVoiceType == 1 ? "original" : "accompaniment") : ""
  1219. eventTrackData.isMusicClip = (audioMixModel?.startTime ?? 0) > 0
  1220. if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.origin.rawValue {
  1221. eventTrackData.canvasRatio = "original"
  1222. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.nineToSixteen.rawValue {
  1223. eventTrackData.canvasRatio = "9:16"
  1224. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.oneToOne.rawValue {
  1225. eventTrackData.canvasRatio = "1:1"
  1226. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.sixteenToNine.rawValue {
  1227. eventTrackData.canvasRatio = "16:9"
  1228. }
  1229. eventTrackData.syncedUpVideoNumber = selectedDataCount - selectedImageDataCount
  1230. eventTrackData.syncedUpImageNumber = selectedImageDataCount
  1231. eventTrackData.syncedUpOriginalMaterialDuration = selectedTotalDuration * 1000
  1232. eventTrackData.syncedUpRhythmNumber = audioMixModel?.speed ?? 2
  1233. eventTrackData.syncedUpVideoDuration = ((audioMixModel?.endTime ?? 0) - (audioMixModel?.startTime ?? 0)) * 1000
  1234. // add by ak
  1235. eventTrackData.syncedUpVideoType = rhythmMode
  1236. eventTrackData.syncedUpVideoSpeedMax = syncedUpVideoSpeedMax
  1237. eventTrackData.syncedUpVideoSpeedMin = syncedUpVideoSpeedMin
  1238. return eventTrackData
  1239. }
  1240. /// 播放视频
  1241. /// - Returns: description
  1242. @objc func playVideo() {
  1243. playBtn.isHidden = !playBtn.isHidden
  1244. changPlayerIsPause(isPause: !playBtn.isHidden)
  1245. }
  1246. /// 按钮点击事件
  1247. /// - Parameter sender: <#sender description#>
  1248. /// - Returns: <#description#>
  1249. @objc func btnClick(sender: UIButton) {
  1250. switch sender.tag {
  1251. case 1:
  1252. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1253. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1254. return
  1255. }
  1256. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1257. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1258. return
  1259. }
  1260. cShowHUB(superView: nil, msg: nil)
  1261. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1262. PQBaseViewModel.wxFriendShareInfo(videoId: (videoData?.uniqueId)!) { [weak self] imagePath, title, shareWeappRawId, msg in
  1263. if msg != nil {
  1264. cShowHUB(superView: nil, msg: "网络不佳哦")
  1265. return
  1266. }
  1267. self?.isShared = true
  1268. PQSingletoWXApiUtil.shared.share(type: 3, scene: Int32(WXSceneSession.rawValue), shareWeappRawId: shareWeappRawId, title: title, description: title, imageUrl: imagePath, path: self?.videoData?.videoPath, videoId: (self?.videoData?.uniqueId)!, pageSource: self?.videoData?.pageSource ?? .sp_category, shareId: shareId).wxApiUtilHander = { _, _ in
  1269. }
  1270. cHiddenHUB(superView: nil)
  1271. }
  1272. // 点击上报:分享微信
  1273. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechat, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享微信)")
  1274. case 2:
  1275. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1276. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1277. return
  1278. }
  1279. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1280. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1281. return
  1282. }
  1283. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1284. PQBaseViewModel.h5ShareLinkInfo(videoId: videoData?.uniqueId ?? "", pageSource: videoData?.pageSource ?? .sp_category) { [weak self] path, _ in
  1285. cHiddenHUB(superView: nil)
  1286. if path != nil {
  1287. PQBaseViewModel.wxFriendShareInfo(videoId: (self?.videoData?.uniqueId)!) { [weak self] imagePath, _, _, msg in
  1288. if msg != nil {
  1289. cShowHUB(superView: nil, msg: "网络不佳哦")
  1290. return
  1291. }
  1292. self?.isShared = true
  1293. PQSingletoWXApiUtil.shared.share(type: 1, scene: Int32(WXSceneTimeline.rawValue), title: self?.videoData?.title ?? "\(BFLoginUserInfo.shared.nickName)made a music video for you", description: "", imageUrl: imagePath, path: path, videoId: (self?.videoData?.uniqueId)!, pageSource: self?.videoData?.pageSource ?? .sp_category, shareId: shareId).wxApiUtilHander = { _, _ in
  1294. }
  1295. }
  1296. } else {
  1297. cShowHUB(superView: nil, msg: "网络不佳哦")
  1298. }
  1299. }
  1300. // 点击上报:分享朋友圈
  1301. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechatMoment, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享朋友圈)")
  1302. case 3:
  1303. // 点击上报:完成
  1304. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_finished, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:完成)")
  1305. navigationController?.viewControllers = [(navigationController?.viewControllers.first)!]
  1306. // 发送通知
  1307. postNotification(name: cFinishedPublishedNotiKey)
  1308. case 97:
  1309. saveRetryBtn.isHidden = true
  1310. saveVideoTipsLabel.text = "视频保存中..."
  1311. self.saveStuckPointVideo()
  1312. default:
  1313. break
  1314. }
  1315. }
  1316. /// 添加提示视图
  1317. /// - Parameters:
  1318. /// - isNetCollected: <#isNetCollected description#>
  1319. /// - msg: <#msg description#>
  1320. func showUploadRemindView(isNetCollected _: Bool = true, msg: String? = nil) {
  1321. view.endEditing(true)
  1322. let emptyData = PQEmptyModel()
  1323. emptyData.isRefreshHidden = false
  1324. emptyData.title = "上传失败"
  1325. emptyData.titleColor = UIColor.hexColor(hexadecimal: "#353535")
  1326. emptyData.summary = "建议切换 WIFI/移动网络后再重试"
  1327. emptyData.summaryColor = UIColor.hexColor(hexadecimal: "#353535")
  1328. emptyData.refreshBgColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  1329. emptyData.refreshTitle = NSMutableAttributedString(string: "立即重试", attributes: [.foregroundColor:UIColor.white])
  1330. emptyData.emptySoureImage = UIImage.moduleImage(named: "stuckPoint_video_empty", moduleName: "BFMaterialKit",isAssets: false)
  1331. emptyData.netDisRefreshBgColor = UIColor.hexColor(hexadecimal: "#FA6400")
  1332. emptyData.netDisTitle = "内容加载失败"
  1333. emptyData.netDisTitleColor = UIColor.hexColor(hexadecimal: "#333333")
  1334. emptyData.netemptyDisImage = UIImage.moduleImage(named: "empty_netDis_icon", moduleName: "BFMaterialKit",isAssets: false)
  1335. emptyData.netDisRefreshTitle = NSMutableAttributedString(string: "重新加载", attributes: [.font: UIFont.systemFont(ofSize: 16, weight: .medium), .foregroundColor: UIColor.white])
  1336. let emptyRemindView = PQEmptyRemindView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: view.frame.width, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei))
  1337. // emptyRemindView.isHidden = true
  1338. emptyRemindView.emptyData = emptyData
  1339. emptyRemindView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  1340. emptyRemindView.fullRefreshBloc = {[weak self] _, _ in
  1341. if emptyRemindView.refreshBtn.currentAttributedTitle?.string == "立即重试" {
  1342. emptyRemindView.isHidden = true
  1343. // 重试逻辑
  1344. if let message = msg{
  1345. if message.contains("token") {
  1346. self?.uploadVideo()
  1347. }else if message.contains("aliOss"){
  1348. self?.uploadVideo()
  1349. }
  1350. }
  1351. }
  1352. }
  1353. emptyRemindView.refreshBtn.addCorner(corner: 4)
  1354. view.addSubview(emptyRemindView)
  1355. // PQRemindView.showUploadRemindView(title: "上传失败", summary: (msg != nil ? msg! : "视频文件已丢失"), confirmTitle: "立即重试") { [weak self] _, _ in
  1356. // self?.navigationController?.popToViewController((self?.navigationController?.viewControllers[1])!, animated: true)
  1357. // }
  1358. }
  1359. @objc func enterBackground() {
  1360. BFLog(message: "进入到后台")
  1361. // 取消导出
  1362. if exporter != nil {
  1363. exporter.cancel()
  1364. }
  1365. playBtn.isHidden = false
  1366. avPlayer.pause()
  1367. }
  1368. @objc func willEnterForeground() {
  1369. BFLog(message: "进入到前台")
  1370. if !isExportSuccess {
  1371. appendAudio()
  1372. }
  1373. playBtn.isHidden = true
  1374. playerHeaderCoverImageView.isHidden = true
  1375. avPlayer.play()
  1376. }
  1377. @objc func didBecomeActiveNotification() {
  1378. if isShared {
  1379. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [weak self] in
  1380. self?.isShared = false
  1381. cShowHUB(superView: nil, msg: "分享成功")
  1382. self?.playBtn.isHidden = true
  1383. self?.avPlayer.play()
  1384. }
  1385. }
  1386. }
  1387. /// 更新进度
  1388. /// - Returns: <#description#>
  1389. func updatePublicCurrentProgress(useProgress: Float) {
  1390. exportProgrss = Int(useProgress * 100)
  1391. progressView.setProgress(useProgress, animated: true)
  1392. let attributedText = NSMutableAttributedString(string: "\(exportProgrss)%\n视频正在处理中,请勿离开")
  1393. attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: "\(exportProgrss)%".count))
  1394. progressTipsLab.attributedText = attributedText
  1395. }
  1396. func changPlayerIsPause(isPause: Bool) {
  1397. if isPause {
  1398. playBtn.isHidden = false
  1399. avPlayer.pause()
  1400. playerHeaderCoverImageView.isHidden = false
  1401. } else {
  1402. playBtn.isHidden = true
  1403. avPlayer.play()
  1404. playerHeaderCoverImageView.isHidden = true
  1405. }
  1406. }
  1407. @objc func titleLabelClick() {
  1408. BFLog(message: "点击输入框")
  1409. changPlayerIsPause(isPause: true)
  1410. pinView.isHidden = true
  1411. publicTitleView.show()
  1412. if publicTitleView.inputTV.text.count > 0 {
  1413. publicTitleView.inputTV.text = titleLabel.text
  1414. }
  1415. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_clickButton_changeTitle, pageSource: .sp_stuck_publishSyncedUp, eventData: ["videoId": videoData?.uniqueId ?? "", "rootPageSource": isReCreate ? "shanyinApp-main-syncedUpMusicRecreate" : "shanyinApp-main-syncedUpMusic"], remindmsg: "")
  1416. }
  1417. @objc func settingCoverImage() {
  1418. if exportLocalURL == nil {
  1419. BFLog(message: "导出的视频地址错误???。。。")
  1420. return
  1421. }
  1422. changPlayerIsPause(isPause: true)
  1423. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  1424. publicEditCoverView.show(videoURL: exportLocalURL!, duration: CMTimeGetSeconds(asset.duration))
  1425. // 点击了确认 btn
  1426. publicEditCoverView.selectImageCallBack = { [weak self] imageData in
  1427. self?.changPlayerIsPause(isPause: false)
  1428. if imageData != nil {
  1429. self?.coverImageView.image = imageData
  1430. self?.playerHeaderCoverImageView.image = imageData
  1431. self?.uploadData?.image = imageData
  1432. self?.updateCoverImagegOrTitle()
  1433. }
  1434. }
  1435. // 点击了从相册选择
  1436. publicEditCoverView.selectPhotoBtnCallBack = { [weak self] in
  1437. let imageSelected = PQImageSelectedController()
  1438. imageSelected.isAssetImage = true
  1439. imageSelected.videoWidth = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  1440. imageSelected.videoHeight = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  1441. // imageSelected.uploadData = uploadData
  1442. // imageSelected.updataVideoData = updataVideoData
  1443. self?.navigationController?.pushViewController(imageSelected, animated: true)
  1444. }
  1445. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_clickButton_changeCover, pageSource: .sp_stuck_publishSyncedUp, eventData: ["videoId": videoData?.uniqueId ?? "", "rootPageSource": isReCreate ? "shanyinApp-main-syncedUpMusicRecreate" : "shanyinApp-main-syncedUpMusic"], remindmsg: "")
  1446. }
  1447. // 更新标题或封面
  1448. func updateCoverImagegOrTitle() {
  1449. PQLoadingHUB.shared.showHUB(isMode: true)
  1450. // SanW - 待修改 -
  1451. // PQLoadingHUB.shared.showHUB(isMode: true)
  1452. PQBaseViewModel.ossTempToken { [weak self] response, msg in
  1453. let image: UIImage = (self?.uploadData?.image)!
  1454. let data = image.jpegData(compressionQuality: 1)
  1455. let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  1456. let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  1457. let securityToken: String = "\(response?["securityToken"] ?? "")"
  1458. let endpoint: String = "\(response?["endPoint"] ?? "")"
  1459. let bucketName: String = "\(response?["bucketName"] ?? "")"
  1460. let objectKey: String = "\(response?["objectKey"] ?? "")"
  1461. _ = PQAliOssUtil.shared
  1462. .startClient(
  1463. accessKeyId: accessKeyId,
  1464. secretKeyId: secretKeyId,
  1465. securityToken: securityToken,
  1466. endpoint: endpoint
  1467. )
  1468. .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  1469. if code == 1 && ossObjectKey == objectKey && ossObjectKey.count > 0 {
  1470. //add by ak 这里会在服务器生成分享使用的图片到1-2S 时间
  1471. PQUploadViewModel.updateVideo(title: self?.videoData?.title ?? "", videoId: self?.videoData?.uniqueId ?? "", coverImgPath: objectKey, descr: "") {_, newVideoData, msg in
  1472. if newVideoData == nil {
  1473. cShowHUB(superView: self?.view, msg: msg)
  1474. // 可能有敏感词 要刷一组新标题并自动更新
  1475. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32(self?.publicTitleView.titles.count ?? 0)))
  1476. self?.setTitleText(text: self?.publicTitleView.titles[Int(numberRandom)] ?? "")
  1477. self?.updateCoverImagegOrTitle()
  1478. sleep(UInt32(1.5))
  1479. PQLoadingHUB.shared.dismissHUB()
  1480. return
  1481. } else {
  1482. PQLoadingHUB.shared.dismissHUB()
  1483. }
  1484. }
  1485. } else {
  1486. PQLoadingHUB.shared.dismissHUB()
  1487. }
  1488. })
  1489. } }
  1490. func setTitleText(text: String, textColor: UIColor = UIColor.hexColor(hexadecimal: "#ABABAB")) {
  1491. selectTitle = text
  1492. // 更新 UI
  1493. titleLabel.text = text
  1494. titleLabel.textColor = textColor
  1495. publicTitleView.inputTV.placeHolder = text
  1496. // 更新数据
  1497. videoData?.title = text
  1498. }
  1499. // 取推荐的10个标题
  1500. func getTitles() {
  1501. PQBaseViewModel.getBaseConfig(completeHander: { [weak self] titles in
  1502. if (titles?.count ?? 0) > 0 {
  1503. var temp:Array<String> = titles!
  1504. if((titles?.count ?? 0) <= 13){
  1505. for _ in 0 ... (13 - (titles?.count ?? 0)){
  1506. temp.append("")
  1507. }
  1508. }
  1509. self?.publicTitleView.titles = temp
  1510. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32(titles!.count)))
  1511. BFLog(message: "接收到的 titles\(String(describing: titles))")
  1512. self?.setTitleText(text: titles?[Int(numberRandom)] ?? "")
  1513. }
  1514. })
  1515. }
  1516. @objc func imageSelectedImage(notify: Notification) {
  1517. let imageData: UIImage? = notify.userInfo?["image"] as? UIImage
  1518. if imageData != nil {
  1519. changPlayerIsPause(isPause: false)
  1520. BFLog(message: "从系统相册选择了一个照片")
  1521. publicEditCoverView.isHidden = true
  1522. coverImageView.image = imageData
  1523. playerHeaderCoverImageView.image = imageData
  1524. uploadData?.image = imageData
  1525. updateCoverImagegOrTitle()
  1526. }
  1527. }
  1528. }
  1529. // MARK: - 导出带水印+片尾的视频相关方法
  1530. extension PQStuckPointPublicController {
  1531. //导出有水印的正片子
  1532. func beginExportWatermarkMovie(inputAsset: AVURLAsset!) {
  1533. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  1534. BFLog(message: "项目段落错误❌")
  1535. return
  1536. }
  1537. // 输出视频地址
  1538. var outPutMP4Path = exportVideosDirectory
  1539. if !directoryIsExists(dicPath: outPutMP4Path) {
  1540. BFLog(message: "文件夹不存在")
  1541. createDirectory(path: outPutMP4Path)
  1542. }
  1543. outPutMP4Path.append("saveMovie_\(String.qe.timestamp()).mp4")
  1544. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  1545. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  1546. watermarkMovieExporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
  1547. watermarkMovieExporter.isAddWatermark = true
  1548. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  1549. if mStickers != nil {
  1550. for stick in mStickers! {
  1551. if stick.type == StickerType.VIDEO.rawValue {
  1552. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
  1553. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  1554. if Int(cbr ?? 0) > orgeBitRate {
  1555. orgeBitRate = Int(cbr ?? 0)
  1556. }
  1557. }
  1558. }
  1559. }
  1560. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  1561. watermarkMovieExporter.showGaussianBlur = true
  1562. if watermarkMovieExporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
  1563. BFLog(message: "开始导出 \(String(describing: playeTimeRange.start)) 结束 \(String(describing: playeTimeRange.end))")
  1564. watermarkMovieExporter.start(playeTimeRange: playeTimeRange)
  1565. BFLog(message: "开始导出")
  1566. }
  1567. watermarkMovieExporter.progressClosure = { _, _, progress in
  1568. BFLog(message: "带水印的合成进度 \(progress) ")
  1569. }
  1570. watermarkMovieExporter.completion = { [weak self] url in
  1571. BFLog(message: "有水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!).duration))")
  1572. // 导出完成后取消导出
  1573. if self?.watermarkMovieExporter != nil {
  1574. self?.watermarkMovieExporter.cancel()
  1575. }
  1576. self?.watermarkMovieLocalURL = url
  1577. //开始导出片尾 成功后自动保存到相册
  1578. self?.beginExportEndMovie()
  1579. }
  1580. }
  1581. //导出片尾视频
  1582. func beginExportEndMovie() {
  1583. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  1584. BFLog(message: "项目段落错误❌")
  1585. return
  1586. }
  1587. // 输出视频地址
  1588. var outPutMP4Path = exportVideosDirectory
  1589. if !directoryIsExists(dicPath: outPutMP4Path) {
  1590. BFLog(message: "文件夹不存在")
  1591. createDirectory(path: outPutMP4Path)
  1592. }
  1593. outPutMP4Path.append("endMovie_\(String.qe.timestamp()).mp4")
  1594. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  1595. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  1596. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  1597. //片尾的视频素材地址
  1598. let moveResPath = currentBundlePath()!.path(forResource: (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) < (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) ? "endMovieB" : "endMovieA", ofType: "mp4")
  1599. if(moveResPath?.count ?? 0 == 0){
  1600. BFLog(message: "片尾的视频素材地址无效!!!")
  1601. return
  1602. }
  1603. let movieAsset = AVURLAsset(url: URL(fileURLWithPath: moveResPath!), options: avAssertOptions)
  1604. let cbr = movieAsset.tracks(withMediaType: .video).first?.estimatedDataRate
  1605. BFLog(message: "cbr is\(cbr ?? 0)")
  1606. if Int(cbr ?? 0) > orgeBitRate {
  1607. orgeBitRate = Int(cbr ?? 0)
  1608. }
  1609. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  1610. //头像保存沙盒地址
  1611. BFLog(message: "头像的网络地址\(BFLoginUserInfo.shared.avatarUrl)")
  1612. let avatarFilePath = NSHomeDirectory().appending("/Documents/").appending("user_avatar.jpg")
  1613. // warning:给默认头像吧
  1614. ImageDownloader.default.downloadImage(with: URL(string: BFLoginUserInfo.shared.avatarUrl)!, options: nil) {[weak self] result in
  1615. var image : UIImage?
  1616. switch result {
  1617. case let .success(imageResult):
  1618. image = UIImage.nx_circleImage(imageResult.image)
  1619. case let .failure(error):
  1620. image = UIImage.moduleImage(named: "user_avatar_normal", moduleName: "BFFramework", isAssets:false)
  1621. BFLog(message: "下载头像图片失败:\(error.localizedDescription)")
  1622. }
  1623. if(image == nil){
  1624. BFLog(message: "image date is error!!")
  1625. return
  1626. }
  1627. UIImage.saveImage(currentImage: image!, outFilePath: avatarFilePath)
  1628. //1,背景视频素材
  1629. let bgMovieInfo:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1630. bgMovieInfo.type = StickerType.VIDEO.rawValue
  1631. bgMovieInfo.locationPath = moveResPath ?? ""
  1632. bgMovieInfo.timelineIn = 0
  1633. bgMovieInfo.timelineOut = CMTimeGetSeconds(movieAsset.duration)
  1634. bgMovieInfo.model_in = bgMovieInfo.timelineIn
  1635. bgMovieInfo.out = bgMovieInfo.timelineOut
  1636. bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  1637. //2,用户头像素材
  1638. BFLog(message: "头像的沙盒地址:\(avatarFilePath)")
  1639. let avatarSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1640. avatarSticker.locationPath = avatarFilePath.replacingOccurrences(of: documensDirectory, with: "")
  1641. avatarSticker.timelineIn = bgMovieInfo.timelineIn
  1642. avatarSticker.timelineOut = bgMovieInfo.timelineOut
  1643. avatarSticker.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  1644. //头像绘制大小\位置
  1645. var avatarSize:Float = 0.0
  1646. var avatarTop:Float = 0.0
  1647. if((self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) > (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){
  1648. //竖屏
  1649. avatarSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * 360.0 / 1080.0
  1650. avatarTop = 430
  1651. }else{
  1652. //横屏屏
  1653. avatarSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 300.0 / 1080.0
  1654. avatarTop = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 130.0 / 1080.0
  1655. }
  1656. let avatarPostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
  1657. avatarPostion.width = Int(avatarSize)
  1658. avatarPostion.height = Int(avatarSize)
  1659. avatarPostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - Int(avatarSize)) / 2
  1660. avatarPostion.y = Int(avatarTop)
  1661. avatarSticker.materialPosition = avatarPostion
  1662. //3,用户名素材
  1663. let userNameSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1664. userNameSticker.timelineIn = bgMovieInfo.timelineIn
  1665. userNameSticker.timelineOut = bgMovieInfo.timelineOut
  1666. userNameSticker.type = StickerType.SUBTITLE.rawValue
  1667. //用户名绘制用到的参数
  1668. var userNameTop:Float = 0.0
  1669. var userNameFontSize:Float = 0.0
  1670. if((self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) > (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){
  1671. //竖屏
  1672. userNameTop = 870
  1673. userNameFontSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * 100.0 / 1080.0
  1674. }else{
  1675. //横屏
  1676. userNameTop = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 480 / 1080.0
  1677. userNameFontSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 70.0 / 1080.0
  1678. }
  1679. let subtitleInfo:PQEditSubtitleInfoModel = PQEditSubtitleInfoModel.init()
  1680. subtitleInfo.fontSize = Int(userNameFontSize)
  1681. subtitleInfo.text = BFLoginUserInfo.shared.nickName
  1682. userNameSticker.subtitleInfo = subtitleInfo
  1683. let userNamePostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
  1684. userNamePostion.width = Int(userNameFontSize ) * 10
  1685. userNamePostion.height = Int(userNameFontSize ) * 3
  1686. userNamePostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - userNamePostion.width) / 2
  1687. userNamePostion.y = Int(userNameTop)
  1688. userNameSticker.materialPosition = userNamePostion
  1689. //4,音频
  1690. let soundResPath = currentBundlePath()!.path(forResource: "endMovieSound", ofType: "mp3")
  1691. let soundAsset = AVURLAsset(url: URL(fileURLWithPath: soundResPath ?? ""), options: nil)
  1692. self?.endMovieExporter = PQCompositionExporter(asset: soundAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: [bgMovieInfo,avatarSticker,userNameSticker], animationTool: nil, exportURL: outPutMP4URL)
  1693. self?.endMovieExporter.isEndMovie = true
  1694. if ((self?.endMovieExporter.prepare(videoSize: CGSize(width: self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate)) != nil) {
  1695. self?.endMovieExporter.start(playeTimeRange: CMTimeRange.init(start: CMTime.zero, duration: CMTimeMakeWithSeconds(Float64(bgMovieInfo.out), preferredTimescale: BASE_FILTER_TIMESCALE)))
  1696. BFLog(message: "开始导出")
  1697. }
  1698. self?.endMovieExporter.progressClosure = { _, _, progress in
  1699. BFLog(message: "片尾合成进度 \(progress) ")
  1700. }
  1701. self?.endMovieExporter.completion = { [weak self] url in
  1702. BFLog(message: "片尾的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!).duration))")
  1703. // 导出完成后取消导出
  1704. if self?.endMovieExporter != nil {
  1705. self?.endMovieExporter.cancel()
  1706. }
  1707. self?.endMovieLocalURL = url
  1708. //拼接水印正片和片尾
  1709. if(self?.watermarkMovieLocalURL != nil && self?.endMovieLocalURL != nil){
  1710. let videoMerge:NXVideoMerge = NXVideoMerge.init()
  1711. videoMerge.mergeAndExportVideos(withFileURLs: [self!.watermarkMovieLocalURL!,self!.endMovieLocalURL!], renderSize:CGSize(width: self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)) { isSuccess, outFileURL in
  1712. if(isSuccess){
  1713. BFLog(message: "合并视频成功 outFilePath is \(outFileURL ?? "")")
  1714. self?.saveMovieLocalURL = outFileURL as? URL
  1715. //保存到相册 fp2-1-1 - 请求权限
  1716. self?.authorizationStatus()
  1717. }
  1718. }
  1719. }
  1720. }
  1721. }
  1722. }
  1723. }