PQStuckPointPublicController.swift 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923
  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. var isTestSaveFailed = false
  439. override func rightBtnClick(sender:UIButton){
  440. isTestSaveFailed = true
  441. }
  442. override func viewDidLoad() {
  443. super.viewDidLoad()
  444. // 注册上传成功的通知
  445. addNotification(self, selector: #selector(uploadSuccess(notify:)), name: cUploadSuccessKey, object: nil)
  446. PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
  447. leftButton(image: nil, tintColor: PQBFConfig.shared.styleTitleColor)
  448. // Test
  449. rightButtonItem(image: nil, title: "TestSave失败")
  450. navHeadImageView?.backgroundColor = UIColor.clear
  451. lineView?.removeFromSuperview()
  452. view.addSubview(bgTopView)
  453. playerHeaderView.frame = CGRect(origin: CGPoint(x: (cScreenWidth - preViewSize.width) / 2, y: (maxHeight - preViewSize.height) / 2), size: preViewSize)
  454. let ges = UITapGestureRecognizer(target: self, action: #selector(playVideo))
  455. playerHeaderView.addGestureRecognizer(ges)
  456. if playerLayer.superlayer == nil {
  457. playerHeaderView.layer.insertSublayer(playerLayer, at: 0)
  458. }
  459. playerHeaderView.addSubview(playBtn)
  460. playerHeaderView.addSubview(progressView)
  461. view.addSubview(oprationBgView)
  462. oprationBgView.addSubview(progressTipsLab)
  463. // 添加导出view
  464. bgTopView.addSubview(playerHeaderView)
  465. playerHeaderCoverImageView.frame = playerHeaderView.frame
  466. playerHeaderCoverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(playVideo)))
  467. (playerHeaderCoverImageView.viewWithTag(4))?.frame =
  468. CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
  469. bgTopView.addSubview(playerHeaderCoverImageView)
  470. view.addSubview(bottomOprationBgView)
  471. bottomOprationBgView.addSubview(remindLab)
  472. bottomOprationBgView.addSubview(shareWechatBtn)
  473. bottomOprationBgView.addSubview(shareFriendBtn)
  474. bottomOprationBgView.addSubview(finishedBtn)
  475. bottomOprationBgView.addSubview(inputBackView)
  476. bottomOprationBgView.addSubview(pinView)
  477. inputBackView.addSubview(coverImageView)
  478. coverImageView.addSubview(coverImageTitle)
  479. inputBackView.addSubview(titleLabel)
  480. view.addSubview(publicTitleView)
  481. view.addSubview(publicEditCoverView)
  482. view.addSubview(saveVideoTipsBgView)
  483. saveVideoTipsBgView.addSubview(saveVideoTipsLabel)
  484. saveVideoTipsBgView.addSubview(saveRetryBtn)
  485. saveVideoTipsLabel.snp.makeConstraints { make in
  486. make.top.height.equalToSuperview()
  487. make.centerX.equalToSuperview()
  488. }
  489. saveRetryBtn.snp.makeConstraints { make in
  490. make.left.equalTo(saveVideoTipsLabel.snp.right).offset(10)
  491. make.top.equalTo(6)
  492. make.bottom.equalTo(-6)
  493. make.width.equalTo(50)
  494. }
  495. coverImageTitle.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  496. coverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  497. progressView.snp.makeConstraints { make in
  498. make.left.right.centerY.equalTo(playerHeaderView)
  499. make.height.equalTo(3)
  500. }
  501. progressTipsLab.snp.makeConstraints { make in
  502. make.centerX.equalToSuperview()
  503. make.top.equalToSuperview().offset(((preViewSize.height - 90) / 2) + ((maxHeight - preViewSize.height) / 2))
  504. make.width.equalToSuperview()
  505. make.height.equalTo(90)
  506. }
  507. finishedBtn.snp.makeConstraints { make in
  508. make.centerX.equalToSuperview()
  509. make.bottom.equalToSuperview().offset(-cSafeAreaHeight)
  510. make.width.equalTo(100)
  511. make.height.equalTo(22)
  512. }
  513. shareWechatBtn.snp.makeConstraints { make in
  514. make.right.equalTo(view.snp.centerX).offset(-cDefaultMargin)
  515. make.width.equalTo(164)
  516. make.height.equalTo(52)
  517. make.bottom.equalTo(finishedBtn.snp.top).offset(-32)
  518. }
  519. shareFriendBtn.snp.makeConstraints { make in
  520. make.left.equalTo(view.snp.centerX).offset(cDefaultMargin)
  521. make.width.bottom.height.equalTo(shareWechatBtn)
  522. }
  523. inputBackView.snp.makeConstraints { make in
  524. make.centerX.equalToSuperview()
  525. make.bottom.equalTo(shareWechatBtn.snp.top).offset(-16)
  526. make.width.equalTo(343)
  527. make.height.equalTo(109)
  528. }
  529. // 根据横竖屏设置不同的 UI
  530. let isWidth: Bool = (Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)) >= 1
  531. var coverImageViewHeight = 50.0 * Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  532. if coverImageViewHeight > 89 {
  533. coverImageViewHeight = 89
  534. }
  535. coverImageView.snp.makeConstraints { make in
  536. make.left.equalToSuperview().offset(12)
  537. make.width.equalTo(50)
  538. make.top.equalToSuperview().offset(10)
  539. make.height.equalTo(coverImageViewHeight)
  540. }
  541. coverImageTitle.snp.makeConstraints { make in
  542. make.left.equalToSuperview()
  543. make.width.equalTo(50)
  544. make.top.equalTo(coverImageView.snp.bottom).offset(isWidth ? 0 : -23)
  545. make.height.equalTo(23)
  546. }
  547. remindLab.snp.makeConstraints { make in
  548. make.centerX.equalToSuperview()
  549. make.bottom.equalTo(inputBackView.snp.top).offset(-16).priorityHigh()
  550. make.height.equalTo(44)
  551. make.top.greaterThanOrEqualTo(5)
  552. make.bottom.lessThanOrEqualTo(inputBackView.snp.top).offset(-5)
  553. }
  554. titleLabel.snp.makeConstraints { make in
  555. make.height.equalTo(48)
  556. make.left.equalTo(coverImageView.snp.right).offset(12)
  557. make.right.equalToSuperview().offset(-14)
  558. make.top.equalToSuperview().offset(10)
  559. }
  560. pinView.snp.makeConstraints { make in
  561. make.height.width.equalTo(72)
  562. make.right.equalToSuperview()
  563. make.bottom.equalTo(inputBackView.snp.bottom)
  564. }
  565. publicTitleView.snp.makeConstraints { make in
  566. make.height.equalTo(cScreenHeigth)
  567. make.width.equalTo(cScreenWidth)
  568. make.bottom.equalToSuperview()
  569. }
  570. // 取消所有的导出
  571. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  572. exportSession.cancelExport()
  573. }
  574. // 开始导出
  575. appendAudio()
  576. /// 保存草稿
  577. saveDraftbox()
  578. // 曝光上报:窗口曝光
  579. PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_publishSyncedUp, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:窗口曝光)")
  580. // 取推荐标题
  581. getTitles()
  582. networkStausListen()
  583. }
  584. override func viewWillAppear(_ animated: Bool) {
  585. super.viewWillAppear(animated)
  586. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  587. PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  588. DispatchQueue.main.async {
  589. UIApplication.shared.isIdleTimerDisabled = true
  590. }
  591. //从相册选择一个照片后回调
  592. addNotification(self, selector: #selector(imageSelectedImage(notify:)), name: cSelectedImageSuccessKey, object: nil)
  593. #if swift(>=4.2)
  594. let memoryNotification = UIApplication.didReceiveMemoryWarningNotification
  595. _ = UIApplication.willTerminateNotification
  596. _ = UIApplication.didEnterBackgroundNotification
  597. #else
  598. let memoryNotification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning
  599. let terminateNotification = NSNotification.Name.UIApplicationWillTerminate
  600. let enterbackgroundNotification = NSNotification.Name.UIApplicationDidEnterBackground
  601. #endif
  602. NotificationCenter.default.addObserver(
  603. self, selector: #selector(clearMemoryCache), name: memoryNotification, object: nil
  604. )
  605. }
  606. @objc public func clearMemoryCache() {
  607. BFLog(message: "收到内存警告")
  608. }
  609. override func viewWillDisappear(_ animated: Bool) {
  610. super.viewWillDisappear(animated)
  611. DispatchQueue.main.async {
  612. UIApplication.shared.isIdleTimerDisabled = false
  613. }
  614. }
  615. deinit {
  616. BFLog(1, message: "发布界面析构release")
  617. view.endEditing(true)
  618. PQNotification.removeObserver(self)
  619. // 取消导出
  620. if exporter != nil {
  621. exporter.cancel()
  622. }
  623. if watermarkMovieExporter != nil{
  624. watermarkMovieExporter.cancel()
  625. }
  626. if endMovieExporter != nil{
  627. endMovieExporter.cancel()
  628. }
  629. avPlayer.pause()
  630. avPlayer.replaceCurrentItem(with: nil)
  631. // 点击上报:返回按钮
  632. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)")
  633. }
  634. // MARK: - 网络监控
  635. func networkStausListen(){
  636. manager?.startListening(onUpdatePerforming: { status in
  637. if status == .reachable(.cellular) || status == .reachable(.ethernetOrWiFi) {
  638. cHiddenHUB(superView: nil)
  639. } else {
  640. cShowHUB(superView: nil, msg: "当前网络不佳,请尝试重新连接")
  641. }
  642. })
  643. }
  644. }
  645. // MARK: - 导出/上传/下载及其他方法
  646. /// 导出/上传/下载及其他方法
  647. extension PQStuckPointPublicController {
  648. /// fp1 - 导出视频
  649. /// 开始导出视频
  650. /// 合并声音
  651. /// - Parameter urls: 所有音频的URL 是全路径方便复用
  652. /// - Parameter completeHander: 返回的 URL 全路径的 URL 如果要保存替换掉前缀
  653. func mergeAudios(originAsset: AVURLAsset, mTotalDuration: Float, clipAudioRange: CMTimeRange = CMTimeRange.zero, mStartTime _: CMTime = .zero, completeHander: @escaping (_ fileURL: URL?) -> Void) {
  654. let timeInterval: TimeInterval = Date().timeIntervalSince1970
  655. let composition = AVMutableComposition()
  656. let originaDuration = CMTimeGetSeconds(clipAudioRange.duration)
  657. BFLog(message: "处理主音频 原始时长startTime = \(originaDuration) 要显示时长totalDuration = \(mTotalDuration)")
  658. //originaDuration = 37.616768 mTotalDuration = 37.616776 TODO 都用 INT 微秒级
  659. if Float64(String(format: "%.3f",mTotalDuration)) ?? 0.0 <= Float64(String(format: "%.3f",originaDuration)) ?? 0.0 {
  660. BFLog(message: "不用拼接音频文件 \(originAsset.url) 时长is \(CMTimeGetSeconds(originAsset.duration))")
  661. completeHander(originAsset.url)
  662. return
  663. }
  664. // 整倍数
  665. let count = Int(mTotalDuration) / Int(originaDuration)
  666. // 有余数多 clip 一整段
  667. let row = mTotalDuration - Float(count) * Float(originaDuration)
  668. // 已经拼接的总时长
  669. var totalDuration: CMTime = .zero
  670. // 第一段的时长
  671. var duration: CMTime = .zero
  672. // 第一段的区间
  673. var timeRange: CMTimeRange = CMTimeRange.zero
  674. if count > 0 {
  675. for index in 0 ..< count {
  676. // 第0段从0开始到推荐的结束,播放器的开始时间不是从0开始的
  677. duration = CMTime(value: CMTimeValue((CMTimeGetSeconds(clipAudioRange.end)) * Double(playerTimescaleInt)), timescale: playerTimescaleInt)
  678. BFLog(message: "每一个文件的 duration \(CMTimeGetSeconds(duration))")
  679. var timeRange = CMTimeRangeMake(start: .zero, duration: duration)
  680. if index != 0 {
  681. // (CMTimeGetSeconds(clipAudioRange.end) - CMTimeGetSeconds(mStartTime))为用户选择的第一段时长
  682. timeRange = clipAudioRange
  683. }
  684. BFLog(message: "合并的文件地址: \(originAsset.url)")
  685. let audioAsset = originAsset
  686. let tracks = audioAsset.tracks(withMediaType: .audio)
  687. if tracks.count == 0 {
  688. BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
  689. completeHander(URL(string: ""))
  690. return
  691. }
  692. let assetTrack: AVAssetTrack = tracks[0]
  693. let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
  694. do {
  695. //
  696. try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
  697. } catch {
  698. BFLog(message: "error is \(error)")
  699. completeHander(URL(string: ""))
  700. return
  701. }
  702. totalDuration = CMTimeAdd(totalDuration, timeRange.duration)
  703. }
  704. }
  705. if row > 0 {
  706. duration = CMTime(value: CMTimeValue(Float(CMTimeGetSeconds(totalDuration)) * Float(playerTimescaleInt)), timescale: playerTimescaleInt)
  707. timeRange = CMTimeRange(start: clipAudioRange.start, duration: CMTime(value: Int64(Double(row) * Double(playerTimescaleInt)), timescale: playerTimescaleInt))
  708. BFLog(message: "合并的文件地址: \(originAsset.url)")
  709. let audioAsset = originAsset
  710. let tracks = audioAsset.tracks(withMediaType: .audio)
  711. if tracks.count == 0 {
  712. BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
  713. completeHander(URL(string: ""))
  714. return
  715. }
  716. let assetTrack: AVAssetTrack = tracks[0]
  717. let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
  718. do {
  719. //
  720. try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
  721. } catch {
  722. BFLog(message: "合并音频 error is \(error)")
  723. completeHander(URL(string: ""))
  724. return
  725. }
  726. totalDuration = CMTimeAdd(totalDuration, timeRange.duration)
  727. }
  728. let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
  729. BFLog(message: "assetExport.supportedFileTypes is \(String(describing: assetExport?.supportedFileTypes))")
  730. assetExport?.outputFileType = .m4a
  731. // XXXX 注意文件名的后缀要和outputFileType 一致 否则会导出失败
  732. var audioFilePath = exportAudiosDirectory
  733. if !directoryIsExists(dicPath: audioFilePath) {
  734. BFLog(message: "文件夹不存在")
  735. createDirectory(path: audioFilePath)
  736. }
  737. audioFilePath.append("merge_\(timeInterval).m4a")
  738. let fileUrl = URL(fileURLWithPath: audioFilePath)
  739. assetExport?.outputURL = fileUrl
  740. assetExport?.exportAsynchronously {
  741. if assetExport!.status == .completed {
  742. let audioAsset = AVURLAsset(url: fileUrl, options: avAssertOptions)
  743. BFLog(1, message: "拼接声音文件 完成 \(fileUrl) 时长is \(CMTimeGetSeconds(audioAsset.duration))")
  744. completeHander(fileUrl)
  745. } else {
  746. print("拼接出错 \(String(describing: assetExport?.error))")
  747. completeHander(URL(string: ""))
  748. }
  749. }
  750. }
  751. func appendAudio() {
  752. //更新一下假进度
  753. updatePublicCurrentProgress(useProgress: 0.01)
  754. let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
  755. let startMergeTime = CFAbsoluteTimeGetCurrent()
  756. mergeAudios(originAsset: inputAsset, mTotalDuration: finallyUserAudioTime, clipAudioRange: clipAudioRange, mStartTime: CMTime(value: CMTimeValue((mStickers?.first?.timelineIn ?? 0.0) * Float64(playerTimescaleInt)), timescale: playerTimescaleInt)) { [weak self] completURL in
  757. if completURL != nil {
  758. let asset = AVURLAsset(url: completURL!, options: nil)
  759. BFLog(message: "拼接后音频时长\(asset.duration.seconds) url is \(String(describing: completURL)) 用时\(CFAbsoluteTimeGetCurrent() - startMergeTime)")
  760. //导出不带水印的正片
  761. self?.beginExport(inputAsset: asset)
  762. //导出带水印的正片
  763. self?.beginExportWatermarkMovie(inputAsset:asset)
  764. }else{
  765. cShowHUB(superView: self?.view, msg: "合成失败请重试。")
  766. }
  767. }
  768. }
  769. func beginExport(inputAsset: AVURLAsset!) {
  770. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  771. BFLog(message: "项目段落错误❌")
  772. return
  773. }
  774. // 输出视频地址
  775. var outPutMP4Path = exportVideosDirectory
  776. if !directoryIsExists(dicPath: outPutMP4Path) {
  777. BFLog(message: "文件夹不存在")
  778. createDirectory(path: outPutMP4Path)
  779. }
  780. outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
  781. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  782. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  783. exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
  784. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  785. if mStickers != nil {
  786. for stick in mStickers! {
  787. if stick.type == StickerType.VIDEO.rawValue {
  788. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
  789. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  790. if Int(cbr ?? 0) > orgeBitRate {
  791. orgeBitRate = Int(cbr ?? 0)
  792. }
  793. }
  794. }
  795. }
  796. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  797. exporter.showGaussianBlur = true
  798. if exporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
  799. BFLog(message: "开始导出 \(String(describing: playeTimeRange.start)) 结束 \(String(describing: playeTimeRange.end))")
  800. exporter.start(playeTimeRange: playeTimeRange)
  801. BFLog(message: "开始导出")
  802. }
  803. exporter.progressClosure = { [weak self] _, _, progress in
  804. BFLog(message: "正片合成进度 \(progress)")
  805. let useProgress = progress > 1 ? 1 : progress
  806. if progress > 0, Int(useProgress * 100) > (self?.exportProgrss ?? 0) {
  807. // 更新进度
  808. self?.updatePublicCurrentProgress(useProgress: useProgress * 0.88)
  809. }
  810. }
  811. exporter.completion = { [weak self] url in
  812. BFLog(message: "无水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: (url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)).duration))")
  813. // 导出完成后取消导出
  814. if self?.exporter != nil {
  815. self?.exporter.cancel()
  816. }
  817. self?.remindView?.removeFromSuperview()
  818. if !(self?.isExportSuccess ?? false) {
  819. self?.isExportSuccess = true
  820. self?.exportEndDate = Date().timeIntervalSince1970
  821. BFLog(message: "视频导出完成-开始去发布视频 总时长为\((self?.exportEndDate ?? 0) - (self?.startExportDate ?? 0) * 1000)")
  822. self?.exportLocalURL = url
  823. /// fp2-1-1 - 请求权限
  824. // self?.authorizationStatus()
  825. /// fp2-2 - 保存草稿
  826. // self?.saveDraftbox()
  827. /// fp2 - 处理视频数据
  828. self?.dealWithVideoData()
  829. }
  830. }
  831. }
  832. /// fp2-1-1 - 请求权限
  833. func authorizationStatus() {
  834. let authStatus = PHPhotoLibrary.authorizationStatus()
  835. if authStatus == .notDetermined {
  836. // 第一次触发授权 alert
  837. PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in
  838. if status != .authorized {
  839. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  840. } else {
  841. /// fp2-1-2 - 保存视频到相册
  842. self?.saveStuckPointVideo()
  843. }
  844. }
  845. } else if authStatus == .authorized {
  846. /// fp2-1-2 - 保存视频到相册
  847. saveStuckPointVideo()
  848. } else {
  849. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  850. }
  851. }
  852. /// fp2-1-2 - 保存视频到相册
  853. /// - Parameter localPath: localPath description
  854. /// - Returns: <#description#>
  855. func saveStuckPointVideo() {
  856. if(saveMovieLocalURL == nil || isTestSaveFailed){
  857. BFLog(message: "保存相册的视频导出地址无效!!!")
  858. saveVideoTipsLabel.text = "视频保存失败"
  859. saveRetryBtn.isHidden = false
  860. saveVideoTipsBgView.isHidden = false
  861. // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  862. // self?.saveVideoTipsBgView.isHidden = true
  863. // }
  864. return
  865. }
  866. let authStatus = PHPhotoLibrary.authorizationStatus()
  867. if authStatus == .authorized {
  868. let photoLibrary = PHPhotoLibrary.shared()
  869. photoLibrary.performChanges({ [weak self] in
  870. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.saveMovieLocalURL)!)
  871. }) { [weak self] isFinished, _ in
  872. DispatchQueue.main.async { [weak self] in
  873. if self?.view != nil {
  874. if isFinished {
  875. self?.saveVideoTipsLabel.text = "视频已保存到相册"
  876. self?.saveRetryBtn.isHidden = true
  877. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  878. self?.saveVideoTipsBgView.isHidden = true
  879. }
  880. } else {
  881. self?.saveVideoTipsLabel.text = "视频保存失败"
  882. self?.saveRetryBtn.isHidden = false
  883. // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) { [weak self] in
  884. // self?.saveVideoTipsBgView.isHidden = true
  885. // }
  886. }
  887. }
  888. }
  889. }
  890. } else {
  891. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  892. }
  893. }
  894. /// fp2-2 - 保存草稿
  895. /// - Returns: <#description#>
  896. @objc func saveDraftbox() {
  897. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false)
  898. if sdata != nil, (sdata?.count ?? 0) > 0 {
  899. DispatchQueue.global().async { [weak self] in
  900. 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
  901. if draftboxInfo != nil {
  902. self?.editProjectModel?.draftboxId = draftboxInfo?["draftboxId"] as? String ?? ""
  903. self?.editProjectModel?.sData?.videoMetaData?.title = draftboxInfo?["title"] as? String ?? ""
  904. self?.editProjectModel?.sData?.videoMetaData?.coverUrl = draftboxInfo?["coverUrl"] as? String ?? ""
  905. self?.editProjectModel?.dataVersionCode = draftboxInfo?["dataVersionCode"] as? Int ?? 0
  906. BFLog(message: "保存远程的草稿成功")
  907. self?.isSaveDraftSuccess = true
  908. /// fp3 - 保存项目
  909. self?.saveProject()
  910. } else {
  911. // 保存草稿失败-播放视频
  912. // self?.publicEnd(isError: true)
  913. }
  914. }
  915. }
  916. } else {
  917. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  918. // 保存草稿失败-播放视频
  919. publicEnd(isError: true)
  920. }
  921. }
  922. /// fp3 - 保存项目
  923. /// - Returns: description
  924. func saveProject() {
  925. if isSaveDraftSuccess {
  926. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false) ?? ""
  927. let draftboxId: String? = editProjectModel?.draftboxId
  928. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint, rhythmMode: rhythmMode) { [weak self] projectId, msg in
  929. BFLog(message: "生成的项目id1111 :\(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: "生成的项目id222 :\(projectId ?? ""),msg = \(msg ?? "")")
  933. if projectId == nil || (projectId?.count ?? 0) <= 0 {
  934. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint, rhythmMode: self?.rhythmMode ?? .createStickersModelPoint) { [weak self] projectId, msg in
  935. BFLog(message: "生成的项目id 3333:\(projectId ?? ""),msg = \(msg ?? "")")
  936. if projectId != nil, (projectId?.count ?? 0) > 0 {
  937. self?.editProjectModel?.projectId = projectId ?? ""
  938. }
  939. /// fp4 - 处理视频数据
  940. // self?.dealWithVideoData()
  941. }
  942. } else {
  943. self?.editProjectModel?.projectId = projectId ?? ""
  944. /// fp4 - 处理视频数据
  945. // self?.dealWithVideoData()
  946. }
  947. }
  948. } else {
  949. self?.editProjectModel?.projectId = projectId ?? ""
  950. /// fp4 - 处理视频数据
  951. // self?.dealWithVideoData()
  952. }
  953. }
  954. }
  955. }
  956. /// fp4 - 处理视频数据
  957. /// - Returns: description
  958. @objc func dealWithVideoData() {
  959. BFLog(message: "开始去发布视频12")
  960. isSaveProjectSuccess = true
  961. if isExportSuccess && exportLocalURL != nil {
  962. BFLog(message: "素材上传完成同时视频导出完成开始发布视频")
  963. // 更新项目
  964. PQBaseViewModel.updateProject(projectId: editProjectModel?.projectId ?? "", produceStatus: "5") { repseon, _ in
  965. BFLog(message: "updateProject 结果 is \(String(describing: repseon))")
  966. }
  967. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  968. let tempUploadData = PQUploadModel()
  969. tempUploadData.duration = CMTimeGetSeconds(asset.duration)
  970. tempUploadData.localPath = exportLocalURL?.absoluteString
  971. tempUploadData.videoWidth = CGFloat(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  972. tempUploadData.videoHeight = CGFloat(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  973. tempUploadData.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  974. if tempUploadData.image == nil {
  975. tempUploadData.image = coverImage
  976. }
  977. tempUploadData.videoFromScene = .stuckPoint
  978. eventTrackData = getExportEventTrackData()
  979. eventTrackData?.projectId = editProjectModel?.projectId ?? ""
  980. uploadData = tempUploadData
  981. if uploadData?.image == nil {
  982. uploadData?.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  983. }
  984. if uploadData?.image != nil {
  985. playerHeaderView.image = uploadData?.image
  986. coverImageView.image = uploadData?.image
  987. }
  988. if isExportSuccess, exportLocalURL != nil {
  989. let size = try! exportLocalURL?.resourceValues(forKeys: [.fileSizeKey])
  990. BFLog(message: "size = \(String(describing: size))")
  991. if Float64(size?.fileSize ?? 0) <= maxUploadSize {
  992. /// fp5 - 上传视频
  993. reUploadVideo()
  994. }
  995. }
  996. }
  997. }
  998. /// fp5 - 上传视频
  999. /// - Returns: <#description#>
  1000. @objc func reUploadVideo() {
  1001. if uploadData?.stsToken != nil {
  1002. multipartUpload(response: uploadData?.stsToken)
  1003. } else {
  1004. uploadVideo()
  1005. }
  1006. }
  1007. /// fp5-1 - 开始上传视频
  1008. /// - Returns: <#description#>
  1009. func uploadVideo() {
  1010. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1011. if uploadRequest != nil, "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1" {
  1012. return
  1013. }
  1014. // 更新进度
  1015. updatePublicCurrentProgress(useProgress: 0.89)
  1016. DispatchQueue.global().async {
  1017. PQBaseViewModel.getStsToken { [weak self] response, _ in
  1018. if response == nil {
  1019. self?.showUploadRemindView(isNetCollected: false, msg: "token获取数据失败了哦~")
  1020. return
  1021. }
  1022. // 更新进度
  1023. self?.updatePublicCurrentProgress(useProgress: 0.90)
  1024. BFLog(message: "取我方服务器STS 返回数据 \(String(describing: response))")
  1025. self?.multipartUpload(response: response)
  1026. }
  1027. }
  1028. }
  1029. /// fp5-2 - 继续上传视频
  1030. /// - Parameter response: <#response description#>
  1031. func multipartUpload(response: [String: Any]?) {
  1032. let FileName: String = "\(response?["FileName"] ?? "")"
  1033. let uploadID: String = "\(response?["Upload"] ?? "")"
  1034. uploadData?.stsToken = response
  1035. uploadData?.videoBucketKey = FileName
  1036. uploadData?.uploadID = uploadID
  1037. if uploadData?.asset != nil && isValidURL(url: uploadData?.localPath) {
  1038. PQPHAssetVideoParaseUtil.exportPHAssetToMP4(phAsset: (uploadData?.asset)!, isCancelCurrentExport: true) { [weak self] _, _, filePath, _ in
  1039. if filePath != nil, (filePath?.count ?? 0) > 0 {
  1040. self?.uploadData?.localPath = filePath
  1041. PQAliOssUtil.multipartUpload(localPath: self?.uploadData?.localPath ?? "", response: response)
  1042. }
  1043. }
  1044. } else {
  1045. PQAliOssUtil.multipartUpload(localPath: uploadData?.localPath ?? "", response: response)
  1046. }
  1047. PQAliOssUtil.shared.aliOssHander = { [weak self] isMatarialUpload, materialType, _, code, objectkey, _, _, _, _, _, _, _, _, _ in
  1048. if !isMatarialUpload, materialType == .VIDEO, self?.uploadData?.videoBucketKey == objectkey {
  1049. if code == 6 { // 无网
  1050. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[self?.uploadData?.videoBucketKey ?? ""]
  1051. if !(uploadRequest != nil && "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1") {
  1052. self?.showUploadRemindView(msg:"aliOss")
  1053. }
  1054. } else if code == 260 {
  1055. self?.showUploadRemindView(isNetCollected: false, msg:"aliOss")
  1056. } else if code != 1 {
  1057. // 上传失败-播放视频
  1058. self?.publicEnd(isError: true)
  1059. }
  1060. }
  1061. }
  1062. PQAliOssUtil.shared.aliOssProgressHander = { [weak self] bytesSent, totalBytesSent, totalBytesExpectedToSend, _, _ in
  1063. let progress: Float = 0.90 + Float(Float(totalBytesSent) / Float(totalBytesExpectedToSend)) * 0.09
  1064. BFLog(message: "卡点视频上传:bytesSent = \(bytesSent),totalBytesSent = \(totalBytesSent),totalBytesExpectedToSend = \(totalBytesExpectedToSend),progress = \(progress)")
  1065. if progress >= 0.90, progress <= 0.99 {
  1066. // 更新进度
  1067. self?.updatePublicCurrentProgress(useProgress: progress)
  1068. }
  1069. }
  1070. }
  1071. /// fp6 - 视频上传成功,处理要发布视频数据
  1072. /// - Parameter notify: <#notify description#>
  1073. @objc func uploadSuccess(notify: NSNotification) {
  1074. let objectKey: String = "\(notify.userInfo?["objectKey"] ?? "")"
  1075. BFLog(message: "收到上传成功请求==\(notify.userInfo ?? [:])")
  1076. if uploadData?.videoBucketKey == objectKey {
  1077. // 上传成功
  1078. isUploadSuccess = true
  1079. /// fp7 - 处理要发布视频数据
  1080. dealWithPublicData()
  1081. }
  1082. }
  1083. /// fp7 - 处理要发布视频数据
  1084. /// - Returns: <#description#>
  1085. func dealWithPublicData() {
  1086. if uploadData?.localPath != nil {
  1087. let size = try! URL(string: uploadData?.localPath ?? "")?.resourceValues(forKeys: [.fileSizeKey])
  1088. BFLog(message: "size = \(String(describing: size))")
  1089. if Float64(size?.fileSize ?? 0) > maxUploadSize {
  1090. cShowHUB(superView: nil, msg: "无法发布大于10G的视频,请重新选择/合成发布")
  1091. // 上传失败-播放视频
  1092. publicEnd(isError: true)
  1093. return
  1094. }
  1095. }
  1096. let projectId: String? = editProjectModel?.projectId
  1097. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1098. if uploadRequest == nil {
  1099. reUploadVideo()
  1100. return
  1101. }
  1102. let tempModel = PQVideoListModel()
  1103. tempModel.title = selectTitle
  1104. tempModel.summary = ""
  1105. tempModel.duration = Float64(uploadData?.duration ?? 0)
  1106. tempModel.uplpadImage = uploadData?.image
  1107. tempModel.uplpadBucketKey = uploadRequest?.objectKey
  1108. tempModel.localPath = uploadData?.localPath
  1109. tempModel.reCreateVideoData = reCreateData
  1110. tempModel.eventTrackData = eventTrackData
  1111. tempModel.uplpadStatus = 1
  1112. tempModel.videoFromScene = .stuckPoint
  1113. tempModel.uid = Int(BFLoginUserInfo.shared.uid) ?? 0
  1114. tempModel.uplpadRequest = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  1115. tempModel.stsToken = uploadData?.stsToken
  1116. tempModel.projectId = projectId
  1117. /// fp8 - 发布视频
  1118. publicVideo(videoData: tempModel)
  1119. }
  1120. /// fp8 - 发布视频
  1121. /// - Parameter videoData: <#videoData description#>
  1122. func publicVideo(videoData: PQVideoListModel) {
  1123. if videoData.uplpadBucketKey == nil {
  1124. BFLog(message: "发布视频:视频uplpadBucketKey为空-\(String(describing: videoData.uplpadBucketKey))")
  1125. // 上传失败-播放视频
  1126. publicEnd(isError: true)
  1127. return
  1128. }
  1129. BFLog(message: "开始发布")
  1130. if (videoData.eventTrackData?.endUploadDate ?? 0) <= 0 {
  1131. // 结束上传时间
  1132. videoData.eventTrackData?.endUploadDate = Date().timeIntervalSince1970
  1133. }
  1134. DispatchQueue.global().async {
  1135. // PQBaseViewModel.ossTempToken { [weak self] response, _ in
  1136. // let image: UIImage = videoData.uplpadImage ?? UIImage()
  1137. // let data = image.jpegData(compressionQuality: 1)
  1138. // let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  1139. // let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  1140. // let securityToken: String = "\(response?["securityToken"] ?? "")"
  1141. // let endpoint: String = "\(response?["uploadDomain"] ?? "")"
  1142. // let bucketName: String = "\(response?["bucketName"] ?? "")"
  1143. // let objectKey: String = "\(response?["objectKey"] ?? "")"
  1144. // BFLog(message: "开始上传视频图片==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey)")
  1145. // PQAliOssUtil.shared
  1146. // .startClient(
  1147. // accessKeyId: accessKeyId,
  1148. // secretKeyId: secretKeyId,
  1149. // securityToken: securityToken,
  1150. // endpoint: endpoint
  1151. // )
  1152. // .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  1153. // BFLog(message: "图片上传完成==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  1154. // if code == 1 && ossObjectKey == objectKey && objectKey.count > 0 {
  1155. // BFLog(message: "开始发布==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  1156. 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
  1157. self?.videoData = newVideoData
  1158. self?.videoData?.title = self?.titleLabel.text
  1159. if self?.videoData?.reCreateVideoData == nil {
  1160. let reCreateVideo = PQReCreateModel()
  1161. reCreateVideo.reProduceVideoFlag = 1
  1162. self?.videoData?.reCreateVideoData = reCreateVideo
  1163. }
  1164. postNotification(name: cPublishStuckPointSuccessKey, userInfo: ["newVideoData": self?.videoData ?? PQVideoListModel()])
  1165. BFLog(message: "发布成功==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? "")")
  1166. // cShowHUB(superView: nil, msg: "视频发布成功")
  1167. // 发布成功后续操作
  1168. self?.publicEnd()
  1169. PQEventTrackViewModel.publishReportUpload(projectId: videoData.projectId, businessType: .bt_publish_success, ossInfo: videoData.stsToken ?? [:], params: ["title": videoData.title ?? "", "videoPath": videoData.uplpadBucketKey ?? "", "descr": videoData.summary ?? ""])
  1170. }
  1171. // } else {
  1172. // // 图片上传失败
  1173. // BFLog(message: "图片上传失败重新发布视频==\(videoData.title ?? ""),\(videoData.uplpadBucketKey ?? "")")
  1174. // self?.publicVideo(videoData: videoData)
  1175. // }
  1176. // })
  1177. // }
  1178. }
  1179. }
  1180. /// 发布结束操作
  1181. /// - Parameter isError: <#isError description#>
  1182. /// - Returns: <#description#>
  1183. func publicEnd(isError: Bool = false) {
  1184. UIApplication.shared.keyWindow?.viewWithTag(100_100)?.removeFromSuperview()
  1185. isPublicSuccess = true
  1186. progressView.removeFromSuperview()
  1187. progressTipsLab.removeFromSuperview()
  1188. oprationBgView.removeFromSuperview()
  1189. playBtn.isHidden = true
  1190. avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: (exportLocalURL?.absoluteString ?? "").replacingOccurrences(of: "file:///", with: ""))))
  1191. avPlayer.play()
  1192. if isError {
  1193. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1194. } else {
  1195. bottomOprationBgView.isHidden = false
  1196. //add by ak 发布成功后如果带片尾的视频还没有生成成功时,出提示
  1197. if(saveMovieLocalURL == nil){
  1198. saveVideoTipsBgView.isHidden = false
  1199. }
  1200. }
  1201. }
  1202. /// 生成创作工具埋点数据
  1203. /// - Returns: <#description#>
  1204. func getExportEventTrackData() -> PQVideoMakeEventTrackModel? {
  1205. let eventTrackData = PQVideoMakeEventTrackModel(projectModel: editProjectModel, reCreateData: reCreateData)
  1206. eventTrackData.entrance = .entranceStuckPointPublic
  1207. eventTrackData.editTimeCost = 0
  1208. eventTrackData.composeTimeCost = (exportEndDate - startExportDate) * 1000
  1209. eventTrackData.musicName = audioMixModel?.musicName ?? ""
  1210. eventTrackData.syncedUpMusicName = audioMixModel?.musicName ?? ""
  1211. eventTrackData.musicId = audioMixModel?.musicId ?? ""
  1212. eventTrackData.syncedUpMusicId = audioMixModel?.musicId ?? ""
  1213. eventTrackData.musicUrl = audioMixModel?.selectVoiceType == 1 ? (audioMixModel?.musicPath ?? "") : (audioMixModel?.accompanimentPath ?? "")
  1214. eventTrackData.musicType = audioMixModel != nil ? (audioMixModel?.selectVoiceType == 1 ? "original" : "accompaniment") : ""
  1215. eventTrackData.isMusicClip = (audioMixModel?.startTime ?? 0) > 0
  1216. if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.origin.rawValue {
  1217. eventTrackData.canvasRatio = "original"
  1218. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.nineToSixteen.rawValue {
  1219. eventTrackData.canvasRatio = "9:16"
  1220. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.oneToOne.rawValue {
  1221. eventTrackData.canvasRatio = "1:1"
  1222. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.sixteenToNine.rawValue {
  1223. eventTrackData.canvasRatio = "16:9"
  1224. }
  1225. eventTrackData.syncedUpVideoNumber = selectedDataCount - selectedImageDataCount
  1226. eventTrackData.syncedUpImageNumber = selectedImageDataCount
  1227. eventTrackData.syncedUpOriginalMaterialDuration = selectedTotalDuration * 1000
  1228. eventTrackData.syncedUpRhythmNumber = audioMixModel?.speed ?? 2
  1229. eventTrackData.syncedUpVideoDuration = ((audioMixModel?.endTime ?? 0) - (audioMixModel?.startTime ?? 0)) * 1000
  1230. // add by ak
  1231. eventTrackData.syncedUpVideoType = rhythmMode
  1232. eventTrackData.syncedUpVideoSpeedMax = syncedUpVideoSpeedMax
  1233. eventTrackData.syncedUpVideoSpeedMin = syncedUpVideoSpeedMin
  1234. return eventTrackData
  1235. }
  1236. /// 播放视频
  1237. /// - Returns: description
  1238. @objc func playVideo() {
  1239. playBtn.isHidden = !playBtn.isHidden
  1240. changPlayerIsPause(isPause: !playBtn.isHidden)
  1241. }
  1242. /// 按钮点击事件
  1243. /// - Parameter sender: <#sender description#>
  1244. /// - Returns: <#description#>
  1245. @objc func btnClick(sender: UIButton) {
  1246. switch sender.tag {
  1247. case 1:
  1248. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1249. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1250. return
  1251. }
  1252. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1253. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1254. return
  1255. }
  1256. cShowHUB(superView: nil, msg: nil)
  1257. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1258. PQBaseViewModel.wxFriendShareInfo(videoId: (videoData?.uniqueId)!) { [weak self] imagePath, title, shareWeappRawId, msg in
  1259. if msg != nil {
  1260. cShowHUB(superView: nil, msg: "网络不佳哦")
  1261. return
  1262. }
  1263. self?.isShared = true
  1264. 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
  1265. }
  1266. cHiddenHUB(superView: nil)
  1267. }
  1268. // 点击上报:分享微信
  1269. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechat, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享微信)")
  1270. case 2:
  1271. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1272. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1273. return
  1274. }
  1275. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1276. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1277. return
  1278. }
  1279. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1280. PQBaseViewModel.h5ShareLinkInfo(videoId: videoData?.uniqueId ?? "", pageSource: videoData?.pageSource ?? .sp_category) { [weak self] path, _ in
  1281. cHiddenHUB(superView: nil)
  1282. if path != nil {
  1283. PQBaseViewModel.wxFriendShareInfo(videoId: (self?.videoData?.uniqueId)!) { [weak self] imagePath, _, _, msg in
  1284. if msg != nil {
  1285. cShowHUB(superView: nil, msg: "网络不佳哦")
  1286. return
  1287. }
  1288. self?.isShared = true
  1289. 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
  1290. }
  1291. }
  1292. } else {
  1293. cShowHUB(superView: nil, msg: "网络不佳哦")
  1294. }
  1295. }
  1296. // 点击上报:分享朋友圈
  1297. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechatMoment, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享朋友圈)")
  1298. case 3:
  1299. // 点击上报:完成
  1300. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_finished, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:完成)")
  1301. navigationController?.viewControllers = [(navigationController?.viewControllers.first)!]
  1302. // 发送通知
  1303. postNotification(name: cFinishedPublishedNotiKey)
  1304. case 97:
  1305. saveRetryBtn.isHidden = true
  1306. saveVideoTipsLabel.text = "视频保存中..."
  1307. isTestSaveFailed = false
  1308. self.saveStuckPointVideo()
  1309. default:
  1310. break
  1311. }
  1312. }
  1313. /// 添加提示视图
  1314. /// - Parameters:
  1315. /// - isNetCollected: <#isNetCollected description#>
  1316. /// - msg: <#msg description#>
  1317. func showUploadRemindView(isNetCollected _: Bool = true, msg: String? = nil) {
  1318. view.endEditing(true)
  1319. let emptyData = PQEmptyModel()
  1320. emptyData.isRefreshHidden = false
  1321. emptyData.title = "上传失败"
  1322. emptyData.titleColor = UIColor.hexColor(hexadecimal: "#353535")
  1323. emptyData.summary = "建议切换 WIFI/移动网络后再重试"
  1324. emptyData.summaryColor = UIColor.hexColor(hexadecimal: "#353535")
  1325. emptyData.refreshBgColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  1326. emptyData.refreshTitle = NSMutableAttributedString(string: "立即重试", attributes: [.foregroundColor:UIColor.white])
  1327. emptyData.emptySoureImage = UIImage.moduleImage(named: "stuckPoint_video_empty", moduleName: "BFMaterialKit",isAssets: false)
  1328. emptyData.netDisRefreshBgColor = UIColor.hexColor(hexadecimal: "#FA6400")
  1329. emptyData.netDisTitle = "内容加载失败"
  1330. emptyData.netDisTitleColor = UIColor.hexColor(hexadecimal: "#333333")
  1331. emptyData.netemptyDisImage = UIImage.moduleImage(named: "empty_netDis_icon", moduleName: "BFMaterialKit",isAssets: false)
  1332. emptyData.netDisRefreshTitle = NSMutableAttributedString(string: "重新加载", attributes: [.font: UIFont.systemFont(ofSize: 16, weight: .medium), .foregroundColor: UIColor.white])
  1333. let emptyRemindView = PQEmptyRemindView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: view.frame.width, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei))
  1334. // emptyRemindView.isHidden = true
  1335. emptyRemindView.emptyData = emptyData
  1336. emptyRemindView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  1337. emptyRemindView.fullRefreshBloc = {[weak self] _, _ in
  1338. if emptyRemindView.refreshBtn.currentAttributedTitle?.string == "立即重试" {
  1339. emptyRemindView.isHidden = true
  1340. // 重试逻辑
  1341. if let message = msg{
  1342. if message.contains("token") {
  1343. self?.uploadVideo()
  1344. }else if message.contains("aliOss"){
  1345. self?.uploadVideo()
  1346. }
  1347. }
  1348. }
  1349. }
  1350. emptyRemindView.refreshBtn.addCorner(corner: 4)
  1351. view.addSubview(emptyRemindView)
  1352. // PQRemindView.showUploadRemindView(title: "上传失败", summary: (msg != nil ? msg! : "视频文件已丢失"), confirmTitle: "立即重试") { [weak self] _, _ in
  1353. // self?.navigationController?.popToViewController((self?.navigationController?.viewControllers[1])!, animated: true)
  1354. // }
  1355. }
  1356. @objc func enterBackground() {
  1357. BFLog(message: "进入到后台")
  1358. // 取消导出
  1359. if exporter != nil {
  1360. exporter.cancel()
  1361. }
  1362. playBtn.isHidden = false
  1363. avPlayer.pause()
  1364. }
  1365. @objc func willEnterForeground() {
  1366. BFLog(message: "进入到前台")
  1367. if !isExportSuccess {
  1368. appendAudio()
  1369. }
  1370. playBtn.isHidden = true
  1371. playerHeaderCoverImageView.isHidden = true
  1372. avPlayer.play()
  1373. }
  1374. @objc func didBecomeActiveNotification() {
  1375. if isShared {
  1376. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [weak self] in
  1377. self?.isShared = false
  1378. cShowHUB(superView: nil, msg: "分享成功")
  1379. self?.playBtn.isHidden = true
  1380. self?.avPlayer.play()
  1381. }
  1382. }
  1383. }
  1384. /// 更新进度
  1385. /// - Returns: <#description#>
  1386. func updatePublicCurrentProgress(useProgress: Float) {
  1387. exportProgrss = Int(useProgress * 100)
  1388. progressView.setProgress(useProgress, animated: true)
  1389. let attributedText = NSMutableAttributedString(string: "\(exportProgrss)%\n视频正在处理中,请勿离开")
  1390. attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: "\(exportProgrss)%".count))
  1391. progressTipsLab.attributedText = attributedText
  1392. }
  1393. func changPlayerIsPause(isPause: Bool) {
  1394. if isPause {
  1395. playBtn.isHidden = false
  1396. avPlayer.pause()
  1397. playerHeaderCoverImageView.isHidden = false
  1398. } else {
  1399. playBtn.isHidden = true
  1400. avPlayer.play()
  1401. playerHeaderCoverImageView.isHidden = true
  1402. }
  1403. }
  1404. @objc func titleLabelClick() {
  1405. BFLog(message: "点击输入框")
  1406. changPlayerIsPause(isPause: true)
  1407. pinView.isHidden = true
  1408. publicTitleView.show()
  1409. if publicTitleView.inputTV.text.count > 0 {
  1410. publicTitleView.inputTV.text = titleLabel.text
  1411. }
  1412. 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: "")
  1413. }
  1414. @objc func settingCoverImage() {
  1415. if exportLocalURL == nil {
  1416. BFLog(message: "导出的视频地址错误???。。。")
  1417. return
  1418. }
  1419. changPlayerIsPause(isPause: true)
  1420. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  1421. publicEditCoverView.show(videoURL: exportLocalURL!, duration: CMTimeGetSeconds(asset.duration))
  1422. // 点击了确认 btn
  1423. publicEditCoverView.selectImageCallBack = { [weak self] imageData in
  1424. self?.changPlayerIsPause(isPause: false)
  1425. if imageData != nil {
  1426. self?.coverImageView.image = imageData
  1427. self?.playerHeaderCoverImageView.image = imageData
  1428. self?.uploadData?.image = imageData
  1429. self?.updateCoverImagegOrTitle()
  1430. }
  1431. }
  1432. // 点击了从相册选择
  1433. publicEditCoverView.selectPhotoBtnCallBack = { [weak self] in
  1434. let imageSelected = PQImageSelectedController()
  1435. imageSelected.isAssetImage = true
  1436. imageSelected.videoWidth = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  1437. imageSelected.videoHeight = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  1438. // imageSelected.uploadData = uploadData
  1439. // imageSelected.updataVideoData = updataVideoData
  1440. self?.navigationController?.pushViewController(imageSelected, animated: true)
  1441. }
  1442. 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: "")
  1443. }
  1444. // 更新标题或封面
  1445. func updateCoverImagegOrTitle() {
  1446. PQLoadingHUB.shared.showHUB(isMode: true)
  1447. // SanW - 待修改 -
  1448. // PQLoadingHUB.shared.showHUB(isMode: true)
  1449. PQBaseViewModel.ossTempToken { [weak self] response, msg in
  1450. let image: UIImage = (self?.uploadData?.image)!
  1451. let data = image.jpegData(compressionQuality: 1)
  1452. let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  1453. let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  1454. let securityToken: String = "\(response?["securityToken"] ?? "")"
  1455. let endpoint: String = "\(response?["endPoint"] ?? "")"
  1456. let bucketName: String = "\(response?["bucketName"] ?? "")"
  1457. let objectKey: String = "\(response?["objectKey"] ?? "")"
  1458. _ = PQAliOssUtil.shared
  1459. .startClient(
  1460. accessKeyId: accessKeyId,
  1461. secretKeyId: secretKeyId,
  1462. securityToken: securityToken,
  1463. endpoint: endpoint
  1464. )
  1465. .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  1466. if code == 1 && ossObjectKey == objectKey && ossObjectKey.count > 0 {
  1467. //add by ak 这里会在服务器生成分享使用的图片到1-2S 时间
  1468. PQUploadViewModel.updateVideo(title: self?.videoData?.title ?? "", videoId: self?.videoData?.uniqueId ?? "", coverImgPath: objectKey, descr: "") {_, newVideoData, msg in
  1469. if newVideoData == nil {
  1470. cShowHUB(superView: self?.view, msg: msg)
  1471. // 可能有敏感词 要刷一组新标题并自动更新
  1472. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32(self?.publicTitleView.titles.count ?? 0)))
  1473. self?.setTitleText(text: self?.publicTitleView.titles[Int(numberRandom)] ?? "")
  1474. self?.updateCoverImagegOrTitle()
  1475. sleep(UInt32(1.5))
  1476. PQLoadingHUB.shared.dismissHUB()
  1477. return
  1478. } else {
  1479. PQLoadingHUB.shared.dismissHUB()
  1480. }
  1481. }
  1482. } else {
  1483. PQLoadingHUB.shared.dismissHUB()
  1484. }
  1485. })
  1486. } }
  1487. func setTitleText(text: String, textColor: UIColor = UIColor.hexColor(hexadecimal: "#ABABAB")) {
  1488. selectTitle = text
  1489. // 更新 UI
  1490. titleLabel.text = text
  1491. titleLabel.textColor = textColor
  1492. publicTitleView.inputTV.placeHolder = text
  1493. // 更新数据
  1494. videoData?.title = text
  1495. }
  1496. // 取推荐的10个标题
  1497. func getTitles() {
  1498. PQBaseViewModel.getBaseConfig(completeHander: { [weak self] titles in
  1499. if (titles?.count ?? 0) > 0 {
  1500. var temp:Array<String> = titles!
  1501. if((titles?.count ?? 0) <= 13){
  1502. for _ in 0 ... (13 - (titles?.count ?? 0)){
  1503. temp.append("")
  1504. }
  1505. }
  1506. self?.publicTitleView.titles = temp
  1507. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32(titles!.count)))
  1508. BFLog(message: "接收到的 titles\(String(describing: titles))")
  1509. self?.setTitleText(text: titles?[Int(numberRandom)] ?? "")
  1510. }
  1511. })
  1512. }
  1513. @objc func imageSelectedImage(notify: Notification) {
  1514. let imageData: UIImage? = notify.userInfo?["image"] as? UIImage
  1515. if imageData != nil {
  1516. changPlayerIsPause(isPause: false)
  1517. BFLog(message: "从系统相册选择了一个照片")
  1518. publicEditCoverView.isHidden = true
  1519. coverImageView.image = imageData
  1520. playerHeaderCoverImageView.image = imageData
  1521. uploadData?.image = imageData
  1522. updateCoverImagegOrTitle()
  1523. }
  1524. }
  1525. }
  1526. // MARK: - 导出带水印+片尾的视频相关方法
  1527. extension PQStuckPointPublicController {
  1528. //导出有水印的正片子
  1529. func beginExportWatermarkMovie(inputAsset: AVURLAsset!) {
  1530. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  1531. BFLog(message: "项目段落错误❌")
  1532. return
  1533. }
  1534. // 输出视频地址
  1535. var outPutMP4Path = exportVideosDirectory
  1536. if !directoryIsExists(dicPath: outPutMP4Path) {
  1537. BFLog(message: "文件夹不存在")
  1538. createDirectory(path: outPutMP4Path)
  1539. }
  1540. outPutMP4Path.append("saveMovie_\(String.qe.timestamp()).mp4")
  1541. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  1542. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  1543. watermarkMovieExporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
  1544. watermarkMovieExporter.isAddWatermark = true
  1545. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  1546. if mStickers != nil {
  1547. for stick in mStickers! {
  1548. if stick.type == StickerType.VIDEO.rawValue {
  1549. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
  1550. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  1551. if Int(cbr ?? 0) > orgeBitRate {
  1552. orgeBitRate = Int(cbr ?? 0)
  1553. }
  1554. }
  1555. }
  1556. }
  1557. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  1558. watermarkMovieExporter.showGaussianBlur = true
  1559. if watermarkMovieExporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
  1560. BFLog(message: "开始导出 \(String(describing: playeTimeRange.start)) 结束 \(String(describing: playeTimeRange.end))")
  1561. watermarkMovieExporter.start(playeTimeRange: playeTimeRange)
  1562. BFLog(message: "开始导出")
  1563. }
  1564. watermarkMovieExporter.progressClosure = { _, _, progress in
  1565. BFLog(message: "带水印的合成进度 \(progress) ")
  1566. }
  1567. watermarkMovieExporter.completion = { [weak self] url in
  1568. BFLog(message: "有水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!).duration))")
  1569. // 导出完成后取消导出
  1570. if self?.watermarkMovieExporter != nil {
  1571. self?.watermarkMovieExporter.cancel()
  1572. }
  1573. self?.watermarkMovieLocalURL = url
  1574. //开始导出片尾 成功后自动保存到相册
  1575. self?.beginExportEndMovie()
  1576. }
  1577. }
  1578. //导出片尾视频
  1579. func beginExportEndMovie() {
  1580. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  1581. BFLog(message: "项目段落错误❌")
  1582. return
  1583. }
  1584. // 输出视频地址
  1585. var outPutMP4Path = exportVideosDirectory
  1586. if !directoryIsExists(dicPath: outPutMP4Path) {
  1587. BFLog(message: "文件夹不存在")
  1588. createDirectory(path: outPutMP4Path)
  1589. }
  1590. outPutMP4Path.append("endMovie_\(String.qe.timestamp()).mp4")
  1591. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  1592. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  1593. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  1594. //片尾的视频素材地址
  1595. let moveResPath = currentBundlePath()!.path(forResource: (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) < (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) ? "endMovieB" : "endMovieA", ofType: "mp4")
  1596. if(moveResPath?.count ?? 0 == 0){
  1597. BFLog(message: "片尾的视频素材地址无效!!!")
  1598. return
  1599. }
  1600. let movieAsset = AVURLAsset(url: URL(fileURLWithPath: moveResPath!), options: avAssertOptions)
  1601. let cbr = movieAsset.tracks(withMediaType: .video).first?.estimatedDataRate
  1602. BFLog(message: "cbr is\(cbr ?? 0)")
  1603. if Int(cbr ?? 0) > orgeBitRate {
  1604. orgeBitRate = Int(cbr ?? 0)
  1605. }
  1606. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  1607. //头像保存沙盒地址
  1608. BFLog(message: "头像的网络地址\(BFLoginUserInfo.shared.avatarUrl)")
  1609. let avatarFilePath = NSHomeDirectory().appending("/Documents/").appending("user_avatar.jpg")
  1610. // warning:给默认头像吧
  1611. ImageDownloader.default.downloadImage(with: URL(string: BFLoginUserInfo.shared.avatarUrl)!, options: nil) {[weak self] result in
  1612. var image : UIImage?
  1613. switch result {
  1614. case let .success(imageResult):
  1615. image = UIImage.nx_circleImage(imageResult.image)
  1616. case let .failure(error):
  1617. image = UIImage.moduleImage(named: "user_avatar_normal", moduleName: "BFFramework", isAssets:false)
  1618. BFLog(message: "下载头像图片失败:\(error.localizedDescription)")
  1619. }
  1620. if(image == nil){
  1621. BFLog(message: "image date is error!!")
  1622. return
  1623. }
  1624. UIImage.saveImage(currentImage: image!, outFilePath: avatarFilePath)
  1625. //1,背景视频素材
  1626. let bgMovieInfo:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1627. bgMovieInfo.type = StickerType.VIDEO.rawValue
  1628. bgMovieInfo.locationPath = moveResPath ?? ""
  1629. bgMovieInfo.timelineIn = 0
  1630. bgMovieInfo.timelineOut = CMTimeGetSeconds(movieAsset.duration)
  1631. bgMovieInfo.model_in = bgMovieInfo.timelineIn
  1632. bgMovieInfo.out = bgMovieInfo.timelineOut
  1633. bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  1634. //2,用户头像素材
  1635. BFLog(message: "头像的沙盒地址:\(avatarFilePath)")
  1636. let avatarSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1637. avatarSticker.locationPath = avatarFilePath.replacingOccurrences(of: documensDirectory, with: "")
  1638. avatarSticker.timelineIn = bgMovieInfo.timelineIn
  1639. avatarSticker.timelineOut = bgMovieInfo.timelineOut
  1640. avatarSticker.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  1641. //头像绘制大小\位置
  1642. var avatarSize:Float = 0.0
  1643. var avatarTop:Float = 0.0
  1644. if((self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) > (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){
  1645. //竖屏
  1646. avatarSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * 360.0 / 1080.0
  1647. avatarTop = 430
  1648. }else{
  1649. //横屏屏
  1650. avatarSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 300.0 / 1080.0
  1651. avatarTop = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 130.0 / 1080.0
  1652. }
  1653. let avatarPostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
  1654. avatarPostion.width = Int(avatarSize)
  1655. avatarPostion.height = Int(avatarSize)
  1656. avatarPostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - Int(avatarSize)) / 2
  1657. avatarPostion.y = Int(avatarTop)
  1658. avatarSticker.materialPosition = avatarPostion
  1659. //3,用户名素材
  1660. let userNameSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
  1661. userNameSticker.timelineIn = bgMovieInfo.timelineIn
  1662. userNameSticker.timelineOut = bgMovieInfo.timelineOut
  1663. userNameSticker.type = StickerType.SUBTITLE.rawValue
  1664. //用户名绘制用到的参数
  1665. var userNameTop:Float = 0.0
  1666. var userNameFontSize:Float = 0.0
  1667. if((self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) > (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){
  1668. //竖屏
  1669. userNameTop = 870
  1670. userNameFontSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * 100.0 / 1080.0
  1671. }else{
  1672. //横屏
  1673. userNameTop = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 480 / 1080.0
  1674. userNameFontSize = Float(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 70.0 / 1080.0
  1675. }
  1676. let subtitleInfo:PQEditSubtitleInfoModel = PQEditSubtitleInfoModel.init()
  1677. subtitleInfo.fontSize = Int(userNameFontSize)
  1678. subtitleInfo.text = BFLoginUserInfo.shared.nickName
  1679. userNameSticker.subtitleInfo = subtitleInfo
  1680. let userNamePostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
  1681. userNamePostion.width = Int(userNameFontSize ) * 10
  1682. userNamePostion.height = Int(userNameFontSize ) * 3
  1683. userNamePostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - userNamePostion.width) / 2
  1684. userNamePostion.y = Int(userNameTop)
  1685. userNameSticker.materialPosition = userNamePostion
  1686. //4,音频
  1687. let soundResPath = currentBundlePath()!.path(forResource: "endMovieSound", ofType: "mp3")
  1688. let soundAsset = AVURLAsset(url: URL(fileURLWithPath: soundResPath ?? ""), options: nil)
  1689. self?.endMovieExporter = PQCompositionExporter(asset: soundAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: [bgMovieInfo,avatarSticker,userNameSticker], animationTool: nil, exportURL: outPutMP4URL)
  1690. self?.endMovieExporter.isEndMovie = true
  1691. if ((self?.endMovieExporter.prepare(videoSize: CGSize(width: self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate)) != nil) {
  1692. self?.endMovieExporter.start(playeTimeRange: CMTimeRange.init(start: CMTime.zero, duration: CMTimeMakeWithSeconds(Float64(bgMovieInfo.out), preferredTimescale: BASE_FILTER_TIMESCALE)))
  1693. BFLog(message: "开始导出")
  1694. }
  1695. self?.endMovieExporter.progressClosure = { _, _, progress in
  1696. BFLog(message: "片尾合成进度 \(progress) ")
  1697. }
  1698. self?.endMovieExporter.completion = { [weak self] url in
  1699. BFLog(message: "片尾的视频导出完成: \(String(describing: url)) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url ?? URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!).duration))")
  1700. // 导出完成后取消导出
  1701. if self?.endMovieExporter != nil {
  1702. self?.endMovieExporter.cancel()
  1703. }
  1704. self?.endMovieLocalURL = url
  1705. //拼接水印正片和片尾
  1706. if(self?.watermarkMovieLocalURL != nil && self?.endMovieLocalURL != nil){
  1707. let videoMerge:NXVideoMerge = NXVideoMerge.init()
  1708. 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
  1709. if(isSuccess){
  1710. BFLog(message: "合并视频成功 outFilePath is \(outFileURL ?? "")")
  1711. self?.saveMovieLocalURL = outFileURL as? URL
  1712. //保存到相册 fp2-1-1 - 请求权限
  1713. self?.authorizationStatus()
  1714. }
  1715. }
  1716. }
  1717. }
  1718. }
  1719. }
  1720. }