Music_Player3/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift

2026 lines
99 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
///
typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel], _ isCompeleted:Bool) -> Void
////
typealias ListRequestResultBlock = (_ list:MPPositive_ListAlbumListViewModel) -> Void
///
class MP_NetWorkManager: NSObject {
//
static let shared = MP_NetWorkManager()
//MARK: -
///
private lazy var MPSession:Session = {
let configuration = URLSessionConfiguration.af.default
//4
configuration.timeoutIntervalForRequest = 12
configuration.timeoutIntervalForResource = 12
return Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
}()
///
private lazy var PlayerSeesion:Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 12
configuration.timeoutIntervalForResource = 12
//4
configuration.httpMaximumConnectionsPerHost = 4
return Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
}()
///
private var playerRequests: [PlayerRequest] = []
///
struct PlayerRequest {
let request: DataRequest
let onCancel: (() -> Void)
}
//MARK: - API
///IP
private let iPInfo:String = "https://api.tikustok.com/app/common/getIPInfo"
///
private let header:String = "https://music.youtube.com"
///
private let point:String = "/youtubei/v1"
///
private let browse:String = "/browse"
///
private let next:String = "/next"
///
private let player:String = "/player"
///
private let suggestions:String = "/music/get_search_suggestions"
///
private let search = "/search"
///YouTuBe
private let youTubeKeys:[String] = ["MUSIC_VIDEO_TYPE_ATV","MUSIC_VIDEO_TYPE_OMV","MUSIC_PAGE_TYPE_ALBUM","MUSIC_PAGE_TYPE_ARTIST","MUSIC_PAGE_TYPE_PLAYLIST","MUSIC_PAGE_TYPE_TRACK_LYRICS","MUSIC_PAGE_TYPE_TRACK_RELATED"]
///IP
private let banIPs:[String] = [
"CN",
"HK",
"TW",
"JP",
"KR",
"GB",
"CH",
"BE",
"MO",
"SG"
]
//
enum NetWorkStatus: String {
case notReachable = "网络不可用"
case unknown = "网络未知"
case reachable = "网络可用"
}
///
private 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 visitorData:String?
//
private lazy var currTimeDate:String = (Date().timeZone() - 7.days).toString(.custom("YYYYMMdd"))
///
private var locaton:String? = "HK"
//
private var continuationAndItct:(String?,String?){
willSet{
//
guard let continuation = newValue.0, let itct = newValue.1, browseQueque != nil, let url = URL(string: header+point+browse) else {
//线
browseQueque = nil
return
}
//
let parameters:[String:Any] = [
"ctoken":continuation,
"continuation":continuation,
"type":"next",
"itct":itct,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? "HK"
]
]
]
//
browseQueque?.async {
[weak self] in
guard let self = self else { return }
requestPostHomeBrowse(url, parameters: parameters)
}
}
}
//MARK: - GCD
///
private let MPNetWorkqueue = DispatchQueue(label: "MPNetWorkManager")
///A/B
private var executionQueue: [() -> Void] = []
///-
private var browseQueque:DispatchQueue?
///-
var playerItemLoadingGroup:DispatchGroup = DispatchGroup()
//MARK: -
///
var browseRequestStateBlock:BrowseRequestStateBlock?
//
private override init() {
self.monitor = NWPathMonitor()
super.init()
}
//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: "”Musiclax“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in
let url = URL(string: UIApplication.openSettingsURLString)
if let url = url,UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:]) { (success) in
}
}else{
UIApplication.shared.canOpenURL(url)
}
}
}
alertController.addAction(CancelAction)
alertController.addAction(OKAction)
oberve.present(alertController, animated: true, completion: nil)
}
}
}
///
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)) {
MPSession.request(url, method: .get, encoding: JSONEncoding.default).responseDecodable(of: JsonIPInfo.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
guard let data = value.data, let code = data.isoCode else {
return
}
if banIPs.contains(code) == true {
locaton = ""
//
completion(false)
}else {
//
locaton = code
completion(true)
}
case .failure(let error):
//
handleError(url, error: error)
completion(true)
}
}
}
//MARK: -
///YouTubemusic/
func requestBrowseDatas() {
//continuationcontinuation,
//
browseQueque = DispatchQueue(label: "com.request.browseQueque")
visitorData = nil
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
browseQueque?.async {
[weak self] in
//IDcontinuation
guard let self = self else { return }
let parameters:[String:Any] = [
"browseId": "FEmusic_home",
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostHomeBrowse(url, parameters: parameters)
}
}
//
private func requestPostHomeBrowse(_ url:URL, parameters:Parameters) {
//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 value.responseContext?.visitorData != nil {
self.visitorData = value.responseContext?.visitorData
}
//
let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0]
if let content = tab?.tabRenderer?.content {
parsingBrowseContents(content)
}else if let continuationContents = value.continuationContents {
parsingBrowseContinuationContents(continuationContents)
}else {
print("Failed to parse browses content")
//
handleError(url, error: nil)
browseQueque = nil
}
case .failure(let error):
//
handleError(url, error: error)
browseQueque = nil
}
}
}
//MARK: -
/// YouTubemusic/browse
/// - Parameters:
/// - browseId: Id
/// - params: 访
func requestAlbumOrListDatas(_ browseId: String, params: String, comletion:@escaping (MPPositive_ListAlbumListViewModel) -> Void) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":browseId,
"params":params,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
// //guard netWorkStatu != .notReachable else {return}
requestPostAlbumOrList(url, parameters: parameters) { results in
comletion(results)
}
}
///
private func requestPostAlbumOrList(_ url:URL, parameters:Parameters, comletion:@escaping (MPPositive_ListAlbumListViewModel) -> Void) {
//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 = parsingListContents(content)
}
if let header = value.header {
list.header = .init(parsingListHeaders(header))
}
//
comletion(list)
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
func requestArtist(_ browseId:String, comletion:@escaping (MPPositive_ArtistViewModel) -> Void) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":browseId,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostArtist(url, parameters: parameters) { result in
comletion(result)
}
}
//
private func requestPostArtist(_ url:URL, parameters:Parameters, comletion:@escaping (MPPositive_ArtistViewModel) -> Void) {
//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
let parameters:[String:Any] = [
"browseId":browseId,
"params":params,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostArtistMore(url, parameters: parameters) { result in
comletion(result)
}
}
///
private func requestPostArtistMore(_ url:URL, parameters:Parameters, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) {
//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
let parameters:[String:Any] = [
"continuation":continuation,
"ctoken":continuation,
"type":"next",
"itct":itct,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostArtistMoreContinuation(url, parameters: parameters) { result in
comletion(result)
}
}
///
private func requestPostArtistMoreContinuation(_ url:URL, parameters:Parameters, comletion:@escaping (([MPPositive_BrowseItemViewModel], String?, String?)) -> Void) {
//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)
}
}
}
//MARK: -
///NextPlayer
/// - Parameter item:
func requestNextList(_ browseId:String, videoId:String, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) {
//next
let path = header+point+next
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//videoIdparams
let parameters:[String:Any] = [
"playlistId":browseId,
"videoId":videoId,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostNextList(url, parameters: parameters) { listSongs in
//
completion(listSongs)
}
}
//next
private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
//list
let listSongs:[MPPositive_SongItemModel] = parsingNextList(value)
completion(listSongs)
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///Next/
/// - Parameter item:
func requestNextLyricsAndRelated(_ item: MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
//next
let path = header+point+next
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//videoIdparams
let parameters:[String:Any] = [
"videoId":(item.videoId ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostNextLyricsAndRelated(url, parameters: parameters) { result in
completion(result)
}
}
//Next/
private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
let result = parsingNextLyricsAndRelated(value)
//
completion(result)
case .failure(let error):
//
handleError(url, error: error)
}
}
}
//MARK: - player
/// Player(/)
/// - Parameter item:
func requestAndroidPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Int],[String])?, [String]?) -> Void)){
//player
let path = header+point+player
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
let day = Date().timeZone()
//videoIdparams
let parameters:[String:Any] = [
"videoId":videoId,
"prettyPrint":"false",
"context":[
"client":[
"clientName": "ANDROID_MUSIC",
"clientVersion": "\(day.month).\(day.toString(.custom("dd"))).1",
"platform":"MOBILE",
"browserVersion":"125.0.0.0",
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestAndroidPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in
completion(resourceUlrs, coverUrls)
}
}
private func requestAndroidPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Int],[String])?, [String]?) -> Void)) {
//
playerRequests = playerRequests.filter({$0.request.task?.state == .running})
//
if playerRequests.count >= 4, let requestToCancel = playerRequests.first {
requestToCancel.request.cancel()
//
requestToCancel.onCancel()
playerRequests.removeFirst()
print("取消多余的Player资源请求: \(requestToCancel)")
}
//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
completion(resourceUlrs, coverUrls)
}
case .failure(let error):
//
handleError(url, error: error)
}
}
//
playerRequests.append(.init(request: request, onCancel: {
completion(nil,nil)
}))
}
// 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": "1.\(currTimeDate).01.00",
// "platform":"MOBILE",
// //
// "hl":Language_first_local,
// //
// "gl":locaton ?? ""
// ]
// ],
// "playbackContext": [
// "contentPlaybackContext": [
// "signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0
// ]
// ]
// ]
// //guard netWorkStatu != .notReachable else {return}
// requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in
// completion(resourceUlrs, coverUrls)
// }
// }
///
// private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)) {
// //post
// MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in
// guard let self = self else {return}
//
// switch response.result {
// case .success(let value):
// parsingPlayer(value) { resourceUlrs, coverUrls in
// completion(resourceUlrs, coverUrls)
// }
// case .failure(let error):
// //
// print("Request failed: \(error)")
// }
// }
// }
//MARK: -
///
/// - Parameter lyricId: id
func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":lyricId,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostLyric(url, parameters: parameters) { lyrics in
completion(lyrics)
}
}
//
private func requestPostLyric(_ url:URL, parameters:Parameters, completion:@escaping((String) -> Void)) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonLyrics.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
completion(parsingLyrics(value) ?? "")
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
func requestRecommend(_ browseId:String, completion: @escaping ([MPPositive_RecommendListViewModel]) -> Void) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":browseId,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostRecommend(url, parameters: parameters) { results in
completion(results)
}
}
///
private func requestPostRecommend(_ url:URL, parameters:Parameters, completion: @escaping ([MPPositive_RecommendListViewModel]) -> Void) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonRecommend.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let contents = value.contents?.sectionListRenderer?.contents {
let section = parsingRecommend(contents)
completion(section)
}
case .failure(let error):
//
handleError(url, error: error)
}
}
}
//MARK: -
///
/// - Parameter content:
func requestSearchSuggestions(_ content:String, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) {
//
let path = header+point+suggestions
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"input":content,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostSearchSuggestions(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchSuggestions(_ url:URL, parameters:Parameters, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchSuggestions.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
parsingSearchSuggestions(value) { results in
completion(results)
}
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
/// - Parameter text:
func requestSearchPreviewResults(_ text:String, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) {
//
let path = header+point+search
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"query":text,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostSearchPreviewResults(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchPreviewResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchPreviewResults.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let contents = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
let result = parsingSearchPreviewResults(contents)
print("一共搜索到\(result.count)相关内容模块")
completion(result)
}
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
/// - Parameters:
/// - query:
/// - params:
func requestSearchTypeResults(_ query:String, params:String, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//
let path = header+point+search
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"query":query,
"prettyPrint":"false",
"params":params,
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostSearchTypeResults(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchTypeResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchTypeResults.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let musicShelfRenderer = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.first?.musicShelfRenderer {
completion(parsingSearchTypeResults(musicShelfRenderer))
}
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
/// - Parameters:
/// - continuation:
/// - itct:
func requestSearchTypeContinuation(_ continuation:String, itct:String, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//
let path = header+point+search
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"ctoken":continuation,
"continuation":continuation,
"type":"next",
"itct":itct,
"prettyPrint":false,
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
//
"hl":Language_first_local,
//
"gl":locaton ?? ""
]
]
]
//guard netWorkStatu != .notReachable else {return}
requestPostSearchTypeContinuation(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchTypeContinuation(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//post
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchTypeContinuation.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let musicShelfContinuation = value.continuationContents?.musicShelfContinuation {
completion(parsingSearchTypeContinuation(musicShelfContinuation))
}
case .failure(let error):
//
handleError(url, error: error)
}
}
}
///
private func handleError(_ url: URL, error:AFError?) {
//
if let statusCode = error?.responseCode {
switch statusCode {
case 400...499:
print("\(url)请求错误,错误码: \(statusCode)")
case 500...599:
print("\(url)服务器错误,错误码: \(statusCode)")
default:
print("\(url)其他 HTTP 错误,错误码: \(statusCode)")
}
} else if let underlyingError = error?.underlyingError as? URLError {
switch underlyingError.code {
case .notConnectedToInternet:
print("\(url)网络连接不可用,请检查你的网络设置。")
case .timedOut:
print("\(url)请求超时,请稍后重试。")
default:
print("\(url)NSURL 错误,错误码: \(underlyingError.code.rawValue)")
}
} else {
print("\(url)未知错误: \(error?.localizedDescription ?? "")")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
//
NotificationCenter.notificationKey.post(notificationName: .netWork_error_deal)
}
}
}
//MARK: -
extension MP_NetWorkManager {
//MARK: -
///_Contents
private func parsingBrowseContents(_ content:JsonBrowses.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) {
//: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 {
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
browses.append(browse)
})
if let nextContinuationData = content.sectionListRenderer?.continuations?.first {
continuation = nextContinuationData.nextContinuationData?.continuation
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
}
guard browseRequestStateBlock != nil else {
return
}
//
self.browseRequestStateBlock!(browses, (continuation == nil))
//
self.continuationAndItct = (continuation,itct)
}
///_ContinuationContents
private func parsingBrowseContinuationContents(_ continuationContents:JsonBrowses.ContinuationContents) {
//: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 {
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
browses.append(browse)
})
if let nextContinuationData = continuationContents.sectionListContinuation?.continuations?.first {
continuation = nextContinuationData.nextContinuationData?.continuation
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
}
guard browseRequestStateBlock != nil else {
return
}
//
self.browseRequestStateBlock!(browses, (continuation == nil))
//
self.continuationAndItct = (continuation,itct)
}
///_Contents
private func parsingListContents(_ content:JsonListOrAlbum.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) -> [MPPositive_BrowseItemViewModel] {
var items:[MPPositive_BrowseItemViewModel] = []
//
content.sectionListRenderer?.contents?.forEach({ content in
//
if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer {
musicPlaylistShelfRenderer.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
}else if let musicShelfRenderer = content.musicShelfRenderer{
musicShelfRenderer.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
}
})
return items
}
///_Header
private func parsingListHeaders(_ header:JsonListOrAlbum.Header) -> MPPositive_ListHeaderModel {
let listHeader = MPPositive_ListHeaderModel()
if let musicDetailHeaderRenderer = header.musicDetailHeaderRenderer {
//
listHeader.maintitle = musicDetailHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
listHeader.subtitle = musicDetailHeaderRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
listHeader.thirdtitle = musicDetailHeaderRenderer.secondSubtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
///
listHeader.forDescription = musicDetailHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
///
listHeader.coverUrl = musicDetailHeaderRenderer.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
}else if let musicImmersiveHeaderRenderer = header.musicImmersiveHeaderRenderer {
listHeader.maintitle = musicImmersiveHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
listHeader.forDescription = musicImmersiveHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
listHeader.coverUrl = musicImmersiveHeaderRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
}
return listHeader
}
///_Header
private func parsingArtistHeaders(_ header:JsonArtist.Header) -> MPPositive_ArtistHeaderModel {
let model = MPPositive_ArtistHeaderModel()
model.title = header.musicImmersiveHeaderRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
model.subscriptions = header.musicImmersiveHeaderRenderer?.subscriptionButton?.subscribeButtonRenderer?.subscriberCountText?.runs?.reduce("", { $0 + ($1.text ?? "")})
model.subscriptionedText = header.musicImmersiveHeaderRenderer?.subscriptionButton?.subscribeButtonRenderer?.subscribedButtonText?.runs?.reduce("", { $0 + ($1.text ?? "")})
model.fordescription = header.musicImmersiveHeaderRenderer?.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
model.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 item.musicResponsiveListItemRenderer != nil {
itemViews.append(.init(parsingMusicResponsiveListItemRenderer(item.musicResponsiveListItemRenderer!)))
}
})
}
if let musicCarouselShelfRenderer = content.musicCarouselShelfRenderer {
//
title = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
id = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.browseId
params = musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.navigationEndpoint?.browseEndpoint?.params
//
musicCarouselShelfRenderer.contents?.forEach({ item in
if item.musicTwoRowItemRenderer != nil {
itemViews.append(.init(parsingMusicTwoRowItemRenderer(item.musicTwoRowItemRenderer!)))
}
})
}
let list = MPPositive_ArtistContentListViewModel(title, itemViews: itemViews, browseId: id, params: params)
listArray.append(list)
}
return listArray
}
///_More
private func parsingArtistMore(_ content:JsonArtistMore.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content) -> ([MPPositive_BrowseItemViewModel], String?, String?) {
//
var continuation:String?
var clickTrackingParams:String?
var array:[MPPositive_BrowseItemViewModel] = []
//
if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer {
//
musicPlaylistShelfRenderer.contents?.forEach({ item in
if let musicResponsiveListItemRenderer = item.musicResponsiveListItemRenderer {
array.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}
})
continuation = musicPlaylistShelfRenderer.continuations?.first?.nextContinuationData?.continuation
clickTrackingParams = musicPlaylistShelfRenderer.continuations?.first?.nextContinuationData?.clickTrackingParams
}
if let gridRenderer = content.gridRenderer {
//
gridRenderer.items?.forEach({ item in
if let musicTwoRowItemRenderer = item.musicTwoRowItemRenderer {
array.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
continuation = gridRenderer.continuations?.first?.nextContinuationData?.continuation
clickTrackingParams = gridRenderer.continuations?.first?.nextContinuationData?.clickTrackingParams
})
}
return (array, continuation, clickTrackingParams)
}
///_Continuation
private func parsingArtistMoreContinuation(_ musicPlaylistShelfContinuation: JsonArtistMore.ContinuationContents.MusicPlaylistShelfContinuation) -> ([MPPositive_BrowseItemViewModel], String?, String?) {
//
var continuation:String?
var clickTrackingParams:String?
var array:[MPPositive_BrowseItemViewModel] = []
//
if let contents = musicPlaylistShelfContinuation.contents {
contents.forEach { item in
if let musicResponsiveListItemRenderer = item.musicResponsiveListItemRenderer {
array.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}
}
}
continuation = musicPlaylistShelfContinuation.continuations?.first?.nextContinuationData?.continuation
clickTrackingParams = musicPlaylistShelfContinuation.continuations?.first?.nextContinuationData?.clickTrackingParams
return (array,continuation,clickTrackingParams)
}
///_Next_
private func parsingNextList(_ next:JsonNext) -> [MPPositive_SongItemModel] {
var array:[MPPositive_SongItemModel] = []
if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs {
if let tab = tabs.first {
//
for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() {
if let playlistPanelVideoRenderer = content.playlistPanelVideoRenderer {
//
let song = MPPositive_SongItemModel()
song.index = index
song.title = playlistPanelVideoRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.longBylineText = playlistPanelVideoRenderer.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.lengthText = playlistPanelVideoRenderer.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.shortBylineText = playlistPanelVideoRenderer.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.reviewUrls = playlistPanelVideoRenderer.thumbnail?.thumbnails?.map({$0.url ?? ""})
song.videoId = playlistPanelVideoRenderer.videoId
array.append(song)
}
}
}
}
return array
}
/// _Next_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)
}
}
/// _Player
/// - Parameters:
/// - player: player
/// - completion:
// private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)){
// var infos:[String]?
// //player
// if let videoDetails = player.videoDetails {
// infos = parsingPlayerVideoDetails(videoDetails)
// }
// if let streamingData = player.streamingData {
// parsingPlayerStreamingData(streamingData){ videos,floats,approxDurationMs in
// completion((videos,floats,approxDurationMs),infos)
// }
// }
// }
private func parsingAndroidPlayer(_ player:JsonAndroidPlayer,completion:@escaping((([String],[Int],[String]), [String]?) -> Void)){
var infos:[String]?
//player
if let videoDetails = player.videoDetails {
infos = parsingAndroidPlayerVideoDetails(videoDetails)
}
if let streamingData = player.streamingData {
parsingAndroidPlayerStreamingData(streamingData){ videos,itags,mimeType in
completion((videos,itags,mimeType),infos)
}
}
}
/// _StreamingData
/// - Parameters:
/// - streamingData:
/// - completion:
private func parsingAndroidPlayerStreamingData(_ streamingData:JsonAndroidPlayer.StreamingData, completion:@escaping(([String],[Int],[String]) -> Void)) {
var videos:[String] = []
var itags:[Int] = []
var mimeType:[String] = []
var allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
// //itag
// allFormats = allFormats.sorted(by: {($0.itag ?? 0) > ($1.itag ?? 0)})
//mimeTypevideo/mp4
// if let index = allFormats.firstIndex(where: {$0.itag == 22}) {
// let element = allFormats.remove(at: index)
// allFormats.insert(element, 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 content.musicResponsiveListItemRenderer != nil {
let item = parsingMusicResponsiveListItemRenderer(content.musicResponsiveListItemRenderer!)
list.items.append(.init(item))
}else if content.musicTwoRowItemRenderer != nil {
let item = parsingMusicTwoRowItemRenderer(content.musicTwoRowItemRenderer!)
list.items.append(.init(item))
}
})
sectionLists.append(list)
}
return sectionLists
}
/// _SearchSuggestions
/// - Parameters:
/// - searchSuggestions:
/// - completion:
private func parsingSearchSuggestions(_ searchSuggestions:JsonSearchSuggestions, completion:@escaping([[MPPositive_SearchSuggestionItemModel]]) -> Void) {
if let contents = searchSuggestions.contents {
var sections:[[MPPositive_SearchSuggestionItemModel]] = []
contents.forEach { section in
var suggestions:[MPPositive_SearchSuggestionItemModel] = []
section.searchSuggestionsSectionRenderer?.contents?.forEach({ content in
//
let item = MPPositive_SearchSuggestionItemModel()
if let searchSuggestionRenderer = content.searchSuggestionRenderer {
//
item.title = searchSuggestionRenderer.suggestion?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
var reviewUrls:[String] = []
//
musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.forEach({ thumbnail in
reviewUrls.append(thumbnail.url ?? "")
})
item.reviewUrls = reviewUrls
if let flexColumns = musicResponsiveListItemRenderer.flexColumns {
for (index,flexColumn) in flexColumns.enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
}
}
suggestions.append(item)
})
sections.append(suggestions)
}
completion(sections)
}
}
///_SearchPreviewResults
private func parsingSearchPreviewResults(_ contents:[JsonSearchPreviewResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) -> [MPPositive_SearchResultListViewModel]{
var resultListSections:[MPPositive_SearchResultListViewModel] = []
contents.forEach { content in
let resultList = MPPositive_SearchResultListViewModel()
//
if let musicCardShelfRenderer = content.musicCardShelfRenderer {
//
//
resultList.title = musicCardShelfRenderer.header?.musicCardShelfHeaderBasicRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
////
let item = MPPositive_BrowseItemModel()
//
item.coverUrls = musicCardShelfRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
item.playListId = musicCardShelfRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
//ID
if let title = musicCardShelfRenderer.title {
title.runs?.forEach({ run in
item.title = run.text
if run.navigationEndpoint?.watchEndpoint != nil {
///
item.videoId = run.navigationEndpoint?.watchEndpoint?.videoId
item.itemType = .single
}else {
////
item.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
let pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
if youTubeKeys.contains(pageType ?? "") == true {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = run.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
})
}
//
if let subtitle = musicCardShelfRenderer.subtitle {
item.subtitle = subtitle.runs?.reduce("", { $0 + ($1.text ?? "")})
}
//
if item.itemType != nil {
resultList.previewItemViews.append(.init(item))
}
//
if let contents = musicCardShelfRenderer.contents {
contents.forEach { content in
//
let item = MPPositive_BrowseItemModel()
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer, (musicResponsiveListItemRenderer.playlistItemData != nil || musicResponsiveListItemRenderer.navigationEndpoint != nil) && (musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE"){
//
item.coverUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (musicResponsiveListItemRenderer.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
item.playListId = musicResponsiveListItemRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
//id
if musicResponsiveListItemRenderer.playlistItemData != nil {
//
item.itemType = .single
item.videoId = musicResponsiveListItemRenderer.playlistItemData?.videoId
}else {
///
item.browseId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId
let pageType = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
}
if item.title != nil && item.itemType != nil, item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
resultList.previewItemViews.append(.init(item))
}
}
}
//,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
/////
let item = MPPositive_BrowseItemModel()
if (content.musicResponsiveListItemRenderer?.playlistItemData != nil || content.musicResponsiveListItemRenderer?.navigationEndpoint != nil) && (content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE") {
//
item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
item.pageType = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
//id
if content.musicResponsiveListItemRenderer?.playlistItemData != nil {
//
item.itemType = .single
item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId
}else {
///
item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
}
if item.itemType != nil, item.title != nil, item.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
resultList.previewItemViews.append(.init(item))
}
})
//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 = resultListSections.filter({$0.previewItemViews.count != 0})
return resultListSections
}
///
private func parsingSearchTypeResults(_ musicShelfRenderer:JsonSearchTypeResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content.MusicShelfRenderer) -> ([MPPositive_SearchResultItemViewModel], String?, String?) {
var array:[MPPositive_SearchResultItemViewModel] = []
if let contents = musicShelfRenderer.contents {
///
contents.forEach({ content in
/////
let item = MPPositive_BrowseItemModel()
if (content.musicResponsiveListItemRenderer?.playlistItemData != nil || content.musicResponsiveListItemRenderer?.navigationEndpoint != nil) && (content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE") {
//
item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
//id
if content.musicResponsiveListItemRenderer?.playlistItemData != nil {
//
item.itemType = .single
item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId
}else {
///
item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
//
if item.itemType != nil {
array.append(.init(item))
}
}
})
}
var continuation:String?
var clickTrackingParams:String?
if let continuations = musicShelfRenderer.continuations?.first {
///
continuation = continuations.nextContinuationData?.continuation
clickTrackingParams = continuations.nextContinuationData?.clickTrackingParams
}
array = array.filter({$0.item.itemType != nil})
return (array, continuation, clickTrackingParams)
}
///
private func parsingSearchTypeContinuation(_ musicShelfContinuation:JsonSearchTypeContinuation.ContinuationContents.MusicShelfContinuation) -> ([MPPositive_SearchResultItemViewModel], String?, String?) {
var array:[MPPositive_SearchResultItemViewModel] = []
if let contents = musicShelfContinuation.contents {
///
contents.forEach({ content in
/////
let item = MPPositive_BrowseItemModel()
//
item.coverUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
item.playListId = content.musicResponsiveListItemRenderer?.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
//id
if content.musicResponsiveListItemRenderer?.playlistItemData != nil {
//
item.itemType = .single
item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId
}else {
///
item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
let pageType = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
if youTubeKeys.contains(pageType ?? "") == true && pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
//
if item.itemType != nil {
array.append(.init(item))
}
})
}
var continuation:String?
var clickTrackingParams:String?
if let continuations = musicShelfContinuation.continuations?.first {
///
continuation = continuations.nextContinuationData?.continuation
clickTrackingParams = continuations.nextContinuationData?.clickTrackingParams
}
array = array.filter({$0.item.itemType != nil})
return (array, continuation, clickTrackingParams)
}
//MARK: -
//musicResponsiveListItemRenderer
private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel {
//
let item = MPPositive_BrowseItemModel()
//
item.coverUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$0.url})
//
if let flexColumns = musicResponsiveListItemRenderer.flexColumns {
for (index,flexColumn) in flexColumns.enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
item.playListId = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.first?.navigationEndpoint?.watchEndpoint?.playlistId
}else{
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
}
if item.playListId == nil {
//playListId
item.playListId = musicResponsiveListItemRenderer.menu?.menuRenderer?.items?.first?.menuNavigationItemRenderer?.navigationEndpoint?.watchEndpoint?.playlistId
}
//
if let playlistItemData = musicResponsiveListItemRenderer.playlistItemData {
//
item.itemType = .single
item.videoId = playlistItemData.videoId
item.pageType = "MUSIC_VIDEO_TYPE_ATV"
}
if let navigationEndpoint = musicResponsiveListItemRenderer.navigationEndpoint {
//
if navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" && navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
item.itemType = .artist
item.artistId = navigationEndpoint.browseEndpoint?.browseId
item.pageType = "MUSIC_PAGE_TYPE_ARTIST"
}else {
if navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE" {
//
item.itemType = .list
item.pageType = navigationEndpoint.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
item.browseId = navigationEndpoint.browseEndpoint?.browseId
item.params = navigationEndpoint.browseEndpoint?.params
}
}
}
return item
}
//musicResponsiveListItemRenderer
private func parsingMusicTwoRowItemRenderer(_ musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer) -> MPPositive_BrowseItemModel {
//
let item = MPPositive_BrowseItemModel()
//
item.coverUrls = musicTwoRowItemRenderer.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.compactMap({$0.url})
//
item.title = musicTwoRowItemRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
item.subtitle = musicTwoRowItemRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
if let watchEndpoint = musicTwoRowItemRenderer.navigationEndpoint?.watchEndpoint {
//
item.itemType = .single
item.videoId = watchEndpoint.videoId
item.playListId = watchEndpoint.playlistId
item.pageType = watchEndpoint.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType
}
if let browseEndpoint = musicTwoRowItemRenderer.navigationEndpoint?.browseEndpoint {
//
if browseEndpoint.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
//
item.itemType = .artist
item.artistId = browseEndpoint.browseId
item.pageType = "MUSIC_PAGE_TYPE_ARTIST"
}else {
//
item.itemType = .list
item.pageType = browseEndpoint.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
item.browseId = browseEndpoint.browseId
item.params = browseEndpoint.params
}
}
return item
}
}
//MARK: -
extension MP_NetWorkManager {
///
private func seperatorOff(_ encodedString: String) -> String {
guard let decodedString = encodedString.removingPercentEncoding else {
print("百分比解码失败")
return ""
}
return decodedString
}
}
//MARK: -
///
class MP_CustomRetrier: RequestInterceptor {
//
private var retryCounts: [String: Int] = [:]
//
private let maxRetryCount: Int = 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
}
}
}