对部分bug的处理以及搜索功能的初步搭建
@ -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 */,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 469 B |
22
MusicPlayer/Assets.xcassets/Positive/Player/Player_Shuffle'logo.imageset/Contents.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
MusicPlayer/Assets.xcassets/Positive/Player/Player_Shuffle'logo.imageset/Group_1597880487@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 405 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Player/Player_Shuffle'logo.imageset/Group_1597880487@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 638 B |
22
MusicPlayer/Assets.xcassets/Positive/Player/Player_Single'logo.imageset/Contents.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
MusicPlayer/Assets.xcassets/Positive/Player/Player_Single'logo.imageset/Group_1597880488@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 524 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Player/Player_Single'logo.imageset/Group_1597880488@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 796 B |
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
//实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列
|
//实行串行异步队列,进行多次请求。由于第一次之后的请求都必须携带对应的continuation编码,所以串行队列。直到最后一次请求的continuation值为空,销毁队列
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
//设置参数,browseId与params参数是必定携带内容
|
//设置参数,browseId与params参数是必定携带内容
|
||||||
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: - 请求列表专辑下一部分
|
||||||
///请求Next列表(优先于Player)
|
///请求Next列表(优先于Player)
|
||||||
/// - 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
|
||||||
}
|
}
|
||||||
//设置参数,videoId与params参数是必定携带内容
|
//设置参数,videoId与params参数是必定携带内容
|
||||||
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
|
|
||||||
}
|
|
||||||
//设置参数,videoId与params参数是必定携带内容
|
|
||||||
var parameters:[String:Any] = [
|
|
||||||
"videoId":(item.videoId ?? ""),
|
|
||||||
"prettyPrint":"false",
|
|
||||||
"context":[
|
|
||||||
"client":[
|
|
||||||
//web端
|
|
||||||
"clientName": "WEB_REMIX",
|
|
||||||
//当前访问版本(日期值)
|
|
||||||
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
|
|
||||||
"platform":"DESKTOP",
|
|
||||||
//语言
|
|
||||||
"hl":Language_first_local,
|
|
||||||
//地址
|
|
||||||
"gl":Location_First
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
//发送next列表歌词/相关内容请求
|
|
||||||
requestPostNextLyricsAndRelated(url, parameters: parameters) { result in
|
|
||||||
completion(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//请求next列表
|
//请求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
|
||||||
|
}
|
||||||
|
//设置参数,videoId与params参数是必定携带内容
|
||||||
|
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
|
||||||
}
|
}
|
||||||
//设置参数,browseId与params参数是必定携带内容
|
//设置参数,browseId与params参数是必定携带内容
|
||||||
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 {
|
||||||
|
|||||||
@ -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)
|
||||||
//当current为0时执行开始事件
|
|
||||||
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来准确控制播放
|
||||||
|
//为这个currentVideo的resourcePlayerItem创建KVO,分别监听这个item的status,playbackLikelyToKeepUp
|
||||||
|
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
|
||||||
|
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||||
|
}
|
||||||
|
//启动除了当前播放Video以外的Item的预加载内容
|
||||||
|
// 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 {
|
||||||
|
//当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放
|
||||||
|
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 {
|
|
||||||
//用户删除了音乐,播放下一首音乐
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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]?
|
||||||
|
///单曲/视频ID(playlistItemData存在说明这条数据单曲/视频)
|
||||||
|
let playlistItemData:PlaylistItemData?
|
||||||
|
///专辑/歌单ID(navigationEndpoint存在说明这条数据是专辑/歌单)
|
||||||
|
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]?
|
||||||
|
///单曲/视频ID(playlistItemData存在说明这条数据单曲/视频)
|
||||||
|
let playlistItemData:PlaylistItemData?
|
||||||
|
///专辑/歌单ID(navigationEndpoint存在说明这条数据是专辑/歌单)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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]?
|
||||||
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
///将选中Video的上下2项包括本身总计3项Video进行补全转为ViewModel,并播放这首音乐
|
///将选中Video的上下2项包括本身总计3项Video进行补全转为ViewModel,并播放这首音乐
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
//获取完成,优先检索ViewModel,看看是否已存在补完video
|
//获取完成,优先检索ViewModel,看看是否已存在补完video
|
||||||
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()
|
||||||
//补全歌词id和相关内容id
|
//补全歌词id和相关内容id
|
||||||
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
///移除选中的song,并更新listViewVideos,移除相同index的值
|
///移除选中的song,并更新listViewVideos,移除相同index的值
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
///调用next对单曲数据歌词ID与相关ID补全
|
///调用next对单曲数据歌词ID与相关ID补全
|
||||||
private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
|
private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
|
||||||
//单曲补全需要再次调用next接口
|
//单曲补全需要再次调用next接口
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
///b面tabBar控制器
|
///b面tabBar控制器
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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: - 通知
|
||||||
guard let self = self else { return }
|
//播放器音乐刷新
|
||||||
|
@objc private func playerReloadAction(_ sender:Notification) {
|
||||||
|
//渲染页面
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
[weak self] in
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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)
|
||||||
/*
|
}
|
||||||
// MARK: - Navigation
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
searchTextField.text = ""
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
suggestionsTableView.isHidden = true
|
||||||
// Get the new view controller using segue.destination.
|
suggestions = nil
|
||||||
// Pass the selected object to the new view controller.
|
}
|
||||||
|
//配置
|
||||||
|
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: - 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Kingfisher
|
||||||
///b面底部播放音乐展示View
|
///b面底部播放音乐展示View
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||