Music_Player3/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift
2024-11-18 21:29:29 +08:00

2540 lines
128 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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())
}()
///IDID
private var relatedRequests: [String:DataRequest] = [:]
///
private var playerRequests: [String:DataRequest] = [:]
//MARK: - API
///IP
private let iPInfo:String = "https://ipinfo.io/json"
///
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"]
///访YoutubeCode
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 code = value.country else {
return
}
let ip = value.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() {
//continuationcontinuation,
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//IDcontinuation
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) {
//postRootBrowses
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
}
//browseIdparams
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) {
//postRootBrowses
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
}
//browseIdparams
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) {
//postRootBrowses
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
}
//browseIdparams
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) {
//postRootBrowses
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
}
//browseIdparams
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) {
//postRootBrowses
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: -
///NextPlayer
/// - 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
}
//videoIdparams
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
}
//videoIdparams
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
}
//videoIdparams
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)
}
}
//videoIdrequest
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
// }
// //videoIdparams
// 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
}
//browseIdparams
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
}
//browseIdparams
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
//:idid
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
//:idid
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 ?? "")})
//IDID
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_IDIDIDbrwose
/// - Parameter next: next
/// - Returns: 0ID1ID
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] {
//:idid
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 {
//8IP
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 ?? [])
//itag100itag100itag140
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: videoIdtitleauthorurls
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))
// }
}
}
//,queryparams
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))
}
})
//queryparams
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,pageTypeflexColumn
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}
//watchEndpointitem
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
}
}
}