2540 lines
128 KiB
Swift
2540 lines
128 KiB
Swift
//
|
||
// MPNetWorkManager.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/4/11.
|
||
//
|
||
|
||
import UIKit
|
||
import Network
|
||
import Alamofire
|
||
import AVFoundation
|
||
import Kanna
|
||
///预览闭包(传递一个预览模块数据组和完成状态)
|
||
typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel]) -> Void
|
||
///预览数据失败闭包
|
||
typealias BrowseRequestErrorBlock = () -> Void
|
||
|
||
///列表/专辑闭包(传递一个列表数据)
|
||
typealias ListRequestResultBlock = (_ list:MPPositive_ListAlbumListViewModel) -> Void
|
||
///首页分页
|
||
struct ContinuationAndItct {
|
||
///续页编码
|
||
var continuation:String
|
||
///续页定值
|
||
var itct:String
|
||
}
|
||
///网页API密钥
|
||
struct NetInnertube: Codable {
|
||
let INNERTUBE_API_KEY: String
|
||
}
|
||
///网络状况管理器
|
||
class MP_NetWorkManager: NSObject {
|
||
//单例工具
|
||
static let shared = MP_NetWorkManager()
|
||
//MARK: - 网络请求会话
|
||
///IP信息加载会话实例
|
||
private lazy var IPSession:Session = {
|
||
let configuration = URLSessionConfiguration.af.default
|
||
configuration.timeoutIntervalForRequest = 8
|
||
configuration.timeoutIntervalForResource = 8
|
||
// 可调整网络服务类型
|
||
configuration.networkServiceType = .default
|
||
// 可调整缓存策略
|
||
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||
let seesion = Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
|
||
return seesion
|
||
}()
|
||
///预览资源加载会话实例
|
||
private lazy var MPSession:Session = {
|
||
let configuration = URLSessionConfiguration.af.default
|
||
configuration.timeoutIntervalForRequest = 20
|
||
configuration.timeoutIntervalForResource = 20
|
||
// 可调整网络服务类型
|
||
configuration.networkServiceType = .default
|
||
// 可调整缓存策略
|
||
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||
let seesion = Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
|
||
return seesion
|
||
}()
|
||
///播放资源加载会话实例
|
||
private lazy var PlayerSeesion:Session = {
|
||
let configuration = URLSessionConfiguration.af.default
|
||
configuration.timeoutIntervalForRequest = 20
|
||
configuration.timeoutIntervalForResource = 20
|
||
//最多同时执行4条
|
||
configuration.httpMaximumConnectionsPerHost = 4
|
||
return Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
|
||
}()
|
||
///播放相关内容ID和歌词ID记录组
|
||
private var relatedRequests: [String:DataRequest] = [:]
|
||
///播放资源请求记录组
|
||
private var playerRequests: [String:DataRequest] = [:]
|
||
|
||
//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"]
|
||
///允许访问Youtube音乐的地区Code
|
||
private let ISOs:[String] = ["DZ","LB","AS","LY","AR","LI","AW","LT","AU","LU","AT","MY","AZ","MT","BH","MX","BD","MA","BY","NP","BE","NL","BM","NZ","BO","NI","BA","NG","BR","MK","BG","MP","KH","NO","CA","OM","KY","PK","CL","PA","CO","PG","CR","PY","HR","PE","CY","PH","CZ","PL","DK","PT","DO","PR","EC","QA","EG","RE","SV","RO","EE","FI","SA","FR","SN","GF","RS","PF","SG","GE","SK","DE","SI","GH","ZA","GR","KR","GP","ES","GU","LK","GT","SE","HN","CH","HK","TW","HU","TZ","IS","TH","IN","TN","ID","IQ","TC","IE","VI","IL","UG","IT","UA","JM","AE","JP","GB","JO","US","KZ","UY","KE","VE","KW","VN","LA","YE","LV","ZW"]
|
||
///允许访问排行榜的区域Code(对部分内容进行塞选)
|
||
private let codes:[String] = [
|
||
"CL","GB","ID","IN","IT","IL","HU","NZ","ES","UY","UA","UG","GT","TR","TZ","SA","RS","SV","CH","SE","JP","PT","NO","NG","NI","ZA","MX","PE","US","RO","LU","KE","ZW","CZ","CA","HN","NL","KR","CR","CO","FI","FR","EC","RU","DO","DK","BO","PL","IS","BE","BR","PA","PY","AU","AT","EE","IE","EG","AE","AR","ZZ"
|
||
]
|
||
|
||
|
||
///禁止关键词
|
||
private var trashKeyWords:[String] = []
|
||
///设置禁止关键词
|
||
func setTrashKeyWords(_ words:[String]){
|
||
trashKeyWords = words
|
||
}
|
||
///禁止歌手(id组)
|
||
private var trashSingerIds:[String] = []
|
||
///设置禁用歌手
|
||
func setTrashSingerIds(_ ids:[String]) {
|
||
trashSingerIds = ids
|
||
}
|
||
///禁止歌曲(id组)
|
||
private var trashVideoIds:[String] = []
|
||
///设置禁用歌曲
|
||
func setTrashVideoIds(_ ids:[String]) {
|
||
trashVideoIds = ids
|
||
}
|
||
///禁止下载
|
||
private var isAllowedDownload:Bool = true
|
||
//网络状态
|
||
enum NetWorkStatus: String {
|
||
case notReachable = "网络不可用"
|
||
case unknown = "网络未知"
|
||
case reachable = "网络可用"
|
||
}
|
||
///网络监听器
|
||
private var monitor:NWPathMonitor
|
||
///对监听器的判断
|
||
private var isReach:Bool = false
|
||
///当前网络状态
|
||
var netWorkStatu:NetWorkStatus!{
|
||
willSet{
|
||
guard newValue != netWorkStatu else {
|
||
//重复情况
|
||
return
|
||
}
|
||
switch newValue {
|
||
case .reachable://网络可用
|
||
print("网络可用")
|
||
NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable)
|
||
case .notReachable://不可用
|
||
print("网络不可用")
|
||
NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable)
|
||
case .unknown://位置情况
|
||
print("网络状况未知")
|
||
case .none:
|
||
break
|
||
}
|
||
}
|
||
}
|
||
//MARK: - 固定参数
|
||
///预览内容键值
|
||
private var browseContext:[String:Any]{
|
||
let client = ["client":browseClient]
|
||
return ["context":client,
|
||
"key":netKeyCode]
|
||
}
|
||
///预览访客键值
|
||
private var browseClient:[String:String]{
|
||
if let text = visitorData, text.isEmpty == false {
|
||
//具备游客数据且游客数据字符不为空
|
||
return ["clientName":"WEB_REMIX",
|
||
"clientVersion":"1.\(currTimeDate).01.00",
|
||
"hl":Language_first_local,
|
||
"gl":locaton ?? "US",
|
||
"visitorData":text]
|
||
}else {
|
||
//没有访客数据
|
||
return ["clientName":"WEB_REMIX",
|
||
"clientVersion":"1.\(currTimeDate).01.00",
|
||
"hl":Language_first_local,
|
||
"gl":locaton ?? "US"]
|
||
}
|
||
}
|
||
|
||
///播放资源键值(可远程配置)
|
||
private var resourceContext:[String:Any]{
|
||
if let playerContextVersion = UserDefaults.standard.object(forKey: "playerContextVersion") as? [String:Any] {
|
||
return playerContextVersion
|
||
}else {
|
||
return [
|
||
"context":[
|
||
"client":[
|
||
"clientName": "ANDROID",
|
||
"clientVersion": "19.05.36",
|
||
"hl": "en",
|
||
"gl": "US"
|
||
]
|
||
],
|
||
"params": "CgIQBg",
|
||
"key":netKeyCode
|
||
]
|
||
}
|
||
}
|
||
|
||
///游客访问数据(首次首页预览时获得)
|
||
private var visitorData:String?{
|
||
if let text = UserDefaults.standard.string(forKey: "Visitor_Data") {
|
||
return text
|
||
}else {
|
||
return nil
|
||
}
|
||
}
|
||
///网页API密钥
|
||
private var netKeyCode:String{
|
||
if let key = UserDefaults.standard.string(forKey: "YBMNetKeyCode") {
|
||
return key
|
||
}else {
|
||
//无数据的情况下,默认使用
|
||
return "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||
}
|
||
}
|
||
|
||
//固定时间点(当前日期的前两天)
|
||
private var currTimeDate:String{
|
||
return (Date().timeZone() - 1.days).toString(.custom("YYYYMMdd"))
|
||
}
|
||
///地址
|
||
private var locaton:String? = "US"
|
||
//预览下一阶段参数(网络请求获取)
|
||
var continuationAndItct:ContinuationAndItct?
|
||
//MARK: - GCD队列任务
|
||
///网络检测队列
|
||
private let MPNetWorkqueue = DispatchQueue(label: "MPNetWorkManager")
|
||
///A/B任务处理队伍
|
||
private var executionQueue: [() -> Void] = []
|
||
///并发队列-单曲资源预加载
|
||
var playerItemLoadingGroup:DispatchGroup = DispatchGroup()
|
||
//MARK: - 闭包
|
||
///预览闭包(传递一个预览模块数据和完成状态)
|
||
var browseRequestStateBlock:BrowseRequestStateBlock?
|
||
///预览失败闭包
|
||
var browseRequestErrorBlock:BrowseRequestErrorBlock?
|
||
//私有初始化
|
||
private override init() {
|
||
self.monitor = NWPathMonitor()
|
||
super.init()
|
||
}
|
||
///更新网页API密钥
|
||
func requestYBMAPIKeyCode() {
|
||
AF.request("https://www.youtube.com").responseString { [weak self] response in
|
||
guard let self = self else { return }
|
||
if let html = response.value {
|
||
if let doc = try? HTML(html: html, encoding: .utf8) {
|
||
if let text = doc.xpath("//script[contains(., 'INNERTUBE_API_KEY')]/text()").first?.text {
|
||
if let results = text.textRegular(with: "ytcfg.set\\((\\{.*?\\})\\)").last?.last {
|
||
if let data = results.data(using: .utf8), let model = try? JSONDecoder().decode(NetInnertube.self, from: data) {
|
||
let key = model.INNERTUBE_API_KEY
|
||
if key.isEmpty == false {
|
||
print("更新了网页API密钥")
|
||
//保存密钥
|
||
UserDefaults.standard.set(key, forKey: "YBMNetKeyCode")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//MARK: - 网络情况
|
||
///检查网络状况
|
||
func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) {
|
||
switch netWorkStatu {
|
||
case .reachable:
|
||
DispatchQueue.main.async {
|
||
guard completeHanlder != nil else {
|
||
return
|
||
}
|
||
completeHanlder!()
|
||
}
|
||
default:
|
||
DispatchQueue.main.async {
|
||
//次要处理
|
||
let alertController = UIAlertController(title: "Access network request", message: "”HiMelody“ 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)
|
||
}
|
||
}
|
||
}
|
||
///网络请求检测
|
||
func requestStatusToYouTube() {
|
||
guard isReach == false else {
|
||
return
|
||
}
|
||
isReach = true
|
||
monitor.start(queue: MPNetWorkqueue)
|
||
monitor.pathUpdateHandler = { [weak self] path in
|
||
self?.updateNetworkStatus(path)
|
||
}
|
||
}
|
||
private func updateNetworkStatus(_ path: NWPath) {
|
||
if path.status == .satisfied {
|
||
netWorkStatu = .reachable
|
||
executePendingTasks()
|
||
} else {
|
||
netWorkStatu = .notReachable
|
||
}
|
||
}
|
||
//AB面检测队列任务
|
||
func performTaskNetWrokAvailable(_ task: @escaping() -> Void) {
|
||
//根据当前网络情况决定是执行任务,还是将任务放置到队列
|
||
if netWorkStatu == .reachable {
|
||
//网络可以执行任务
|
||
task()
|
||
}else {
|
||
//网络不可执行任务,放入队列
|
||
executionQueue.append(task)
|
||
}
|
||
}
|
||
//当网络恢复可用后立刻处理队列任务
|
||
private func executePendingTasks() {
|
||
while !executionQueue.isEmpty {
|
||
let task = executionQueue.removeFirst()
|
||
task()
|
||
}
|
||
}
|
||
}
|
||
//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)) {
|
||
IPSession.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
|
||
}
|
||
let ip = data.ip
|
||
UserDefaults.standard.set(ip, forKey: "IP_Info")
|
||
if ISOs.contains(code) {
|
||
locaton = code
|
||
}else {
|
||
locaton = "US"
|
||
}
|
||
completion(true)
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error)
|
||
//请求失败默认为US
|
||
locaton = "US"
|
||
completion(true)
|
||
}
|
||
}
|
||
}
|
||
|
||
//MARK: - 请求首页预览
|
||
///向YouTubemusic请求预览/首页数据
|
||
func requestBrowseDatas() {
|
||
//实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//进行第一次请求,有预览ID,无continuation编码
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = "FEmusic_home"
|
||
//guard netWorkStatu != .notReachable else {return}
|
||
requestPostHomeBrowse(url, parameters: parameters)
|
||
}
|
||
//更多请求
|
||
func requestContinuationBrowseDatas() {
|
||
//当获取新值后,判断新值是否存在,存在则继续串行异步请求
|
||
guard let continuation = continuationAndItct?.continuation, let itct = continuationAndItct?.itct, let url = URL(string: header+point+browse) else {
|
||
//首页基础数据已经获取完毕,不需要继续调用接口拉取数据
|
||
print("首页数据已经加载完毕")
|
||
NotificationCenter.notificationKey.post(notificationName: .positive_browses_completion)
|
||
return
|
||
}
|
||
//生成新参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["ctoken"] = continuation
|
||
parameters["continuation"] = continuation
|
||
parameters["type"] = "next"
|
||
parameters["itct"] = itct
|
||
//执行异步请求
|
||
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 let data = value.responseContext?.visitorData {
|
||
if data != self.visitorData {
|
||
print("Visitor_Data更改了")
|
||
UserDefaults.standard.set(data, forKey: "Visitor_Data")
|
||
}
|
||
}
|
||
//解析结构体
|
||
let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0]
|
||
if let content = tab?.tabRenderer?.content {
|
||
parsingBrowseContents(content)
|
||
//同时调用charts和艺术家排行接口
|
||
requestArtistsRank()
|
||
requestChartsUS()
|
||
}else if let continuationContents = value.continuationContents {
|
||
parsingBrowseContinuationContents(continuationContents)
|
||
}else {
|
||
//不在服务范围内
|
||
print("Failed to parse browses content")
|
||
// 请求失败,处理错误
|
||
homeError(error: nil)
|
||
}
|
||
case .failure(let error):
|
||
print("Failed to parse browses content")
|
||
// 请求失败,处理错误
|
||
homeError(error: error)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
///执行艺术家排行请求
|
||
func requestArtistsRank() {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//异步执行任务
|
||
var code = locaton ?? "US"
|
||
if codes.contains(code) == false {
|
||
//没有包含在内,默认替换为US美国国家码
|
||
code = "US"
|
||
}
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = "FEmusic_charts"
|
||
let formData = ["selectedValues":[code]]
|
||
parameters["formData"] = formData
|
||
|
||
requestPostArtistsRank(url, parameters: parameters)
|
||
}
|
||
///请求艺术家排行
|
||
private func requestPostArtistsRank(_ url:URL, parameters:Parameters) {
|
||
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonCharts.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let charts):
|
||
if let content = charts.contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
|
||
parsingCharts(content)
|
||
}
|
||
case .failure(let error):
|
||
print("Failed to parse browses content")
|
||
// 请求失败,处理错误
|
||
homeError(error: error)
|
||
}
|
||
}
|
||
}
|
||
///请求歌单排行榜(美国限定)
|
||
func requestChartsUS() {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = "FEmusic_charts"
|
||
let formData = ["selectedValues":["US"]]
|
||
parameters["formData"] = formData
|
||
requestPostChartsUS(url, parameters: parameters)
|
||
}
|
||
///请求最后的排行数据
|
||
private func requestPostChartsUS(_ url:URL, parameters:Parameters) {
|
||
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonCharts.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let charts):
|
||
if let content = charts.contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
|
||
parsingChartsUS(content)
|
||
}
|
||
case .failure(let error):
|
||
print("Failed to parse browses content")
|
||
// 请求失败,处理错误
|
||
homeError(error: error)
|
||
}
|
||
}
|
||
}
|
||
//MARK: - 请求列表专辑预览
|
||
/// 向YouTubemusic请求列表/专辑数据,该接口调用的同样是browse预览接口
|
||
/// - Parameters:
|
||
/// - browseId: 专辑列表的Id
|
||
/// - params: 专辑列表的访问编码
|
||
func requestAlbumOrListDatas(_ browseId: String, params: String, clickTrackingParams: 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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = browseId
|
||
parameters["params"] = params
|
||
// //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 = parsingListSingleContents(content)
|
||
if let header = value.header {
|
||
list.header = .init(parsingListSingleHeaders(header))
|
||
}
|
||
//列表数据完善,回调
|
||
comletion(list)
|
||
}else if let content = contents?.twoColumnBrowseResultsRenderer?.secondaryContents {//使用二号版本
|
||
list.items = parsingListTwoContents(content)
|
||
if let musicResponsiveHeaderRenderer = contents?.twoColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.first?.musicResponsiveHeaderRenderer {
|
||
list.header = .init(parsingListTwoHeaders(musicResponsiveHeaderRenderer))
|
||
}
|
||
//列表数据完善,回调
|
||
comletion(list)
|
||
}
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error)
|
||
}
|
||
}
|
||
}
|
||
///请求艺术家
|
||
func requestArtist(_ browseId:String, clickTrackingParams: 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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = browseId
|
||
//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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = browseId
|
||
parameters["params"] = params
|
||
|
||
//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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["continuation"] = continuation
|
||
parameters["ctoken"] = continuation
|
||
parameters["type"] = "next"
|
||
parameters["itct"] = itct
|
||
|
||
//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)
|
||
}
|
||
}
|
||
}
|
||
///请求模块数据
|
||
func requestGenres(_ completion:@escaping (([MPPositive_GridViewModel]) -> Void)) {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = "FEmusic_moods_and_genres"
|
||
|
||
requestPostGenres(url, parameters: parameters) { array in
|
||
completion(array)
|
||
}
|
||
}
|
||
///请求模块数据
|
||
private func requestPostGenres(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_GridViewModel]) -> Void)) {
|
||
//发送post请求
|
||
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonGenres.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let value):
|
||
if let contents = value.contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
|
||
let array = parsingGenres(contents)
|
||
completion(array)
|
||
}
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error)
|
||
}
|
||
}
|
||
}
|
||
///请求模块详情数据
|
||
func requestMoodDetails(_ browseId:String, params:String, completion:@escaping (([MPPositive_BrowseModuleListViewModel]) -> Void)) {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = browseId
|
||
parameters["params"] = params
|
||
|
||
requestPostMoodDetails(url, parameters: parameters) { array in
|
||
completion(array)
|
||
}
|
||
}
|
||
///请求模块详情数据
|
||
private func requestPostMoodDetails(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_BrowseModuleListViewModel]) -> Void)) {
|
||
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):
|
||
//解析结构体
|
||
let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0]
|
||
if let content = tab?.tabRenderer?.content {
|
||
let array = parsingMoodDetails(content)
|
||
completion(array)
|
||
}
|
||
case .failure(let error):
|
||
print("Failed to parse browses content")
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error)
|
||
}
|
||
}
|
||
}
|
||
|
||
//MARK: - 请求列表专辑下一部分
|
||
///请求Next列表(优先于Player)
|
||
/// - Parameter item: 请求的预览实体
|
||
func requestNextList(_ browseId:String, videoId:String, clickTrackingParams: 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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["playlistId"] = browseId
|
||
parameters["videoId"] = videoId
|
||
|
||
//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)) {
|
||
guard netWorkStatu != .notReachable else {
|
||
completion((nil,nil))
|
||
return
|
||
}
|
||
//拼接出next路径
|
||
let path = header+point+next
|
||
//设置url
|
||
guard let url = URL(string: path), let videoId = item.videoId else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,videoId与params参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["videoId"] = videoId
|
||
|
||
//guard netWorkStatu != .notReachable else {return}
|
||
requestPostNextLyricsAndRelated(url, videoId: videoId, parameters: parameters) { result in
|
||
completion(result)
|
||
}
|
||
}
|
||
//请求请求Next歌词/相关内容
|
||
private func requestPostNextLyricsAndRelated(_ url:URL, videoId:String, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) {
|
||
//发送post请求
|
||
let request = MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
relatedRequests[videoId] = nil
|
||
switch response.result {
|
||
case .success(let value):
|
||
let result = parsingNextLyricsAndRelated(value)
|
||
//回掉数据
|
||
completion(result)
|
||
case .failure(let error):
|
||
//当前无数据
|
||
completion((nil,nil))
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error, status: false)
|
||
}
|
||
}
|
||
relatedRequests[videoId] = request
|
||
}
|
||
|
||
//MARK: - 请求player播放资源
|
||
/// 请求Player(单曲/视频)播放资源
|
||
/// - Parameter item: 请求的预览实体
|
||
func requestAndroidPlayer(_ videoId: String, playlistId: String, clickTrackingParams: String?, completion:@escaping ((([String],[Int],[String])?, [String]?) -> Void), failure: ((Bool) -> Void)? = nil){
|
||
guard netWorkStatu != .notReachable else {
|
||
completion(nil,nil)
|
||
return
|
||
}
|
||
//拼接出player路径
|
||
let path = header+point+player
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
completion(nil,nil)
|
||
return
|
||
}
|
||
//设置参数,videoId与params参数是必定携带内容
|
||
var parameters:[String:Any] = resourceContext
|
||
parameters["videoId"] = videoId
|
||
//guard netWorkStatu != .notReachable else {return}
|
||
requestAndroidPostPlayer(url, videoId: videoId, parameters: parameters){ resourceUlrs, coverUrls in
|
||
completion(resourceUlrs, coverUrls)
|
||
} failure: { statu in
|
||
failure?(statu)
|
||
}
|
||
}
|
||
private func requestAndroidPostPlayer(_ url:URL, videoId:String, parameters:Parameters, index:Int = 0, completion:@escaping((([String],[Int],[String])?, [String]?) -> Void), failure:((Bool) -> Void)? = nil) {
|
||
//发送post请求
|
||
let request = PlayerSeesion.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
|
||
if self.playerRequests[videoId] != nil {
|
||
self.playerRequests[videoId] = nil // 清除请求
|
||
}
|
||
//成功获得资源
|
||
completion(resourceUlrs, coverUrls)
|
||
} failure: { statu in
|
||
if self.playerRequests[videoId] != nil {
|
||
self.playerRequests[videoId] = nil // 清除请求
|
||
}
|
||
failure?(statu)
|
||
} resetion: {
|
||
//需要重启网络请求
|
||
if index < 3 {
|
||
print("对于歌曲:\(videoId),进行重复请求")
|
||
//可以继续请求
|
||
let next = index + 1
|
||
self.requestAndroidPostPlayer(url, videoId: videoId, parameters: parameters, index: next, completion: completion, failure: failure)
|
||
}else {
|
||
//重复请求超过限制了,不再重复请求,结束请求
|
||
print("对于歌曲:\(videoId),请求超限,不再继续")
|
||
if self.playerRequests[videoId] != nil {
|
||
self.playerRequests[videoId] = nil // 清除请求
|
||
}
|
||
failure?(false)
|
||
self.setNumbersFailures()
|
||
}
|
||
}
|
||
case .failure(let error):
|
||
if self.playerRequests[videoId] != nil {
|
||
self.playerRequests[videoId] = nil // 清除请求
|
||
}
|
||
//当前无数据
|
||
failure?(false)
|
||
// 请求失败,处理错误
|
||
handleError(url, error: error, status: false)
|
||
}
|
||
}
|
||
//根据当前videoId添加request
|
||
playerRequests[videoId] = request
|
||
}
|
||
///移除补全任务请求任务
|
||
func removeVideoResource(_ videoId: String) {
|
||
// playerRequests[videoId]?.cancel()
|
||
playerRequests.removeValue(forKey: videoId)
|
||
// relatedRequests[videoId]?.cancel()
|
||
relatedRequests.removeValue(forKey: videoId)
|
||
}
|
||
// 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": clientVersion,
|
||
// "platform":"MOBILE",
|
||
// //语言
|
||
// "hl":Language_first_local,
|
||
// //地址
|
||
// "gl":locaton ?? "US"
|
||
// ]
|
||
// ],
|
||
// "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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = lyricId
|
||
|
||
//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参数是必定携带内容
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["browseId"] = browseId
|
||
|
||
guard netWorkStatu != .notReachable else {
|
||
completion([])
|
||
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)
|
||
completion([])
|
||
}
|
||
}
|
||
}
|
||
|
||
//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
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["input"] = content
|
||
|
||
//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
|
||
}
|
||
//检索关键词
|
||
guard trashKeyWords.contains(text) == false else {
|
||
//是禁用关键词
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
|
||
print("是禁用关键词")
|
||
completion([])
|
||
}
|
||
return
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["query"] = text
|
||
//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)) {
|
||
guard netWorkStatu != .notReachable else {
|
||
completion([])
|
||
handleError(url, error: nil)
|
||
return
|
||
}
|
||
//发送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
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["query"] = query
|
||
parameters["params"] = params
|
||
|
||
//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))
|
||
}else {
|
||
value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.forEach({ item in
|
||
if let musicShelfRenderer = item.musicShelfRenderer{
|
||
completion(self.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
|
||
}
|
||
//设置参数
|
||
var parameters:[String:Any] = browseContext
|
||
parameters["ctoken"] = continuation
|
||
parameters["continuation"] = continuation
|
||
parameters["type"] = "next"
|
||
parameters["itct"] = itct
|
||
|
||
//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 homeError(error:AFError?) {
|
||
// 根据错误类型处理
|
||
if let statusCode = error?.responseCode {
|
||
switch statusCode {
|
||
case 400...499:
|
||
print("请求错误,错误码: \(statusCode)。")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Request Error, Code:\(statusCode)")
|
||
return
|
||
case 500...599:
|
||
print("服务器错误,错误码: \(statusCode)。")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Server Error, Code:\(statusCode)")
|
||
default:
|
||
print("其他 HTTP 错误,错误码: \(statusCode)。")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("HTTP Error, Code:\(statusCode)")
|
||
}
|
||
} else if let underlyingError = error?.underlyingError as? URLError {
|
||
switch underlyingError.code {
|
||
case .notConnectedToInternet:
|
||
print("网络连接不可用,请检查你的网络设置")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Network Unavailable, Error:\(underlyingError.localizedDescription)")
|
||
case .timedOut:
|
||
print("请求超时,即将重启请求")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Timed Out, Error:\(underlyingError.localizedDescription)")
|
||
return
|
||
case .networkConnectionLost:
|
||
print("网络权限开启,但网络本身不可用,请检查你的网络设置")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("International comics not yet launched, Error:\(underlyingError.localizedDescription)")
|
||
case .cannotLoadFromNetwork:
|
||
print("请求证书失效,即将重启请求")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("SSL Error, Error:\(underlyingError.localizedDescription)")
|
||
return
|
||
case .cannotConnectToHost:
|
||
print("服务器响应失败,请待会儿调用")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Server not present, Error:\(underlyingError.localizedDescription)")
|
||
case .badURL:
|
||
print("链接失败,即将重启请求")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Bad URL, Error:\(underlyingError.localizedDescription)")
|
||
return
|
||
case .cannotDecodeContentData:
|
||
print("解析响应体失败,即将重启请求")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Can't Decode Content Data, Error:\(underlyingError.localizedDescription)")
|
||
return
|
||
default:
|
||
print("NSURL 错误,错误码: \(underlyingError.code.rawValue)。")
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Request Error, Error:\(underlyingError.localizedDescription)")
|
||
}
|
||
} else {
|
||
print("未知错误: \(error?.localizedDescription ?? "")")
|
||
pingYoutube { status in
|
||
if status {
|
||
//ping成功了,说明当前地区可以访问油管本体,但是不能属于油管音乐服务区
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("Can access YouTube, but not in the service area")
|
||
}else {
|
||
MP_AnalyticsManager.shared.home_b_module_showfailure_errorAction("No Area")
|
||
}
|
||
}
|
||
}
|
||
if browseRequestErrorBlock != nil {
|
||
browseRequestErrorBlock!()
|
||
}
|
||
}
|
||
///二次检索网络是否拉通了
|
||
private func pingYoutube(completion: @escaping (Bool) -> Void) {
|
||
guard let url = URL(string: "https://www.youtube.com/") else {return}
|
||
AF.request(url).response { response in
|
||
if let error = response.error {
|
||
print("Ping failed with error: \(error)")
|
||
completion(false)
|
||
} else {
|
||
print("Ping successful, response code: \(response.response?.statusCode ?? -1)")
|
||
completion(true)
|
||
}
|
||
}
|
||
}
|
||
|
||
///通用错误处理方法
|
||
private func handleError(_ url: URL, error:AFError?, status:Bool = true) {
|
||
// 根据错误类型处理
|
||
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 ?? "")")
|
||
}
|
||
if status {
|
||
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) {
|
||
//重置分页信息数据
|
||
continuationAndItct = nil
|
||
//获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表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 {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
browse.items.append(.init(d))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
})
|
||
if let nextContinuationData = content.sectionListRenderer?.continuations?.first {
|
||
continuation = nextContinuationData.nextContinuationData?.continuation
|
||
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
|
||
}
|
||
if let text1 = continuation, let text2 = itct {
|
||
//更新分页数据
|
||
self.continuationAndItct = .init(continuation: text1, itct: text2)
|
||
}
|
||
//传递解析数据
|
||
if let block = browseRequestStateBlock {
|
||
block(browses)
|
||
}
|
||
}
|
||
///排行榜艺术家数据解析
|
||
private func parsingCharts(_ contents:[JsonCharts.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) {
|
||
var browses:[MPPositive_BrowseModuleListViewModel] = []
|
||
contents.forEach { content in
|
||
//检索content是否存在二阶段数据
|
||
if let musicCarouselShelfRenderer = content.musicCarouselShelfRenderer {
|
||
//存在,继续操作
|
||
let browse = MPPositive_BrowseModuleListViewModel()
|
||
browse.title = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
|
||
//获取数据
|
||
musicCarouselShelfRenderer.contents?.forEach({ content in
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
let item = MPPositive_BrowseItemModel()
|
||
//设置封面
|
||
item.coverUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$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 ?? "")}) ?? "")
|
||
}
|
||
}
|
||
//设置ID
|
||
item.itemType = .artist
|
||
item.browseId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId
|
||
item.artistId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId
|
||
item.pageType = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
|
||
browse.items.append(.init(item))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
}
|
||
}
|
||
//清理为空的数据
|
||
browses = browses.filter({$0.items.contains(where: {$0.browseItem.pageType == "MUSIC_PAGE_TYPE_ARTIST"})})
|
||
if let block = browseRequestStateBlock {
|
||
block(browses)
|
||
}
|
||
}
|
||
private func parsingChartsUS(_ contents:[JsonCharts.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) {
|
||
var browses:[MPPositive_BrowseModuleListViewModel] = []
|
||
contents.forEach { content in
|
||
//检索content是否存在二阶段数据
|
||
if let musicCarouselShelfRenderer = content.musicCarouselShelfRenderer {
|
||
//存在,继续操作
|
||
let browse = MPPositive_BrowseModuleListViewModel()
|
||
browse.title = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
|
||
//获取数据
|
||
musicCarouselShelfRenderer.contents?.forEach({ content in
|
||
if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let item = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
}
|
||
}
|
||
//清理为空的数据
|
||
browses = browses.filter({$0.items.contains(where: {$0.browseItem.pageType == "MUSIC_PAGE_TYPE_PLAYLIST"})})
|
||
if let block = browseRequestStateBlock {
|
||
block(browses)
|
||
}
|
||
}
|
||
|
||
///解析预览内容_ContinuationContents
|
||
private func parsingBrowseContinuationContents(_ continuationContents:JsonBrowses.ContinuationContents) {
|
||
//重置分页信息
|
||
continuationAndItct = nil
|
||
//获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表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 {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
browse.items.append(.init(d))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
})
|
||
if let nextContinuationData = continuationContents.sectionListContinuation?.continuations?.first {
|
||
continuation = nextContinuationData.nextContinuationData?.continuation
|
||
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
|
||
}
|
||
if let text1 = continuation, let text2 = itct {
|
||
//更新分页数据
|
||
self.continuationAndItct = .init(continuation: text1, itct: text2)
|
||
}
|
||
//传递解析数据
|
||
if let block = browseRequestStateBlock {
|
||
block(browses)
|
||
}
|
||
}
|
||
///解析列表_Contents_一号版本
|
||
private func parsingListSingleContents(_ 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 {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
items.append(.init(d))
|
||
}
|
||
})
|
||
}else if let musicShelfRenderer = content.musicShelfRenderer{
|
||
musicShelfRenderer.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
items.append(.init(d))
|
||
}
|
||
})
|
||
}
|
||
})
|
||
return items
|
||
}
|
||
///解析列表_Contents_二号版本
|
||
private func parsingListTwoContents(_ content:JsonListOrAlbum.Contents.TwoColumnBrowseResultsRenderer.SecondaryContents) -> [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 {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
items.append(.init(d))
|
||
}
|
||
})
|
||
}else if let musicShelfRenderer = content.musicShelfRenderer{
|
||
musicShelfRenderer.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
items.append(.init(d))
|
||
}
|
||
})
|
||
}
|
||
})
|
||
return items
|
||
}
|
||
///解析列表_Header_一号版本
|
||
private func parsingListSingleHeaders(_ 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 parsingListTwoHeaders(_ header:JsonListOrAlbum.Contents.TwoColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content.MusicResponsiveHeaderRenderer) -> MPPositive_ListHeaderModel {
|
||
let listHeader = MPPositive_ListHeaderModel()
|
||
//封面
|
||
if let thumbnail = header.thumbnail {
|
||
listHeader.coverUrl = thumbnail.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
|
||
}
|
||
//标题
|
||
if let title = header.title {
|
||
listHeader.maintitle = title.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
}
|
||
//副标题
|
||
if let subtitle = header.subtitle {
|
||
listHeader.subtitle = subtitle.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
}
|
||
//说明
|
||
if let description = header.description?.musicDescriptionShelfRenderer?.description?.runs?.first?.text {
|
||
//对说明文本裁剪
|
||
let _d = truncateTextAfterTwoNewlines(from: description)
|
||
listHeader.forDescription = _d
|
||
}
|
||
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.fordescription = truncateTextAfterTwoNewlines(from: model.fordescription ?? "")
|
||
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 let musicResponsiveListItemRenderer = item.musicResponsiveListItemRenderer, let asd = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
itemViews.append(.init(asd))
|
||
}
|
||
})
|
||
}
|
||
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 let musicTwoRowItemRenderer = item.musicTwoRowItemRenderer, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
itemViews.append(.init(d))
|
||
}
|
||
})
|
||
}
|
||
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 {
|
||
if let asd = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
array.append(.init(asd))
|
||
}
|
||
}
|
||
})
|
||
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, let d = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
array.append(.init(d))
|
||
}
|
||
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 {
|
||
if let asd = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
array.append(.init(asd))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
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 ?? "")})
|
||
//设置艺术家ID,专辑ID
|
||
playlistPanelVideoRenderer.longBylineText?.runs?.forEach({ run in
|
||
//艺术家
|
||
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
|
||
song.artistID = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
//专辑
|
||
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ALBUM" {
|
||
song.albumID = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
})
|
||
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
|
||
song.clickTrackingParams = playlistPanelVideoRenderer.trackingParams
|
||
song.playListID = playlistPanelVideoRenderer.navigationEndpoint?.watchEndpoint?.playlistId
|
||
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)
|
||
}
|
||
}
|
||
///解析模块数据_Genres
|
||
private func parsingGenres(_ contents:[JsonGenres.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) -> [MPPositive_GridViewModel] {
|
||
var array:[MPPositive_GridViewModel] = []
|
||
contents.forEach { content in
|
||
if let items = content.gridRenderer?.items {
|
||
items.forEach { item in
|
||
if let text = item.musicNavigationButtonRenderer?.buttonText?.runs?.first?.text, let leftStripeColor = item.musicNavigationButtonRenderer?.solid?.leftStripeColor, let browseId = item.musicNavigationButtonRenderer?.clickCommand?.browseEndpoint?.browseId, let params = item.musicNavigationButtonRenderer?.clickCommand?.browseEndpoint?.params {
|
||
let gride = MPPositive_GridModel(title: text, stringColor: leftStripeColor, browseId: browseId, params: params)
|
||
array.append(.init(gride))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return array
|
||
}
|
||
///解析模块详情数据_Mood
|
||
private func parsingMoodDetails(_ content:JsonBrowses.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) -> [MPPositive_BrowseModuleListViewModel] {
|
||
//获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表id和视频id)
|
||
var browses:[MPPositive_BrowseModuleListViewModel] = []
|
||
//循环获取音乐内容
|
||
content.sectionListRenderer?.contents?.forEach({ content in
|
||
//该循环获取预览模块内容,生成一个预览模型接收数据
|
||
let browse = MPPositive_BrowseModuleListViewModel()
|
||
if let musicCarouselShelfRenderer = content.musicCarouselShelfRenderer {
|
||
browse.title = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
|
||
//循环音乐内容组
|
||
musicCarouselShelfRenderer.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
if let item = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}
|
||
})
|
||
if browse.items.isEmpty != true {
|
||
browses.append(browse)
|
||
}
|
||
}else if let gridRenderer = content.gridRenderer {
|
||
browse.title = gridRenderer.header?.gridHeaderRenderer?.title?.runs?.first?.text
|
||
gridRenderer.items?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
if let item = parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer) {
|
||
browse.items.append(.init(item))
|
||
}
|
||
}
|
||
})
|
||
if browse.items.isEmpty != true {
|
||
browses.append(browse)
|
||
}
|
||
}
|
||
})
|
||
return browses
|
||
}
|
||
/// 解析播放器_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), failure:((Bool) -> Void)? = nil, resetion:(() -> Void)? = nil){
|
||
//检索相应内容状态
|
||
guard let statu = player.playabilityStatus?.status, statu == "OK" || statu == "LOGIN_REQUIRED" else {
|
||
//检索失败,重新请求
|
||
if let block = resetion {
|
||
block()
|
||
}
|
||
return
|
||
}
|
||
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)
|
||
}
|
||
UserDefaults.standard.set(0, forKey: "Number_Failures")
|
||
UserDefaults.standard.synchronize()
|
||
}else {
|
||
//不存在资源(通常是IP被拉黑了)
|
||
failure?(true)
|
||
setNumbersFailures()
|
||
}
|
||
}
|
||
private func setNumbersFailures() {
|
||
MP_HUD.error("Failed to obtain resource, please try again later".localizableString(), delay: 1.0, completion: nil)
|
||
//检索不存在资源的次数
|
||
if let number = UserDefaults.standard.object(forKey: "Number_Failures") as? Int {
|
||
//当次数超过8次时,确定为IP被拉黑了
|
||
if number > 6 {
|
||
//触发拉黑上传事件
|
||
MP_AnalyticsManager.shared.resource_IP_blackAction()
|
||
}else {
|
||
//没有达到8次,新增计数值
|
||
UserDefaults.standard.set(number+1, forKey: "Number_Failures")
|
||
UserDefaults.standard.synchronize()
|
||
}
|
||
}else {
|
||
//无次数,说明是第一次触发该问题
|
||
UserDefaults.standard.set(1, forKey: "Number_Failures")
|
||
UserDefaults.standard.synchronize()
|
||
}
|
||
MP_AnalyticsManager.shared.player_resource_failureAction(locaton ?? "HK")
|
||
}
|
||
|
||
/// 解析播放器_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] = []
|
||
var allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
|
||
//判断第一个itag值是否大于100,当第一条itag大于100,那么第一条资源将不可用,将itag140移动到首位
|
||
if let first = allFormats.first, (first.itag ?? 0) >= 100 {
|
||
//第一条资源不可用,将音轨itag140资源移动到第一条
|
||
if let index = allFormats.firstIndex(where: {$0.itag == 140 || $0.itag == 251}) {
|
||
let itag = allFormats.remove(at: index)
|
||
allFormats.insert(itag, at: 0)
|
||
}
|
||
}
|
||
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..<originalURLString.endIndex) else {
|
||
return
|
||
}
|
||
//第二段是url
|
||
guard let urlRange = originalURLString.range(of: "&url=")?.lowerBound else {
|
||
return
|
||
}
|
||
let urlStartIndex = originalURLString.index(urlRange, offsetBy: 5) // "&url=" 的长度为 5
|
||
//提取URl路径
|
||
let urlSubstring = originalURLString[urlStartIndex...] // 从 &url= 之后开始提取
|
||
let signString = String(originalURLString[sRange.upperBound..<spSigRange.lowerBound])
|
||
|
||
// //加密的权限请求解码
|
||
// MP_WebWork.shared.excuteJavaScript(signString) { result in
|
||
// //与权限拼接
|
||
// let abString = urlSubstring + "&sig=" + result
|
||
// completion(abString)
|
||
// }
|
||
}
|
||
/// 解析播放器_VideoDetails
|
||
/// - Parameter videoDetails: 信息库
|
||
/// - Returns: 返回一个元组,值分别为 videoId,title,author,urls
|
||
private func parsingAndroidPlayerVideoDetails(_ videoDetails:JsonAndroidPlayer.VideoDetails) -> [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
|
||
}
|
||
// private func parsingPlayerVideoDetails(_ videoDetails:JsonPlayer.VideoDetails) -> [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 ""
|
||
}
|
||
///解析相关内容
|
||
private func parsingRecommend(_ contents:[JsonRecommend.Contents.SectionListRenderer.Content]) -> [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 let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer{
|
||
if let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
list.items.append(.init(item))
|
||
}
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer{
|
||
if let item = parsingMusicTwoRowItemRenderer(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
|
||
item.clickTrackingParams = musicCardShelfRenderer.trackingParams
|
||
//设置第一个内容块的一级标题和类型和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
|
||
//生成更多内容块
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
if item.title != nil && item.itemType != nil, item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
|
||
resultList.previewItemViews.append(.init(item))
|
||
}
|
||
}
|
||
// 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"){
|
||
// if let trackingParams = musicResponsiveListItemRenderer.trackingParams {
|
||
// item.clickTrackingParams = trackingParams
|
||
// }
|
||
// //设置预览图
|
||
// 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
|
||
//专辑/列表/单曲视频/艺术家
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
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.forEach { list in
|
||
list.previewItemViews = list.previewItemViews.filter({ item in
|
||
return (trashSingerIds.contains(item.item.artistId ?? "") == false) && (trashVideoIds.contains(item.item.videoId ?? "") == false)
|
||
})
|
||
}
|
||
|
||
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 let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
//添加内容块
|
||
if item.itemType != nil {
|
||
array.append(.init(item))
|
||
}
|
||
}
|
||
// 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({ item in
|
||
return (trashSingerIds.contains(item.item.artistId ?? "") == false) && (trashVideoIds.contains(item.item.videoId ?? "") == false)
|
||
})
|
||
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
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, let item = parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer) {
|
||
//添加内容块
|
||
if item.itemType != nil {
|
||
array.append(.init(item))
|
||
}
|
||
}
|
||
// //专辑/列表/单曲视频/艺术家
|
||
// 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({ item in
|
||
return (trashSingerIds.contains(item.item.artistId ?? "") == false) && (trashVideoIds.contains(item.item.videoId ?? "") == false)
|
||
})
|
||
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 clickTrackingParams = musicResponsiveListItemRenderer.trackingParams {
|
||
item.clickTrackingParams = clickTrackingParams
|
||
}
|
||
//判断预览项性质,并继续补完数据
|
||
if let playlistItemData = musicResponsiveListItemRenderer.playlistItemData {
|
||
//当前预览项是视频/单曲
|
||
item.itemType = .single
|
||
item.videoId = playlistItemData.videoId
|
||
//设置主副标题
|
||
guard var flexColumns = musicResponsiveListItemRenderer.flexColumns, flexColumns.isEmpty == false else {
|
||
return nil
|
||
}
|
||
//主标题为第一位,同时VideoId,PlayListId,pageType都以第一个flexColumn为主
|
||
let first = flexColumns.removeFirst()
|
||
item.title = first.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
if let clickTrackingParams = first.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.clickTrackingParams {
|
||
item.clickTrackingParams = clickTrackingParams
|
||
}
|
||
//判断watchEndpoint(资源导向)是否存在
|
||
guard let watchEndpoint = first.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.watchEndpoint else {return nil}
|
||
//watchEndpoint内容赋值给item
|
||
item.playListId = watchEndpoint.playlistId
|
||
item.pageType = watchEndpoint.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType
|
||
//设置副标题,通常为作者/专辑
|
||
flexColumns.forEach { flexColumn in
|
||
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
|
||
if let ari = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first(where: {$0.navigationEndpoint?.browseEndpoint != nil}) {
|
||
if ari.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST", let id = ari.navigationEndpoint?.browseEndpoint?.browseId {
|
||
//这个文本指向作者,获取作者ID
|
||
item.artistId = id
|
||
}
|
||
}
|
||
}
|
||
//继续补完videoId,检索是否具备playListId,当playListId为空时,说明并没有成功补完,需要使用菜单结构补全内容
|
||
guard (item.playListId ?? "").isEmpty == true else {
|
||
//判断是否禁止的作者或者禁止的音乐
|
||
if (trashSingerIds.contains(item.artistId ?? "")) == false && (trashVideoIds.contains(item.videoId ?? "")) == false {
|
||
return item
|
||
}else {
|
||
//禁止内容
|
||
return nil
|
||
}
|
||
}
|
||
//未补完,继续
|
||
if let watchItem = musicResponsiveListItemRenderer.menu?.menuRenderer?.items?.first(where: {$0.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint != nil}) {
|
||
item.playListId = watchItem.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
|
||
//判断是否禁止的作者或者禁止的音乐
|
||
if (trashSingerIds.contains(item.artistId ?? "")) == false && (trashVideoIds.contains(item.videoId ?? "")) == false {
|
||
return item
|
||
}else {
|
||
//禁止内容
|
||
return nil
|
||
}
|
||
}else {
|
||
//补完失败
|
||
return nil
|
||
}
|
||
}else if let navigationEndpoint = musicResponsiveListItemRenderer.navigationEndpoint {
|
||
//是专辑/艺术家/歌单
|
||
//设置主副标题
|
||
guard var flexColumns = musicResponsiveListItemRenderer.flexColumns, flexColumns.isEmpty != true else {
|
||
return nil
|
||
}
|
||
//主标题为第一位,其余内容都是副标题
|
||
let first = flexColumns.removeFirst()
|
||
item.title = first.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
if let clickTrackingParams = first.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.clickTrackingParams {
|
||
item.clickTrackingParams = clickTrackingParams
|
||
}
|
||
//设置副标题,通常为作者/专辑
|
||
flexColumns.forEach { flexColumn in
|
||
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
|
||
if let ari = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first(where: {$0.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST"}) {
|
||
//这个文本指向作者,获取作者ID
|
||
if let id = ari.navigationEndpoint?.browseEndpoint?.browseId {
|
||
item.artistId = id
|
||
}
|
||
}
|
||
}
|
||
//设置预览Id
|
||
guard let browseEndpoint = navigationEndpoint.browseEndpoint else {
|
||
return nil
|
||
}
|
||
item.browseId = browseEndpoint.browseId
|
||
//根据pageType确定导航至何处
|
||
guard let pageType = browseEndpoint.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType else {return nil}
|
||
item.pageType = pageType
|
||
if pageType == "MUSIC_PAGE_TYPE_ALBUM" {
|
||
//专辑
|
||
item.itemType = .list
|
||
return item
|
||
}else if pageType == "MUSIC_PAGE_TYPE_PLAYLIST" {
|
||
//列表
|
||
item.itemType = .list
|
||
return item
|
||
}else if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
|
||
//艺术家
|
||
item.itemType = .artist
|
||
item.artistId = item.browseId
|
||
if (trashSingerIds.contains(item.artistId ?? "")) == false {
|
||
return item
|
||
}else {
|
||
return nil
|
||
}
|
||
}else {
|
||
return nil
|
||
}
|
||
}else {
|
||
return nil
|
||
}
|
||
}
|
||
//解析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 art = musicTwoRowItemRenderer.subtitle?.runs?.first(where: {$0.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST"}) {
|
||
item.artistId = art.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
if let clickTrackingParams = musicTwoRowItemRenderer.navigationEndpoint?.clickTrackingParams {
|
||
item.clickTrackingParams = clickTrackingParams
|
||
}
|
||
//判断这个内容是音视频还是专辑列表艺术家
|
||
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
|
||
}
|
||
}
|
||
if (trashSingerIds.contains(item.artistId ?? "")) == false {
|
||
return item
|
||
}else {
|
||
return nil
|
||
}
|
||
}
|
||
}
|
||
//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 = 2
|
||
// 重试间隔时间
|
||
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
|
||
}
|
||
}
|
||
}
|