INVideoExportController.swift 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. //
  2. // INVideoExportController.swift
  3. // Introduce
  4. //
  5. // Created by 胡志强 on 2021/11/29.
  6. //
  7. import BFAnalyzeKit
  8. import BFCommonKit
  9. import BFRecordScreenKit
  10. import BFUIKit
  11. import Foundation
  12. import Photos
  13. import SwiftUI
  14. import UIKit
  15. class INVideoExportController: BFBaseViewController {
  16. var avplayerTimeObserver: NSKeyValueObservation?
  17. let backV : UIView = {
  18. let v = UIView()
  19. v.backgroundColor = UIColor.hexColor(hexadecimal: "#1A1A1A")
  20. return v
  21. }()
  22. var hasExportAll = false
  23. var hasExportOnly = false
  24. var hasSaveAll = false
  25. var hasSaveOnly = false
  26. var saveAllUlr = URL(fileURLWithPath: "aaa")
  27. var saveOnlyUlr = URL(fileURLWithPath: "aaa")
  28. var playViewFrame: CGRect = CGRect.zero
  29. var isExporting = false {
  30. didSet {
  31. if isExporting {
  32. avplayer.pause()
  33. }
  34. }
  35. }
  36. var enterBackgroundCancleExport = false
  37. // 预览播放进度
  38. var sliderView: BFVideoPlayerSliderView?
  39. // 合成进度指示条
  40. lazy var progressView: UIView = {
  41. let v = UIView()
  42. v.backgroundColor = ThemeStyleColor
  43. return v
  44. }()
  45. // 合成进度百分比lable
  46. lazy var progressL: UILabel = {
  47. let la = UILabel()
  48. la.textColor = .white
  49. la.textAlignment = .center
  50. la.text = "0%"
  51. la.font = UIFont.systemFont(ofSize: 28)
  52. la.shadowColor = .black
  53. la.shadowOffset = CGSize(width: 1, height: 1)
  54. return la
  55. }()
  56. lazy var saveAllBtn: UIButton = {
  57. let btn = UIButton()
  58. btn.setImage(UIImage(named: "export_saveall_n"), for: .normal)
  59. btn.setImage(UIImage(named: "export_saveall_h"), for: .selected)
  60. btn.addTarget(self, action: #selector(saveAllAction(btn:)), for: .touchUpInside)
  61. return btn
  62. }()
  63. lazy var saveOnlyBtn: UIButton = {
  64. let btn = UIButton()
  65. btn.setImage(UIImage(named: "export_saveonly_n"), for: .normal)
  66. btn.setImage(UIImage(named: "export_saveonly_h"), for: .selected)
  67. btn.addTarget(self, action: #selector(saveOnlyAction(btn:)), for: .touchUpInside)
  68. return btn
  69. }()
  70. lazy var completeBtn: UIButton = {
  71. let btn = UIButton()
  72. btn.setTitle("回到首页", for: .normal)
  73. btn.setTitleColor(UIColor.hexColor(hexadecimal: "#B1B1B1"), for: .normal)
  74. btn.setTitleColor(.gray, for: .disabled)
  75. btn.addTarget(self, action: #selector(completeAction), for: .touchUpInside)
  76. return btn
  77. }()
  78. lazy var saveToPhotoBtn: UIButton = {
  79. let btn = UIButton()
  80. btn.setTitle(" 保存到相册", for: .normal)
  81. btn.setTitleColor(.white, for: .normal)
  82. btn.setTitleColor(.lightGray, for: .disabled)
  83. btn.setImage(UIImage(named: "export_btn"), for: .normal)
  84. btn.adjustsImageWhenDisabled = true
  85. btn.backgroundColor = ThemeStyleColor
  86. btn.addCorner(roundingCorners: .allCorners, corner: 8)
  87. btn.addTarget(self, action: #selector(saveToPhotoNow), for: .touchUpInside)
  88. return btn
  89. }()
  90. lazy var bottomView: UIView = {
  91. let vv = UIView()
  92. vv.backgroundColor = .black
  93. // vv.isHidden = true
  94. return vv
  95. }()
  96. lazy var errorView : UIView = {
  97. let backV = UIView()
  98. backV.backgroundColor = .black
  99. backV.isHidden = true
  100. let titleL = UILabel()
  101. titleL.text = "合成失败"
  102. titleL.font = UIFont.systemFont(ofSize: 36)
  103. titleL.textColor = .white
  104. titleL.textAlignment = .center
  105. backV.addSubview(titleL)
  106. let subTitleL = UILabel()
  107. subTitleL.text = "请重新尝试"
  108. subTitleL.tag = 33000
  109. subTitleL.font = UIFont.systemFont(ofSize: 24)
  110. subTitleL.textColor = UIColor.hexColor(hexadecimal: "#A6A6A6")
  111. subTitleL.textAlignment = .center
  112. subTitleL.contentCompressionResistancePriority(for: NSLayoutConstraint.Axis.horizontal)
  113. subTitleL.numberOfLines = 0
  114. backV.addSubview(subTitleL)
  115. let retryBtn = UIButton()
  116. retryBtn.backgroundColor = ThemeStyleColor
  117. retryBtn.setTitle("重试", for: .normal)
  118. retryBtn.tag = 33001
  119. retryBtn.setTitleColor(.white, for: .normal)
  120. retryBtn.addCorner(corner:5)
  121. retryBtn.addTarget(self, action: #selector(retryAction), for: .touchUpInside)
  122. backV.addSubview(retryBtn)
  123. retryBtn.snp.makeConstraints { make in
  124. make.width.equalTo(245)
  125. make.height.equalTo(50)
  126. make.centerX.equalToSuperview()
  127. make.centerY.equalToSuperview().offset(10)
  128. }
  129. subTitleL.snp.makeConstraints { make in
  130. make.left.right.equalToSuperview()
  131. make.bottom.equalTo(retryBtn.snp.top).offset(-30)
  132. }
  133. titleL.snp.makeConstraints { make in
  134. make.left.right.equalToSuperview()
  135. make.height.equalTo(70)
  136. make.bottom.equalTo(subTitleL.snp.top)
  137. }
  138. return backV
  139. }()
  140. var playerLayer: AVPlayerLayer!
  141. lazy var avplayer: AVPlayer = {
  142. let avplayer = AVPlayer()
  143. avplayerTimeObserver = avplayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 100), queue: DispatchQueue.global()) { [weak self, weak avplayer] _ in
  144. // 进度监控
  145. if let item = avplayer?.currentItem, !(self?.sliderView?.isDragingProgressSlder ?? false) {
  146. DispatchQueue.main.async { [weak self, weak avplayer] in
  147. if avplayer?.currentItem?.status != .readyToPlay {
  148. self?.sliderView?.progress = 0
  149. }else{
  150. self?.sliderView?.progress = CMTimeGetSeconds(item.currentTime()) / CMTimeGetSeconds(item.duration)
  151. }
  152. }
  153. }
  154. } as? NSKeyValueObservation
  155. NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avplayer.currentItem, queue: .main) { [weak avplayer, weak self] _ in
  156. avplayer?.seek(to: CMTime.zero)
  157. self?.sliderView?.playEnd()
  158. // self?.changeToOriginalFrame()
  159. }
  160. return avplayer
  161. }()
  162. lazy var export: BFRecordExport = {
  163. let export = BFRecordExport()
  164. export.progress = { [weak self] progress in
  165. DispatchQueue.main.async { [weak self] in
  166. if let sself = self {
  167. // let width = export.data?.first?.width ?? 0
  168. sself.progressL.text = String(format: "%d%%", Int(progress * 100))
  169. sself.progressView.snp.updateConstraints { make in
  170. make.width.equalTo(cScreenWidth * CGFloat(progress))
  171. }
  172. }
  173. }
  174. }
  175. export.exportCompletion = { [weak self] error, url in
  176. DispatchQueue.main.async { [weak self] in
  177. guard let sself = self else {
  178. return
  179. }
  180. UIApplication.shared.isIdleTimerDisabled = false
  181. sself.isExporting = false
  182. // sself.bottomView.isHidden = false
  183. sself.saveAllBtn.setImage(UIImage(named: "export_saveall_n"), for: .normal)
  184. sself.saveOnlyBtn.setImage(UIImage(named: "export_saveonly_n"), for: .normal)
  185. sself.saveAllBtn.isEnabled = true
  186. sself.saveOnlyBtn.isEnabled = true
  187. sself.saveToPhotoBtn.isEnabled = true
  188. sself.completeBtn.isEnabled = true
  189. sself.sliderView?.isHidden = false
  190. sself.progressView.isHidden = true
  191. sself.progressL.isHidden = true
  192. if let fileUrl = url {
  193. let item = AVPlayerItem(url: fileUrl)
  194. sself.avplayer.replaceCurrentItem(with: item)
  195. sself.avplayer.play()
  196. sself.playerLayer.borderColor = ThemeStyleColor.cgColor
  197. if sself.saveAllBtn.isSelected {
  198. sself.saveAllUlr = fileUrl
  199. sself.hasExportAll = true
  200. }
  201. if sself.saveOnlyBtn.isSelected {
  202. sself.saveOnlyUlr = fileUrl
  203. sself.hasExportOnly = true
  204. }
  205. // 添加播放进度视图
  206. self?.addVideoSliderView()
  207. }else {
  208. if let l = sself.errorView.viewWithTag(33000) as? UILabel, let err = error as NSError?,let b = sself.errorView.viewWithTag(33001) as? UIButton {
  209. switch err.code {
  210. case ExportError.FileNotExist.rawValue :
  211. l.text = "原视频/图片文件被删除"
  212. b.setTitle("重新制作", for: .normal)
  213. case ExportError.DiskNoSpace.rawValue :
  214. l.text = "存储空间不足:" + (err.userInfo["msg"] as! String)
  215. b.setTitle("重试", for: .normal)
  216. case ExportError.VoiceLost.rawValue :
  217. l.text = "手机录音文件丢失"
  218. b.setTitle("重试", for: .normal)
  219. default:
  220. l.text = "请重新尝试"
  221. b.setTitle("重试", for: .normal)
  222. }
  223. l.sizeToFit()
  224. }
  225. sself.errorView.isHidden = false
  226. }
  227. }
  228. // 合成成功上报
  229. BFEventTrackAdaptor.baseReportUpload(businessType: nil, objectType: .ot_composeSuccess, pageSource: .sp_composePage, commonParams: commonParams())
  230. }
  231. return export
  232. }()
  233. deinit {
  234. avplayerTimeObserver?.invalidate()
  235. NotificationCenter.default.removeObserver(self)
  236. }
  237. override func viewWillAppear(_ animated: Bool) {
  238. super.viewWillAppear(animated)
  239. PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  240. PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  241. //进入活跃状态
  242. PQNotification.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
  243. //进入非活跃状态
  244. PQNotification.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
  245. showNavigation()
  246. // 曝光上报
  247. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_pageView, objectType: nil, pageSource: .sp_composePage, commonParams: commonParams())
  248. }
  249. @objc func didBecomeActive(){
  250. BFLog(message: "进入活跃状态")
  251. if let sbtn = sliderView?.viewWithTag(1) as? UIButton ,!isExporting {
  252. sbtn.isSelected = false
  253. avplayer.play()
  254. }
  255. }
  256. @objc func willResignActive(){
  257. BFLog(message: "进入非活跃状态")
  258. if let sbtn = sliderView?.viewWithTag(1) as? UIButton , !isExporting{
  259. sbtn.isSelected = true
  260. avplayer.pause()
  261. }
  262. }
  263. @objc func enterBackground() {
  264. BFLog(message: "进入到后台")
  265. if isExporting {
  266. enterBackgroundCancleExport = true
  267. self.export.cancelExport()
  268. }else{
  269. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  270. sbtn.isSelected = true
  271. avplayer.pause()
  272. }
  273. }
  274. }
  275. @objc func willEnterForeground() {
  276. BFLog(message: "进入到前台")
  277. if enterBackgroundCancleExport {
  278. export.startExprot(synthesisAll: saveAllBtn.isSelected)
  279. enterBackgroundCancleExport = false
  280. }else {
  281. //重启播放器
  282. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  283. sbtn.isSelected = false
  284. avplayer.play()
  285. }
  286. }
  287. }
  288. override func viewWillDisappear(_ animated: Bool) {
  289. super.viewWillDisappear(animated)
  290. PQNotification.removeObserver(self)
  291. UIApplication.shared.isIdleTimerDisabled = false
  292. }
  293. override func backBtnClick() {
  294. if isExporting {
  295. whetherCancelExport {
  296. self.export.cancelExport()
  297. super.backBtnClick()
  298. }
  299. return
  300. }
  301. if !(hasExportAll || hasExportOnly) {}
  302. export.cancelExport()
  303. super.backBtnClick()
  304. }
  305. override func viewDidLoad() {
  306. super.viewDidLoad()
  307. view.backgroundColor = .black
  308. navHeadImageView?.backgroundColor = .black
  309. leftButton(image: nil, imageName: nil, tintColor: UIColor.white)
  310. // backV.frame = CGRect(x: 0, y: navHeadImageView?.bottomY ?? 0, width: cScreenWidth, height: cScreenWidth)
  311. // backV.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(play)))
  312. addSubviews()
  313. // export.startExprot()
  314. // 默认保留录音合成
  315. saveAllBtn.isSelected = true
  316. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
  317. self?.exportNow()
  318. }
  319. }
  320. func addSubviews() {
  321. view.addSubview(bottomView)
  322. view.addSubview(backV)
  323. view.addSubview(errorView)
  324. playerLayer = AVPlayerLayer(player: avplayer)
  325. playerLayer.borderWidth = 1
  326. playerLayer.borderColor = UIColor.hexColor(hexadecimal: "#1A1A1A").cgColor
  327. backV.layer.addSublayer(playerLayer)
  328. backV.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(playAction)))
  329. backV.addSubview(progressView)
  330. backV.addSubview(progressL)
  331. bottomView.addSubview(saveAllBtn)
  332. bottomView.addSubview(saveOnlyBtn)
  333. bottomView.addSubview(saveToPhotoBtn)
  334. bottomView.addSubview(completeBtn)
  335. errorView.snp.makeConstraints { make in
  336. make.edges.equalToSuperview()
  337. }
  338. progressView.snp.makeConstraints { make in
  339. make.left.top.height.equalToSuperview()
  340. make.width.equalTo(0)
  341. }
  342. progressL.snp.makeConstraints { make in
  343. make.center.width.equalToSuperview()
  344. make.height.equalTo(28)
  345. }
  346. bottomView.snp.makeConstraints { make in
  347. make.left.right.equalToSuperview()
  348. if #available(iOS 11.0, *) {
  349. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
  350. } else {
  351. make.bottom.equalToSuperview()
  352. }
  353. }
  354. completeBtn.snp.makeConstraints { make in
  355. make.left.equalTo(16)
  356. make.right.equalTo(-16)
  357. make.bottom.equalTo(-1 * cSafeAreaHeight - 3)
  358. make.height.equalTo(20)
  359. }
  360. saveToPhotoBtn.snp.makeConstraints { make in
  361. make.left.right.equalTo(completeBtn)
  362. make.height.equalTo(50)
  363. make.bottom.equalTo(completeBtn.snp.top).offset(-10)
  364. }
  365. saveAllBtn.snp.makeConstraints { make in
  366. make.left.equalTo(completeBtn)
  367. make.bottom.equalTo(saveToPhotoBtn.snp.top).offset(-21)
  368. make.height.equalTo(70)
  369. make.top.equalTo(28)
  370. }
  371. saveOnlyBtn.snp.makeConstraints { make in
  372. make.left.equalTo(saveAllBtn.snp.right).offset(16)
  373. make.top.height.width.equalTo(saveAllBtn)
  374. make.right.equalTo(completeBtn)
  375. }
  376. DispatchQueue.main.asyncAfter(deadline: .now() + 0) { [weak self] in
  377. guard let sself = self else {
  378. return
  379. }
  380. sself.backV.frame = CGRect(x: 0, y: sself.navHeadImageView?.frame.maxY ?? 0, width: sself.view.width, height: (sself.bottomView.frame.minY ) - (sself.navHeadImageView?.frame.maxY ?? 0))
  381. let width = sself.backV.height * (cScreenWidth / cScreenHeigth)
  382. sself.playerLayer.frame = CGRect(x: (sself.backV.width - width)/2, y: 0, width: width, height: sself.backV.height)
  383. }
  384. }
  385. /// 添加播放进度视图
  386. func addVideoSliderView() {
  387. if sliderView == nil {
  388. sliderView = BFVideoPlayerSliderView(frame: CGRect(x: (backV.frame.width - playerLayer.frame.width) / 2, y: backV.frame.height - 60, width: playerLayer.frame.width, height: 50),isShowFullScreen: false)
  389. sliderView?.valueChangeBloc = { [weak self] sender in
  390. let cmtime = CMTime(value: CMTimeValue(Float64(sender.value) * Float64(self?.avplayer.currentItem?.asset.duration.seconds ?? 0) * 1000.0), timescale: CMTimeScale(1000.0))
  391. BFLog(message: "cmtime == \(cmtime),\(cmtime.seconds)")
  392. self?.avplayer.seek(to: cmtime, toleranceBefore: CMTime(seconds: 1, preferredTimescale: 1000), toleranceAfter: CMTime(seconds: 1, preferredTimescale: 1000))
  393. self?.avplayer.play()
  394. }
  395. sliderView?.slideBeginDrag = { [weak self] in
  396. self?.avplayer.pause()
  397. }
  398. sliderView?.btnClickBloc = { [weak self] sender in
  399. // 按钮点击
  400. if sender.tag == 1 {
  401. self?.play(sender: sender)
  402. } else if sender.tag == 2 {
  403. if sender.isSelected {
  404. self?.changeToFullScreen()
  405. } else {
  406. self?.changeToOriginalFrame()
  407. }
  408. }
  409. }
  410. }
  411. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  412. if sliderView?.superview == nil {
  413. backV.addSubview(sliderView!)
  414. }
  415. }
  416. // MARK: - 按钮事件
  417. @objc func retryAction(){
  418. errorView.isHidden = true
  419. exportNow()
  420. }
  421. func exportNow() {
  422. if isExporting {
  423. cShowHUB(superView: nil, msg: "正在合成中。。。")
  424. return
  425. }
  426. isExporting = true
  427. playerLayer.borderColor = UIColor.hexColor(hexadecimal: "#1A1A1A").cgColor
  428. avplayer.replaceCurrentItem(with: nil)
  429. // bottomView.isHidden = true
  430. if saveAllBtn.isSelected {
  431. // saveAllBtn.setImage(UIImage(named: "export_saveall_h"), for: .disabled)
  432. saveAllBtn.setImage(UIImage(named: "export_saveall_h"), for: .normal)
  433. saveOnlyBtn.setImage(UIImage(named: "export_saveonly_n"), for: .normal)
  434. }else{
  435. // saveAllBtn.setImage(UIImage(named: "export_saveall_n"), for: .disabled)
  436. saveAllBtn.setImage(UIImage(named: "export_saveall_n"), for: .normal)
  437. saveOnlyBtn.setImage(UIImage(named: "export_saveonly_h"), for: .normal)
  438. }
  439. saveAllBtn.isEnabled = false
  440. saveOnlyBtn.isEnabled = false
  441. saveToPhotoBtn.isEnabled = false
  442. completeBtn.isEnabled = false
  443. progressView.isHidden = false
  444. progressL.isHidden = false
  445. sliderView?.isHidden = true
  446. export.startExprot(synthesisAll: saveAllBtn.isSelected)
  447. UIApplication.shared.isIdleTimerDisabled = true
  448. // 开始合成上报
  449. BFEventTrackAdaptor.baseReportUpload(businessType: nil, objectType: .ot_startCompose, pageSource: .sp_composePage, commonParams: commonParams())
  450. }
  451. @objc func saveToPhotoNow() {
  452. if (saveAllBtn.isSelected && hasSaveAll) || saveOnlyBtn.isSelected && hasSaveOnly {
  453. cShowHUB(superView: nil, msg: "已保存过了")
  454. return
  455. }
  456. if let url = (avplayer.currentItem?.asset as? AVURLAsset)?.url {
  457. PHPhotoLibrary.shared().performChanges {
  458. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
  459. } completionHandler: { [weak self] isFinished, _ in
  460. guard let sself = self else {
  461. return
  462. }
  463. if isFinished {
  464. DispatchQueue.main.async {
  465. cShowHUB(superView: nil, msg: "保存成功")
  466. }
  467. if sself.saveAllBtn.isSelected {
  468. sself.hasSaveAll = true
  469. } else if sself.saveOnlyBtn.isSelected {
  470. sself.hasSaveOnly = true
  471. }
  472. }
  473. }
  474. }
  475. // 点击保存至相册上报
  476. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_saveToAblum, pageSource: .sp_composePage, extParams: saveAllBtn.isSelected ? ["saveAll": true] : ["saveRecord": true], commonParams: commonParams())
  477. }
  478. @objc func saveAllAction(btn: UIButton) {
  479. if btn.isSelected {
  480. return
  481. }
  482. btn.isSelected = true
  483. saveOnlyBtn.isSelected = false
  484. avplayer.pause()
  485. if !hasExportAll {
  486. exportNow()
  487. } else {
  488. avplayer.pause()
  489. let item = AVPlayerItem(url: saveAllUlr)
  490. avplayer.replaceCurrentItem(with: item)
  491. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  492. sbtn.isSelected = false
  493. avplayer.play()
  494. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  495. }
  496. }
  497. }
  498. @objc func saveOnlyAction(btn: UIButton) {
  499. if btn.isSelected {
  500. return
  501. }
  502. btn.isSelected = true
  503. saveAllBtn.isSelected = false
  504. avplayer.pause()
  505. if !hasExportOnly {
  506. exportNow()
  507. } else {
  508. avplayer.pause()
  509. let item = AVPlayerItem(url: saveOnlyUlr)
  510. avplayer.replaceCurrentItem(with: item)
  511. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  512. sbtn.isSelected = false
  513. avplayer.play()
  514. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  515. }
  516. }
  517. }
  518. @objc func playAction(){
  519. if !isExporting {
  520. if let sbtn = sliderView?.viewWithTag(1) as? UIButton{
  521. sbtn.isSelected = !sbtn.isSelected
  522. if sbtn.isSelected {
  523. avplayer.pause()
  524. }else{
  525. avplayer.play()
  526. }
  527. }
  528. }
  529. }
  530. @objc func completeAction() {
  531. // MARK: 删除所有录制资源. 现在放在了选择相册展示时清理cache
  532. if (!hasSaveOnly && !saveOnlyUlr.absoluteString.contains("aaa"))
  533. || (!hasSaveAll && !saveAllUlr.absoluteString.contains("aaa")) {
  534. let alertController = UIAlertController(title: "你还未保存视频哦,是否需要保存视频再离开?",
  535. message: "", preferredStyle: .alert)
  536. let cancelAction = UIAlertAction(title: "不保存", style: .default){[weak self] action in
  537. guard let sself = self else {
  538. return
  539. }
  540. sself.export.cancelExport()
  541. sself.navigationController?.popToRootViewController(animated: true)
  542. }
  543. let okAction = UIAlertAction(title: "保存视频", style: .default) {[weak self] action in
  544. guard let sself = self else {
  545. return
  546. }
  547. if sself.hasExportOnly {
  548. PHPhotoLibrary.shared().performChanges {
  549. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveOnlyUlr)
  550. } completionHandler: { _, _ in
  551. }
  552. }
  553. if sself.hasExportAll {
  554. PHPhotoLibrary.shared().performChanges {
  555. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveAllUlr)
  556. } completionHandler: { _, _ in
  557. }
  558. }
  559. sself.export.cancelExport()
  560. sself.navigationController?.popToRootViewController(animated: true)
  561. }
  562. alertController.addAction(cancelAction)
  563. alertController.addAction(okAction)
  564. self.present(alertController, animated: true, completion: nil)
  565. //
  566. // let remindData = BFBaseModel()
  567. // remindData.summary = "合成的视频尚未保存到相册"
  568. // let alertV = BFRemindView(frame: view.bounds)
  569. // alertV.isBanned = true
  570. // alertV.confirmBtn.setTitle("不保存", for: .normal)
  571. // alertV.cancelBtn.setTitle("确认保存", for: .normal)
  572. // alertV.remindData = remindData
  573. // alertV.remindBlock = { [weak self] item, _ in
  574. // guard let sself = self else {
  575. // return
  576. // }
  577. // if item.tag == 1 { // 确定返回到上一层
  578. // if sself.hasExportOnly {
  579. // PHPhotoLibrary.shared().performChanges {
  580. // PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveOnlyUlr)
  581. // } completionHandler: { _, _ in
  582. // }
  583. // }
  584. // if sself.hasExportAll {
  585. // PHPhotoLibrary.shared().performChanges {
  586. // PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveAllUlr)
  587. // } completionHandler: { _, _ in
  588. // }
  589. // }
  590. // }
  591. //
  592. // sself.export.cancelExport()
  593. // sself.navigationController?.popToRootViewController(animated: true)
  594. // }
  595. // UIApplication.shared.keyWindow?.addSubview(alertV)
  596. } else {
  597. export.cancelExport()
  598. navigationController?.popToRootViewController(animated: true)
  599. }
  600. }
  601. @objc func play(sender: UIButton) {
  602. if isExporting {
  603. avplayer.pause()
  604. return
  605. }
  606. if avplayer.currentItem != nil {
  607. if avplayer.timeControlStatus == .playing || sender.isSelected {
  608. avplayer.pause()
  609. } else if avplayer.timeControlStatus == .paused {
  610. avplayer.play()
  611. }
  612. }
  613. }
  614. func whetherCancelExport(comfirm: (() -> Void)?) {
  615. let alertController = UIAlertController(title: "正在合成中,是否取消?",
  616. message: "", preferredStyle: .alert)
  617. let cancelAction = UIAlertAction(title: "继续合成", style: .default, handler: nil)
  618. let okAction = UIAlertAction(title: "取消合成", style: .default) {[weak self] action in
  619. guard let sself = self else {
  620. return
  621. }
  622. sself.export.cancelExport()
  623. comfirm?()
  624. }
  625. alertController.addAction(okAction)
  626. alertController.addAction(cancelAction)
  627. self.present(alertController, animated: true, completion: nil)
  628. // let remindData = BFBaseModel()
  629. // remindData.summary = "正在合成中,是否取消?"
  630. // let alertV = BFRemindView(frame: view.bounds)
  631. // alertV.isBanned = true
  632. // alertV.confirmBtn.setTitle("继续合成", for: .normal)
  633. // alertV.cancelBtn.setTitle("取消合成", for: .normal)
  634. // alertV.remindData = remindData
  635. // alertV.remindBlock = { [weak self] item, _ in
  636. // guard let sself = self else {
  637. // return
  638. // }
  639. // if item.tag == 1 { // 确定返回到上一层
  640. // sself.export.cancelExport()
  641. // comfirm?()
  642. // }
  643. // }
  644. // UIApplication.shared.keyWindow?.addSubview(alertV)
  645. }
  646. }
  647. extension INVideoExportController {
  648. @objc func changeToOriginalFrame() {
  649. if !(sliderView?.isFullScreen ?? false) {
  650. isHiddenStatus = false // (0.0, 64.0, 375.0, 401.0)
  651. navHeadImageView?.isHidden = false
  652. backV.frame = CGRect(x: 0, y: navHeadImageView?.frame.maxY ?? 0, width: view.width, height: bottomView.frame.minY - (navHeadImageView?.frame.maxY ?? 0))
  653. let width = backV.height * (cScreenWidth / cScreenHeigth)
  654. playerLayer.frame = CGRect(x: (backV.width - width)/2, y: 0, width: width, height: backV.height)
  655. // playerLayer.frame = CGRect(x: 0, y: 0, width: view.width, height: bottomView.frame.minY - (navHeadImageView?.frame.maxY ?? 0))
  656. sliderView?.frame = CGRect(x: 0, y: backV.frame.height - 60, width: view.frame.width, height: 50)
  657. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  658. self?.changeOrientation(orientation: .portrait)
  659. self?.backV.center = CGPoint(x: (self?.backV.width ?? 0) / 2, y: (self?.backV.height ?? 0) / 2 + (self?.navHeadImageView?.frame.maxY ?? 0))
  660. }) { [weak self] _ in
  661. }
  662. }
  663. }
  664. func changeToFullScreen() {
  665. if sliderView?.isFullScreen ?? false {
  666. isHiddenStatus = true
  667. navHeadImageView?.isHidden = true
  668. backV.frame = CGRect(x: 0, y: 0, width: view.frame.height, height: view.frame.width)
  669. let width = view.width * (cScreenWidth / cScreenHeigth)
  670. playerLayer.frame = CGRect(x: (view.height - width)/2, y: 0, width: width, height: backV.width)
  671. // playerLayer.frame = backV.bounds
  672. sliderView?.frame = CGRect(x: 0, y: backV.frame.height - 60, width: backV.frame.width, height: 50)
  673. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  674. let orientation = UIDevice.current.orientation
  675. if orientation == .landscapeRight {
  676. self?.changeOrientation(orientation: .landscapeLeft)
  677. } else {
  678. self?.changeOrientation(orientation: .landscapeRight)
  679. }
  680. self?.backV.center = CGPoint(x: (self?.view.frame.width ?? 0) / 2, y: (self?.view.frame.height ?? 0) / 2)
  681. }) { [weak self] _ in
  682. }
  683. }
  684. }
  685. func changeOrientation(orientation: UIInterfaceOrientation) {
  686. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  687. self?.backV.transform = self?.transformRotation(orientation: orientation) as! CGAffineTransform
  688. }) { [weak self] _ in
  689. }
  690. }
  691. @objc func transformRotation(orientation: UIInterfaceOrientation) -> CGAffineTransform {
  692. if orientation == .portrait {
  693. return .identity
  694. } else if orientation == .landscapeLeft {
  695. return CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
  696. } else if orientation == .landscapeRight {
  697. return CGAffineTransform(rotationAngle: CGFloat.pi / 2)
  698. }
  699. return .identity
  700. }
  701. }