// WA_LiveVideoVC.swift // wallpaper_project import UIKit import AVFoundation import MobileCoreServices import FLAnimatedImage import Photos import PhotosUI import SVProgressHUD import Lottie import AppLovinSDK import Alamofire import FirebaseRemoteConfig import MTGSDKNewInterstitial import MTGSDKBidding import MTGSDK import AnyThinkInterstitial struct FilePaths { // 创建目录搜索路径的列表。 static let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.cachesDirectory,.userDomainMask,true)[0] as AnyObject struct VidToLive { // 返回通过将给定字符串附加到接收器而生成的新字符串。 static var livePath = FilePaths.documentsPath.appending("/") } } class WA_LiveVideoVC: WA_RootVC { @IBOutlet weak var bgView: UIView! @IBOutlet weak var tipLabel: UILabel! var model = WA_3DModel() var livePhotoView = PHLivePhotoView() let activityView = UIActivityIndicatorView() let btn = UIButton() // let bgView = UIView() var sizi:CGSize? let Retrybtn = UIButton() private var animationView: LottieAnimationView? var animationDuration: TimeInterval = 1.0 var workProgress: Float = 0.0 var remoteConfig: RemoteConfig! var isadshow:Bool = false var bidToken:String? var newInterstitialAdManager:MTGNewInterstitialBidAdManager? override func viewDidLoad() { super.viewDidLoad() livePhotoView.frame = CGRect(x: 0, y: 0, width: Screen_Width, height: screenHeight); btn.setImage(UIImage(named: "back_black"), for: .normal) btn.backgroundColor = .white btn.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) // 添加按钮 btn.layer.cornerRadius = 20 self.view.addSubview(self.livePhotoView) self.livePhotoView.addSubview(btn) btn.snp.makeConstraints { make in make.left.equalTo(livePhotoView).offset(20) make.top.equalTo(livePhotoView).offset(50) make.height.equalTo(40) make.width.equalTo(40) } Retrybtn.setTitle("Retry", for: .normal) Retrybtn.setTitleColor(.white, for: .normal) Retrybtn.backgroundColor = .black Retrybtn.addTarget(self, action: #selector(Retrybtntapped), for: .touchUpInside) Retrybtn.layer.cornerRadius = 10 Retrybtn.isHidden = true self.livePhotoView.addSubview(Retrybtn) Retrybtn.snp.makeConstraints { make in make.centerX.equalTo(livePhotoView) make.centerY.equalTo(livePhotoView) make.height.equalTo(40) make.width.equalTo(80) } animationView = .init(name: "loadProgress") animationView!.frame = CGRect(x: 0, y: 100, width: Int(Screen_Width), height: Int(Screen_height) - 100) animationView!.contentMode = .scaleAspectFit // animationView!.loopMode = .loop animationView!.loopMode = .autoReverse self.livePhotoView.addSubview(animationView!) // animationView!.play() self.animationView!.currentProgress = 0.1 animationView!.sendSubviewToBack(btn) self.steMoV() activityView.center = CGPoint(x: self.view.center.x, y: self.view.center.y - kSafeArea_Top - 44) // 停止后,隐藏菊花 activityView.hidesWhenStopped = true activityView.color = .black; activityView.style = UIActivityIndicatorView.Style.whiteLarge self.view.addSubview(activityView) self.view.bringSubviewToFront(tipLabel) self.view.bringSubviewToFront(bgView) self.bgView.isHidden = true self.tipLabel.isHidden = true // self.btn.isHidden = true // self.activityView.startAnimating() self.view.makeToast("Picture loading Please wait", duration: 1.5, position: .center) // 添加手势识别器到 UIView 上 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(viewTapped)) livePhotoView.isUserInteractionEnabled = true livePhotoView.addGestureRecognizer(tapGesture) let model1 = WA_3DModel() model1.preview = self.model.preview model1.title = self.model.title model1.thumbnail = self.model.thumbnail model1.is_free = self.model.is_free model1.category = self.model.category model1.id = self.model.id model1.cnt_like = self.model.cnt_like liveManager.shared.addHistoryItem(item: model1) } @objc func buttonTapped(){ if self.isadshow == true{ if ATAdManager.shared().interstitialReady(forPlacementID: "n66bdc18696105"){ ATAdManager.shared().showInterstitial(withPlacementID: "n66bdc18696105", in: self, delegate: self) StartManager.shared.shelfNumber = "动态详情页面show" setPostSHOW() self.navigationController?.popViewController(animated: true) }else{ self.navigationController?.popViewController(animated: true) } }else{ self.navigationController?.popViewController(animated: true) } } @objc func Retrybtntapped(){ // self.activityView.startAnimating() self.animationView?.isHidden = false self.Retrybtn.isHidden = true steMoV() } // 手势识别器的处理方法 @objc func viewTapped() { // 根据当前 UIView 的位置和透明度进行不同的动画 if bgView.frame.origin.y == UIScreen.main.bounds.height - bgView.frame.height { // 当 UIView 在底部时,执行向下移动并渐变消失的动画 UIView.animate(withDuration: 0.5, animations: { self.bgView.frame.origin.y += self.bgView.frame.height self.tipLabel.frame.origin.y += self.bgView.frame.height - 20 self.bgView.alpha = 0 self.btn.isHidden = true }) } else { self.btn.isHidden = false // 当 UIView不在底部时,执行向上移动并渐变显示的动画 UIView.animate(withDuration: 0.5, animations: { self.bgView.frame.origin.y -= self.bgView.frame.height self.tipLabel.frame.origin.y -= self.bgView.frame.height - 20 self.bgView.alpha = 1 }) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.navigationBar.isHidden = true let size = self.view.bounds.size self.sizi = size // 初始化 Remote Config remoteConfig = RemoteConfig.remoteConfig() // 设置最小获取间隔(开发期间可以设置较小的值) let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 remoteConfig.configSettings = settings // Fetch 配置值 remoteConfig.setDefaults(fromPlist: "isopen") loadTopAD() } func loadTopAD(){ let extra: [String: Any] = [ kATAdLoadingExtraMediaExtraKey: "custom_values" ] ATAdManager.shared().loadAD(withPlacementID: "n66bdc18696105", extra: extra, delegate: self) self.fetchRemoteConfig() } ///是否有广告 func fetchRemoteConfig() { let localVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0" remoteConfig.fetch{ (status, error) -> Void in if status == .success { print("Config fetched!") self.remoteConfig.activate { changed, error in if error == nil{ let js = self.remoteConfig.configValue(forKey: "isopen").jsonValue as! [String:Any] let valueopen = js["isadopen"] as! Bool let valueversion = js["version"] as! String DispatchQueue.main.async { if valueversion == localVersion{ self.isadshow = false }else{ if valueopen { self.isadshow = true } else { self.isadshow = false } } } } } } else { print("Config not fetched") if let error = error { print("Error: \(error.localizedDescription)") } } } } @IBAction func saveBtn(_ sender: UIButton) { self.exportLivePhoto() self.activityView.startAnimating() DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.activityView.stopAnimating() // self.view.makeToast("Save Success", duration: 1.5, position: .center) // 创建一个 UIAlertController let alertController = UIAlertController(title: "Successfully saved", message: "Do you need to set up a tutorial", preferredStyle: .alert) // 添加一个取消按钮 alertController.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: nil)) // 添加一个确定按钮 alertController.addAction(UIAlertAction(title: "Need", style: .default, handler: { action in // 点击确定按钮后执行的操作 // print("点击了确定按钮") let vc = WA_TipVC() self.present(vc, animated: true) })) // 在当前视图控制器中 present 显示提示框 self.present(alertController, animated: true, completion: nil) } } } @IBAction func shareBtn(_ sender: UIButton) { self.activityView.startAnimating() // 创建要分享的图片 guard let image = self.livePhotoView.livePhoto else { SVProgressHUD.showInfo(withStatus: "Can't find the image to share") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } print("找不到要分享的图片") return } // 创建要分享的对象数组 let items: [Any] = [image] // 创建 UIActivityViewController 实例 let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) // 设置 UIActivityViewController 的主题 activityViewController.modalPresentationStyle = .popover // 在 iPad 上设置 UIActivityViewController 的位置 if let popoverController = activityViewController.popoverPresentationController { popoverController.sourceView = self.view popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) popoverController.permittedArrowDirections = [] } // 弹出分享视图 self.present(activityViewController, animated: true, completion: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.activityView.stopAnimating() } } //初始化下载mp4到沙盒路径 func steMoV(){ // 原始的 mp4 文件 URL let mp4URL = URL(string: "\(model.preview ?? "")")! // 本地存储路径 let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let str = generateRandomString(length: 10) let destinationURL = documentsDirectory.appendingPathComponent("\(str).mov") // 创建下载任务 let task = URLSession.shared.downloadTask(with: mp4URL) { (tempURL, response, error) in guard let tempURL = tempURL else { print("下载失败:\(error?.localizedDescription ?? "未知错误")") DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.animationView?.isHidden = true self.Retrybtn.isHidden = false SVProgressHUD.showInfo(withStatus: "request timeout") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } } return } do { // 将临时文件移动到目标位置 try FileManager.default.moveItem(at: tempURL, to: destinationURL) // 输出下载完成的信息 print("下载完成:\(destinationURL)") print("文档目录路径:\(documentsDirectory.path)") self.animationView!.currentProgress = 0.5 // 将 mp4 转换为 mov 格式 self.convertMP4ToMOV(mp4URL: destinationURL) } catch { print("文件移动失败:\(error.localizedDescription)") DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.animationView?.isHidden = true self.Retrybtn.isHidden = false SVProgressHUD.showInfo(withStatus: "request timeout") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } } } } // 开始下载任务 task.resume() } //转化mp4为mov,并截取视频第一帧为jpg func convertMP4ToMOV(mp4URL: URL) { let asset = AVAsset(url: mp4URL) let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) // 生成一个随机的字符串作为文件名 let randomString = generateRandomString(length: 10) // 创建输出文件路径 guard let outputURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(randomString).mov") else { print("无法创建输出文件路径") DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.animationView?.isHidden = true self.Retrybtn.isHidden = false SVProgressHUD.showInfo(withStatus: "request timeout") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } } return } exportSession?.outputURL = outputURL exportSession?.outputFileType = .mov exportSession?.exportAsynchronously(completionHandler: { DispatchQueue.main.async { if exportSession?.status == .completed { // 转换完成,打印输出URL print("转换完成,保存位置:\(outputURL)") self.animationView!.currentProgress = 0.8 self.loadVideoWithVideoURL(outputURL) } else if exportSession?.status == .failed { // 转换失败,打印错误信息 print("转换失败:\(exportSession?.error?.localizedDescription ?? "未知错误")") DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.animationView?.isHidden = true self.Retrybtn.isHidden = false SVProgressHUD.showInfo(withStatus: "request timeout") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } } } } }) } func loadVideoWithVideoURL(_ videoURL: URL) { // self.view.makeToast("Start", duration: 0.5, position: .center) livePhotoView.livePhoto = nil //下载到本地的URL let asset = AVURLAsset(url: videoURL) //从视频资源生成图像对像 let generator = AVAssetImageGenerator(asset: asset) //制定资源是否对应多个跟踪矩阵 generator.appliesPreferredTrackTransform = true // 创建一个时间,表示首选时间刻度中的秒数。 let time = NSValue(time: CMTimeMakeWithSeconds(CMTimeGetSeconds(asset.duration)/2, preferredTimescale: asset.duration.timescale)) // 为请求的时间数组异步生成图像,并在回调中返回结果。 generator.generateCGImagesAsynchronously(forTimes: [time]) { [weak self] _, image, _, _, _ in if let image = image, let data = UIImage(cgImage: image).pngData() { let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) // 通过将指定的路径组件附加到self,返回URL。 let imageURL = urls[0].appendingPathComponent("image.jpg") // 将数据缓冲区的内容写入某个位置。 try? data.write(to: imageURL, options: [.atomic]) let image = imageURL.path let mov = videoURL.path let output = FilePaths.VidToLive.livePath // 返回从UUID创建的字符串 let assetIdentifier = UUID().uuidString // 在指定的路径上创建具有给定属性的目录。 let _ = try? FileManager.default.createDirectory(atPath: output, withIntermediateDirectories: true, attributes: nil) do { // 删除指定路径中的文件或目录。 try FileManager.default.removeItem(atPath: output + "/IMG.JPG") try FileManager.default.removeItem(atPath: output + "/IMG.MOV") } catch { } JPEG(path: image).write(output + "/IMG.JPG", assetIdentifier: assetIdentifier) QuickTimeMov(path: mov).write(output + "/IMG.MOV", assetIdentifier: assetIdentifier) if let sizi = self?.sizi { _ = DispatchQueue.main.sync { PHLivePhoto.request(withResourceFileURLs: [ URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.MOV"), URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.JPG")], placeholderImage: nil, targetSize: (self?.sizi)!, contentMode: PHImageContentMode.aspectFit, resultHandler:{ (livePhoto, info) -> Void in self?.livePhotoView.livePhoto = livePhoto if (info.first?.key.description == "PHLivePhotoInfoIsDegradedKey") && (info.first?.value as? NSInteger == 1) { self?.animationView!.currentProgress = 1.0 self?.animationView?.isHidden = true self?.bgView.isHidden = false self?.tipLabel.isHidden = false self?.btn.isHidden = false } }) } } else { // 处理 sizi 为 nil 的情况 } } } } func exportLivePhoto () { PHPhotoLibrary.shared().performChanges({ () -> Void in // 创建使用资产资源向照片库添加新资产的请求 let creationRequest = PHAssetCreationRequest.forAsset() let options = PHAssetResourceCreationOptions() // 使用指定URL处的文件将数据资源添加到正在创建的资产中。 creationRequest.addResource(with: PHAssetResourceType.pairedVideo, fileURL: URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.MOV"), options: options) creationRequest.addResource(with: PHAssetResourceType.photo, fileURL: URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.JPG"), options: options) }, completionHandler: { (success, error) -> Void in if !success { // DTLog((error?.localizedDescription)!) print("转化错误: \(error?.localizedDescription ?? "")") DispatchQueue.main.asyncAfter(deadline: .now() + 0){ self.animationView?.isHidden = true self.Retrybtn.isHidden = false SVProgressHUD.showInfo(withStatus: "request timeout") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { SVProgressHUD.dismiss() } } } }) } } extension WA_LiveVideoVC:ATInterstitialDelegate{ // 插页广告展示成功 func interstitialDidShow(forPlacementID placementID: String, extra: [AnyHashable : Any]) { print("----成功") } /// 插页广告被点击 func interstitialDidClick(forPlacementID placementID: String, extra: [AnyHashable : Any]) { print("----点击") } // 插页广告已关闭 func interstitialDidClose(forPlacementID placementID: String, extra: [AnyHashable : Any]) { print("----关闭") // WA_TabbarCommon.TabBarController() self.navigationController?.popViewController(animated: true) } func didFinishLoadingAD(withPlacementID placementID: String!) { print("ATInterstitialViewController::didFailToLoadADWithPlacementID:\(placementID)") } // load失败 func didFailToLoadAD(withPlacementID placementID: String!, error: (any Error)!) { print("----load失败") print("ATInterstitialViewController::didFailToLoadADWithPlacementID:\(String(describing: placementID))---error\(String(describing: error))") } // 完成加载广告 func didFinishLoadingADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?) { let networkID = extra?["network_firm_id"] let ecpm = extra?["adsource_price"] let country = extra?["country"] if let adsourcePriceString = extra?["adsource_price"] as? String, let adsourcePrice = Double(adsourcePriceString) { StartManager.shared.ecpm = Float(adsourcePrice) / 1000 print("-----ecpm ID: \(StartManager.shared.ecpm)") } else { print("无法获取 adsource_price 或类型不匹配") } StartManager.shared.countryCode = country as? String StartManager.shared.network = network(networkID as! Int) StartManager.shared.shelfNumber = "动态详情页面load" StartManager.shared.adId = "n66bdc18696105" setPostload() print("ATInterstitialViewController::didFailToLoadADWithPlacementID:\(placementID)") } }