// // PQStuckPointPublicController.swift // PQSpeed // // Created by SanW on 2021/5/6. // Copyright © 2021 BytesFlow. All rights reserved. // import ObjectMapper import Photos import UIKit import WechatOpenSDK class PQStuckPointPublicController: PQBaseViewController { private var isShared: Bool = false // 是否在分享 private var isExportSuccess: Bool = false // 是否导出完成 private var isSaveDraftSuccess: Bool = false // 是否保存草稿完成 private var isSaveProjectSuccess: Bool = false // 是否保存项目完成 private var isUploadSuccess: Bool = false // 是否上传完成 private var isPublicSuccess: Bool = false // 是否发布完成 private var exportLocalURL: URL? // 导出的地址 // 再创作数据 private var reCreateData: PQReCreateModel? // 确定上传的数据 private var uploadData: PQUploadModel? // 发布成功的视频数据 private var videoData: PQVideoListModel? // 视频创作埋点数据 private var eventTrackData: PQVideoMakeEventTrackModel? // 选中的总时长-统计使用 var selectedTotalDuration: Float64 = 0 // 选择的总数-统计使用 var selectedDataCount: Int = 0 // 选择的图片总数-统计使用 var selectedImageDataCount: Int = 0 // 最大的宽度 private var maxWidth: CGFloat = cScreenWidth // 最大的高度 private var maxHeight: CGFloat = cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei - cSafeAreaHeight - cDefaultMargin * 5 - cDefaultMargin * 12 - cDefaultMargin * 5 // 开始导出的时间 private let startExportDate: Float64 = Date().timeIntervalSince1970 // 导出结束的时间 private var exportEndDate: Float64 = Date().timeIntervalSince1970 // 取到的封面 给发布界面使用 private var coverImage: UIImage? // 导出视频工具类 private var exporter: PQCompositionExporter! // 导出进度 private var exportProgrss = 0 var mStickers: [PQEditVisionTrackMaterialsModel]? var remindView:PQRemindView? // 预览大小 private var preViewSize: CGSize { switch aspectRatio { case let .origin(width, height): var tempHeight: CGFloat = 0 var tempWidth: CGFloat = 0 if width > height { tempWidth = maxWidth tempHeight = (maxWidth * height / width) if tempHeight > maxHeight { tempHeight = maxHeight tempWidth = (maxHeight * width / height) } } else { tempHeight = maxHeight tempWidth = (maxHeight * width / height) if tempWidth > maxWidth { tempWidth = maxWidth tempHeight = (maxWidth * height / width) } } if tempHeight.isNaN || tempWidth.isNaN { return CGSize.zero } else { return CGSize(width: tempWidth, height: tempHeight) } case .oneToOne: if maxWidth > maxHeight { return CGSize(width: maxHeight, height: maxHeight) } else { return CGSize(width: maxWidth, height: maxWidth) } case .sixteenToNine: return CGSize(width: maxWidth, height: maxWidth * 9.0 / 16.0) case .nineToSixteen: return CGSize(width: maxHeight * 9.0 / 16.0, height: maxHeight) default: break } return CGSize(width: maxHeight, height: maxHeight) } // 背景音乐 var audioMixModel: PQVoiceModel? // 画面比例 var aspectRatio: aspectRatio? // 导出的项目数据 var editProjectModel: PQEditProjectModel? { didSet { aspectRatio = PQPlayerViewModel.videoCanvasTypeToAspectRatio(projectModel: editProjectModel) var totalDuration: Float64 = 0 if editProjectModel?.sData?.sections.count ?? 0 > 0 { for section in (editProjectModel?.sData?.sections)! { totalDuration = totalDuration + section.sectionDuration } } editProjectModel?.sData?.videoMetaData?.duration = totalDuration if editProjectModel?.sData?.sections != nil, (editProjectModel?.sData?.sections.count ?? 0) > 0 { // 查找出背景图并设置 var coverImageMaterialsModel: PQEditVisionTrackMaterialsModel? for section in (editProjectModel?.sData?.sections)! { if coverImageMaterialsModel != nil { break } coverImageMaterialsModel = section.sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().first } if coverImageMaterialsModel != nil { coverImage = coverImageMaterialsModel?.getCoverImage() playerHeaderView.image = coverImage playerHeaderView.contentMode = coverImageMaterialsModel!.canvasFillType == stickerContentMode.aspectFitStr.rawValue ? .scaleAspectFill : .scaleAspectFit } } } } /// 所有需要导出的filter var filters: Array = Array.init() /// 预览背景页 lazy var bgTopView: UIView = { let bgTopView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: maxHeight)) bgTopView.backgroundColor = PQBFConfig.shared.styleBackGroundColor return bgTopView }() // 预览界面 var playerHeaderView: UIImageView = { let playerHeaderView = UIImageView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 0)) playerHeaderView.isUserInteractionEnabled = true playerHeaderView.contentMode = .scaleAspectFit playerHeaderView.clipsToBounds = true return playerHeaderView }() /// 播放器 lazy var avPlayer: AVPlayer = { let avPlayer = AVPlayer() NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem, queue: .main) { [weak self] notify in BFLog(message: "AVPlayerItemDidPlayToEndTime = \(notify)") avPlayer.seek(to: CMTime.zero) self?.playBtn.isHidden = false } NotificationCenter.default.addObserver(forName: .AVPlayerItemNewErrorLogEntry, object: avPlayer.currentItem, queue: .main) { notify in BFLog(message: "AVPlayerItemNewErrorLogEntry = \(notify)") } NotificationCenter.default.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: avPlayer.currentItem, queue: .main) { notify in BFLog(message: "AVPlayerItemFailedToPlayToEndTime = \(notify)") } NotificationCenter.default.addObserver(forName: .AVPlayerItemPlaybackStalled, object: avPlayer.currentItem, queue: .main) { notify in BFLog(message: "AVPlayerItemPlaybackStalled = \(notify)") } avPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1000), queue: .main) { [weak self] _ in let progress = CMTimeGetSeconds(avPlayer.currentItem?.currentTime() ?? CMTime.zero) / CMTimeGetSeconds(avPlayer.currentItem?.duration ?? CMTime.zero) if progress >= 1 { self?.playBtn.isHidden = false } } return avPlayer }() /// 预览layer lazy var playerLayer: AVPlayerLayer = { let playerLayer = AVPlayerLayer(player: avPlayer) playerLayer.frame = playerHeaderView.bounds return playerLayer }() /// 播放按钮 lazy var playBtn: UIButton = { let playBtn = UIButton(type: .custom) playBtn.frame = CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5) playBtn.setImage(UIImage().BF_Image(named: "icon_video_play"), for: .normal) playBtn.tag = 4 playBtn.isHidden = true playBtn.isUserInteractionEnabled = false return playBtn }() // progressTipsLab lazy var progressTipsLab: UILabel = { let progressTipsLab = UILabel() progressTipsLab.textAlignment = .center progressTipsLab.font = UIFont.systemFont(ofSize: 16, weight: .medium) progressTipsLab.numberOfLines = 2 progressTipsLab.textColor = UIColor.white let attributedText = NSMutableAttributedString(string: "0%\n视频正在处理中,请勿离开") attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: 2)) progressTipsLab.attributedText = attributedText progressTipsLab.addShadow() return progressTipsLab }() // 进度条 lazy var progressView: UIProgressView = { let progressView = UIProgressView(progressViewStyle: .default) progressView.trackTintColor = UIColor(white: 0, alpha: 0.5) progressView.progressTintColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue) progressView.transform = CGAffineTransform(scaleX: 1.0, y: playerHeaderView.frame.height / 3.0) return progressView }() lazy var remindLab: UILabel = { let remindLab = UILabel() remindLab.isHidden = true remindLab.font = UIFont.systemFont(ofSize: 14) remindLab.textColor = PQBFConfig.shared.styleTitleColor remindLab.textAlignment = .center remindLab.numberOfLines = 3 let arr = NSMutableAttributedString(string: "制作完成啦🎉\n\n快分享秀一下") arr.addAttributes([.font: UIFont.systemFont(ofSize: 30, weight: .semibold)], range: NSRange(location: arr.length - 6, length: 6)) remindLab.attributedText = arr return remindLab }() lazy var shareWechatBtn: UIButton = { let shareWechatBtn = UIButton(type: .custom) shareWechatBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70) shareWechatBtn.setImage(UIImage().BF_Image(named: "reCreate_opration_wechat"), for: .normal) shareWechatBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor shareWechatBtn.addCorner(corner: 6) shareWechatBtn.tag = 1 shareWechatBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return shareWechatBtn }() lazy var shareFriendBtn: UIButton = { let shareFriendBtn = UIButton(type: .custom) shareFriendBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70) shareFriendBtn.setImage(UIImage().BF_Image(named: "reCreate_opration_friend"), for: .normal) shareFriendBtn.backgroundColor = PQBFConfig.shared.styleBackGroundColor shareFriendBtn.addCorner(corner: 6) shareFriendBtn.tag = 2 shareFriendBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return shareFriendBtn }() lazy var finishedBtn: UIButton = { let finishedBtn = UIButton(type: .custom) finishedBtn.setTitle("完成", for: .normal) finishedBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#999999"), for: .normal) finishedBtn.setTitleColor(UIColor.white, for: .selected) finishedBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .medium) finishedBtn.backgroundColor = PQBFConfig.shared.otherTintColor finishedBtn.tag = 3 finishedBtn.addCorner(corner: 3) finishedBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return finishedBtn }() /// 背景View lazy var oprationBgView: UIView = { let oprationBgView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: view.frame.height - cDevice_iPhoneNavBarAndStatusBarHei)) oprationBgView.backgroundColor = cShadowColor return oprationBgView }() override func backBtnClick() { if isExportSuccess { navigationController?.popViewController(animated: true) } else { view.endEditing(true) let remindData = PQBaseModel() remindData.title = "编辑的内容,将不会被保存" remindView = PQRemindView(frame: CGRect(x: 0, y: 0, width: cScreenWidth, height: cScreenHeigth)) remindView?.isBanned = true remindView?.confirmBtn.setTitle("确认", for: .normal) remindView?.cancelBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#333333"), for: .normal) remindView?.confirmBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#EE0051"), for: .normal) UIApplication.shared.keyWindow?.addSubview(remindView!) remindView?.remindData = remindData remindView?.remindBlock = { [weak self] item, _ in if item.tag == 2 { // 取消导出 if self?.exporter != nil { self?.exporter.cancel() } self?.navigationController?.popViewController(animated: true) } } } } override func viewDidLoad() { super.viewDidLoad() // 注册上传成功的通知 addNotification(self, selector: #selector(uploadSuccess(notify:)), name: cUploadSuccessKey, object: nil) PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil) leftButton(image: "icon_detail_back", tintColor: PQBFConfig.shared.styleTitleColor) navHeadImageView?.backgroundColor = UIColor.clear lineView?.removeFromSuperview() view.addSubview(bgTopView) playerHeaderView.frame = CGRect(origin: CGPoint(x: (cScreenWidth - preViewSize.width) / 2, y: (maxHeight - preViewSize.height) / 2), size: preViewSize) let ges = UITapGestureRecognizer(target: self, action: #selector(playVideo)) playerHeaderView.addGestureRecognizer(ges) // 添加导出view bgTopView.addSubview(playerHeaderView) if playerLayer.superlayer == nil { playerHeaderView.layer.insertSublayer(playerLayer, at: 0) } playerHeaderView.addSubview(playBtn) playerHeaderView.addSubview(progressView) view.addSubview(remindLab) view.addSubview(shareWechatBtn) view.addSubview(shareFriendBtn) navHeadImageView?.addSubview(finishedBtn) view.addSubview(oprationBgView) oprationBgView.addSubview(progressTipsLab) progressView.snp.makeConstraints { make in make.left.right.centerY.equalTo(playerHeaderView) make.height.equalTo(3) } progressTipsLab.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview().offset(((preViewSize.height - 90) / 2) + ((maxHeight - preViewSize.height) / 2)) make.width.equalToSuperview() make.height.equalTo(90) } finishedBtn.snp.makeConstraints { make in make.centerY.equalTo(backButton!) make.width.equalTo(cDefaultMargin * 5) make.height.equalTo(cDefaultMargin * 3) make.right.equalToSuperview().offset(-12) } shareWechatBtn.snp.makeConstraints { make in make.right.equalTo(view.snp_centerX).offset(-cDefaultMargin) make.width.equalTo(70) make.height.equalTo(cDefaultMargin * 7) make.bottom.equalToSuperview().offset(-(cSafeAreaHeight + 32)) } shareFriendBtn.snp.makeConstraints { make in make.left.equalTo(view.snp_centerX).offset(cDefaultMargin) make.width.bottom.height.equalTo(shareWechatBtn) } remindLab.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalTo(shareWechatBtn.snp_top).offset(-cDefaultMargin * 2) } // 取消所有的导出 PQSingletoMemoryUtil.shared.allExportSession.forEach { _, exportSession in exportSession.cancelExport() } // 开始导出 beginExport() // 曝光上报:窗口曝光 PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_publishSyncedUp, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(曝光上报:窗口曝光)") } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) PQNotification.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) PQNotification.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) UIApplication.shared.isIdleTimerDisabled = true #if swift(>=4.2) let memoryNotification = UIApplication.didReceiveMemoryWarningNotification _ = UIApplication.willTerminateNotification _ = UIApplication.didEnterBackgroundNotification #else let memoryNotification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning let terminateNotification = NSNotification.Name.UIApplicationWillTerminate let enterbackgroundNotification = NSNotification.Name.UIApplicationDidEnterBackground #endif NotificationCenter.default.addObserver( self, selector: #selector(clearMemoryCache), name: memoryNotification, object: nil ) } @objc public func clearMemoryCache() { BFLog(message: "收到内存警告") } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) UIApplication.shared.isIdleTimerDisabled = false PQNotification.removeObserver(self) } deinit { view.endEditing(true) PQNotification.removeObserver(self) // 取消导出 if exporter != nil { exporter.cancel() } avPlayer.pause() avPlayer.replaceCurrentItem(with: nil) // 点击上报:返回按钮 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_back, pageSource: .sp_stuck_publishSyncedUp, extParams: nil, remindmsg: "卡点视频数据上报-(点击上报:返回按钮)") } } // MARK: - 导出/上传/下载及其他方法 /// 导出/上传/下载及其他方法 extension PQStuckPointPublicController { /// fp1 - 导出视频 /// 开始导出视频 func beginExport() { if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) { BFLog(message: "项目段落错误❌") return } // 输出视频地址 var outPutMP4Path = exportVideosDirectory if !directoryIsExists(dicPath: outPutMP4Path) { BFLog(message: "文件夹不存在") createDirectory(path: outPutMP4Path) } outPutMP4Path.append("video_\(String.qe.timestamp()).mp4") let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path) BFLog(message: "导出视频地址 \(outPutMP4URL)") let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil) // 每次初始化的时候设置初始值 为 nIl exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL) exporter.showGaussianBlur = true if exporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)) { 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)) BFLog(message: "开始导出 \(String(describing: audioMixModel?.startTime)) 结束 \(String(describing: audioMixModel?.endTime))") exporter.start(playeTimeRange: playeTimeRange) BFLog(message: "开始导出") } exporter.progressClosure = { [weak self] _, _, progress in BFLog(message: "合成进度 \(progress)") let useProgress = progress > 1 ? 1 : progress if progress > 0, Int(useProgress * 100) > (self?.exportProgrss ?? 0) { self?.exportProgrss = Int(useProgress * 100) if (self?.exportProgrss ?? 0) >= 100 { self?.exportProgrss = 99 } self?.progressView.setProgress(useProgress, animated: true) let attributedText = NSMutableAttributedString(string: "\(self?.exportProgrss ?? 0)%\n视频正在处理中,请勿离开") attributedText.addAttributes([.font: UIFont.systemFont(ofSize: 34)], range: NSRange(location: 0, length: "\(self?.exportProgrss ?? 0)%".count)) self?.progressTipsLab.attributedText = attributedText } } exporter.completion = { [weak self] url in BFLog(message: "导了完成: \(url)") // 导出完成后取消导出 if self?.exporter != nil { self?.exporter.cancel() } self?.remindView?.removeFromSuperview() if !(self?.isExportSuccess ?? false) { self?.isExportSuccess = true self?.exportEndDate = Date().timeIntervalSince1970 BFLog(message: "视频导出完成-开始去发布视频") self?.exportLocalURL = url /// fp2-1-1 - 请求权限 // self?.authorizationStatus() /// fp2-2 - 保存草稿 self?.saveDraftbox() } } } /// fp2-1-1 - 请求权限 func authorizationStatus() { let authStatus = PHPhotoLibrary.authorizationStatus() if authStatus == .notDetermined { // 第一次触发授权 alert PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in if status != .authorized { cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限") } else { /// fp2-1-2 - 保存视频到相册 self?.saveStuckPointVideo() } } } else if authStatus == .authorized { /// fp2-1-2 - 保存视频到相册 saveStuckPointVideo() } else { // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限") } } /// fp2-1-2 - 保存视频到相册 /// - Parameter localPath: localPath description /// - Returns: <#description#> func saveStuckPointVideo() { let authStatus = PHPhotoLibrary.authorizationStatus() if authStatus == .authorized { let photoLibrary = PHPhotoLibrary.shared() photoLibrary.performChanges({ [weak self] in PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.exportLocalURL)!) }) { [weak self] isFinished, _ in DispatchQueue.main.async { [weak self] in if self?.view != nil { if isFinished { // cShowHUB(superView: self!.view, msg: "视频已保存至相册") } else { // cShowHUB(superView: self!.view, msg: "视频保存失败") } } } } } else { // cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限") } } /// fp2-2 - 保存草稿 /// - Returns: <#description#> @objc func saveDraftbox() { let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false) if sdata != nil, (sdata?.count ?? 0) > 0 { DispatchQueue.global().async { [weak self] in 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 if draftboxInfo != nil { self?.editProjectModel?.draftboxId = draftboxInfo?["draftboxId"] as? String ?? "" self?.editProjectModel?.sData?.videoMetaData?.title = draftboxInfo?["title"] as? String ?? "" self?.editProjectModel?.sData?.videoMetaData?.coverUrl = draftboxInfo?["coverUrl"] as? String ?? "" self?.editProjectModel?.dataVersionCode = draftboxInfo?["dataVersionCode"] as? Int ?? 0 BFLog(message: "保存远程的草稿成功") self?.isSaveDraftSuccess = true /// fp3 - 保存项目 self?.saveProject() } else { // 保存草稿失败-播放视频 self?.publicEnd(isError: true) } } } } else { cShowHUB(superView: nil, msg: "您尚未打开相册权限,请到设置页打开相册权限") // 保存草稿失败-播放视频 publicEnd(isError: true) } } /// fp3 - 保存项目 /// - Returns: description func saveProject() { if isSaveDraftSuccess, isExportSuccess, exportLocalURL != nil { let sdata = editProjectModel?.sData?.toJSONString(prettyPrint: false) ?? "" let draftboxId: String? = editProjectModel?.draftboxId PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in BFLog(message: "生成的项目id1111 :\(projectId ?? ""),msg = \(msg ?? "")") if projectId == nil || (projectId?.count ?? 0) <= 0 { PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in BFLog(message: "生成的项目id222 :\(projectId ?? ""),msg = \(msg ?? "")") if projectId == nil || (projectId?.count ?? 0) <= 0 { PQBaseViewModel.saveProject(draftboxId: draftboxId, sdata: sdata, videoFromScene: .stuckPoint) { [weak self] projectId, msg in BFLog(message: "生成的项目id 3333:\(projectId ?? ""),msg = \(msg ?? "")") if projectId != nil, (projectId?.count ?? 0) > 0 { self?.editProjectModel?.projectId = projectId ?? "" } /// fp4 - 处理视频数据 self?.dealWithVideoData() } } else { self?.editProjectModel?.projectId = projectId ?? "" /// fp4 - 处理视频数据 self?.dealWithVideoData() } } } else { self?.editProjectModel?.projectId = projectId ?? "" /// fp4 - 处理视频数据 self?.dealWithVideoData() } } } } /// fp4 - 处理视频数据 /// - Returns: description @objc func dealWithVideoData() { BFLog(message: "开始去发布视频12") isSaveProjectSuccess = true if isExportSuccess && exportLocalURL != nil { BFLog(message: "素材上传完成同时视频导出完成开始发布视频") // 更新项目 PQBaseViewModel.updateProject(projectId: editProjectModel?.projectId ?? "", produceStatus: "5") { repseon, _ in BFLog(message: "updateProject 结果 is \(String(describing: repseon))") } let asset = AVURLAsset(url: exportLocalURL!, options: nil) let tempUploadData = PQUploadModel() tempUploadData.duration = CMTimeGetSeconds(asset.duration) tempUploadData.localPath = exportLocalURL?.absoluteString tempUploadData.videoWidth = CGFloat(editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) tempUploadData.videoHeight = CGFloat(editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) tempUploadData.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0) if tempUploadData.image == nil { tempUploadData.image = coverImage } tempUploadData.videoFromScene = .stuckPoint eventTrackData = getExportEventTrackData() eventTrackData?.projectId = editProjectModel?.projectId ?? "" uploadData = tempUploadData if uploadData?.image == nil { uploadData?.image = PQVideoSnapshotUtil.videoSnapshot(videoURL: exportLocalURL!, time: 0) } if uploadData?.image != nil { playerHeaderView.image = uploadData?.image } if isExportSuccess, exportLocalURL != nil { let size = try! exportLocalURL?.resourceValues(forKeys: [.fileSizeKey]) BFLog(message: "size = \(String(describing: size))") if Float64(size?.fileSize ?? 0) <= maxUploadSize { /// fp5 - 上传视频 reUploadVideo() } } } } /// fp5 - 上传视频 /// - Returns: <#description#> @objc func reUploadVideo() { if uploadData?.stsToken != nil { multipartUpload(response: uploadData?.stsToken) } else { uploadVideo() } } /// fp5-1 - 开始上传视频 /// - Returns: <#description#> func uploadVideo() { let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""] if uploadRequest != nil, "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1" { return } DispatchQueue.global().async { PQBaseViewModel.getStsToken { [weak self] response, _ in if response == nil { self?.showUploadRemindView(isNetCollected: false, msg: "获取数据失败了哦~") return } BFLog(message: "取我方服务器STS 返回数据 \(String(describing: response))") self?.multipartUpload(response: response) } } } /// fp5-2 - 继续上传视频 /// - Parameter response: <#response description#> func multipartUpload(response: [String: Any]?) { let FileName: String = "\(response?["FileName"] ?? "")" let uploadID: String = "\(response?["Upload"] ?? "")" uploadData?.stsToken = response uploadData?.videoBucketKey = FileName uploadData?.uploadID = uploadID if uploadData?.asset != nil && isValidURL(url: uploadData?.localPath) { PQPHAssetVideoParaseUtil.exportPHAssetToMP4(phAsset: (uploadData?.asset)!, isCancelCurrentExport: true) { [weak self] _, _, filePath, _ in if filePath != nil, (filePath?.count ?? 0) > 0 { self?.uploadData?.localPath = filePath PQAliOssUtil.multipartUpload(localPath: self?.uploadData?.localPath ?? "", response: response) } } } else { PQAliOssUtil.multipartUpload(localPath: uploadData?.localPath ?? "", response: response) } PQAliOssUtil.shared.aliOssHander = { [weak self] isMatarialUpload, materialType, _, code, objectkey, _, _, _, _, _, _, _, _, _ in if !isMatarialUpload, materialType == .VIDEO, self?.uploadData?.videoBucketKey == objectkey { if code == 6 { // 无网 let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[self?.uploadData?.videoBucketKey ?? ""] if !(uploadRequest != nil && "\(uploadRequest?.callbackParam["code"] ?? "0")" == "1") { self?.showUploadRemindView() } } else if code == 260 { self?.showUploadRemindView(isNetCollected: false) } else if code != 1 { // 上传失败-播放视频 self?.publicEnd(isError: true) } } } } /// fp6 - 视频上传成功,处理要发布视频数据 /// - Parameter notify: <#notify description#> @objc func uploadSuccess(notify: NSNotification) { let objectKey: String = "\(notify.userInfo?["objectKey"] ?? "")" BFLog(message: "收到上传成功请求==\(notify.userInfo ?? [:])") if uploadData?.videoBucketKey == objectKey { // 上传成功 isUploadSuccess = true /// fp7 - 处理要发布视频数据 dealWithPublicData() } } /// fp7 - 处理要发布视频数据 /// - Returns: <#description#> func dealWithPublicData() { if uploadData?.localPath != nil { let size = try! URL(string: uploadData?.localPath ?? "")?.resourceValues(forKeys: [.fileSizeKey]) BFLog(message: "size = \(String(describing: size))") if Float64(size?.fileSize ?? 0) > maxUploadSize { cShowHUB(superView: nil, msg: "无法发布大于10G的视频,请重新选择/合成发布") // 上传失败-播放视频 publicEnd(isError: true) return } } let projectId: String? = editProjectModel?.projectId let uploadRequest: OSSMultipartUploadRequest? = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""] if uploadRequest == nil { reUploadVideo() return } let tempModel = PQVideoListModel() tempModel.title = "" tempModel.summary = "" tempModel.duration = CGFloat(uploadData?.duration ?? 0) tempModel.uplpadImage = uploadData?.image tempModel.uplpadBucketKey = uploadRequest?.objectKey tempModel.localPath = uploadData?.localPath tempModel.reCreateVideoData = reCreateData tempModel.eventTrackData = eventTrackData tempModel.uplpadStatus = 1 tempModel.videoFromScene = .stuckPoint tempModel.uid = Int(BFLoginUserInfo.shared.uid) ?? 0 tempModel.uplpadRequest = PQAliOssUtil.shared.allTasks[uploadData?.videoBucketKey ?? ""] tempModel.stsToken = uploadData?.stsToken tempModel.projectId = projectId // let tempTitleH: CGFloat = sizeWithText(text: title, font: UIFont.systemFont(ofSize: 16), size: CGSize(width: (cScreenWidth - cDefaultMargin * 3) / 2, height: cDefaultMargin * 4)).height // let rate: CGFloat = ((uploadData?.image?.size.height ?? 1) / (uploadData?.image?.size.width ?? 1)) // tempModel.itemHeight = (cScreenWidth - cDefaultMargin * 3) / 2 * rate + tempTitleH + cDefaultMargin * 4.5 // let isContains = PQSingletoMemoryUtil.shared.uploadDatas.contains { (item) -> Bool in // item.uplpadBucketKey == tempModel.uplpadBucketKey // } // if !isContains { // BFLog(message: "添加正在上传数据===\(tempModel)") // PQSingletoMemoryUtil.shared.uploadDatas.insert(tempModel, at: 0) // } // currentController().dismiss(animated: false) { // currentController().navigationController?.viewControllers = [currentController().navigationController?.viewControllers.first ?? PQBaseViewController()] // rootViewController()?.selectedIndex = 4 // if !isContains { // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { // postNotification(name: cPublishSuccessKey) // } // } // } /// fp8 - 发布视频 publicVideo(videoData: tempModel) } /// fp8 - 发布视频 /// - Parameter videoData: <#videoData description#> func publicVideo(videoData: PQVideoListModel) { if videoData.uplpadBucketKey == nil { BFLog(message: "发布视频:视频uplpadBucketKey为空-\(String(describing: videoData.uplpadBucketKey))") // 上传失败-播放视频 publicEnd(isError: true) return } BFLog(message: "开始发布") if (videoData.eventTrackData?.endUploadDate ?? 0) <= 0 { // 结束上传时间 videoData.eventTrackData?.endUploadDate = Date().timeIntervalSince1970 } DispatchQueue.global().async { PQBaseViewModel.ossTempToken { [weak self] response, _ in let image: UIImage = videoData.uplpadImage ?? UIImage() let data = image.jpegData(compressionQuality: 1) let accessKeyId: String = "\(response?["accessKeyId"] ?? "")" let secretKeyId: String = "\(response?["accessKeySecret"] ?? "")" let securityToken: String = "\(response?["securityToken"] ?? "")" let endpoint: String = "\(response?["endPoint"] ?? "")" let bucketName: String = "\(response?["bucketName"] ?? "")" let objectKey: String = "\(response?["objectKey"] ?? "")" BFLog(message: "开始上传视频图片==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey)") PQAliOssUtil.shared .startClient( accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, endpoint: endpoint ) .uploadObjectAsync(bucketName: bucketName, objectKey: objectKey, data: data!, fileExtensions: "png", imageUploadBlock: { _, code, ossObjectKey, _ in BFLog(message: "图片上传完成==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)") if code == 1 && ossObjectKey == objectKey && objectKey.count > 0 { BFLog(message: "开始发布==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)") PQUploadViewModel.publishVideo(projectId: videoData.projectId, fileExtensions: videoData.localPath?.pathExtension, title: videoData.title ?? "", videoPath: videoData.uplpadBucketKey ?? "", coverImgPath: objectKey, descr: videoData.summary ?? "", videoFromScene: .stuckPoint, reCreateData: videoData.reCreateVideoData, eventTrackData: videoData.eventTrackData) { [weak self] newVideoData, _, _ in postNotification(name: cPublishStuckPointSuccessKey, userInfo: ["newVideoData": newVideoData!]) BFLog(message: "发布成功==\(videoData.title ?? ""),uplpadBucketKey = \(videoData.uplpadBucketKey ?? ""),objectKey =\(objectKey),ossObjectKey = \(ossObjectKey)") // cShowHUB(superView: nil, msg: "视频发布成功") self?.videoData = newVideoData // 发布成功后续操作 self?.publicEnd() PQEventTrackViewModel.publishReportUpload(projectId: videoData.projectId, businessType: .bt_publish_success, ossInfo: videoData.stsToken ?? [:], params: ["title": videoData.title ?? "", "videoPath": videoData.uplpadBucketKey ?? "", "coverImgPath": objectKey, "descr": videoData.summary ?? ""]) } } else { // 图片上传失败 BFLog(message: "图片上传失败重新发布视频==\(videoData.title ?? ""),\(videoData.uplpadBucketKey ?? "")") self?.publicVideo(videoData: videoData) } }) } } } /// 发布结束操作 /// - Parameter isError: <#isError description#> /// - Returns: <#description#> func publicEnd(isError: Bool = false) { UIApplication.shared.keyWindow?.viewWithTag(100_100)?.removeFromSuperview() isPublicSuccess = true progressView.removeFromSuperview() progressTipsLab.removeFromSuperview() oprationBgView.removeFromSuperview() playBtn.isHidden = true finishedBtn.isSelected = true finishedBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue) avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: (exportLocalURL?.absoluteString ?? "").replacingOccurrences(of: "file:///", with: "")))) avPlayer.play() if isError { cShowHUB(superView: nil, msg: "视频发布失败,请重新合成") } else { remindLab.isHidden = false /// fp2-1-1 - 请求权限 authorizationStatus() } } /// 生成创作工具埋点数据 /// - Returns: <#description#> func getExportEventTrackData() -> PQVideoMakeEventTrackModel? { let eventTrackData = PQVideoMakeEventTrackModel(projectModel: editProjectModel, reCreateData: reCreateData) eventTrackData.entrance = .entranceStuckPointPublic eventTrackData.editTimeCost = 0 eventTrackData.composeTimeCost = (exportEndDate - startExportDate) * 1000 eventTrackData.musicName = audioMixModel?.musicName ?? "" eventTrackData.syncedUpMusicName = audioMixModel?.musicName ?? "" eventTrackData.musicId = audioMixModel?.musicId ?? "" eventTrackData.syncedUpMusicId = audioMixModel?.musicId ?? "" eventTrackData.musicUrl = audioMixModel?.selectVoiceType == 1 ? (audioMixModel?.musicPath ?? "") : (audioMixModel?.accompanimentPath ?? "") eventTrackData.musicType = audioMixModel != nil ? (audioMixModel?.selectVoiceType == 1 ? "original" : "accompaniment") : "" eventTrackData.isMusicClip = (audioMixModel?.startTime ?? 0) > 0 if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.origin.rawValue { eventTrackData.canvasRatio = "original" } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.nineToSixteen.rawValue { eventTrackData.canvasRatio = "9:16" } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.oneToOne.rawValue { eventTrackData.canvasRatio = "1:1" } else if editProjectModel?.sData?.videoMetaData?.canvasType == videoCanvasType.sixteenToNine.rawValue { eventTrackData.canvasRatio = "16:9" } eventTrackData.syncedUpVideoNumber = selectedDataCount - selectedImageDataCount eventTrackData.syncedUpImageNumber = selectedImageDataCount eventTrackData.syncedUpOriginalMaterialDuration = selectedTotalDuration * 1000 eventTrackData.syncedUpRhythmNumber = audioMixModel?.speed ?? 2 eventTrackData.syncedUpVideoDuration = ((audioMixModel?.endTime ?? 0) - (audioMixModel?.startTime ?? 0)) * 1000 return eventTrackData } /// 播放视频 /// - Returns: <#description#> @objc func playVideo() { playBtn.isHidden = !playBtn.isHidden if playBtn.isHidden { avPlayer.play() } else { avPlayer.pause() } } /// 按钮点击事件 /// - Parameter sender: <#sender description#> /// - Returns: <#description#> @objc func btnClick(sender: UIButton) { switch sender.tag { case 1: if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) { cShowHUB(superView: nil, msg: "视频发布失败,请重新合成") return } if !PQSingletoWXApiUtil.shared.isInstallWX() { cShowHUB(superView: nil, msg: "您还未安装微信客户端!") return } cShowHUB(superView: nil, msg: nil) let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId") PQBaseViewModel.wxFriendShareInfo(videoId: (videoData?.uniqueId)!) { [weak self] imagePath, title, shareWeappRawId, msg in if msg != nil { cShowHUB(superView: nil, msg: "网络不佳哦") return } self?.isShared = true 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 } cHiddenHUB(superView: nil) } // 点击上报:分享微信 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechat, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享微信)") case 2: if !(isExportSuccess && isSaveProjectSuccess && isUploadSuccess && isPublicSuccess) { cShowHUB(superView: nil, msg: "视频发布失败,请重新合成") return } if !PQSingletoWXApiUtil.shared.isInstallWX() { cShowHUB(superView: nil, msg: "您还未安装微信客户端!") return } let shareId = getUniqueId(desc: "\(videoData?.uniqueId ?? "")shareId") PQBaseViewModel.h5ShareLinkInfo(videoId: videoData?.uniqueId ?? "", pageSource: videoData?.pageSource ?? .sp_category) { [weak self] path, _ in cHiddenHUB(superView: nil) if path != nil { self?.isShared = true PQSingletoWXApiUtil.shared.share(type: 1, scene: Int32(WXSceneTimeline.rawValue), title: BFLoginUserInfo.shared.isLogin() ? "\(BFLoginUserInfo.shared.nickName)made a music video for you" : "Music Video for U", description: "", imageUrl: self?.videoData?.shareImgPath, path: path, videoId: (self?.videoData?.uniqueId)!, pageSource: self?.videoData?.pageSource ?? .sp_category, shareId: shareId).wxApiUtilHander = { _, _ in } } else { cShowHUB(superView: nil, msg: "网络不佳哦") } } // 点击上报:分享朋友圈 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_shareWechatMoment, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:分享朋友圈)") case 3: if sender.isSelected { // 点击上报:完成 PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_click_finished, pageSource: .sp_stuck_publishSyncedUp, extParams: ["videoId": videoData?.uniqueId ?? ""], remindmsg: "卡点视频数据上报-(点击上报:完成)") navigationController?.viewControllers = [(navigationController?.viewControllers.first)!] // 发送通知 postNotification(name: cFinishedPublishedNotiKey) } default: break } } /// 添加提示视图 /// - Parameters: /// - isNetCollected: <#isNetCollected description#> /// - msg: <#msg description#> func showUploadRemindView(isNetCollected _: Bool = true, msg _: String? = nil) { view.endEditing(true) // PQUploadRemindView.showUploadRemindView(title: isNetCollected ? "上传中断" : "上传失败", summary: (isNetCollected ? "似乎已断开与互联网的连接" : (msg != nil ? msg : "视频文件已丢失"))!, confirmTitle: isNetCollected ? "重新连接网络" : "重新上传") { [weak self] _, _ in // if isNetCollected { // openAppSetting() // } else { // self?.navigationController?.popToViewController((self?.navigationController?.viewControllers[1])!, animated: true) // } // } } @objc func enterBackground() { BFLog(message: "进入到后台") // 取消导出 if exporter != nil { exporter.cancel() } playBtn.isHidden = false avPlayer.pause() } @objc func willEnterForeground() { BFLog(message: "进入到前台") if !isExportSuccess { beginExport() } playBtn.isHidden = true avPlayer.play() } @objc func didBecomeActiveNotification() { if isShared { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [weak self] in self?.isShared = false cShowHUB(superView: nil, msg: "分享成功") } } } }