对部分bug的处理以及搜索功能的初步搭建

This commit is contained in:
Mr.zhou 2024-05-13 13:55:44 +08:00
parent 733cd969de
commit 1a43357552
29 changed files with 1973 additions and 268 deletions

View File

@ -150,6 +150,12 @@
CBEE8E342BEB16BB007DA798 /* MPPositive_PlayerSilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */; }; CBEE8E342BEB16BB007DA798 /* MPPositive_PlayerSilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */; };
CBEE8E362BEB2604007DA798 /* MPPositive_PlayerLyricView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */; }; CBEE8E362BEB2604007DA798 /* MPPositive_PlayerLyricView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */; };
CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */; }; CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */; };
CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */; };
CBFECE372BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */; };
CBFECE392BF0CFFA00E07DC4 /* MPPositive_SearchSuggestionItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */; };
CBFECE3B2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */; };
CBFECE3D2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3C2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift */; };
CBFECE3F2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -299,6 +305,12 @@
CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerSilder.swift; sourceTree = "<group>"; }; CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerSilder.swift; sourceTree = "<group>"; };
CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerLyricView.swift; sourceTree = "<group>"; }; CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerLyricView.swift; sourceTree = "<group>"; };
CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SongViewModel.swift; sourceTree = "<group>"; }; CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SongViewModel.swift; sourceTree = "<group>"; };
CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemTableViewCell.swift; sourceTree = "<group>"; };
CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchSuggestions.swift; sourceTree = "<group>"; };
CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemModel.swift; sourceTree = "<group>"; };
CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionListTableViewCell.swift; sourceTree = "<group>"; };
CBFECE3C2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultShowViewController.swift; sourceTree = "<group>"; };
CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchResults.swift; sourceTree = "<group>"; };
E2C6C85BFD4CD80DBA96D149 /* Pods-MusicPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicPlayer.release.xcconfig"; path = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig"; sourceTree = "<group>"; }; E2C6C85BFD4CD80DBA96D149 /* Pods-MusicPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicPlayer.release.xcconfig"; path = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -646,6 +658,7 @@
CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */, CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */,
CBB5D31E2BDF711600CC333D /* MPPositive_SongItemModel.swift */, CBB5D31E2BDF711600CC333D /* MPPositive_SongItemModel.swift */,
CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */, CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */,
CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -757,6 +770,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CB0918A02BD26B0A006D2B39 /* MPPositive_SearchViewController.swift */, CB0918A02BD26B0A006D2B39 /* MPPositive_SearchViewController.swift */,
CBFECE3C2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift */,
); );
path = "Search搜索页"; path = "Search搜索页";
sourceTree = "<group>"; sourceTree = "<group>";
@ -771,6 +785,8 @@
CBCB50212BD118BB009760B3 /* Search */ = { CBCB50212BD118BB009760B3 /* Search */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */,
CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */,
); );
path = Search; path = Search;
sourceTree = "<group>"; sourceTree = "<group>";
@ -809,6 +825,8 @@
CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */, CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */,
CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */, CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */,
CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */, CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */,
CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */,
CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */,
); );
path = JsonStructs; path = JsonStructs;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1045,12 +1063,14 @@
CBE1CB4C2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift in Sources */, CBE1CB4C2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift in Sources */,
CBEE8E362BEB2604007DA798 /* MPPositive_PlayerLyricView.swift in Sources */, CBEE8E362BEB2604007DA798 /* MPPositive_PlayerLyricView.swift in Sources */,
CBCB4FF22BD11402009760B3 /* MPSideA_AboutViewController.swift in Sources */, CBCB4FF22BD11402009760B3 /* MPSideA_AboutViewController.swift in Sources */,
CBFECE372BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift in Sources */,
CB0918A52BD26E16006D2B39 /* MPPositive_BottomShowView.swift in Sources */, CB0918A52BD26E16006D2B39 /* MPPositive_BottomShowView.swift in Sources */,
CBB5D31F2BDF711600CC333D /* MPPositive_SongItemModel.swift in Sources */, CBB5D31F2BDF711600CC333D /* MPPositive_SongItemModel.swift in Sources */,
CBCAFB5A2BB3C2A000BC6520 /* LayoutConstraint.swift in Sources */, CBCAFB5A2BB3C2A000BC6520 /* LayoutConstraint.swift in Sources */,
CBCB4FEF2BD11402009760B3 /* MPSideA_NavigationController.swift in Sources */, CBCB4FEF2BD11402009760B3 /* MPSideA_NavigationController.swift in Sources */,
CBCB35212BD7ACE900802900 /* MPPositive_JsonBrowse.swift in Sources */, CBCB35212BD7ACE900802900 /* MPPositive_JsonBrowse.swift in Sources */,
CBCB4FF62BD11402009760B3 /* MPSideA_DeleteViewController.swift in Sources */, CBCB4FF62BD11402009760B3 /* MPSideA_DeleteViewController.swift in Sources */,
CBFECE392BF0CFFA00E07DC4 /* MPPositive_SearchSuggestionItemModel.swift in Sources */,
CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */, CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */,
CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */, CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */,
CBCB50122BD11402009760B3 /* MPSideA_Home_FirstListCollectionViewCell.swift in Sources */, CBCB50122BD11402009760B3 /* MPSideA_Home_FirstListCollectionViewCell.swift in Sources */,
@ -1070,6 +1090,7 @@
CBCB4FEA2BD11402009760B3 /* MPSideA_MusicModel.swift in Sources */, CBCB4FEA2BD11402009760B3 /* MPSideA_MusicModel.swift in Sources */,
CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */, CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */,
CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */, CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */,
CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */,
CBCB50042BD11402009760B3 /* MPSideA_HomeViewController.swift in Sources */, CBCB50042BD11402009760B3 /* MPSideA_HomeViewController.swift in Sources */,
CB09189D2BD25F63006D2B39 /* MPPositive_CustomTabBarItem.swift in Sources */, CB09189D2BD25F63006D2B39 /* MPPositive_CustomTabBarItem.swift in Sources */,
CBCB4FFC2BD11402009760B3 /* MPSideA_RenameViewController.swift in Sources */, CBCB4FFC2BD11402009760B3 /* MPSideA_RenameViewController.swift in Sources */,
@ -1108,6 +1129,7 @@
CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */, CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */,
CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */, CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */,
CBCB4FF82BD11402009760B3 /* MPSideA_MoreViewController.swift in Sources */, CBCB4FF82BD11402009760B3 /* MPSideA_MoreViewController.swift in Sources */,
CBFECE3B2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift in Sources */,
CBCB50142BD11402009760B3 /* MPSideA_Home_FourthListCollectionViewCell.swift in Sources */, CBCB50142BD11402009760B3 /* MPSideA_Home_FourthListCollectionViewCell.swift in Sources */,
CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */, CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */,
CBCB50062BD11402009760B3 /* MPSideA_PlayerViewController.swift in Sources */, CBCB50062BD11402009760B3 /* MPSideA_PlayerViewController.swift in Sources */,
@ -1123,6 +1145,7 @@
009662372BB14A5A00FCA65F /* MusicPlayer.xcdatamodeld in Sources */, 009662372BB14A5A00FCA65F /* MusicPlayer.xcdatamodeld in Sources */,
CB09189B2BD25F50006D2B39 /* MPPositive_CustomTabBarView.swift in Sources */, CB09189B2BD25F50006D2B39 /* MPPositive_CustomTabBarView.swift in Sources */,
CBE1CB502BDE4CC500701D57 /* MPPositive_ListHeaderViewModel.swift in Sources */, CBE1CB502BDE4CC500701D57 /* MPPositive_ListHeaderViewModel.swift in Sources */,
CBFECE3D2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift in Sources */,
CBCAFB5F2BB3C55500BC6520 /* DateTime.swift in Sources */, CBCAFB5F2BB3C55500BC6520 /* DateTime.swift in Sources */,
CBD958D22BB6600500666B0D /* MP_PlayerSlider.swift in Sources */, CBD958D22BB6600500666B0D /* MP_PlayerSlider.swift in Sources */,
CBCC23532BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift in Sources */, CBCC23532BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift in Sources */,
@ -1140,6 +1163,7 @@
CBCB321A2BD7578500802900 /* MP_LocationManager.swift in Sources */, CBCB321A2BD7578500802900 /* MP_LocationManager.swift in Sources */,
CBCB4FEB2BD11402009760B3 /* MPSideA_MusicViewModel.swift in Sources */, CBCB4FEB2BD11402009760B3 /* MPSideA_MusicViewModel.swift in Sources */,
CBCB4FF02BD11402009760B3 /* MPSideA_PresentationController.swift in Sources */, CBCB4FF02BD11402009760B3 /* MPSideA_PresentationController.swift in Sources */,
CBFECE3F2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift in Sources */,
CBC6874B2BC2B0710023ECA6 /* String.swift in Sources */, CBC6874B2BC2B0710023ECA6 /* String.swift in Sources */,
CBD3135F2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift in Sources */, CBD3135F2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift in Sources */,
); );

View File

@ -49,6 +49,13 @@
ReferencedContainer = "container:MusicPlayer.xcodeproj"> ReferencedContainer = "container:MusicPlayer.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 B

After

Width:  |  Height:  |  Size: 469 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1597880487@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1597880487@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1597880488@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1597880488@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

View File

@ -71,10 +71,18 @@ extension NotificationCenter{
case positive_browses_reload case positive_browses_reload
/// ///
case positive_list_reload case positive_list_reload
///
case switch_player_status
///
case pup_bottom_show
/// ///
case pup_player_vc case pup_player_vc
/// ///
case positive_player_reload case positive_player_reload
///
case player_type_switch
///
case player_delete_list
} }
} }
} }

View File

@ -112,4 +112,14 @@ func createLabel(_ text:String? = nil, font:UIFont, textColor:UIColor, textAlign
label.numberOfLines = lines label.numberOfLines = lines
return label return label
} }
///
func switchPlayTypeBtnIcon(_ btn:UIButton) {
switch MP_PlayerManager.shared.getPlayType() {
case .normal://
btn.setBackgroundImage(UIImage(named: "List_NormolPlay'logo"), for: .normal)
case .random://
btn.setBackgroundImage(UIImage(named: "Player_Shuffle'logo"), for: .normal)
case .single://
btn.setBackgroundImage(UIImage(named: "Player_Single'logo"), for: .normal)
}
}

View File

@ -28,6 +28,10 @@ class MP_NetWorkManager: NSObject {
private let next:String = "/next" private let next:String = "/next"
/// ///
private let player:String = "/player" private let player:String = "/player"
///
private let suggestions:String = "/music/get_search_suggestions"
///
private let search = "/search"
//MARK: - //MARK: -
//访 //访
private var visitorData:String? private var visitorData:String?
@ -42,7 +46,7 @@ class MP_NetWorkManager: NSObject {
return return
} }
// //
var parameters:[String:Any] = [ let parameters:[String:Any] = [
"ctoken":continuation, "ctoken":continuation,
"continuation":continuation, "continuation":continuation,
"type":"next", "type":"next",
@ -127,6 +131,7 @@ class MP_NetWorkManager: NSObject {
} }
//MARK: - API //MARK: - API
extension MP_NetWorkManager { extension MP_NetWorkManager {
//MARK: -
///YouTubemusic/ ///YouTubemusic/
func requestBrowseDatas() { func requestBrowseDatas() {
//continuationcontinuation, //continuationcontinuation,
@ -193,6 +198,7 @@ extension MP_NetWorkManager {
} }
} }
} }
//MARK: -
/// YouTubemusic/browse /// YouTubemusic/browse
/// - Parameters: /// - Parameters:
/// - item: /// - item:
@ -205,7 +211,7 @@ extension MP_NetWorkManager {
return return
} }
//browseIdparams //browseIdparams
var parameters:[String:Any] = [ let parameters:[String:Any] = [
"browseId":(item.browseItem.browseContent.browseId ?? ""), "browseId":(item.browseItem.browseContent.browseId ?? ""),
"params":(item.browseItem.browseContent.params ?? ""), "params":(item.browseItem.browseContent.params ?? ""),
"prettyPrint":"false", "prettyPrint":"false",
@ -213,6 +219,7 @@ extension MP_NetWorkManager {
"client":[ "client":[
//web //web
"clientName": "WEB_REMIX", "clientName": "WEB_REMIX",
"visitorData":visitorData,
//访 //访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP", "platform":"DESKTOP",
@ -255,6 +262,8 @@ extension MP_NetWorkManager {
} }
} }
} }
//MARK: -
///NextPlayer ///NextPlayer
/// - Parameter item: /// - Parameter item:
func requestNextList(_ item: MPPositive_BrowseItemViewModel, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) { func requestNextList(_ item: MPPositive_BrowseItemViewModel, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) {
@ -266,7 +275,7 @@ extension MP_NetWorkManager {
return return
} }
//videoIdparams //videoIdparams
var parameters:[String:Any] = [ let parameters:[String:Any] = [
"playlistId":(item.browseItem.musicVideo.playListId ?? ""), "playlistId":(item.browseItem.musicVideo.playListId ?? ""),
"videoId":(item.browseItem.musicVideo.videoId ?? ""), "videoId":(item.browseItem.musicVideo.videoId ?? ""),
"prettyPrint":"false", "prettyPrint":"false",
@ -274,6 +283,7 @@ extension MP_NetWorkManager {
"client":[ "client":[
//web //web
"clientName": "WEB_REMIX", "clientName": "WEB_REMIX",
"visitorData":visitorData,
//访 //访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP", "platform":"DESKTOP",
@ -290,39 +300,6 @@ extension MP_NetWorkManager {
completion(listSongs) 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
}
//videoIdparams
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 //next
private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) { private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) {
//post //post
@ -339,6 +316,40 @@ extension MP_NetWorkManager {
} }
} }
} }
///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":Location_First
]
]
]
//next/
requestPostNextLyricsAndRelated(url, parameters: parameters) { result in
completion(result)
}
}
//Next/ //Next/
private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) { private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) {
//post //post
@ -355,6 +366,8 @@ extension MP_NetWorkManager {
} }
} }
} }
//MARK: - player
/// Player(/) /// Player(/)
/// - Parameter item: /// - Parameter item:
func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping (([String]?, [String]?) -> Void)){ func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping (([String]?, [String]?) -> Void)){
@ -406,7 +419,7 @@ extension MP_NetWorkManager {
} }
} }
} }
//MARK: -
/// ///
/// - Parameter lyricId: id /// - Parameter lyricId: id
func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) { func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) {
@ -418,13 +431,14 @@ extension MP_NetWorkManager {
return return
} }
//browseIdparams //browseIdparams
var parameters:[String:Any] = [ let parameters:[String:Any] = [
"browseId":lyricId, "browseId":lyricId,
"prettyPrint":"false", "prettyPrint":"false",
"context":[ "context":[
"client":[ "client":[
//web //web
"clientName": "WEB_REMIX", "clientName": "WEB_REMIX",
"visitorData":visitorData,
//访 //访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP", "platform":"DESKTOP",
@ -453,6 +467,105 @@ extension MP_NetWorkManager {
} }
} }
} }
//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":Location_First
]
]
]
requestPostSearchSuggestions(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchSuggestions(_ url:URL, parameters:Parameters, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) {
//post
AF.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 requestSearchResults(_ text:String) {
//
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":Location_First
]
]
]
//
}
//
private func requestPostSearchResults(_ url:URL, parameters:Parameters) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchResults.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 {
parsingSearchResults(contents)
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
} }
//MARK: - //MARK: -
extension MP_NetWorkManager { extension MP_NetWorkManager {
@ -581,19 +694,21 @@ extension MP_NetWorkManager {
if let tab = tabs.first { if let tab = tabs.first {
// //
for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() { for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() {
if let playlistPanelVideoRenderer = content.playlistPanelVideoRenderer {
// //
let song = MPPositive_SongItemModel() let song = MPPositive_SongItemModel()
song.index = index song.index = index
song.title = content.playlistPanelVideoRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.title = playlistPanelVideoRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.longBylineText = content.playlistPanelVideoRenderer?.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.longBylineText = playlistPanelVideoRenderer.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.lengthText = content.playlistPanelVideoRenderer?.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.lengthText = playlistPanelVideoRenderer.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.shortBylineText = content.playlistPanelVideoRenderer?.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")}) song.shortBylineText = playlistPanelVideoRenderer.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.reviewUrls = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""}) song.reviewUrls = playlistPanelVideoRenderer.thumbnail?.thumbnails?.map({$0.url ?? ""})
song.videoId = content.playlistPanelVideoRenderer?.videoId song.videoId = playlistPanelVideoRenderer.videoId
array.append(song) array.append(song)
} }
} }
} }
}
return array return array
} }
@ -713,6 +828,60 @@ extension MP_NetWorkManager {
return "" return ""
} }
/// _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)
}
}
///_SearchResults
private func parsingSearchResults(_ contents:[JsonSearchResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) {
contents.forEach { content in
//
if let musicCardShelfRenderer = content.musicCardShelfRenderer {
//
}else {
//
}
}
}
//MARK: - //MARK: -
//musicResponsiveListItemRenderer/ //musicResponsiveListItemRenderer/
private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel { private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel {
@ -742,12 +911,6 @@ extension MP_NetWorkManager {
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params 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 item.browseContent = browseContent
} }
@ -785,12 +948,6 @@ extension MP_NetWorkManager {
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params 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 musicTwoRowItemRenderer.subtitle?.runs?.forEach({ run in
if run.navigationEndpoint?.browseEndpoint?.browseId != nil { if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
@ -803,12 +960,6 @@ extension MP_NetWorkManager {
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params 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 item.browseContent = browseContent
if let playListId = musicTwoRowItemRenderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchPlaylistEndpoint?.playlistId { if let playListId = musicTwoRowItemRenderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchPlaylistEndpoint?.playlistId {

View File

@ -43,21 +43,60 @@ typealias MP_PlayTimerStopAction = () -> Void
/// ///
typealias MP_PlayTimerEditEndAction = () -> Void typealias MP_PlayTimerEditEndAction = () -> Void
/// ///
class MP_PlayerManager{ class MP_PlayerManager:NSObject{
/// ///
static let shared = MP_PlayerManager() static let shared = MP_PlayerManager()
/// ///
private var player:AVPlayer = AVPlayer() private var player:AVPlayer = AVPlayer()
///load ///load
var loadPlayer:MPPositive_PlayerLoadViewModel! var loadPlayer:MPPositive_PlayerLoadViewModel!{
didSet{
if loadPlayer != nil {
//load
NotificationCenter.notificationKey.post(notificationName: .pup_bottom_show)
}else {
//load
NotificationCenter.notificationKey.post(notificationName: .player_delete_list)
}
}
}
// //
private var playState:MP_PlayerStateType = .Null private var playState:MP_PlayerStateType = .Null{
didSet{
//
NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState)
}
}
///
func getPlayState() -> MP_PlayerStateType {
return playState
}
///
private var playType:MP_PlayerPlayType = .normal{
didSet{
//
NotificationCenter.notificationKey.post(notificationName: .player_type_switch)
}
}
///
func getPlayType() -> MP_PlayerPlayType {
return playType
}
///
/// - Parameter type:
func setPlayType(_ type:MP_PlayerPlayType) {
playType = type
if playType == .random {
}
}
/// ///
private var startActionBlock:MP_PlayTimerStartAction! private var startActionBlock:MP_PlayTimerStartAction!
/// ///
private var runActionBlock:MP_PlayTimerRunAction! var runActionBlock:MP_PlayTimerRunAction!
private init() { private override init() {
super.init()
// //
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload) NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload)
@ -65,16 +104,17 @@ class MP_PlayerManager{
deinit { deinit {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
///
func getPlayState() -> MP_PlayerStateType {
return playState
}
/// ///
/// - Parameters: /// - Parameters:
/// - startAction: /// - startAction:
/// - runAction: /// - runAction:
/// - endAction: /// - endAction:
func play(startAction:MP_PlayTimerStartAction? = nil, runAction:MP_PlayTimerRunAction? = nil) { func play(startAction:MP_PlayTimerStartAction? = nil) {
guard loadPlayer != nil, loadPlayer.currentVideo != nil else {
//
print("Player No Data")
return
}
// //
switch playState { switch playState {
case .Null:// case .Null://
@ -88,10 +128,7 @@ class MP_PlayerManager{
if startAction != nil { if startAction != nil {
startActionBlock = startAction startActionBlock = startAction
} }
if runAction != nil { //playerItem
runActionBlock = runAction
}
//PlayerItem
player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem) player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem)
//0 //0
player.seek(to: .zero) player.seek(to: .zero)
@ -102,12 +139,6 @@ class MP_PlayerManager{
guard let self = self else { return } guard let self = self else { return }
// //
let currentDuration = CMTimeGetSeconds(time) let currentDuration = CMTimeGetSeconds(time)
//current0
if currentDuration == 0 {
if startActionBlock != nil {
startActionBlock!()
}
}
// //
let maxDuration = getMusicDuration() let maxDuration = getMusicDuration()
if maxDuration.isNaN == false { if maxDuration.isNaN == false {
@ -120,17 +151,65 @@ class MP_PlayerManager{
} }
} }
}) })
// //Video
if loadPlayer.currentVideo.isPreloading == true {
//
print("开始播放音乐-\(loadPlayer.currentVideo.title ?? "")")
player.play() player.play()
playState = .Playing playState = .Playing
// //
// let set = Set(loadPlayer.listViewVideos.filter({$0.index != loadPlayer.currentVideo.index})) if startActionBlock != nil {
// set.forEach { item in startActionBlock!()
// if item.canBePreloaded() == false { }
}else {
//KVO
//currentVideoresourcePlayerItemKVO,itemstatusplaybackLikelyToKeepUp
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
}
//VideoItem
// for item in loadPlayer.listViewVideos where item.song.videoId != loadPlayer.currentVideo.song.videoId {
// item.preloadPlayerItem() // item.preloadPlayerItem()
// }
// } // }
} }
//KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else {
return
}
//keyPath
switch keyPath {
case "status"://playerItem
if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue {
//statuVlaueplayerItem
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 已经准备好播放")
}else {
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 未做好准备播放")
//
}
case "playbackLikelyToKeepUp"://
if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true {
//
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 有足够的缓存来播放")
//
if playState != .Playing {
//
print("开始播放音乐-\(loadPlayer.currentVideo.title ?? "")")
player.play()
playState = .Playing
//
if startActionBlock != nil {
startActionBlock!()
}
}else {
//
}
}
default:
break
}
}
//MARK: -
/// ///
private func getMusicDuration() -> TimeInterval { private func getMusicDuration() -> TimeInterval {
return CMTimeGetSeconds(player.currentItem?.duration ?? .zero) return CMTimeGetSeconds(player.currentItem?.duration ?? .zero)
@ -143,9 +222,16 @@ class MP_PlayerManager{
guard playState == .Playing else { guard playState == .Playing else {
return return
} }
switch playType {
case .single:
playState = .Null
//
player.seek(to: CMTime.zero)
default:
// //
nextEvent() nextEvent()
} }
}
//MARK: - //MARK: -
/// ///
private func pause() { private func pause() {
@ -211,7 +297,7 @@ class MP_PlayerManager{
//MARK: - //MARK: -
// //
private func stop() { func stop() {
// //
guard playState != .Null else { guard playState != .Null else {
// //
@ -224,40 +310,101 @@ class MP_PlayerManager{
//MARK: - / //MARK: - /
/// ///
func previousEvent() { func previousEvent() {
// //
let targetIndex = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) playState = .Null
if targetIndex == 0 { var nextIndex:Int = 0
// //
switch playType {
case .random://
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index - 1
}
}
//next
if nextIndex < 0 {
//
let last = loadPlayer.randomVideos.last
loadPlayer.improveData(last?.videoId ?? "")
}else {
//
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
}
default://
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index - 1
}
}
//next
if nextIndex < 0 {
//
let last = loadPlayer.songVideos.last let last = loadPlayer.songVideos.last
loadPlayer.improveData(last?.videoId ?? "") loadPlayer.improveData(last?.videoId ?? "")
}else { }else {
//,ID //
let song = loadPlayer.songVideos.first(where: {$0.index == (loadPlayer.currentVideo.index-1)}) let song = loadPlayer.songVideos[nextIndex]
loadPlayer.improveData(song?.videoId ?? "") loadPlayer.improveData(song.videoId ?? "")
}
} }
} }
/// ///
func nextEvent() { func nextEvent() {
// //
let targetIndex = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) playState = .Null
if targetIndex == (loadPlayer.listViewVideos.count - 1) { var nextIndex:Int = 0
// switch playType {
case .random:
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index + 1
}
}
//
if nextIndex > (loadPlayer.randomVideos.count-1) {
//
let first = loadPlayer.randomVideos.first
loadPlayer.improveData(first?.videoId ?? "")
}else {
//,ID
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
}
default:
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index + 1
}
}
//
if nextIndex > (loadPlayer.songVideos.count-1) {
//
let first = loadPlayer.songVideos.first let first = loadPlayer.songVideos.first
loadPlayer.improveData(first?.videoId ?? "") loadPlayer.improveData(first?.videoId ?? "")
}else { }else {
//,ID //,ID
let song = loadPlayer.songVideos.first(where: {$0.index == (loadPlayer.currentVideo.index+1)}) let song = loadPlayer.songVideos[nextIndex]
loadPlayer.improveData(song?.videoId ?? "") loadPlayer.improveData(song.videoId ?? "")
}
} }
} }
/// ///
@objc private func userSwitchCurrentVideoAction(_ sender:Notification) { @objc private func userSwitchCurrentVideoAction(_ sender:Notification) {
//
playState = .Null
//
if let video = sender.object as? MPPositive_SongViewModel {
video.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
}
if loadPlayer.currentVideo != nil { if loadPlayer.currentVideo != nil {
// //
play(startAction: startActionBlock,runAction: runActionBlock) play(startAction: startActionBlock)
}else {
//
} }
} }

View File

@ -0,0 +1,622 @@
//
// MPPositive_JsonSearchResults.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
///
struct JsonSearchResults: Codable {
let contents:Contents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
}
struct Contents: Codable {
let tabbedSearchResultsRenderer:TabbedSearchResultsRenderer?
enum CodingKeys: String, CodingKey {
case tabbedSearchResultsRenderer = "tabbedSearchResultsRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabbedSearchResultsRenderer = try values.decodeIfPresent(TabbedSearchResultsRenderer.self, forKey: .tabbedSearchResultsRenderer)
}
struct TabbedSearchResultsRenderer: Codable {
let tabs:[Tab]?
enum CodingKeys: String, CodingKey {
case tabs = "tabs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabs = try values.decodeIfPresent([Tab].self, forKey: .tabs)
}
struct Tab:Codable {
let tabRenderer:TabRenderer?
enum CodingKeys: String, CodingKey {
case tabRenderer = "tabRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabRenderer = try values.decodeIfPresent(TabRenderer.self, forKey: .tabRenderer)
}
struct TabRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
//MARK: -
struct Content: Codable {
///
let musicCardShelfRenderer:MusicCardShelfRenderer?
///
let musicShelfRenderer:MusicShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicCardShelfRenderer = "musicCardShelfRenderer"
case musicShelfRenderer = "musicShelfRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCardShelfRenderer = try values.decodeIfPresent(MusicCardShelfRenderer.self, forKey: .musicCardShelfRenderer)
musicShelfRenderer = try values.decodeIfPresent(MusicShelfRenderer.self, forKey: .musicShelfRenderer)
}
//MARK: -
///
struct MusicCardShelfRenderer: Codable {
///
let thumbnail:Thumbnail?
///(ID)
let title:Title?
///
let subtitle:Subtitle?
///
let header:Header?
///,0
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case title = "title"
case subtitle = "subtitle"
case header = "header"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
title = try values.decodeIfPresent(Title.self, forKey: .title)
subtitle = try values.decodeIfPresent(Subtitle.self, forKey: .subtitle)
header = try values.decodeIfPresent(Header.self, forKey: .header)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
///
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
///
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
///
let watchEndpoint:WatchEndpoint?
///
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct WatchEndpoint: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
struct BrowseEndpoint: Codable {
let browseId:String?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
}
}
}
}
}
///
struct Subtitle: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
///
struct Header: Codable {
let musicCardShelfHeaderBasicRenderer:MusicCardShelfHeaderBasicRenderer?
enum CodingKeys: String, CodingKey {
case musicCardShelfHeaderBasicRenderer = "musicCardShelfHeaderBasicRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCardShelfHeaderBasicRenderer = try values.decodeIfPresent(MusicCardShelfHeaderBasicRenderer.self, forKey: .musicCardShelfHeaderBasicRenderer)
}
struct MusicCardShelfHeaderBasicRenderer: Codable {
let title:Title?
enum CodingKeys: String, CodingKey {
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
}
struct Title:Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
///
struct Content: Codable {
let musicResponsiveListItemRenderer:MusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(MusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
struct MusicResponsiveListItemRenderer: Codable {
///
let thumbnail:Thumbnail?
///(0)
let flexColumns:[FlexColumn]?
////IDplaylistItemData/
let playlistItemData:PlaylistItemData?
////IDnavigationEndpoint/
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case flexColumns = "flexColumns"
case playlistItemData = "playlistItemData"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
playlistItemData = try values.decodeIfPresent(PlaylistItemData.self, forKey: .playlistItemData)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
struct FlexColumn: Codable {
let musicResponsiveListItemFlexColumnRenderer:MusicResponsiveListItemFlexColumnRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemFlexColumnRenderer = "musicResponsiveListItemFlexColumnRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemFlexColumnRenderer = try values.decodeIfPresent(MusicResponsiveListItemFlexColumnRenderer.self, forKey: .musicResponsiveListItemFlexColumnRenderer)
}
struct MusicResponsiveListItemFlexColumnRenderer: Codable {
let text:Text?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(Text.self, forKey: .text)
}
struct Text: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
struct PlaylistItemData: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct BrowseEndpoint: Codable {
let browseId:String?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
}
}
}
}
}
}
///
struct MusicShelfRenderer: Codable {
///
let title:Title?
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case title = "title"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Title:Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct Content: Codable {
let musicResponsiveListItemRenderer:MusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(MusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
struct MusicResponsiveListItemRenderer: Codable {
///
let thumbnail:Thumbnail?
///(0)
let flexColumns:[FlexColumn]?
////IDplaylistItemData/
let playlistItemData:PlaylistItemData?
////IDnavigationEndpoint/
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case flexColumns = "flexColumns"
case playlistItemData = "playlistItemData"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
playlistItemData = try values.decodeIfPresent(PlaylistItemData.self, forKey: .playlistItemData)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
struct FlexColumn: Codable {
let musicResponsiveListItemFlexColumnRenderer:MusicResponsiveListItemFlexColumnRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemFlexColumnRenderer = "musicResponsiveListItemFlexColumnRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemFlexColumnRenderer = try values.decodeIfPresent(MusicResponsiveListItemFlexColumnRenderer.self, forKey: .musicResponsiveListItemFlexColumnRenderer)
}
struct MusicResponsiveListItemFlexColumnRenderer: Codable {
let text:Text?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(Text.self, forKey: .text)
}
struct Text: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
struct PlaylistItemData: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct BrowseEndpoint: Codable {
let browseId:String?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
}
}
}
}
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,190 @@
//
// MPPositive_JsonSearchSuggestions.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
struct JsonSearchSuggestions: Codable {
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content: Codable {
let searchSuggestionsSectionRenderer:SearchSuggestionsSectionRenderer?
enum CodingKeys: String, CodingKey {
case searchSuggestionsSectionRenderer = "searchSuggestionsSectionRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
searchSuggestionsSectionRenderer = try values.decodeIfPresent(SearchSuggestionsSectionRenderer.self, forKey: .searchSuggestionsSectionRenderer)
}
struct SearchSuggestionsSectionRenderer: Codable {
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content: Codable {
///
let searchSuggestionRenderer:SearchSuggestionRenderer?
///
let musicResponsiveListItemRenderer:MusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case searchSuggestionRenderer = "searchSuggestionRenderer"
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
searchSuggestionRenderer = try values.decodeIfPresent(SearchSuggestionRenderer.self, forKey: .searchSuggestionRenderer)
musicResponsiveListItemRenderer = try values.decodeIfPresent(MusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
//MARK: -
struct SearchSuggestionRenderer: Codable {
let suggestion:Suggestion?
enum CodingKeys: String, CodingKey {
case suggestion = "suggestion"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
suggestion = try values.decodeIfPresent(Suggestion.self, forKey: .suggestion)
}
struct Suggestion: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
//MARK: -
struct MusicResponsiveListItemRenderer: Codable {
let thumbnail:Thumbnail?
let flexColumns:[FlexColumn]?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case flexColumns = "flexColumns"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
}
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
///
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
//MARK: -
///
struct Thumbnails: Codable {
///
let url:String?
let width:CGFloat?
let height:CGFloat?
enum CodingKeys: String, CodingKey {
case url = "url"
case width = "width"
case height = "height"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
width = try values.decodeIfPresent(CGFloat.self, forKey: .width)
height = try values.decodeIfPresent(CGFloat.self, forKey: .height)
}
}
}
}
}
struct FlexColumn: Codable {
let musicResponsiveListItemFlexColumnRenderer:MusicResponsiveListItemFlexColumnRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemFlexColumnRenderer = "musicResponsiveListItemFlexColumnRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemFlexColumnRenderer = try values.decodeIfPresent(MusicResponsiveListItemFlexColumnRenderer.self, forKey: .musicResponsiveListItemFlexColumnRenderer)
}
struct MusicResponsiveListItemFlexColumnRenderer: Codable {
let text:Text?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(Text.self, forKey: .text)
}
struct Text: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,17 @@
//
// MPPositive_SearchSuggestionsModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
///
class MPPositive_SearchSuggestionItemModel: NSObject {
///
var title:String?
///
var subtitle:String?
///
var reviewUrls:[String]?
}

View File

@ -23,7 +23,7 @@ class MPPositive_SongViewModel: NSObject {
var lyrics:String? var lyrics:String?
///ID ///ID
var relatedId:String? var relatedId:String?
/// ///
var isPreloading:Bool? var isPreloading:Bool?
/// ///
var isCollection:Bool? var isCollection:Bool?
@ -39,10 +39,9 @@ class MPPositive_SongViewModel: NSObject {
configure() configure()
} }
deinit { deinit {
if self.isKvo == true { if isKvo == true {
// resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
self.resourcePlayerItem.removeObserver(self, forKeyPath: "status") isKvo = false
self.isKvo = false
} }
} }
// //
@ -83,46 +82,41 @@ class MPPositive_SongViewModel: NSObject {
} }
//MARK: - //MARK: -
// //
func canBePreloaded() -> Bool { private func canBePreloaded() -> Bool {
return self.isPreloading ?? false return self.isPreloading ?? false
} }
// ///
func preloadPlayerItem() { func preloadPlayerItem() {
if isKvo == false { //
//playerItem guard canBePreloaded() == false else {
self.resourcePlayerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil) print("\(title ?? "")已经预加载了")
isKvo = true return
} }
print(resourcePlayerItem.status)
// //
self.resourcePlayerItem.seek(to: .zero) {[weak self] _ in self.resourcePlayerItem.seek(to: .zero) {[weak self] _ in
guard let self = self else {return} guard let self = self else {return}
// 使 dispatchSource //
let timer = DispatchSource.makeTimerSource(queue: .global(qos: .background)) DispatchQueue.global(qos: .background).async {
// if self.isKvo == false {
timer.schedule(deadline: .now(), repeating: .seconds(1)) //playerItem
timer.setEventHandler{ self.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
if self.resourcePlayerItem.isPlaybackLikelyToKeepUp { self.isKvo = true
//
self.isPreloading = true
if self.isKvo == true {
//
self.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
self.isKvo = false
timer.cancel()
} }
} }
} }
timer.resume()
}
} }
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status" { if keyPath == "playbackLikelyToKeepUp" {
if let status = change?[.newKey] as? Int, status == Int(AVPlayerItem.Status.readyToPlay.rawValue) { if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true {
// //KVO
print("\(self.title ?? "") is Ok") print("\(title ?? "")预加载到合适的进度")
}else { if isKvo == true {
// resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
print("\(self.title ?? "") is bad") isKvo = false
}
//
isPreloading = true
} }
} }
} }

View File

@ -8,17 +8,24 @@
import UIKit import UIKit
///ViewModel ///ViewModel
class MPPositive_PlayerLoadViewModel: NSObject { class MPPositive_PlayerLoadViewModel: NSObject {
/// ///
var songVideos:[MPPositive_SongItemModel]! var songVideos:[MPPositive_SongItemModel]!
///
var randomVideos:[MPPositive_SongItemModel]!
///ViewModel ///ViewModel
var currentVideo:MPPositive_SongViewModel!{ var currentVideo:MPPositive_SongViewModel!{
didSet{ willSet{
if newValue != nil {
if currentVideo != nil { if currentVideo != nil {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload, object: currentVideo)
}else {
//UI //UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload) NotificationCenter.notificationKey.post(notificationName: .positive_player_reload)
} }
} }
} }
}
///ID ///ID
var currentVideoId:String! var currentVideoId:String!
///ViewModel ///ViewModel
@ -31,35 +38,39 @@ class MPPositive_PlayerLoadViewModel: NSObject {
/// - firstVideoId: /// - firstVideoId:
init(_ songs:[MPPositive_SongItemModel], currentVideoId: String) { init(_ songs:[MPPositive_SongItemModel], currentVideoId: String) {
super.init() super.init()
//
self.songVideos = songs self.songVideos = songs
//
self.randomVideos = self.songVideos.shuffled()
self.listViewVideos = [] self.listViewVideos = []
self.currentVideoId = currentVideoId self.currentVideoId = currentVideoId
} }
///Video23VideoViewModel, ///Video23VideoViewModel,
func improveData(_ targetVideoId:String) { func improveData(_ targetVideoId:String) {
guard let targetVideo = self.songVideos.first(where: {$0.videoId == targetVideoId}) else { //targetVideoId
guard let targetIndex = self.songVideos.firstIndex(where: {$0.videoId == targetVideoId}) else {
return return
} }
//Video //Video
var array:[MPPositive_SongItemModel] = [] var array:[MPPositive_SongItemModel] = []
array.append(self.songVideos[targetIndex])
// //
if let previous = self.songVideos.first(where: {$0.index == (targetVideo.index-1)}) { let previousIndex = targetIndex-1
array.append(previous) if previousIndex >= 0 {
array.append(self.songVideos[previousIndex])
} }
array.append(targetVideo) let nextIndex = targetIndex+1
// if nextIndex < songVideos.count {
if let next = self.songVideos.first(where: {$0.index == (targetVideo.index+1)}) { array.append(self.songVideos[nextIndex])
array.append(next)
} }
//ViewModelvideo //ViewModelvideo
let videoIDs = Set(listViewVideos.map({$0.song.videoId})) let videoIDs = Set(listViewVideos.map({$0.song.videoId}))
//videoID, //videoID,
array = array.filter({!videoIDs.contains($0.videoId)}) array = array.filter({!videoIDs.contains($0.videoId)})
group = DispatchGroup() group = DispatchGroup()
var numbers = 0
//, //,
array.forEach { item in for item in array {
group?.enter() group?.enter()
//idid //idid
improveDataforLycirsAndRelated(item) {[weak self] (result) in improveDataforLycirsAndRelated(item) {[weak self] (result) in
@ -82,36 +93,22 @@ class MPPositive_PlayerLoadViewModel: NSObject {
self.listViewVideos = self.listViewVideos.sorted(by: {$0.index < $1.index}) self.listViewVideos = self.listViewVideos.sorted(by: {$0.index < $1.index})
// //
self.currentVideo = self.listViewVideos.first(where: {$0.song.videoId == targetVideoId}) self.currentVideo = self.listViewVideos.first(where: {$0.song.videoId == targetVideoId})
self.group = nil
}) })
} }
///songlistViewVideosindex ///songlistViewVideosindex
func removeData(_ targetVideoId:String) { func removeData(_ targetVideoId:String) {
let targetIndex = songVideos.firstIndex(where: {$0.videoId == targetVideoId}) let targetIndex = songVideos.firstIndex(where: {$0.videoId == targetVideoId}) ?? 0
//listView //listView
songVideos = songVideos.filter({$0.videoId != targetVideoId}) songVideos = songVideos.filter({$0.videoId != targetVideoId})
randomVideos = randomVideos.filter({$0.videoId != targetVideoId})
listViewVideos = listViewVideos.filter({$0.song.videoId != targetVideoId}) listViewVideos = listViewVideos.filter({$0.song.videoId != targetVideoId})
/// if currentVideo != nil {
for (index, item) in songVideos.enumerated() {
item.index = index
}
listViewVideos.forEach { listModel in
songVideos.forEach { song in
if listModel.song.videoId == song.videoId {
listModel.index = song.index
listModel.song.index = song.index
}
if currentVideo.song.videoId == song.videoId {
currentVideo.index = song.index
currentVideo.song.index = song.index
}
}
}
// //
if currentVideo.song.videoId == targetVideoId { if currentVideo.song.videoId == targetVideoId {
if let videoId = songVideos.first(where: {$0.index == targetIndex})?.videoId { //targetIndex
// if targetIndex < songVideos.count {
let videoId = songVideos[targetIndex].videoId ?? ""
improveData(videoId) improveData(videoId)
}else { }else {
// //
@ -120,6 +117,7 @@ class MPPositive_PlayerLoadViewModel: NSObject {
} }
} }
} }
}
///nextIDID ///nextIDID
private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) { private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
//next //next

View File

@ -7,7 +7,7 @@
import UIKit import UIKit
///btabBar ///btabBar
class MPPositive_TabBarController: UITabBarController { class MPPositive_TabBarController: UITabBarController, UIViewControllerTransitioningDelegate {
//tabBar //tabBar
private lazy var customTabBar:MPPositive_CustomTabBar = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 72*width)) private lazy var customTabBar:MPPositive_CustomTabBar = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 72*width))
private lazy var bottomView:MPPositive_BottomShowView = .init(frame: .init(x: 0, y: 0, width: 351, height: 82)) private lazy var bottomView:MPPositive_BottomShowView = .init(frame: .init(x: 0, y: 0, width: 351, height: 82))
@ -36,6 +36,17 @@ class MPPositive_TabBarController: UITabBarController {
make.width.equalTo(351*width) make.width.equalTo(351*width)
make.height.equalTo(82*width) make.height.equalTo(82*width)
} }
bottomView.showListBlock = {
[weak self] in
if MP_PlayerManager.shared.loadPlayer != nil {
MPPositive_ModalType = .PlayerList
let listVC = MPPositive_PlayerListShowViewController()
listVC.transitioningDelegate = self
listVC.modalPresentationStyle = .custom
self?.present(listVC, animated: true)
}
}
bottomView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pupPlayerAction)))
addNotification() addNotification()
} }
// //
@ -43,12 +54,19 @@ class MPPositive_TabBarController: UITabBarController {
// //
NotificationCenter.notificationKey.add(observer: self, selector: #selector(switchAction(_:)), notificationName: .switch_tabBarItem) NotificationCenter.notificationKey.add(observer: self, selector: #selector(switchAction(_:)), notificationName: .switch_tabBarItem)
// //
NotificationCenter.notificationKey.add(observer: self, selector: #selector(pupPlayerAction(_:)), notificationName: .pup_player_vc) NotificationCenter.notificationKey.add(observer: self, selector: #selector(pupPlayerAction), notificationName: .pup_player_vc)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(bottomAnimationAction(_:)), notificationName: .pup_bottom_show)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(bottomAnimationAction(_:)), notificationName: .player_delete_list)
} }
deinit { deinit {
// //
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
//
} }
//MARK: - //MARK: -
extension MPPositive_TabBarController { extension MPPositive_TabBarController {
@ -58,12 +76,33 @@ extension MPPositive_TabBarController {
selectedIndex = tag selectedIndex = tag
} }
//player //player
@objc private func pupPlayerAction(_ sender:Notification) { @objc private func pupPlayerAction() {
DispatchQueue.main.async {
[weak self] in
//load //load
if MP_PlayerManager.shared.loadPlayer != nil { if MP_PlayerManager.shared.loadPlayer != nil {
let playerVC = MPPositive_PlayerViewController() let playerVC = MPPositive_PlayerViewController()
playerVC.modalPresentationStyle = .fullScreen playerVC.modalPresentationStyle = .fullScreen
present(playerVC, animated: true) self?.present(playerVC, animated: true)
}
}
}
//
@objc private func bottomAnimationAction(_ sender:Notification) {
switch_bottomShowAnimation(MP_PlayerManager.shared.loadPlayer != nil)
}
//BottomView
private func switch_bottomShowAnimation(_ state:Bool) {
UIView.animate(withDuration: 0.3) {
[weak self] in
guard let self = self else { return }
if state {
//
bottomView.transform = .init(translationX: 0, y: -145*width)
}else {
//
bottomView.transform = .identity
}
} }
} }
} }

View File

@ -31,8 +31,12 @@ class MPPositive_PlayerListShowViewController: UIViewController {
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.layer.cornerRadius = 18*width view.layer.cornerRadius = 18*width
configure() configure()
//
NotificationCenter.notificationKey.add(observer: self, selector: #selector(currentVideoReloadAction(_:)), notificationName: .positive_player_reload)
}
deinit {
NotificationCenter.default.removeObserver(self)
} }
private func configure() { private func configure() {
view.addSubview(indictorImageView) view.addSubview(indictorImageView)
indictorImageView.snp.makeConstraints { make in indictorImageView.snp.makeConstraints { make in
@ -51,6 +55,10 @@ class MPPositive_PlayerListShowViewController: UIViewController {
super.viewWillAppear(animated) super.viewWillAppear(animated)
tableView.reloadData() tableView.reloadData()
} }
@objc private func currentVideoReloadAction(_ sender:Notification) {
tableView.reloadData()
}
} }
//MARK: - tableView //MARK: - tableView
extension MPPositive_PlayerListShowViewController: UITableViewDataSource, UITableViewDelegate { extension MPPositive_PlayerListShowViewController: UITableViewDataSource, UITableViewDelegate {
@ -63,9 +71,16 @@ extension MPPositive_PlayerListShowViewController: UITableViewDataSource, UITabl
cell.song = MP_PlayerManager.shared.loadPlayer.songVideos[indexPath.row] cell.song = MP_PlayerManager.shared.loadPlayer.songVideos[indexPath.row]
cell.removeBlock = { cell.removeBlock = {
[weak self] in [weak self] in
if MP_PlayerManager.shared.loadPlayer.songVideos.count > 1 {
// //
MP_PlayerManager.shared.loadPlayer.removeData(MP_PlayerManager.shared.loadPlayer.songVideos[indexPath.row].videoId) MP_PlayerManager.shared.loadPlayer.removeData(MP_PlayerManager.shared.loadPlayer.songVideos[indexPath.row].videoId)
tableView.reloadData() tableView.reloadData()
}else {
//
MP_PlayerManager.shared.stop()
MP_PlayerManager.shared.loadPlayer = nil
self?.dismiss(animated: true)
}
} }
return cell return cell
} }

View File

@ -74,6 +74,8 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
btn.setBackgroundImage(UIImage(named: "Player_Pause'logo"), for: .normal) btn.setBackgroundImage(UIImage(named: "Player_Pause'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "Player_Player'logo"), for: .selected) btn.setBackgroundImage(UIImage(named: "Player_Player'logo"), for: .selected)
btn.addTarget(self, action: #selector(playClick(_ :)), for: .touchUpInside) btn.addTarget(self, action: #selector(playClick(_ :)), for: .touchUpInside)
//
btn.isUserInteractionEnabled = false
return btn return btn
}() }()
// //
@ -87,7 +89,6 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
private lazy var typeBtn:UIButton = { private lazy var typeBtn:UIButton = {
let btn = UIButton() let btn = UIButton()
btn.setBackgroundImage(UIImage(named: "List_NormolPlay'logo"), for: .normal) btn.setBackgroundImage(UIImage(named: "List_NormolPlay'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "List_ShufflePlay'logo"), for: .selected)
btn.addTarget(self, action: #selector(typeClick(_ :)), for: .touchUpInside) btn.addTarget(self, action: #selector(typeClick(_ :)), for: .touchUpInside)
return btn return btn
}() }()
@ -115,10 +116,43 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
configure() configure()
// //
NotificationCenter.notificationKey.add(observer: self, selector: #selector(playerReloadAction(_ :)), notificationName: .positive_player_reload) NotificationCenter.notificationKey.add(observer: self, selector: #selector(playerReloadAction(_ :)), notificationName: .positive_player_reload)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(statusSwitchAction(_:)), notificationName: .switch_player_status)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(playerTypeSwitchAction(_:)), notificationName: .player_type_switch)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(deleteListAction(_:)), notificationName: .player_delete_list)
//
MP_PlayerManager.shared.runActionBlock = { [weak self] (currentTime, duration) in
guard let self = self else { return }
//
coverView.durationLabel.text = setTimesToMinSeconds(currentTime)
//
let remain:TimeInterval = duration - currentTime
coverView.maxTimesLabel.text = setTimesToMinSeconds(remain)
//
let value = currentTime/duration
coverView.sliderView.value = Float(value)
}
switch MP_PlayerManager.shared.getPlayState() {
case .Null://
playBtn.isSelected = false
playBtn.isUserInteractionEnabled = false
case .Playing://
playBtn.isSelected = true
playBtn.isUserInteractionEnabled = true
default://
playBtn.isSelected = false
playBtn.isUserInteractionEnabled = true
}
} }
deinit { deinit {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//
if MP_PlayerManager.shared.loadPlayer.currentVideo != nil {
uploadUI()
}
}
// //
private func configure() { private func configure() {
//View //View
@ -251,12 +285,9 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
} }
return bottomView return bottomView
} }
//MARK: - //MARK: -
@objc private func playerReloadAction(_ sender:Notification) { private func uploadUI() {
// //
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
backImageView.kf.setImage(with: MP_PlayerManager.shared.loadPlayer.currentVideo?.coverUrl) backImageView.kf.setImage(with: MP_PlayerManager.shared.loadPlayer.currentVideo?.coverUrl)
coverView.coverImageView.kf.setImage(with: MP_PlayerManager.shared.loadPlayer.currentVideo?.coverUrl) coverView.coverImageView.kf.setImage(with: MP_PlayerManager.shared.loadPlayer.currentVideo?.coverUrl)
coverView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title coverView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title
@ -264,26 +295,60 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
lyricsView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title lyricsView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title
lyricsView.subtitleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.subtitle lyricsView.subtitleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.subtitle
lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo.lyrics ?? "No Lyrics" lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo.lyrics ?? "No Lyrics"
// }
MP_PlayerManager.shared.play { [weak self] in //MARK: -
//
@objc private func playerReloadAction(_ sender:Notification) {
//
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return} guard let self = self else {return}
uploadUI()
// //
coverView.sliderView.value = 0 coverView.sliderView.value = 0
playBtn.isSelected = true //
playBtn.isUserInteractionEnabled = true coverView.durationLabel.text = setTimesToMinSeconds(0)
} runAction: { [weak self] (currentTime, duration) in //
coverView.maxTimesLabel.text = setTimesToMinSeconds(0)
//
MP_PlayerManager.shared.play { [weak self] in
guard let self = self else { return } guard let self = self else { return }
// //playBtn
coverView.durationLabel.text = setTimesToMinSeconds(currentTime) playBtn.isUserInteractionEnabled = true
//
let remain:TimeInterval = duration - currentTime
coverView.maxTimesLabel.text = setTimesToMinSeconds(remain)
//
let value = currentTime/duration
coverView.sliderView.value = Float(value)
} }
} }
} }
//
@objc private func statusSwitchAction(_ sender:Notification) {
if sender.object != nil {
let state:MP_PlayerStateType = sender.object as! MP_PlayerStateType
DispatchQueue.main.async {
[weak self] in
switch state {
case .Playing:
self?.playBtn.isSelected = true
default:
self?.playBtn.isSelected = false
}
}
}
}
//
@objc private func playerTypeSwitchAction(_ sender:Notification) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
switchPlayTypeBtnIcon(typeBtn)
}
}
//
@objc private func deleteListAction(_ sender:Notification) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
dismiss(animated: true)
}
}
//MARK: - //MARK: -
//dismiss //dismiss
@objc private func disMissClick(_ sender:UIButton) { @objc private func disMissClick(_ sender:UIButton) {
@ -343,6 +408,10 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
//// ////
@objc private func playClick(_ sender:UIButton) { @objc private func playClick(_ sender:UIButton) {
guard MP_PlayerManager.shared.loadPlayer != nil else {
return
}
//
switch MP_PlayerManager.shared.getPlayState() { switch MP_PlayerManager.shared.getPlayState() {
case .Null: case .Null:
// //
@ -350,36 +419,23 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
guard let self = self else { return } guard let self = self else { return }
// //
coverView.sliderView.value = 0 coverView.sliderView.value = 0
sender.isSelected = true
} runAction: { [weak self] (currentTime, duration) in
guard let self = self else { return }
//
coverView.durationLabel.text = setTimesToMinSeconds(currentTime)
//
let remain:TimeInterval = duration - currentTime
coverView.maxTimesLabel.text = setTimesToMinSeconds(remain)
//
let value = currentTime/duration
coverView.sliderView.value = Float(value)
} }
case .Playing: case .Playing:
// //
MP_PlayerManager.shared.pause { MP_PlayerManager.shared.pause {
[weak self] in [weak self] in
sender.isSelected = false
} }
case .Pause: case .Pause:
// //
MP_PlayerManager.shared.resume { MP_PlayerManager.shared.resume {
[weak self] in [weak self] in
sender.isSelected = true
} }
} }
} }
// //
@objc private func listClick(_ sender:UIButton) { @objc private func listClick(_ sender:UIButton) {
if MP_PlayerManager.shared.loadPlayer != nil { if MP_PlayerManager.shared.loadPlayer != nil {
MPPositive_ModalType = .PlayerList
let listVC = MPPositive_PlayerListShowViewController() let listVC = MPPositive_PlayerListShowViewController()
listVC.transitioningDelegate = self listVC.transitioningDelegate = self
listVC.modalPresentationStyle = .custom listVC.modalPresentationStyle = .custom
@ -391,12 +447,17 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
} }
//// ////
@objc private func typeClick(_ sender:UIButton) { @objc private func typeClick(_ sender:UIButton) {
//
var value = MP_PlayerManager.shared.getPlayType().rawValue
value += 1
if value > 2 {
value = 0
}
MP_PlayerManager.shared.setPlayType(.init(rawValue: value)!)
} }
// //
@objc private func nextClick(_ sender:UIButton) { @objc private func nextClick(_ sender:UIButton) {
coverView.sliderView.value = 0 coverView.sliderView.value = 0
playBtn.isSelected = false
playBtn.isUserInteractionEnabled = false playBtn.isUserInteractionEnabled = false
MP_PlayerManager.shared.nextEvent() MP_PlayerManager.shared.nextEvent()
@ -404,7 +465,6 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
// //
@objc private func previousClick(_ sender:UIButton) { @objc private func previousClick(_ sender:UIButton) {
coverView.sliderView.value = 0 coverView.sliderView.value = 0
playBtn.isSelected = false
playBtn.isUserInteractionEnabled = false playBtn.isUserInteractionEnabled = false
MP_PlayerManager.shared.previousEvent() MP_PlayerManager.shared.previousEvent()
} }

View File

@ -0,0 +1,18 @@
//
// MPPositive_SearchResultShowViewController.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
///
class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
setTitle("Result")
setPopBtn()
}
}

View File

@ -7,23 +7,201 @@
import UIKit import UIKit
class MPPositive_SearchViewController: UIViewController { class MPPositive_SearchViewController: MPPositive_BaseViewController {
//
private lazy var bgImageView:UIImageView = {
let imageView:UIImageView = .init(image: .init(named: "B_Home_BG'bg"))
imageView.contentMode = .scaleAspectFill
return imageView
}()
//textField
private lazy var searchTextField:UITextField = {
let textField = UITextField()
textField.delegate = self
textField.font = .systemFont(ofSize: 14*width, weight: .regular)
textField.textColor = .white
//
let attributedText = NSAttributedString(string: "Search songs,artists,playlists", attributes: [.font:UIFont.systemFont(ofSize: 14*width, weight: .regular), .foregroundColor:UIColor(hex: "#666666")])
textField.attributedPlaceholder = attributedText
return textField
}()
//tableView
private lazy var suggestionsTableView:UITableView = {
let tableView = UITableView()
tableView.backgroundColor = .init(hex: "#151718")
tableView.separatorStyle = .none
//
tableView.layer.borderWidth = 1*width
tableView.layer.borderColor = UIColor(hex: "#FFFFFF", alpha: 0.35).cgColor
tableView.layer.masksToBounds = true
tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
tableView.layer.cornerRadius = 10*width
tableView.rowHeight = 60*width
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_SearchSuggestionItemTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchSuggestionItemTableViewCellID)
tableView.register(MPPositive_SearchSuggestionListTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchSuggestionListTableViewCellID)
return tableView
}()
private let MPPositive_SearchSuggestionItemTableViewCellID = "MPPositive_SearchSuggestionItemTableViewCell"
private let MPPositive_SearchSuggestionListTableViewCellID = "MPPositive_SearchSuggestionListTableViewCell"
//
private var suggestions:[[MPPositive_SearchSuggestionItemModel]]!{
willSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if newValue != nil {
//
var count:Int = 0
newValue.forEach { items in
count += items.count
}
suggestionsTableView.isHidden = false
suggestionsTableView.snp.updateConstraints { make in
make.height.equalTo(CGFloat(count*60)*width)
}
}else {
//
suggestionsTableView.isHidden = true
suggestionsTableView.snp.updateConstraints { make in
make.height.equalTo(0)
}
}
suggestionsTableView.reloadData()
}
}
}
//
private var debounceTimer: Timer?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setTitle("")
// Do any additional setup after loading the view. configure()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
searchTextField.text = ""
suggestionsTableView.isHidden = true
suggestions = nil
}
//
private func configure() {
let searchView = createSearchView()
navView.addSubview(searchView)
searchView.snp.makeConstraints { make in
make.width.equalTo(339*width)
make.height.equalTo(32*width)
make.center.equalToSuperview()
}
view.addSubview(bgImageView)
bgImageView.snp.makeConstraints { make in
make.top.right.left.equalToSuperview()
make.height.equalTo(981*width)
}
view.addSubview(suggestionsTableView)
suggestionsTableView.snp.makeConstraints { make in
make.top.equalTo(searchView.snp.bottom)
make.left.equalTo(searchView.snp.left).offset(16*width)
make.right.equalTo(searchView.snp.right).offset(-16*width)
make.height.equalTo(240*width)
}
suggestionsTableView.isHidden = true
}
//
private func createSearchView() -> UIView{
let searchView:UIView = UIView()
searchView.backgroundColor = .init(hex: "#212121")
searchView.isUserInteractionEnabled = true
searchView.layer.masksToBounds = true
searchView.layer.cornerRadius = 16*width
//icon
let iconImageView = UIImageView(image: .init(named: "B_Seach"))
searchView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.height.width.equalTo(16*width)
make.left.equalToSuperview().offset(16*width)
make.centerY.equalToSuperview()
}
//textField
searchView.addSubview(searchTextField)
searchTextField.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(8*width)
make.right.equalToSuperview().offset(-16*width)
}
return searchView
} }
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
} }
*/ //MARK: - textField
extension MPPositive_SearchViewController:UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
guard text.count <= 30 else {
return false
}
if text.isEmpty {
suggestions = nil
}else {
//
loadSearchSuggestions(text)
}
return true
}
//
private func cancelDebounceTimer() {
debounceTimer?.invalidate()
debounceTimer = nil
}
//
private func loadSearchSuggestions(_ text:String) {
cancelDebounceTimer()
//0.8
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.8, repeats: false) { [weak self] _ in
self?.fetchSearchSuggestions(text)
}
}
//
private func fetchSearchSuggestions(_ text:String) {
MP_NetWorkManager.shared.requestSearchSuggestions(text) { [weak self] (result) in
self?.suggestions = result
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
//textField
if let text = textField.text, text.isEmpty != true {
//
let showVC = MPPositive_SearchResultShowViewController()
navigationController?.pushViewController(showVC, animated: true)
return true
}else {
return false
}
}
}
//MARK: - tableView
extension MPPositive_SearchViewController:UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return suggestions != nil ? suggestions.count:0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return suggestions != nil ? suggestions[section].count:0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if suggestions[indexPath.section][indexPath.row].reviewUrls != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchSuggestionListTableViewCellID, for: indexPath) as! MPPositive_SearchSuggestionListTableViewCell
cell.item = suggestions[indexPath.section][indexPath.row]
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchSuggestionItemTableViewCellID, for: indexPath) as! MPPositive_SearchSuggestionItemTableViewCell
cell.item = suggestions[indexPath.section][indexPath.row]
return cell
}
}
} }

View File

@ -6,6 +6,7 @@
// //
import UIKit import UIKit
import Kingfisher
///bView ///bView
class MPPositive_BottomShowView: UIView { class MPPositive_BottomShowView: UIView {
//绿 //绿
@ -37,14 +38,22 @@ class MPPositive_BottomShowView: UIView {
btn.addTarget(self, action: #selector(switchStatuClick(_ :)), for: .touchUpInside) btn.addTarget(self, action: #selector(switchStatuClick(_ :)), for: .touchUpInside)
return btn return btn
}() }()
//
var showListBlock:(() -> Void)?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
//
NotificationCenter.notificationKey.add(observer: self, selector: #selector(currentVideoSwitchAction(_:)), notificationName: .positive_player_reload)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(statusSwitchAction(_:)), notificationName: .switch_player_status)
confirgue() confirgue()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
} }
deinit {
NotificationCenter.default.removeObserver(self)
}
// //
private func confirgue() { private func confirgue() {
addSubview(bgGreenImageView) addSubview(bgGreenImageView)
@ -77,13 +86,62 @@ class MPPositive_BottomShowView: UIView {
make.right.equalTo(titleLabel) make.right.equalTo(titleLabel)
} }
} }
//
@objc private func currentVideoSwitchAction(_ sender:Notification) {
//
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if MP_PlayerManager.shared.loadPlayer?.currentVideo != nil {
//
coverImageView.kf.setImage(with: MP_PlayerManager.shared.loadPlayer?.currentVideo.coverUrl, placeholder: placeholderImage)
titleLabel.text = MP_PlayerManager.shared.loadPlayer?.currentVideo?.title ?? ""
subtitleLabel.text = MP_PlayerManager.shared.loadPlayer?.currentVideo.subtitle ?? ""
}
}
}
//
@objc private func statusSwitchAction(_ sender:Notification) {
if sender.object != nil {
let state:MP_PlayerStateType = sender.object as! MP_PlayerStateType
DispatchQueue.main.async {
[weak self] in
switch state {
case .Playing:
self?.playStatuBtn.isSelected = true
default:
self?.playStatuBtn.isSelected = false
}
}
}
}
// //
@objc private func expandListsClick(_ sender:UIButton) { @objc private func expandListsClick(_ sender:UIButton) {
guard showListBlock != nil else {
return
}
showListBlock!()
} }
// //
@objc private func switchStatuClick(_ sender:UIButton) { @objc private func switchStatuClick(_ sender:UIButton) {
guard MP_PlayerManager.shared.loadPlayer != nil else {
return
}
//
switch MP_PlayerManager.shared.getPlayState() {
case .Null:
//
MP_PlayerManager.shared.play()
case .Playing:
//
MP_PlayerManager.shared.pause {
[weak self] in
}
case .Pause:
//
MP_PlayerManager.shared.resume {
[weak self] in
}
}
} }
} }

View File

@ -29,6 +29,14 @@ class MPPositive_PlayerListShowTableViewCell: UITableViewCell {
}() }()
var song:MPPositive_SongItemModel!{ var song:MPPositive_SongItemModel!{
didSet{ didSet{
//
if song.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId {
titleLabel.textColor = .init(hex: "#80F988")
subtitleLabel.textColor = .init(hex: "#80F988")
}else {
titleLabel.textColor = .white
subtitleLabel.textColor = .init(hex: "#FFFFFF", alpha: 0.6)
}
coverImageView.kf.setImage(with: URL(string: song.reviewUrls?.first ?? ""), placeholder: placeholderImage) coverImageView.kf.setImage(with: URL(string: song.reviewUrls?.first ?? ""), placeholder: placeholderImage)
titleLabel.text = song.title titleLabel.text = song.title
subtitleLabel.text = song.shortBylineText subtitleLabel.text = song.shortBylineText

View File

@ -21,8 +21,8 @@ class MPPositive_PlayerLyricView: UIView {
scrollView.addSubview(lyricsLabel) scrollView.addSubview(lyricsLabel)
scrollView.contentSize = .init(width: screen_Width, height: lyricsLabel.frame.origin.y + lyricsLabel.frame.height) scrollView.contentSize = .init(width: screen_Width, height: lyricsLabel.frame.origin.y + lyricsLabel.frame.height)
lyricsLabel.snp.makeConstraints { make in lyricsLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(20*width) make.width.equalToSuperview().multipliedBy(0.89)
make.right.equalToSuperview().offset(-20*width) make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(15*width) make.top.equalToSuperview().offset(15*width)
make.bottom.equalToSuperview().offset(30*width) make.bottom.equalToSuperview().offset(30*width)
} }

View File

@ -0,0 +1,52 @@
//
// MPPositive_SearchSuggestionItemTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
class MPPositive_SearchSuggestionItemTableViewCell: UITableViewCell {
//Icon
private lazy var iconImageView:UIImageView = UIImageView(image: .init(named: "B_Seach"))
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .left)
var item:MPPositive_SearchSuggestionItemModel!{
didSet{
titleLabel.text = item.title ?? ""
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12*width)
make.width.height.equalTo(30*width)
make.centerY.equalToSuperview()
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(10*width)
make.right.equalToSuperview().offset(-12*width)
}
}
}

View File

@ -0,0 +1,65 @@
//
// MPPositive_SearchSuggestionListTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
import Kingfisher
class MPPositive_SearchSuggestionListTableViewCell: UITableViewCell {
//Icon
private lazy var reviewImageView:UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
return imageView
}()
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .white, textAlignment: .left)
private lazy var subtitleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .left)
var item:MPPositive_SearchSuggestionItemModel!{
didSet{
reviewImageView.kf.setImage(with: URL(string: item.reviewUrls?.last ?? ""), placeholder: placeholderImage)
titleLabel.text = item.title ?? ""
subtitleLabel.text = item.subtitle ?? ""
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(reviewImageView)
reviewImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12*width)
make.width.height.equalTo(30*width)
make.centerY.equalToSuperview()
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(reviewImageView.snp.top)
make.left.equalTo(reviewImageView.snp.right).offset(10*width)
make.right.equalToSuperview().offset(-12*width)
}
contentView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.bottom.equalTo(reviewImageView.snp.bottom)
make.left.equalTo(titleLabel.snp.left)
make.right.equalTo(titleLabel.snp.right)
}
}
}