// // bbbAdManager.swift // playbtest // // Created by mac on 2025/3/11. // import Foundation import AnyThinkInterstitial import AnyThinkSDK class bConfig: NSObject { var appId:String = "h682ad31de9bb3" /// 广告Key var adKey:String = "af927fa8beee871757a1d54e560441d18" /// 广告数组 var allAdIds:[String] = ["n682ae1dc6d1b6", "n682ae1c9416bb", "n682ae1b496431","n682ae0f785ed8"] /// 广告数组 var adids:[String] = [] ///设备ID var adbrush_deviceid:String? ///最低ecpm var adbrush_ecpm:Double = 0.0005 /// 本地ip var adbrush_localip:String? /// A面load show var adbrush_base_url:String = "http://183.222.62.53:58078" /// 本地服务 var adbrush_local_url:String = "http://127.0.0.1:6000" /// 远程ip var remouteIP:String = "" /// dataId var dataId:String = "" ///IDFA var idfa:String = "" /// var washParam:Bool = false /// var linkId:String = "" ///load次数 var loadcount:Int = 0 ///load次数 var loadcount1:Int = 0 var ipTime:Int = 0 override init() { super.init() if self.allAdIds.count > 3 { self.adids = Array(self.allAdIds.shuffled().prefix(3)) } else { self.adids = self.allAdIds } } ///是否正在展示中 @objc dynamic var isadsureshow:Bool = false func getRandomString() -> String? { return adids.randomElement() } // 判断当前是否为广告不量模式 func isADSSMode() -> Bool { // return true return UserDefaults.standard.bool(forKey: "kLuxSSFaceKey") } } class AdItem :NSObject, ATInterstitialDelegate { func didFinishLoadingAD(withPlacementID placementID: String!) { BbbAdManager.config.loadcount1 += 1 NSLog("XS- didFinishLoadingAD\(String(describing: placementID))") NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载广告1: \(String(describing: placementID)) 成功 - \(BbbAdManager.config.loadcount1)"]) changeStatus(st: 2) } // var interstitialAd:MAInterstitialAd! var interstitialAdID: String = "" var retryAttempt = 0.0 // 定义广告关闭后的操作闭包 var _onAdClosed: (() -> Void)? var onStatusChange:((_:String,_:Int, _:Double) -> Void)? var startLoadTime: DispatchTime? // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 private(set) var status: Int = 0 private(set) var ecpm:Double = 0.0 init(adID:String){ super.init() self.interstitialAdID = adID // loadInterstitialAd() } func onAdClosed() { if self._onAdClosed != nil { self._onAdClosed!() } } func changeStatus(st:Int) { self.status = st onStatusChange?(self.interstitialAdID, st, self.ecpm) } func loadInterstitialAd(){ NSLog("XS- placementIDLoad 1: ---- \(interstitialAdID)") NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text":"begin load:\(interstitialAdID)"]) startLoadTime = DispatchTime.now() changeStatus(st: 1) let extra: [String: Any] = [ kATAdLoadingExtraMediaExtraKey: "custom_values" ] ATAdManager.shared().loadAD(withPlacementID: interstitialAdID, extra: extra, delegate: self) NSLog("XS- placementIDLoad 2: ---- \(interstitialAdID)") } func showAd(viewController:UIViewController, onAdClosed: @escaping () -> Void) -> Bool{ self._onAdClosed = onAdClosed NSLog("XS- onAdClosed set: \(self._onAdClosed != nil)") if ATAdManager.shared().interstitialReady(forPlacementID: interstitialAdID) { ATAdManager.shared().showInterstitial(withPlacementID: interstitialAdID, in: viewController, delegate: self) NSLog("XS- placementIDShow : ---- \(interstitialAdID)") return true } else { NSLog("XS- interstitialAdID no redy 插页广告尚未准备好") // retryLoadAdIfNecessary() } return false } func to_network(_ network: Int) -> String { switch network { case 6: return "Mintegral" case 11: return "Ironsource" case 12: return "UnityAds" case 13: return "Vungle" case 50: return "Pangle" default: return "Mintegral" } } // 完成加载广告 func didFinishLoadingADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?) { BbbAdManager.config.loadcount += 1 var thatecpm = 0.0 if let adsourcePriceValue = extra?["adsource_price"] as? NSNumber { thatecpm = Double(Float(truncating: adsourcePriceValue) / 1000) } else { NSLog("XS- not get type adsource_price or type not match") } if thatecpm > self.ecpm { self.ecpm = thatecpm } NSLog("XS- ad load ok:\(BbbAdManager.config.linkId) - \(String(describing: placementID)) ecpm:\(self.ecpm * 1000)") // 计算并打印加载时间 var time = 0 if let startTime = startLoadTime { let loadDuration = calculateElapsedTime(since: startTime) NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") time = loadDuration } let networkID:Int = extra?["network_firm_id"] as! Int let network = to_network(networkID) let country = extra?["country"] ?? "" NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载广告: \(String(describing: placementID)) 成功 - \(network), ecpm:\(self.ecpm * 1000) \(country) \(BbbAdManager.config.loadcount)"]) retryAttempt = 0 // 重置重试次数 DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: thatecpm , network: network, countryCode: country as! String, platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time) } // 发布广告加载成功通知 // NotificationCenter.default.post(name: .adDidLoad, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) // changeStatus(st: 2) } func didFailBiddingADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?, error: (any Error)!) { // BbbAdManager.config.loadcount += 1 NSLog("XS- load \(String(describing: placementID)) err.... :\(String(describing: error))") /* var time = 0 if let startTime = startLoadTime { let loadDuration = calculateElapsedTime(since: startTime) NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") time = loadDuration } DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: 0.0 , network: "", countryCode: "", platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") } */ NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "bidding error:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) // self.onAdClosed() // changeStatus(st: 5) } func didFailToLoadAD(withPlacementID placementID: String!, error: (any Error)!) { BbbAdManager.config.loadcount += 1 NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) NSLog("XS- load\(String(describing: placementID)) err.... :\(String(describing: error))") // NotificationCenter.default.post(name: .adDidFailToLoad, object: nil, userInfo: ["adId": adUnitIdentifier]) var time = 0 if let startTime = startLoadTime { let loadDuration = calculateElapsedTime(since: startTime) NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") time = loadDuration } DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: 0.0 , network: "", countryCode: "", platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") } // self.onAdClosed() changeStatus(st: 5) } /* func didFailToLoadADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?, error: (any Error)!) { BbbAdManager.config.loadcount += 1 NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) NSLog("XS- load\(String(describing: placementID)) err.... :\(String(describing: error))") // NotificationCenter.default.post(name: .adDidFailToLoad, object: nil, userInfo: ["adId": adUnitIdentifier]) var thatecpm = 0.0 if let adsourcePriceValue = extra?["adsource_price"] as? NSNumber{ thatecpm = Double(Float(truncating: adsourcePriceValue) / 1000) } else { NSLog("XS- not get type adsource_price or type not match") } // self.ecpm = thatecpm // 计算并打印加载时间 var time = 0 if let startTime = startLoadTime { let loadDuration = calculateElapsedTime(since: startTime) NSLog("广告 \(String(describing: placementID)) 加载时间: \(loadDuration) ms") time = loadDuration } let networkID:Int = extra?["network_firm_id"] as! Int let network = to_network(networkID) let country = extra?["country"] ?? "" DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: thatecpm , network: network, countryCode: country as! String, platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") } // self.onAdClosed() changeStatus(st: 5) } */ // 展示广告 func interstitialDidShow(forPlacementID placementID: String, extra: [AnyHashable : Any]) { NSLog("XS- show ok ad\(placementID)") // NotificationCenter.default.post(name: .adDidDisplay, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) // let currentIDFV = UIDevice.current.identifierForVendor?.uuidString var ecpmprice: Double? if let adsourcePriceValue = extra["adsource_price"] as? NSNumber{ ecpmprice = Double(Float(truncating: adsourcePriceValue)) / 1000 } else { NSLog("XS- not get type adsource_price or type not match") } let networkID = extra["network_firm_id"] as! Int let network = to_network(networkID) let country = extra["country"] ?? "" // let currentIDFV = UIDevice.current.identifierForVendor?.uuidString DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.showAd(adId: placementID, ecpm: ecpmprice, ad: true) { self?.changeStatus(st: 4) NSLog("XS- close ad ok\(placementID)") self?.onAdClosed() } } DispatchQueue.global(qos: .utility).async { [weak self] in guard self != nil else { return } YL_NetWorkManager.uploadAD_Show(adid: placementID, ecpm: ecpmprice ?? 0.0, network: network, countryCode: country as! String, platformResponseTime:0 , dsp: "MTG") } NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "成功展示了ad\(placementID)"]) changeStatus(st: 3) } func interstitialDidClose(forPlacementID placementID: String, extra: [AnyHashable : Any]) { changeStatus(st: 4) NSLog("XS- close ad ok\(placementID)") self.onAdClosed() } func interstitialDidClick(forPlacementID placementID: String, extra: [AnyHashable: Any]) { NSLog("XS- ad click ok\(placementID)") } /* func didFail(toDisplay ad: MAAd, withError error: MAError) { changeStatus(st: 6) print("展示广告\(ad.adUnitIdentifier),\(error)失败") NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "展示广告\(ad.adUnitIdentifier),\(error)失败"]) self.onAdClosed!() } */ // 辅助函数以毫秒为单位计算经过的时间 private func calculateElapsedTime(since startTime: DispatchTime) -> Int { let endTime = DispatchTime.now() let nanoseconds = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds return Int(nanoseconds / 1_000_000) // 转换为毫秒 } } class BbbAdManager: NSObject { static let shared = BbbAdManager() static let config = bConfig() ///是否正在展示中 @objc dynamic var isshow:Bool = false // 用于存储多个广告位的管理器,Key 是广告位的 ID private var adItems: [String: AdItem] = [:] var openADTimer:Timer? let kOpenADPerSec: CGFloat = 1 // 假设的秒数 let kOpenAdCTimeLength: CGFloat = 60 // 假设的超时时长 private var view:UIViewController? static var totalTimeC: CGFloat = 0.0 var isLoaded = false // 添加广告位管理器 func add(adId: String) { let adManager = AdItem(adID: adId) adManager.onStatusChange = {id, st, ecpm in // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 var text = "初始" if st == 1 { text = "加载中" } else if st == 2 { text = "加载完成" } else if st == 3 { text = "展示中" } else if st == 4 { text = "关闭" } else if st == 5 { text = "加载失败" } else if st == 6 { text = "展示失败" } NotificationCenter.default.post(name: NSNotification.Name("adStatus"), object: nil, userInfo: ["id": id, "text":"\(text),ecpm:\(ecpm * 1000)"]) } adItems[adId] = adManager adManager.loadInterstitialAd() } override init(){ super.init() if BbbAdManager.config.isADSSMode() { self.initConfig() } } func initConfig () { if #available(iOS 14, *) { IDFA.shared.checkATT { idfa in if let idfa = idfa { NSLog("IDFA: \(idfa)") BbbAdManager.config.idfa = idfa } else { NSLog("无法获取 IDFA") } } } else { IDFA.shared.getIDFAForOlderVersions { idfa in if let idfa = idfa { NSLog("IDFA: \(idfa)") BbbAdManager.config.idfa = idfa } else { NSLog("无法获取 IDFA") } } } if let bfaceDict = UserDefaults.standard.dictionary(forKey: "bfaceDictKey"){ BbbAdManager.config.adbrush_base_url = bfaceDict["adbrush_base_url"] as? String ?? "http://183.222.62.53:58078" BbbAdManager.config.adbrush_deviceid = bfaceDict["adbrush_deviceid"] as? String ?? "" BbbAdManager.config.adbrush_localip = bfaceDict["adbrush_localip"] as? String ?? "" BbbAdManager.config.remouteIP = bfaceDict["remouteIP"] as? String ?? "" BbbAdManager.config.adbrush_local_url = bfaceDict["adbrush_local_url"] as? String ?? "http://127.0.0.1:6000" BbbAdManager.config.dataId = bfaceDict["dataId"] as? String ?? "" BbbAdManager.config.adbrush_ecpm = bfaceDict["adbrush_ecpm"] as? Double ?? 0.005 BbbAdManager.config.linkId = bfaceDict["linkId"] as? String ?? "" BbbAdManager.config.washParam = bfaceDict["washParam"] as? Bool ?? false } else { NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "获取字典失败"]) } } func initAd() { NSLog("XS- init ad") initializationTopOn.toponeSDK(BbbAdManager.config.appId,appKey: BbbAdManager.config.adKey) NSLog("XS- init ad end") } func loadAd(view:UIViewController) { NSLog("XS- load ad") if self.isLoaded { return } self.isLoaded = true self.view = view if BbbAdManager.config.washParam == true{ for (_, adId) in BbbAdManager.config.adids.enumerated() { BbbAdManager.shared.add(adId: adId) } }else{ for (_, adId) in BbbAdManager.config.adids.enumerated() { NSLog("XS- ad load start:\(BbbAdManager.config.linkId) - \(adId)") BbbAdManager.shared.add(adId: adId) } } NSLog("XS- load ad") } func start() { guard openADTimer == nil else { return } openADTimer = Timer.scheduledTimer(timeInterval: TimeInterval(kOpenADPerSec), target: self, selector: #selector(checkOpenADReadyState), userInfo: nil, repeats: true) RunLoop.current.add(openADTimer!, forMode: .common) } func isEnd () -> Bool { NSLog("XS- ad end") if(self.isshow) { NSLog("XS- ad end 1") return false } for (_, ad) in BbbAdManager.shared.adItems { if(ad.status == 1) { NSLog("XS- ad end 2") return false } if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { NSLog("XS- ad end 3") return false } } NSLog("XS- ad end 4") return true } func showAd(v:UIViewController) { if(self.isshow == false) { for (_, ad) in BbbAdManager.shared.adItems { NSLog("XS- ad info:\(ad.interstitialAdID), ecpm:\(ad.ecpm * 1000)") if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { self.isshow = ad.showAd(viewController: v) { [weak self] in NSLog("XS- ad close") self!.isshow = false } break } } } } func closeAd(v:Int) { initializationTopOn.removeADVC(byDelayTime: v, onclose:{ for (_, ad) in BbbAdManager.shared.adItems { if(ad.status == 3) { ad.changeStatus(st: 4) } } }) self.isshow = false } @objc func checkOpenADReadyState(){ BbbAdManager.totalTimeC += kOpenADPerSec if (self.isEnd() && BbbAdManager.totalTimeC > 8) || BbbAdManager.totalTimeC >= kOpenAdCTimeLength { openADTimer?.invalidate() openADTimer = nil DispatchQueue.global().async { [weak self] in guard self != nil else { return } YL_NetWorkManager.loadend(max_ecpm: 0) } } if self.isshow == false{ self.showAd(v: self.view!) } } }