PQStuckPointPublicController.swift 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380
  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. class PQStuckPointPublicController: PQBaseViewController {
  13. private var isShared: Bool = false // 是否在分享
  14. private var isExportSuccess: Bool = false // 是否导出完成
  15. private var isSaveDraftSuccess: Bool = false // 是否保存草稿完成
  16. private var isSaveProjectSuccess: Bool = false // 是否保存项目完成
  17. private var isUploadSuccess: Bool = false // 是否上传完成
  18. private var isPublicSuccess: Bool = false // 是否发布完成
  19. private var exportLocalURL: URL? // 导出的地址
  20. // 再创作数据
  21. private var reCreateData: PQReCreateModel?
  22. // 确定上传的数据
  23. private var uploadData: PQUploadModel?
  24. // 发布成功的视频数据
  25. private var videoData: PQVideoListModel?
  26. // 视频创作埋点数据
  27. private var eventTrackData: PQVideoMakeEventTrackModel?
  28. // 选中的总时长-统计使用
  29. var selectedTotalDuration: Float64 = 0
  30. // 选择的总数-统计使用
  31. var selectedDataCount: Int = 0
  32. // 选择的图片总数-统计使用
  33. var selectedImageDataCount: Int = 0
  34. // 最大的宽度
  35. private var maxWidth: CGFloat = cScreenWidth
  36. // 最大的高度
  37. private var maxHeight: CGFloat = 385
  38. // 开始导出的时间
  39. private let startExportDate: Float64 = Date().timeIntervalSince1970
  40. // 导出结束的时间
  41. private var exportEndDate: Float64 = Date().timeIntervalSince1970
  42. // 取到的封面 给发布界面使用
  43. private var coverImage: UIImage?
  44. // 导出视频工具类
  45. private var exporter: PQCompositionExporter!
  46. // 导出进度
  47. private var exportProgrss = 0
  48. var mStickers: [PQEditVisionTrackMaterialsModel]?
  49. var remindView: PQRemindView?
  50. //已经选择标题内容,加一个属性接收 使用有不在主线不能直接使用 titleLabel text
  51. var selectTitle:String = ""
  52. //add by ak 是否是再创作模式
  53. var isReCreate:Bool = false
  54. // 预览大小
  55. private var preViewSize: CGSize {
  56. switch aspectRatio {
  57. case let .origin(width, height):
  58. var tempHeight: CGFloat = 0
  59. var tempWidth: CGFloat = 0
  60. if width > height {
  61. tempWidth = maxWidth
  62. tempHeight = (maxWidth * height / width)
  63. if tempHeight > maxHeight {
  64. tempHeight = maxHeight
  65. tempWidth = (maxHeight * width / height)
  66. }
  67. } else {
  68. tempHeight = maxHeight
  69. tempWidth = (maxHeight * width / height)
  70. if tempWidth > maxWidth {
  71. tempWidth = maxWidth
  72. tempHeight = (maxWidth * height / width)
  73. }
  74. }
  75. if tempHeight.isNaN || tempWidth.isNaN {
  76. return CGSize.zero
  77. } else {
  78. return CGSize(width: tempWidth, height: tempHeight)
  79. }
  80. case .oneToOne:
  81. if maxWidth > maxHeight {
  82. return CGSize(width: maxHeight, height: maxHeight)
  83. } else {
  84. return CGSize(width: maxWidth, height: maxWidth)
  85. }
  86. case .sixteenToNine:
  87. return CGSize(width: maxWidth, height: maxWidth * 9.0 / 16.0)
  88. case .nineToSixteen:
  89. return CGSize(width: maxHeight * 9.0 / 16.0, height: maxHeight)
  90. default:
  91. break
  92. }
  93. return CGSize(width: maxHeight, height: maxHeight)
  94. }
  95. // 背景音乐
  96. var audioMixModel: PQVoiceModel?
  97. // 画面比例
  98. var aspectRatio: aspectRatio?
  99. // 导出的项目数据
  100. var editProjectModel: PQEditProjectModel? {
  101. didSet {
  102. aspectRatio = PQPlayerViewModel.videoCanvasTypeToAspectRatio(projectModel: editProjectModel)
  103. var totalDuration: Float64 = 0
  104. if editProjectModel?.sData?.sections.count ?? 0 > 0 {
  105. for section in (editProjectModel?.sData?.sections)! {
  106. totalDuration = totalDuration + section.sectionDuration
  107. }
  108. }
  109. editProjectModel?.sData?.videoMetaData?.duration = totalDuration
  110. if editProjectModel?.sData?.sections != nil, (editProjectModel?.sData?.sections.count ?? 0) > 0 {
  111. // 查找出背景图并设置
  112. var coverImageMaterialsModel: PQEditVisionTrackMaterialsModel?
  113. for section in (editProjectModel?.sData?.sections)! {
  114. if coverImageMaterialsModel != nil {
  115. break
  116. }
  117. coverImageMaterialsModel = section.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first
  118. }
  119. if coverImageMaterialsModel != nil {
  120. coverImage = coverImageMaterialsModel?.getCoverImage()
  121. playerHeaderView.image = coverImage
  122. playerHeaderView.contentMode = coverImageMaterialsModel!.canvasFillType == stickerContentMode.aspectFitStr.rawValue ? .scaleAspectFill : .scaleAspectFit
  123. }
  124. }
  125. }
  126. }
  127. /// 所有需要导出的filter
  128. var filters: Array = Array<ImageProcessingOperation>.init()
  129. /// 预览背景页
  130. lazy var bgTopView: UIView = {
  131. let bgTopView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: maxHeight))
  132. bgTopView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  133. return bgTopView
  134. }()
  135. // 预览界面
  136. var playerHeaderView: UIImageView = {
  137. let playerHeaderView = UIImageView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 0))
  138. playerHeaderView.isUserInteractionEnabled = true
  139. playerHeaderView.contentMode = .scaleAspectFit
  140. playerHeaderView.clipsToBounds = true
  141. return playerHeaderView
  142. }()
  143. // add by ak 播放器的封面 为了不和原有的播放器层级单独添加一个 view
  144. lazy var playerHeaderCoverImageView: UIImageView = {
  145. let playerHeaderCoverImageView = UIImageView.init()
  146. playerHeaderCoverImageView.isUserInteractionEnabled = true
  147. playerHeaderCoverImageView.contentMode = .scaleAspectFit
  148. playerHeaderCoverImageView.clipsToBounds = true
  149. let playBtn = UIButton(type: .custom)
  150. playBtn.setImage(UIImage().BF_Image(named: "icon_video_play"), for: .normal)
  151. playBtn.tag = 4
  152. playBtn.isUserInteractionEnabled = false
  153. playerHeaderCoverImageView.addSubview(playBtn)
  154. playerHeaderCoverImageView.isHidden = true
  155. return playerHeaderCoverImageView
  156. }()
  157. /// 播放器
  158. lazy var avPlayer: AVPlayer = {
  159. let avPlayer = AVPlayer()
  160. NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem, queue: .main) { [weak self] notify in
  161. BFLog(message: "AVPlayerItemDidPlayToEndTime = \(notify)")
  162. avPlayer.seek(to: CMTime.zero)
  163. if(self?.playerHeaderCoverImageView.image != nil){
  164. self?.playerHeaderCoverImageView.isHidden = false
  165. }
  166. self?.playBtn.isHidden = false
  167. }
  168. NotificationCenter.default.addObserver(forName: .AVPlayerItemNewErrorLogEntry, object: avPlayer.currentItem, queue: .main) { notify in
  169. BFLog(message: "AVPlayerItemNewErrorLogEntry = \(notify)")
  170. }
  171. NotificationCenter.default.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: avPlayer.currentItem, queue: .main) { notify in
  172. BFLog(message: "AVPlayerItemFailedToPlayToEndTime = \(notify)")
  173. }
  174. NotificationCenter.default.addObserver(forName: .AVPlayerItemPlaybackStalled, object: avPlayer.currentItem, queue: .main) { notify in
  175. BFLog(message: "AVPlayerItemPlaybackStalled = \(notify)")
  176. }
  177. avPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1000), queue: .main) { [weak self] _ in
  178. let progress = CMTimeGetSeconds(avPlayer.currentItem?.currentTime() ?? CMTime.zero) / CMTimeGetSeconds(avPlayer.currentItem?.duration ?? CMTime.zero)
  179. if progress >= 1 {
  180. self?.playBtn.isHidden = false
  181. }
  182. }
  183. return avPlayer
  184. }()
  185. /// 预览layer
  186. lazy var playerLayer: AVPlayerLayer = {
  187. let playerLayer = AVPlayerLayer(player: avPlayer)
  188. playerLayer.frame = playerHeaderView.bounds
  189. return playerLayer
  190. }()
  191. /// 播放按钮
  192. lazy var playBtn: UIButton = {
  193. let playBtn = UIButton(type: .custom)
  194. playBtn.frame = CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
  195. playBtn.setImage(UIImage().BF_Image(named: "icon_video_play"), for: .normal)
  196. playBtn.tag = 4
  197. playBtn.isHidden = true
  198. playBtn.isUserInteractionEnabled = false
  199. return playBtn
  200. }()
  201. // progressTipsLab
  202. lazy var progressTipsLab: UILabel = {
  203. let progressTipsLab = UILabel()
  204. progressTipsLab.textAlignment = .center
  205. progressTipsLab.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  206. progressTipsLab.numberOfLines = 2
  207. progressTipsLab.textColor = UIColor.white
  208. let attributedText = NSMutableAttributedString(string: "0%\n视频正在处理中,请勿离开")
  209. attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: 2))
  210. progressTipsLab.attributedText = attributedText
  211. progressTipsLab.addShadow()
  212. return progressTipsLab
  213. }()
  214. // 进度条
  215. lazy var progressView: UIProgressView = {
  216. let progressView = UIProgressView(progressViewStyle: .default)
  217. progressView.trackTintColor = UIColor(white: 0, alpha: 0.5)
  218. progressView.progressTintColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  219. progressView.transform = CGAffineTransform(scaleX: 1.0, y: playerHeaderView.frame.height)
  220. return progressView
  221. }()
  222. //提示
  223. lazy var remindLab: UILabel = {
  224. let remindLab = UILabel()
  225. remindLab.font = UIFont.boldSystemFont(ofSize: 18)
  226. remindLab.textColor = PQBFConfig.shared.styleTitleColor
  227. remindLab.textAlignment = .center
  228. remindLab.numberOfLines = 2
  229. remindLab.backgroundColor = .clear
  230. remindLab.text = "为你的大作起个响亮的标题\n分享秀一下🎉"
  231. return remindLab
  232. }()
  233. //输入框背景
  234. lazy var inputBackView: UIView = {
  235. let inputBackView = UIView()
  236. inputBackView.backgroundColor = .clear
  237. inputBackView.layer.cornerRadius = 7
  238. inputBackView.layer.borderWidth = 2
  239. inputBackView.layer.borderColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue).cgColor
  240. return inputBackView
  241. }()
  242. // 手势提示
  243. lazy var pinView: UIImageView = {
  244. let pinView = UIImageView()
  245. pinView.kf.setImage(with: URL(fileURLWithPath: Bundle().BF_mainbundle().path(forResource: "editCoverPin", ofType: ".gif")!))
  246. return pinView
  247. }()
  248. //封面
  249. lazy var coverImageView: UIImageView = {
  250. let coverImageView = UIImageView()
  251. coverImageView.isUserInteractionEnabled = true
  252. coverImageView.backgroundColor = .clear
  253. coverImageView.contentMode = .scaleToFill
  254. return coverImageView
  255. }()
  256. //封面标题
  257. lazy var coverImageTitle: UILabel = {
  258. let coverImageTitle = UILabel()
  259. coverImageTitle.text = "换封面"
  260. coverImageTitle.textAlignment = .center
  261. coverImageTitle.backgroundColor = UIColor(red: 0.22, green: 0.26, blue: 0.35, alpha: 0.5)
  262. coverImageTitle.isUserInteractionEnabled = true
  263. coverImageTitle.textColor = .white
  264. coverImageTitle.font = UIFont.boldSystemFont(ofSize: 12)
  265. return coverImageTitle
  266. }()
  267. //标题
  268. lazy var titleLabel: UILabel = {
  269. let titleLabel = UILabel()
  270. titleLabel.numberOfLines = 2
  271. titleLabel.isUserInteractionEnabled = true
  272. titleLabel.textColor = UIColor.hexColor(hexadecimal: "#ABABAB")
  273. titleLabel.textAlignment = .left
  274. titleLabel.font = UIFont.systemFont(ofSize: 17)
  275. let ges = UITapGestureRecognizer(target: self, action: #selector(titleLabelClick))
  276. titleLabel.addGestureRecognizer(ges)
  277. return titleLabel
  278. }()
  279. //编辑发布标题
  280. lazy var publicTitleView: PQEditPublicTitleView = {
  281. let publicTitleView = PQEditPublicTitleView()
  282. publicTitleView.isHidden = true
  283. publicTitleView.confirmBtnClock = { [weak self] title in
  284. BFLog(message: "传出的 title is :\(String(describing: title))")
  285. if(title?.count != 0 && title != self?.titleLabel.text){
  286. self?.changPlayerIsPause(isPause: false)
  287. //判断文字是否有效
  288. var inputText = ""
  289. inputText = title?.replacingOccurrences(of: "\n", with: "") ?? ""
  290. inputText = inputText.replacingOccurrences(of: " ", with: "")
  291. if(inputText.count > 0){
  292. self?.setTitleText(text: title ?? "",textColor: .black)
  293. //更新数据
  294. self?.videoData?.title = title
  295. self?.updateCoverImagegOrTitle()
  296. }
  297. }
  298. }
  299. publicTitleView.viewIsHiddenCallBack = { [weak self] in
  300. self?.changPlayerIsPause(isPause: false)
  301. }
  302. return publicTitleView
  303. }()
  304. //编辑发布封面
  305. lazy var publicEditCoverView: PQEditPublicCoverImageView = {
  306. let publicEditCoverView = PQEditPublicCoverImageView.init(frame: CGRect.init(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  307. publicEditCoverView.isHidden = true
  308. return publicEditCoverView
  309. }()
  310. //分享到朋友圈
  311. lazy var shareWechatBtn: UIButton = {
  312. let shareWechatBtn = UIButton(type: .custom)
  313. shareWechatBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
  314. shareWechatBtn.setImage(UIImage().BF_Image(named: "reCreate_opration_wechat"), for: .normal)
  315. shareWechatBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  316. shareWechatBtn.addCorner(corner: 6)
  317. shareWechatBtn.tag = 2
  318. shareWechatBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  319. return shareWechatBtn
  320. }()
  321. //分享到好友
  322. lazy var shareFriendBtn: UIButton = {
  323. let shareFriendBtn = UIButton(type: .custom)
  324. shareFriendBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
  325. shareFriendBtn.setImage(UIImage().BF_Image(named: "reCreate_opration_friend"), for: .normal)
  326. shareFriendBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  327. shareFriendBtn.addCorner(corner: 6)
  328. shareFriendBtn.tag = 1
  329. shareFriendBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  330. return shareFriendBtn
  331. }()
  332. //关闭
  333. lazy var finishedBtn: UIButton = {
  334. let finishedBtn = UIButton(type: .custom)
  335. finishedBtn.setTitle("完成", for: .normal)
  336. finishedBtn.setTitleColor(UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue), for: .normal)
  337. finishedBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  338. finishedBtn.backgroundColor = .clear
  339. finishedBtn.tag = 3
  340. finishedBtn.addCorner(corner: 3)
  341. finishedBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
  342. return finishedBtn
  343. }()
  344. /// 背景View
  345. lazy var oprationBgView: UIView = {
  346. let oprationBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei))
  347. oprationBgView.backgroundColor = .clear
  348. return oprationBgView
  349. }()
  350. //除了播放器以外的 下半部分操作区
  351. lazy var bottomOprationBgView: UIView = {
  352. let bottomOprationBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei + maxHeight, width: cScreenWidth, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei - maxHeight))
  353. bottomOprationBgView.backgroundColor = .clear
  354. bottomOprationBgView.isHidden = true
  355. return bottomOprationBgView
  356. }()
  357. override func backBtnClick() {
  358. if isExportSuccess {
  359. navigationController?.popViewController(animated: true)
  360. } else {
  361. view.endEditing(true)
  362. let remindData = PQBaseModel()
  363. remindData.title = "编辑的内容,将不会被保存"
  364. remindView = PQRemindView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth))
  365. remindView?.isBanned = true
  366. remindView?.confirmBtn.setTitle("确认", for: .normal)
  367. remindView?.cancelBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#333333"), for: .normal)
  368. remindView?.confirmBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#EE0051"), for: .normal)
  369. UIApplication.shared.keyWindow?.addSubview(remindView!)
  370. remindView?.remindData = remindData
  371. remindView?.remindBlock = { [weak self] item, _ in
  372. if item.tag == 2 {
  373. // 取消导出
  374. if self?.exporter != nil {
  375. self?.exporter.cancel()
  376. }
  377. self?.navigationController?.popViewController(animated: true)
  378. }
  379. }
  380. }
  381. }
  382. override func viewDidLoad() {
  383. super.viewDidLoad()
  384. // 注册上传成功的通知
  385. addNotification(self, selector: #selector(uploadSuccess(notify:)), name: cUploadSuccessKey, object: nil)
  386. PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
  387. leftButton(image: "icon_detail_back", tintColor: PQBFConfig.shared.styleTitleColor)
  388. navHeadImageView?.backgroundColor = UIColor.clear
  389. lineView?.removeFromSuperview()
  390. view.addSubview(bgTopView)
  391. playerHeaderView.frame = CGRect(origin: CGPoint(x: (cScreenWidth - preViewSize.width) / 2, y: (maxHeight - preViewSize.height) / 2), size: preViewSize)
  392. let ges = UITapGestureRecognizer(target: self, action: #selector(playVideo))
  393. playerHeaderView.addGestureRecognizer(ges)
  394. if playerLayer.superlayer == nil {
  395. playerHeaderView.layer.insertSublayer(playerLayer, at: 0)
  396. }
  397. playerHeaderView.addSubview(playBtn)
  398. playerHeaderView.addSubview(progressView)
  399. view.addSubview(oprationBgView)
  400. oprationBgView.addSubview(progressTipsLab)
  401. // 添加导出view
  402. bgTopView.addSubview(playerHeaderView)
  403. playerHeaderCoverImageView.frame = playerHeaderView.frame
  404. playerHeaderCoverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(playVideo)))
  405. (playerHeaderCoverImageView.viewWithTag(4))?.frame =
  406. CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
  407. bgTopView.addSubview(playerHeaderCoverImageView)
  408. view.addSubview(bottomOprationBgView)
  409. bottomOprationBgView.addSubview(remindLab)
  410. bottomOprationBgView.addSubview(shareWechatBtn)
  411. bottomOprationBgView.addSubview(shareFriendBtn)
  412. bottomOprationBgView.addSubview(finishedBtn)
  413. bottomOprationBgView.addSubview(inputBackView)
  414. bottomOprationBgView.addSubview(pinView)
  415. inputBackView.addSubview(coverImageView)
  416. coverImageView.addSubview(coverImageTitle)
  417. inputBackView.addSubview(titleLabel)
  418. view.addSubview(publicTitleView)
  419. view.addSubview(publicEditCoverView)
  420. coverImageTitle.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  421. coverImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingCoverImage)))
  422. progressView.snp.makeConstraints { make in
  423. make.left.right.centerY.equalTo(playerHeaderView)
  424. make.height.equalTo(3)
  425. }
  426. progressTipsLab.snp.makeConstraints { make in
  427. make.centerX.equalToSuperview()
  428. make.top.equalToSuperview().offset(((preViewSize.height - 90) / 2) + ((maxHeight - preViewSize.height) / 2))
  429. make.width.equalToSuperview()
  430. make.height.equalTo(90)
  431. }
  432. finishedBtn.snp.makeConstraints { make in
  433. make.centerX.equalToSuperview()
  434. make.bottom.equalToSuperview().offset(-cSafeAreaHeight)
  435. make.width.equalTo(100)
  436. make.height.equalTo(22)
  437. }
  438. shareWechatBtn.snp.makeConstraints { make in
  439. make.right.equalTo(view.snp_centerX).offset(-cDefaultMargin)
  440. make.width.equalTo(164)
  441. make.height.equalTo(52)
  442. make.bottom.equalTo(finishedBtn.snp_top).offset(-34)
  443. }
  444. shareFriendBtn.snp.makeConstraints { make in
  445. make.left.equalTo(view.snp_centerX).offset(cDefaultMargin)
  446. make.width.bottom.height.equalTo(shareWechatBtn)
  447. }
  448. inputBackView.snp.makeConstraints { make in
  449. make.centerX.equalToSuperview()
  450. make.bottom.equalTo(shareWechatBtn.snp_top).offset(-16)
  451. make.width.equalTo(343)
  452. make.height.equalTo(109)
  453. }
  454. //根据横竖屏设置不同的 UI
  455. let isWidth:Bool = (Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) ) >= 1
  456. var coverImageViewHeight = 50.0 * Float(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) / Float(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  457. if(coverImageViewHeight > 89){
  458. coverImageViewHeight = 89
  459. }
  460. coverImageView.snp.makeConstraints { make in
  461. make.left.equalToSuperview().offset(12)
  462. make.width.equalTo(50)
  463. make.top.equalToSuperview().offset(10)
  464. make.height.equalTo(coverImageViewHeight)
  465. }
  466. coverImageTitle.snp.makeConstraints { make in
  467. make.left.equalToSuperview()
  468. make.width.equalTo(50)
  469. make.top.equalTo(coverImageView.snp_bottom).offset(isWidth ? 0 : -23)
  470. make.height.equalTo(23)
  471. }
  472. remindLab.snp.makeConstraints { make in
  473. make.centerX.equalToSuperview()
  474. make.bottom.equalTo(inputBackView.snp_top).offset(-16)
  475. }
  476. titleLabel.snp.makeConstraints { make in
  477. make.height.equalTo(48)
  478. make.left.equalTo(coverImageView.snp_right).offset(12)
  479. make.right.equalToSuperview().offset(-14)
  480. make.top.equalToSuperview().offset(10)
  481. }
  482. pinView.snp.makeConstraints { make in
  483. make.height.width.equalTo(72)
  484. make.right.equalToSuperview()
  485. make.bottom.equalTo(inputBackView.snp_bottom)
  486. }
  487. publicTitleView.snp.makeConstraints { make in
  488. make.height.equalTo(cScreenHeigth)
  489. make.width.equalTo(cScreenWidth)
  490. make.bottom.equalToSuperview()
  491. }
  492. // 取消所有的导出
  493. PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in
  494. exportSession.cancelExport()
  495. }
  496. // 开始导出
  497. beginExport()
  498. /// 保存草稿
  499. saveDraftbox()
  500. // 曝光上报:窗口曝光
  501. PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_publishSyncedUp, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:窗口曝光)")
  502. //取推荐标题
  503. getTitles()
  504. }
  505. override func viewWillAppear(_ animated: Bool) {
  506. super.viewWillAppear(animated)
  507. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  508. PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  509. UIApplication.shared.isIdleTimerDisabled = true
  510. //从相册选择一个照片后回调
  511. addNotification(self, selector: #selector(imageSelectedImage(notify:)), name: cSelectedImageSuccessKey, object: nil)
  512. #if swift(>=4.2)
  513. let memoryNotification = UIApplication.didReceiveMemoryWarningNotification
  514. _ = UIApplication.willTerminateNotification
  515. _ = UIApplication.didEnterBackgroundNotification
  516. #else
  517. let memoryNotification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning
  518. let terminateNotification = NSNotification.Name.UIApplicationWillTerminate
  519. let enterbackgroundNotification = NSNotification.Name.UIApplicationDidEnterBackground
  520. #endif
  521. NotificationCenter.default.addObserver(
  522. self, selector: #selector(clearMemoryCache), name: memoryNotification, object: nil
  523. )
  524. }
  525. @objc public func clearMemoryCache() {
  526. BFLog(message: "收到内存警告")
  527. }
  528. override func viewWillDisappear(_ animated: Bool) {
  529. super.viewWillDisappear(animated)
  530. UIApplication.shared.isIdleTimerDisabled = false
  531. }
  532. deinit {
  533. view.endEditing(true)
  534. PQNotification.removeObserver(self)
  535. // 取消导出
  536. if exporter != nil {
  537. exporter.cancel()
  538. }
  539. avPlayer.pause()
  540. avPlayer.replaceCurrentItem(with: nil)
  541. // 点击上报:返回按钮
  542. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)")
  543. }
  544. }
  545. // MARK: - 导出/上传/下载及其他方法
  546. /// 导出/上传/下载及其他方法
  547. extension PQStuckPointPublicController {
  548. /// fp1 - 导出视频
  549. /// 开始导出视频
  550. func beginExport() {
  551. if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
  552. BFLog(message: "项目段落错误❌")
  553. return
  554. }
  555. // 输出视频地址
  556. var outPutMP4Path = exportVideosDirectory
  557. if !directoryIsExists(dicPath: outPutMP4Path) {
  558. BFLog(message: "文件夹不存在")
  559. createDirectory(path: outPutMP4Path)
  560. }
  561. outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
  562. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  563. BFLog(message: "导出视频地址 \(outPutMP4URL)")
  564. let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
  565. // 每次初始化的时候设置初始值 为 nIl
  566. exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
  567. var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
  568. if(mStickers != nil){
  569. for stick in mStickers! {
  570. if stick.type == StickerType.VIDEO.rawValue {
  571. let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
  572. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  573. if Int(cbr ?? 0) > orgeBitRate {
  574. orgeBitRate = Int(cbr ?? 0)
  575. }
  576. }
  577. }
  578. }
  579. BFLog(message: "导出设置的码率为:\(orgeBitRate)")
  580. exporter.showGaussianBlur = true
  581. if exporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
  582. let playeTimeRange: CMTimeRange = CMTimeRange(start: CMTime(value: CMTimeValue(Int((audioMixModel?.startTime ?? 0) * 600)), timescale: 600), end: CMTime(value: CMTimeValue(Int((audioMixModel?.endTime ?? 0) * 600)), timescale: 600))
  583. BFLog(message: "开始导出 \(String(describing: audioMixModel?.startTime)) 结束 \(String(describing: audioMixModel?.endTime))")
  584. exporter.start(playeTimeRange: playeTimeRange)
  585. BFLog(message: "开始导出")
  586. }
  587. exporter.progressClosure = { [weak self] _, _, progress in
  588. BFLog(message: "合成进度 \(progress)")
  589. let useProgress = progress > 1 ? 1 : progress
  590. if progress > 0, Int(useProgress * 100) > (self?.exportProgrss ?? 0) {
  591. // 更新进度
  592. self?.updatePublicCurrentProgress(useProgress: useProgress * 0.88)
  593. }
  594. }
  595. exporter.completion = { [weak self] url in
  596. BFLog(message: "MovieOutput total frames appended:导了完成: \(url) 生成视频时长为:\(CMTimeGetSeconds(AVAsset.init(url: url).duration))")
  597. // 导出完成后取消导出
  598. if self?.exporter != nil {
  599. self?.exporter.cancel()
  600. }
  601. self?.remindView?.removeFromSuperview()
  602. if !(self?.isExportSuccess ?? false) {
  603. self?.isExportSuccess = true
  604. self?.exportEndDate = Date().timeIntervalSince1970
  605. BFLog(message: "视频导出完成-开始去发布视频")
  606. self?.exportLocalURL = url
  607. /// fp2-1-1 - 请求权限
  608. // self?.authorizationStatus()
  609. /// fp2-2 - 保存草稿
  610. // self?.saveDraftbox()
  611. /// fp2 - 处理视频数据
  612. self?.dealWithVideoData()
  613. }
  614. }
  615. }
  616. /// fp2-1-1 - 请求权限
  617. func authorizationStatus() {
  618. let authStatus = PHPhotoLibrary.authorizationStatus()
  619. if authStatus == .notDetermined {
  620. // 第一次触发授权 alert
  621. PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in
  622. if status != .authorized {
  623. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  624. } else {
  625. /// fp2-1-2 - 保存视频到相册
  626. self?.saveStuckPointVideo()
  627. }
  628. }
  629. } else if authStatus == .authorized {
  630. /// fp2-1-2 - 保存视频到相册
  631. saveStuckPointVideo()
  632. } else {
  633. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  634. }
  635. }
  636. /// fp2-1-2 - 保存视频到相册
  637. /// - Parameter localPath: localPath description
  638. /// - Returns: <#description#>
  639. func saveStuckPointVideo() {
  640. let authStatus = PHPhotoLibrary.authorizationStatus()
  641. if authStatus == .authorized {
  642. let photoLibrary = PHPhotoLibrary.shared()
  643. photoLibrary.performChanges({ [weak self] in
  644. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.exportLocalURL)!)
  645. }) { [weak self] isFinished, _ in
  646. DispatchQueue.main.async { [weak self] in
  647. if self?.view != nil {
  648. if isFinished {
  649. // cShowHUB(superView: self!.view, msg: "视频已保存至相册")
  650. } else {
  651. // cShowHUB(superView: self!.view, msg: "视频保存失败")
  652. }
  653. }
  654. }
  655. }
  656. } else {
  657. // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  658. }
  659. }
  660. /// fp2-2 - 保存草稿
  661. /// - Returns: <#description#>
  662. @objc func saveDraftbox() {
  663. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false)
  664. if sdata != nil, (sdata?.count ?? 0) > 0 {
  665. DispatchQueue.global().async { [weak self] in
  666. 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
  667. if draftboxInfo != nil {
  668. self?.editProjectModel?.draftboxId = draftboxInfo?["draftboxId"] as? String ?? ""
  669. self?.editProjectModel?.sData?.videoMetaData?.title = draftboxInfo?["title"] as? String ?? ""
  670. self?.editProjectModel?.sData?.videoMetaData?.coverUrl = draftboxInfo?["coverUrl"] as? String ?? ""
  671. self?.editProjectModel?.dataVersionCode = draftboxInfo?["dataVersionCode"] as? Int ?? 0
  672. BFLog(message: "保存远程的草稿成功")
  673. self?.isSaveDraftSuccess = true
  674. /// fp3 - 保存项目
  675. self?.saveProject()
  676. } else {
  677. // 保存草稿失败-播放视频
  678. // self?.publicEnd(isError: true)
  679. }
  680. }
  681. }
  682. } else {
  683. cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限")
  684. // 保存草稿失败-播放视频
  685. publicEnd(isError: true)
  686. }
  687. }
  688. /// fp3 - 保存项目
  689. /// - Returns: description
  690. func saveProject() {
  691. if isSaveDraftSuccess {
  692. let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false) ?? ""
  693. let draftboxId: String? = editProjectModel?.draftboxId
  694. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in
  695. BFLog(message: "生成的项目id1111 :\(projectId ?? ""),msg = \(msg ?? "")")
  696. if projectId == nil || (projectId?.count ?? 0) <= 0 {
  697. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in
  698. BFLog(message: "生成的项目id222 :\(projectId ?? ""),msg = \(msg ?? "")")
  699. if projectId == nil || (projectId?.count ?? 0) <= 0 {
  700. PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in
  701. BFLog(message: "生成的项目id 3333:\(projectId ?? ""),msg = \(msg ?? "")")
  702. if projectId != nil, (projectId?.count ?? 0) > 0 {
  703. self?.editProjectModel?.projectId = projectId ?? ""
  704. }
  705. /// fp4 - 处理视频数据
  706. // self?.dealWithVideoData()
  707. }
  708. } else {
  709. self?.editProjectModel?.projectId = projectId ?? ""
  710. /// fp4 - 处理视频数据
  711. // self?.dealWithVideoData()
  712. }
  713. }
  714. } else {
  715. self?.editProjectModel?.projectId = projectId ?? ""
  716. /// fp4 - 处理视频数据
  717. // self?.dealWithVideoData()
  718. }
  719. }
  720. }
  721. }
  722. /// fp4 - 处理视频数据
  723. /// - Returns: description
  724. @objc func dealWithVideoData() {
  725. BFLog(message: "开始去发布视频12")
  726. isSaveProjectSuccess = true
  727. if isExportSuccess && exportLocalURL != nil {
  728. BFLog(message: "素材上传完成同时视频导出完成开始发布视频")
  729. // 更新项目
  730. PQBaseViewModel.updateProject(projectId: editProjectModel?.projectId ?? "", produceStatus: "5") { repseon, _ in
  731. BFLog(message: "updateProject 结果 is \(String(describing: repseon))")
  732. }
  733. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  734. let tempUploadData = PQUploadModel()
  735. tempUploadData.duration = CMTimeGetSeconds(asset.duration)
  736. tempUploadData.localPath = exportLocalURL?.absoluteString
  737. tempUploadData.videoWidth = CGFloat(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  738. tempUploadData.videoHeight = CGFloat(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  739. tempUploadData.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  740. if tempUploadData.image == nil {
  741. tempUploadData.image = coverImage
  742. }
  743. tempUploadData.videoFromScene = .stuckPoint
  744. eventTrackData = getExportEventTrackData()
  745. eventTrackData?.projectId = editProjectModel?.projectId ?? ""
  746. uploadData = tempUploadData
  747. if uploadData?.image == nil {
  748. uploadData?.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0)
  749. }
  750. if uploadData?.image != nil {
  751. playerHeaderView.image = uploadData?.image
  752. coverImageView.image = uploadData?.image
  753. }
  754. if isExportSuccess, exportLocalURL != nil {
  755. let size = try! exportLocalURL?.resourceValues(forKeys: [.fileSizeKey])
  756. BFLog(message: "size = \(String(describing: size))")
  757. if Float64(size?.fileSize ?? 0) <= maxUploadSize {
  758. /// fp5 - 上传视频
  759. reUploadVideo()
  760. }
  761. }
  762. }
  763. }
  764. /// fp5 - 上传视频
  765. /// - Returns: <#description#>
  766. @objc func reUploadVideo() {
  767. if uploadData?.stsToken != nil {
  768. multipartUpload(response: uploadData?.stsToken)
  769. } else {
  770. uploadVideo()
  771. }
  772. }
  773. /// fp5-1 - 开始上传视频
  774. /// - Returns: <#description#>
  775. func uploadVideo() {
  776. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  777. if uploadRequest != nil, "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1" {
  778. return
  779. }
  780. // 更新进度
  781. updatePublicCurrentProgress(useProgress: 0.89)
  782. DispatchQueue.global().async {
  783. PQBaseViewModel.getStsToken { [weak self] response, _ in
  784. if response == nil {
  785. self?.showUploadRemindView(isNetCollected: false, msg: "获取数据失败了哦~")
  786. return
  787. }
  788. // 更新进度
  789. self?.updatePublicCurrentProgress(useProgress: 0.90)
  790. BFLog(message: "取我方服务器STS 返回数据 \(String(describing: response))")
  791. self?.multipartUpload(response: response)
  792. }
  793. }
  794. }
  795. /// fp5-2 - 继续上传视频
  796. /// - Parameter response: <#response description#>
  797. func multipartUpload(response: [String: Any]?) {
  798. let FileName: String = "\(response?["FileName"] ?? "")"
  799. let uploadID: String = "\(response?["Upload"] ?? "")"
  800. uploadData?.stsToken = response
  801. uploadData?.videoBucketKey = FileName
  802. uploadData?.uploadID = uploadID
  803. if uploadData?.asset != nil && isValidURL(url: uploadData?.localPath) {
  804. PQPHAssetVideoParaseUtil.exportPHAssetToMP4(phAsset: (uploadData?.asset)!, isCancelCurrentExport: true) { [weak self] _, _, filePath, _ in
  805. if filePath != nil, (filePath?.count ?? 0) > 0 {
  806. self?.uploadData?.localPath = filePath
  807. PQAliOssUtil.multipartUpload(localPath: self?.uploadData?.localPath ?? "", response: response)
  808. }
  809. }
  810. } else {
  811. PQAliOssUtil.multipartUpload(localPath: uploadData?.localPath ?? "", response: response)
  812. }
  813. PQAliOssUtil.shared.aliOssHander = { [weak self] isMatarialUpload, materialType, _, code, objectkey, _, _, _, _, _, _, _, _, _ in
  814. if !isMatarialUpload, materialType == .VIDEO, self?.uploadData?.videoBucketKey == objectkey {
  815. if code == 6 { // 无网
  816. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[self?.uploadData?.videoBucketKey ?? ""]
  817. if !(uploadRequest != nil && "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1") {
  818. self?.showUploadRemindView()
  819. }
  820. } else if code == 260 {
  821. self?.showUploadRemindView(isNetCollected: false)
  822. } else if code != 1 {
  823. // 上传失败-播放视频
  824. self?.publicEnd(isError: true)
  825. }
  826. }
  827. }
  828. PQAliOssUtil.shared.aliOssProgressHander = { [weak self] bytesSent, totalBytesSent, totalBytesExpectedToSend, _, _ in
  829. let progress: Float = 0.90 + Float(Float(totalBytesSent) / Float(totalBytesExpectedToSend)) * 0.09
  830. BFLog(message: "卡点视频上传:bytesSent = \(bytesSent),totalBytesSent = \(totalBytesSent),totalBytesExpectedToSend = \(totalBytesExpectedToSend),progress = \(progress)")
  831. if progress >= 0.90, progress <= 0.99 {
  832. // 更新进度
  833. self?.updatePublicCurrentProgress(useProgress: progress)
  834. }
  835. }
  836. }
  837. /// fp6 - 视频上传成功,处理要发布视频数据
  838. /// - Parameter notify: <#notify description#>
  839. @objc func uploadSuccess(notify: NSNotification) {
  840. let objectKey: String = "\(notify.userInfo?["objectKey"] ?? "")"
  841. BFLog(message: "收到上传成功请求==\(notify.userInfo ?? [:])")
  842. if uploadData?.videoBucketKey == objectKey {
  843. // 上传成功
  844. isUploadSuccess = true
  845. /// fp7 - 处理要发布视频数据
  846. dealWithPublicData()
  847. }
  848. }
  849. /// fp7 - 处理要发布视频数据
  850. /// - Returns: <#description#>
  851. func dealWithPublicData() {
  852. if uploadData?.localPath != nil {
  853. let size = try! URL(string: uploadData?.localPath ?? "")?.resourceValues(forKeys: [.fileSizeKey])
  854. BFLog(message: "size = \(String(describing: size))")
  855. if Float64(size?.fileSize ?? 0) > maxUploadSize {
  856. cShowHUB(superView: nil, msg: "无法发布大于10G的视频,请重新选择/合成发布")
  857. // 上传失败-播放视频
  858. publicEnd(isError: true)
  859. return
  860. }
  861. }
  862. let projectId: String? = editProjectModel?.projectId
  863. let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  864. if uploadRequest == nil {
  865. reUploadVideo()
  866. return
  867. }
  868. let tempModel = PQVideoListModel()
  869. tempModel.title = selectTitle
  870. tempModel.summary = ""
  871. tempModel.duration = CGFloat(uploadData?.duration ?? 0)
  872. tempModel.uplpadImage = uploadData?.image
  873. tempModel.uplpadBucketKey = uploadRequest?.objectKey
  874. tempModel.localPath = uploadData?.localPath
  875. tempModel.reCreateVideoData = reCreateData
  876. tempModel.eventTrackData = eventTrackData
  877. tempModel.uplpadStatus = 1
  878. tempModel.videoFromScene = .stuckPoint
  879. tempModel.uid = Int(BFLoginUserInfo.shared.uid) ?? 0
  880. tempModel.uplpadRequest = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""]
  881. tempModel.stsToken = uploadData?.stsToken
  882. tempModel.projectId = projectId
  883. /// fp8 - 发布视频
  884. publicVideo(videoData: tempModel)
  885. }
  886. /// fp8 - 发布视频
  887. /// - Parameter videoData: <#videoData description#>
  888. func publicVideo(videoData: PQVideoListModel) {
  889. if videoData.uplpadBucketKey == nil {
  890. BFLog(message: "发布视频:视频uplpadBucketKey为空-\(String(describing: videoData.uplpadBucketKey))")
  891. // 上传失败-播放视频
  892. publicEnd(isError: true)
  893. return
  894. }
  895. BFLog(message: "开始发布")
  896. if (videoData.eventTrackData?.endUploadDate ?? 0) <= 0 {
  897. // 结束上传时间
  898. videoData.eventTrackData?.endUploadDate = Date().timeIntervalSince1970
  899. }
  900. DispatchQueue.global().async {
  901. // PQBaseViewModel.ossTempToken { [weak self] response, _ in
  902. // let image: UIImage = videoData.uplpadImage ?? UIImage()
  903. // let data = image.jpegData(compressionQuality: 1)
  904. // let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  905. // let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  906. // let securityToken: String = "\(response?["securityToken"] ?? "")"
  907. // let endpoint: String = "\(response?["uploadDomain"] ?? "")"
  908. // let bucketName: String = "\(response?["bucketName"] ?? "")"
  909. // let objectKey: String = "\(response?["objectKey"] ?? "")"
  910. // BFLog(message: "开始上传视频图片==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey)")
  911. // PQAliOssUtil.shared
  912. // .startClient(
  913. // accessKeyId: accessKeyId,
  914. // secretKeyId: secretKeyId,
  915. // securityToken: securityToken,
  916. // endpoint: endpoint
  917. // )
  918. // .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  919. // BFLog(message: "图片上传完成==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  920. // if code == 1 && ossObjectKey == objectKey && objectKey.count > 0 {
  921. // BFLog(message: "开始发布==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)")
  922. 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
  923. self?.videoData = newVideoData
  924. self?.videoData?.title = self?.titleLabel.text
  925. if self?.videoData?.reCreateVideoData == nil {
  926. let reCreateVideo = PQReCreateModel()
  927. reCreateVideo.reProduceVideoFlag = 1
  928. self?.videoData?.reCreateVideoData = reCreateVideo
  929. }
  930. postNotification(name: cPublishStuckPointSuccessKey, userInfo: ["newVideoData": self?.videoData!])
  931. BFLog(message: "发布成功==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? "")")
  932. // cShowHUB(superView: nil, msg: "视频发布成功")
  933. // 发布成功后续操作
  934. self?.publicEnd()
  935. PQEventTrackViewModel.publishReportUpload(projectId: videoData.projectId, businessType: .bt_publish_success, ossInfo: videoData.stsToken ?? [:], params: ["title": videoData.title ?? "", "videoPath": videoData.uplpadBucketKey ?? "", "descr": videoData.summary ?? ""])
  936. }
  937. // } else {
  938. // // 图片上传失败
  939. // BFLog(message: "图片上传失败重新发布视频==\(videoData.title ?? ""),\(videoData.uplpadBucketKey ?? "")")
  940. // self?.publicVideo(videoData: videoData)
  941. // }
  942. // })
  943. // }
  944. }
  945. }
  946. /// 发布结束操作
  947. /// - Parameter isError: <#isError description#>
  948. /// - Returns: <#description#>
  949. func publicEnd(isError: Bool = false) {
  950. UIApplication.shared.keyWindow?.viewWithTag(100_100)?.removeFromSuperview()
  951. isPublicSuccess = true
  952. progressView.removeFromSuperview()
  953. progressTipsLab.removeFromSuperview()
  954. oprationBgView.removeFromSuperview()
  955. playBtn.isHidden = true
  956. avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: (exportLocalURL?.absoluteString ?? "").replacingOccurrences(of: "file:///", with: ""))))
  957. avPlayer.play()
  958. if isError {
  959. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  960. } else {
  961. bottomOprationBgView.isHidden = false
  962. /// fp2-1-1 - 请求权限
  963. authorizationStatus()
  964. }
  965. }
  966. /// 生成创作工具埋点数据
  967. /// - Returns: <#description#>
  968. func getExportEventTrackData() -> PQVideoMakeEventTrackModel? {
  969. let eventTrackData = PQVideoMakeEventTrackModel(projectModel: editProjectModel, reCreateData: reCreateData)
  970. eventTrackData.entrance = .entranceStuckPointPublic
  971. eventTrackData.editTimeCost = 0
  972. eventTrackData.composeTimeCost = (exportEndDate - startExportDate) * 1000
  973. eventTrackData.musicName = audioMixModel?.musicName ?? ""
  974. eventTrackData.syncedUpMusicName = audioMixModel?.musicName ?? ""
  975. eventTrackData.musicId = audioMixModel?.musicId ?? ""
  976. eventTrackData.syncedUpMusicId = audioMixModel?.musicId ?? ""
  977. eventTrackData.musicUrl = audioMixModel?.selectVoiceType == 1 ? (audioMixModel?.musicPath ?? "") : (audioMixModel?.accompanimentPath ?? "")
  978. eventTrackData.musicType = audioMixModel != nil ? (audioMixModel?.selectVoiceType == 1 ? "original" : "accompaniment") : ""
  979. eventTrackData.isMusicClip = (audioMixModel?.startTime ?? 0) > 0
  980. if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.origin.rawValue {
  981. eventTrackData.canvasRatio = "original"
  982. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.nineToSixteen.rawValue {
  983. eventTrackData.canvasRatio = "9:16"
  984. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.oneToOne.rawValue {
  985. eventTrackData.canvasRatio = "1:1"
  986. } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.sixteenToNine.rawValue {
  987. eventTrackData.canvasRatio = "16:9"
  988. }
  989. eventTrackData.syncedUpVideoNumber = selectedDataCount - selectedImageDataCount
  990. eventTrackData.syncedUpImageNumber = selectedImageDataCount
  991. eventTrackData.syncedUpOriginalMaterialDuration = selectedTotalDuration * 1000
  992. eventTrackData.syncedUpRhythmNumber = audioMixModel?.speed ?? 2
  993. eventTrackData.syncedUpVideoDuration = ((audioMixModel?.endTime ?? 0) - (audioMixModel?.startTime ?? 0)) * 1000
  994. return eventTrackData
  995. }
  996. /// 播放视频
  997. /// - Returns: description
  998. @objc func playVideo() {
  999. playBtn.isHidden = !playBtn.isHidden
  1000. changPlayerIsPause(isPause: !playBtn.isHidden)
  1001. }
  1002. /// 按钮点击事件
  1003. /// - Parameter sender: <#sender description#>
  1004. /// - Returns: <#description#>
  1005. @objc func btnClick(sender: UIButton) {
  1006. switch sender.tag {
  1007. case 1:
  1008. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1009. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1010. return
  1011. }
  1012. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1013. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1014. return
  1015. }
  1016. cShowHUB(superView: nil, msg: nil)
  1017. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1018. PQBaseViewModel.wxFriendShareInfo(videoId: (videoData?.uniqueId)!) { [weak self] imagePath, title, shareWeappRawId, msg in
  1019. if msg != nil {
  1020. cShowHUB(superView: nil, msg: "网络不佳哦")
  1021. return
  1022. }
  1023. self?.isShared = true
  1024. 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
  1025. }
  1026. cHiddenHUB(superView: nil)
  1027. }
  1028. // 点击上报:分享微信
  1029. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechat, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享微信)")
  1030. case 2:
  1031. if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) {
  1032. cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
  1033. return
  1034. }
  1035. if !PQSingletoWXApiUtil.shared.isInstallWX() {
  1036. cShowHUB(superView: nil, msg: "您还未安装微信客户端!")
  1037. return
  1038. }
  1039. let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId")
  1040. PQBaseViewModel.h5ShareLinkInfo(videoId: videoData?.uniqueId ?? "", pageSource: videoData?.pageSource ?? .sp_category) { [weak self] path, _ in
  1041. cHiddenHUB(superView: nil)
  1042. if path != nil {
  1043. PQBaseViewModel.wxFriendShareInfo(videoId: (self?.videoData?.uniqueId)!) { [weak self] imagePath, title, shareWeappRawId, msg in
  1044. if msg != nil {
  1045. cShowHUB(superView: nil, msg: "网络不佳哦")
  1046. return
  1047. }
  1048. self?.isShared = true
  1049. 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
  1050. }
  1051. }
  1052. } else {
  1053. cShowHUB(superView: nil, msg: "网络不佳哦")
  1054. }
  1055. }
  1056. // 点击上报:分享朋友圈
  1057. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechatMoment, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享朋友圈)")
  1058. case 3:
  1059. // 点击上报:完成
  1060. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_finished, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:完成)")
  1061. navigationController?.viewControllers = [(navigationController?.viewControllers.first)!]
  1062. // 发送通知
  1063. postNotification(name: cFinishedPublishedNotiKey)
  1064. default:
  1065. break
  1066. }
  1067. }
  1068. /// 添加提示视图
  1069. /// - Parameters:
  1070. /// - isNetCollected: <#isNetCollected description#>
  1071. /// - msg: <#msg description#>
  1072. func showUploadRemindView(isNetCollected _: Bool = true, msg _: String? = nil) {
  1073. view.endEditing(true)
  1074. // PQUploadRemindView.showUploadRemindView(title: isNetCollected ? "上传中断" : "上传失败", summary: (isNetCollected ? "似乎已断开与互联网的连接" : (msg != nil ? msg : "视频文件已丢失"))!, confirmTitle: isNetCollected ? "重新连接网络" : "重新上传") { [weak self] _, _ in
  1075. // if isNetCollected {
  1076. // openAppSetting()
  1077. // } else {
  1078. // self?.navigationController?.popToViewController((self?.navigationController?.viewControllers[1])!, animated: true)
  1079. // }
  1080. // }
  1081. }
  1082. @objc func enterBackground() {
  1083. BFLog(message: "进入到后台")
  1084. // 取消导出
  1085. if exporter != nil {
  1086. exporter.cancel()
  1087. }
  1088. playBtn.isHidden = false
  1089. avPlayer.pause()
  1090. }
  1091. @objc func willEnterForeground() {
  1092. BFLog(message: "进入到前台")
  1093. if !isExportSuccess {
  1094. beginExport()
  1095. }
  1096. playBtn.isHidden = true
  1097. avPlayer.play()
  1098. }
  1099. @objc func didBecomeActiveNotification() {
  1100. if isShared {
  1101. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [weak self] in
  1102. self?.isShared = false
  1103. cShowHUB(superView: nil, msg: "分享成功")
  1104. self?.playBtn.isHidden = true
  1105. self?.avPlayer.play()
  1106. }
  1107. }
  1108. }
  1109. /// 更新进度
  1110. /// - Returns: <#description#>
  1111. func updatePublicCurrentProgress(useProgress: Float) {
  1112. exportProgrss = Int(useProgress * 100)
  1113. progressView.setProgress(useProgress, animated: true)
  1114. let attributedText = NSMutableAttributedString(string: "\(exportProgrss)%\n视频正在处理中,请勿离开")
  1115. attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: "\(exportProgrss)%".count))
  1116. progressTipsLab.attributedText = attributedText
  1117. }
  1118. func changPlayerIsPause(isPause:Bool) {
  1119. if(isPause){
  1120. playBtn.isHidden = false
  1121. avPlayer.pause()
  1122. playerHeaderCoverImageView.isHidden = false
  1123. }else{
  1124. playBtn.isHidden = true
  1125. avPlayer.play()
  1126. playerHeaderCoverImageView.isHidden = true
  1127. }
  1128. }
  1129. @objc func titleLabelClick() {
  1130. BFLog(message: "点击输入框")
  1131. changPlayerIsPause(isPause: true)
  1132. pinView.isHidden = true
  1133. publicTitleView.show()
  1134. if(publicTitleView.inputTV.text.count > 0){
  1135. publicTitleView.inputTV.text = titleLabel.text
  1136. }
  1137. 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: "")
  1138. }
  1139. @objc func settingCoverImage() {
  1140. if(exportLocalURL == nil){
  1141. BFLog(message: "导出的视频地址错误???。。。")
  1142. return
  1143. }
  1144. changPlayerIsPause(isPause: true)
  1145. let asset = AVURLAsset(url: exportLocalURL!, options: nil)
  1146. publicEditCoverView.show(videoURL: exportLocalURL!, duration: CMTimeGetSeconds(asset.duration))
  1147. //点击了确认 btn
  1148. publicEditCoverView.selectImageCallBack = { [weak self] imageData in
  1149. self?.changPlayerIsPause(isPause: false)
  1150. if(imageData != nil){
  1151. self?.coverImageView.image = imageData
  1152. self?.playerHeaderCoverImageView.image = imageData
  1153. self?.uploadData?.image = imageData
  1154. self?.updateCoverImagegOrTitle()
  1155. }
  1156. }
  1157. //点击了从相册选择
  1158. publicEditCoverView.selectPhotoBtnCallBack = { [weak self] in
  1159. let imageSelected = PQImageSelectedController()
  1160. imageSelected.isAssetImage = true
  1161. imageSelected.videoWidth = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)
  1162. imageSelected.videoHeight = CGFloat(self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)
  1163. // imageSelected.uploadData = uploadData
  1164. // imageSelected.updataVideoData = updataVideoData
  1165. self?.navigationController?.pushViewController(imageSelected, animated: true)
  1166. }
  1167. 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: "")
  1168. }
  1169. //更新标题或封面
  1170. func updateCoverImagegOrTitle() {
  1171. PQLoadingHUB.shared.showHUB(isMode: true)
  1172. PQBaseViewModel.ossTempToken { [weak self] response, msg in
  1173. let image: UIImage = (self?.uploadData?.image)!
  1174. let data = image.jpegData(compressionQuality: 1)
  1175. let accessKeyId: String = "\(response?["accessKeyId"] ?? "")"
  1176. let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")"
  1177. let securityToken: String = "\(response?["securityToken"] ?? "")"
  1178. let endpoint: String = "\(response?["endPoint"] ?? "")"
  1179. let bucketName: String = "\(response?["bucketName"] ?? "")"
  1180. let objectKey: String = "\(response?["objectKey"] ?? "")"
  1181. PQAliOssUtil.shared
  1182. .startClient(
  1183. accessKeyId: accessKeyId,
  1184. secretKeyId: secretKeyId,
  1185. securityToken: securityToken,
  1186. endpoint: endpoint
  1187. )
  1188. .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in
  1189. if code == 1 && ossObjectKey == objectKey && ossObjectKey.count > 0 {
  1190. //add by ak 这里会在服务器生成分享使用的图片到1-2S 时间
  1191. PQUploadViewModel.updateVideo(title: self?.videoData?.title ?? "", videoId: self?.videoData?.uniqueId ?? "", coverImgPath: objectKey, descr: "") { newVideoData, msg in
  1192. if newVideoData == nil {
  1193. cShowHUB(superView: self?.view, msg: msg)
  1194. //可能有敏感词 要刷一组新标题并自动更新
  1195. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32( self?.publicTitleView.titles.count ?? 0)))
  1196. self?.setTitleText(text: self?.publicTitleView.titles[Int(numberRandom)] ?? "")
  1197. self?.updateCoverImagegOrTitle()
  1198. sleep(UInt32(1.5))
  1199. PQLoadingHUB.shared.dismissHUB()
  1200. return
  1201. }else{
  1202. PQLoadingHUB.shared.dismissHUB()
  1203. }
  1204. }
  1205. } else {
  1206. PQLoadingHUB.shared.dismissHUB()
  1207. }
  1208. })
  1209. }}
  1210. func setTitleText(text:String ,textColor:UIColor = UIColor.hexColor(hexadecimal: "#ABABAB")) {
  1211. selectTitle = text
  1212. //更新 UI
  1213. titleLabel.text = text
  1214. titleLabel.textColor = textColor
  1215. publicTitleView.inputTV.placeHolder = text
  1216. //更新数据
  1217. videoData?.title = text
  1218. }
  1219. //取推荐的10个标题
  1220. func getTitles(){
  1221. PQBaseViewModel.getBaseConfig(completeHander: {[weak self] titles in
  1222. if((titles?.count ?? 0) > 0){
  1223. self?.publicTitleView.titles = titles!
  1224. let numberRandom: UInt32 = UInt32(arc4random_uniform(UInt32(titles!.count)))
  1225. BFLog(message: "接收到的 titles\(String(describing: titles))")
  1226. self?.setTitleText(text: titles?[Int(numberRandom)] ?? "")
  1227. }
  1228. })
  1229. }
  1230. @objc func imageSelectedImage(notify: Notification) {
  1231. let imageData: UIImage? = notify.userInfo?["image"] as? UIImage
  1232. if imageData != nil {
  1233. changPlayerIsPause(isPause: false)
  1234. BFLog(message: "从系统相册选择了一个照片")
  1235. publicEditCoverView.isHidden = true
  1236. coverImageView.image = imageData
  1237. playerHeaderCoverImageView.image = imageData
  1238. uploadData?.image = imageData
  1239. updateCoverImagegOrTitle()
  1240. }
  1241. }
  1242. }