// // MPNetWorkManager.swift // MusicPlayer // // Created by Mr.Zhou on 2024/4/11. // import UIKit import Network import Alamofire import AVFoundation ///预览闭包(传递一个预览模块数据组和完成状态) typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel], _ isCompeleted:Bool) -> Void ///列表/专辑闭包(传递一个列表数据) typealias ListRequestResultBlock = (_ list:MPPositive_ListAlbumListViewModel) -> Void ///网络状况管理器 class MP_NetWorkManager: NSObject { //单例工具 static let shared = MP_NetWorkManager() //MARK: - 网络请求会话 ///会话实例 private let MPSession = Alamofire.Session(interceptor: MP_CustomRetrier()) //MARK: - API接口 ///IP获取 private let iPInfo:String = "https://api.tikustok.com/app/common/getIPInfo" ///域名链接 private let header:String = "https://music.youtube.com" ///端点 private let point:String = "/youtubei/v1" ///预览接口 private let browse:String = "/browse" ///相关接口 private let next:String = "/next" ///播放器接口 private let player:String = "/player" ///搜索建议接口 private let suggestions:String = "/music/get_search_suggestions" ///搜索接口 private let search = "/search" ///YouTuBe资源键值 private let youTubeKeys:[String] = ["MUSIC_VIDEO_TYPE_ATV","MUSIC_VIDEO_TYPE_OMV","MUSIC_PAGE_TYPE_ALBUM","MUSIC_PAGE_TYPE_ARTIST","MUSIC_PAGE_TYPE_PLAYLIST","MUSIC_PAGE_TYPE_TRACK_LYRICS","MUSIC_PAGE_TYPE_TRACK_RELATED"] ///禁止接入IP信息组 private let banIPs:[String] = [ "CN", "HK", "TW", "JP", "KR", "GB", "CH", "BE", "MO", "SG" ] //网络状态 enum NetWorkStatus: String { case notReachable = "网络不可用" case unknown = "网络未知" case reachable = "网络可用" } ///网络监听器 private let reachabilityManager:NetworkReachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/")! ///当前网络状态 var netWorkStatu:NetWorkStatus!{ willSet{ //旧值为网络可用,新值为网络不可用,为断网第一时间,发出通知告知播放器 if netWorkStatu == .reachable, newValue == .notReachable { print("网络不可用") NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable) } //旧值为网络不可用,新值为网络可用,为网络回复第一时间,发出通知告知播放器 if (netWorkStatu == .notReachable), newValue == .reachable { print("网络可用") NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable) } } } //MARK: - 固定参数 //访问数据(首次首页预览时获得) private var visitorData:String? //固定时间点(同一天的上一周) private lazy var currTimeDate:String = (Date().timeZone() - 7.days).toString(.custom("YYYYMMdd")) ///地址 private var locaton:String? //预览下一阶段参数(网络请求获取) private var continuationAndItct:(String?,String?){ willSet{ //当获取新值后,判断新值是否存在,存在则继续串行异步请求 guard let continuation = newValue.0, let itct = newValue.1, browseQueque != nil, let url = URL(string: header+point+browse) else { //移除异步线程 browseQueque = nil return } //生成新参数 let parameters:[String:Any] = [ "ctoken":continuation, "continuation":continuation, "type":"next", "itct":itct, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //执行异步请求 browseQueque?.async { [weak self] in guard let self = self else { return } requestPostHomeBrowse(url, parameters: parameters) } } } //MARK: - GCD队列 ///串行队列-预览 private var browseQueque:DispatchQueue? ///并发队列-单曲资源预加载 var playerItemLoadingGroup:DispatchGroup = DispatchGroup() //MARK: - 闭包 ///预览闭包(传递一个预览模块数据和完成状态) var browseRequestStateBlock:BrowseRequestStateBlock? //私有初始化 private override init() { super.init() } //MARK: - 网络情况 ///检查网络状况 func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) { let monitor = NWPathMonitor() monitor.pathUpdateHandler = { path in switch path.status { case .satisfied: DispatchQueue.main.async { guard completeHanlder != nil else { return } completeHanlder!() } default://网络权限出现问题 DispatchQueue.main.async { //次要处理 let alertController = UIAlertController(title: "Access network request", message: "”Musiclax“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert) let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in } let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in let url = URL(string: UIApplication.openSettingsURLString) if let url = url,UIApplication.shared.canOpenURL(url){ if #available(iOS 10, *) { UIApplication.shared.open(url, options: [:]) { (success) in } }else{ UIApplication.shared.canOpenURL(url) } } } alertController.addAction(CancelAction) alertController.addAction(OKAction) oberve.present(alertController, animated: true, completion: nil) } } } let queue = DispatchQueue(label: "MPNetWorkManager") monitor.start(queue: queue) } ///网络请求检测 func requestStatusToYouTube() { //通过ping节点确认是否能执行网络请求 reachabilityManager.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in guard let self = self else {return} switch status { case .unknown://未知状况 netWorkStatu = .unknown case .notReachable://网络不可用 netWorkStatu = .notReachable case .reachable(.ethernetOrWiFi), .reachable(.cellular)://网络可用,且做出了分类 netWorkStatu = .reachable } }) } } //MARK: - API请求 extension MP_NetWorkManager { //MARK: - 请求iP信息 ///请求IP信息 func requestIPInfo(_ completion:@escaping((Bool) -> Void)) { //拼接出browse路径 let path = iPInfo //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } requestPostIPInfo(url) { open in completion(open) } } private func requestPostIPInfo(_ url:URL, completion:@escaping((Bool) -> Void)) { MPSession.request(url, method: .get, encoding: JSONEncoding.default).responseDecodable(of: JsonIPInfo.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): guard let data = value.data, let code = data.isoCode else { return } if banIPs.contains(code) == true { locaton = "" //包含,是禁止区域 completion(false) }else { //不包含,是通行区域 locaton = code completion(true) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) completion(true) } } } //MARK: - 请求首页预览 ///向YouTubemusic请求预览/首页数据 func requestBrowseDatas() { //实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列 // 实例化串行队列 browseQueque = DispatchQueue(label: "com.request.browseQueque") visitorData = nil //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //异步执行任务 browseQueque?.async { [weak self] in //进行第一次请求,有预览ID,无continuation编码 guard let self = self else { return } let parameters:[String:Any] = [ "browseId": "FEmusic_home", "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostHomeBrowse(url, parameters: parameters) } } //请求首页预览内容(执行多次) private func requestPostHomeBrowse(_ url:URL, parameters:Parameters) { //发送post请求,并将结果转为RootBrowses MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonBrowses.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if value.responseContext?.visitorData != nil { self.visitorData = value.responseContext?.visitorData } //解析结构体 let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0] if let content = tab?.tabRenderer?.content { parsingBrowseContents(content) }else if let continuationContents = value.continuationContents { parsingBrowseContinuationContents(continuationContents) }else { print("Failed to parse browses content") } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) browseQueque = nil } } } //MARK: - 请求列表专辑预览 /// 向YouTubemusic请求列表/专辑数据,该接口调用的同样是browse预览接口 /// - Parameters: /// - browseId: 专辑列表的Id /// - params: 专辑列表的访问编码 func requestAlbumOrListDatas(_ browseId: String, params: String, comletion:@escaping (MPPositive_ListAlbumListViewModel) -> Void) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "browseId":browseId, "params":params, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] // //guard netWorkStatu != .notReachable else {return} requestPostAlbumOrList(url, parameters: parameters) { results in comletion(results) } } //请求列表/专辑数据 private func requestPostAlbumOrList(_ url:URL, parameters:Parameters, comletion:@escaping (MPPositive_ListAlbumListViewModel) -> Void) { //发送post请求,并将结果转为RootBrowses MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonListOrAlbum.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): let contents = value.contents //创建一个列表ViewModel let list = MPPositive_ListAlbumListViewModel() if let content = contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content { list.items = parsingListContents(content) } if let header = value.header { list.header = .init(parsingListHeaders(header)) } //列表数据完善,回调 comletion(list) case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///请求艺术家 func requestArtist(_ browseId:String, comletion:@escaping (MPPositive_ArtistViewModel) -> Void) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "browseId":browseId, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostArtist(url, parameters: parameters) { result in comletion(result) } } //请求艺术家信息 private func requestPostArtist(_ url:URL, parameters:Parameters, comletion:@escaping (MPPositive_ArtistViewModel) -> Void) { //发送post请求,并将结果转为RootBrowses MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonArtist.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): //解析value let artist = MPPositive_ArtistViewModel() if let header = value.header { artist.header = parsingArtistHeaders(header) } if let contents = value.contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents { artist.lists = parsingArtistContents(contents) } comletion(artist) case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///请求艺术家更多数据 func requestArtistMore(_ browseId:String, params: String, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "browseId":browseId, "params":params, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostArtistMore(url, parameters: parameters) { result in comletion(result) } } ///请求艺术家更多数据 private func requestPostArtistMore(_ url:URL, parameters:Parameters, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) { //发送post请求,并将结果转为RootBrowses MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonArtistMore.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let content = value.contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.first { let result = parsingArtistMore(content) comletion(result) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///请求艺术家更多数据继续 func requestArtistMoreContinuation(_ continuation:String, itct:String, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "continuation":continuation, "ctoken":continuation, "type":"next", "itct":itct, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostArtistMoreContinuation(url, parameters: parameters) { result in comletion(result) } } ///请求艺术家更多数据继续 private func requestPostArtistMoreContinuation(_ url:URL, parameters:Parameters, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) { //发送post请求,并将结果转为RootBrowses MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonArtistMore.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let musicPlaylistShelfContinuation = value.continuationContents?.musicPlaylistShelfContinuation { let result = parsingArtistMoreContinuation(musicPlaylistShelfContinuation) comletion(result) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } //MARK: - 请求列表专辑下一部分 ///请求Next列表(优先于Player) /// - Parameter item: 请求的预览实体 func requestNextList(_ browseId:String, videoId:String, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) { //拼接出next路径 let path = header+point+next //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,videoId与params参数是必定携带内容 let parameters:[String:Any] = [ "playlistId":browseId, "videoId":videoId, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostNextList(url, parameters: parameters) { listSongs in //成功拿到列表所有歌曲(内容尚不完善) completion(listSongs) } } //请求next列表 private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): //转化为list值 let listSongs:[MPPositive_SongItemModel] = parsingNextList(value) completion(listSongs) case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///请求Next歌词/相关内容 /// - Parameter item: 请求的预览实体 func requestNextLyricsAndRelated(_ item: MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) { //拼接出next路径 let path = header+point+next //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,videoId与params参数是必定携带内容 let parameters:[String:Any] = [ "videoId":(item.videoId ?? ""), "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostNextLyricsAndRelated(url, parameters: parameters) { result in completion(result) } } //请求请求Next歌词/相关内容 private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): let result = parsingNextLyricsAndRelated(value) //回掉数据 completion(result) case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } //MARK: - 请求player播放资源 /// 请求Player(单曲/视频)播放资源 /// - Parameter item: 请求的预览实体 func requestAndroidPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Int],[String]), [String]?) -> Void)){ //拼接出player路径 let path = header+point+player //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } let day = Date().timeZone() //设置参数,videoId与params参数是必定携带内容 let parameters:[String:Any] = [ "videoId":videoId, "prettyPrint":"false", "context":[ "client":[ "clientName": "ANDROID_MUSIC", "clientVersion": "\(day.month).\(day.toString(.custom("dd"))).1", "platform":"MOBILE", "browserVersion":"125.0.0.0", // "userAgent": // "clientName": "ANDROID_MUSIC", // "clientVersion": "5.28.1", // "platform": "MOBILE", // "androidSdkVersion":"30", // "userAgent": "com.google.android.apps.youtube.music/5.28.1 (Linux; U; Android 11) gzip" ] ] ] //guard netWorkStatu != .notReachable else {return} requestAndroidPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in completion(resourceUlrs, coverUrls) } } private func requestAndroidPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Int],[String]), [String]?) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonAndroidPlayer.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): parsingAndroidPlayer(value) { resourceUlrs, coverUrls in completion(resourceUlrs, coverUrls) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } // func requestPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Float],[String]), [String]?) -> Void)){ // //拼接出player路径 // let path = header+point+player // //设置url // guard let url = URL(string: path) else { // print("Url is Incorrect") // return // } // //设置参数,videoId与params参数是必定携带内容 // let parameters:[String:Any] = [ // "videoId":videoId, // "prettyPrint":"false", // "context":[ // "client":[ // "clientName": "WEB_REMIX", //// //"visitorData":visitorData, //// "originalUrl":"https://music.youtube.com/watch?v=\(videoId)", // //当前访问版本(日期值) // "clientVersion": "1.\(currTimeDate).01.00", // "platform":"MOBILE", // //语言 // "hl":Language_first_local, // //地址 // "gl":locaton ?? "" // ] // ], // "playbackContext": [ // "contentPlaybackContext": [ // "signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0 // ] // ] // ] // //guard netWorkStatu != .notReachable else {return} // requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in // completion(resourceUlrs, coverUrls) // } // } //请求单曲/视频 // private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)) { // //发送post请求 // MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in // guard let self = self else {return} // // switch response.result { // case .success(let value): // parsingPlayer(value) { resourceUlrs, coverUrls in // completion(resourceUlrs, coverUrls) // } // case .failure(let error): // // 请求失败,处理错误 // print("Request failed: \(error)") // } // } // } //MARK: - 请求歌词 /// 请求歌词 /// - Parameter lyricId: 歌词id func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "browseId":lyricId, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostLyric(url, parameters: parameters) { lyrics in completion(lyrics) } } //请求歌词 private func requestPostLyric(_ url:URL, parameters:Parameters, completion:@escaping((String) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonLyrics.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): completion(parsingLyrics(value) ?? "") case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///请求相关内容 func requestRecommend(_ browseId:String, completion: @escaping ([MPPositive_RecommendListViewModel]) -> Void) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 let parameters:[String:Any] = [ "browseId":browseId, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostRecommend(url, parameters: parameters) { results in completion(results) } } ///请求相关内容 private func requestPostRecommend(_ url:URL, parameters:Parameters, completion: @escaping ([MPPositive_RecommendListViewModel]) -> Void) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonRecommend.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let contents = value.contents?.sectionListRenderer?.contents { let section = parsingRecommend(contents) completion(section) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } //MARK: - 请求搜索建议 /// 请求搜索建议 /// - Parameter content: 用户输入的文本 func requestSearchSuggestions(_ content:String, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) { //拼接路径 let path = header+point+suggestions //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数 let parameters:[String:Any] = [ "input":content, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostSearchSuggestions(url, parameters: parameters) { result in completion(result) } } //请求搜索建议 private func requestPostSearchSuggestions(_ url:URL, parameters:Parameters, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchSuggestions.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): parsingSearchSuggestions(value) { results in completion(results) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } /// 请求搜索预览结果 /// - Parameter text: 用户请求文本 func requestSearchPreviewResults(_ text:String, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) { //拼接路径 let path = header+point+search //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数 let parameters:[String:Any] = [ "query":text, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostSearchPreviewResults(url, parameters: parameters) { result in completion(result) } } //请求搜索预览结果 private func requestPostSearchPreviewResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchPreviewResults.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let contents = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents { let result = parsingSearchPreviewResults(contents) print("一共搜索到\(result.count)相关内容模块") completion(result) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } /// 请求搜索分类结果 /// - Parameters: /// - query: 搜索文本 /// - params: 事件参数码 func requestSearchTypeResults(_ query:String, params:String, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) { //拼接路径 let path = header+point+search //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数 let parameters:[String:Any] = [ "query":query, "prettyPrint":"false", "params":params, "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostSearchTypeResults(url, parameters: parameters) { result in completion(result) } } //请求搜索分类结果 private func requestPostSearchTypeResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchTypeResults.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let musicShelfRenderer = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.first?.musicShelfRenderer { completion(parsingSearchTypeResults(musicShelfRenderer)) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } /// 请求搜索分类继续结果 /// - Parameters: /// - continuation: 继续编码 /// - itct: 继续事件码 func requestSearchTypeContinuation(_ continuation:String, itct:String, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) { //拼接路径 let path = header+point+search //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数 let parameters:[String:Any] = [ "ctoken":continuation, "continuation":continuation, "type":"next", "itct":itct, "prettyPrint":false, "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //"visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(currTimeDate).01.00", "platform":"MOBILE", //语言 "hl":Language_first_local, //地址 "gl":locaton ?? "" ] ] ] //guard netWorkStatu != .notReachable else {return} requestPostSearchTypeContinuation(url, parameters: parameters) { result in completion(result) } } //请求搜索分类继续结果 private func requestPostSearchTypeContinuation(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchTypeContinuation.self) { [weak self] (response) in guard let self = self else {return} switch response.result { case .success(let value): if let musicShelfContinuation = value.continuationContents?.musicShelfContinuation { completion(parsingSearchTypeContinuation(musicShelfContinuation)) } case .failure(let error): // 请求失败,处理错误 handleError(url, error: error) } } } ///错误处理方法 private func handleError(_ url: URL, error:AFError) { // 根据错误类型处理 if let statusCode = error.responseCode { switch statusCode { case 400...499: print("\(url)请求错误,错误码: \(statusCode)。") case 500...599: print("\(url)服务器错误,错误码: \(statusCode)。") default: print("\(url)其他 HTTP 错误,错误码: \(statusCode)。") } } else if let underlyingError = error.underlyingError as? URLError { switch underlyingError.code { case .notConnectedToInternet: print("\(url)网络连接不可用,请检查你的网络设置。") case .timedOut: print("\(url)请求超时,请稍后重试。") default: print("\(url)NSURL 错误,错误码: \(underlyingError.code.rawValue)。") } } else { print("\(url)未知错误: \(error.localizedDescription)") } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { //统一发一个报错通知 NotificationCenter.notificationKey.post(notificationName: .netWork_error_deal) } } } //MARK: - 数据解析 extension MP_NetWorkManager { //MARK: - 浅层解析 ///解析预览内容_Contents private func parsingBrowseContents(_ content:JsonBrowses.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) { //获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表id和视频id) var browses:[MPPositive_BrowseModuleListViewModel] = [] var continuation:String? var itct:String? //循环获取音乐内容 content.sectionListRenderer?.contents?.forEach({ content in //该循环获取预览模块内容,生成一个预览模型接收数据 let browse = MPPositive_BrowseModuleListViewModel() browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text //循环音乐内容组 content.musicCarouselShelfRenderer?.contents?.forEach({ content in //设置标题、歌手、专辑/列表 if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer { browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) }else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer { browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer))) } }) browses.append(browse) }) if let nextContinuationData = content.sectionListRenderer?.continuations?.first { continuation = nextContinuationData.nextContinuationData?.continuation itct = nextContinuationData.nextContinuationData?.clickTrackingParams } guard browseRequestStateBlock != nil else { return } //传递解析数据 self.browseRequestStateBlock!(browses, (continuation == nil)) //重新赋值,直到没有下一阶段数据为止 self.continuationAndItct = (continuation,itct) } ///解析预览内容_ContinuationContents private func parsingBrowseContinuationContents(_ continuationContents:JsonBrowses.ContinuationContents) { //获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表id和视频id) var browses:[MPPositive_BrowseModuleListViewModel] = [] var continuation:String? var itct:String? //循环获取音乐内容 continuationContents.sectionListContinuation?.contents?.forEach({ content in //该循环获取预览模块内容,生成一个预览模型接收数据 let browse = MPPositive_BrowseModuleListViewModel() browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text //循环音乐内容组 content.musicCarouselShelfRenderer?.contents?.forEach({ content in //设置标题、歌手、专辑/列表 if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer { browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) }else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer { browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer))) } }) browses.append(browse) }) if let nextContinuationData = continuationContents.sectionListContinuation?.continuations?.first { continuation = nextContinuationData.nextContinuationData?.continuation itct = nextContinuationData.nextContinuationData?.clickTrackingParams } guard browseRequestStateBlock != nil else { return } //传递解析数据 self.browseRequestStateBlock!(browses, (continuation == nil)) //重新赋值,直到没有下一阶段数据为止 self.continuationAndItct = (continuation,itct) } ///解析列表_Contents private func parsingListContents(_ content:JsonListOrAlbum.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) -> [MPPositive_BrowseItemViewModel] { var items:[MPPositive_BrowseItemViewModel] = [] //循环获取音乐内容 content.sectionListRenderer?.contents?.forEach({ content in //循环音乐内容组 if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer { musicPlaylistShelfRenderer.contents?.forEach({ content in //设置标题、歌手、专辑/列表 if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer { items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) }else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer { items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer))) } }) }else if let musicShelfRenderer = content.musicShelfRenderer{ musicShelfRenderer.contents?.forEach({ content in //设置标题、歌手、专辑/列表 if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer { items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) }else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer { items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer))) } }) } }) return items } ///解析列表_Header private func parsingListHeaders(_ header:JsonListOrAlbum.Header) -> MPPositive_ListHeaderModel { let listHeader = MPPositive_ListHeaderModel() if let musicDetailHeaderRenderer = header.musicDetailHeaderRenderer { //主标题 listHeader.maintitle = musicDetailHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //次标题 listHeader.subtitle = musicDetailHeaderRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")}) //三级标题 listHeader.thirdtitle = musicDetailHeaderRenderer.secondSubtitle?.runs?.reduce("", { $0 + ($1.text ?? "")}) //说明/介绍 listHeader.forDescription = musicDetailHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")}) //图片(默认获取最后/最大的) listHeader.coverUrl = musicDetailHeaderRenderer.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail?.thumbnails?.last?.url }else if let musicImmersiveHeaderRenderer = header.musicImmersiveHeaderRenderer { listHeader.maintitle = musicImmersiveHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) listHeader.forDescription = musicImmersiveHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")}) listHeader.coverUrl = musicImmersiveHeaderRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url } return listHeader } ///解析艺术家_Header private func parsingArtistHeaders(_ header:JsonArtist.Header) -> MPPositive_ArtistHeaderModel { let model = MPPositive_ArtistHeaderModel() model.title = header.musicImmersiveHeaderRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) model.subscriptions = header.musicImmersiveHeaderRenderer?.subscriptionButton?.subscribeButtonRenderer?.subscriberCountText?.runs?.reduce("", { $0 + ($1.text ?? "")}) model.subscriptionedText = header.musicImmersiveHeaderRenderer?.subscriptionButton?.subscribeButtonRenderer?.subscribedButtonText?.runs?.reduce("", { $0 + ($1.text ?? "")}) model.fordescription = header.musicImmersiveHeaderRenderer?.description?.runs?.reduce("", { $0 + ($1.text ?? "")}) model.thumbnails = header.musicImmersiveHeaderRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$0.url}) return model } ///解析艺术家_Contents private func parsingArtistContents(_ contents:[JsonArtist.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) -> [MPPositive_ArtistContentListViewModel] { var listArray:[MPPositive_ArtistContentListViewModel] = [] ///遍历解析模块内容 contents.forEach { content in var title:String? var itemViews:[MPPositive_BrowseItemViewModel] = [] var id:String? var params:String? if let musicShelfRenderer = content.musicShelfRenderer { //这是首选模块 title = musicShelfRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //获取模块的事件值 id = musicShelfRenderer.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.browseId params = musicShelfRenderer.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.params //解析模块内容 musicShelfRenderer.contents?.forEach({ item in if item.musicResponsiveListItemRenderer != nil { itemViews.append(.init(parsingMusicResponsiveListItemRenderer(item.musicResponsiveListItemRenderer!))) } }) } if let musicCarouselShelfRenderer = content.musicCarouselShelfRenderer { //这是次选模块 title = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) id = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.browseId params = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.params //解析模块内容 musicCarouselShelfRenderer.contents?.forEach({ item in if item.musicTwoRowItemRenderer != nil { itemViews.append(.init(parsingMusicTwoRowItemRenderer(item.musicTwoRowItemRenderer!))) } }) } let list = MPPositive_ArtistContentListViewModel(title, itemViews: itemViews, browseId: id, params: params) listArray.append(list) } return listArray } ///解析艺术家更多_More private func parsingArtistMore(_ content:JsonArtistMore.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content) -> ([MPPositive_BrowseItemViewModel], String?, String?) { //返回一组预览实体和一对继续编码 var continuation:String? var clickTrackingParams:String? var array:[MPPositive_BrowseItemViewModel] = [] //判断返回内容是音视频还是专辑列表 if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer { //是音视频 musicPlaylistShelfRenderer.contents?.forEach({ item in if let musicResponsiveListItemRenderer = item.musicResponsiveListItemRenderer { array.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) } }) continuation = musicPlaylistShelfRenderer.continuations?.first?.nextContinuationData?.continuation clickTrackingParams = musicPlaylistShelfRenderer.continuations?.first?.nextContinuationData?.clickTrackingParams } if let gridRenderer = content.gridRenderer { //是专辑列表 gridRenderer.items?.forEach({ item in if let musicTwoRowItemRenderer = item.musicTwoRowItemRenderer { array.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer))) } continuation = gridRenderer.continuations?.first?.nextContinuationData?.continuation clickTrackingParams = gridRenderer.continuations?.first?.nextContinuationData?.clickTrackingParams }) } return (array, continuation, clickTrackingParams) } ///解析艺术家更多继续_Continuation private func parsingArtistMoreContinuation(_ musicPlaylistShelfContinuation: JsonArtistMore.ContinuationContents.MusicPlaylistShelfContinuation) -> ([MPPositive_BrowseItemViewModel], String?, String?) { //返回一组预览实体和一对继续编码 var continuation:String? var clickTrackingParams:String? var array:[MPPositive_BrowseItemViewModel] = [] //判断返回内容是音视频还是专辑列表 if let contents = musicPlaylistShelfContinuation.contents { contents.forEach { item in if let musicResponsiveListItemRenderer = item.musicResponsiveListItemRenderer { array.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer))) } } } continuation = musicPlaylistShelfContinuation.continuations?.first?.nextContinuationData?.continuation clickTrackingParams = musicPlaylistShelfContinuation.continuations?.first?.nextContinuationData?.clickTrackingParams return (array,continuation,clickTrackingParams) } ///解析相关内容_Next_中的接下来播放列表 private func parsingNextList(_ next:JsonNext) -> [MPPositive_SongItemModel] { var array:[MPPositive_SongItemModel] = [] if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs { if let tab = tabs.first { //获取一张播放列表 for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() { if let playlistPanelVideoRenderer = content.playlistPanelVideoRenderer { //生成一个音乐实体,用来装填部分数据 let song = MPPositive_SongItemModel() song.index = index song.title = playlistPanelVideoRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.longBylineText = playlistPanelVideoRenderer.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.lengthText = playlistPanelVideoRenderer.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.shortBylineText = playlistPanelVideoRenderer.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.reviewUrls = playlistPanelVideoRenderer.thumbnail?.thumbnails?.map({$0.url ?? ""}) song.videoId = playlistPanelVideoRenderer.videoId array.append(song) } } } } return array } /// 解析相关内容_Next_中的歌词ID和相关内容ID(两个ID都适用于用brwose接口) /// - Parameter next: 需要解析的next组 /// - Returns: 0位是歌词ID,1位相关内容ID private func parsingNextLyricsAndRelated(_ next:JsonNext) -> (String?,String?){ if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs { if tabs.count == 3 { //歌词ID let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId //相关内容ID let relatedID = tabs[2].tabRenderer?.endpoint?.browseEndpoint?.browseId return (lyrcisId, relatedID) }else if tabs.count == 2 { //歌词ID let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId return (lyrcisId, nil) }else { return (nil,nil) } }else { return (nil,nil) } } /// 解析播放器_Player /// - Parameters: /// - player: player库 /// - completion: 传递两个字符串数组,第一个资源路径组,第二个是封面路径组 // private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)){ // var infos:[String]? // //解析player,获取资源库和信息库 // if let videoDetails = player.videoDetails { // infos = parsingPlayerVideoDetails(videoDetails) // } // if let streamingData = player.streamingData { // parsingPlayerStreamingData(streamingData){ videos,floats,approxDurationMs in // completion((videos,floats,approxDurationMs),infos) // } // } // } private func parsingAndroidPlayer(_ player:JsonAndroidPlayer,completion:@escaping((([String],[Int],[String]), [String]?) -> Void)){ var infos:[String]? //解析player,获取资源库和信息库 if let videoDetails = player.videoDetails { infos = parsingAndroidPlayerVideoDetails(videoDetails) } if let streamingData = player.streamingData { parsingAndroidPlayerStreamingData(streamingData){ videos,itags,mimeType in completion((videos,itags,mimeType),infos) } } } /// 解析播放器_StreamingData /// - Parameters: /// - streamingData: 资源库 /// - completion: 第一位是音频资源,第二位是视频资源 private func parsingAndroidPlayerStreamingData(_ streamingData:JsonAndroidPlayer.StreamingData, completion:@escaping(([String],[Int],[String]) -> Void)) { var videos:[String] = [] var itags:[Int] = [] var mimeType:[String] = [] let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? []) for format in allFormats { videos.append(format.url ?? "") itags.append(format.itag ?? 0) mimeType.append(format.mimeType ?? "") } completion(videos,itags,mimeType) } // private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[Float],[String]) -> Void)) { // var group:DispatchGroup? = DispatchGroup() // var videos:[String] = [] // var floats:[Float] = [] // var approxDurationMs:[String] = [] // let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? []) // for format in allFormats { // if let signatureCipher = format.signatureCipher { // // 进入DispatchGroup,表示开始一个异步任务 // group?.enter() // //获得资源签名,开始解密签名内容 // parsingPlayerSignatureCipher(signatureCipher) { result in // //这是条视频资源 // videos.append(result) // floats.append(format.bitrate ?? 0) // approxDurationMs.append(format.approxDurationMs ?? "") // // 离开DispatchGroup,表示异步任务完成 // group?.leave() // } // } // } // group?.notify(queue: .main) { // completion(videos, floats, approxDurationMs) // group = nil // } // } ///解析加密签名_SignatureCipher private func parsingPlayerSignatureCipher(_ signatureCipher:String, completion:@escaping((String) -> Void)) { // print("Resources-SignatureCipher:\(signatureCipher)") //该加密资源有两段式加密,先进行百分比加密解码 let originalURLString = seperatorOff(String(signatureCipher)) //第一段加密资源为权限资源 guard let sRange = originalURLString.range(of: "s=") else { return } guard let spSigRange = originalURLString.range(of: "&sp=sig", range: sRange.upperBound.. [MPPositive_RecommendListViewModel] { var sectionLists:[MPPositive_RecommendListViewModel] = [] contents.forEach { section in let list = MPPositive_RecommendListViewModel() list.title = section.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) section.musicCarouselShelfRenderer?.contents?.forEach({ content in if content.musicResponsiveListItemRenderer != nil { let item = parsingMusicResponsiveListItemRenderer(content.musicResponsiveListItemRenderer!) list.items.append(.init(item)) }else if content.musicTwoRowItemRenderer != nil { let item = parsingMusicTwoRowItemRenderer(content.musicTwoRowItemRenderer!) list.items.append(.init(item)) } }) sectionLists.append(list) } return sectionLists } /// 解析搜索建议_SearchSuggestions /// - Parameters: /// - searchSuggestions: 需要解析搜索建议 /// - completion: 回掉两组搜索建议组 private func parsingSearchSuggestions(_ searchSuggestions:JsonSearchSuggestions, completion:@escaping([[MPPositive_SearchSuggestionItemModel]]) -> Void) { if let contents = searchSuggestions.contents { var sections:[[MPPositive_SearchSuggestionItemModel]] = [] contents.forEach { section in var suggestions:[MPPositive_SearchSuggestionItemModel] = [] section.searchSuggestionsSectionRenderer?.contents?.forEach({ content in //生成搜索建议模型 let item = MPPositive_SearchSuggestionItemModel() if let searchSuggestionRenderer = content.searchSuggestionRenderer { //这里是搜索词条建议 item.title = searchSuggestionRenderer.suggestion?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer { var reviewUrls:[String] = [] //这里是搜索音乐建议 musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.forEach({ thumbnail in reviewUrls.append(thumbnail.url ?? "") }) item.reviewUrls = reviewUrls if let flexColumns = musicResponsiveListItemRenderer.flexColumns { for (index,flexColumn) in flexColumns.enumerated() { if index == 0 { //主标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else { //副标题 item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } } } suggestions.append(item) }) sections.append(suggestions) } completion(sections) } } ///解析搜索预览结果_SearchPreviewResults private func parsingSearchPreviewResults(_ contents:[JsonSearchPreviewResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) -> [MPPositive_SearchResultListViewModel]{ var resultListSections:[MPPositive_SearchResultListViewModel] = [] contents.forEach { content in let resultList = MPPositive_SearchResultListViewModel() //判断当前模块是最佳结果还是其它模块 if let musicCardShelfRenderer = content.musicCardShelfRenderer { //当前是最佳结果 //设置模块标题 resultList.title = musicCardShelfRenderer.header?.musicCardShelfHeaderBasicRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //生成第一个内容块(根据内容不同,可能是单曲/歌手/专辑) let item = MPPositive_BrowseItemModel() //设置第一个内容块的预览图 item.coverUrls = musicCardShelfRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) item.playListId = musicCardShelfRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId //设置第一个内容块的一级标题和类型和ID if let title = musicCardShelfRenderer.title { title.runs?.forEach({ run in item.title = run.text if run.navigationEndpoint?.watchEndpoint != nil { //这个内容块是单曲/视频 item.videoId = run.navigationEndpoint?.watchEndpoint?.videoId item.itemType = .single }else { //这个内容块是艺术家/列表/专辑 item.browseId = run.navigationEndpoint?.browseEndpoint?.browseId let pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType if youTubeKeys.contains(pageType ?? "") == true { //判断细分 if pageType == "MUSIC_PAGE_TYPE_ARTIST" { item.artistId = run.navigationEndpoint?.browseEndpoint?.browseId //是艺术家 item.itemType = .artist }else { //是列表/专辑 item.itemType = .list } } } }) } //设置第一个内容块的二级标题 if let subtitle = musicCardShelfRenderer.subtitle { item.subtitle = subtitle.runs?.reduce("", { $0 + ($1.text ?? "")}) } //第一个内容块填充完毕 if item.itemType != nil { resultList.previewItemViews.append(.init(item)) } //设置最佳结果的其他内容块 if let contents = musicCardShelfRenderer.contents { contents.forEach { content in //生成更多内容块 let item = MPPositive_BrowseItemModel() if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, (musicResponsiveListItemRenderer.playlistItemData != nil || musicResponsiveListItemRenderer.navigationEndpoint != nil) && (musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE"){ //设置预览图 item.coverUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) //设置一级标题与二级标题 for (index, flexColumn) in (musicResponsiveListItemRenderer.flexColumns ?? []).enumerated() { if index == 0 { //一级标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else { //二级标题 item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } item.playListId = musicResponsiveListItemRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId //设置id和类型 if musicResponsiveListItemRenderer.playlistItemData != nil { //是单曲 item.itemType = .single item.videoId = musicResponsiveListItemRenderer.playlistItemData?.videoId }else { //是专辑列表/艺术家 item.browseId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId let pageType = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //判断细分 if pageType == "MUSIC_PAGE_TYPE_ARTIST" { item.artistId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId //是艺术家 item.itemType = .artist }else { //是列表/专辑 item.itemType = .list } } } } if item.title != nil && item.itemType != nil, item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { resultList.previewItemViews.append(.init(item)) } } } //由于是最佳结果模块,所以没有query和params值 resultList.query = nil resultList.params = nil }else if let musicShelfRenderer = content.musicShelfRenderer { //当前是其他结果,不存在首选内容块 //设置模块标题 resultList.title = musicShelfRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //设置内容块 musicShelfRenderer.contents?.forEach({ content in //专辑/列表/单曲视频/艺术家 let item = MPPositive_BrowseItemModel() if (content.musicResponsiveListItemRenderer?.playlistItemData != nil || content.musicResponsiveListItemRenderer?.navigationEndpoint != nil) && (content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE") { //设置内容块的封面 item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) //设置一级标题与二级标题 for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() { if index == 0 { //一级标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) item.pageType = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType }else { //二级标题 item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId //设置id和类型 if content.musicResponsiveListItemRenderer?.playlistItemData != nil { //是单曲 item.itemType = .single item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId }else { //是专辑列表/艺术家 item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //判断细分 if pageType == "MUSIC_PAGE_TYPE_ARTIST" { item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId //是艺术家 item.itemType = .artist }else { //是列表/专辑 item.itemType = .list } } } } if item.itemType != nil, item.title != nil, item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { resultList.previewItemViews.append(.init(item)) } }) //设置query和params值 if let searchEndpoint = musicShelfRenderer.bottomEndpoint?.searchEndpoint { resultList.query = searchEndpoint.query resultList.params = searchEndpoint.params } } resultListSections.append(resultList) } resultListSections.forEach { list in list.previewItemViews = list.previewItemViews.filter({ item in return item.item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" }) } resultListSections = resultListSections.filter({$0.previewItemViews.count != 0}) return resultListSections } ///解析搜索分页结果 private func parsingSearchTypeResults(_ musicShelfRenderer:JsonSearchTypeResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content.MusicShelfRenderer) -> ([MPPositive_SearchResultItemViewModel], String?, String?) { var array:[MPPositive_SearchResultItemViewModel] = [] if let contents = musicShelfRenderer.contents { ///解析内容块 contents.forEach({ content in //专辑/列表/单曲视频/艺术家 let item = MPPositive_BrowseItemModel() if (content.musicResponsiveListItemRenderer?.playlistItemData != nil || content.musicResponsiveListItemRenderer?.navigationEndpoint != nil) && (content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE") { //设置内容块的封面 item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) //设置一级标题与二级标题 for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() { if index == 0 { //一级标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else { //二级标题 item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId //设置id和类型 if content.musicResponsiveListItemRenderer?.playlistItemData != nil { //是单曲 item.itemType = .single item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId }else { //是专辑列表/艺术家 item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //判断细分 if pageType == "MUSIC_PAGE_TYPE_ARTIST" { item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId //是艺术家 item.itemType = .artist }else { //是列表/专辑 item.itemType = .list } } } //添加内容块 if item.itemType != nil { array.append(.init(item)) } } }) } var continuation:String? var clickTrackingParams:String? if let continuations = musicShelfRenderer.continuations?.first { ///解析继续编码 continuation = continuations.nextContinuationData?.continuation clickTrackingParams = continuations.nextContinuationData?.clickTrackingParams } array = array.filter({$0.item.itemType != nil}) return (array, continuation, clickTrackingParams) } ///解析搜索分页继续 private func parsingSearchTypeContinuation(_ musicShelfContinuation:JsonSearchTypeContinuation.ContinuationContents.MusicShelfContinuation) -> ([MPPositive_SearchResultItemViewModel], String?, String?) { var array:[MPPositive_SearchResultItemViewModel] = [] if let contents = musicShelfContinuation.contents { ///解析内容块 contents.forEach({ content in //专辑/列表/单曲视频/艺术家 let item = MPPositive_BrowseItemModel() //设置内容块的封面 item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) //设置一级标题与二级标题 for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() { if index == 0 { //一级标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else { //二级标题 item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId //设置id和类型 if content.musicResponsiveListItemRenderer?.playlistItemData != nil { //是单曲 item.itemType = .single item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId }else { //是专辑列表/艺术家 item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //判断细分 if pageType == "MUSIC_PAGE_TYPE_ARTIST" { item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId //是艺术家 item.itemType = .artist }else { //是列表/专辑 item.itemType = .list } } } //添加内容块 if item.itemType != nil { array.append(.init(item)) } }) } var continuation:String? var clickTrackingParams:String? if let continuations = musicShelfContinuation.continuations?.first { ///解析继续编码 continuation = continuations.nextContinuationData?.continuation clickTrackingParams = continuations.nextContinuationData?.clickTrackingParams } array = array.filter({$0.item.itemType != nil}) return (array, continuation, clickTrackingParams) } //MARK: - 解析具体内容形式 //解析musicResponsiveListItemRenderer(通常是音视频内容) private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel { //生成一个音乐模型接收数据 let item = MPPositive_BrowseItemModel() //预览图片路径 item.coverUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$0.url}) //设置主副标题 if let flexColumns = musicResponsiveListItemRenderer.flexColumns { for (index,flexColumn) in flexColumns.enumerated() { if index == 0 { //添加主标题 item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) item.playListId = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.watchEndpoint?.playlistId }else{ item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "") } } } if item.playListId == nil { //通过操作菜单获取playListId item.playListId = musicResponsiveListItemRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId } //判断这个内容是音视频还是专辑列表艺术家 if let playlistItemData = musicResponsiveListItemRenderer.playlistItemData { //是音视频 item.itemType = .single item.videoId = playlistItemData.videoId item.pageType = "MUSIC_VIDEO_TYPE_ATV" } if let navigationEndpoint = musicResponsiveListItemRenderer.navigationEndpoint { //是专辑列表艺术家 if navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" && navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //是艺术家 item.itemType = .artist item.artistId = navigationEndpoint.browseEndpoint?.browseId item.pageType = "MUSIC_PAGE_TYPE_ARTIST" }else { if navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" { //是专辑列表 item.itemType = .list item.pageType = navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType item.browseId = navigationEndpoint.browseEndpoint?.browseId item.params = navigationEndpoint.browseEndpoint?.params } } } return item } //解析musicResponsiveListItemRenderer(是列表专辑内容) private func parsingMusicTwoRowItemRenderer(_ musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer) -> MPPositive_BrowseItemModel { //生成一个音乐模型接收数据 let item = MPPositive_BrowseItemModel() //封面路径 item.coverUrls = musicTwoRowItemRenderer.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$0.url}) //标题 item.title = musicTwoRowItemRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //副标题 item.subtitle = musicTwoRowItemRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")}) //判断这个内容是音视频还是专辑列表艺术家 if let watchEndpoint = musicTwoRowItemRenderer.navigationEndpoint?.watchEndpoint { //是音视频 item.itemType = .single item.videoId = watchEndpoint.videoId item.playListId = watchEndpoint.playlistId item.pageType = watchEndpoint.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType } if let browseEndpoint = musicTwoRowItemRenderer.navigationEndpoint?.browseEndpoint { //是列表专辑艺术家 if browseEndpoint.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" { //是艺术家 item.itemType = .artist item.artistId = browseEndpoint.browseId item.pageType = "MUSIC_PAGE_TYPE_ARTIST" }else { //是专辑列表 item.itemType = .list item.pageType = browseEndpoint.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType item.browseId = browseEndpoint.browseId item.params = browseEndpoint.params } } return item } } //MARK: - 解析资源加密 extension MP_NetWorkManager { ///百分比解码 private func seperatorOff(_ encodedString: String) -> String { guard let decodedString = encodedString.removingPercentEncoding else { print("百分比解码失败") return "" } return decodedString } } //MARK: - 自动重试 ///重试策略类 class MP_CustomRetrier: RequestInterceptor { // 追踪重试次数的字典 private var retryCounts: [String: Int] = [:] // 最大重试次数 private let maxRetryCount: Int = 3 // 重试间隔时间 private let retryInterval: TimeInterval = 1.0 //重试策略 func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) { // 检查请求是否具有URL以便于在字典中进行追踪 guard let url = request.request?.url?.absoluteString else { completion(.doNotRetry) return } // 获取这个 URL 当前的重试次数 let currentRetryCount = retryCounts[url] ?? 0 // 如果未超过最大重试次数,那么决定重试 if currentRetryCount < maxRetryCount { // 更新重试次数 retryCounts[url] = currentRetryCount + 1 print("进行对\(url)访问的第\(currentRetryCount)次重试") // 告诉 Alamofire 重试并包含间隔时间 completion(.retryWithDelay(retryInterval)) } else { // 如果达到最大重试次数,则不再重试 completion(.doNotRetry) print("访问\(url)失败,不在进行重试") // // 移除追踪记录 // retryCounts[url] = nil } } }