Music_Player3/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift
2024-05-22 14:25:58 +08:00

1763 lines
87 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 let MPSession = Alamofire.Session(interceptor: MP_CustomRetrier())
//MARK: - API
///
private let header:String = "https://music.youtube.com"
///
private let point:String = "/youtubei/v1"
///
private let browse:String = "/browse"
///
private let next:String = "/next"
///
private let player:String = "/player"
///
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"]
///
enum NetWorkStatus: String {
case notReachable = "网络不可用"
case unknown = "网络未知"
case reachable = "网络可用"
}
private var netWorkStatu:NetWorkStatus = .reachable{
willSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
if netWorkStatu == .reachable, newValue == .notReachable {
print("网络不可用")
NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable)
}
//
if netWorkStatu == .notReachable, newValue == .reachable {
print("网络可用")
NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable)
}
}
}
}
//MARK: -
//访
private var visitorData:String?
///
private lazy var locaton:String = MP_LocationManager.shared.requestLocation()
//
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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":locaton
]
]
]
//
browseQueque?.async {
[weak self] in
guard let self = self else { return }
requestPostHomeBrowse(url, parameters: parameters)
}
}
}
//MARK: -
//-
private var browseQueque:DispatchQueue?
//MARK: -
///
var browseRequestStateBlock:BrowseRequestStateBlock?
//
private override init() {
super.init()
}
//MARK: -
///
func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) {
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
switch path.status {
case .satisfied:
DispatchQueue.main.async {
guard completeHanlder != nil else {
return
}
completeHanlder!()
}
default://
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access network request", message: "”Musicoo“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in
let url = URL(string: UIApplication.openSettingsURLString)
if let url = url,UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:]) { (success) in
}
}else{
UIApplication.shared.canOpenURL(url)
}
}
}
alertController.addAction(CancelAction)
alertController.addAction(OKAction)
oberve.present(alertController, animated: true, completion: nil)
}
}
}
let queue = DispatchQueue(label: "MPNetWorkManager")
monitor.start(queue: queue)
}
///
func requestStatusToYouTube() {
//
let reachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/")
//ping
reachabilityManager?.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in
guard let self = self else {return}
switch status {
case .unknown://
netWorkStatu = .unknown
case .notReachable://
netWorkStatu = .notReachable
case .reachable(.ethernetOrWiFi), .reachable(.cellular)://
netWorkStatu = .reachable
}
})
}
}
//MARK: - API
extension MP_NetWorkManager {
//MARK: -
///YouTubemusic/
func requestBrowseDatas() {
//continuationcontinuation,
//
browseQueque = DispatchQueue(label: "com.request.browseQueque")
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
browseQueque?.async {
[weak self] in
//IDcontinuation
guard let self = self else { return }
let parameters:[String:Any] = [
"browseId": "FEmusic_home",
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":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")
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(error)")
}
}
}
//MARK: - player
/// Player(/)
/// - Parameter item:
func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping ((([String],[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":(item.videoId ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//访
"clientName": "WEB_REMIX",
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00"
]
],
"playbackContext": [
"contentPlaybackContext": [
"signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0
]
]
]
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],[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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(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.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"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):
//
print("Request failed: \(error)")
}
}
}
}
//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.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],[String]), [String]?) -> Void)){
var infos:[String]?
//player
if let videoDetails = player.videoDetails {
infos = parsingPlayerVideoDetails(videoDetails)
}
if let streamingData = player.streamingData {
parsingPlayerStreamingData(streamingData){ audios,videos in
completion((audios,videos),infos)
}
}
}
/// _StreamingData
/// - Parameters:
/// - streamingData:
/// - completion:
private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[String]) -> Void)) {
var group:DispatchGroup? = DispatchGroup()
var videos:[String] = []
var audios:[String] = []
let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
for format in allFormats {
if let signatureCipher = format.signatureCipher {
// DispatchGroup
group?.enter()
//
parsingPlayerSignatureCipher(signatureCipher) { result in
//
if format.mimeType?.contains("video") == true {
//
videos.append(result)
}
if format.mimeType?.contains("audio") == true {
audios.append(result)
}
// DispatchGroup
group?.leave()
}
}
}
group?.notify(queue: .main) {
completion(audios,videos)
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
// print("Resources-SignatureDecryption:\(abString)")
completion(abString)
}
}
/// _VideoDetails
/// - Parameter videoDetails:
/// - Returns: videoIdtitleauthorurls
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 {
//
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 {
//
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 {
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()
//
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 {
//
if pageType == "MUSIC_PAGE_TYPE_ARTIST" {
item.artistId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
if item.itemType != nil {
resultList.previewItemViews.append(.init(item))
}
})
//queryparams
if let searchEndpoint = musicShelfRenderer.bottomEndpoint?.searchEndpoint {
resultList.query = searchEndpoint.query
resultList.params = searchEndpoint.params
}
}
resultListSections.append(resultList)
}
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()
//
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 {
//
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 {
//
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" {
//
item.itemType = .artist
item.artistId = navigationEndpoint.browseEndpoint?.browseId
item.pageType = "MUSIC_PAGE_TYPE_ARTIST"
}else {
//
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 let maximumRetryCount = 3
//
private var retryCounts: [String: Int] = [:]
func retry(_ request: Alamofire.Request, for session: Alamofire.Session, dueTo error: any Error, completion: @escaping (Alamofire.RetryResult) -> Void) {
// id
let requestID = request.id
let currentRetryCount = retryCounts[requestID.uuidString] ?? 0
//
if currentRetryCount < maximumRetryCount, let response = request.task?.response as? HTTPURLResponse, response.statusCode == 503 {
//
retryCounts[requestID.uuidString] = currentRetryCount + 1
// 1
completion(.retryWithDelay(1))
print("请求\(requestID.uuidString)执行重复请求")
} else {
//
retryCounts[requestID.uuidString] = nil
completion(.doNotRetry)
}
}
//
func requestDidFinish(_ request: Request) {
let requestID = request.id
retryCounts[requestID.uuidString] = nil
}
}