Browse Source

feat:项目初始化暂存

kailen 3 months ago
parent
commit
19f442e59c
100 changed files with 5211 additions and 0 deletions
  1. 580 0
      TSLiveWallpaper.xcodeproj/project.pbxproj
  2. 6 0
      TSLiveWallpaper/Assets.xcassets/Music/Contents.json
  3. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/Contents.json
  4. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@1x.png
  5. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@2x.png
  6. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@3x.png
  7. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/Contents.json
  8. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@1x.png
  9. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@2x.png
  10. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@3x.png
  11. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/Contents.json
  12. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@1x.png
  13. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@2x.png
  14. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@3x.png
  15. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/Contents.json
  16. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@1x.png
  17. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@2x.png
  18. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@3x.png
  19. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/Contents.json
  20. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@1x.png
  21. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@2x.png
  22. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@3x.png
  23. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/Contents.json
  24. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@1x.png
  25. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@2x.png
  26. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@3x.png
  27. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/Contents.json
  28. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@1x.png
  29. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@2x.png
  30. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@3x.png
  31. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/Contents.json
  32. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@1x.png
  33. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@2x.png
  34. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@3x.png
  35. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/Contents.json
  36. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@1x.png
  37. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@2x.png
  38. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@3x.png
  39. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/Contents.json
  40. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@1x.png
  41. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@2x.png
  42. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@3x.png
  43. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/Contents.json
  44. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@1x.png
  45. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@2x.png
  46. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@3x.png
  47. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/Contents.json
  48. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@1x.png
  49. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@2x.png
  50. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@3x.png
  51. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/Contents.json
  52. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@1x.png
  53. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@2x.png
  54. BIN
      TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@3x.png
  55. 23 0
      TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/Contents.json
  56. BIN
      TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@1x.png
  57. BIN
      TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@2x.png
  58. BIN
      TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@3x.png
  59. 23 0
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/Contents.json
  60. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@1x.png
  61. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@2x.png
  62. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@3x.png
  63. 23 0
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/Contents.json
  64. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@1x.png
  65. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@2x.png
  66. BIN
      TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@3x.png
  67. 14 0
      TSLiveWallpaper/Business/TSMusic/Consts/Consts.swift
  68. 64 0
      TSLiveWallpaper/Business/TSMusic/Custom/BasePresentViewController.swift
  69. 135 0
      TSLiveWallpaper/Business/TSMusic/Custom/BubbleMenuView.swift
  70. 25 0
      TSLiveWallpaper/Business/TSMusic/Custom/FitManager.swift
  71. 70 0
      TSLiveWallpaper/Business/TSMusic/Custom/ImageTextControl.swift
  72. 75 0
      TSLiveWallpaper/Business/TSMusic/Custom/ManageMenuAlertView.swift
  73. 102 0
      TSLiveWallpaper/Business/TSMusic/Custom/MusicEmptyView.swift
  74. 59 0
      TSLiveWallpaper/Business/TSMusic/Custom/MusicSearchBar.swift
  75. 21 0
      TSLiveWallpaper/Business/TSMusic/Custom/NotifactionKey.swift
  76. 139 0
      TSLiveWallpaper/Business/TSMusic/Custom/PlayMiniBar.swift
  77. 68 0
      TSLiveWallpaper/Business/TSMusic/Custom/SpacedButton.swift
  78. 92 0
      TSLiveWallpaper/Business/TSMusic/Custom/THUD+CW.swift
  79. 100 0
      TSLiveWallpaper/Business/TSMusic/Custom/THUD.swift
  80. 20 0
      TSLiveWallpaper/Business/TSMusic/Custom/THUDProtocol.swift
  81. 125 0
      TSLiveWallpaper/Business/TSMusic/Custom/TipsView.swift
  82. 206 0
      TSLiveWallpaper/Business/TSMusic/Custom/ToastView.swift
  83. 29 0
      TSLiveWallpaper/Business/TSMusic/Detail/Controller/PlayDetailViewController+Ext.swift
  84. 561 0
      TSLiveWallpaper/Business/TSMusic/Detail/Controller/PlayDetailViewController.swift
  85. 206 0
      TSLiveWallpaper/Business/TSMusic/Detail/Controller/SleepTimeViewController.swift
  86. 210 0
      TSLiveWallpaper/Business/TSMusic/Detail/PlayerManager.swift
  87. 48 0
      TSLiveWallpaper/Business/TSMusic/Detail/View/PlayButtonView.swift
  88. 307 0
      TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailControlView.swift
  89. 38 0
      TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailPlaceHolderView.swift
  90. 92 0
      TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailTopView.swift
  91. 65 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWCustomProgressView.swift
  92. 53 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWLoadingView.swift
  93. 44 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWMutiSelectOpeateView.swift
  94. 59 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWOperateButton.swift
  95. 50 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWOperateItemView.swift
  96. 152 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWOperateViewController+Ext.swift
  97. 123 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWOperateViewController.swift
  98. 550 0
      TSLiveWallpaper/Business/TSMusic/Helper/CWProgressView.swift
  99. 181 0
      TSLiveWallpaper/Business/TSMusic/Helper/DownloadButton.swift
  100. 174 0
      TSLiveWallpaper/Business/TSMusic/Helper/FilterBarView.swift

+ 580 - 0
TSLiveWallpaper.xcodeproj/project.pbxproj

@@ -8,6 +8,99 @@
 
 /* Begin PBXBuildFile section */
 		059E844A164B0E39971303B9 /* Pods_TSLiveWallpaper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E33A770AEFA5810AED7219D3 /* Pods_TSLiveWallpaper.framework */; };
+		60553F722D3B528A00BAAD7F /* PlayDetailListViewContoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F242D3B528A00BAAD7F /* PlayDetailListViewContoller.swift */; };
+		60553F732D3B528A00BAAD7F /* SearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F0F2D3B528A00BAAD7F /* SearchResultViewModel.swift */; };
+		60553F742D3B528A00BAAD7F /* Consts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F282D3B528A00BAAD7F /* Consts.swift */; };
+		60553F752D3B528A00BAAD7F /* MusicEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6E2D3B528A00BAAD7F /* MusicEmptyView.swift */; };
+		60553F762D3B528A00BAAD7F /* CWMutiSelectOpeateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F392D3B528A00BAAD7F /* CWMutiSelectOpeateView.swift */; };
+		60553F772D3B528A00BAAD7F /* DownloadButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F302D3B528A00BAAD7F /* DownloadButton.swift */; };
+		60553F782D3B528A00BAAD7F /* ManageMenuAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F632D3B528A00BAAD7F /* ManageMenuAlertView.swift */; };
+		60553F792D3B528A00BAAD7F /* SongListBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F162D3B528A00BAAD7F /* SongListBottomView.swift */; };
+		60553F7A2D3B528A00BAAD7F /* PlaylistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F202D3B528A00BAAD7F /* PlaylistViewController.swift */; };
+		60553F7B2D3B528A00BAAD7F /* PlaylistTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F582D3B528A00BAAD7F /* PlaylistTopView.swift */; };
+		60553F7C2D3B528A00BAAD7F /* AddPlayListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F232D3B528A00BAAD7F /* AddPlayListViewController.swift */; };
+		60553F7D2D3B528A00BAAD7F /* PlayDetailTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F4A2D3B528A00BAAD7F /* PlayDetailTopView.swift */; };
+		60553F7E2D3B528A00BAAD7F /* CWOperateViewController+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F362D3B528A00BAAD7F /* CWOperateViewController+Ext.swift */; };
+		60553F7F2D3B528A00BAAD7F /* OperateTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F3A2D3B528A00BAAD7F /* OperateTopView.swift */; };
+		60553F802D3B528A00BAAD7F /* PlayDetailViewController+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F502D3B528A00BAAD7F /* PlayDetailViewController+Ext.swift */; };
+		60553F812D3B528A00BAAD7F /* SJIJKMediaPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 60553F042D3B528A00BAAD7F /* SJIJKMediaPlayer.m */; };
+		60553F822D3B528A00BAAD7F /* SearchResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F0E2D3B528A00BAAD7F /* SearchResultViewController.swift */; };
+		60553F832D3B528A00BAAD7F /* THUD+CW.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6A2D3B528A00BAAD7F /* THUD+CW.swift */; };
+		60553F842D3B528A00BAAD7F /* SongListManageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F1D2D3B528A00BAAD7F /* SongListManageViewModel.swift */; };
+		60553F852D3B528A00BAAD7F /* SongDownloadCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F5B2D3B528A00BAAD7F /* SongDownloadCellViewModel.swift */; };
+		60553F862D3B528A00BAAD7F /* PlayDetailControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F4B2D3B528A00BAAD7F /* PlayDetailControlView.swift */; };
+		60553F872D3B528A00BAAD7F /* PlayDetailListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F1E2D3B528A00BAAD7F /* PlayDetailListViewModel.swift */; };
+		60553F882D3B528A00BAAD7F /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F3C2D3B528A00BAAD7F /* SearchViewModel.swift */; };
+		60553F892D3B528A00BAAD7F /* CWOperateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F352D3B528A00BAAD7F /* CWOperateViewController.swift */; };
+		60553F8A2D3B528A00BAAD7F /* THUDProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F692D3B528A00BAAD7F /* THUDProtocol.swift */; };
+		60553F8B2D3B528A00BAAD7F /* THUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F682D3B528A00BAAD7F /* THUD.swift */; };
+		60553F8C2D3B528A00BAAD7F /* PlayDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F4F2D3B528A00BAAD7F /* PlayDetailViewController.swift */; };
+		60553F8D2D3B528A00BAAD7F /* CWCustomProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F312D3B528A00BAAD7F /* CWCustomProgressView.swift */; };
+		60553F8E2D3B528A00BAAD7F /* PlaylistDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F222D3B528A00BAAD7F /* PlaylistDetailViewController.swift */; };
+		60553F8F2D3B528A00BAAD7F /* SleepTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F4E2D3B528A00BAAD7F /* SleepTimeViewController.swift */; };
+		60553F902D3B528A00BAAD7F /* CWLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F342D3B528A00BAAD7F /* CWLoadingView.swift */; };
+		60553F912D3B528A00BAAD7F /* PlayListDetaiViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F1B2D3B528A00BAAD7F /* PlayListDetaiViewModel.swift */; };
+		60553F922D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 60553F062D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.m */; };
+		60553F932D3B528A00BAAD7F /* CWSearchTextBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F402D3B528A00BAAD7F /* CWSearchTextBar.swift */; };
+		60553F942D3B528A00BAAD7F /* SongListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F542D3B528A00BAAD7F /* SongListCell.swift */; };
+		60553F952D3B528A00BAAD7F /* AddPlayListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F1C2D3B528A00BAAD7F /* AddPlayListViewModel.swift */; };
+		60553F962D3B528A00BAAD7F /* BaseDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F102D3B528A00BAAD7F /* BaseDataModel.swift */; };
+		60553F972D3B528A00BAAD7F /* PlayDetailPlaceHolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F4C2D3B528A00BAAD7F /* PlayDetailPlaceHolderView.swift */; };
+		60553F982D3B528A00BAAD7F /* SJIJKMediaPlaybackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60553F0A2D3B528A00BAAD7F /* SJIJKMediaPlaybackController.m */; };
+		60553F992D3B528A00BAAD7F /* FilterBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F2D2D3B528A00BAAD7F /* FilterBarViewModel.swift */; };
+		60553F9A2D3B528A00BAAD7F /* PlayListManageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F182D3B528A00BAAD7F /* PlayListManageView.swift */; };
+		60553F9B2D3B528A00BAAD7F /* LWSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F3F2D3B528A00BAAD7F /* LWSearchBar.swift */; };
+		60553F9C2D3B528A00BAAD7F /* BubbleMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6D2D3B528A00BAAD7F /* BubbleMenuView.swift */; };
+		60553F9D2D3B528A00BAAD7F /* SongListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F5C2D3B528A00BAAD7F /* SongListViewModel.swift */; };
+		60553F9E2D3B528A00BAAD7F /* SearchResultStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F112D3B528A00BAAD7F /* SearchResultStateView.swift */; };
+		60553F9F2D3B528A00BAAD7F /* FilterBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F2E2D3B528A00BAAD7F /* FilterBarView.swift */; };
+		60553FA02D3B528A00BAAD7F /* SongListViewController+Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F5F2D3B528A00BAAD7F /* SongListViewController+Target.swift */; };
+		60553FA12D3B528A00BAAD7F /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6B2D3B528A00BAAD7F /* ToastView.swift */; };
+		60553FA22D3B528A00BAAD7F /* SongListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F5E2D3B528A00BAAD7F /* SongListViewController.swift */; };
+		60553FA32D3B528A00BAAD7F /* PlayListTopItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F172D3B528A00BAAD7F /* PlayListTopItemView.swift */; };
+		60553FA42D3B528A00BAAD7F /* VipTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F332D3B528A00BAAD7F /* VipTagView.swift */; };
+		60553FA52D3B528A00BAAD7F /* CWTopCustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F562D3B528A00BAAD7F /* CWTopCustomButton.swift */; };
+		60553FA62D3B528A00BAAD7F /* RelateSeachCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F432D3B528A00BAAD7F /* RelateSeachCell.swift */; };
+		60553FA72D3B528A00BAAD7F /* PlaylistViewController+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F212D3B528A00BAAD7F /* PlaylistViewController+Ext.swift */; };
+		60553FA82D3B528A00BAAD7F /* CWOperateItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F372D3B528A00BAAD7F /* CWOperateItemView.swift */; };
+		60553FA92D3B528A00BAAD7F /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F412D3B528A00BAAD7F /* CustomTextField.swift */; };
+		60553FAA2D3B528A00BAAD7F /* CWOperateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F382D3B528A00BAAD7F /* CWOperateButton.swift */; };
+		60553FAB2D3B528A00BAAD7F /* PlayListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F1A2D3B528A00BAAD7F /* PlayListViewModel.swift */; };
+		60553FAC2D3B528A00BAAD7F /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F422D3B528A00BAAD7F /* TagListView.swift */; };
+		60553FAD2D3B528A00BAAD7F /* ImageTextControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F642D3B528A00BAAD7F /* ImageTextControl.swift */; };
+		60553FAE2D3B528A00BAAD7F /* CWProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F322D3B528A00BAAD7F /* CWProgressView.swift */; };
+		60553FAF2D3B528A00BAAD7F /* TipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F672D3B528A00BAAD7F /* TipsView.swift */; };
+		60553FB02D3B528A00BAAD7F /* PlayMiniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F652D3B528A00BAAD7F /* PlayMiniBar.swift */; };
+		60553FB12D3B528A00BAAD7F /* SongDownloadCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F552D3B528A00BAAD7F /* SongDownloadCell.swift */; };
+		60553FB22D3B528A00BAAD7F /* LocalSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F3D2D3B528A00BAAD7F /* LocalSearchViewModel.swift */; };
+		60553FB32D3B528A00BAAD7F /* SortMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F2F2D3B528A00BAAD7F /* SortMenuViewController.swift */; };
+		60553FB42D3B528A00BAAD7F /* ImportFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F2A2D3B528A00BAAD7F /* ImportFilesManager.swift */; };
+		60553FB52D3B528A00BAAD7F /* PlayButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F492D3B528A00BAAD7F /* PlayButtonView.swift */; };
+		60553FB62D3B528A00BAAD7F /* PlayDetailListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F152D3B528A00BAAD7F /* PlayDetailListViewCell.swift */; };
+		60553FB72D3B528A00BAAD7F /* FilterBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F2C2D3B528A00BAAD7F /* FilterBarViewController.swift */; };
+		60553FB82D3B528A00BAAD7F /* SongListTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F592D3B528A00BAAD7F /* SongListTopView.swift */; };
+		60553FB92D3B528A00BAAD7F /* CDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F572D3B528A00BAAD7F /* CDView.swift */; };
+		60553FBA2D3B528A00BAAD7F /* NotifactionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F662D3B528A00BAAD7F /* NotifactionKey.swift */; };
+		60553FBB2D3B528A00BAAD7F /* PlayListAddCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F142D3B528A00BAAD7F /* PlayListAddCell.swift */; };
+		60553FBC2D3B528A00BAAD7F /* SearchOnlineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F452D3B528A00BAAD7F /* SearchOnlineViewController.swift */; };
+		60553FBD2D3B528A00BAAD7F /* SpacedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6F2D3B528A00BAAD7F /* SpacedButton.swift */; };
+		60553FBE2D3B528A00BAAD7F /* BasePresentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F622D3B528A00BAAD7F /* BasePresentViewController.swift */; };
+		60553FBF2D3B528A00BAAD7F /* PlayListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F132D3B528A00BAAD7F /* PlayListCell.swift */; };
+		60553FC12D3B528A00BAAD7F /* MusicSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F6C2D3B528A00BAAD7F /* MusicSearchBar.swift */; };
+		60553FC22D3B528A00BAAD7F /* PlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F522D3B528A00BAAD7F /* PlayerManager.swift */; };
+		60553FC32D3B528A00BAAD7F /* LocalSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553F462D3B528A00BAAD7F /* LocalSearchViewController.swift */; };
+		60553FC42D3B528A00BAAD7F /* IJKPlayer.md in Resources */ = {isa = PBXBuildFile; fileRef = 60553F082D3B528A00BAAD7F /* IJKPlayer.md */; };
+		60553FD02D3B54A400BAAD7F /* LWNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FC72D3B54A400BAAD7F /* LWNavigationBar.swift */; };
+		60553FD22D3B54A400BAAD7F /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FCA2D3B54A400BAAD7F /* GradientButton.swift */; };
+		60553FD42D3B54A400BAAD7F /* LWBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FC82D3B54A400BAAD7F /* LWBaseViewController.swift */; };
+		60553FD62D3B54A400BAAD7F /* SaveSuccessTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FCB2D3B54A400BAAD7F /* SaveSuccessTipsView.swift */; };
+		60553FD72D3B54A400BAAD7F /* GradientText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FCD2D3B54A400BAAD7F /* GradientText.swift */; };
+		60553FD82D3B54A400BAAD7F /* LWBaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FC92D3B54A400BAAD7F /* LWBaseNavigationController.swift */; };
+		60553FD92D3B54A400BAAD7F /* GradientBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FCE2D3B54A400BAAD7F /* GradientBackgroundModifier.swift */; };
+		60553FDB2D3B7CC600BAAD7F /* FitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FDA2D3B7CC600BAAD7F /* FitManager.swift */; };
+		60553FDD2D3B84E700BAAD7F /* UIScrollView+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FDC2D3B84E700BAAD7F /* UIScrollView+Ext.swift */; };
+		60553FDF2D3B850C00BAAD7F /* TimeInterval+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FDE2D3B850C00BAAD7F /* TimeInterval+Ext.swift */; };
+		60553FE32D3DF12200BAAD7F /* CustomSegementItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60553FE22D3DF12200BAAD7F /* CustomSegementItem.swift */; };
 		A81CA4652D15685F00A3AAC8 /* TSLaunchVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81CA4642D15685D00A3AAC8 /* TSLaunchVC.swift */; };
 		A81CA4692D156AB600A3AAC8 /* TSBaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81CA4682D156AAB00A3AAC8 /* TSBaseVC.swift */; };
 		A81CA46B2D156BDC00A3AAC8 /* TSBaseNavigationC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81CA46A2D156BC600A3AAC8 /* TSBaseNavigationC.swift */; };
@@ -109,6 +202,103 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		60553F032D3B528A00BAAD7F /* SJIJKMediaPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJIJKMediaPlayer.h; sourceTree = "<group>"; };
+		60553F042D3B528A00BAAD7F /* SJIJKMediaPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJIJKMediaPlayer.m; sourceTree = "<group>"; };
+		60553F052D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJIJKMediaPlayerLayerView.h; sourceTree = "<group>"; };
+		60553F062D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJIJKMediaPlayerLayerView.m; sourceTree = "<group>"; };
+		60553F082D3B528A00BAAD7F /* IJKPlayer.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = IJKPlayer.md; sourceTree = "<group>"; };
+		60553F092D3B528A00BAAD7F /* SJIJKMediaPlaybackController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJIJKMediaPlaybackController.h; sourceTree = "<group>"; };
+		60553F0A2D3B528A00BAAD7F /* SJIJKMediaPlaybackController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJIJKMediaPlaybackController.m; sourceTree = "<group>"; };
+		60553F0B2D3B528A00BAAD7F /* TSAvatar-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TSAvatar-Header.h"; sourceTree = "<group>"; };
+		60553F0E2D3B528A00BAAD7F /* SearchResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultViewController.swift; sourceTree = "<group>"; };
+		60553F0F2D3B528A00BAAD7F /* SearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultViewModel.swift; sourceTree = "<group>"; };
+		60553F102D3B528A00BAAD7F /* BaseDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDataModel.swift; sourceTree = "<group>"; };
+		60553F112D3B528A00BAAD7F /* SearchResultStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultStateView.swift; sourceTree = "<group>"; };
+		60553F132D3B528A00BAAD7F /* PlayListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListCell.swift; sourceTree = "<group>"; };
+		60553F142D3B528A00BAAD7F /* PlayListAddCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListAddCell.swift; sourceTree = "<group>"; };
+		60553F152D3B528A00BAAD7F /* PlayDetailListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailListViewCell.swift; sourceTree = "<group>"; };
+		60553F162D3B528A00BAAD7F /* SongListBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListBottomView.swift; sourceTree = "<group>"; };
+		60553F172D3B528A00BAAD7F /* PlayListTopItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListTopItemView.swift; sourceTree = "<group>"; };
+		60553F182D3B528A00BAAD7F /* PlayListManageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListManageView.swift; sourceTree = "<group>"; };
+		60553F1A2D3B528A00BAAD7F /* PlayListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListViewModel.swift; sourceTree = "<group>"; };
+		60553F1B2D3B528A00BAAD7F /* PlayListDetaiViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListDetaiViewModel.swift; sourceTree = "<group>"; };
+		60553F1C2D3B528A00BAAD7F /* AddPlayListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPlayListViewModel.swift; sourceTree = "<group>"; };
+		60553F1D2D3B528A00BAAD7F /* SongListManageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListManageViewModel.swift; sourceTree = "<group>"; };
+		60553F1E2D3B528A00BAAD7F /* PlayDetailListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailListViewModel.swift; sourceTree = "<group>"; };
+		60553F202D3B528A00BAAD7F /* PlaylistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistViewController.swift; sourceTree = "<group>"; };
+		60553F212D3B528A00BAAD7F /* PlaylistViewController+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaylistViewController+Ext.swift"; sourceTree = "<group>"; };
+		60553F222D3B528A00BAAD7F /* PlaylistDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistDetailViewController.swift; sourceTree = "<group>"; };
+		60553F232D3B528A00BAAD7F /* AddPlayListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPlayListViewController.swift; sourceTree = "<group>"; };
+		60553F242D3B528A00BAAD7F /* PlayDetailListViewContoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailListViewContoller.swift; sourceTree = "<group>"; };
+		60553F282D3B528A00BAAD7F /* Consts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consts.swift; sourceTree = "<group>"; };
+		60553F2A2D3B528A00BAAD7F /* ImportFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportFilesManager.swift; sourceTree = "<group>"; };
+		60553F2C2D3B528A00BAAD7F /* FilterBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBarViewController.swift; sourceTree = "<group>"; };
+		60553F2D2D3B528A00BAAD7F /* FilterBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBarViewModel.swift; sourceTree = "<group>"; };
+		60553F2E2D3B528A00BAAD7F /* FilterBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBarView.swift; sourceTree = "<group>"; };
+		60553F2F2D3B528A00BAAD7F /* SortMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortMenuViewController.swift; sourceTree = "<group>"; };
+		60553F302D3B528A00BAAD7F /* DownloadButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadButton.swift; sourceTree = "<group>"; };
+		60553F312D3B528A00BAAD7F /* CWCustomProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWCustomProgressView.swift; sourceTree = "<group>"; };
+		60553F322D3B528A00BAAD7F /* CWProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWProgressView.swift; sourceTree = "<group>"; };
+		60553F332D3B528A00BAAD7F /* VipTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipTagView.swift; sourceTree = "<group>"; };
+		60553F342D3B528A00BAAD7F /* CWLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWLoadingView.swift; sourceTree = "<group>"; };
+		60553F352D3B528A00BAAD7F /* CWOperateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWOperateViewController.swift; sourceTree = "<group>"; };
+		60553F362D3B528A00BAAD7F /* CWOperateViewController+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CWOperateViewController+Ext.swift"; sourceTree = "<group>"; };
+		60553F372D3B528A00BAAD7F /* CWOperateItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWOperateItemView.swift; sourceTree = "<group>"; };
+		60553F382D3B528A00BAAD7F /* CWOperateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWOperateButton.swift; sourceTree = "<group>"; };
+		60553F392D3B528A00BAAD7F /* CWMutiSelectOpeateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWMutiSelectOpeateView.swift; sourceTree = "<group>"; };
+		60553F3A2D3B528A00BAAD7F /* OperateTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperateTopView.swift; sourceTree = "<group>"; };
+		60553F3C2D3B528A00BAAD7F /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
+		60553F3D2D3B528A00BAAD7F /* LocalSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSearchViewModel.swift; sourceTree = "<group>"; };
+		60553F3F2D3B528A00BAAD7F /* LWSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LWSearchBar.swift; sourceTree = "<group>"; };
+		60553F402D3B528A00BAAD7F /* CWSearchTextBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWSearchTextBar.swift; sourceTree = "<group>"; };
+		60553F412D3B528A00BAAD7F /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
+		60553F422D3B528A00BAAD7F /* TagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagListView.swift; sourceTree = "<group>"; };
+		60553F432D3B528A00BAAD7F /* RelateSeachCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelateSeachCell.swift; sourceTree = "<group>"; };
+		60553F452D3B528A00BAAD7F /* SearchOnlineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnlineViewController.swift; sourceTree = "<group>"; };
+		60553F462D3B528A00BAAD7F /* LocalSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSearchViewController.swift; sourceTree = "<group>"; };
+		60553F492D3B528A00BAAD7F /* PlayButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayButtonView.swift; sourceTree = "<group>"; };
+		60553F4A2D3B528A00BAAD7F /* PlayDetailTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailTopView.swift; sourceTree = "<group>"; };
+		60553F4B2D3B528A00BAAD7F /* PlayDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailControlView.swift; sourceTree = "<group>"; };
+		60553F4C2D3B528A00BAAD7F /* PlayDetailPlaceHolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailPlaceHolderView.swift; sourceTree = "<group>"; };
+		60553F4E2D3B528A00BAAD7F /* SleepTimeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepTimeViewController.swift; sourceTree = "<group>"; };
+		60553F4F2D3B528A00BAAD7F /* PlayDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayDetailViewController.swift; sourceTree = "<group>"; };
+		60553F502D3B528A00BAAD7F /* PlayDetailViewController+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayDetailViewController+Ext.swift"; sourceTree = "<group>"; };
+		60553F522D3B528A00BAAD7F /* PlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerManager.swift; sourceTree = "<group>"; };
+		60553F542D3B528A00BAAD7F /* SongListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListCell.swift; sourceTree = "<group>"; };
+		60553F552D3B528A00BAAD7F /* SongDownloadCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongDownloadCell.swift; sourceTree = "<group>"; };
+		60553F562D3B528A00BAAD7F /* CWTopCustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CWTopCustomButton.swift; sourceTree = "<group>"; };
+		60553F572D3B528A00BAAD7F /* CDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CDView.swift; sourceTree = "<group>"; };
+		60553F582D3B528A00BAAD7F /* PlaylistTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistTopView.swift; sourceTree = "<group>"; };
+		60553F592D3B528A00BAAD7F /* SongListTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListTopView.swift; sourceTree = "<group>"; };
+		60553F5B2D3B528A00BAAD7F /* SongDownloadCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongDownloadCellViewModel.swift; sourceTree = "<group>"; };
+		60553F5C2D3B528A00BAAD7F /* SongListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListViewModel.swift; sourceTree = "<group>"; };
+		60553F5E2D3B528A00BAAD7F /* SongListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListViewController.swift; sourceTree = "<group>"; };
+		60553F5F2D3B528A00BAAD7F /* SongListViewController+Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SongListViewController+Target.swift"; sourceTree = "<group>"; };
+		60553F622D3B528A00BAAD7F /* BasePresentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasePresentViewController.swift; sourceTree = "<group>"; };
+		60553F632D3B528A00BAAD7F /* ManageMenuAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageMenuAlertView.swift; sourceTree = "<group>"; };
+		60553F642D3B528A00BAAD7F /* ImageTextControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTextControl.swift; sourceTree = "<group>"; };
+		60553F652D3B528A00BAAD7F /* PlayMiniBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMiniBar.swift; sourceTree = "<group>"; };
+		60553F662D3B528A00BAAD7F /* NotifactionKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifactionKey.swift; sourceTree = "<group>"; };
+		60553F672D3B528A00BAAD7F /* TipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipsView.swift; sourceTree = "<group>"; };
+		60553F682D3B528A00BAAD7F /* THUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = THUD.swift; sourceTree = "<group>"; };
+		60553F692D3B528A00BAAD7F /* THUDProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = THUDProtocol.swift; sourceTree = "<group>"; };
+		60553F6A2D3B528A00BAAD7F /* THUD+CW.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "THUD+CW.swift"; sourceTree = "<group>"; };
+		60553F6B2D3B528A00BAAD7F /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
+		60553F6C2D3B528A00BAAD7F /* MusicSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicSearchBar.swift; sourceTree = "<group>"; };
+		60553F6D2D3B528A00BAAD7F /* BubbleMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleMenuView.swift; sourceTree = "<group>"; };
+		60553F6E2D3B528A00BAAD7F /* MusicEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicEmptyView.swift; sourceTree = "<group>"; };
+		60553F6F2D3B528A00BAAD7F /* SpacedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacedButton.swift; sourceTree = "<group>"; };
+		60553FC72D3B54A400BAAD7F /* LWNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LWNavigationBar.swift; sourceTree = "<group>"; };
+		60553FC82D3B54A400BAAD7F /* LWBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LWBaseViewController.swift; sourceTree = "<group>"; };
+		60553FC92D3B54A400BAAD7F /* LWBaseNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LWBaseNavigationController.swift; sourceTree = "<group>"; };
+		60553FCA2D3B54A400BAAD7F /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = "<group>"; };
+		60553FCB2D3B54A400BAAD7F /* SaveSuccessTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSuccessTipsView.swift; sourceTree = "<group>"; };
+		60553FCD2D3B54A400BAAD7F /* GradientText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientText.swift; sourceTree = "<group>"; };
+		60553FCE2D3B54A400BAAD7F /* GradientBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBackgroundModifier.swift; sourceTree = "<group>"; };
+		60553FDA2D3B7CC600BAAD7F /* FitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitManager.swift; sourceTree = "<group>"; };
+		60553FDC2D3B84E700BAAD7F /* UIScrollView+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Ext.swift"; sourceTree = "<group>"; };
+		60553FDE2D3B850C00BAAD7F /* TimeInterval+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Ext.swift"; sourceTree = "<group>"; };
+		60553FE22D3DF12200BAAD7F /* CustomSegementItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSegementItem.swift; sourceTree = "<group>"; };
 		71E5F623537702A8306DF3C8 /* Pods-TSLiveWallpaper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSLiveWallpaper.release.xcconfig"; path = "Target Support Files/Pods-TSLiveWallpaper/Pods-TSLiveWallpaper.release.xcconfig"; sourceTree = "<group>"; };
 		A81CA4642D15685D00A3AAC8 /* TSLaunchVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSLaunchVC.swift; sourceTree = "<group>"; };
 		A81CA4682D156AAB00A3AAC8 /* TSBaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseVC.swift; sourceTree = "<group>"; };
@@ -237,6 +427,300 @@
 			path = Pods;
 			sourceTree = "<group>";
 		};
+		60553F022D3B4DF800BAAD7F /* TSMusic */ = {
+			isa = PBXGroup;
+			children = (
+				60553FCF2D3B54A400BAAD7F /* MusicBase */,
+				60553F0D2D3B528A00BAAD7F /* OC */,
+				60553F122D3B528A00BAAD7F /* SearchResult */,
+				60553F272D3B528A00BAAD7F /* PlayList */,
+				60553F292D3B528A00BAAD7F /* Consts */,
+				60553F2B2D3B528A00BAAD7F /* Import */,
+				60553F3B2D3B528A00BAAD7F /* Helper */,
+				60553F482D3B528A00BAAD7F /* Search */,
+				60553F532D3B528A00BAAD7F /* Detail */,
+				60553F612D3B528A00BAAD7F /* List */,
+				60553F702D3B528A00BAAD7F /* Custom */,
+			);
+			path = TSMusic;
+			sourceTree = "<group>";
+		};
+		60553F072D3B528A00BAAD7F /* Core */ = {
+			isa = PBXGroup;
+			children = (
+				60553F032D3B528A00BAAD7F /* SJIJKMediaPlayer.h */,
+				60553F042D3B528A00BAAD7F /* SJIJKMediaPlayer.m */,
+				60553F052D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.h */,
+				60553F062D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.m */,
+			);
+			path = Core;
+			sourceTree = "<group>";
+		};
+		60553F0C2D3B528A00BAAD7F /* IJKPlayer */ = {
+			isa = PBXGroup;
+			children = (
+				60553F072D3B528A00BAAD7F /* Core */,
+				60553F082D3B528A00BAAD7F /* IJKPlayer.md */,
+				60553F092D3B528A00BAAD7F /* SJIJKMediaPlaybackController.h */,
+				60553F0A2D3B528A00BAAD7F /* SJIJKMediaPlaybackController.m */,
+				60553F0B2D3B528A00BAAD7F /* TSAvatar-Header.h */,
+			);
+			path = IJKPlayer;
+			sourceTree = "<group>";
+		};
+		60553F0D2D3B528A00BAAD7F /* OC */ = {
+			isa = PBXGroup;
+			children = (
+				60553F0C2D3B528A00BAAD7F /* IJKPlayer */,
+			);
+			path = OC;
+			sourceTree = "<group>";
+		};
+		60553F122D3B528A00BAAD7F /* SearchResult */ = {
+			isa = PBXGroup;
+			children = (
+				60553F0E2D3B528A00BAAD7F /* SearchResultViewController.swift */,
+				60553F0F2D3B528A00BAAD7F /* SearchResultViewModel.swift */,
+				60553F102D3B528A00BAAD7F /* BaseDataModel.swift */,
+				60553F112D3B528A00BAAD7F /* SearchResultStateView.swift */,
+			);
+			path = SearchResult;
+			sourceTree = "<group>";
+		};
+		60553F192D3B528A00BAAD7F /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				60553F132D3B528A00BAAD7F /* PlayListCell.swift */,
+				60553F142D3B528A00BAAD7F /* PlayListAddCell.swift */,
+				60553F152D3B528A00BAAD7F /* PlayDetailListViewCell.swift */,
+				60553F162D3B528A00BAAD7F /* SongListBottomView.swift */,
+				60553F172D3B528A00BAAD7F /* PlayListTopItemView.swift */,
+				60553F182D3B528A00BAAD7F /* PlayListManageView.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		60553F1F2D3B528A00BAAD7F /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				60553F1A2D3B528A00BAAD7F /* PlayListViewModel.swift */,
+				60553F1B2D3B528A00BAAD7F /* PlayListDetaiViewModel.swift */,
+				60553F1C2D3B528A00BAAD7F /* AddPlayListViewModel.swift */,
+				60553F1D2D3B528A00BAAD7F /* SongListManageViewModel.swift */,
+				60553F1E2D3B528A00BAAD7F /* PlayDetailListViewModel.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
+		60553F262D3B528A00BAAD7F /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				60553F202D3B528A00BAAD7F /* PlaylistViewController.swift */,
+				60553F212D3B528A00BAAD7F /* PlaylistViewController+Ext.swift */,
+				60553F222D3B528A00BAAD7F /* PlaylistDetailViewController.swift */,
+				60553F232D3B528A00BAAD7F /* AddPlayListViewController.swift */,
+				60553F242D3B528A00BAAD7F /* PlayDetailListViewContoller.swift */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		60553F272D3B528A00BAAD7F /* PlayList */ = {
+			isa = PBXGroup;
+			children = (
+				60553F192D3B528A00BAAD7F /* Views */,
+				60553F1F2D3B528A00BAAD7F /* ViewModel */,
+				60553F262D3B528A00BAAD7F /* Controller */,
+			);
+			path = PlayList;
+			sourceTree = "<group>";
+		};
+		60553F292D3B528A00BAAD7F /* Consts */ = {
+			isa = PBXGroup;
+			children = (
+				60553F282D3B528A00BAAD7F /* Consts.swift */,
+			);
+			path = Consts;
+			sourceTree = "<group>";
+		};
+		60553F2B2D3B528A00BAAD7F /* Import */ = {
+			isa = PBXGroup;
+			children = (
+				60553F2A2D3B528A00BAAD7F /* ImportFilesManager.swift */,
+			);
+			path = Import;
+			sourceTree = "<group>";
+		};
+		60553F3B2D3B528A00BAAD7F /* Helper */ = {
+			isa = PBXGroup;
+			children = (
+				60553F2C2D3B528A00BAAD7F /* FilterBarViewController.swift */,
+				60553F2D2D3B528A00BAAD7F /* FilterBarViewModel.swift */,
+				60553F2E2D3B528A00BAAD7F /* FilterBarView.swift */,
+				60553F2F2D3B528A00BAAD7F /* SortMenuViewController.swift */,
+				60553F302D3B528A00BAAD7F /* DownloadButton.swift */,
+				60553F312D3B528A00BAAD7F /* CWCustomProgressView.swift */,
+				60553F322D3B528A00BAAD7F /* CWProgressView.swift */,
+				60553F332D3B528A00BAAD7F /* VipTagView.swift */,
+				60553F342D3B528A00BAAD7F /* CWLoadingView.swift */,
+				60553F352D3B528A00BAAD7F /* CWOperateViewController.swift */,
+				60553F362D3B528A00BAAD7F /* CWOperateViewController+Ext.swift */,
+				60553F372D3B528A00BAAD7F /* CWOperateItemView.swift */,
+				60553F382D3B528A00BAAD7F /* CWOperateButton.swift */,
+				60553F392D3B528A00BAAD7F /* CWMutiSelectOpeateView.swift */,
+				60553F3A2D3B528A00BAAD7F /* OperateTopView.swift */,
+			);
+			path = Helper;
+			sourceTree = "<group>";
+		};
+		60553F3E2D3B528A00BAAD7F /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				60553F3C2D3B528A00BAAD7F /* SearchViewModel.swift */,
+				60553F3D2D3B528A00BAAD7F /* LocalSearchViewModel.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
+		60553F442D3B528A00BAAD7F /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				60553F3F2D3B528A00BAAD7F /* LWSearchBar.swift */,
+				60553F402D3B528A00BAAD7F /* CWSearchTextBar.swift */,
+				60553F412D3B528A00BAAD7F /* CustomTextField.swift */,
+				60553F422D3B528A00BAAD7F /* TagListView.swift */,
+				60553F432D3B528A00BAAD7F /* RelateSeachCell.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		60553F472D3B528A00BAAD7F /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				60553F452D3B528A00BAAD7F /* SearchOnlineViewController.swift */,
+				60553F462D3B528A00BAAD7F /* LocalSearchViewController.swift */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		60553F482D3B528A00BAAD7F /* Search */ = {
+			isa = PBXGroup;
+			children = (
+				60553F3E2D3B528A00BAAD7F /* ViewModel */,
+				60553F442D3B528A00BAAD7F /* Views */,
+				60553F472D3B528A00BAAD7F /* Controller */,
+			);
+			path = Search;
+			sourceTree = "<group>";
+		};
+		60553F4D2D3B528A00BAAD7F /* View */ = {
+			isa = PBXGroup;
+			children = (
+				60553F492D3B528A00BAAD7F /* PlayButtonView.swift */,
+				60553F4A2D3B528A00BAAD7F /* PlayDetailTopView.swift */,
+				60553F4B2D3B528A00BAAD7F /* PlayDetailControlView.swift */,
+				60553F4C2D3B528A00BAAD7F /* PlayDetailPlaceHolderView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		60553F512D3B528A00BAAD7F /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				60553F4E2D3B528A00BAAD7F /* SleepTimeViewController.swift */,
+				60553F4F2D3B528A00BAAD7F /* PlayDetailViewController.swift */,
+				60553F502D3B528A00BAAD7F /* PlayDetailViewController+Ext.swift */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		60553F532D3B528A00BAAD7F /* Detail */ = {
+			isa = PBXGroup;
+			children = (
+				60553F4D2D3B528A00BAAD7F /* View */,
+				60553F512D3B528A00BAAD7F /* Controller */,
+				60553F522D3B528A00BAAD7F /* PlayerManager.swift */,
+			);
+			path = Detail;
+			sourceTree = "<group>";
+		};
+		60553F5A2D3B528A00BAAD7F /* View */ = {
+			isa = PBXGroup;
+			children = (
+				60553F542D3B528A00BAAD7F /* SongListCell.swift */,
+				60553F552D3B528A00BAAD7F /* SongDownloadCell.swift */,
+				60553F562D3B528A00BAAD7F /* CWTopCustomButton.swift */,
+				60553F572D3B528A00BAAD7F /* CDView.swift */,
+				60553F582D3B528A00BAAD7F /* PlaylistTopView.swift */,
+				60553F592D3B528A00BAAD7F /* SongListTopView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		60553F5D2D3B528A00BAAD7F /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				60553F5B2D3B528A00BAAD7F /* SongDownloadCellViewModel.swift */,
+				60553F5C2D3B528A00BAAD7F /* SongListViewModel.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
+		60553F602D3B528A00BAAD7F /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				60553F5E2D3B528A00BAAD7F /* SongListViewController.swift */,
+				60553FE22D3DF12200BAAD7F /* CustomSegementItem.swift */,
+				60553F5F2D3B528A00BAAD7F /* SongListViewController+Target.swift */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		60553F612D3B528A00BAAD7F /* List */ = {
+			isa = PBXGroup;
+			children = (
+				60553F5A2D3B528A00BAAD7F /* View */,
+				60553F5D2D3B528A00BAAD7F /* ViewModel */,
+				60553F602D3B528A00BAAD7F /* Controller */,
+			);
+			path = List;
+			sourceTree = "<group>";
+		};
+		60553F702D3B528A00BAAD7F /* Custom */ = {
+			isa = PBXGroup;
+			children = (
+				60553FDA2D3B7CC600BAAD7F /* FitManager.swift */,
+				60553F622D3B528A00BAAD7F /* BasePresentViewController.swift */,
+				60553F632D3B528A00BAAD7F /* ManageMenuAlertView.swift */,
+				60553F642D3B528A00BAAD7F /* ImageTextControl.swift */,
+				60553F652D3B528A00BAAD7F /* PlayMiniBar.swift */,
+				60553F662D3B528A00BAAD7F /* NotifactionKey.swift */,
+				60553F672D3B528A00BAAD7F /* TipsView.swift */,
+				60553F682D3B528A00BAAD7F /* THUD.swift */,
+				60553F692D3B528A00BAAD7F /* THUDProtocol.swift */,
+				60553F6A2D3B528A00BAAD7F /* THUD+CW.swift */,
+				60553F6B2D3B528A00BAAD7F /* ToastView.swift */,
+				60553F6C2D3B528A00BAAD7F /* MusicSearchBar.swift */,
+				60553F6D2D3B528A00BAAD7F /* BubbleMenuView.swift */,
+				60553F6E2D3B528A00BAAD7F /* MusicEmptyView.swift */,
+				60553F6F2D3B528A00BAAD7F /* SpacedButton.swift */,
+			);
+			path = Custom;
+			sourceTree = "<group>";
+		};
+		60553FCF2D3B54A400BAAD7F /* MusicBase */ = {
+			isa = PBXGroup;
+			children = (
+				60553FC72D3B54A400BAAD7F /* LWNavigationBar.swift */,
+				60553FC82D3B54A400BAAD7F /* LWBaseViewController.swift */,
+				60553FC92D3B54A400BAAD7F /* LWBaseNavigationController.swift */,
+				60553FCA2D3B54A400BAAD7F /* GradientButton.swift */,
+				60553FCB2D3B54A400BAAD7F /* SaveSuccessTipsView.swift */,
+				60553FCD2D3B54A400BAAD7F /* GradientText.swift */,
+				60553FCE2D3B54A400BAAD7F /* GradientBackgroundModifier.swift */,
+			);
+			path = MusicBase;
+			sourceTree = "<group>";
+		};
 		A81CA45F2D1567CD00A3AAC8 /* LaunchVC */ = {
 			isa = PBXGroup;
 			children = (
@@ -301,12 +785,14 @@
 				A81CA4802D157B0A00A3AAC8 /* UIFont+Ex.swift */,
 				A81CA4762D15779400A3AAC8 /* UIColor+Ex.swift */,
 				A81CA4782D1577E100A3AAC8 /* NSString+Ex.swift */,
+				60553FDE2D3B850C00BAAD7F /* TimeInterval+Ext.swift */,
 				A81CA47A2D15784400A3AAC8 /* Int+Ex.swift */,
 				A81CA4822D157F5200A3AAC8 /* UIImageView+Ex.swift */,
 				A81CA4842D15829E00A3AAC8 /* UIButton+Ex.swift */,
 				A81CA4862D15830E00A3AAC8 /* UILabel+Ex.swift */,
 				A81F5B532D1969D500740085 /* CGFloat+Ex.swift */,
 				A81F5B482D1956E600740085 /* UIScreen.swift */,
+				60553FDC2D3B84E700BAAD7F /* UIScrollView+Ext.swift */,
 			);
 			path = Ex;
 			sourceTree = "<group>";
@@ -354,6 +840,7 @@
 		A81CA48C2D15855300A3AAC8 /* Business */ = {
 			isa = PBXGroup;
 			children = (
+				60553F022D3B4DF800BAAD7F /* TSMusic */,
 				A8F76C3A2D35022300AA6E93 /* TSPurchaseMembershipVC */,
 				A8477C952D2272FB00DF0B93 /* TSBusinessWebVC */,
 				A81CA4932D16527E00A3AAC8 /* TSEditLiveVC */,
@@ -757,6 +1244,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				A8C4C0A42D24218A003C46FC /* metadata.mov in Resources */,
+				60553FC42D3B528A00BAAD7F /* IJKPlayer.md in Resources */,
 				A8E56BF92D1520EC003C54AF /* Assets.xcassets in Resources */,
 				A81F5B522D19685900740085 /* response.json in Resources */,
 				A8E56BFB2D1520EC003C54AF /* LaunchScreen.storyboard in Resources */,
@@ -830,6 +1318,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				A81CA49D2D1654B600A3AAC8 /* UITableView+Ex.swift in Sources */,
+				60553FDB2D3B7CC600BAAD7F /* FitManager.swift in Sources */,
 				A81CA4692D156AB600A3AAC8 /* TSBaseVC.swift in Sources */,
 				A81CA4992D1652C400A3AAC8 /* TSMineVC.swift in Sources */,
 				A8477C992D2291F800DF0B93 /* UserDefault+Ex.swift in Sources */,
@@ -860,11 +1349,19 @@
 				A8E56BF62D1520EC003C54AF /* AppDelegate.swift in Sources */,
 				A8F778B42D1BB8F600BF55D5 /* PhotoManager.swift in Sources */,
 				A81CA4872D15832900A3AAC8 /* UILabel+Ex.swift in Sources */,
+				60553FD02D3B54A400BAAD7F /* LWNavigationBar.swift in Sources */,
+				60553FD22D3B54A400BAAD7F /* GradientButton.swift in Sources */,
+				60553FD42D3B54A400BAAD7F /* LWBaseViewController.swift in Sources */,
+				60553FD62D3B54A400BAAD7F /* SaveSuccessTipsView.swift in Sources */,
+				60553FD72D3B54A400BAAD7F /* GradientText.swift in Sources */,
+				60553FD82D3B54A400BAAD7F /* LWBaseNavigationController.swift in Sources */,
+				60553FD92D3B54A400BAAD7F /* GradientBackgroundModifier.swift in Sources */,
 				A81F5B5D2D1A906C00740085 /* TSHomeDataModel.swift in Sources */,
 				A8F778B02D1AC17500BF55D5 /* TSBaseView.swift in Sources */,
 				A81CA49B2D1652CA00A3AAC8 /* TSHomeVC.swift in Sources */,
 				A81CA4B82D16A6BD00A3AAC8 /* View+Ex.swift in Sources */,
 				A8F76C422D350A9600AA6E93 /* TSPurchaseManager.swift in Sources */,
+				60553FDD2D3B84E700BAAD7F /* UIScrollView+Ext.swift in Sources */,
 				A81CA4792D1577E800A3AAC8 /* NSString+Ex.swift in Sources */,
 				A81CA4852D1582A600A3AAC8 /* UIButton+Ex.swift in Sources */,
 				A81CA46B2D156BDC00A3AAC8 /* TSBaseNavigationC.swift in Sources */,
@@ -884,8 +1381,91 @@
 				A81CA48B2D15843700A3AAC8 /* TSCommonTool.swift in Sources */,
 				A81CA4AD2D16944B00A3AAC8 /* TSBaseTabViewCell.swift in Sources */,
 				A84C239A2D1E3A4300B61B55 /* GPVideoPlayerView.swift in Sources */,
+				60553F722D3B528A00BAAD7F /* PlayDetailListViewContoller.swift in Sources */,
+				60553F732D3B528A00BAAD7F /* SearchResultViewModel.swift in Sources */,
+				60553F742D3B528A00BAAD7F /* Consts.swift in Sources */,
+				60553F752D3B528A00BAAD7F /* MusicEmptyView.swift in Sources */,
+				60553F762D3B528A00BAAD7F /* CWMutiSelectOpeateView.swift in Sources */,
+				60553F772D3B528A00BAAD7F /* DownloadButton.swift in Sources */,
+				60553F782D3B528A00BAAD7F /* ManageMenuAlertView.swift in Sources */,
+				60553F792D3B528A00BAAD7F /* SongListBottomView.swift in Sources */,
+				60553F7A2D3B528A00BAAD7F /* PlaylistViewController.swift in Sources */,
+				60553F7B2D3B528A00BAAD7F /* PlaylistTopView.swift in Sources */,
+				60553F7C2D3B528A00BAAD7F /* AddPlayListViewController.swift in Sources */,
+				60553F7D2D3B528A00BAAD7F /* PlayDetailTopView.swift in Sources */,
+				60553F7E2D3B528A00BAAD7F /* CWOperateViewController+Ext.swift in Sources */,
+				60553F7F2D3B528A00BAAD7F /* OperateTopView.swift in Sources */,
+				60553F802D3B528A00BAAD7F /* PlayDetailViewController+Ext.swift in Sources */,
+				60553F812D3B528A00BAAD7F /* SJIJKMediaPlayer.m in Sources */,
+				60553F822D3B528A00BAAD7F /* SearchResultViewController.swift in Sources */,
+				60553F832D3B528A00BAAD7F /* THUD+CW.swift in Sources */,
+				60553F842D3B528A00BAAD7F /* SongListManageViewModel.swift in Sources */,
+				60553F852D3B528A00BAAD7F /* SongDownloadCellViewModel.swift in Sources */,
+				60553F862D3B528A00BAAD7F /* PlayDetailControlView.swift in Sources */,
+				60553F872D3B528A00BAAD7F /* PlayDetailListViewModel.swift in Sources */,
+				60553F882D3B528A00BAAD7F /* SearchViewModel.swift in Sources */,
+				60553F892D3B528A00BAAD7F /* CWOperateViewController.swift in Sources */,
+				60553F8A2D3B528A00BAAD7F /* THUDProtocol.swift in Sources */,
+				60553F8B2D3B528A00BAAD7F /* THUD.swift in Sources */,
+				60553F8C2D3B528A00BAAD7F /* PlayDetailViewController.swift in Sources */,
+				60553F8D2D3B528A00BAAD7F /* CWCustomProgressView.swift in Sources */,
+				60553F8E2D3B528A00BAAD7F /* PlaylistDetailViewController.swift in Sources */,
+				60553F8F2D3B528A00BAAD7F /* SleepTimeViewController.swift in Sources */,
+				60553F902D3B528A00BAAD7F /* CWLoadingView.swift in Sources */,
+				60553F912D3B528A00BAAD7F /* PlayListDetaiViewModel.swift in Sources */,
+				60553F922D3B528A00BAAD7F /* SJIJKMediaPlayerLayerView.m in Sources */,
+				60553F932D3B528A00BAAD7F /* CWSearchTextBar.swift in Sources */,
+				60553F942D3B528A00BAAD7F /* SongListCell.swift in Sources */,
+				60553F952D3B528A00BAAD7F /* AddPlayListViewModel.swift in Sources */,
+				60553F962D3B528A00BAAD7F /* BaseDataModel.swift in Sources */,
+				60553F972D3B528A00BAAD7F /* PlayDetailPlaceHolderView.swift in Sources */,
+				60553F982D3B528A00BAAD7F /* SJIJKMediaPlaybackController.m in Sources */,
+				60553F992D3B528A00BAAD7F /* FilterBarViewModel.swift in Sources */,
+				60553F9A2D3B528A00BAAD7F /* PlayListManageView.swift in Sources */,
+				60553F9B2D3B528A00BAAD7F /* LWSearchBar.swift in Sources */,
+				60553F9C2D3B528A00BAAD7F /* BubbleMenuView.swift in Sources */,
+				60553F9D2D3B528A00BAAD7F /* SongListViewModel.swift in Sources */,
+				60553F9E2D3B528A00BAAD7F /* SearchResultStateView.swift in Sources */,
+				60553F9F2D3B528A00BAAD7F /* FilterBarView.swift in Sources */,
+				60553FA02D3B528A00BAAD7F /* SongListViewController+Target.swift in Sources */,
+				60553FA12D3B528A00BAAD7F /* ToastView.swift in Sources */,
+				60553FA22D3B528A00BAAD7F /* SongListViewController.swift in Sources */,
+				60553FA32D3B528A00BAAD7F /* PlayListTopItemView.swift in Sources */,
+				60553FA42D3B528A00BAAD7F /* VipTagView.swift in Sources */,
+				60553FA52D3B528A00BAAD7F /* CWTopCustomButton.swift in Sources */,
+				60553FA62D3B528A00BAAD7F /* RelateSeachCell.swift in Sources */,
+				60553FA72D3B528A00BAAD7F /* PlaylistViewController+Ext.swift in Sources */,
+				60553FA82D3B528A00BAAD7F /* CWOperateItemView.swift in Sources */,
+				60553FA92D3B528A00BAAD7F /* CustomTextField.swift in Sources */,
+				60553FAA2D3B528A00BAAD7F /* CWOperateButton.swift in Sources */,
+				60553FAB2D3B528A00BAAD7F /* PlayListViewModel.swift in Sources */,
+				60553FAC2D3B528A00BAAD7F /* TagListView.swift in Sources */,
+				60553FAD2D3B528A00BAAD7F /* ImageTextControl.swift in Sources */,
+				60553FAE2D3B528A00BAAD7F /* CWProgressView.swift in Sources */,
+				60553FAF2D3B528A00BAAD7F /* TipsView.swift in Sources */,
+				60553FB02D3B528A00BAAD7F /* PlayMiniBar.swift in Sources */,
+				60553FB12D3B528A00BAAD7F /* SongDownloadCell.swift in Sources */,
+				60553FB22D3B528A00BAAD7F /* LocalSearchViewModel.swift in Sources */,
+				60553FB32D3B528A00BAAD7F /* SortMenuViewController.swift in Sources */,
+				60553FB42D3B528A00BAAD7F /* ImportFilesManager.swift in Sources */,
+				60553FB52D3B528A00BAAD7F /* PlayButtonView.swift in Sources */,
+				60553FB62D3B528A00BAAD7F /* PlayDetailListViewCell.swift in Sources */,
+				60553FB72D3B528A00BAAD7F /* FilterBarViewController.swift in Sources */,
+				60553FB82D3B528A00BAAD7F /* SongListTopView.swift in Sources */,
+				60553FB92D3B528A00BAAD7F /* CDView.swift in Sources */,
+				60553FBA2D3B528A00BAAD7F /* NotifactionKey.swift in Sources */,
+				60553FBB2D3B528A00BAAD7F /* PlayListAddCell.swift in Sources */,
+				60553FBC2D3B528A00BAAD7F /* SearchOnlineViewController.swift in Sources */,
+				60553FBD2D3B528A00BAAD7F /* SpacedButton.swift in Sources */,
+				60553FBE2D3B528A00BAAD7F /* BasePresentViewController.swift in Sources */,
+				60553FDF2D3B850C00BAAD7F /* TimeInterval+Ext.swift in Sources */,
+				60553FBF2D3B528A00BAAD7F /* PlayListCell.swift in Sources */,
+				60553FC12D3B528A00BAAD7F /* MusicSearchBar.swift in Sources */,
+				60553FC22D3B528A00BAAD7F /* PlayerManager.swift in Sources */,
+				60553FC32D3B528A00BAAD7F /* LocalSearchViewController.swift in Sources */,
 				A8F76C472D3510FE00AA6E93 /* TSNetworkManager.swift in Sources */,
 				A84C239B2D1E3A4300B61B55 /* GPVideoClipperController.swift in Sources */,
+				60553FE32D3DF12200BAAD7F /* CustomSegementItem.swift in Sources */,
 				A84C239C2D1E3A4300B61B55 /* GPVideoClipperView.swift in Sources */,
 				A84C239D2D1E3A4300B61B55 /* GPVideoConfigMaker.swift in Sources */,
 				A839463C2D1D6E3600ABFF0D /* TSRandomWallpaperCopyrightVC.swift in Sources */,

+ 6 - 0
TSLiveWallpaper/Assets.xcassets/Music/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_add_playlist.imageset/ic_add_playlist@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_n.imageset/ic_check_n@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_check_s.imageset/ic_check_s@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_default.imageset/ic_default@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_delete.imageset/ic_delete@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_empty.imageset/ic_empty@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_music_placeholder.imageset/ic_music_placeholder@1x@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_import.imageset/ic_nav_import@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_manage.imageset/ic_nav_manage@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_music.imageset/ic_nav_music@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_nav_sort.imageset/ic_nav_sort@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_playlist_more.imageset/ic_playlist_more@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/ic_remove_playlist.imageset/ic_remove_playlist@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Music/icon_search.imageset/icon_search@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_select_music.imageset/tabbar_select_music@3x.png


+ 23 - 0
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/Contents.json

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

BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@1x.png


BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Tabbar/tabbar_unSelect_music.imageset/tabbar_unSelect_music@3x.png


+ 14 - 0
TSLiveWallpaper/Business/TSMusic/Consts/Consts.swift

@@ -0,0 +1,14 @@
+//
+//  Consts.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/11.
+//
+
+import Foundation
+
+extension Notification.Name {
+    static let K_RefreshNotifaction = Notification.Name("RefreshNotifaction")
+    static let K_ImportSuccessNotifaction = Notification.Name("ImportSuccessNotifaction")
+
+}

+ 64 - 0
TSLiveWallpaper/Business/TSMusic/Custom/BasePresentViewController.swift

@@ -0,0 +1,64 @@
+//
+//  BasePresentViewController.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/25.
+//
+
+import Foundation
+import UIKit
+
+class BasePresentViewController: LWBaseViewController {
+    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+        self.modalPresentationStyle = .overFullScreen
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    private let tapGesture = UITapGestureRecognizer()
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        view.backgroundColor = alertBackgroundColor
+        
+        if shouldDismissWhenTapBackground {
+            tapGesture.addTarget(self, action: #selector(tapBackground))
+            tapGesture.delegate = self
+            view.addGestureRecognizer(tapGesture)
+        }
+    }
+    
+    @objc private func tapBackground() {
+        dismissPresent()
+    }
+    
+    var shouldDismissWhenTapBackground: Bool {
+        return false
+    }
+    
+    var alertBackgroundColor: UIColor {
+        return UIColor.black.withAlphaComponent(0.4)
+    }
+    
+    var animationContentView: UIView {
+        return view
+    }
+    
+    func dismissPresent() {
+        
+    }
+}
+
+extension BasePresentViewController: UIGestureRecognizerDelegate {
+    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+        if gestureRecognizer == tapGesture {
+            let point = gestureRecognizer.location(in: view)
+            if animationContentView.frame.contains(point) {
+                return false
+            }
+        }
+        return true
+    }
+}

+ 135 - 0
TSLiveWallpaper/Business/TSMusic/Custom/BubbleMenuView.swift

@@ -0,0 +1,135 @@
+//
+//  BubbleMenuView.swift
+//  PhysicalWallPaper
+//
+//  Created by nkl on 2024/11/13.
+//
+
+import Foundation
+import UIKit
+import KLExtension
+
+enum ImportSource {
+    case file // 文件
+    case library // 相册
+}
+
+class BubbleMenuView: UIView {
+    enum Position {
+        case topLeft
+        case topRight
+    }
+
+    lazy var stackView: UIStackView = {
+        let v = UIStackView()
+        v.axis = .vertical
+        v.alignment = .fill
+        v.distribution = .fill
+        return v
+    }()
+
+    lazy var videoButton = UIButton()
+    lazy var fileButton = UIButton()
+
+    var actionHanlder: ((ImportSource) -> Void)?
+    var position: Position = .topLeft
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        backgroundColor = .clear
+
+        addSubview(stackView)
+        stackView.snp.makeConstraints { make in
+            make.leading.equalTo(12)
+            make.trailing.equalTo(-12)
+            make.bottom.equalToSuperview()
+            make.top.equalTo(8)
+            make.width.equalTo(164)
+        }
+
+        stackView.addArrangedSubview(videoButton)
+        videoButton.snp.makeConstraints { make in
+            make.height.equalTo(44)
+        }
+
+        let line = UIView()
+        line.backgroundColor = .white.withAlphaComponent(0.2)
+        stackView.addArrangedSubview(line)
+        line.snp.makeConstraints { make in
+            make.height.equalTo(1)
+            make.width.equalTo(164)
+        }
+
+        stackView.addArrangedSubview(fileButton)
+        fileButton.snp.makeConstraints { make in
+            make.height.equalTo(44)
+        }
+
+        videoButton.setLocalizedImageAndTitle(image: UIImage(named: "ic-ring-import-video".localized()), title: "From Photo".localized(), for: .normal)
+        videoButton.setTitleColor(.white, for: .normal)
+        videoButton.titleLabel?.font = .systemFont(ofSize: 14)
+
+        fileButton.setLocalizedImageAndTitle(image: UIImage(named: "ic-ring-import-file".localized()), title: "From Files".localized(), for: .normal)
+        fileButton.setTitleColor(.white, for: .normal)
+        fileButton.titleLabel?.font = .systemFont(ofSize: 14)
+
+        videoButton.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
+        fileButton.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        drawPath()
+    }
+
+    func drawPath() {
+        guard frame.size != .zero else {
+            return
+        }
+
+        let path = UIBezierPath(roundedRect: CGRect(x: 0, y: stackView.y, width: width, height: stackView.frame.height), cornerRadius: 8)
+
+        switch position {
+        case .topLeft:
+            path.move(to: CGPoint(x: 24, y: stackView.y))
+            path.addLine(to: CGPoint(x: 24 + 8, y: 0))
+            path.addLine(to: CGPoint(x: 24 + 16, y: stackView.y))
+        case .topRight:
+            path.move(to: CGPoint(x: width - 10, y: stackView.y))
+            path.addLine(to: CGPoint(x: width - 10 - 8, y: 0))
+            path.addLine(to: CGPoint(x: width - 10 - 16, y: stackView.y))
+        }
+        path.close()
+
+        let shapeLayer = CAShapeLayer()
+        shapeLayer.fillColor = UIColor.black.cgColor
+        shapeLayer.path = path.cgPath
+        layer.insertSublayer(shapeLayer, at: 0)
+    }
+
+    @objc func buttonClick(_ sender: UIButton) {
+        switch sender {
+        case videoButton:
+            actionHanlder?(.library)
+        case fileButton:
+            actionHanlder?(.file)
+        default:
+            break
+        }
+    }
+
+    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+        let view = super.hitTest(point, with: event)
+        if !bounds.contains(point) {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                self.removeFromSuperview()
+            }
+        }
+        return view
+    }
+}
+

+ 25 - 0
TSLiveWallpaper/Business/TSMusic/Custom/FitManager.swift

@@ -0,0 +1,25 @@
+//
+//  FitManager.swift
+//  PhysicalWallPaper
+//
+//  Created by nkl on 2024/11/13.
+//
+
+import Foundation
+import Localize_Swift
+
+class FitManager {
+    static var isAr: Bool {
+        let current = Localize.currentLanguage()
+        return current == "ar"
+    }
+
+    static var currentLanguage: String {
+        if let array = UserDefaults.standard.object(forKey: "AppleLanguages") as? [String] {
+            let lan = array.first ?? "en"
+            let final = lan.split(separator: "-").first ?? "en"
+            return String(final)
+        }
+        return "en"
+    }
+}

+ 70 - 0
TSLiveWallpaper/Business/TSMusic/Custom/ImageTextControl.swift

@@ -0,0 +1,70 @@
+//
+//  ImageTextControl.swift
+//  TSLiveWallpaper
+//
+//  Created by nkl on 2024/12/24.
+//
+
+import Foundation
+import UIKit
+
+class ImageTextControl: UIControl {
+    private lazy var imageView: UIImageView = {
+        let imageView = UIImageView()
+        imageView.contentMode = .scaleAspectFit
+        return imageView
+    }()
+
+    private lazy var titleLabel: UILabel = {
+        let label = UILabel()
+        label.textAlignment = .center
+        label.font = .systemFont(ofSize: 14)
+        label.numberOfLines = 0
+        return label
+    }()
+    
+    lazy var vStack : UIStackView = .vStack
+
+    var image: UIImage? {
+        didSet {
+            imageView.image = image
+        }
+    }
+
+    var text: String? {
+        didSet {
+            titleLabel.text = text
+        }
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupViews()
+    }
+
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+        setupViews()
+    }
+
+    private func setupViews() {
+        vStack.isUserInteractionEnabled = false
+        addSubview(vStack)
+        vStack.addArrangedSubview(imageView)
+        vStack.addArrangedSubview(titleLabel)
+
+        vStack.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        imageView.snp.makeConstraints { make in
+            make.width.height.equalTo(24)
+        }
+    }
+
+    // 配置图片和文字
+    func configure(image: UIImage?, text: String?) {
+        self.image = image
+        self.text = text
+    }
+}

+ 75 - 0
TSLiveWallpaper/Business/TSMusic/Custom/ManageMenuAlertView.swift

@@ -0,0 +1,75 @@
+//
+//  ManageMenuAlertView.swift
+//  TSLiveWallpaper
+//
+//  Created by nkl on 2025/1/7.
+//
+
+import Foundation
+
+class ManageMenuAlertView: UIControl {
+    lazy var sortButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Sort By", for: .normal)
+        btn.setTitleColor(.black, for: .normal)
+        btn.setImage(.icNavSort, for: .normal)
+        // 调整图片和标题的间距
+        btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) // 右侧添加间距
+        btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0) // 左侧添加间距
+        return btn
+    }()
+
+    lazy var manageButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Manage", for: .normal)
+        btn.setTitleColor(.black, for: .normal)
+        btn.setImage(.icNavManage, for: .normal)
+        // 调整图片和标题的间距
+        btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) // 右侧添加间距
+        btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0) // 左侧添加间距
+        return btn
+    }()
+
+    lazy var bgView: UIView = .simpleView(color: .white)
+
+    lazy var vStack: UIStackView = .vStack
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        bgView.customCornerRadius = 20
+        addSubview(bgView)
+        bgView.addSubview(vStack)
+        vStack.addArrangedSubview(sortButton)
+        vStack.addArrangedSubview(manageButton)
+    }
+
+    func makeConstraints() {
+        bgView.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-16)
+            make.top.equalToSuperview().offset(100)
+        }
+
+        vStack.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+
+        sortButton.snp.makeConstraints { make in
+            make.width.equalTo(125)
+            make.height.equalTo(48)
+        }
+
+        manageButton.snp.makeConstraints { make in
+            make.width.equalTo(125)
+            make.height.equalTo(48)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 102 - 0
TSLiveWallpaper/Business/TSMusic/Custom/MusicEmptyView.swift

@@ -0,0 +1,102 @@
+//
+//  MusicEmptyView.swift
+//  PhysicalWallPaper
+//
+//  Created by nkl on 2024/11/13.
+//
+
+import Foundation
+import UIKit
+
+enum MusicEmptyAlignment {
+    case top
+    case center
+    case bottom
+}
+
+class MusicEmptyView: UIView {
+    lazy var iconView: UIImageView = UIImageView(image: .icEmpty)
+
+    lazy var titleLabel: UILabel = UILabel.simpleLabel(text: "No Music".localized(), font: .systemFont14, color: .black)
+
+    lazy var jumpBtn: UIButton = {
+        let btn = UIButton()
+        btn.backgroundColor = .hexColor("#6EF4F4")
+        btn.setTitle("Go to Add".localized(), for: .normal)
+        btn.setTitleColor(.black, for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 16)
+        btn.customCornerRadius = 25
+        return btn
+    }()
+
+    lazy var containerView: UIView = .simpleView(color: .clear)
+
+    var alignment: MusicEmptyAlignment = .top
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+    }
+
+    convenience init(title: String = "No Music".localized(),
+                     icon: UIImage? = UIImage(named: "ic_empty"),
+                     buttonTitle: String = "Go to Add".localized(),
+                     algin: MusicEmptyAlignment = .top,
+                     needButton: Bool = true
+    ) {
+        self.init(frame: .zero)
+        titleLabel.text = title
+        iconView.image = icon
+        iconView.contentMode = .scaleAspectFill
+        jumpBtn.setTitle(buttonTitle, for: .normal)
+        alignment = algin
+        jumpBtn.isHidden = !needButton
+
+        backgroundColor = .clear
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        addSubview(containerView)
+        containerView.addSubview(iconView)
+        containerView.addSubview(titleLabel)
+        containerView.addSubview(jumpBtn)
+    }
+
+    func makeConstraints() {
+        containerView.snp.makeConstraints { make in
+            if alignment == .top {
+                make.top.equalToSuperview().offset(100)
+                make.centerX.equalToSuperview()
+            } else if alignment == .center {
+                make.center.equalToSuperview()
+            } else if alignment == .bottom {
+                make.bottom.equalToSuperview().offset(-100)
+                make.centerX.equalToSuperview()
+            }
+        }
+
+        iconView.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.centerX.equalToSuperview()
+            make.width.height.equalTo(100)
+        }
+
+        titleLabel.snp.makeConstraints { make in
+            make.top.equalTo(iconView.snp.bottom).offset(20)
+            make.centerX.equalTo(iconView)
+        }
+
+        jumpBtn.snp.makeConstraints { make in
+            make.top.equalTo(titleLabel.snp.bottom).offset(20)
+            make.bottom.equalToSuperview()
+            make.leading.trailing.equalToSuperview()
+            make.height.equalTo(50)
+            make.width.equalTo(250)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 59 - 0
TSLiveWallpaper/Business/TSMusic/Custom/MusicSearchBar.swift

@@ -0,0 +1,59 @@
+//
+//  MusicSearchBar.swift
+//  PhysicalWallPaper
+//
+//  Created by nkl on 2024/11/13.
+//
+
+import Foundation
+import KLExtension
+import UIKit
+
+class MusicSearchBar: UIControl {
+    lazy var searchView: UIView = {
+        let v = UIView()
+        v.backgroundColor = .white.withAlphaComponent(0.1)
+        v.layer.cornerRadius = 12
+        v.layer.masksToBounds = true
+        v.isUserInteractionEnabled = false
+        return v
+    }()
+
+    lazy var iconView: UIImageView = .init(image: .iconSearch)
+
+    lazy var titleLabel: UILabel = .simpleLabel(text: "Search Music", font: .systemFont14, color: .white.withAlphaComponent(0.3))
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeCOnstraints()
+    }
+
+    func addChildren() {
+        addSubview(searchView)
+        searchView.addSubview(iconView)
+        searchView.addSubview(titleLabel)
+    }
+
+    func makeCOnstraints() {
+        searchView.snp.makeConstraints { make in
+            make.horizontalEdges.equalToSuperview()
+            make.verticalEdges.equalToSuperview().inset(4)
+        }
+        iconView.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(16)
+        }
+
+        titleLabel.snp.makeConstraints { make in
+            make.leading.equalTo(iconView.snp.trailing).offset(8)
+            make.trailing.equalToSuperview().offset(-16)
+            make.centerY.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 21 - 0
TSLiveWallpaper/Business/TSMusic/Custom/NotifactionKey.swift

@@ -0,0 +1,21 @@
+//
+//  NotifactionKey.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2023/11/9.
+//
+
+import Foundation
+
+/// 命名规则 kXxxxxxxNotifationName  字符串 “ kXxxxxxxNotifaction ”
+struct NotifactionKey {
+    static let kRefreshListNotifationName: Notification.Name = Notification.Name("RefreshListNotifaction")
+
+    static let kSuccessCachedNotifactionName: Notification.Name = Notification.Name("SuccessCachedNotifaction")
+
+    static let kVideoDidChangedNotifactionName: Notification.Name = Notification.Name("VideoDidChangedNotifactionName")
+    
+    static let kVideoStatusDidChangedNotifactionName: Notification.Name = Notification.Name("VideoStatusDidChangedNotifactionName")
+    
+    static let kPlayDetailUpdateNotifactionName: Notification.Name = Notification.Name("PlayDetailUpdateNotifactionName")
+}

+ 139 - 0
TSLiveWallpaper/Business/TSMusic/Custom/PlayMiniBar.swift

@@ -0,0 +1,139 @@
+//
+//  PlayMiniBar.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/2/23.
+//
+
+import Foundation
+import TSVideoKit
+import UIKit
+
+class PlayMiniBar: UIView {
+    lazy var iconView: UIImageView = .simpleImage(imageName: "ic_default")
+    lazy var titleLabel: UILabel = .simpleLabel(text: "", color: .white)
+    lazy var bgImageView: UIView = UIView()
+    lazy var playButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_mini_play"), for: .normal)
+        btn.setImage(UIImage(named: "ic_mini_pause"), for: .selected)
+        return btn
+    }()
+
+    lazy var nextButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_mini_next"), for: .normal)
+        return btn
+    }()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        addNotifaction()
+        makeConstraints()
+    }
+
+    func addNotifaction() {
+        NotificationCenter.default.addObserver(self, selector: #selector(updateMiniBarInfo), name: NotifactionKey.kVideoDidChangedNotifactionName, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(updateMiniBarStatus), name: NotifactionKey.kVideoStatusDidChangedNotifactionName, object: nil)
+    }
+
+    @objc func playButtonAction() {
+        /// 播放器没源,但是有当前播放歌曲
+        if PlayerManager.shared.player?.playControl.player.urlAsset == nil, let current = PlayerManager.shared.currentVideo {
+            PlayerManager.shared.playVideo(video: current, list: [], scene: .local, onceAdKey: "")
+        } else {
+            PlayerManager.shared.playOrPause()
+        }
+    }
+
+    @objc func playNextButtonAction() {
+        PlayerManager.shared.playNext()
+    }
+
+    @objc func updateMiniBarStatus() {
+    }
+
+    @objc func updateMiniBarInfo() {
+    }
+
+    func resetVideoInfo() {
+        iconView.contentMode = .scaleAspectFill
+        titleLabel.text = "Unknown".localized()
+        iconView.image = UIImage(named: "ic_default")
+        playButton.setImage(UIImage(named: "ic_mini_play"), for: .normal)
+    }
+
+    func updateVideoInfo(video: TSVideo, state: PlayState) {
+        titleLabel.text = video.title
+//        titleLabel.speed = .duration(10)
+
+        let isPlaying = video.videoId == PlayerManager.shared.currentVideo?.videoId
+        titleLabel.textColor = isPlaying ? .hexColor("#6EF4F4") : .white
+
+        if video.isOnline {
+            iconView.kf.setImage(with: video.iconUrl)
+        } else {
+            if let imageData = video.artwork {
+                iconView.image = UIImage(data: imageData)
+            } else {
+                iconView.image = UIImage(named: "ic_default")
+            }
+        }
+        if state == .pause {
+            playButton.setImage(UIImage(named: "ic_mini_play"), for: .normal)
+        } else {
+            playButton.setImage(UIImage(named: "ic_mini_pause"), for: .normal)
+        }
+    }
+
+    func addChildren() {
+        iconView.customCornerRadius = 19
+        iconView.contentMode = .scaleAspectFill
+
+        playButton.addTarget(self, action: #selector(playButtonAction), for: .touchUpInside)
+        nextButton.addTarget(self, action: #selector(playNextButtonAction), for: .touchUpInside)
+        bgImageView.isUserInteractionEnabled = true
+        addSubview(bgImageView)
+        bgImageView.addSubview(iconView)
+        bgImageView.addSubview(titleLabel)
+        bgImageView.addSubview(playButton)
+        bgImageView.addSubview(nextButton)
+    }
+
+    func makeConstraints() {
+        bgImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        iconView.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(12)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(38)
+        }
+        titleLabel.snp.makeConstraints { make in
+            make.leading.equalTo(iconView.snp.trailing).offset(8)
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(playButton.snp.leading)
+        }
+        playButton.snp.makeConstraints { make in
+            make.trailing.equalTo(nextButton.snp.leading).offset(-16)
+            make.width.height.equalTo(24)
+            make.centerY.equalToSuperview()
+        }
+        nextButton.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-16)
+            make.width.height.equalTo(24)
+            make.centerY.equalToSuperview()
+        }
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+
+        bgImageView.backgroundColor = .hexColor("#262626")
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 68 - 0
TSLiveWallpaper/Business/TSMusic/Custom/SpacedButton.swift

@@ -0,0 +1,68 @@
+//
+//  SpacedButton.swift
+//  PhysicalWallPaper
+//
+//  Created by nkl on 2024/11/13.
+//
+
+import Foundation
+import UIKit
+
+class SpacedButton: UIButton {
+
+    var spacing: CGFloat = 8.0 {
+        didSet {
+            updateInsets()
+        }
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setup()
+    }
+
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+        setup()
+    }
+
+    private func setup() {
+        // 初始化配置
+        updateInsets()
+    }
+
+    private func updateInsets() {
+        guard let imageView = imageView, let titleLabel = titleLabel else {
+            return
+        }
+
+        let imageSize = imageView.frame.size
+        let titleSize = titleLabel.intrinsicContentSize
+
+        imageEdgeInsets = UIEdgeInsets(
+            top: 0,
+            left: -spacing/2,
+            bottom: 0,
+            right: spacing/2
+        )
+
+        titleEdgeInsets = UIEdgeInsets(
+            top: 0,
+            left: spacing/2,
+            bottom: 0,
+            right: -spacing/2
+        )
+
+        contentEdgeInsets = UIEdgeInsets(
+            top: 0,
+            left: spacing/2,
+            bottom: 0,
+            right: spacing/2
+        )
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        updateInsets()
+    }
+}

+ 92 - 0
TSLiveWallpaper/Business/TSMusic/Custom/THUD+CW.swift

@@ -0,0 +1,92 @@
+//
+//  THUD+CW.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/23.
+//
+
+import Foundation
+import UIKit
+
+extension THUD {
+    static func showLoading(_ message: String?,
+                            cancelHandler: (() -> Void)?) {
+        showLoading { config in
+            config.blur = true
+            config.stateMessage = message
+            config.actionTitle = "Cancel".localized()
+            config.actionHandler = { _ in
+                cancelHandler?()
+            }
+        }
+    }
+
+    static func showLoading(_ state: String?,
+                            message: String?) {
+        showLoading { config in
+            config.blur = true
+            config.stateMessage = state
+            config.descMessage = message
+        }
+    }
+
+    static func showLoading(_ stateImage: UIImage?,
+                            message: String?,
+                            actionHandler: ((Bool) -> Void)?) {
+        showLoading { config in
+            config.blur = true
+            config.stateImage = stateImage
+            config.descMessage = message
+            config.actionTitle = "Retry".localized()
+            config.actionHandler = actionHandler
+            config.closeable = true
+        }
+    }
+}
+
+// MARK: loading
+
+public extension THUD {
+    static func showLoading() {
+        showLoading { config in
+            config.backgroundColor = .hexColor("#232327")
+            config.minWidth = nil
+            config.loadingSize = CGSize(width: 32, height: 32)
+        }
+    }
+}
+
+// MARK: toast
+
+public extension THUD {
+    static func toast(_ message: String?,
+                      at position: THUD.Position = .top,
+                      in view: UIView? = nil,
+                      shake: Bool = false) {
+        guard let message = message, !message.isEmpty else { return }
+
+        var tipConfig = TipsViewConfig()
+        tipConfig.message = message
+        tipConfig.background = .hexColor("#2C2B2C")
+        tipConfig.textColor = .white
+        tipConfig.duration = 3.0
+        show(tipConfig, position: position, in: view)
+
+        if shake {
+            UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
+        }
+    }
+}
+
+public extension THUD {
+    static func showLoading(_ configHandler: ((HUDConfig) -> Void)?) {
+        let config = HUDConfig()
+        configHandler?(config)
+
+        hide()
+        guard let superView = UIApplication.topViewController?.view else {
+            return
+        }
+        show(config, position: .center, in: superView)
+    }
+}

+ 100 - 0
TSLiveWallpaper/Business/TSMusic/Custom/THUD.swift

@@ -0,0 +1,100 @@
+//
+//  THUD.swift
+//  ClockWidget
+//
+//  Created by TSYH on 2024/3/8.
+//
+
+import UIKit
+//import ExtensionsKit
+import SnapKit
+
+public class THUD: NSObject {
+    public enum `Position` {
+        case top
+        case center
+        case bottom
+    }
+    
+    public static let shared = THUD()
+    private lazy var tipViews = [THUDViewProtocol]()
+    
+    private var currentViewController: UIViewController? {
+        return UIApplication.shared.delegate?.window??.rootViewController
+    }
+    
+    @discardableResult
+    public static func show(_ element: THUDProtocol,
+                            position: THUD.Position,
+                            in superView: UIView?) -> UIView? {
+        let view = element.hudView
+        // 去重
+        guard !shared.tipViews.contains(where: { $0.hudIdentifier == view.hudIdentifier }) else {
+            return view
+        }
+        shared.show(view: view, in: superView, at: position)
+        
+        if let duration = element.hudDuration, duration > 0 {
+            shared.perform(#selector(hide(identifier:)), with: view.hudIdentifier, afterDelay: duration)
+        }
+        return view
+    }
+    
+    public static func showTips(_ message: String,
+                         in view: UIView? = nil,
+                         style: TipsViewStyle = .normal,
+                         position: `Position` = .center,
+                         shakeType: UIImpactFeedbackGenerator.FeedbackStyle? = nil) {
+        
+        let tipsConfig = style.tipsConfig(message: message)
+        show(tipsConfig, position: position, in: view)
+        
+        if let type = shakeType {
+            UIImpactFeedbackGenerator(style: type).impactOccurred()
+        }
+    }
+    
+    public func show(view: THUDViewProtocol,
+                     in superView: UIView?,
+                     at position: `Position`) {
+        let superView = superView ?? currentViewController?.view
+        guard let superView = superView else { return }
+        
+        superView.addSubview(view)
+        view.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            switch position {
+            case .top:
+                make.centerY.equalToSuperview().multipliedBy(0.3)
+            case .center:
+                make.centerY.equalToSuperview()
+            case .bottom:
+                make.centerY.equalToSuperview().multipliedBy(1.6)
+            }
+            if view.frame.size != .zero {
+                make.size.equalTo(view.frame.size)
+            }
+        }
+        tipViews.append(view)
+    }
+    
+    public static func hide() {
+        shared.tipViews.forEach({ $0.removeFromSuperview() })
+        shared.tipViews.removeAll()
+    }
+    
+    public static func hide(_ identifier: String) {
+        shared.hide(identifier: identifier)
+    }
+}
+
+// MARK: private Method
+extension THUD {
+    @objc func hide(identifier: String) {
+        let views = tipViews.filter({ $0.hudIdentifier == identifier })
+        views.forEach { view in
+            view.removeFromSuperview()
+            tipViews.removeAll(where: { $0 == view })
+        }
+    }
+}

+ 20 - 0
TSLiveWallpaper/Business/TSMusic/Custom/THUDProtocol.swift

@@ -0,0 +1,20 @@
+//
+//  THUDProtocol.swift
+//  THUD
+//
+//  Created by TSYH on 2024/4/23.
+//
+
+import UIKit
+
+public protocol THUDProtocol {
+    // 视图View,其大小必须用约束,由subviews撑开,position则由THUD统一设置
+    var hudView: THUDViewProtocol { get }
+    
+    /// 显示时长,未nil,不自动隐藏,需手动调用hide
+    var hudDuration: TimeInterval? { get }
+}
+
+public protocol THUDViewProtocol where Self: UIView {
+    var hudIdentifier: String? { get }
+}

+ 125 - 0
TSLiveWallpaper/Business/TSMusic/Custom/TipsView.swift

@@ -0,0 +1,125 @@
+//
+//  TipsView.swift
+//  THUD
+//
+//  Created by TSYH on 2024/4/11.
+//
+
+import UIKit
+import SnapKit
+//import ExtensionsKit
+
+public enum TipsViewStyle {
+    case normal
+    case success
+    case warning
+}
+
+public struct TipsViewConfig: THUDProtocol {
+    public var iconName: String?
+    public var message: String?
+    public var background: UIColor?
+    public var textColor: UIColor?
+    public var duration: TimeInterval = 0
+    
+    public init() {
+        
+    }
+    
+    // MARK: THUDProtocol
+    public var hudView: THUDViewProtocol {
+        let v = TipsView()
+        v.config = self
+        return v
+    }
+    
+    public var hudDuration: TimeInterval? {
+        return duration
+    }
+}
+
+class TipsView: UIView, THUDViewProtocol {
+    lazy var iconView = UIImageView()
+    lazy var messageLabel = UILabel()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        customCornerRadius = 12
+        
+        messageLabel.numberOfLines = 0
+        messageLabel.font = UIFont.systemFont(ofSize: 14)
+        messageLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+        
+        let stackView = UIStackView(arrangedSubviews: [iconView, messageLabel])
+        stackView.axis = .horizontal
+        stackView.spacing = 10
+        stackView.alignment = .center
+        stackView.distribution = .fill
+        addSubview(stackView)
+        stackView.snp.makeConstraints { make in
+            make.left.equalTo(16)
+            make.right.equalTo(-16)
+            make.top.equalTo(16)
+            make.bottom.equalTo(-16)
+            make.width.lessThanOrEqualTo(280)
+        }
+        iconView.snp.makeConstraints { make in
+            make.width.height.equalTo(24)
+        }
+    }
+    
+    func setShadow() {
+        layer.shadowColor = UIColor.black.cgColor
+        layer.shadowOpacity = 0.1
+        layer.shadowRadius = 4
+        layer.shadowOffset = CGSize(width: 0, height: 2)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    var hudIdentifier: String? {
+        return messageLabel.text
+    }
+    
+    lazy var config: TipsViewConfig = TipsViewConfig() {
+        didSet {
+            updateUI(config: config)
+        }
+    }
+    
+    private func updateUI(config: TipsViewConfig) {
+        iconView.isHidden = true
+        if let imageName = config.iconName {
+            iconView.isHidden = false
+            iconView.image = UIImage(named: imageName)
+        }
+        backgroundColor = config.background
+        messageLabel.textColor = config.textColor
+        setShadow()
+        messageLabel.text = config.message
+    }
+}
+
+extension TipsViewStyle {
+    func tipsConfig(message: String) -> TipsViewConfig {
+        var config = TipsViewConfig()
+        config.message = message
+        config.duration = 5.0
+        switch self {
+        case .normal:
+            config.background = UIColor.black.withAlphaComponent(0.6)
+            config.textColor = .white
+        case .success:
+            config.background = .white
+            config.textColor = .black
+            config.iconName = "ic-hud-tip-success"
+        case .warning:
+            config.background = .white
+            config.textColor = .black
+            config.iconName = "ic-hud-tip-warning"
+        }
+        return config
+    }
+}

+ 206 - 0
TSLiveWallpaper/Business/TSMusic/Custom/ToastView.swift

@@ -0,0 +1,206 @@
+//
+//  ToastView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/23.
+//
+
+import Foundation
+import UIKit
+
+public class HUDConfig: THUDProtocol {
+    var blur: Bool = false
+    var backgroundColor: UIColor?
+    
+    var minWidth: CGFloat? = 156
+    var loadingSize: CGSize = CGSize(width: 24, height: 24)
+    
+    // 状态
+    var stateMessage: String?
+    var stateImage: UIImage?
+    // 描述
+    var descMessage: String?
+    // 按钮
+    var actionTitle: String?
+    // 按钮事件,true:取消,false:按钮点击
+    var actionHandler: ((Bool) -> Void)?
+    var closeable: Bool = false
+    
+    public var hudView: THUDViewProtocol {
+        let v = ToastView(model: self)
+        if blur {
+            let blurView = ToastBlurView()
+            blurView.identifier = self.stateMessage
+            blurView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
+            blurView.addSubview(v)
+            v.snp.makeConstraints { make in
+                make.center.equalToSuperview()
+            }
+            blurView.frame = UIScreen.main.bounds
+            return blurView
+        }
+        return v
+    }
+    
+    public var hudDuration: TimeInterval? {
+        return nil
+    }
+}
+
+class ToastBlurView: UIView, THUDViewProtocol {
+    var identifier: String?
+    
+    var hudIdentifier: String? {
+        return identifier
+    }
+}
+
+class ToastView: UIView, THUDViewProtocol {
+    lazy var loadingIV = UIImageView(image: UIImage(named: "ic-rotate-loading"))
+    lazy var stateImageView = UIImageView()
+    lazy var stateLabel = UILabel()
+    lazy var messageLabel = UILabel()
+    lazy var actionButton: UIButton = {
+        let bt = UIButton()
+        bt.setTitle("", for: .normal)
+        bt.setTitleColor(.white, for: .normal)
+        bt.titleLabel?.font = UIFont.systemFont(ofSize: 14)
+        bt.backgroundColor = UIColor.white.withAlphaComponent(0.1)
+        bt.customCornerRadius = 18
+        bt.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
+        return bt
+    }()
+    lazy var closeButton: UIButton = {
+        let bt = UIButton()
+        bt.setImage(UIImage(named: "ic-close"), for: .normal)
+        bt.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
+        bt.isHidden = true
+        return bt
+    }()
+    
+    lazy var model = HUDConfig()
+    required init(model: HUDConfig) {
+        super.init(frame: .zero)
+        self.model = model
+        
+        customCornerRadius = 16
+        backgroundColor = model.backgroundColor ?? .hexColor("#262626")
+        
+        let stackView = UIStackView(arrangedSubviews:
+                                        [loadingIV,
+                                         stateImageView,
+                                         stateLabel,
+                                         messageLabel,
+                                         actionButton])
+        stackView.axis = .vertical
+        stackView.spacing = 10
+        stackView.distribution = .fill
+        stackView.alignment = .center
+        addSubview(stackView)
+        stackView.snp.makeConstraints { make in
+            make.top.equalTo(16)
+            make.bottom.equalTo(-16)
+            if let width = model.minWidth {
+                make.width.greaterThanOrEqualTo(width)
+            }
+            make.left.equalTo(16)
+            make.right.equalTo(-16)
+        }
+        
+        loadingIV.contentMode = .scaleAspectFit
+        loadingIV.snp.makeConstraints { make in
+            make.size.equalTo(model.loadingSize)
+        }
+        
+        loadingIV.isHidden = model.stateImage != nil
+        stateImageView.isHidden = model.stateImage == nil
+        stateImageView.contentMode = .scaleAspectFit
+        stateImageView.snp.makeConstraints { make in
+            make.width.height.equalTo(30)
+        }
+        
+        stateLabel.isHidden = model.stateMessage?.isEmpty ?? true
+        stateLabel.snp.makeConstraints { make in
+            make.height.equalTo(18)
+        }
+        
+        messageLabel.isHidden = model.descMessage?.isEmpty ?? true
+        messageLabel.snp.makeConstraints { make in
+            make.height.equalTo(18)
+        }
+        
+        actionButton.isHidden = model.actionTitle == nil
+        actionButton.setTitle(model.actionTitle, for: .normal)
+        actionButton.snp.makeConstraints { make in
+            make.width.equalTo(100)
+            make.height.equalTo(36)
+        }
+        
+        closeButton.isHidden = !model.closeable
+        addSubview(closeButton)
+        closeButton.snp.makeConstraints { make in
+            make.top.equalTo(4)
+            make.right.equalTo(-4)
+            make.width.height.equalTo(24)
+        }
+        
+        stateImageView.image = model.stateImage
+        
+        stateLabel.font = .systemFont(ofSize: 12)
+        stateLabel.textColor = .white
+        stateLabel.text = model.stateMessage
+        
+        messageLabel.font = .systemFont(ofSize: 12)
+        messageLabel.textColor = .white
+        messageLabel.text = model.descMessage
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func layoutSubviews() {
+        super.layoutSubviews()
+//        setGradient(colors: AppTheme.gradientAlphaColors, index: 0)
+    }
+    
+    override func didMoveToSuperview() {
+        super.didMoveToSuperview()
+        if superview != nil {
+            startLoadingAnimation()
+        } else {
+            loadingIV.layer.removeAllAnimations()
+        }
+    }
+    
+    @objc private func buttonClick(_ sender: UIButton) {
+        switch sender {
+        case actionButton:
+            model.actionHandler?(false)
+        case closeButton:
+            model.actionHandler?(true)
+        default:
+            break
+        }
+    }
+    
+    private func startLoadingAnimation() {
+        // 1.创建动画
+        let animation = CABasicAnimation(keyPath: "transform.rotation.z")
+        // 2.设置动画属性
+        animation.fromValue = 0 // 开始角度
+        animation.toValue = Double.pi * 2 // 结束角度
+        animation.repeatCount = .greatestFiniteMagnitude // 重复次数
+        animation.duration = 1
+        // 动画完成后自动重新开始,默认为NO
+        animation.autoreverses = false
+        // 默认是true,切换到其他控制器再回来,动画效果会消失,需要设置成false,动画就不会停了
+        animation.isRemovedOnCompletion = false
+        // 给需要旋转的view增加动画
+        loadingIV.layer.add(animation, forKey: nil)
+    }
+    
+    var hudIdentifier: String? {
+        return stateLabel.text
+    }
+}

+ 29 - 0
TSLiveWallpaper/Business/TSMusic/Detail/Controller/PlayDetailViewController+Ext.swift

@@ -0,0 +1,29 @@
+//
+//  PlayDetailViewController+Ext.swift
+//  ColorfulWallpaper
+//
+//  Created by ni on 2024/9/18.
+//
+
+import Foundation
+import SJVideoPlayer
+
+extension PlayDetailViewController: SJProgressSliderDelegate {
+    func sliderWillBeginDragging(_ slider: SJProgressSlider) {
+        playControl.pause()
+    }
+
+    func slider(_ slider: SJProgressSlider, valueDidChange value: CGFloat) {
+        
+        let durationTime = playControl.player.duration
+        let currentTime = durationTime * value
+        controlView.updateTime(currentTime: currentTime, duration: durationTime)
+    }
+
+    func sliderDidEndDragging(_ slider: SJProgressSlider) {
+        playControl.seekToProgress(progress: slider.value) { _ in
+            self.playControl.play()
+        }
+        
+    }
+}

+ 561 - 0
TSLiveWallpaper/Business/TSMusic/Detail/Controller/PlayDetailViewController.swift

@@ -0,0 +1,561 @@
+//
+//  PlayDetailViewController.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+//import ADManager
+import Combine
+import Foundation
+import KLTips
+import MediaPlayer
+import SJVideoPlayer
+import TSVideoKit
+import UIKit
+
+class PlayDetailViewController: LWBGViewController {
+    var playControl: TSVideoPlayController {
+        if !(TSVideoOperator.shared.playerController.player.playbackController is SJIJKMediaPlaybackController) {
+            TSVideoOperator.shared.playerController.player.playbackController = SJIJKMediaPlaybackController()
+            TSVideoOperator.shared.playerController.player.isPausedInBackground = false
+            TSVideoOperator.shared.playerController.player.rotationManager?.isDisabledAutorotation = true
+            TSVideoOperator.shared.playerController.player.delayInSecondsForHiddenPlaceholderImageView = 0
+        }
+        return TSVideoOperator.shared.playerController
+    }
+
+    lazy var customLoading: SJLoadingView = SJLoadingView()
+
+    lazy var placeHolderView: PlayDetailPlaceHolderView = PlayDetailPlaceHolderView()
+
+    lazy var retryButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Reload", for: .normal)
+        btn.setTitleColor(.white, for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 14)
+        btn.customCornerRadius = 20
+        btn.isHidden = true
+        return btn
+    }()
+
+    lazy var topView: PlayDetailTopView = PlayDetailTopView()
+    lazy var controlView: PlayDetailControlView = PlayDetailControlView()
+    var downloader: NewChunkDownloader?
+    private var timer: Timer?
+    private var cancellable: [AnyCancellable] = []
+    private var downloaderCancellable: [AnyCancellable] = []
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        bgImageView.image = .viewMainBg
+        playControl.view.backgroundColor = .clear
+
+        addNotifaction()
+        addActionTargets()
+    }
+
+    func updateNavibar(video: TSVideo?) {
+        if video?.videoStatus == .cached {
+            topView.moreButton.isHidden = false
+        } else {
+            topView.moreButton.isHidden = true
+        }
+    }
+
+    override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+//        controlView.setRectCorner(corner:  [.topLeft, .topRight], radii: CGSize.init(width: 24, height: 24))
+        retryButton.setGradient(colors: [.hexColor("#72F4E6"), .hexColor("#374BEC")], index: 0)
+    }
+
+    override func addNotifaction() {
+        super.addNotifaction()
+        addPlayNotifaction()
+        addTaskNotifaction()
+        addSleepNotifacion()
+    }
+
+    func addSleepNotifacion() {
+        PlayerManager.shared.$sleepModel.receive(on: DispatchQueue.main).sink { [weak self] model in
+            guard let self = self else {
+                return
+            }
+            self.topView.timeButton.setTitle(model.countTime.hhmmss, for: .normal)
+            self.topView.timeButton.isSelected = model.isOpen
+
+        }.store(in: &cancellable)
+
+        NotificationCenter.default.addObserver(self, selector: #selector(updateLikeInfo), name: kDataChangedNotifactionName, object: nil)
+
+        NotificationCenter.default.addObserver(self, selector: #selector(retryAudio(notify:)), name: kSingleVideoFailNotifactionName, object: nil)
+    }
+
+    @objc func retryAudio(notify: Notification) {
+        if let videoId = notify.object as? String,
+           videoId == PlayerManager.shared.currentVideo?.videoId {
+            TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: true) { [weak self] downloader in
+                guard let self = self else { return }
+                self.downloader = downloader
+                self.updateDownloader(id: videoId)
+            }
+        }
+    }
+
+    func addPlayNotifaction() {
+        playControl.viewModel.$loadState.receive(on: DispatchQueue.main).sink { [weak self] state in
+            guard let self = self else { return }
+            self.loadingStateChangeHandler(state: state)
+        }.store(in: &cancellable)
+
+        playControl.viewModel.$currentTime.receive(on: DispatchQueue.main).sink { [weak self] timeValue in
+            guard let self = self else { return }
+            self.controlView.updateTime(currentTime: timeValue, duration: self.playControl.player.duration)
+
+        }.store(in: &cancellable)
+
+        playControl.viewModel.$playStatus.receive(on: DispatchQueue.main).sink { [weak self] status in
+            guard let self = self else { return }
+            self.playStatusChangeHandler(status)
+
+        }.store(in: &cancellable)
+
+        playControl.viewModel.$loopMode.receive(on: DispatchQueue.main).sink { [weak self] loop in
+            guard let self = self else { return }
+            self.controlView.playModelButton.setImage(UIImage(named: loop.icon), for: .normal)
+        }.store(in: &cancellable)
+
+        playControl.viewModel.$currentVideo.receive(on: DispatchQueue.main).dropFirst().scan((nil, nil)) { previous, current in
+            (previous.1, current)
+        }.sink { [weak self] oldValue, newValue in
+            guard let self = self else { return }
+
+            /// 即将播放的和正在播放的视频是同一个
+            if newValue != nil &&
+                oldValue?.videoId == newValue?.videoId {
+                PlayerManager.shared.showPlayerViewController()
+                return
+            }
+
+            if let mVideo = newValue {
+                self.controlView.updateVideoInfo(video: mVideo)
+                PlayerManager.shared.miniBar.updateVideoInfo(video: mVideo, state: .pause)
+                self.updateDownloader(id: mVideo.videoId ?? "")
+            } else {
+                self.controlView.resetVideoInfo()
+                self.controlView.resetProgress()
+                PlayerManager.shared.miniBar.resetVideoInfo()
+            }
+            self.updatePlaceHoderView(video: newValue, status: .stopped)
+            self.updateNavibar(video: newValue)
+            if let videoId = newValue?.videoId {
+                UserDefaults.standard.set(videoId, forKey: "LAST_PlAYED_VIDEO_ID")
+            }
+            NotificationCenter.default.post(name: kDataChangedNotifactionName, object: nil)
+        }.store(in: &cancellable)
+    }
+
+    func updatePlaceHoderView(video: TSVideo?, status: VLCMediaPlayerState) {
+        guard let mVideo = video else {
+            return
+        }
+        playControl.view.isHidden = false
+        if mVideo.isOnline {
+            placeHolderView.isHidden = true
+            playControl.player.presentView.placeholderImageView.kf.setImage(with: mVideo.iconUrl)
+        } else {
+            playControl.player.presentView.placeholderImageView.image = nil
+            if mVideo.isAudio {
+                placeHolderView.isHidden = false
+                playControl.view.isHidden = true
+            } else {
+                print("placeHolderView.frame === \(placeHolderView.frame)")
+                if mVideo.videoStatus == .cached {
+                    placeHolderView.isHidden = true
+                } else {
+                    switch status {
+                    case .stopped, .opening:
+                        placeHolderView.isHidden = false
+                    default:
+                        placeHolderView.isHidden = true
+                    }
+                }
+            }
+        }
+    }
+
+    func loadingStateChangeHandler(state: TSLoadState) {
+        /// 展示状态
+        if state == .loading {
+            retryButton.isHidden = true
+            showLoading()
+        } else if state == .fail {
+            hideLoading()
+            retryButton.isHidden = false
+        } else if state == .success {
+            retryButton.isHidden = true
+            hideLoading()
+        } else {
+            retryButton.isHidden = true
+            hideLoading()
+        }
+
+        /// 按钮是否可用
+        if state == .loading {
+            controlView.playButton.playingState = .pause
+            controlView.playlistButton.isEnabled = false
+            controlView.progressView.isUserInteractionEnabled = false
+        } else {
+            controlView.playlistButton.isEnabled = true
+            controlView.progressView.isUserInteractionEnabled = true
+        }
+    }
+
+    fileprivate func playStatusChangeHandler(_ status: VLCMediaPlayerState) {
+        if status == .playing {
+            controlView.playButton.playingState = .playing
+            updatePlaceHoderView(video: playControl.viewModel.currentVideo, status: status)
+            if let video = playControl.viewModel.currentVideo {
+                PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .playing)
+            }
+        } else if status == .paused || status == .ended || status == .stopped {
+            controlView.playButton.playingState = .pause
+            if let video = playControl.viewModel.currentVideo {
+                PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .pause)
+            }
+        } else if status == .buffering {
+            /// 缓冲了展示loading
+            if !playControl.player.isPlaying && !controlView.progressView.isDragging {
+                showLoading()
+            } else {
+                hideLoading()
+            }
+        }
+    }
+
+    func addTaskNotifaction() {
+        // 单个完成回调
+        NotificationCenter.default.addObserver(self, selector: #selector(updateDownoloadStatus(notify:)), name: kGroupDownloadFinishNotifactionName, object: nil)
+    }
+
+    func updateDownloader(id: String) {
+        if let downloader = TSNewDownloadManager.shared.fetchDownloader(identifier: id) {
+            downloaderCancellable.removeAll()
+            self.downloader = downloader
+            downloader.$progress.receive(on: DispatchQueue.main).sink { [weak self] progress in
+                self?.controlView.downloadButton.progressView.set(progress: Double(progress))
+            }.store(in: &downloaderCancellable)
+
+            downloader.$downloadState.receive(on: DispatchQueue.main).sink { state in
+                self.controlView.setDownloadButtonStates(status: state)
+            }.store(in: &downloaderCancellable)
+        } else {
+            downloaderCancellable.removeAll()
+            downloader = nil
+            if let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: id) {
+                controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
+            } else {
+                controlView.setDownloadButtonStates(status: .idle)
+            }
+        }
+    }
+
+    @objc func updateDownoloadStatus(notify: Notification) {
+        if let videoId = notify.object as? String,
+           let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: videoId),
+           let currentVideo = TSVideoOperator.shared.playerViewModel.currentVideo {
+            if videoId == currentVideo.videoId {
+                controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
+            }
+        }
+    }
+
+    @objc func showMoreOperation() {
+        let vc = CWOperateViewController(types: [.like, .addPlaylist, .share, .deleteVideo])
+        vc.modalPresentationStyle = .overFullScreen
+        vc.operteItem = playControl.viewModel.currentVideo
+        PlayerManager.shared.rootVc?.present(vc, animated: true)
+    }
+
+    func countDownTime() {
+        timer?.invalidate()
+        timer = nil
+        PlayerManager.shared.sleepModel.isOpen = true
+        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
+            PlayerManager.shared.sleepModel.countTime -= 1
+
+            if PlayerManager.shared.sleepModel.countTime < 1 {
+                /// 倒计时结束,暂停播放器
+                self.playControl.pause()
+                self.stopTimer()
+            }
+        }
+    }
+
+    func stopTimer() {
+        PlayerManager.shared.sleepModel.isOpen = false
+        PlayerManager.shared.sleepModel.countTime = 0
+        timer?.invalidate()
+        timer = nil
+    }
+
+    func addActionTargets() {
+        topView.backButton.addTarget(self, action: #selector(swapDown), for: .touchUpInside)
+        topView.timeButton.addTarget(self, action: #selector(showSleepTimeController), for: .touchUpInside)
+        topView.moreButton.addTarget(self, action: #selector(showMoreOperation), for: .touchUpInside)
+
+        controlView.playButton.addTarget(self, action: #selector(playButtonAction), for: .touchUpInside)
+        controlView.listButton.addTarget(self, action: #selector(playlistButtonAction), for: .touchUpInside)
+        controlView.nextButton.addTarget(self, action: #selector(nextButtonAction), for: .touchUpInside)
+        controlView.lastButton.addTarget(self, action: #selector(lastButtonAction), for: .touchUpInside)
+        controlView.playModelButton.addTarget(self, action: #selector(playModeButtonAction), for: .touchUpInside)
+        controlView.downloadButton.addTarget(self, action: #selector(downloadButtonAction), for: .touchUpInside)
+        controlView.playlistButton.addTarget(self, action: #selector(addToPlaylistAction), for: .touchUpInside)
+        controlView.favouriteButton.addTarget(self, action: #selector(favouriteButtonAction), for: .touchUpInside)
+        retryButton.addTarget(self, action: #selector(reloadVideo), for: .touchUpInside)
+        controlView.progressView.tappedExeBlock = { [weak self] _, progress in
+
+            self?.controlView.progressView.value = progress
+            let durationTime = (self?.playControl.player.duration ?? 0)
+            let currentTime = durationTime * progress
+            print("duration---\(durationTime),current ---- \(currentTime)")
+            self?.controlView.updateTime(currentTime: currentTime, duration: durationTime)
+            self?.playControl.pause()
+            self?.playControl.seekToProgress(progress: progress) { [weak self] _ in
+                self?.playControl.play()
+            }
+        }
+        // 创建下滑手势识别器
+        let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(swapDown))
+        swipeDownGesture.direction = .down
+
+        // 添加手势识别器到 myView
+        view.addGestureRecognizer(swipeDownGesture)
+    }
+
+    @objc func reloadVideo() {
+        playControl.viewModel.reloadCurrentVideo()
+    }
+
+    override func addChildren() {
+        super.addChildren()
+        controlView.progressView.delegate = self
+        placeHolderView.isHidden = true
+        addChild(playControl)
+        view.addSubview(topView)
+        view.addSubview(playControl.view)
+        view.addSubview(controlView)
+        view.addSubview(placeHolderView)
+        view.addSubview(customLoading)
+        view.addSubview(retryButton)
+        view.bringSubviewToFront(topView)
+    }
+
+    override func makeConstarints() {
+        super.makeConstarints()
+        topView.snp.makeConstraints { make in
+            make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
+            make.leading.trailing.equalToSuperview()
+            make.height.equalTo(44)
+        }
+        let vRadio = UIScreen.kScreenHeight / 896.0
+        playControl.view.snp.makeConstraints { make in
+            make.top.equalTo(topView.snp.bottom).offset(100 * vRadio)
+            make.leading.trailing.equalToSuperview()
+            make.height.equalTo(playControl.view.snp.width).multipliedBy(9.0 / 16.0)
+        }
+
+        retryButton.snp.makeConstraints { make in
+            make.center.equalTo(playControl.view)
+            make.height.equalTo(40)
+            make.width.equalTo(100)
+        }
+
+        customLoading.snp.makeConstraints { make in
+            make.center.equalTo(playControl.view)
+        }
+
+        placeHolderView.iconView.contentMode = .scaleAspectFit
+        placeHolderView.snp.makeConstraints { make in
+            make.top.equalTo(topView.snp.bottom).offset(50 * vRadio)
+            make.leading.trailing.equalToSuperview()
+            make.height.equalTo(placeHolderView.snp.width).multipliedBy(9.0 / 16.0)
+        }
+
+        controlView.snp.makeConstraints { make in
+            make.leading.trailing.bottom.equalToSuperview()
+            make.top.equalTo(playControl.view.snp.bottom).offset(40)
+            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
+        }
+    }
+}
+
+//
+/// targets
+extension PlayDetailViewController {
+    @objc func swapDown() {
+        PlayerManager.shared.hiddePlayerViewController()
+    }
+
+    @objc func playButtonAction() {
+        /// 播放器没源,但是有当前播放歌曲
+        if PlayerManager.shared.player?.playControl.player.urlAsset == nil, let current = PlayerManager.shared.currentVideo {
+            PlayerManager.shared.playVideo(video: current, list: [], scene: .local, onceAdKey: "")
+        } else {
+            PlayerManager.shared.playOrPause()
+        }
+    }
+
+    @objc func playlistButtonAction() {
+        let vc = PlayDetailListViewContoller()
+        vc.viewModel.videos = playControl.viewModel.currentVideos
+        vc.viewModel.recommendDatas = playControl.viewModel.recommendDatas
+        vc.viewModel.playScene = playControl.viewModel.scene
+        vc.modalPresentationStyle = .overFullScreen
+        present(vc, animated: true)
+    }
+
+    @objc func nextButtonAction() {
+        if FitManager.isAr {
+            playControl.playLast()
+        } else {
+            playControl.playNext()
+        }
+    }
+
+    @objc func lastButtonAction() {
+        if FitManager.isAr {
+            playControl.playNext()
+        } else {
+            playControl.playLast()
+        }
+    }
+
+    @objc func playModeButtonAction() {
+        playControl.viewModel.loopMode = playControl.viewModel.loopMode.nextMode()
+        THUD.toast(playControl.viewModel.loopMode.rawValue, at: .center)
+    }
+
+    @objc func favouriteButtonAction() {
+        if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
+            if video.isFavorite {
+                TSVideoOperator.shared.dataManager.dislikeCurrentVideo { _ in
+                    updateLikeInfo()
+                }
+            } else {
+                TSVideoOperator.shared.dataManager.likeCurrentVideo { _ in
+                    updateLikeInfo()
+                }
+            }
+        }
+    }
+
+    @objc func updateLikeInfo() {
+        if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
+            controlView.favouriteButton.isSelected = video.isFavorite
+        }
+    }
+
+    @objc func downloadButtonAction(sender: DownloadButton) {
+        if let video = TSVideoOperator.shared.playerViewModel.currentVideo,
+           let videoId = video.videoId {
+            switch sender.downloadState {
+            case .idle(isAnimate: false), .idle(isAnimate: true):
+
+//                if PurchaseManager.default.isVip {
+                TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
+                    self?.downloader = downloader
+                    self?.updateDownloader(id: videoId)
+                }
+                if let rootVc = PlayerManager.shared.rootVc {
+                    rootVc.presentingViewController?.dismiss(animated: false)
+//                    if !PurchaseManager.default.isVip {
+//                        ADManager.shared.showAd(scene: ADScene.downloadInsert, from: rootVc)
+//                    }
+                }
+
+//                } else {
+//                    let loading = ADLoadingViewController()
+//                    loading.adScene = .downloadReward
+//                    loading.finishedHandler = { [weak self] finished in
+//                        if finished {
+//                    TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
+//                        self?.downloader = downloader
+//                        self?.updateDownloader(id: videoId)
+//                    }
+//                        }
+//                    }
+//                    PlayerManager.shared.rootVc?.present(loading, animated: false)
+//                }
+                break
+            case .pause:
+                TSNewDownloadManager.shared.resumeTask(id: videoId) { [weak self] downloader in
+                    self?.downloader = downloader
+                    self?.updateDownloader(id: videoId)
+                }
+
+            case .downloading:
+                TSNewDownloadManager.shared.pauseTask(id: videoId) { [weak self] downloader in
+                    self?.downloader = downloader
+                    self?.updateDownloader(id: videoId)
+                }
+
+            case .retry:
+                TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: false) { [weak self] downloader in
+                    self?.downloader = downloader
+                    self?.updateDownloader(id: videoId)
+                }
+                break
+            default:
+                break
+            }
+        }
+    }
+
+    // 调用这个方法来显示加载圈
+    func showLoading() {
+        customLoading.start()
+    }
+
+    // 调用这个方法来隐藏加载圈
+    func hideLoading() {
+        customLoading.stop()
+    }
+
+    @objc func addToPlaylistAction() {
+        if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
+            let addVc: AddPlayListViewController = AddPlayListViewController(video: video)
+            addVc.addSuccessBlock = {
+                THUD.toast("Added Successfully".localized())
+            }
+            addVc.modalPresentationStyle = .overFullScreen
+            PlayerManager.shared.rootVc?.present(addVc, animated: true)
+        }
+    }
+
+    @objc func showSleepTimeController() {
+        let times: [Int] = [15, 30, 60, 90, 120]
+        // 创建 UIAlertController 实例
+        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+        for time in times {
+            let option = UIAlertAction(title: "\(time) minutes", style: .default) { [weak self] _ in
+                PlayerManager.shared.sleepModel.countTime = Double(time) * 60.0
+                self?.countDownTime()
+            }
+            alertController.addAction(option)
+        }
+
+        // 添加关闭按钮
+        let offAction = UIAlertAction(title: "Turn Off".localized(), style: .default) { _ in
+            self.stopTimer()
+        }
+        offAction.setValue(UIColor.red, forKey: "titleTextColor")
+        alertController.addAction(offAction)
+
+        let cancelAction = UIAlertAction(title: "Cancel".localized(), style: .cancel) { _ in }
+        alertController.addAction(cancelAction)
+
+        present(alertController, animated: true, completion: nil)
+    }
+}

+ 206 - 0
TSLiveWallpaper/Business/TSMusic/Detail/Controller/SleepTimeViewController.swift

@@ -0,0 +1,206 @@
+//
+//  SleepTimeViewController.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/26.
+//
+
+import Combine
+import Foundation
+import KLExtension
+
+let kSleepModelKey = "SleepModelKey"
+
+class SleepTimeViewController: LWBaseViewController, UIPickerViewDataSource, UIPickerViewDelegate {
+    lazy var bgView: UIView = .simpleView(color: .hexColor("#01143C"))
+    lazy var titleLabel: UILabel = .simpleLabel(text: "Sleep Mode".localized(), font: .boldSystemFont18, color: .white)
+    lazy var dismissControl: UIControl = {
+        let control = UIControl()
+        control.addTarget(self, action: #selector(dismissCurrentVc), for: .touchUpInside)
+        return control
+    }()
+
+    lazy var switchButton: UISwitch = {
+        let btn = UISwitch()
+        btn.onTintColor = UIColor.hexColor("#56A4E9")
+        btn.addTarget(self, action: #selector(startSleepCountDown(sender:)), for: .valueChanged)
+        return btn
+    }()
+
+    lazy var customPickerView: UIPickerView = {
+        let pick = UIPickerView()
+        pick.dataSource = self
+        pick.delegate = self
+        return pick
+    }()
+
+    lazy var timeView: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("00:00:00", for: .normal)
+        btn.setTitleColor(.white, for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 18)
+        btn.setImage(UIImage(named: "ic_clock_time_large"), for: .normal)
+        let spacing: CGFloat = 10.0
+        btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing / 2, bottom: 0, right: spacing / 2)
+        btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: -spacing / 2)
+        btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: spacing / 2)
+        return btn
+    }()
+
+    let hours = Array(0 ... 23)
+    let minutes = Array(0 ... 59)
+
+    private var cancellables: [AnyCancellable] = []
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        view.backgroundColor = .black.withAlphaComponent(0.6)
+        addNotifaction()
+
+        /// 默认为1分钟
+        if PlayerManager.shared.sleepModel.countTime == 0 {
+            PlayerManager.shared.sleepModel.countTime = 0
+        }
+    }
+
+    @objc func startSleepCountDown(sender: UISwitch) {
+        if PlayerManager.shared.sleepModel.countTime == 0 {
+            sender.isOn = false
+            return
+        }
+        PlayerManager.shared.sleepModel.isOpen = sender.isOn
+        if sender.isOn {
+            PlayerManager.shared.player?.countDownTime()
+        } else {
+            PlayerManager.shared.player?.stopTimer()
+        }
+        dismissCurrentVc()
+    }
+
+    func selectTime(time: TimeInterval) {
+        timeView.setTitle(time.hhmmss, for: .normal)
+        let hour = Int(time) / 3600
+        let minute = (Int(time) % 3600) / 60
+        if let index = hours.firstIndex(of: hour) {
+            customPickerView.selectRow(index, inComponent: 0, animated: false)
+        } else {
+            customPickerView.selectRow(0, inComponent: 0, animated: false)
+        }
+        if let minIndex = minutes.firstIndex(of: minute) {
+            customPickerView.selectRow(minIndex, inComponent: 1, animated: false)
+        } else {
+            customPickerView.selectRow(0, inComponent: 1, animated: false)
+        }
+    }
+
+    override func addNotifaction() {
+        super.addNotifaction()
+        PlayerManager.shared.$sleepModel.receive(on: DispatchQueue.main).sink { model in
+            self.selectTime(time: model.countTime)
+            self.switchButton.isOn = model.isOpen
+        }.store(in: &cancellables)
+    }
+
+    override func addChildren() {
+        super.addChildren()
+        view.addSubview(bgView)
+        view.addSubview(dismissControl)
+        bgView.addSubview(titleLabel)
+        bgView.addSubview(switchButton)
+        bgView.addSubview(customPickerView)
+        bgView.addSubview(timeView)
+    }
+
+    override func makeConstarints() {
+        super.makeConstarints()
+
+        dismissControl.snp.makeConstraints { make in
+            make.top.leading.trailing.equalToSuperview()
+            make.bottom.equalTo(bgView.snp.top)
+        }
+
+        bgView.snp.makeConstraints { make in
+            make.leading.trailing.equalToSuperview()
+            make.bottom.equalToSuperview()
+        }
+
+        titleLabel.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.bottom.equalTo(timeView.snp.top).offset(-30)
+            make.top.equalToSuperview().offset(30)
+        }
+
+        switchButton.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-16)
+            make.centerY.equalTo(titleLabel)
+        }
+
+        timeView.snp.makeConstraints { make in
+            make.bottom.equalTo(customPickerView.snp.top).offset(-30)
+            make.centerX.equalToSuperview()
+        }
+
+        customPickerView.snp.makeConstraints { make in
+            make.leading.trailing.equalToSuperview()
+            make.bottom.equalToSuperview().offset(-(view.safeAreaInsets.bottom + 20))
+        }
+    }
+
+    @objc func dismissCurrentVc() {
+        dismiss(animated: true)
+    }
+
+    // MARK: - UIPickerViewDataSource
+
+    func numberOfComponents(in pickerView: UIPickerView) -> Int {
+        return 2 // 两个组件:小时和分钟
+    }
+
+    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+        if component == 0 {
+            return hours.count // 小时的行数
+        } else {
+            return minutes.count // 分钟的行数
+        }
+    }
+
+    // MARK: - UIPickerViewDelegate
+
+    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
+        if component == 0 {
+            return "\(hours[row]) hours" // 显示小时
+        } else {
+            return String(format: "%2d min", minutes[row]) // 显示分钟
+        }
+    }
+
+    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+        let selectedHour = pickerView.selectedRow(inComponent: 0)
+        let selectedMinute = pickerView.selectedRow(inComponent: 1)
+
+        let totalScends = hours[selectedHour] * 60 * 60 + minutes[selectedMinute] * 60
+        PlayerManager.shared.sleepModel.countTime = TimeInterval(totalScends)
+        print("Selected time: \(hours[selectedHour]):\(minutes[selectedMinute])")
+    }
+
+    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
+        40
+    }
+
+    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
+        let label = (view as? UILabel) ?? UILabel()
+        if component == 0 {
+            label.text = "\(hours[row]) hours"
+        } else {
+            label.text = String(format: "%2d min", minutes[row])
+        }
+        label.textAlignment = .center
+        label.font = .boldSystemFont(ofSize: 18)
+        label.textColor = .white // 自定义文字颜色
+        return label
+    }
+
+    deinit {
+        cancellables.removeAll()
+    }
+}

+ 210 - 0
TSLiveWallpaper/Business/TSMusic/Detail/PlayerManager.swift

@@ -0,0 +1,210 @@
+//
+//  PlayRootManager.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+//import ADManager
+import Foundation
+import KLTips
+import TSVideoKit
+
+struct SleepModel: Codable {
+    var isOpen: Bool = false
+    var countTime: TimeInterval = 0
+}
+
+extension LoopMode {
+    var icon: String {
+        switch self {
+        case .cyclic:
+            return "ic_cycle"
+        case .random:
+            return "ic_shuffle"
+        case .repeat:
+            return "ic_single"
+        }
+    }
+}
+
+class PlayerManager {
+    static let shared: PlayerManager = .init()
+    @Published var sleepModel: SleepModel = .init()
+    var showedPlayAd: Bool = false
+
+    var selectedVideos: [TSVideo] = []
+    var removePlaylist: TSPlayList?
+
+    var currentLoopMode: LoopMode {
+        player?.playControl.viewModel.loopMode ?? .cyclic
+    }
+
+    var rootVc: TSTabBarController? {
+        if let vc = UIApplication.shared.windows.first?.rootViewController as? TSTabBarController {
+            return vc
+        }
+        return nil
+    }
+
+    var miniBar: PlayMiniBar {
+        if let vc = UIApplication.shared.windows.first?.rootViewController as? TSTabBarController {
+            return vc.miniBar
+        }
+        return .init()
+    }
+
+    var player: PlayDetailViewController? {
+        if let vc = UIApplication.shared.windows.first?.rootViewController as? TSTabBarController {
+            return vc.playerVc
+        }
+        return nil
+    }
+
+    var currentVideo: TSVideo? {
+        player?.playControl.viewModel.currentVideo
+    }
+
+    func hiddePlayerViewController() {
+        if let tabVc = UIApplication.shared.windows.first?.rootViewController as? TSTabBarController {
+            tabVc.hidePlayerVc()
+        }
+    }
+
+    func showPlayerViewController() {
+        if let tabVc = UIApplication.shared.windows.first?.rootViewController as? TSTabBarController {
+            tabVc.showPlayerVc()
+        }
+    }
+
+    func playOrPause() {
+        guard let player = player else {
+            return
+        }
+        /// 非vip且非国区,暂停要出广告
+        if !PurchaseManager.default.isVip,
+           player.playControl.player.isPlaying,
+           let rootVc = rootVc {
+            rootVc.presentingViewController?.dismiss(animated: false)
+//            ADManager.shared.showAd(scene: ADScene.pauseInsert, from: rootVc)
+        }
+        player.playControl.pauseOrPlay()
+    }
+
+    func pause() {
+        player?.playControl.pause()
+    }
+
+    func play() {
+        player?.playControl.play()
+    }
+
+    func nextPlayModel() {
+        player?.playControl.viewModel.loopMode = player?.playControl.viewModel.loopMode.nextMode() ?? .cyclic
+    }
+
+    func playNext() {
+        if FitManager.isAr {
+            player?.playControl.playLast()
+        } else {
+            player?.playControl.playNext()
+        }
+    }
+
+    func playLast() {
+        if FitManager.isAr {
+            player?.playControl.playNext()
+        } else {
+            player?.playControl.playLast()
+        }
+    }
+
+    func playVideo(video: TSVideo, list: [TSVideo], scene: TSPlayScene, onceAdKey: String) {
+//        if let vc = rootVc, !PurchaseManager.default.isVip {
+//            ADManager.shared.showAd(scene: ADScene.playInsert, from: vc) { state in
+//                if state == .finished || state == .fail {
+//                    self.showPlayerViewController()
+//                    self.player?.playControl.playVideo(video: video, list: list, scene: scene)
+//                }
+//            }
+//        } else {
+            showPlayerViewController()
+            player?.playControl.playVideo(video: video, list: list, scene: scene)
+//        }
+    }
+
+    func playVideo(onlineVideo: VideoOnlineModel, recommendDatas: [VideoOnlineModel], scene: TSPlayScene) {
+        showPlayerViewController()
+        player?.playControl.playOnlineVideo(video: onlineVideo, recommendDaras: recommendDatas, scene: scene)
+    }
+
+    func showMutiOperateView(isFromPlaylist: Bool, playlist: TSPlayList? = nil) {
+//        rootVc?.showMultiSelectView()
+    }
+
+    func hideMutiOperateView() {
+//        rootVc?.hideMultiSelectView()
+    }
+
+    @objc func addVideosToPlaylist() {
+        if selectedVideos.isEmpty {
+            return
+        }
+        let vc = AddPlayListViewController(videos: selectedVideos)
+        vc.addSuccessBlock = {
+//            KLTips.showAutoHidden("Added Successfully".localized())
+            THUD.toast("Added Successfully".localized())
+            PlayerManager.shared.hideMutiOperateView()
+            /// 发送刷新通知
+            NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+        }
+        PlayerManager.shared.rootVc?.present(vc, animated: true)
+    }
+
+    @objc func deleteSelectedVideos() {
+        if selectedVideos.isEmpty {
+            return
+        }
+
+        let ac = UIAlertController(title: nil,
+                                   message: "Are you sure to delete".localized(), preferredStyle: .alert)
+        ac.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel))
+        ac.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: { _ in
+
+            /// 删除当前歌曲
+            if self.selectedVideos.contains(where: {
+                $0.videoId == TSVideoOperator.shared.playerViewModel.currentVideo?.videoId
+            }) {
+                TSVideoOperator.shared.playerController.player.stop()
+                TSVideoOperator.shared.playerViewModel.currentVideo = nil
+            }
+
+            /// 删除播放列表歌曲
+            let videoIds: [String] = self.selectedVideos.compactMap {
+                $0.videoId
+            }
+
+            TSVideoOperator.shared.dataManager.deleteVideos(videos: self.selectedVideos) { _ in
+
+                PlayerManager.shared.hideMutiOperateView()
+
+                for item in videoIds {
+                    TSVideoOperator.shared.playerViewModel.currentVideos.removeAll { $0.isFault }
+                }
+                // 播放删完列表后的第一首
+                let newVideos = TSVideoOperator.shared.playerViewModel.currentVideos
+                if let video = newVideos.first {
+                    /// 播放的时候回刷新列表不用重复刷
+                    TSVideoOperator.shared.playerController.playVideo(video: video, list: newVideos, scene: .local)
+                    /// 发送刷新通知
+                    NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                } else {
+                    /// 发送刷新通知
+                    NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                }
+            }
+
+        }))
+        PlayerManager.shared.rootVc?.present(ac, animated: true)
+    }
+}

+ 48 - 0
TSLiveWallpaper/Business/TSMusic/Detail/View/PlayButtonView.swift

@@ -0,0 +1,48 @@
+//
+//  PlayButtonView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+import Foundation
+import UIKit
+
+enum PlayState {
+    case pause
+    case playing
+}
+
+class PlayButtonView: UIControl {
+    var playingState: PlayState = .pause {
+        didSet {
+            if playingState == .pause {
+                iconView.image = UIImage(named: "ic_detail_playBtn")
+            } else {
+                iconView.image = UIImage(named: "ic_detail_pauseBtn")
+            }
+        }
+    }
+
+    lazy var iconView: UIImageView = .init(image: UIImage(named: "ic_detail_playBtn"))
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        addSubview(iconView)
+    }
+
+    func makeConstraints() {
+        iconView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 307 - 0
TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailControlView.swift

@@ -0,0 +1,307 @@
+//
+//  PlayDetailControlView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+import Foundation
+import Kingfisher
+import MarqueeLabel
+import SJVideoPlayer
+import TSVideoKit
+import UIKit
+
+class PlayDetailControlView: UIView {
+    lazy var titleLabel: MarqueeLabel = {
+        let title: MarqueeLabel = .init(frame: CGRect(x: 0, y: 0, width: 300, height: 30))
+        title.trailingBuffer = 20
+        title.animationDelay = 0
+        title.speed = .duration(10)
+        title.font = .boldSystemFont20
+        title.textAlignment = .left
+        title.textColor = .hexColor("#111111")
+        return title
+    }()
+
+    lazy var detailLabel: UILabel = .simpleLabel(text: "--", font: .systemFont14, color: .hexColor("#999999"), align: .left)
+
+    lazy var topHStack: UIStackView = .hStack
+
+    lazy var favouriteButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_like"), for: .normal)
+        btn.setImage(UIImage(named: "ic_like_s"), for: .selected)
+        return btn
+    }()
+
+    lazy var playlistButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(.icAddPlaylist, for: .normal)
+        btn.isHidden = true
+        return btn
+    }()
+
+    lazy var lastButton: UIButton = {
+        let btn = UIButton()
+        btn.setLocalizedImage(UIImage(named: "ic_play_last"), for: .normal)
+        btn.backgroundColor = .black
+        let vRadio = UIScreen.kScreenHeight / 896.0
+        btn.customCornerRadius = 24 * vRadio
+        return btn
+    }()
+
+    lazy var playButton: PlayButtonView = {
+        let btn = PlayButtonView()
+        let vRadio = UIScreen.kScreenHeight / 896.0
+        btn.customCornerRadius = 35 * vRadio
+        return btn
+    }()
+
+    lazy var nextButton: UIButton = {
+        let btn = UIButton()
+        btn.setLocalizedImage(UIImage(named: "ic_play_next"), for: .normal)
+        btn.backgroundColor = .black
+        let vRadio = UIScreen.kScreenHeight / 896.0
+        btn.customCornerRadius = 24 * vRadio
+        return btn
+    }()
+
+    lazy var progressView: SJProgressSlider = {
+        let progress = SJProgressSlider()
+        progress.trackHeight = 4
+        progress.thumbImageView.image = UIImage(named: "ic_image_thubnail")
+        progress.trackImageView.backgroundColor = .white
+        progress.traceImageView.backgroundColor = .hexColor("#6EF4F4")
+        progress.traceImageView.contentMode = .scaleAspectFill
+        progress.isRound = false
+        progress.animaMaxDuration = 0
+        progress.tap.isEnabled = true
+        return progress
+    }()
+
+    lazy var currentLabel: UILabel = .simpleLabel(text: "00:00", font: .systemFont12, color: .hexColor("#B8B9C1"))
+    lazy var durationLabel: UILabel = .simpleLabel(text: "00:00", font: .systemFont12, color: .hexColor("#B8B9C1"))
+
+    lazy var videoHStack: UIStackView = {
+        let stack: UIStackView = .hStack
+        stack.distribution = .fillEqually
+        return stack
+    }()
+
+    lazy var downloadButton: DownloadButton = {
+        let btn = DownloadButton()
+        btn.downloadState = .idle(isAnimate: false)
+        btn.isHidden = true
+        return btn
+    }()
+
+    lazy var playModelButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_cycle"), for: .normal)
+        return btn
+    }()
+
+    lazy var listButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_current_playlist"), for: .normal)
+        return btn
+    }()
+
+    lazy var hStackView: UIStackView = {
+        let stack: UIStackView = .hStack
+        stack.distribution = .equalSpacing
+        return stack
+    }()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    public func updateTime(currentTime: TimeInterval, duration: TimeInterval) {
+        let value = currentTime / duration
+        progressView.value = value
+        currentLabel.text = currentTime.mmsshh
+        durationLabel.text = duration.mmsshh
+    }
+
+    public func updateVideoInfo(video: TSVideo) {
+        titleLabel.text = video.title
+        detailLabel.text = video.artist
+
+        playlistButton.isHidden = video.videoStatus != .cached
+        downloadButton.isHidden = video.videoStatus == .cached
+        favouriteButton.isHidden = video.videoStatus != .cached
+        setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
+        downloadButton.progressView.set(progress: Double(video.progress))
+        favouriteButton.isSelected = video.isFavorite
+    }
+
+    public func resetVideoInfo() {
+        titleLabel.text = "--"
+        detailLabel.text = "--"
+        playlistButton.isHidden = true
+        downloadButton.isHidden = true
+    }
+
+    func resetProgress() {
+        progressView.value = 0
+        currentLabel.text = "00:00"
+        durationLabel.text = "00:00"
+    }
+
+    public func updateProgress(_ progress: Float) {
+        downloadButton.progressView.set(progress: Double(progress))
+    }
+
+    public func setDownloadButtonStates(status: ChunkDownloadStatus) {
+        switch status {
+        case .idle, .cancel:
+            downloadButton.downloadState = .idle(isAnimate: true)
+            playlistButton.isHidden = true
+            downloadButton.isHidden = false
+            downloadButton.adTag.isHidden = PurchaseManager.default.isVip
+        case .prasing:
+            downloadButton.downloadState = .loading
+            playlistButton.isHidden = true
+            downloadButton.isHidden = false
+        case .praseSuccess:
+            break
+        case .downloading:
+            downloadButton.downloadState = .downloading
+            playlistButton.isHidden = true
+            downloadButton.isHidden = false
+        case .pause:
+            downloadButton.downloadState = .pause
+            playlistButton.isHidden = true
+            downloadButton.isHidden = false
+        case .failed:
+            downloadButton.downloadState = .retry
+            playlistButton.isHidden = true
+            downloadButton.isHidden = false
+        case .finished:
+            /// 完成,隐藏进度条,展示下载按钮,按钮状态设置为更多
+            downloadButton.downloadState = .idle(isAnimate: false)
+            playlistButton.isHidden = false
+            downloadButton.isHidden = true
+            favouriteButton.isHidden = false
+        }
+    }
+
+    public func setDefaultDownloadStatus() {
+        downloadButton.downloadState = .idle(isAnimate: false)
+        downloadButton.progressView.set(progress: 0)
+    }
+
+    private func addChildren() {
+        titleLabel.textAlignment = .left
+        detailLabel.textAlignment = .left
+        addSubview(titleLabel)
+        addSubview(detailLabel)
+        detailLabel.text = ""
+
+        addSubview(topHStack)
+        topHStack.alignment = .center
+        topHStack.spacing = 44
+        topHStack.addArrangedSubview(lastButton)
+        topHStack.addArrangedSubview(playButton)
+        topHStack.addArrangedSubview(nextButton)
+
+        addSubview(progressView)
+        addSubview(currentLabel)
+        addSubview(durationLabel)
+
+        addSubview(videoHStack)
+        videoHStack.addArrangedSubview(playModelButton)
+        videoHStack.addArrangedSubview(favouriteButton)
+        videoHStack.addArrangedSubview(downloadButton)
+        videoHStack.addArrangedSubview(playlistButton)
+        videoHStack.addArrangedSubview(listButton)
+    }
+
+    private func makeConstraints() {
+        let vRadio = UIScreen.kScreenHeight / 896.0
+
+        titleLabel.snp.makeConstraints { make in
+            make.horizontalEdges.equalToSuperview().inset(16)
+            make.top.equalToSuperview()
+        }
+
+        detailLabel.snp.makeConstraints { make in
+            make.horizontalEdges.equalToSuperview().inset(16)
+            make.top.equalTo(titleLabel.snp.bottom).offset(22 * vRadio)
+        }
+
+        progressView.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.trailing.equalToSuperview().offset(-16)
+            make.top.equalTo(detailLabel.snp.bottom).offset(34 * vRadio)
+            make.height.equalTo(50 * vRadio)
+        }
+
+        currentLabel.snp.makeConstraints { make in
+            if FitManager.isAr {
+                make.trailing.equalTo(progressView)
+            } else {
+                make.leading.equalTo(progressView)
+            }
+
+            make.top.equalTo(progressView.snp.bottom).offset(-14 * vRadio)
+        }
+
+        durationLabel.snp.makeConstraints { make in
+            if FitManager.isAr {
+                make.leading.equalTo(progressView)
+            } else {
+                make.trailing.equalTo(progressView)
+            }
+            make.top.equalTo(progressView.snp.bottom).offset(-14 * vRadio)
+        }
+
+        topHStack.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(progressView.snp.bottom).offset(20 * vRadio)
+        }
+
+        lastButton.snp.makeConstraints { make in
+            make.width.height.equalTo(48 * vRadio)
+        }
+
+        playButton.snp.makeConstraints { make in
+            make.width.height.equalTo(70 * vRadio)
+        }
+
+        nextButton.snp.makeConstraints { make in
+            make.width.height.equalTo(48 * vRadio)
+        }
+
+        downloadButton.snp.makeConstraints { make in
+            make.width.height.equalTo(44 * vRadio)
+        }
+        playlistButton.snp.makeConstraints { make in
+            make.width.height.equalTo(44 * vRadio)
+        }
+        favouriteButton.snp.makeConstraints { make in
+            make.width.height.equalTo(44 * vRadio)
+        }
+
+        videoHStack.snp.makeConstraints { make in
+            make.top.equalTo(topHStack.snp.bottom).offset(40 * vRadio)
+            make.leading.trailing.equalToSuperview()
+        }
+
+        playModelButton.snp.makeConstraints { make in
+            make.width.height.equalTo(64 * vRadio)
+        }
+
+        listButton.snp.makeConstraints { make in
+            make.width.height.equalTo(64 * vRadio)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 38 - 0
TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailPlaceHolderView.swift

@@ -0,0 +1,38 @@
+//
+//  PlayDetailPlaceHolderView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+import Foundation
+import UIKit
+
+class PlayDetailPlaceHolderView: UIView {
+    lazy var iconView: UIImageView = UIImageView(image: .icMusicPlaceholder)
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        iconView.contentMode = .scaleAspectFill
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        addSubview(iconView)
+    }
+
+    func makeConstraints() {
+        iconView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+    }
+}

+ 92 - 0
TSLiveWallpaper/Business/TSMusic/Detail/View/PlayDetailTopView.swift

@@ -0,0 +1,92 @@
+//
+//  PlayDetailTopView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/13.
+//
+
+import Foundation
+import UIKit
+
+class PlayDetailTopView: UIView {
+    lazy var backButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_dismiss"), for: .normal)
+        return btn
+    }()
+
+    lazy var hStack: UIStackView = {
+        let stack: UIStackView = .hStack
+        stack.alignment = .center
+        return stack
+    }()
+
+    lazy var timeButton: UIButton = {
+        let button = UIButton()
+        button.frame = CGRect(x: 0, y: 0, width: 100, height: 32)
+        button.backgroundColor = .white.withAlphaComponent(0.2)
+        button.customCornerRadius = 16
+        button.setTitle("00:00:00", for: .normal)
+        button.setTitleColor(.white.withAlphaComponent(0.5), for: .normal)
+        button.setTitleColor(.white, for: .selected)
+        button.titleLabel?.font = .systemFont(ofSize: 12)
+        button.setImage(UIImage(named: "ic_clock_time"), for: .normal)
+        button.setImage(UIImage(named: "ic_clock_time_selected"), for: .selected)
+        let spacing: CGFloat = 10.0
+        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing / 2, bottom: 0, right: spacing / 2)
+        button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: -spacing / 2)
+        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: spacing / 2)
+        button.contentMode = .scaleAspectFit
+        if  FitManager.isAr {
+            button.semanticContentAttribute = .forceLeftToRight
+        }
+
+        return button
+    }()
+
+    lazy var moreButton: UIButton = {
+        let button = UIButton()
+        button.setImage(UIImage(named: "ic_detail_more"), for: .normal)
+        return button
+    }()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    private func addChildren() {
+        
+        addSubview(backButton)
+        addSubview(hStack)
+        timeButton.isHidden = true
+        moreButton.isHidden = true
+        hStack.addArrangedSubview(timeButton)
+        hStack.addArrangedSubview(moreButton)
+    }
+
+    private func makeConstraints() {
+        backButton.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.width.height.equalTo(44)
+        }
+
+        hStack.snp.makeConstraints { make in
+            make.trailing.equalToSuperview().offset(-12)
+            make.centerY.equalToSuperview()
+            make.height.equalToSuperview()
+        }
+        timeButton.snp.makeConstraints { make in
+            make.width.equalTo(100)
+            make.height.equalTo(32)
+        }
+        moreButton.snp.makeConstraints { make in
+            make.width.height.equalTo(44)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 65 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWCustomProgressView.swift

@@ -0,0 +1,65 @@
+//
+//  CWCustomProgressView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/10.
+//
+
+import Foundation
+import UIKit
+
+class CWCustomProgressView : UIView {
+    lazy var progressTitle : UILabel =  {
+        let label = UILabel()
+        label.textAlignment = .center
+        label.font = UIFont.systemFont(ofSize: 10, weight: .medium)
+        label.textColor = .hexColor("#111111")
+        label.text = "0%"
+        return label
+    }()
+    
+    lazy var progressView : CWProgressView = {
+        let progress =  CWProgressView(frame: .init(x: 0, y: 0, width: 36, height: 36), colors: .hexColor("#111111"))
+        progress.trackColor = .clear
+        progress.startAngle = -90
+        return progress
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraint()
+    }
+    
+    func addChildren(){
+        addSubview(progressTitle)
+        addSubview(progressView)
+    }
+    
+    func makeConstraint(){
+        progressView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        progressTitle.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
+        
+    }
+    
+    func set(progress: Double) {
+        if progress.isFinite {
+            self.progressTitle.text = "\(Int(progress * 100))%"
+            self.progressView.progress = progress
+        }else{
+            self.progressTitle.text = "0%"
+            self.progressView.progress = 0
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    
+}

+ 53 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWLoadingView.swift

@@ -0,0 +1,53 @@
+//
+//  CWLoadingView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/11.
+//
+
+import Foundation
+import UIKit
+
+class CWLoadingView: UIView {
+    lazy var activityIndicator: UIActivityIndicatorView = {
+        let activityIndicator = UIActivityIndicatorView(style: .medium)
+        activityIndicator.color = .black
+        return activityIndicator
+    }()
+        
+    fileprivate var topOffset: CGFloat = 20
+
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+        startLoading()
+        
+    }
+
+    public func startLoading() {
+        activityIndicator.startAnimating()
+    }
+
+    public func stopLoading() {
+        activityIndicator.stopAnimating()
+    }
+
+    private func addChildren() {
+        addSubview(activityIndicator)
+    }
+
+    private func makeConstraints() {
+        activityIndicator.snp.makeConstraints { make in
+            make.width.height.equalTo(50)
+            make.centerX.equalToSuperview()
+            make.top.equalToSuperview().offset(self.topOffset)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+

+ 44 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWMutiSelectOpeateView.swift

@@ -0,0 +1,44 @@
+//
+//  CWMutiSelectOpeateView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/23.
+//
+
+import Foundation
+import UIKit
+
+class CWMutiSelectOpeateView: UIView {
+    lazy var hStack: UIStackView = {
+        let stack: UIStackView = .hStack
+        stack.distribution = .fillEqually
+        return stack
+    }()
+
+    lazy var deleteButton: CWOperateButton = .init(title: "Delete".localized(), imgName: "ic_delete")
+    lazy var addPlaylist: CWOperateButton = .init(title: "Add to Playlist".localized(), imgName: "ic_addPlaylist")
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        backgroundColor = .hexColor("#262626")
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        addSubview(hStack)
+        hStack.addArrangedSubview(deleteButton)
+        hStack.addArrangedSubview(addPlaylist)
+    }
+
+    func makeConstraints() {
+        hStack.snp.makeConstraints { make in
+            make.top.equalToSuperview().offset(20)
+            make.leading.trailing.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 59 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWOperateButton.swift

@@ -0,0 +1,59 @@
+//
+//  CWOperateButton.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/23.
+//
+
+import Foundation
+import UIKit
+
+class CWOperateButton : UIControl {
+    
+    lazy var bgView : UIView = .simpleView(color: .white.withAlphaComponent(0.1))
+    
+    lazy var iconView : UIImageView = .simpleImage(imageName: "icon_delete")
+    
+    lazy var titleLabel : UILabel = .simpleLabel(text: "Delete".localized(),font: .systemFont12,color: .white)
+    
+    
+    init(title:String,imgName:String) {
+        super.init(frame: .zero)
+        self.iconView.image = UIImage.init(named: imgName)
+        self.titleLabel.text = title
+        self.titleLabel.textAlignment = .center
+        bgView.isUserInteractionEnabled = false
+        addChildren()
+        makeConstraints()
+    }
+    
+    func addChildren(){
+        bgView.customCornerRadius = 16
+        addSubview(bgView)
+        bgView.addSubview(iconView)
+        addSubview(titleLabel)
+    }
+    
+    func makeConstraints(){
+        bgView.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.centerX.equalToSuperview()
+            make.height.width.equalTo(48)
+        }
+        
+        iconView.snp.makeConstraints { make in
+            make.center.equalTo(bgView)
+        }
+        
+        titleLabel.snp.makeConstraints { make in
+            make.leading.trailing.equalToSuperview()
+            make.width.greaterThanOrEqualTo(48)
+            make.bottom.equalToSuperview()
+            make.top.equalTo(bgView.snp.bottom).offset(8)
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 50 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWOperateItemView.swift

@@ -0,0 +1,50 @@
+//
+//  CWOperateItemView.swift
+//  ColorfulWallpaper
+//
+//  Created by ni on 2024/9/19.
+//
+
+import Foundation
+import UIKit
+
+class CWOperateItemView: UIControl {
+    lazy var iconView: UIImageView = .simpleImage(imageName: "")
+    lazy var titleLabel: UILabel = .simpleLabel(text: "", font: .systemFont14, color: .black)
+    var type: CWOperateType = .like
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    convenience init(imgName: String, title: String, type: CWOperateType) {
+        self.init(frame: .zero)
+        iconView.image = .init(named: imgName)
+        titleLabel.text = title
+        self.type = type
+    }
+
+    func addChildren() {
+        addSubview(iconView)
+        addSubview(titleLabel)
+    }
+
+    func makeConstraints() {
+        iconView.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(16)
+            make.width.height.equalTo(28)
+            make.verticalEdges.equalToSuperview().inset(18)
+        }
+
+        titleLabel.snp.makeConstraints { make in
+            make.leading.equalTo(iconView.snp.trailing).offset(12)
+            make.trailing.equalToSuperview().offset(-16)
+            make.centerY.equalTo(iconView)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 152 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWOperateViewController+Ext.swift

@@ -0,0 +1,152 @@
+//
+//  CWOperateViewController+Ext.swift
+//  ColorfulWallpaper
+//
+//  Created by ni on 2024/9/19.
+//
+
+import Foundation
+import KLTips
+import TSVideoKit
+
+extension CWOperateViewController {
+    func addTargets() {
+        disAppearArea.addTarget(self, action: #selector(dismissCurrentVc), for: .touchUpInside)
+        likeItem.addTarget(self, action: #selector(likeVideoAction(sender:)), for: .touchUpInside)
+        addPlaylistItem.addTarget(self, action: #selector(addToPlayList), for: .touchUpInside)
+        shareItem.addTarget(self, action: #selector(shareApp), for: .touchUpInside)
+        deleteItem.addTarget(self, action: #selector(deleteVideoAction), for: .touchUpInside)
+        removeFromPlaylist.addTarget(self, action: #selector(removeSongFromPlaylist), for: .touchUpInside)
+    }
+
+    @objc func likeVideoAction(sender: CWOperateItemView) {
+        dismiss(animated: true) {
+            if let mVideo = self.operteItem {
+                if mVideo.isFavorite {
+                    TSVideoOperator.shared.dataManager.dislikeVideo(video: mVideo) { _ in
+                        self.updateLikeInfo(item: sender)
+                        NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                    }
+                } else {
+                    TSVideoOperator.shared.dataManager.likeVideo(video: mVideo) { _ in
+                        self.updateLikeInfo(item: sender)
+                        NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                    }
+                }
+            }
+        }
+    }
+
+    @objc func removeSongFromPlaylist() {
+        dismiss(animated: true) {
+            if let list = self.playlist, let item = self.operteItem {
+                TSVideoOperator.shared.dataManager.removeVideosFromPlaylist(videos: [item], from: list) { _ in
+                    THUD.toast("Removed Successfully".localized())
+                    NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                }
+            }
+        }
+    }
+
+    @objc func addToPlayList() {
+        if let mVideo = operteItem {
+            let addVc: AddPlayListViewController = AddPlayListViewController(video: mVideo)
+            addVc.addSuccessBlock = { [weak self] in
+                NotificationCenter.default.post(name: kDataChangedNotifactionName, object: nil)
+                self?.dismiss(animated: true, completion: {
+                    THUD.toast("Added Successfully".localized())
+                })
+            }
+            addVc.modalPresentationStyle = .overFullScreen
+            present(addVc, animated: true)
+        }
+    }
+
+    @objc func shareApp() {
+        dismiss(animated: true) {
+            let httpAppStoreLink = "https://apps.apple.com/app/id\(TSConfig.appid)"
+            let text = ""
+            let url = URL(string: httpAppStoreLink)!
+            let image = UIImage(named: "App-Icon")!
+            let vc = UIActivityViewController(activityItems: [image, text, url], applicationActivities: nil)
+            vc.completionWithItemsHandler = { activity, value, _, error in
+                if let type = activity, type == .copyToPasteboard {
+                    UIPasteboard.general.string = httpAppStoreLink
+                }
+            }
+        
+            if UIDevice.isPad {
+                if let popover = vc.popoverPresentationController {
+                    popover.sourceView = self.view // 设置锚点视图
+                    popover.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) // 设置弹窗位置为屏幕中心
+                    popover.permittedArrowDirections = [] // 禁止箭头指向
+                }
+            }
+    
+            PlayerManager.shared.rootVc?.present(vc, animated: true)
+        }
+    }
+
+    /// finish.kailen
+    @objc func deleteVideoAction() {
+        dismiss(animated: true) {
+            let ac = UIAlertController(title: nil,
+                                       message: "Are you sure to delete".localized(), preferredStyle: .alert)
+            ac.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel))
+            ac.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: { _ in
+                if let mVideo = self.operteItem, let mVideoId = mVideo.videoId {
+                    TSVideoOperator.shared.dataManager.deleteVideo(video: mVideo) { success in
+                        if success {
+                            /// 如果删除视频为当前播放视频,那么要清理当前播放
+                            if mVideo.videoId == PlayerManager.shared.currentVideo?.videoId {
+                                TSVideoOperator.shared.playerViewModel.clearCurrenVideo()
+                            }
+                        }
+                        NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                    }
+                }
+                self.dismissCurrentVc()
+            }))
+
+            PlayerManager.shared.rootVc?.present(ac, animated: true)
+        }
+    }
+
+    static func renamePlaylist(playlist: TSPlayList, completion: ((String) -> Void)? = nil) {
+        let alertVC = UIAlertController(title: nil, message: "Playlist Name".localized(), preferredStyle: .alert)
+        alertVC.addTextField { textField in
+            textField.placeholder = "input name".localized()
+            textField.font = UIFont.systemFont(ofSize: 16)
+            textField.text = playlist.title ?? ""
+        }
+        let ok = UIAlertAction(title: "OK".localized(), style: .default) { _ in
+            if let name = alertVC.textFields?.first?.text, !name.isEmpty {
+                TSVideoOperator.shared.dataManager.editPlaylistName(playList: playlist, name: name) { _ in
+                    THUD.toast("Renamed Successfully".localized())
+                    NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                    completion?(name)
+                }
+            }
+        }
+        let cancel = UIAlertAction(title: "Cancel".localized(), style: .cancel)
+        alertVC.addAction(cancel)
+        alertVC.addAction(ok)
+        PlayerManager.shared.rootVc?.present(alertVC, animated: true)
+    }
+
+    static func deletePlaylist(playlist: TSPlayList, completion: (() -> Void)? = nil) {
+        let ac = UIAlertController(title: nil,
+                                   message: "Are you sure to delete".localized(), preferredStyle: .alert)
+        ac.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel))
+        ac.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: { _ in
+
+            TSVideoOperator.shared.dataManager.deletePlaylist(playlist: playlist) { _ in
+                NotificationCenter.default.post(name: .K_RefreshNotifaction, object: nil)
+                THUD.toast("Delete Successfully".localized())
+                completion?()
+            }
+
+        }))
+        PlayerManager.shared.rootVc?.present(ac, animated: true)
+    }
+}

+ 123 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWOperateViewController.swift

@@ -0,0 +1,123 @@
+//
+//  CWOperateViewController.swift
+//  ColorfulWallpaper
+//
+//  Created by ni on 2024/9/19.
+//
+
+import Foundation
+import TSVideoKit
+
+enum CWOperateType {
+    case like
+    case addPlaylist
+    case share
+    case deleteVideo
+    case rename
+    case deletePlaylist
+    case removeFromPlaylist
+}
+
+class CWOperateViewController: LWBaseViewController {
+    var operteItem: TSVideo?
+    var playlist: TSPlayList?
+
+    var disAppearArea: UIControl = .init()
+    lazy var songlistCell: OperateTopView = OperateTopView()
+    lazy var bgView: UIView = .simpleView(color: .white)
+    lazy var vStack: UIStackView = .vStack
+    lazy var likeItem: CWOperateItemView = .init(imgName: "ic_like_n", title: "Add to like", type: .like)
+    lazy var addPlaylistItem: CWOperateItemView = .init(imgName: "ic_add_playlist", title: "Add to playlist".localized(), type: .addPlaylist)
+    lazy var removeFromPlaylist: CWOperateItemView = .init(imgName: "ic_remove_playlist", title: "Remove from playlist".localized(), type: .removeFromPlaylist)
+    lazy var shareItem: CWOperateItemView = .init(imgName: "ic_share", title: "Share".localized(), type: .share)
+    lazy var deleteItem: CWOperateItemView = .init(imgName: "ic_delete", title: "Delete".localized(), type: .deleteVideo)
+
+    var types: [CWOperateType] = [.like, .addPlaylist, .share, .deleteVideo]
+
+    lazy var cancelButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Cancel".localized(), for: .normal)
+        btn.setTitleColor(.white, for: .normal)
+        btn.titleLabel?.font = .systemFont14
+        btn.addTarget(self, action: #selector(dismissCurrentVc), for: .touchUpInside)
+        return btn
+    }()
+
+    @objc func dismissCurrentVc() {
+        dismiss(animated: true)
+    }
+
+    init(types: [CWOperateType] = [.like, .addPlaylist, .share, .deleteVideo]) {
+        super.init(nibName: nil, bundle: nil)
+        self.types = types
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        if let model = operteItem {
+            songlistCell.bindData(video: model)
+        }
+        super.viewDidLoad()
+        addTargets()
+        updateLikeInfo(item: likeItem)
+    }
+
+    override func addChildren() {
+        super.addChildren()
+        view.backgroundColor = .black.withAlphaComponent(0.4)
+        view.addSubview(disAppearArea)
+        bgView.layer.cornerRadius = 16
+        bgView.layer.masksToBounds = true
+        view.addSubview(bgView)
+        bgView.addSubview(songlistCell)
+        bgView.addSubview(vStack)
+        vStack.addArrangedSubview(likeItem)
+        vStack.addArrangedSubview(addPlaylistItem)
+        vStack.addArrangedSubview(removeFromPlaylist)
+        vStack.addArrangedSubview(shareItem)
+        vStack.addArrangedSubview(deleteItem)
+        vStack.addArrangedSubview(cancelButton)
+        vStack.arrangedSubviews.forEach {
+            if let item = $0 as? CWOperateItemView, types.contains(item.type) {
+                $0.isHidden = false
+            } else {
+                $0.isHidden = true
+            }
+        }
+    }
+
+    override func makeConstarints() {
+        super.makeConstarints()
+        disAppearArea.snp.makeConstraints { make in
+            make.leading.trailing.top.equalToSuperview()
+            make.bottom.equalTo(bgView.snp.top)
+        }
+        bgView.snp.makeConstraints { make in
+            make.bottom.leading.trailing.equalToSuperview()
+        }
+
+        songlistCell.snp.makeConstraints { make in
+            make.height.equalTo(64)
+            make.top.leading.trailing.equalToSuperview()
+        }
+
+        vStack.snp.makeConstraints { make in
+            make.top.equalTo(songlistCell.snp.bottom)
+            make.leading.trailing.equalToSuperview()
+            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
+        }
+        cancelButton.snp.makeConstraints { make in
+            make.height.equalTo(44)
+        }
+    }
+
+    func updateLikeInfo(item: CWOperateItemView) {
+        let favIcon = operteItem?.isFavorite == true ? UIImage(
+            named: "ic_like_s") : UIImage(named: "ic_like")
+        item.iconView.image = favIcon
+        item.titleLabel.text = operteItem?.isFavorite == true ? "Remove from like".localized() : "Add to like".localized()
+    }
+}

+ 550 - 0
TSLiveWallpaper/Business/TSMusic/Helper/CWProgressView.swift

@@ -0,0 +1,550 @@
+import Foundation
+import UIKit
+
+@objc public enum CWCircularProgressGlowMode: Int {
+    case forward, reverse, constant, noGlow
+}
+
+@IBDesignable
+@objcMembers
+public class CWProgressView: UIView, CAAnimationDelegate {
+    private var progressLayer: KDCircularProgressViewLayer {
+        get {
+            return layer as! KDCircularProgressViewLayer
+        }
+    }
+    
+    private var radius: CGFloat = 0.0 {
+        didSet {
+            progressLayer.radius = radius
+        }
+    }
+    
+    public var progress: Double {
+        get { return angle.mod(between: 0.0, and: 360.0, byIncrementing: 360.0) / 360.0 }
+        set { angle = newValue.clamp(lowerBound: 0.0, upperBound: 1.0) * 360.0 }
+    }
+    
+    @IBInspectable public var angle: Double = 0.0 {
+        didSet {
+            pauseIfAnimating()
+            progressLayer.angle = angle
+        }
+    }
+    
+    @IBInspectable public var startAngle: Double = 0.0 {
+        didSet {
+            startAngle = startAngle.mod(between: 0.0, and: 360.0, byIncrementing: 360.0)
+            progressLayer.startAngle = startAngle
+            progressLayer.setNeedsDisplay()
+        }
+    }
+    
+    @IBInspectable public var clockwise: Bool = true {
+        didSet {
+            progressLayer.clockwise = clockwise
+            progressLayer.setNeedsDisplay()
+        }
+    }
+    
+    @IBInspectable public var roundedCorners: Bool = true {
+        didSet {
+            progressLayer.roundedCorners = roundedCorners
+        }
+    }
+    
+    @IBInspectable public var lerpColorMode: Bool = false {
+        didSet {
+            progressLayer.lerpColorMode = lerpColorMode
+        }
+    }
+    
+    @IBInspectable public var gradientRotateSpeed: CGFloat = 0.0 {
+        didSet {
+            progressLayer.gradientRotateSpeed = gradientRotateSpeed
+        }
+    }
+    
+    @IBInspectable public var glowAmount: CGFloat = 1.0 {
+        didSet {
+            glowAmount = glowAmount.clamp(lowerBound: 0.0, upperBound: 1.0)
+            progressLayer.glowAmount = glowAmount
+        }
+    }
+    
+    public var glowMode: CWCircularProgressGlowMode = .forward {
+        didSet {
+            progressLayer.glowMode = glowMode
+        }
+    }
+    
+    @IBInspectable public var progressThickness: CGFloat = 0.4 {
+        didSet {
+            progressThickness = progressThickness.clamp(lowerBound: 0.0, upperBound: 1.0)
+            progressLayer.progressThickness = progressThickness / 2.0
+        }
+    }
+    
+    @IBInspectable public var trackThickness: CGFloat = 0.5 {//Between 0 and 1
+        didSet {
+            trackThickness = trackThickness.clamp(lowerBound: 0.0, upperBound: 1.0)
+            progressLayer.trackThickness = trackThickness / 2.0
+        }
+    }
+    
+    @IBInspectable public var trackColor: UIColor = .black {
+        didSet {
+            progressLayer.trackColor = trackColor
+            progressLayer.setNeedsDisplay()
+        }
+    }
+    
+    @IBInspectable public var progressInsideFillColor: UIColor? = nil {
+        didSet {
+            progressLayer.progressInsideFillColor = progressInsideFillColor ?? .clear
+        }
+    }
+    
+    public var progressColors: [UIColor] {
+        get { return progressLayer.colorsArray }
+        set { set(colors: newValue) }
+    }
+    
+    //These are used only from the Interface-Builder. Changing these from code will have no effect.
+    //Also IB colors are limited to 3, whereas programatically we can have an arbitrary number of them.
+    @objc @IBInspectable private var IBColor1: UIColor?
+    @objc @IBInspectable private var IBColor2: UIColor?
+    @objc @IBInspectable private var IBColor3: UIColor?
+    
+    private var animationCompletionBlock: ((Bool) -> Void)?
+    
+    override public init(frame: CGRect) {
+        super.init(frame: frame)
+        setInitialValues()
+        refreshValues()
+    }
+    
+    convenience public init(frame:CGRect, colors: UIColor...) {
+        self.init(frame: frame)
+        set(colors: colors)
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        translatesAutoresizingMaskIntoConstraints = false
+        setInitialValues()
+        refreshValues()
+    }
+    
+    public override func awakeFromNib() {
+        checkAndSetIBColors()
+    }
+    
+    override public class var layerClass: AnyClass {
+        return KDCircularProgressViewLayer.self
+    }
+    
+    public override func layoutSubviews() {
+        super.layoutSubviews()
+        radius = (frame.size.width / 2.0) * 0.8
+    }
+    
+    private func setInitialValues() {
+        radius = (frame.size.width / 2.0) * 0.8 //We always apply a 20% padding, stopping glows from being clipped
+        backgroundColor = .clear
+        set(colors: .white, .cyan)
+    }
+    
+    private func refreshValues() {
+        progressLayer.angle = angle
+        progressLayer.startAngle = startAngle
+        progressLayer.clockwise = clockwise
+        progressLayer.roundedCorners = roundedCorners
+        progressLayer.lerpColorMode = lerpColorMode
+        progressLayer.gradientRotateSpeed = gradientRotateSpeed
+        progressLayer.glowAmount = glowAmount
+        progressLayer.glowMode = glowMode
+        progressLayer.progressThickness = progressThickness / 2.0
+        progressLayer.trackColor = trackColor
+        progressLayer.trackThickness = trackThickness / 2.0
+    }
+    
+    private func checkAndSetIBColors() {
+        let IBColors = [IBColor1, IBColor2, IBColor3].compactMap { $0 }
+        if IBColors.isEmpty == false {
+            set(colors: IBColors)
+        }
+    }
+    
+    public func set(colors: UIColor...) {
+        set(colors: colors)
+    }
+    
+    private func set(colors: [UIColor]) {
+        progressLayer.colorsArray = colors
+        progressLayer.setNeedsDisplay()
+    }
+    
+    public func animate(fromAngle: Double, toAngle: Double, duration: TimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
+        pauseIfAnimating()
+        let animationDuration: TimeInterval
+        if relativeDuration {
+            animationDuration = duration
+        } else {
+            let traveledAngle = (toAngle - fromAngle).mod(between: 0.0, and: 360.0, byIncrementing: 360.0)
+            let scaledDuration = TimeInterval(traveledAngle) * duration / 360.0
+            animationDuration = scaledDuration
+        }
+        
+        let animation = CABasicAnimation(keyPath: #keyPath(KDCircularProgressViewLayer.angle))
+        animation.fromValue = fromAngle
+        animation.toValue = toAngle
+        animation.duration = animationDuration
+        animation.delegate = self
+        animation.isRemovedOnCompletion = false
+        angle = toAngle
+        animationCompletionBlock = completion
+        
+        progressLayer.add(animation, forKey: "angle")
+    }
+    
+    public func animate(toAngle: Double, duration: TimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
+        pauseIfAnimating()
+        animate(fromAngle: angle, toAngle: toAngle, duration: duration, relativeDuration: relativeDuration, completion: completion)
+    }
+    
+    public func pauseAnimation() {
+        guard let presentationLayer = progressLayer.presentation() else { return }
+        
+        let currentValue = presentationLayer.angle
+        progressLayer.removeAllAnimations()
+        angle = currentValue
+    }
+    
+    private func pauseIfAnimating() {
+        if isAnimating() {
+            pauseAnimation()
+        }
+    }
+    
+    public func stopAnimation() {
+        progressLayer.removeAllAnimations()
+        angle = 0
+    }
+    
+    public func isAnimating() -> Bool {
+        return progressLayer.animation(forKey: "angle") != nil
+    }
+    
+    public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
+        animationCompletionBlock?(flag)
+        animationCompletionBlock = nil
+    }
+    
+    public override func didMoveToWindow() {
+        window.map { progressLayer.contentsScale = $0.screen.scale }
+    }
+    
+    public override func willMove(toSuperview newSuperview: UIView?) {
+        if newSuperview == nil {
+            pauseIfAnimating()
+        }
+    }
+    
+    public override func prepareForInterfaceBuilder() {
+        setInitialValues()
+        refreshValues()
+        checkAndSetIBColors()
+        progressLayer.setNeedsDisplay()
+    }
+    
+    private class KDCircularProgressViewLayer: CALayer {
+        @NSManaged var angle: Double
+        var radius: CGFloat = 0.0 {
+            didSet { invalidateGradientCache() }
+        }
+        var startAngle: Double = 0.0
+        var clockwise: Bool = true {
+            didSet {
+                if clockwise != oldValue {
+                    invalidateGradientCache()
+                }
+            }
+        }
+        var roundedCorners: Bool = true
+        var lerpColorMode: Bool = false
+        var gradientRotateSpeed: CGFloat = 0.0 {
+            didSet { invalidateGradientCache() }
+        }
+        var glowAmount: CGFloat = 0.0
+        var glowMode: CWCircularProgressGlowMode = .forward
+        var progressThickness: CGFloat = 0.5
+        var trackThickness: CGFloat = 0.5
+        var trackColor: UIColor = .black
+        var progressInsideFillColor: UIColor = .clear
+        var colorsArray: [UIColor] = [] {
+            didSet { invalidateGradientCache() }
+        }
+        private var gradientCache: CGGradient?
+        private var locationsCache: [CGFloat]?
+        
+        private enum GlowConstants {
+            private static let sizeToGlowRatio: CGFloat = 0.00015
+            static func glowAmount(forAngle angle: Double, glowAmount: CGFloat, glowMode: CWCircularProgressGlowMode, size: CGFloat) -> CGFloat {
+                switch glowMode {
+                case .forward:
+                    return CGFloat(angle) * size * sizeToGlowRatio * glowAmount
+                case .reverse:
+                    return CGFloat(360.0 - angle) * size * sizeToGlowRatio * glowAmount
+                case .constant:
+                    return 360.0 * size * sizeToGlowRatio * glowAmount
+                default:
+                    return 0
+                }
+            }
+        }
+        
+        override class func needsDisplay(forKey key: String) -> Bool {
+            if key == #keyPath(angle) {
+                return true
+            }
+            return super.needsDisplay(forKey: key)
+        }
+        
+        override init(layer: Any) {
+            super.init(layer: layer)
+            let progressLayer = layer as! KDCircularProgressViewLayer
+            radius = progressLayer.radius
+            angle = progressLayer.angle
+            startAngle = progressLayer.startAngle
+            clockwise = progressLayer.clockwise
+            roundedCorners = progressLayer.roundedCorners
+            lerpColorMode = progressLayer.lerpColorMode
+            gradientRotateSpeed = progressLayer.gradientRotateSpeed
+            glowAmount = progressLayer.glowAmount
+            glowMode = progressLayer.glowMode
+            progressThickness = progressLayer.progressThickness
+            trackThickness = progressLayer.trackThickness
+            trackColor = progressLayer.trackColor
+            colorsArray = progressLayer.colorsArray
+            progressInsideFillColor = progressLayer.progressInsideFillColor
+        }
+        
+        override init() {
+            super.init()
+        }
+        
+        required init?(coder aDecoder: NSCoder) {
+            super.init(coder: aDecoder)
+        }
+        
+        override func draw(in ctx: CGContext) {
+            UIGraphicsPushContext(ctx)
+            
+            let size = bounds.size
+            let width = size.width
+            let height = size.height
+            
+            let trackLineWidth = radius * trackThickness
+            let progressLineWidth = radius * progressThickness
+            let arcRadius = max(radius - trackLineWidth / 2.0, radius - progressLineWidth / 2.0)
+            ctx.addArc(center: CGPoint(x: width / 2.0, y: height / 2.0),
+                       radius: arcRadius,
+                       startAngle: 0,
+                       endAngle: CGFloat.pi * 2,
+                       clockwise: false)
+            ctx.setStrokeColor(trackColor.cgColor)
+            ctx.setFillColor(progressInsideFillColor.cgColor)
+            ctx.setLineWidth(trackLineWidth)
+            ctx.setLineCap(CGLineCap.butt)
+            ctx.drawPath(using: .fillStroke)
+            
+            UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
+            
+            let imageCtx = UIGraphicsGetCurrentContext()
+            let canonicalAngle = angle.mod(between: 0.0, and: 360.0, byIncrementing: 360.0)
+            let fromAngle = -startAngle.radians
+            let toAngle: Double
+            if clockwise {
+                toAngle = (-canonicalAngle - startAngle).radians
+            } else {
+                toAngle = (canonicalAngle - startAngle).radians
+            }
+            
+            imageCtx?.addArc(center: CGPoint(x: width / 2.0, y: height / 2.0),
+                             radius: arcRadius,
+                             startAngle: CGFloat(fromAngle),
+                             endAngle: CGFloat(toAngle),
+                             clockwise: clockwise)
+            
+            let glowValue = GlowConstants.glowAmount(forAngle: canonicalAngle, glowAmount: glowAmount, glowMode: glowMode, size: width)
+            if glowValue > 0 {
+                imageCtx?.setShadow(offset: .zero, blur: glowValue, color: UIColor.black.cgColor)
+            }
+            
+            let linecap: CGLineCap = roundedCorners ? .round : .butt
+            imageCtx?.setLineCap(linecap)
+            imageCtx?.setLineWidth(progressLineWidth)
+            imageCtx?.drawPath(using: .stroke)
+            
+            let drawMask: CGImage = UIGraphicsGetCurrentContext()!.makeImage()!
+            UIGraphicsEndImageContext()
+            
+            ctx.saveGState()
+            ctx.clip(to: bounds, mask: drawMask)
+            
+            if colorsArray.isEmpty {
+                fillRect(withContext: ctx, color: .white)
+            } else if colorsArray.count == 1 {
+                fillRect(withContext: ctx, color: colorsArray[0])
+            } else if lerpColorMode {
+                lerp(withContext: ctx, colorsArray: colorsArray)
+            } else {
+                drawGradient(withContext: ctx, colorsArray: colorsArray)
+            }
+
+            ctx.restoreGState()
+            UIGraphicsPopContext()
+        }
+        
+        private func lerp(withContext context: CGContext, colorsArray: [UIColor]) {
+            let canonicalAngle = angle.mod(between: 0.0, and: 360.0, byIncrementing: 360.0)
+            let percentage = canonicalAngle / 360.0
+            let steps = colorsArray.count - 1
+            let step = 1.0 / Double(steps)
+            
+            for i in 1...steps {
+                let di = Double(i)
+                if percentage <= di * step || i == steps {
+                    let colorT = percentage.inverseLerp(min: (di - 1) * step, max: di * step)
+                    let color = colorT.colorLerp(minColor: colorsArray[i - 1], maxColor: colorsArray[i])
+                    fillRect(withContext: context, color: color)
+                    break
+                }
+            }
+        }
+        
+        private func fillRect(withContext context: CGContext, color: UIColor) {
+            context.setFillColor(color.cgColor)
+            context.fill(bounds)
+        }
+        
+        private func drawGradient(withContext context: CGContext, colorsArray: [UIColor]) {
+            let baseSpace = CGColorSpaceCreateDeviceRGB()
+            let locations = locationsCache ?? gradientLocationsFor(colorCount: colorsArray.count, gradientWidth: bounds.size.width)
+            let gradient: CGGradient
+            
+            if let cachedGradient = gradientCache {
+                gradient = cachedGradient
+            } else {
+                guard let newGradient = CGGradient(colorSpace: baseSpace, colorComponents: colorsArray.rgbNormalized.componentsJoined,
+                                                   locations: locations, count: colorsArray.count) else { return }
+                
+                gradientCache = newGradient
+                gradient = newGradient
+            }
+            
+            let halfX = bounds.size.width / 2.0
+            let floatPi = CGFloat.pi
+            let rotateSpeed = clockwise == true ? gradientRotateSpeed : gradientRotateSpeed * -1.0
+            let angleInRadians = (rotateSpeed * CGFloat(angle) - 90.0).radians
+            let oppositeAngle = angleInRadians > floatPi ? angleInRadians - floatPi : angleInRadians + floatPi
+            
+            let startPoint = CGPoint(x: (cos(angleInRadians) * halfX) + halfX, y: (sin(angleInRadians) * halfX) + halfX)
+            let endPoint = CGPoint(x: (cos(oppositeAngle) * halfX) + halfX, y: (sin(oppositeAngle) * halfX) + halfX)
+            
+            context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation)
+        }
+        
+        private func gradientLocationsFor(colorCount: Int, gradientWidth: CGFloat) -> [CGFloat] {
+            guard colorCount > 0, gradientWidth > 0 else { return [] }
+
+            let progressLineWidth = radius * progressThickness
+            let firstPoint = gradientWidth / 2.0 - (radius - progressLineWidth / 2.0)
+            let increment = (gradientWidth - (2.0 * firstPoint)) / CGFloat(colorCount - 1)
+            
+            let locationsArray = (0..<colorCount).map { firstPoint + (CGFloat($0) * increment) }
+            let result = locationsArray.map { $0 / gradientWidth }
+            locationsCache = result
+            return result
+        }
+        
+        private func invalidateGradientCache() {
+            gradientCache = nil
+            locationsCache = nil
+        }
+    }
+}
+
+//Some helper extensions below
+
+private extension Array where Element == UIColor {
+    // Make sure every color in colors array is in RGB color space
+    var rgbNormalized: [UIColor] {
+        return map { color in
+            guard color.cgColor.numberOfComponents == 2 else {
+                return color
+            }
+            
+            let white: CGFloat = color.cgColor.components![0]
+            return UIColor(red: white, green: white, blue: white, alpha: 1.0)
+        }
+    }
+    
+    var componentsJoined: [CGFloat] {
+        return flatMap { $0.cgColor.components ?? [] }
+    }
+}
+
+private extension Comparable {
+    func clamp(lowerBound: Self, upperBound: Self) -> Self {
+        return min(max(self, lowerBound), upperBound)
+    }
+}
+
+private extension FloatingPoint {
+    var radians: Self {
+        return self * .pi / Self(180)
+    }
+    
+    func mod(between left: Self, and right: Self, byIncrementing interval: Self) -> Self {
+        assert(interval > 0)
+        assert(interval <= right - left)
+        assert(right > left)
+        
+        if self >= left, self <= right {
+            return self
+        } else if self < left {
+            return (self + interval).mod(between: left, and: right, byIncrementing: interval)
+        } else {
+            return (self - interval).mod(between: left, and: right, byIncrementing: interval)
+        }
+    }
+}
+
+private extension BinaryFloatingPoint {
+    func inverseLerp(min: Self, max: Self) -> Self {
+        return (self - min) / (max - min)
+    }
+    
+    func lerp(min: Self, max: Self) -> Self {
+        return (max - min) * self + min
+    }
+    
+    func colorLerp(minColor: UIColor, maxColor: UIColor) -> UIColor {
+        let clampedValue = CGFloat(self.clamp(lowerBound: 0.0, upperBound: 1.0))
+        let zero = CGFloat(0.0)
+        
+        
+        var (r0, g0, b0, a0) = (zero, zero, zero, zero)
+        minColor.getRed(&r0, green: &g0, blue: &b0, alpha: &a0)
+        
+        var (r1, g1, b1, a1) = (zero, zero, zero, zero)
+        maxColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
+        
+        return UIColor(red: clampedValue.lerp(min: r0, max: r1),
+                       green: clampedValue.lerp(min: g0, max: g1),
+                       blue: clampedValue.lerp(min: b0, max: b1),
+                       alpha: clampedValue.lerp(min: a0, max: a1))
+    }
+}

+ 181 - 0
TSLiveWallpaper/Business/TSMusic/Helper/DownloadButton.swift

@@ -0,0 +1,181 @@
+//
+//  DownloadButton.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/10.
+//
+
+import Foundation
+import Kingfisher
+
+enum DownloadButtonState {
+    case idle(isAnimate: Bool)
+    case loading
+    case downloading
+    case pause
+    case retry
+    case done
+
+    var imageName: String {
+        switch self {
+        case let .idle(isAnimate):
+            if isAnimate {
+                return "download"
+            } else {
+                return "ic_song_download"
+            }
+        case .loading:
+            return ""
+        case .downloading:
+            return ""
+        case .pause:
+            return "ic_download_pause"
+        case .retry:
+            return "ic_retry"
+        case .done:
+            return "ic_more"
+        }
+    }
+}
+
+class DownloadButton: UIControl {
+    
+    lazy var adTag: UIImageView = {
+        let tag = UIImageView(image: UIImage(named: "ic_tag"))
+        tag.isHidden = false
+        return tag
+    }()
+    
+    /// 下载状态
+    var downloadState: DownloadButtonState = .idle(isAnimate: false) {
+        didSet {
+            setStateForButton()
+        }
+    }
+
+    lazy var loadingView: UIImageView = {
+        let img = UIImageView(image: UIImage(named: "ic-rotate-loading"))
+        img.isHidden = true
+        return img
+    }()
+
+    /// 进度条
+    lazy var progressView: CWCustomProgressView = {
+        let progress = CWCustomProgressView()
+        progress.isHidden = true
+        progress.isUserInteractionEnabled = false
+        return progress
+    }()
+
+    lazy var stateImageView: AnimatedImageView = AnimatedImageView()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraints()
+    }
+
+    func addChildren() {
+        addSubview(progressView)
+        addSubview(stateImageView)
+        addSubview(loadingView)
+        addSubview(adTag)
+    }
+
+    func makeConstraints() {
+        
+        adTag.snp.makeConstraints { make in
+            make.centerX.equalToSuperview().offset(13)
+            make.centerY.equalToSuperview().offset(-10)
+            make.width.equalTo(14.4)
+            make.height.equalTo(12)
+        }
+        
+        progressView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.height.equalTo(40)
+        }
+
+        stateImageView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.height.equalTo(24)
+        }
+
+        loadingView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
+    }
+
+    private func startLoadingAnimation() {
+        // 1.创建动画
+        let animation = CABasicAnimation(keyPath: "transform.rotation.z")
+        // 2.设置动画属性
+        animation.fromValue = 0 // 开始角度
+        animation.toValue = Double.pi * 2 // 结束角度
+        animation.repeatCount = .greatestFiniteMagnitude // 重复次数
+        animation.duration = 1
+        // 动画完成后自动重新开始,默认为NO
+        animation.autoreverses = false
+        // 默认是true,切换到其他控制器再回来,动画效果会消失,需要设置成false,动画就不会停了
+        animation.isRemovedOnCompletion = false
+        loadingView.layer.add(animation, forKey: nil) // 给需要旋转的view增加动画
+    }
+
+    func setStateForButton() {
+        stateImageView.setLocalizedImage(UIImage.init(named: downloadState.imageName))
+        stateImageView.snp.updateConstraints { make in
+            make.width.height.equalTo(24)
+        }
+        switch downloadState {
+        case let .idle(animate):
+            if !animate {
+                stateImageView.setLocalizedImage(UIImage.init(named: downloadState.imageName))
+                stateImageView.snp.updateConstraints { make in
+                    make.width.height.equalTo(24)
+                }
+            }else{
+                let path = Bundle.main.path(forResource:downloadState.imageName, ofType:"gif")
+                let url = URL(fileURLWithPath: path!)
+                let resource = Kingfisher.KF.ImageResource(downloadURL: url)
+                stateImageView.kf.setImage(with: resource)
+                stateImageView.snp.updateConstraints { make in
+                    make.width.height.equalTo(24)
+                }
+//                stateImageView.setLocalizedImage(UIImage.init(named: downloadState.imageName))
+//                stateImageView.snp.updateConstraints { make in
+//                    make.width.height.equalTo(24)
+//                }
+            }
+            progressView.isHidden = true
+            loadingView.isHidden = true
+            adTag.isHidden = PurchaseManager.default.isVip
+        case .loading:
+            adTag.isHidden = true
+            progressView.isHidden = true
+            loadingView.isHidden = false
+            startLoadingAnimation()
+        case .downloading:
+            adTag.isHidden = true
+            progressView.isHidden = false
+            progressView.progressTitle.isHidden = false
+            loadingView.isHidden = true
+        case .pause:
+            adTag.isHidden = true
+            progressView.isHidden = true
+            progressView.progressTitle.isHidden = true
+            loadingView.isHidden = true
+        case .retry:
+            adTag.isHidden = true
+            progressView.isHidden = true
+            loadingView.isHidden = true
+        case .done:
+            adTag.isHidden = true
+            progressView.isHidden = true
+            loadingView.isHidden = true
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 174 - 0
TSLiveWallpaper/Business/TSMusic/Helper/FilterBarView.swift

@@ -0,0 +1,174 @@
+//
+//  FilterBarView.swift
+//  ColorfulWallpaper
+//
+//  Created by nkl on 2024/9/10.
+//
+
+import Foundation
+import UIKit
+class FilterBarView: UIView {
+    /// default layout items
+    lazy var playIcon: UIImageView = UIImageView(image: UIImage(named: "ic_playall"))
+
+    lazy var playButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Play All (0)", for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 14)
+        return btn
+    }()
+
+    lazy var mutiButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_mine_ring_muti"), for: .normal)
+        return btn
+    }()
+
+    lazy var sortButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "ic_mine_ring_sort_down"), for: .normal)
+        return btn
+    }()
+
+    lazy var selectButton: SpacedButton = {
+        let btn = SpacedButton()
+        btn.setImage(UIImage(named: "ic_mine_ring_mutil_unselect"), for: .normal)
+        btn.setImage(UIImage(named: "ic_mine_ring_mutil_select"), for: .selected)
+        btn.setTitle("Select All (0/0)", for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 14)
+        btn.isHidden = true
+        btn.spacing = 8
+        if FitManager.isAr {
+            btn.semanticContentAttribute = .forceLeftToRight
+        }
+        return btn
+    }()
+
+    lazy var searchButton: UIButton = {
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "local_search"), for: .normal)
+        return btn
+    }()
+
+    lazy var finishMutiButton: UIButton = {
+        let btn = UIButton()
+        btn.setTitle("Done".localized(), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 14)
+        btn.setTitleColor(.hexColor("#7E38EF"), for: .normal)
+        btn.isHidden = true
+        return btn
+    }()
+
+    lazy var hStack: UIStackView = .hStack
+
+    var type: MineRingTopType = .default {
+        didSet {
+            if type == .default {
+                defaultlayout()
+            } else {
+                mutiSelectlayout()
+            }
+        }
+    }
+
+    func defaultlayout() {
+        playIcon.isHidden = false
+        playButton.isHidden = false
+        mutiButton.isHidden = true
+        sortButton.isHidden = true
+        searchButton.isHidden = true
+
+        selectButton.isHidden = true
+        finishMutiButton.isHidden = true
+        playButton.isHidden = false
+        mutiButton.isHidden = false
+        sortButton.isHidden = false
+        searchButton.isHidden = true
+
+        selectButton.isHidden = true
+        finishMutiButton.isHidden = true
+    }
+
+    func mutiSelectlayout() {
+        playIcon.isHidden = false
+        playButton.isHidden = false
+        playButton.isHidden = true
+        mutiButton.isHidden = true
+        sortButton.isHidden = true
+        searchButton.isHidden = true
+
+        selectButton.isHidden = true
+        finishMutiButton.isHidden = true
+        playIcon.isHidden = true
+        playButton.isHidden = true
+        mutiButton.isHidden = true
+        sortButton.isHidden = true
+        searchButton.isHidden = true
+
+        selectButton.isHidden = false
+        finishMutiButton.isHidden = false
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addChildren()
+        makeConstraint()
+    }
+
+    func addChildren() {
+        playIcon.isUserInteractionEnabled = true
+        addSubview(playIcon)
+        addSubview(playButton)
+        addSubview(selectButton)
+
+        addSubview(hStack)
+        hStack.addArrangedSubview(sortButton)
+        hStack.addArrangedSubview(mutiButton)
+        hStack.addArrangedSubview(finishMutiButton)
+//        hStack.addArrangedSubview(searchButton)
+    }
+
+    func makeConstraint() {
+        playIcon.snp.makeConstraints { make in
+            make.leading.equalToSuperview()
+            make.centerY.equalToSuperview()
+        }
+
+        playButton.snp.makeConstraints { make in
+            make.leading.equalTo(playIcon.snp.trailing).offset(8)
+            make.centerY.equalTo(playIcon)
+            make.height.equalToSuperview()
+        }
+
+        hStack.snp.makeConstraints { make in
+            make.trailing.equalToSuperview()
+            make.centerY.equalToSuperview()
+        }
+
+        selectButton.snp.makeConstraints { make in
+            make.leading.equalToSuperview()
+            make.centerY.equalToSuperview()
+        }
+        selectButton.sizeToFit()
+
+        searchButton.snp.makeConstraints { make in
+            make.width.height.equalTo(34)
+        }
+
+        finishMutiButton.snp.makeConstraints { make in
+            make.height.equalTo(34)
+        }
+
+        mutiButton.snp.makeConstraints { make in
+            make.width.height.equalTo(34)
+        }
+
+        sortButton.snp.makeConstraints { make in
+            make.width.height.equalTo(34)
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

Some files were not shown because too many files changed in this diff