// // 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: - API接口 ///域名链接 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" //MARK: - 固定参数 //访问数据(首次首页预览时获得) private var visitorData: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 } //生成新参数 var parameters:[String:Any] = [ "ctoken":continuation, "continuation":continuation, "type":"next", "itct":itct, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] //执行异步请求 browseQueque?.async { [weak self] in guard let self = self else { return } requestPostHomeBrowse(url, parameters: parameters) } } } //MARK: - 异步队列 //串行队列-预览 private var browseQueque:DispatchQueue? //MARK: - 闭包 ///预览闭包(传递一个预览模块数据和完成状态) var browseRequestStateBlock:BrowseRequestStateBlock? ///列表专辑闭包(传递一个请求完成的列表数据) var listRequestResultBlock:ListRequestResultBlock? //私有初始化 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: "”Musicoo“ 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) } } //MARK: - API请求 extension MP_NetWorkManager { ///向YouTubemusic请求预览/首页数据 func requestBrowseDatas() { //实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列 // 实例化串行队列 browseQueque = DispatchQueue(label: "com.request.browseQueque") //拼接出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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] requestPostHomeBrowse(url, parameters: parameters) } } //请求首页预览内容(执行多次) private func requestPostHomeBrowse(_ url:URL, parameters:Parameters) { //发送post请求,并将结果转为RootBrowses AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonBrowses.self) { [weak self] (response) in guard let self = self else {return} if let task = response.request { print("URL: \(task.url!)") } 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): // 请求失败,处理错误 print("Request failed: \(error)") } } } /// 向YouTubemusic请求列表/专辑数据,该接口调用的同样是browse预览接口 /// - Parameters: /// - item: 需要查看的模块 func requestAlbumOrListDatas(_ item: MPPositive_BrowseItemViewModel) { //拼接出browse路径 let path = header+point+browse //设置url guard let url = URL(string: path) else { print("Url is Incorrect") return } //设置参数,browseId与params参数是必定携带内容 var parameters:[String:Any] = [ "browseId":(item.browseItem.browseContent.browseId ?? ""), "params":(item.browseItem.browseContent.params ?? ""), "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] requestPostAlbumOrList(url, parameters: parameters) } //请求列表/专辑数据 private func requestPostAlbumOrList(_ url:URL, parameters:Parameters) { //发送post请求,并将结果转为RootBrowses AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonListOrAlbum.self) { [weak self] (response) in guard let self = self else {return} if let task = response.request { print("URL: \(task.url!)") } 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)) } //传递列表值 guard listRequestResultBlock != nil else { return } listRequestResultBlock!(list) case .failure(let error): // 请求失败,处理错误 print("Request failed: \(error)") } } } ///请求Next列表(优先于Player) /// - Parameter item: 请求的预览实体 func requestNextList(_ item: MPPositive_BrowseItemViewModel, 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参数是必定携带内容 var parameters:[String:Any] = [ "playlistId":(item.browseItem.musicVideo.playListId ?? ""), "videoId":(item.browseItem.musicVideo.videoId ?? ""), "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] //发送next列表请求 requestPostNextList(url, parameters: parameters) { listSongs in //成功拿到列表所有歌曲(内容尚不完善) completion(listSongs) } } ///请求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参数是必定携带内容 var parameters:[String:Any] = [ "videoId":(item.videoId ?? ""), "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] //发送next列表歌词/相关内容请求 requestPostNextLyricsAndRelated(url, parameters: parameters) { result in completion(result) } } //请求next列表 private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) { //发送post请求 AF.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): // 请求失败,处理错误 print("Request failed: \(error)") } } } //请求请求Next歌词/相关内容 private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) { //发送post请求 AF.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): // 请求失败,处理错误 print("Request failed: \(error)") } } } /// 请求Player(单曲/视频)播放资源 /// - Parameter item: 请求的预览实体 func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping (([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] = [ // "playlistId":(item.browseItem.musicVideo.playListId ?? ""), "videoId":(item.videoId ?? ""), "prettyPrint":"false", "context":[ "client":[ //当前访问版本(日期值) "clientName": "WEB_REMIX", "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00" ] ], "playbackContext": [ "contentPlaybackContext": [ "signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0 ] ] ] requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in completion(resourceUlrs, coverUrls) } } //请求单曲/视频 private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping(([String]?, [String]?) -> Void)) { //发送post请求 AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in guard let self = self else {return} if let task = response.request { print("URL: \(task.url!)") } switch response.result { case .success(let value): parsingPlayer(value) { resourceUlrs, coverUrls in completion(resourceUlrs, coverUrls) } case .failure(let error): // 请求失败,处理错误 print("Request failed: \(error)") } } } /// 请求歌词 /// - 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参数是必定携带内容 var parameters:[String:Any] = [ "browseId":lyricId, "prettyPrint":"false", "context":[ "client":[ //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, //地址 "gl":Location_First ] ] ] requestPostLyric(url, parameters: parameters) { lyrics in completion(lyrics) } } //请求歌词 private func requestPostLyric(_ url:URL, parameters:Parameters, completion:@escaping((String) -> Void)) { //发送post请求 AF.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): // 请求失败,处理错误 print("Request failed: \(error)") } } } } //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 } ///解析相关内容_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() { //生成一个音乐实体,用来装填部分数据 let song = MPPositive_SongItemModel() song.index = index song.title = content.playlistPanelVideoRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.longBylineText = content.playlistPanelVideoRenderer?.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.lengthText = content.playlistPanelVideoRenderer?.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.shortBylineText = content.playlistPanelVideoRenderer?.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.reviewUrls = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) song.videoId = content.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]?, [String]?) -> Void)){ var infos:[String]? //解析player,获取资源库和信息库 if let videoDetails = player.videoDetails { infos = parsingPlayerVideoDetails(videoDetails) } if let streamingData = player.streamingData { parsingPlayerStreamingData(streamingData){ urls in completion(urls,infos) } } } /// 解析播放器_StreamingData /// - Parameter streamingData: 资源库 private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String]) -> Void)) { var group:DispatchGroup? = DispatchGroup() var array:[String] = [] let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? []) for format in allFormats { if let signatureCipher = format.signatureCipher { // 进入DispatchGroup,表示开始一个异步任务 group?.enter() //获得资源签名,开始解密签名内容 parsingPlayerSignatureCipher(signatureCipher) { result in array.append(result) // 离开DispatchGroup,表示异步任务完成 group?.leave() } } } group?.notify(queue: .main) { completion(array) 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.. [String]? { var urls:[String]? videoDetails.thumbnail?.thumbnails?.forEach({ item in if item.url != nil { if urls != nil { urls!.append(item.url!) }else { urls = [] urls!.append(item.url!) } } }) return urls } ///解析歌词_Lyrics private func parsingLyrics(_ lyrics:JsonLyrics) -> String? { if let first = lyrics.contents?.sectionListRenderer?.contents?.first { return first.musicDescriptionShelfRenderer?.description?.runs?.first?.text } return "" } //MARK: - 解析具体内容形式 //解析musicResponsiveListItemRenderer(单曲/视频) private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel { //生成一个音乐模型接收数据 let item = MPPositive_BrowseItemModel() item.itemType = .single //封面路径 item.coverUrl = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url if let flexColumns = musicResponsiveListItemRenderer.flexColumns { for (index,flexColumn) in flexColumns.enumerated() { if index == 0 { item.maintitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else if index == 1 { item.subtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) }else { item.thirdtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) } var browseContent = BrowseItemContent() flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.forEach({ run in if run.navigationEndpoint?.browseEndpoint?.browseId != nil { //获取到艺术家的ID item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId } if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" { //专辑或则歌单 browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.params = run.navigationEndpoint?.browseEndpoint?.params } // if run.navigationEndpoint?.browseEndpoint?.browseId != nil { // browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId // } // if run.navigationEndpoint?.browseEndpoint?.params != nil { // browseContent.params = run.navigationEndpoint?.browseEndpoint?.params // } }) item.browseContent = browseContent } } //设置音乐id和列表id if let watch = musicResponsiveListItemRenderer.overlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchEndpoint { var musicVideo = BrowseItemMusicVideo() musicVideo.videoId = watch.videoId musicVideo.playListId = watch.playlistId musicVideo.musicVideoType = watch.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType item.musicVideo = musicVideo } return item } //解析musicResponsiveListItemRenderer(list) private func parsingMusicTwoRowItemRenderer(_ musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer) -> MPPositive_BrowseItemModel { //生成一个音乐模型接收数据 let item = MPPositive_BrowseItemModel() item.itemType = .list //封面路径 item.coverUrl = musicTwoRowItemRenderer.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url //标题 item.maintitle = musicTwoRowItemRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) //副标题 item.subtitle = musicTwoRowItemRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")}) var browseContent = BrowseItemContent() musicTwoRowItemRenderer.title?.runs?.forEach({ run in if run.navigationEndpoint?.browseEndpoint?.browseId != nil { //获取到艺术家的ID item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId } if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" { //专辑或则歌单 browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.params = run.navigationEndpoint?.browseEndpoint?.params } // if run.navigationEndpoint?.browseEndpoint?.browseId != nil { // browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId // } // if run.navigationEndpoint?.browseEndpoint?.params != nil { // browseContent.params = run.navigationEndpoint?.browseEndpoint?.params // } }) musicTwoRowItemRenderer.subtitle?.runs?.forEach({ run in if run.navigationEndpoint?.browseEndpoint?.browseId != nil { //获取到艺术家的ID item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId } if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" { //专辑或则歌单 browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.params = run.navigationEndpoint?.browseEndpoint?.params } // if run.navigationEndpoint?.browseEndpoint?.browseId != nil { // browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId // } // if run.navigationEndpoint?.browseEndpoint?.params != nil { // browseContent.params = run.navigationEndpoint?.browseEndpoint?.params // } }) item.browseContent = browseContent if let playListId = musicTwoRowItemRenderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchPlaylistEndpoint?.playlistId { //设置列表ID var musicVideo = BrowseItemMusicVideo() musicVideo.playListId = playListId item.musicVideo = musicVideo } if let videoId = musicTwoRowItemRenderer.navigationEndpoint?.watchEndpoint?.videoId { //是视频 var musicVideo = BrowseItemMusicVideo() musicVideo.videoId = videoId item.musicVideo = musicVideo item.itemType = .single } return item } } //MARK: - 解析资源加密 extension MP_NetWorkManager { ///百分比解码 private func seperatorOff(_ encodedString: String) -> String { guard let decodedString = encodedString.removingPercentEncoding else { print("百分比解码失败") return "" } return decodedString } }