841 lines
40 KiB
Swift
841 lines
40 KiB
Swift
//
|
||
// MPNetWorkManager.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/4/11.
|
||
//
|
||
|
||
import UIKit
|
||
import Network
|
||
import Alamofire
|
||
import AVFoundation
|
||
///预览闭包(传递一个预览模块数据组和完成状态)
|
||
typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel], _ isCompeleted:Bool) -> Void
|
||
///列表/专辑闭包(传递一个列表数据)
|
||
typealias ListRequestResultBlock = (_ list:MPPositive_ListAlbumListViewModel) -> Void
|
||
///网络状况管理器
|
||
class MP_NetWorkManager: NSObject {
|
||
//单例工具
|
||
static let shared = MP_NetWorkManager()
|
||
//MARK: - API接口
|
||
///域名链接
|
||
private let header:String = "https://music.youtube.com"
|
||
///端点
|
||
private let point:String = "/youtubei/v1"
|
||
///预览接口
|
||
private let browse:String = "/browse"
|
||
///相关接口
|
||
private let next:String = "/next"
|
||
///播放器接口
|
||
private let player:String = "/player"
|
||
//MARK: - 固定参数
|
||
//访问数据(首次首页预览时获得)
|
||
private var visitorData:String?
|
||
|
||
//预览下一阶段参数(网络请求获取)
|
||
private var continuationAndItct:(String?,String?){
|
||
willSet{
|
||
//当获取新值后,判断新值是否存在,存在则继续串行异步请求
|
||
guard let continuation = newValue.0, let itct = newValue.1, browseQueque != nil, let url = URL(string: header+point+browse) else {
|
||
//移除异步线程
|
||
browseQueque = nil
|
||
return
|
||
}
|
||
//生成新参数
|
||
var parameters:[String:Any] = [
|
||
"ctoken":continuation,
|
||
"continuation":continuation,
|
||
"type":"next",
|
||
"itct":itct,
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
"visitorData":visitorData,
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
//执行异步请求
|
||
browseQueque?.async {
|
||
[weak self] in
|
||
guard let self = self else { return }
|
||
requestPostHomeBrowse(url, parameters: parameters)
|
||
}
|
||
}
|
||
}
|
||
//MARK: - 异步队列
|
||
//串行队列-预览
|
||
private var browseQueque:DispatchQueue?
|
||
//MARK: - 闭包
|
||
///预览闭包(传递一个预览模块数据和完成状态)
|
||
var browseRequestStateBlock:BrowseRequestStateBlock?
|
||
///列表专辑闭包(传递一个请求完成的列表数据)
|
||
var listRequestResultBlock:ListRequestResultBlock?
|
||
//私有初始化
|
||
private override init() {
|
||
super.init()
|
||
}
|
||
|
||
//MARK: - 网络情况
|
||
///检查网络状况
|
||
func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) {
|
||
let monitor = NWPathMonitor()
|
||
monitor.pathUpdateHandler = { path in
|
||
switch path.status {
|
||
case .satisfied:
|
||
DispatchQueue.main.async {
|
||
guard completeHanlder != nil else {
|
||
return
|
||
}
|
||
completeHanlder!()
|
||
}
|
||
default://网络权限出现问题
|
||
DispatchQueue.main.async {
|
||
//次要处理
|
||
let alertController = UIAlertController(title: "Access network request", message: "”Musicoo“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
|
||
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
|
||
|
||
}
|
||
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in
|
||
let url = URL(string: UIApplication.openSettingsURLString)
|
||
if let url = url,UIApplication.shared.canOpenURL(url){
|
||
if #available(iOS 10, *) {
|
||
UIApplication.shared.open(url, options: [:]) { (success) in
|
||
}
|
||
}else{
|
||
UIApplication.shared.canOpenURL(url)
|
||
}
|
||
}
|
||
}
|
||
alertController.addAction(CancelAction)
|
||
alertController.addAction(OKAction)
|
||
oberve.present(alertController, animated: true, completion: nil)
|
||
}
|
||
}
|
||
}
|
||
let queue = DispatchQueue(label: "MPNetWorkManager")
|
||
monitor.start(queue: queue)
|
||
}
|
||
}
|
||
//MARK: - API请求
|
||
extension MP_NetWorkManager {
|
||
///向YouTubemusic请求预览/首页数据
|
||
func requestBrowseDatas() {
|
||
//实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列
|
||
// 实例化串行队列
|
||
browseQueque = DispatchQueue(label: "com.request.browseQueque")
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//异步执行任务
|
||
browseQueque?.async {
|
||
[weak self] in
|
||
//进行第一次请求,有预览ID,无continuation编码
|
||
guard let self = self else { return }
|
||
let parameters:[String:Any] = [
|
||
"browseId": "FEmusic_home",
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
requestPostHomeBrowse(url, parameters: parameters)
|
||
}
|
||
|
||
}
|
||
//请求首页预览内容(执行多次)
|
||
private func requestPostHomeBrowse(_ url:URL, parameters:Parameters) {
|
||
//发送post请求,并将结果转为RootBrowses
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonBrowses.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
if let task = response.request {
|
||
print("URL: \(task.url!)")
|
||
}
|
||
switch response.result {
|
||
case .success(let value):
|
||
if value.responseContext?.visitorData != nil {
|
||
self.visitorData = value.responseContext?.visitorData
|
||
}
|
||
//解析结构体
|
||
let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0]
|
||
if let content = tab?.tabRenderer?.content {
|
||
parsingBrowseContents(content)
|
||
}else if let continuationContents = value.continuationContents {
|
||
parsingBrowseContinuationContents(continuationContents)
|
||
}else {
|
||
print("Failed to parse browses content")
|
||
}
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
/// 向YouTubemusic请求列表/专辑数据,该接口调用的同样是browse预览接口
|
||
/// - Parameters:
|
||
/// - item: 需要查看的模块
|
||
func requestAlbumOrListDatas(_ item: MPPositive_BrowseItemViewModel) {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,browseId与params参数是必定携带内容
|
||
var parameters:[String:Any] = [
|
||
"browseId":(item.browseItem.browseContent.browseId ?? ""),
|
||
"params":(item.browseItem.browseContent.params ?? ""),
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
requestPostAlbumOrList(url, parameters: parameters)
|
||
}
|
||
//请求列表/专辑数据
|
||
private func requestPostAlbumOrList(_ url:URL, parameters:Parameters) {
|
||
//发送post请求,并将结果转为RootBrowses
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonListOrAlbum.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
if let task = response.request {
|
||
print("URL: \(task.url!)")
|
||
}
|
||
switch response.result {
|
||
case .success(let value):
|
||
let contents = value.contents
|
||
//创建一个列表ViewModel
|
||
let list = MPPositive_ListAlbumListViewModel()
|
||
if let content = contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content {
|
||
list.items = parsingListContents(content)
|
||
}
|
||
if let header = value.header {
|
||
list.header = .init(parsingListHeaders(header))
|
||
}
|
||
//传递列表值
|
||
guard listRequestResultBlock != nil else {
|
||
return
|
||
}
|
||
listRequestResultBlock!(list)
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
///请求Next列表(优先于Player)
|
||
/// - Parameter item: 请求的预览实体
|
||
func requestNextList(_ item: MPPositive_BrowseItemViewModel, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) {
|
||
//拼接出next路径
|
||
let path = header+point+next
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,videoId与params参数是必定携带内容
|
||
var parameters:[String:Any] = [
|
||
"playlistId":(item.browseItem.musicVideo.playListId ?? ""),
|
||
"videoId":(item.browseItem.musicVideo.videoId ?? ""),
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
//发送next列表请求
|
||
requestPostNextList(url, parameters: parameters) { listSongs in
|
||
//成功拿到列表所有歌曲(内容尚不完善)
|
||
completion(listSongs)
|
||
}
|
||
}
|
||
///请求Next歌词/相关内容
|
||
/// - Parameter item: 请求的预览实体
|
||
func requestNextLyricsAndRelated(_ item: MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
|
||
//拼接出next路径
|
||
let path = header+point+next
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,videoId与params参数是必定携带内容
|
||
var parameters:[String:Any] = [
|
||
"videoId":(item.videoId ?? ""),
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
//发送next列表歌词/相关内容请求
|
||
requestPostNextLyricsAndRelated(url, parameters: parameters) { result in
|
||
completion(result)
|
||
}
|
||
}
|
||
//请求next列表
|
||
private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) {
|
||
//发送post请求
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let value):
|
||
//转化为list值
|
||
let listSongs:[MPPositive_SongItemModel] = parsingNextList(value)
|
||
completion(listSongs)
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
//请求请求Next歌词/相关内容
|
||
private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) {
|
||
//发送post请求
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let value):
|
||
let result = parsingNextLyricsAndRelated(value)
|
||
//回掉数据
|
||
completion(result)
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
/// 请求Player(单曲/视频)播放资源
|
||
/// - Parameter item: 请求的预览实体
|
||
func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping (([String]?, [String]?) -> Void)){
|
||
//拼接出player路径
|
||
let path = header+point+player
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,videoId与params参数是必定携带内容
|
||
let parameters:[String:Any] = [
|
||
// "playlistId":(item.browseItem.musicVideo.playListId ?? ""),
|
||
"videoId":(item.videoId ?? ""),
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//当前访问版本(日期值)
|
||
"clientName": "WEB_REMIX",
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00"
|
||
]
|
||
],
|
||
"playbackContext": [
|
||
"contentPlaybackContext": [
|
||
"signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0
|
||
]
|
||
]
|
||
]
|
||
requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in
|
||
completion(resourceUlrs, coverUrls)
|
||
}
|
||
}
|
||
//请求单曲/视频
|
||
private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping(([String]?, [String]?) -> Void)) {
|
||
//发送post请求
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
if let task = response.request {
|
||
print("URL: \(task.url!)")
|
||
}
|
||
switch response.result {
|
||
case .success(let value):
|
||
parsingPlayer(value) { resourceUlrs, coverUrls in
|
||
completion(resourceUlrs, coverUrls)
|
||
}
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 请求歌词
|
||
/// - Parameter lyricId: 歌词id
|
||
func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) {
|
||
//拼接出browse路径
|
||
let path = header+point+browse
|
||
//设置url
|
||
guard let url = URL(string: path) else {
|
||
print("Url is Incorrect")
|
||
return
|
||
}
|
||
//设置参数,browseId与params参数是必定携带内容
|
||
var parameters:[String:Any] = [
|
||
"browseId":lyricId,
|
||
"prettyPrint":"false",
|
||
"context":[
|
||
"client":[
|
||
//web端
|
||
"clientName": "WEB_REMIX",
|
||
//当前访问版本(日期值)
|
||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
||
"platform":"DESKTOP",
|
||
//语言
|
||
"hl":Language_first_local,
|
||
//地址
|
||
"gl":Location_First
|
||
]
|
||
]
|
||
]
|
||
requestPostLyric(url, parameters: parameters) { lyrics in
|
||
completion(lyrics)
|
||
}
|
||
}
|
||
//请求歌词
|
||
private func requestPostLyric(_ url:URL, parameters:Parameters, completion:@escaping((String) -> Void)) {
|
||
//发送post请求
|
||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonLyrics.self) { [weak self] (response) in
|
||
guard let self = self else {return}
|
||
switch response.result {
|
||
case .success(let value):
|
||
completion(parsingLyrics(value) ?? "")
|
||
case .failure(let error):
|
||
// 请求失败,处理错误
|
||
print("Request failed: \(error)")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//MARK: - 数据解析
|
||
extension MP_NetWorkManager {
|
||
//MARK: - 浅层解析
|
||
///解析预览内容_Contents
|
||
private func parsingBrowseContents(_ content:JsonBrowses.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) {
|
||
//获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表id和视频id)
|
||
var browses:[MPPositive_BrowseModuleListViewModel] = []
|
||
var continuation:String?
|
||
var itct:String?
|
||
//循环获取音乐内容
|
||
content.sectionListRenderer?.contents?.forEach({ content in
|
||
//该循环获取预览模块内容,生成一个预览模型接收数据
|
||
let browse = MPPositive_BrowseModuleListViewModel()
|
||
browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
|
||
//循环音乐内容组
|
||
content.musicCarouselShelfRenderer?.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
})
|
||
if let nextContinuationData = content.sectionListRenderer?.continuations?.first {
|
||
continuation = nextContinuationData.nextContinuationData?.continuation
|
||
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
|
||
}
|
||
guard browseRequestStateBlock != nil else {
|
||
return
|
||
}
|
||
//传递解析数据
|
||
self.browseRequestStateBlock!(browses, (continuation == nil))
|
||
//重新赋值,直到没有下一阶段数据为止
|
||
self.continuationAndItct = (continuation,itct)
|
||
}
|
||
///解析预览内容_ContinuationContents
|
||
private func parsingBrowseContinuationContents(_ continuationContents:JsonBrowses.ContinuationContents) {
|
||
//获取预览结构体中需要的数据(模块标题;歌曲信息:封面路径,音乐标题,歌手姓名,列表id和视频id)
|
||
var browses:[MPPositive_BrowseModuleListViewModel] = []
|
||
var continuation:String?
|
||
var itct:String?
|
||
//循环获取音乐内容
|
||
continuationContents.sectionListContinuation?.contents?.forEach({ content in
|
||
//该循环获取预览模块内容,生成一个预览模型接收数据
|
||
let browse = MPPositive_BrowseModuleListViewModel()
|
||
browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
|
||
//循环音乐内容组
|
||
content.musicCarouselShelfRenderer?.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
|
||
}
|
||
})
|
||
browses.append(browse)
|
||
})
|
||
if let nextContinuationData = continuationContents.sectionListContinuation?.continuations?.first {
|
||
continuation = nextContinuationData.nextContinuationData?.continuation
|
||
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
|
||
}
|
||
guard browseRequestStateBlock != nil else {
|
||
return
|
||
}
|
||
//传递解析数据
|
||
self.browseRequestStateBlock!(browses, (continuation == nil))
|
||
//重新赋值,直到没有下一阶段数据为止
|
||
self.continuationAndItct = (continuation,itct)
|
||
}
|
||
///解析列表_Contents
|
||
private func parsingListContents(_ content:JsonListOrAlbum.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) -> [MPPositive_BrowseItemViewModel] {
|
||
var items:[MPPositive_BrowseItemViewModel] = []
|
||
//循环获取音乐内容
|
||
content.sectionListRenderer?.contents?.forEach({ content in
|
||
//循环音乐内容组
|
||
if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer {
|
||
musicPlaylistShelfRenderer.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
|
||
}
|
||
})
|
||
}else if let musicShelfRenderer = content.musicShelfRenderer{
|
||
musicShelfRenderer.contents?.forEach({ content in
|
||
//设置标题、歌手、专辑/列表
|
||
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
|
||
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
|
||
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
|
||
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
|
||
}
|
||
})
|
||
}
|
||
})
|
||
return items
|
||
}
|
||
///解析列表_Header
|
||
private func parsingListHeaders(_ header:JsonListOrAlbum.Header) -> MPPositive_ListHeaderModel {
|
||
let listHeader = MPPositive_ListHeaderModel()
|
||
if let musicDetailHeaderRenderer = header.musicDetailHeaderRenderer {
|
||
//主标题
|
||
listHeader.maintitle = musicDetailHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
//次标题
|
||
listHeader.subtitle = musicDetailHeaderRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
//三级标题
|
||
listHeader.thirdtitle = musicDetailHeaderRenderer.secondSubtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
//说明/介绍
|
||
listHeader.forDescription = musicDetailHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
//图片(默认获取最后/最大的)
|
||
listHeader.coverUrl = musicDetailHeaderRenderer.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
|
||
}else if let musicImmersiveHeaderRenderer = header.musicImmersiveHeaderRenderer {
|
||
listHeader.maintitle = musicImmersiveHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
listHeader.forDescription = musicImmersiveHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
listHeader.coverUrl = musicImmersiveHeaderRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
|
||
}
|
||
return listHeader
|
||
}
|
||
///解析相关内容_Next_中的接下来播放列表
|
||
private func parsingNextList(_ next:JsonNext) -> [MPPositive_SongItemModel] {
|
||
var array:[MPPositive_SongItemModel] = []
|
||
if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs {
|
||
if let tab = tabs.first {
|
||
//获取一张播放列表
|
||
for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() {
|
||
//生成一个音乐实体,用来装填部分数据
|
||
let song = MPPositive_SongItemModel()
|
||
song.index = index
|
||
song.title = content.playlistPanelVideoRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
song.longBylineText = content.playlistPanelVideoRenderer?.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
song.lengthText = content.playlistPanelVideoRenderer?.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
song.shortBylineText = content.playlistPanelVideoRenderer?.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
song.reviewUrls = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
|
||
song.videoId = content.playlistPanelVideoRenderer?.videoId
|
||
array.append(song)
|
||
}
|
||
}
|
||
}
|
||
return array
|
||
}
|
||
|
||
/// 解析相关内容_Next_中的歌词ID和相关内容ID(两个ID都适用于用brwose接口)
|
||
/// - Parameter next: 需要解析的next组
|
||
/// - Returns: 0位是歌词ID,1位相关内容ID
|
||
private func parsingNextLyricsAndRelated(_ next:JsonNext) -> (String?,String?){
|
||
if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs {
|
||
if tabs.count == 3 {
|
||
//歌词ID
|
||
let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId
|
||
//相关内容ID
|
||
let relatedID = tabs[2].tabRenderer?.endpoint?.browseEndpoint?.browseId
|
||
return (lyrcisId, relatedID)
|
||
}else if tabs.count == 2 {
|
||
//歌词ID
|
||
let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId
|
||
return (lyrcisId, nil)
|
||
}else {
|
||
return (nil,nil)
|
||
}
|
||
}else {
|
||
return (nil,nil)
|
||
}
|
||
}
|
||
|
||
/// 解析播放器_Player
|
||
/// - Parameters:
|
||
/// - player: player库
|
||
/// - completion: 传递两个字符串数组,第一个资源路径组,第二个是封面路径组
|
||
private func parsingPlayer(_ player:JsonPlayer, completion:@escaping(([String]?, [String]?) -> Void)){
|
||
var infos:[String]?
|
||
//解析player,获取资源库和信息库
|
||
if let videoDetails = player.videoDetails {
|
||
infos = parsingPlayerVideoDetails(videoDetails)
|
||
}
|
||
if let streamingData = player.streamingData {
|
||
parsingPlayerStreamingData(streamingData){ urls in
|
||
completion(urls,infos)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 解析播放器_StreamingData
|
||
/// - Parameter streamingData: 资源库
|
||
private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String]) -> Void)) {
|
||
var group:DispatchGroup? = DispatchGroup()
|
||
var array:[String] = []
|
||
let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
|
||
for format in allFormats {
|
||
if let signatureCipher = format.signatureCipher {
|
||
// 进入DispatchGroup,表示开始一个异步任务
|
||
group?.enter()
|
||
//获得资源签名,开始解密签名内容
|
||
parsingPlayerSignatureCipher(signatureCipher) { result in
|
||
array.append(result)
|
||
// 离开DispatchGroup,表示异步任务完成
|
||
group?.leave()
|
||
}
|
||
}
|
||
}
|
||
group?.notify(queue: .main) {
|
||
completion(array)
|
||
group = nil
|
||
}
|
||
}
|
||
///解析加密签名_SignatureCipher
|
||
private func parsingPlayerSignatureCipher(_ signatureCipher:String, completion:@escaping((String) -> Void)) {
|
||
// print("Resources-SignatureCipher:\(signatureCipher)")
|
||
//该加密资源有两段式加密,先进行百分比加密解码
|
||
let originalURLString = seperatorOff(String(signatureCipher))
|
||
//第一段加密资源为权限资源
|
||
guard let sRange = originalURLString.range(of: "s=") else {
|
||
return
|
||
}
|
||
guard let spSigRange = originalURLString.range(of: "&sp=sig", range: sRange.upperBound..<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: 返回一个元组,值分别为 videoId,title,author,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 ""
|
||
}
|
||
|
||
//MARK: - 解析具体内容形式
|
||
//解析musicResponsiveListItemRenderer(单曲/视频)
|
||
private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel {
|
||
//生成一个音乐模型接收数据
|
||
let item = MPPositive_BrowseItemModel()
|
||
item.itemType = .single
|
||
//封面路径
|
||
item.coverUrl = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
|
||
if let flexColumns = musicResponsiveListItemRenderer.flexColumns {
|
||
for (index,flexColumn) in flexColumns.enumerated() {
|
||
if index == 0 {
|
||
item.maintitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
}else if index == 1 {
|
||
item.subtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
}else {
|
||
item.thirdtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
}
|
||
var browseContent = BrowseItemContent()
|
||
flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.forEach({ run in
|
||
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
//获取到艺术家的ID
|
||
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
|
||
//专辑或则歌单
|
||
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
|
||
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
}
|
||
// if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
// browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
// }
|
||
// if run.navigationEndpoint?.browseEndpoint?.params != nil {
|
||
// browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
// }
|
||
})
|
||
item.browseContent = browseContent
|
||
}
|
||
}
|
||
//设置音乐id和列表id
|
||
if let watch = musicResponsiveListItemRenderer.overlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchEndpoint {
|
||
var musicVideo = BrowseItemMusicVideo()
|
||
musicVideo.videoId = watch.videoId
|
||
musicVideo.playListId = watch.playlistId
|
||
musicVideo.musicVideoType = watch.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType
|
||
item.musicVideo = musicVideo
|
||
}
|
||
return item
|
||
}
|
||
//解析musicResponsiveListItemRenderer(list)
|
||
private func parsingMusicTwoRowItemRenderer(_ musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer) -> MPPositive_BrowseItemModel {
|
||
//生成一个音乐模型接收数据
|
||
let item = MPPositive_BrowseItemModel()
|
||
item.itemType = .list
|
||
//封面路径
|
||
item.coverUrl = musicTwoRowItemRenderer.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
|
||
//标题
|
||
item.maintitle = musicTwoRowItemRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
//副标题
|
||
item.subtitle = musicTwoRowItemRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
|
||
var browseContent = BrowseItemContent()
|
||
musicTwoRowItemRenderer.title?.runs?.forEach({ run in
|
||
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
//获取到艺术家的ID
|
||
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
|
||
//专辑或则歌单
|
||
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
|
||
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
}
|
||
// if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
// browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
// }
|
||
// if run.navigationEndpoint?.browseEndpoint?.params != nil {
|
||
// browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
// }
|
||
})
|
||
musicTwoRowItemRenderer.subtitle?.runs?.forEach({ run in
|
||
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
//获取到艺术家的ID
|
||
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
}
|
||
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
|
||
//专辑或则歌单
|
||
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
|
||
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
}
|
||
// if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
|
||
// browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
|
||
// }
|
||
// if run.navigationEndpoint?.browseEndpoint?.params != nil {
|
||
// browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
|
||
// }
|
||
})
|
||
item.browseContent = browseContent
|
||
if let playListId = musicTwoRowItemRenderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchPlaylistEndpoint?.playlistId {
|
||
//设置列表ID
|
||
var musicVideo = BrowseItemMusicVideo()
|
||
musicVideo.playListId = playListId
|
||
item.musicVideo = musicVideo
|
||
}
|
||
if let videoId = musicTwoRowItemRenderer.navigationEndpoint?.watchEndpoint?.videoId {
|
||
//是视频
|
||
var musicVideo = BrowseItemMusicVideo()
|
||
musicVideo.videoId = videoId
|
||
item.musicVideo = musicVideo
|
||
item.itemType = .single
|
||
}
|
||
return item
|
||
}
|
||
}
|
||
//MARK: - 解析资源加密
|
||
extension MP_NetWorkManager {
|
||
///百分比解码
|
||
private func seperatorOff(_ encodedString: String) -> String {
|
||
guard let decodedString = encodedString.removingPercentEncoding else {
|
||
print("百分比解码失败")
|
||
return ""
|
||
}
|
||
return decodedString
|
||
}
|
||
}
|