// // MP_AnalyticsManager.swift // MusicPlayer // // Created by Mr.Zhou on 2024/5/30. // import UIKit import FirebaseAnalytics import FirebaseCrashlytics import FirebaseRemoteConfig import GoogleMobileAds /// 埋点管理工具 class MP_AnalyticsManager: NSObject { static let shared = MP_AnalyticsManager() //是否新用户 private var isOLD:Bool //MARK: - 配置开关 private let remoteConfig = RemoteConfig.remoteConfig() //MARK: - 事件名 //用户启动 private let user_launch:String = "user_launch" //app闪退 private let app_crash:String = "app_crash" //启动页曝光 private let launch_pv:String = "launch_pv" //启动页计时结束点 private let launch_progress_end:String = "launch_progress_end" //跳转页面动作 private let jump_event:String = "jump_event" //A面首页曝光 private let home_a_pv:String = "home_a_pv" //B面首页曝光 private let home_b_pv:String = "home_b_pv" //首页资源曝光失败 private let home_b_module_showfailure_error:String = "home_b_module_showfailure_error" //首页资源曝光成功 private let home_b_module_showsucces_action:String = "home_b_module_showsucces_action" //点击首页模块 private let home_b_module_click:String = "home_b_module_click" //B面我的曝光 private let me_b_pv = "me_b_pv" //播放器弹出 private let song_click = "song_click" //播放器曝光 private let player_b_imp = "player_b_imp" //播放器歌曲列表加载成功 private let player_b_list = "player_b_list" //B面播放器曝光 private let player_b_pv = "player_b_pv" //统计加载时间 private let player_b_delay_action = "player_b_delay_action" //播放成功 private let player_b_success_action = "player_b_success_action" //播放失败 private let player_b_failure_error = "player_b_failure_error" //点击收藏 private let player_b_love_click = "player_b_love_click" //取消收藏 private let player_b_unlove_click = "player_b_unlove_click" //点击下载 private let player_b_download_click = "player_b_download_click" //下载成功 private let player_b_downloadsuccess_action = "player_b_downloadsuccess_action" //资源下载失败 private let player_b_downloadfailure_error = "player_b_downloadfailure_error" //B面搜索曝光 private let search_pv = "search_pv" //B面搜索心情流派模块点击 private let grid_mood_click = "grid_mood_click" //搜索SUG曝光 private let search_sug_show = "search_sug_show" //点击sug结果 private let search_sug_click = "search_sug_click" //搜索结果曝光 private let search_result_pv = "search_result_pv" //搜索有结果 private let search_resultsuccess_action = "search_resultsuccess_action" //好评引导 private let guide_click = "guide_click" private override init() { //获取用户是新用户还是老用户 if UserDefaults.standard.bool(forKey: "UserStatus") { //是老用户 isOLD = true }else { //新用户 isOLD = false UserDefaults.standard.setValue(true, forKey: "UserStatus") } super.init() Crashlytics.crashlytics().log(app_crash) //为配置设置默认值 remoteConfig.setDefaults(["versionCode":app_Version as NSObject, "enter":true as NSObject]) } //MARK: - 开关 ///A/B面开关(包括间隔时长更新) func getOpenStatus(_ completion:@escaping ((Bool) -> Void)) { //每次启动都要获取值 let duration:TimeInterval = 0 remoteConfig.fetch(withExpirationDuration: duration) { status, error in if status == .success { print("获取配置成功,各项功能使用线上配置") //配置获取成功 self.remoteConfig.activate { changed, error in if error == nil{ //更新启动App广告时长间隔 if let text = self.remoteConfig.configValue(forKey: "openAppEventDuration").jsonValue as? [String:Any] { let seconds = text["times"] as! TimeInterval MP_AdMobManager.shared.setOpenAppDuration(seconds) print("更新了启动App广告时长间隔") } //更新插页广告时长间隔 if let text = self.remoteConfig.configValue(forKey: "interstitialEventDuration").jsonValue as? [String:Any] { let seconds = text["times"] as! TimeInterval MP_AdMobManager.shared.setInterstitialDuration(seconds) print("更新了插页广告时长间隔") } //更新广告ID设置 if let adTextIDs = self.remoteConfig.configValue(forKey: "adMobLevelIDs").jsonValue as? [String:[[String:Any]]] { //对所有广告ID组进行更新 for (key, values) in adTextIDs { var array:[MPPositive_AdModelModel] = [] values.forEach { value in if let level = value["level"] as? Int, let identifier = value["identifier"] as? String, let ad = value["ad"] as? String, let item = value["type"] as? String, let type = MPPositive_AdModelType(rawValue: item) { array.append(.init(level: level, identifier: identifier, ad: ad, type: type)) } } //将array转为jsonData if array.isEmpty != true, let data = coreAdModelforJson(array) { UserDefaults.standard.set(data, forKey: key) }else { UserDefaults.standard.set(nil, forKey: key) } } print("更新了所有广告ID") MP_AdMobManager.shared.reloadAdMobIDs() } //更新版本数据设置 if let versionData = self.remoteConfig.configValue(forKey: "dataVersion").jsonValue as? [String:String] { //对数据进行更新 for (key, value) in versionData { UserDefaults.standard.setValue(value, forKey: key) } print("更新了所有版本数据") MP_NetWorkManager.shared.reloadVersion() } //更新通知内容 if let notificationBodyTexts = self.remoteConfig.configValue(forKey: "notificationBodyTexts").jsonValue as? [String:[String]] { //对数据进行更新 for (key, value) in notificationBodyTexts { UserDefaults.standard.setValue(value, forKey: key) } print("更新了通知文本数据") //更新通知 scheduleDailyNotifications() } let js = self.remoteConfig.configValue(forKey: "openStatus").jsonValue as! [String:Any] let value = js["versionCode"] as! String if value == app_Version { //是同一个版本,当前根据开关状态确认进入页面是什么 let open = js["enter"] as! Bool if open { //进入b面 completion(true) }else { //进入a面 completion(false) } }else { //不是同一个版本,直接进入B面 completion(true) } } } }else { print("获取配置失败,各项功能使用默认配置") //配置获取失败,查看缓存内容 if let js = self.remoteConfig.configValue(forKey: "openStatus").jsonValue as? [String:Any] { //存在缓存数据,调用缓存数据内容 let value = js["versionCode"] as! String if value == app_Version { //是同一个版本,当前根据开关状态确认进入页面是什么 let open = js["enter"] as! Bool if open { //进入b面 completion(true) }else { //进入a面 completion(false) } }else { //不是同一个版本,直接进入B面 completion(true) } }else { //不存在,直接进入A面 completion(false) } } } } //MARK: - 事件日志 ///执行用户启动日志 func user_launchAction(){ Analytics.logEvent(user_launch, parameters: nil) } ///启动页曝光 func launch_pvAction(){ Analytics.logEvent(launch_pv, parameters: nil) } ///启动页进度结束时间 func launch_progress_endAction() { Analytics.logEvent(launch_progress_end, parameters: nil) } ///跳转事件 func jump_eventAction(_ side:String, reason:String) { Analytics.logEvent(jump_event, parameters: ["side":side, "reason":reason]) } ///A面首页曝光 func home_a_pvAction(){ Analytics.logEvent(home_a_pv, parameters: nil) } ///B面首页曝光 func home_b_pvAction(){ Analytics.logEvent(home_b_pv, parameters: nil) } ///首页资源曝光失败 func home_b_module_showfailure_errorAction(_ error:String) { Analytics.logEvent(home_b_module_showfailure_error, parameters: ["error":error]) } ///首页资源曝光成功 func home_b_module_showsucces_actionAction(){ Analytics.logEvent(home_b_module_showsucces_action, parameters: nil) } /// 点击首页模块 /// - Parameter modulename: 模块名 func home_b_module_clickAction(_ modulename:String){ Analytics.logEvent(home_b_module_click, parameters: ["modulename":modulename]) } ///B面我的曝光 func me_b_pvAction(){ Analytics.logEvent(me_b_pv, parameters: nil) } ///需要播放器弹出 func song_clickAction(_ songfrom:String) { Analytics.logEvent(song_click, parameters: ["songfrom":songfrom]) } ///B面播放器曝光 func player_b_impAction() { Analytics.logEvent(player_b_imp, parameters: nil) } ///B面列表资源加载成功 func player_b_listAction() { Analytics.logEvent(player_b_list, parameters: nil) } /// B面播放器切歌 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_pvAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_pv, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///统计加载时间 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_delay_actionAction(_ videoid:String, videoname:String, artistname:String, delay:String){ Analytics.logEvent(player_b_delay_action, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname, "delay":delay ]) } ///播放成功 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_success_actionAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_success_action, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///播放失败 func player_b_failure_errorAction(_ videoid:String, videoname:String, artistname:String, error:String) { Analytics.logEvent(player_b_failure_error, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname, "error":error ]) } ///点击收藏 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_love_clickAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_love_click, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///取消收藏 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_unlove_clickAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_unlove_click, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///点击下载 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_download_clickAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_download_click, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///下载成功 /// - Parameters: /// - videoid: 音乐id /// - videoname: 音乐名 /// - artistname: 歌手 func player_b_downloadsuccess_actionAction(_ videoid:String, videoname:String, artistname:String){ Analytics.logEvent(player_b_downloadsuccess_action, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname ]) } ///下载失败 func player_b_downloadfailure_errorAction(_ videoid:String, videoname:String, artistname:String, error:String) { Analytics.logEvent(player_b_downloadfailure_error, parameters: [ "videoid":videoid, "videoname":videoname, "artistname":artistname, "error":error ]) } ///B面搜索曝光 func search_pvAction(){ Analytics.logEvent(search_pv, parameters: nil) } ///B面搜索心情流派模块点击 func grid_mood_clickAction(_ mood:String) { Analytics.logEvent(grid_mood_click, parameters: ["mood":mood]) } ///搜索SUG曝光 func search_sug_showAction(){ Analytics.logEvent(search_sug_show, parameters: nil) } /// 点击sug结果 /// - Parameter sugname: 搜索标签名 func search_sug_clickAction(_ sugname:String){ Analytics.logEvent(search_sug_click, parameters: ["sugname":sugname]) } ///搜索结果曝光 func search_result_pvAction(){ Analytics.logEvent(search_result_pv, parameters: nil) } ///搜索有结果 func search_resultsuccess_actionAction(){ Analytics.logEvent(search_resultsuccess_action, parameters: nil) } ///好评引导结果 func guide_clickAction(_ result:String) { Analytics.logEvent(guide_click, parameters: ["result":result]) } //MARK: - 广告埋点事件 //冷启动展示机会 private let cold_ads_chance:String = "cold_ads_chance" //冷启动展示成功 private let cold_ads_showSuccess:String = "cold_ads_showSuccess" //冷启动展示失败 private let cold_ads_showFailure:String = "cold_ads_showFailure" //热启动展示机会 private let hot_ads_chance:String = "hot_ads_chance" //热启动展示成功 private let hot_ads_showSuccess:String = "hot_ads_showSuccess" //热启动展示失败 private let hot_ads_showFailure:String = "hot_ads_showFailure" //搜索展示机会 private let search_ads_chance:String = "search_ads_chance" //搜索展示次数 private let search_ads_showSuccess:String = "search_ads_showSuccess" //搜索展示次数 private let search_ads_showFailure:String = "search_ads_showFailure" //搜索结果页展示机会 private let result_ads_chance:String = "result_ads_chance" //搜索结果原生 private let result_ads_show:String = "result_ads_show" //播放展示机会 private let play_ads_chance:String = "play_ads_chance" //播放展示次数 private let play_ads_showSuccess:String = "play_ads_showSuccess" //播放展示次数 private let play_ads_showFailure:String = "play_ads_showFailure" //下载展示机会 private let dl_ads_chance:String = "dl_ads_chance" //下载展示次数 private let dl_ads_showSuccess:String = "dl_ads_showSuccess" //下载展示次数 private let dl_ads_showFailure:String = "dl_ads_showFailure" //切歌展示机会 private let cut_ads_chance:String = "cut_ads_chance" //切歌展示次数 private let cut_ads_showSuccess:String = "cut_ads_showSuccess" //切歌展示次数 private let cut_ads_showFailure:String = "cut_ads_showFailure" //列表进入机会 private let listclk_ads_chance:String = "listclk_ads_chance" //列表进入次数 private let listclk_ads_showSuccess:String = "listclk_ads_showSuccess" //列表进入次数 private let listclk_ads_showFailure:String = "listclk_ads_showFailure" //曲库原生机会 private let lbr_ads_chance:String = "lbr_ads_chance" //曲库原生次数 private let lbr_ads_show:String = "lbr_ads_show" //列表原生机会 private let list_ads_chance:String = "list_ads_chance" //列表原生次数 private let list_ads_show:String = "list_ads_show" //总价值上报 private let ad_session_total_value:String = "ad_session_total_value" //插页广告内容值转化 private func infoToParameters(_ responseInfo:GADResponseInfo, adValue:GADAdValue) -> [String:Any] { var mediation = "Unknown" if let mediation_group_name = responseInfo.extrasDictionary["mediation_group_name"] as? String { mediation = mediation_group_name } let price = valueToUSD(adValue) //创建参数 let parameters:[String:Any] = ["CS_PLATFORM":responseInfo.loadedAdNetworkResponseInfo?.adSourceName ?? "Unknown", "CS_SOURCE":responseInfo.loadedAdNetworkResponseInfo?.adSourceInstanceName ?? "Unknown", "CS_UNIT_NAME":mediation, "CS_CURRENCY":adValue.currencyCode, "CS_VALUE":price, "CS_STATUS":isOLD ? "Old":"New"] //同时执行当前活跃会话的总价格和总次数更新 if var totalPrices = UserDefaults.standard.array(forKey: "Session_TotalPrices") as? [Double] { print("更新了当前活跃会话的广告总次数和总价值") totalPrices.append(price) UserDefaults.standard.set(totalPrices, forKey: "Session_TotalPrices") }else { print("更新了当前活跃会话的广告总次数和总价值") //没有这个数据 var array:[Double] = [] array.append(price) UserDefaults.standard.set(array, forKey: "Session_TotalPrices") } return parameters } //价格转化(全部转化为美元) private func valueToUSD(_ adValue:GADAdValue) -> Double { //将价格全部转为美元 if adValue.currencyCode == "USD" { return Double(truncating: adValue.value) }else { //日后添加转化 return Double(truncating: adValue.value) } } //MARK: - 广告事件日志 ///冷启动展示机会 func cold_ads_chanceAction() { Analytics.logEvent(cold_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///冷启动展示成功 func cold_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("冷启动成功展示信息--\(parameters)") Analytics.logEvent(cold_ads_showSuccess, parameters: parameters) } ///冷启动展示失败 func cold_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("冷启动失败展示信息--\(parameters)") Analytics.logEvent(cold_ads_showFailure, parameters: parameters) } ///热启动展示机会 func hot_ads_chanceAction() { Analytics.logEvent(hot_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///热启动展示成功 func hot_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("热启动成功展示信息--\(parameters)") Analytics.logEvent(hot_ads_showSuccess, parameters: parameters) } ///热启动展示失败 func hot_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("热启动失败展示信息--\(parameters)") Analytics.logEvent(hot_ads_showFailure, parameters: parameters) } ///搜索展示机会 func search_ads_chanceAction() { Analytics.logEvent(search_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///搜索展示成功 func search_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("搜索插页成功展示信息--\(parameters)") Analytics.logEvent(search_ads_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///搜索展示失败 func search_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("搜索插页失败展示信息--\(parameters)") Analytics.logEvent(search_ads_showFailure, parameters: parameters) } ///搜索结果页展示机会 func result_ads_chanceAction() { Analytics.logEvent(result_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///搜索结果页成功 func result_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("搜索结果页成功展示信息--\(parameters)") Analytics.logEvent(result_ads_show, parameters: parameters) } ///播放展示机会 func play_ads_chanceAction() { Analytics.logEvent(play_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///播放展示成功 func play_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("播放插页成功展示信息--\(parameters)") Analytics.logEvent(play_ads_showSuccess, parameters: parameters) } ///播放展示失败 func play_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("播放插页失败展示信息--\(parameters)") Analytics.logEvent(play_ads_showFailure, parameters: parameters) } ///下载展示机会 func dl_ads_chanceAction() { Analytics.logEvent(dl_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///下载展示成功 func dl_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("下载插页成功展示信息--\(parameters)") Analytics.logEvent(dl_ads_showSuccess, parameters: parameters) } ///下载展示失败 func dl_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("下载插页失败展示信息--\(parameters)") Analytics.logEvent(dl_ads_showFailure, parameters: parameters) } ///切歌展示机会 func cut_ads_chanceAction() { Analytics.logEvent(cut_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///切歌展示成功 func cut_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("切歌插页成功展示信息--\(parameters)") Analytics.logEvent(cut_ads_showSuccess, parameters: parameters) } ///切歌展示失败 func cut_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("切歌插页失败展示信息--\(parameters)") Analytics.logEvent(cut_ads_showFailure, parameters: parameters) } ///列表进入机会 func listclk_ads_chanceAction() { Analytics.logEvent(listclk_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///列表进入成功 func listclk_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("列表插页成功展示信息--\(parameters)") Analytics.logEvent(listclk_ads_showSuccess, parameters: parameters) } ///列表进入失败 func listclk_ads_showFailureAction(_ error:String) { let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New", "CS_ERROR":error] print("列表插页失败展示信息--\(parameters)") Analytics.logEvent(listclk_ads_showFailure, parameters: parameters) } ///曲库原生机会 func lbr_ads_chanceAction() { Analytics.logEvent(lbr_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///曲库原生次数 func lbr_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("曲库原生成功展示信息--\(parameters)") Analytics.logEvent(lbr_ads_show, parameters: parameters) } ///列表原生机会 func list_ads_chanceAction() { Analytics.logEvent(list_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"]) } ///列表原生次数 func list_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) { let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue) print("列表原生成功展示信息--\(parameters)") Analytics.logEvent(list_ads_show, parameters: parameters) } ///对总价值的价格上报事件 func ad_session_total_valueAction() { //取出当前活跃次数总价值以及对应数据 if let totalPrices = UserDefaults.standard.array(forKey: "Session_TotalPrices") as? [Double] { //成功获取了总价值与总次数 var totalPrice:Double = 0 totalPrices.forEach { item in totalPrice += item } let count = totalPrices.count let currentTime = getCurrentLosAngelesTime() let parameters:[String:Any] = ["session_currentTime":currentTime, "session_totalPrice":totalPrice, "session_totalNumber":count] print("当前活跃会话总价值--\(parameters)") //发布事件 Analytics.logEvent(ad_session_total_value, parameters: parameters) //清理当前活跃会话广告总价值 UserDefaults.standard.removeObject(forKey: "Session_TotalPrices") } } //获取太平洋洛杉矶时间 func getCurrentLosAngelesTime() -> String { // 获取当前日期和时间 let now = Date() // 创建 DateFormatter 实例 let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" // 设置时区为洛杉矶时间 if let losAngelesTimeZone = TimeZone(identifier: "America/Los_Angeles") { dateFormatter.timeZone = losAngelesTimeZone } else { return "Error: Unable to find Los Angeles Time Zone" } // 将当前时间转换为洛杉矶时间并格式化为字符串 let losAngelesTimeString = dateFormatter.string(from: now) return losAngelesTimeString } }