Browse Source

音频剪辑,开发到设置铃声逻辑修改地方了

100Years 3 weeks ago
parent
commit
6fa2221d25
51 changed files with 2183 additions and 1148 deletions
  1. 20 12
      AIRingtone.xcodeproj/project.pbxproj
  2. 22 0
      AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/Contents.json
  3. BIN
      AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/edit_ring_icon@2x.png
  4. BIN
      AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/edit_ring_icon@3x.png
  5. 22 0
      AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/Contents.json
  6. BIN
      AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/editaudio_pause@2x.png
  7. BIN
      AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/editaudio_pause@3x.png
  8. 22 0
      AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/Contents.json
  9. BIN
      AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/editaudio_play@2x.png
  10. BIN
      AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/editaudio_play@3x.png
  11. 22 0
      AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/Contents.json
  12. BIN
      AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/trackBg_gradient@2x.png
  13. BIN
      AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/trackBg_gradient@3x.png
  14. 22 0
      AIRingtone/Assets.xcassets/Common/edit_field.imageset/Contents.json
  15. BIN
      AIRingtone/Assets.xcassets/Common/edit_field.imageset/edit_field@2x.png
  16. BIN
      AIRingtone/Assets.xcassets/Common/edit_field.imageset/edit_field@3x.png
  17. 1 1
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPhotoBrowseVC.swift
  18. 3 3
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPicBrowseVC.swift
  19. 1 1
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPosterBrowseVC.swift
  20. 2 2
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC.swift
  21. 18 2
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/Model/TSRingModel.swift
  22. 18 19
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift
  23. 147 33
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift
  24. 18 1
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift
  25. 19 20
      AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift
  26. 19 19
      AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/TSDiscoverListVC.swift
  27. 22 23
      AIRingtone/Business/TSDiscoverVC/TSRingDownVC/TSRingDownVC.swift
  28. 2 2
      AIRingtone/Business/TSDiscoverVC/TSRingDownVC/VM/TSRingDownVM.swift
  29. 55 0
      AIRingtone/Business/TSEditAudioVideoVC/TSEditAudioSliderView.swift
  30. 758 371
      AIRingtone/Business/TSEditAudioVideoVC/TSEditAudioVideoBaseVC.swift
  31. 13 3
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift
  32. 19 3
      AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift
  33. 50 8
      AIRingtone/Business/VIewTool/TSButton.swift
  34. 13 0
      AIRingtone/Business/VIewTool/TSRingLoadingView.swift
  35. 39 3
      AIRingtone/Business/VIewTool/TSRingToneCellView.swift
  36. 18 11
      AIRingtone/Business/VIewTool/TSSavePhotoSuccessTool.swift
  37. 31 34
      AIRingtone/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  38. 65 0
      AIRingtone/Common/NetworkManager/TSNetWork/TSNetworkManager.swift
  39. 2 2
      AIRingtone/Common/Purchase/TSPurchaseManager/TSPurchaseTool.swift
  40. 56 56
      AIRingtone/Common/Tool/NotUse/TSAudioPlayerFileTool.swift
  41. 0 214
      AIRingtone/Common/Tool/NotUse/TSDownloadManager.swift
  42. 214 0
      AIRingtone/Common/Tool/NotUse/TSDownloadTool.swift
  43. 2 2
      AIRingtone/Common/Tool/TSAudioPlayer/TSAudioPlayer.swift
  44. 110 13
      AIRingtone/Common/Tool/TSAudioPlayer/TSBusinessAudioPlayer.swift
  45. 61 104
      AIRingtone/Common/Tool/TSBandRingTool/AudioTool.swift
  46. 42 23
      AIRingtone/Common/Tool/TSBandRingTool/TSBandRingTool.swift
  47. 7 0
      AIRingtone/Common/Tool/TSBusinessCommon.swift
  48. 151 0
      AIRingtone/Common/Tool/TSBusinessFileManager.swift
  49. 77 0
      AIRingtone/Common/Tool/TSDownloadManager.swift
  50. 0 157
      AIRingtone/Common/Tool/TSFileManagerTool.swift
  51. 0 6
      AIRingtone/Common/Tool/TSPublicContent.swift

+ 20 - 12
AIRingtone.xcodeproj/project.pbxproj

@@ -17,7 +17,6 @@
 		A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6D2D718CEA003CD332 /* TSNetWork+Business.swift */; };
 		A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6D2D718CEA003CD332 /* TSNetWork+Business.swift */; };
 		A80EDEDB2D718CEA003CD332 /* TSPhotoPickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEBB2D718CEA003CD332 /* TSPhotoPickerManager.swift */; };
 		A80EDEDB2D718CEA003CD332 /* TSPhotoPickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEBB2D718CEA003CD332 /* TSPhotoPickerManager.swift */; };
 		A80EDEE32D718CEA003CD332 /* TSNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6C2D718CEA003CD332 /* TSNetworkManager.swift */; };
 		A80EDEE32D718CEA003CD332 /* TSNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6C2D718CEA003CD332 /* TSNetworkManager.swift */; };
-		A80EDEE92D718CEA003CD332 /* TSFileManagerTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEB62D718CEA003CD332 /* TSFileManagerTool.swift */; };
 		A80EDEEA2D718CEA003CD332 /* PaddedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEBF2D718CEA003CD332 /* PaddedLabel.swift */; };
 		A80EDEEA2D718CEA003CD332 /* PaddedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEBF2D718CEA003CD332 /* PaddedLabel.swift */; };
 		A80EDEEB2D718CEA003CD332 /* StreamPostRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6F2D718CEA003CD332 /* StreamPostRequest.swift */; };
 		A80EDEEB2D718CEA003CD332 /* StreamPostRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDE6F2D718CEA003CD332 /* StreamPostRequest.swift */; };
 		A80EDEF52D718DEA003CD332 /* TSTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEF32D718DEA003CD332 /* TSTabBarController.swift */; };
 		A80EDEF52D718DEA003CD332 /* TSTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80EDEF32D718DEA003CD332 /* TSTabBarController.swift */; };
@@ -126,13 +125,16 @@
 		A899D39C2D894F9800AB9C1C /* TSRingDownVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */; };
 		A899D39C2D894F9800AB9C1C /* TSRingDownVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */; };
 		A899D3A32D896A7B00AB9C1C /* TSRingDownNullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */; };
 		A899D3A32D896A7B00AB9C1C /* TSRingDownNullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */; };
 		A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */; };
 		A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */; };
-		A899D3CB2D89A53D00AB9C1C /* TSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */; };
+		A899D3CB2D89A53D00AB9C1C /* TSDownloadTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CA2D89A53C00AB9C1C /* TSDownloadTool.swift */; };
 		A899D3CF2D89AD4C00AB9C1C /* TSAudioPlayerFileTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */; };
 		A899D3CF2D89AD4C00AB9C1C /* TSAudioPlayerFileTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */; };
 		A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */; };
 		A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */; };
 		A899D3D42D8A6B6600AB9C1C /* TSGenerateHistoryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */; };
 		A899D3D42D8A6B6600AB9C1C /* TSGenerateHistoryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */; };
 		A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */; };
 		A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */; };
 		A899D3D82D8A76CE00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */; };
 		A899D3D82D8A76CE00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */; };
 		A899D3DB2D8A97F100AB9C1C /* TSRingDownVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */; };
 		A899D3DB2D8A97F100AB9C1C /* TSRingDownVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */; };
+		A8A93A912D95206C00ABF9C6 /* TSBusinessCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A93A902D95204900ABF9C6 /* TSBusinessCommon.swift */; };
+		A8A93A932D954C1000ABF9C6 /* TSEditAudioSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A93A922D954C0800ABF9C6 /* TSEditAudioSliderView.swift */; };
+		A8A93A952D95616600ABF9C6 /* TSBusinessFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A93A942D95616500ABF9C6 /* TSBusinessFileManager.swift */; };
 		A8C6436C2D79A8C8001068D0 /* TSAIRintoneHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */; };
 		A8C6436C2D79A8C8001068D0 /* TSAIRintoneHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */; };
 		A8CC55822D797720002E0CAA /* TSGeneralPicBrowseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */; };
 		A8CC55822D797720002E0CAA /* TSGeneralPicBrowseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */; };
 		A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */; };
 		A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */; };
@@ -141,7 +143,7 @@
 		A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC558F2D799F9C002E0CAA /* TSAIRintoneVM.swift */; };
 		A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC558F2D799F9C002E0CAA /* TSAIRintoneVM.swift */; };
 		A8D776F82D8D3448007EAB35 /* TSBaseOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776F72D8D343F007EAB35 /* TSBaseOperationQueue.swift */; };
 		A8D776F82D8D3448007EAB35 /* TSBaseOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776F72D8D343F007EAB35 /* TSBaseOperationQueue.swift */; };
 		A8D776FA2D8D345C007EAB35 /* TSBaseOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776F92D8D345B007EAB35 /* TSBaseOperation.swift */; };
 		A8D776FA2D8D345C007EAB35 /* TSBaseOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776F92D8D345B007EAB35 /* TSBaseOperation.swift */; };
-		A8D776FE2D8D3E5E007EAB35 /* TSPublicContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776FD2D8D3E4E007EAB35 /* TSPublicContent.swift */; };
+		A8D776FE2D8D3E5E007EAB35 /* TSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D776FD2D8D3E4E007EAB35 /* TSDownloadManager.swift */; };
 		A8DEC2702D7C395C002EB948 /* TSThemeCopyrightVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8DEC26F2D7C395B002EB948 /* TSThemeCopyrightVC.swift */; };
 		A8DEC2702D7C395C002EB948 /* TSThemeCopyrightVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8DEC26F2D7C395B002EB948 /* TSThemeCopyrightVC.swift */; };
 		F5FF0EC10B0056B65FDB9C78 /* Pods_AIRingtone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C823AB7F3D49E8B2356E07 /* Pods_AIRingtone.framework */; };
 		F5FF0EC10B0056B65FDB9C78 /* Pods_AIRingtone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C823AB7F3D49E8B2356E07 /* Pods_AIRingtone.framework */; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
@@ -162,7 +164,6 @@
 		A80EDE6E2D718CEA003CD332 /* TSNetworkManager+Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSNetworkManager+Loading.swift"; sourceTree = "<group>"; };
 		A80EDE6E2D718CEA003CD332 /* TSNetworkManager+Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSNetworkManager+Loading.swift"; sourceTree = "<group>"; };
 		A80EDE6F2D718CEA003CD332 /* StreamPostRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPostRequest.swift; sourceTree = "<group>"; };
 		A80EDE6F2D718CEA003CD332 /* StreamPostRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPostRequest.swift; sourceTree = "<group>"; };
 		A80EDEB32D718CEA003CD332 /* TSRandomTextPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRandomTextPicker.swift; sourceTree = "<group>"; };
 		A80EDEB32D718CEA003CD332 /* TSRandomTextPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRandomTextPicker.swift; sourceTree = "<group>"; };
-		A80EDEB62D718CEA003CD332 /* TSFileManagerTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSFileManagerTool.swift; sourceTree = "<group>"; };
 		A80EDEBB2D718CEA003CD332 /* TSPhotoPickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPhotoPickerManager.swift; sourceTree = "<group>"; };
 		A80EDEBB2D718CEA003CD332 /* TSPhotoPickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPhotoPickerManager.swift; sourceTree = "<group>"; };
 		A80EDEBF2D718CEA003CD332 /* PaddedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddedLabel.swift; sourceTree = "<group>"; };
 		A80EDEBF2D718CEA003CD332 /* PaddedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddedLabel.swift; sourceTree = "<group>"; };
 		A80EDEF32D718DEA003CD332 /* TSTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTabBarController.swift; sourceTree = "<group>"; };
 		A80EDEF32D718DEA003CD332 /* TSTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTabBarController.swift; sourceTree = "<group>"; };
@@ -273,13 +274,16 @@
 		A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVC.swift; sourceTree = "<group>"; };
 		A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVC.swift; sourceTree = "<group>"; };
 		A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownNullView.swift; sourceTree = "<group>"; };
 		A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownNullView.swift; sourceTree = "<group>"; };
 		A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioAVPlayer.swift; sourceTree = "<group>"; };
 		A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioAVPlayer.swift; sourceTree = "<group>"; };
-		A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadManager.swift; sourceTree = "<group>"; };
+		A899D3CA2D89A53C00AB9C1C /* TSDownloadTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadTool.swift; sourceTree = "<group>"; };
 		A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioPlayerFileTool.swift; sourceTree = "<group>"; };
 		A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioPlayerFileTool.swift; sourceTree = "<group>"; };
 		A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDiscoverListCellVM.swift; sourceTree = "<group>"; };
 		A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDiscoverListCellVM.swift; sourceTree = "<group>"; };
 		A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVC.swift; sourceTree = "<group>"; };
 		A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVC.swift; sourceTree = "<group>"; };
 		A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVM.swift; sourceTree = "<group>"; };
 		A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVM.swift; sourceTree = "<group>"; };
 		A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistorySectionHeaderView.swift; sourceTree = "<group>"; };
 		A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistorySectionHeaderView.swift; sourceTree = "<group>"; };
 		A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVM.swift; sourceTree = "<group>"; };
 		A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVM.swift; sourceTree = "<group>"; };
+		A8A93A902D95204900ABF9C6 /* TSBusinessCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessCommon.swift; sourceTree = "<group>"; };
+		A8A93A922D954C0800ABF9C6 /* TSEditAudioSliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSEditAudioSliderView.swift; sourceTree = "<group>"; };
+		A8A93A942D95616500ABF9C6 /* TSBusinessFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessFileManager.swift; sourceTree = "<group>"; };
 		A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistoryCell.swift; sourceTree = "<group>"; };
 		A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistoryCell.swift; sourceTree = "<group>"; };
 		A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralPicBrowseVC.swift; sourceTree = "<group>"; };
 		A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralPicBrowseVC.swift; sourceTree = "<group>"; };
 		A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTextGeneralRintoneVC.swift; sourceTree = "<group>"; };
 		A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTextGeneralRintoneVC.swift; sourceTree = "<group>"; };
@@ -288,7 +292,7 @@
 		A8CC558F2D799F9C002E0CAA /* TSAIRintoneVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneVM.swift; sourceTree = "<group>"; };
 		A8CC558F2D799F9C002E0CAA /* TSAIRintoneVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneVM.swift; sourceTree = "<group>"; };
 		A8D776F72D8D343F007EAB35 /* TSBaseOperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperationQueue.swift; sourceTree = "<group>"; };
 		A8D776F72D8D343F007EAB35 /* TSBaseOperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperationQueue.swift; sourceTree = "<group>"; };
 		A8D776F92D8D345B007EAB35 /* TSBaseOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperation.swift; sourceTree = "<group>"; };
 		A8D776F92D8D345B007EAB35 /* TSBaseOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperation.swift; sourceTree = "<group>"; };
-		A8D776FD2D8D3E4E007EAB35 /* TSPublicContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPublicContent.swift; sourceTree = "<group>"; };
+		A8D776FD2D8D3E4E007EAB35 /* TSDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadManager.swift; sourceTree = "<group>"; };
 		A8DEC26F2D7C395B002EB948 /* TSThemeCopyrightVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSThemeCopyrightVC.swift; sourceTree = "<group>"; };
 		A8DEC26F2D7C395B002EB948 /* TSThemeCopyrightVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSThemeCopyrightVC.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 /* End PBXFileReference section */
 
 
@@ -419,14 +423,15 @@
 		A80EDEBA2D718CEA003CD332 /* Tool */ = {
 		A80EDEBA2D718CEA003CD332 /* Tool */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				A8D776FD2D8D3E4E007EAB35 /* TSPublicContent.swift */,
+				A8A93A942D95616500ABF9C6 /* TSBusinessFileManager.swift */,
+				A8A93A902D95204900ABF9C6 /* TSBusinessCommon.swift */,
+				A8D776FD2D8D3E4E007EAB35 /* TSDownloadManager.swift */,
 				A8D776F62D8D342D007EAB35 /* OperationQueue */,
 				A8D776F62D8D342D007EAB35 /* OperationQueue */,
 				A840A7EE2D8D82100044B8B9 /* NotUse */,
 				A840A7EE2D8D82100044B8B9 /* NotUse */,
 				A868A8DC2D76F90E00F6D884 /* TSBandRingTool */,
 				A868A8DC2D76F90E00F6D884 /* TSBandRingTool */,
 				A868A8BC2D75C68300F6D884 /* TSAudioPlayer */,
 				A868A8BC2D75C68300F6D884 /* TSAudioPlayer */,
 				A80EDEB32D718CEA003CD332 /* TSRandomTextPicker.swift */,
 				A80EDEB32D718CEA003CD332 /* TSRandomTextPicker.swift */,
 				A868A8F02D77081B00F6D884 /* TSContactsTool.swift */,
 				A868A8F02D77081B00F6D884 /* TSContactsTool.swift */,
-				A80EDEB62D718CEA003CD332 /* TSFileManagerTool.swift */,
 				A868A8EE2D77040F00F6D884 /* TSLoadingAnimation.swift */,
 				A868A8EE2D77040F00F6D884 /* TSLoadingAnimation.swift */,
 			);
 			);
 			path = Tool;
 			path = Tool;
@@ -651,7 +656,7 @@
 		A840A7EE2D8D82100044B8B9 /* NotUse */ = {
 		A840A7EE2D8D82100044B8B9 /* NotUse */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */,
+				A899D3CA2D89A53C00AB9C1C /* TSDownloadTool.swift */,
 				A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */,
 				A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */,
 				A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */,
 				A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */,
 			);
 			);
@@ -672,6 +677,7 @@
 		A840A8062D9404060044B8B9 /* TSEditAudioVideoVC */ = {
 		A840A8062D9404060044B8B9 /* TSEditAudioVideoVC */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				A8A93A922D954C0800ABF9C6 /* TSEditAudioSliderView.swift */,
 				A840A8072D9404480044B8B9 /* TSEditAudioVideoBaseVC.swift */,
 				A840A8072D9404480044B8B9 /* TSEditAudioVideoBaseVC.swift */,
 			);
 			);
 			path = TSEditAudioVideoVC;
 			path = TSEditAudioVideoVC;
@@ -1237,7 +1243,7 @@
 				A80EDE652D718CAF003CD332 /* GlobalImports.swift in Sources */,
 				A80EDE652D718CAF003CD332 /* GlobalImports.swift in Sources */,
 				A840A7ED2D8D81DB0044B8B9 /* TSGenerateRintoneOperation.swift in Sources */,
 				A840A7ED2D8D81DB0044B8B9 /* TSGenerateRintoneOperation.swift in Sources */,
 				A868A9092D7828EC00F6D884 /* TSTGPPhotoStyleView.swift in Sources */,
 				A868A9092D7828EC00F6D884 /* TSTGPPhotoStyleView.swift in Sources */,
-				A8D776FE2D8D3E5E007EAB35 /* TSPublicContent.swift in Sources */,
+				A8D776FE2D8D3E5E007EAB35 /* TSDownloadManager.swift in Sources */,
 				A8DEC2702D7C395C002EB948 /* TSThemeCopyrightVC.swift in Sources */,
 				A8DEC2702D7C395C002EB948 /* TSThemeCopyrightVC.swift in Sources */,
 				A8D776F82D8D3448007EAB35 /* TSBaseOperationQueue.swift in Sources */,
 				A8D776F82D8D3448007EAB35 /* TSBaseOperationQueue.swift in Sources */,
 				A80EDF112D718ED1003CD332 /* TSAIRintoneVC.swift in Sources */,
 				A80EDF112D718ED1003CD332 /* TSAIRintoneVC.swift in Sources */,
@@ -1249,6 +1255,7 @@
 				A80EDEC62D718CEA003CD332 /* TSRandomTextPicker.swift in Sources */,
 				A80EDEC62D718CEA003CD332 /* TSRandomTextPicker.swift in Sources */,
 				A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */,
 				A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */,
 				A80EDEC82D718CEA003CD332 /* TSNetworkManager+Loading.swift in Sources */,
 				A80EDEC82D718CEA003CD332 /* TSNetworkManager+Loading.swift in Sources */,
+				A8A93A952D95616600ABF9C6 /* TSBusinessFileManager.swift in Sources */,
 				A899D34D2D82C61C00AB9C1C /* TSPurchaseVC.swift in Sources */,
 				A899D34D2D82C61C00AB9C1C /* TSPurchaseVC.swift in Sources */,
 				A868A9182D78555200F6D884 /* TSGenneralPicVM.swift in Sources */,
 				A868A9182D78555200F6D884 /* TSGenneralPicVM.swift in Sources */,
 				A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */,
 				A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */,
@@ -1285,7 +1292,6 @@
 				A868A8BA2D75C22300F6D884 /* TSThemeBrowseVM.swift in Sources */,
 				A868A8BA2D75C22300F6D884 /* TSThemeBrowseVM.swift in Sources */,
 				A868A89C2D75506C00F6D884 /* TSThemeContentCell.swift in Sources */,
 				A868A89C2D75506C00F6D884 /* TSThemeContentCell.swift in Sources */,
 				A868A91E2D785CEA00F6D884 /* TSGeneralPicVC+Event.swift in Sources */,
 				A868A91E2D785CEA00F6D884 /* TSGeneralPicVC+Event.swift in Sources */,
-				A80EDEE92D718CEA003CD332 /* TSFileManagerTool.swift in Sources */,
 				A868A8A22D7560B900F6D884 /* TSPageNullView.swift in Sources */,
 				A868A8A22D7560B900F6D884 /* TSPageNullView.swift in Sources */,
 				A868A8CE2D76AAC600F6D884 /* TSThemeSetRingToneView.swift in Sources */,
 				A868A8CE2D76AAC600F6D884 /* TSThemeSetRingToneView.swift in Sources */,
 				A868A90E2D7846D600F6D884 /* TSSavePhotoSuccessTool.swift in Sources */,
 				A868A90E2D7846D600F6D884 /* TSSavePhotoSuccessTool.swift in Sources */,
@@ -1294,6 +1300,7 @@
 				A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */,
 				A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */,
 				A868A9112D784CFB00F6D884 /* TSGeneralPicVC.swift in Sources */,
 				A868A9112D784CFB00F6D884 /* TSGeneralPicVC.swift in Sources */,
 				A8272EBB2D7AFD0F00F1C814 /* TSBusinessAudioPlayer.swift in Sources */,
 				A8272EBB2D7AFD0F00F1C814 /* TSBusinessAudioPlayer.swift in Sources */,
+				A8A93A912D95206C00ABF9C6 /* TSBusinessCommon.swift in Sources */,
 				A868A8A42D7560B900F6D884 /* TSCommonloadingView.swift in Sources */,
 				A868A8A42D7560B900F6D884 /* TSCommonloadingView.swift in Sources */,
 				A868A89A2D75505E00F6D884 /* TSThemeBannerCell.swift in Sources */,
 				A868A89A2D75505E00F6D884 /* TSThemeBannerCell.swift in Sources */,
 				A840A80F2D94057B0044B8B9 /* ZHAudioProcessing.swift in Sources */,
 				A840A80F2D94057B0044B8B9 /* ZHAudioProcessing.swift in Sources */,
@@ -1305,6 +1312,7 @@
 				A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */,
 				A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */,
 				A80EDEEA2D718CEA003CD332 /* PaddedLabel.swift in Sources */,
 				A80EDEEA2D718CEA003CD332 /* PaddedLabel.swift in Sources */,
 				A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */,
 				A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */,
+				A8A93A932D954C1000ABF9C6 /* TSEditAudioSliderView.swift in Sources */,
 				A80EDEEB2D718CEA003CD332 /* StreamPostRequest.swift in Sources */,
 				A80EDEEB2D718CEA003CD332 /* StreamPostRequest.swift in Sources */,
 				A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */,
 				A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */,
 				A80EDF2A2D71C215003CD332 /* TSThemeVM.swift in Sources */,
 				A80EDF2A2D71C215003CD332 /* TSThemeVM.swift in Sources */,
@@ -1323,7 +1331,7 @@
 				A868A8F82D77E2BC00F6D884 /* TSTextGeneralPicVC.swift in Sources */,
 				A868A8F82D77E2BC00F6D884 /* TSTextGeneralPicVC.swift in Sources */,
 				A840A7FE2D916DB50044B8B9 /* TSGeneratePhotoOperation.swift in Sources */,
 				A840A7FE2D916DB50044B8B9 /* TSGeneratePhotoOperation.swift in Sources */,
 				A80EDF022D718DF1003CD332 /* TSSettingListView.swift in Sources */,
 				A80EDF022D718DF1003CD332 /* TSSettingListView.swift in Sources */,
-				A899D3CB2D89A53D00AB9C1C /* TSDownloadManager.swift in Sources */,
+				A899D3CB2D89A53D00AB9C1C /* TSDownloadTool.swift in Sources */,
 				A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */,
 				A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */,
 				A80EDF032D718DF1003CD332 /* ShareActivityItemProvider.swift in Sources */,
 				A80EDF032D718DF1003CD332 /* ShareActivityItemProvider.swift in Sources */,
 				A80EDF042D718DF1003CD332 /* TSSetingModel.swift in Sources */,
 				A80EDF042D718DF1003CD332 /* TSSetingModel.swift in Sources */,

+ 22 - 0
AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/edit_ring_icon@2x.png


BIN
AIRingtone/Assets.xcassets/AIRing/edit_ring_icon.imageset/edit_ring_icon@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/editaudio_pause@2x.png


BIN
AIRingtone/Assets.xcassets/AIRing/editaudio_pause.imageset/editaudio_pause@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/editaudio_play@2x.png


BIN
AIRingtone/Assets.xcassets/AIRing/editaudio_play.imageset/editaudio_play@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/trackBg_gradient@2x.png


BIN
AIRingtone/Assets.xcassets/AIRing/trackBg_gradient.imageset/trackBg_gradient@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/Common/edit_field.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Common/edit_field.imageset/edit_field@2x.png


BIN
AIRingtone/Assets.xcassets/Common/edit_field.imageset/edit_field@3x.png


+ 1 - 1
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPhotoBrowseVC.swift

@@ -108,7 +108,7 @@ extension TSGeneralPhotoBrowseVC {
             guard let self = self else { return }
             guard let self = self else { return }
             contactsTool.setContactsAvatar(avatarImage: image) { data, error in
             contactsTool.setContactsAvatar(avatarImage: image) { data, error in
                 if error == nil {
                 if error == nil {
-                    kSavePhotoSuccesswShared.show(atView: self.view,showViewBtn: false)
+                    kSaveSuccesswShared.show(atView: self.view,showViewBtn: false)
                 }
                 }
             }
             }
         }
         }

+ 3 - 3
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPicBrowseVC.swift

@@ -139,7 +139,7 @@ class TSGeneralPicBrowseVC: TSBottomAlertVC {
         if let model = currentModel{
         if let model = currentModel{
             //拷贝文字到截切板
             //拷贝文字到截切板
             UIPasteboard.general.string = model.request.prompt
             UIPasteboard.general.string = model.request.prompt
-            kSavePhotoSuccesswShared.show(atView: self.view,text: "Copy Successfully".localized,showViewBtn:false)
+            kSaveSuccesswShared.show(atView: self.view,text: "Copy Successfully".localized,showViewBtn:false)
         }else{
         }else{
             kShowToastDataMissing()
             kShowToastDataMissing()
         }
         }
@@ -155,7 +155,7 @@ class TSGeneralPicBrowseVC: TSBottomAlertVC {
             if style == .poster {
             if style == .poster {
                 PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 PhotoManagerShared.saveImageToAlbum(image) { success, error in
                     if success {
                     if success {
-                        kSavePhotoSuccesswShared.show(atView:self.view)
+                        kSaveSuccesswShared.show(atView:self.view)
                     }else{
                     }else{
                         debugPrint(error)
                         debugPrint(error)
                     }
                     }
@@ -163,7 +163,7 @@ class TSGeneralPicBrowseVC: TSBottomAlertVC {
             }else if style == .photo {
             }else if style == .photo {
                 contactsTool.setContactsAvatar(avatarImage: image) { data, error in
                 contactsTool.setContactsAvatar(avatarImage: image) { data, error in
                     if error == nil {
                     if error == nil {
-                        kSavePhotoSuccesswShared.show(atView: self.view,showViewBtn: false)
+                        kSaveSuccesswShared.show(atView: self.view,showViewBtn: false)
                     }
                     }
                 }
                 }
             }
             }

+ 1 - 1
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicBrowseVC/TSGeneralPosterBrowseVC.swift

@@ -121,7 +121,7 @@ extension TSGeneralPosterBrowseVC {
                 if let image = image{
                 if let image = image{
                     PhotoManagerShared.saveImageToAlbum(image) { success, error in
                     PhotoManagerShared.saveImageToAlbum(image) { success, error in
                         if success {
                         if success {
-                            kSavePhotoSuccesswShared.show(atView:self.view)
+                            kSaveSuccesswShared.show(atView:self.view)
                         }else{
                         }else{
                             debugPrint(error)
                             debugPrint(error)
                         }
                         }

+ 2 - 2
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC.swift

@@ -104,7 +104,7 @@ class TSGeneralPicVC: TSBottomAlertVC {
             if gennerateType == .poster {
             if gennerateType == .poster {
                 PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 PhotoManagerShared.saveImageToAlbum(image) { success, error in
                     if success {
                     if success {
-                        kSavePhotoSuccesswShared.show(atView:self.view)
+                        kSaveSuccesswShared.show(atView:self.view)
                     }else{
                     }else{
                         debugPrint(error)
                         debugPrint(error)
                     }
                     }
@@ -112,7 +112,7 @@ class TSGeneralPicVC: TSBottomAlertVC {
             }else if gennerateType == .photo {
             }else if gennerateType == .photo {
                 contactsTool.setContactsAvatar(avatarImage: image) { data, error in
                 contactsTool.setContactsAvatar(avatarImage: image) { data, error in
                     if error == nil {
                     if error == nil {
-                        kSavePhotoSuccesswShared.show(atView: self.view,showViewBtn: false)
+                        kSaveSuccesswShared.show(atView: self.view,showViewBtn: false)
                     }
                     }
                 }
                 }
             }
             }

+ 18 - 2
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/Model/TSRingModel.swift

@@ -15,6 +15,9 @@ class TSRingModel:TSBaseModel {
     var duration:Int = 0
     var duration:Int = 0
     var categoryId:String = ""
     var categoryId:String = ""
     
     
+    
+    var documentPath: String = ""
+    
     override func mapping(map: Map) {
     override func mapping(map: Map) {
         vip <- map["vip"]
         vip <- map["vip"]
         size <- map["size"]
         size <- map["size"]
@@ -22,11 +25,24 @@ class TSRingModel:TSBaseModel {
         audioUrl <- map["audioUrl"]
         audioUrl <- map["audioUrl"]
         duration <- map["duration"]
         duration <- map["duration"]
         categoryId <- map["categoryId"]
         categoryId <- map["categoryId"]
+        
+        documentPath <- map["documentPath"]
     }
     }
     
     
+}
+
+
+extension TSRingModel {
+    
+    static func getTSRingModel(TSActionInfoModel model:TSActionInfoModel)->TSRingModel{
+        let ringModle = TSRingModel()
+        ringModle.title = model.response.title
+        ringModle.audioUrl = model.response.musicUrl
+        ringModle.duration = 30
+        ringModle.size = 30
+        return ringModle
+    }
     
     
-    var downloadOp:DownloadOperation?
-    var audioPlayerFileTool:TSAudioPlayerFileTool?
 }
 }
 
 
 class TSRingCategoryModel:TSBaseModel {
 class TSRingCategoryModel:TSBaseModel {

+ 18 - 19
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift

@@ -168,9 +168,8 @@ extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate,UI
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
            let cell = cell as? TSAIRintoneHistoryCell
            let cell = cell as? TSAIRintoneHistoryCell
         {
         {
-            cell.delegate = self
-            cell.setRingBtn.indexPath = indexPath
-            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+            cell.setTargetVC(targetVC: self, indexPath: indexPath)
+//            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
             if let model = itemModel as? TSActionInfoModel {
             if let model = itemModel as? TSActionInfoModel {
                 cell.model = model
                 cell.model = model
             }else if let ringModel = itemModel as? TSRingModel {
             }else if let ringModel = itemModel as? TSRingModel {
@@ -181,22 +180,22 @@ extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate,UI
         return cell
         return cell
     }
     }
 
 
-    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
-        let indexPath = btn.indexPath
-        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
-        let model = sectionModel.list.safeObj(At: indexPath.item){
-            if let model = model as? TSActionInfoModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
-            }else if let ringModel = model as? TSRingModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){ success in
-                    if success {
-                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
-                    }
-                }
-            }
-        }
-        TSBusinessAudioPlayer.shared.stop()
-    }
+//    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+//        let indexPath = btn.indexPath
+//        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+//        let model = sectionModel.list.safeObj(At: indexPath.item){
+//            if let model = model as? TSActionInfoModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+//            }else if let ringModel = model as? TSRingModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){ success in
+//                    if success {
+//                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
+//                    }
+//                }
+//            }
+//        }
+//        TSBusinessAudioPlayer.shared.stop()
+//    }
     
     
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
     }
     }

+ 147 - 33
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift

@@ -7,11 +7,9 @@
 import Combine
 import Combine
 import SwipeCellKit
 import SwipeCellKit
 class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
 class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
-    
-    public var colComponent:TSCollectionViewComponent?
-    public var colAttributes:[String : Any]?
-    public var cancellable: [AnyCancellable] = []
-    
+
+    weak var targetVC:UIViewController?
+    var indexPath:IndexPath = IndexPath(item: 0, section: 0)
     override init(frame: CGRect) {
     override init(frame: CGRect) {
         super.init(frame: frame)
         super.init(frame: frame)
         creatUI()
         creatUI()
@@ -24,24 +22,29 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
 
 
     static let cellID = "TSAIRintoneHistoryCell"
     static let cellID = "TSAIRintoneHistoryCell"
 
 
-    lazy var setRingBtn: TSUIExpandedTouchButton = {
-        let setRingBtn = TSUIExpandedTouchButton()
-        setRingBtn.setUpButton(image: UIImage(named: "ai_setRing_icon"))
-        setRingBtn.setImage(UIImage(named: "ai_setRing_icon"), for: .normal)
-        return setRingBtn
-    }()
-    
     lazy var ringView: TSRingToneCellView = {
     lazy var ringView: TSRingToneCellView = {
         let ringToneView = TSRingToneCellView()
         let ringToneView = TSRingToneCellView()
         ringToneView.cornerRadius = 0.0
         ringToneView.cornerRadius = 0.0
+        ringToneView.setRingBtn.isHidden = false
+        ringToneView.editBtn.isHidden = false
         ringToneView.clickPlayHandel = { [weak self] isplay in
         ringToneView.clickPlayHandel = { [weak self] isplay in
             guard let self = self else { return }
             guard let self = self else { return }
             clickPlay()
             clickPlay()
         }
         }
+        
+        ringToneView.clickEditHandel = { [weak self] in
+            guard let self = self else { return }
+            clickEidtBtn()
+        }
+        
+        ringToneView.clickSetUpHandel = { [weak self] in
+            guard let self = self else { return }
+            clickSetRingBtn()
+      
+        }
         return ringToneView
         return ringToneView
     }()
     }()
     
     
-    
     lazy var generateView: TSRingToneGenerateView = {
     lazy var generateView: TSRingToneGenerateView = {
         let generateView = TSRingToneGenerateView()
         let generateView = TSRingToneGenerateView()
         generateView.isHidden = true
         generateView.isHidden = true
@@ -104,13 +107,6 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
             make.edges.equalToSuperview()
             make.edges.equalToSuperview()
         }
         }
     
     
-        contentView.addSubview(setRingBtn)
-        setRingBtn.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.trailing.equalTo(-16)
-            make.width.height.equalTo(20)
-        }
-        
         contentView.addSubview(vipView)
         contentView.addSubview(vipView)
         vipView.snp.makeConstraints { make in
         vipView.snp.makeConstraints { make in
             make.top.trailing.equalTo(0)
             make.top.trailing.equalTo(0)
@@ -131,19 +127,23 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
         
         
     }
     }
     
     
+    func setTargetVC(targetVC:UIViewController?,indexPath:IndexPath) {
+        self.targetVC = targetVC
+        self.indexPath = indexPath
+    }
+    
     func dealThings(){
     func dealThings(){
-        NotificationCenter.default.addObserver(forName: .kBusinessAudioStateChange, object: nil, queue: nil) { notification in
-            if let userInfo = notification.userInfo as? [String: TSBusinessAudioPlayer.PlayerState], let state = userInfo["PlayerState"] {
-                kExecuteOnMainThread {
-                    self.changePlayerState(state: state)
-                }
-            }
-        }
+//        NotificationCenter.default.addObserver(forName: .kBusinessAudioStateChange, object: nil, queue: nil) { notification in
+//            if let userInfo = notification.userInfo as? [String: TSBusinessAudioPlayer.PlayerState], let state = userInfo["PlayerState"] {
+//                kExecuteOnMainThread {
+//                    self.changePlayerState(state: state)
+//                }
+//            }
+//        }
     }
     }
     
     
     override func prepareForReuse() {
     override func prepareForReuse() {
         super.prepareForReuse()
         super.prepareForReuse()
-//        cancellable.removeAll()
     }
     }
     
     
     deinit {
     deinit {
@@ -168,6 +168,12 @@ extension TSAIRintoneHistoryCell {
 
 
         updataActionInfoModelView(model: model)
         updataActionInfoModelView(model: model)
         dePrint("model actionStatus 收到=\(model.actionStatus)")
         dePrint("model actionStatus 收到=\(model.actionStatus)")
+        
+        if playSelf{
+            setPlayerStateChangedHandle()
+        }else{
+            changePlayerState(state: .stop)
+        }
     }
     }
     
     
     func ringModelDidSet(){
     func ringModelDidSet(){
@@ -179,6 +185,20 @@ extension TSAIRintoneHistoryCell {
         exampleView.isHidden = true
         exampleView.isHidden = true
         generateView.isHidden = true
         generateView.isHidden = true
         vipView.isHidden = !ringModel.vip
         vipView.isHidden = !ringModel.vip
+        
+        if playSelf{
+            setPlayerStateChangedHandle()
+        }else{
+            changePlayerState(state: .stop)
+        }
+    }
+    
+    func setPlayerStateChangedHandle(){
+        
+        TSBusinessAudioPlayer.shared.stateChangedHandle = { [weak self] playerState in
+            guard let self = self else { return }
+            self.changePlayerState(state: playerState)
+        }
         self.changePlayerState(state: TSBusinessAudioPlayer.shared.currentPlayerState)
         self.changePlayerState(state: TSBusinessAudioPlayer.shared.currentPlayerState)
     }
     }
     
     
@@ -194,11 +214,13 @@ extension TSAIRintoneHistoryCell {
             if TSBusinessAudioPlayer.shared.isPlaying{
             if TSBusinessAudioPlayer.shared.isPlaying{
                 TSBusinessAudioPlayer.shared.stop()
                 TSBusinessAudioPlayer.shared.stop()
             }else{
             }else{
-                TSBusinessAudioPlayer.shared.playUrlString(modelUrlString,indexPath: indexPath)
+                TSBusinessAudioPlayer.shared.playUrlString(modelUrlString,localURL: modelLocalURL,indexPath: indexPath)
             }
             }
             
             
         }else{
         }else{
-            TSBusinessAudioPlayer.shared.playUrlString(modelUrlString,indexPath: indexPath)
+            TSBusinessAudioPlayer.shared.stop()//让上一个对象停止播放
+            setPlayerStateChangedHandle()//更换监听
+            TSBusinessAudioPlayer.shared.playUrlString(modelUrlString,localURL: modelLocalURL,indexPath: indexPath)
         }
         }
     }
     }
     
     
@@ -245,7 +267,7 @@ extension TSAIRintoneHistoryCell {
     }
     }
 
 
     var playSelf:Bool{
     var playSelf:Bool{
-        return TSBusinessAudioPlayer.shared.isPlayURLString(string: modelUrlString,indexPath: indexPath)
+        return TSBusinessAudioPlayer.shared.isPlayURLString(string: modelUrlString,localURL: modelLocalURL,indexPath: indexPath)
     }
     }
 
 
     var modelUrlString:String{
     var modelUrlString:String{
@@ -257,9 +279,101 @@ extension TSAIRintoneHistoryCell {
         }
         }
         return urlString
         return urlString
     }
     }
+
+    var modelLocalURL:URL?{
+        if let model = model{
+            return nil
+        }else if let ringModel = ringModel{
+            return TSDownloadManager.getRingLocalURL(ringModel: ringModel)
+        }
+        return nil
+    }
+    
+}
+//点击编辑和设置按钮
+extension TSAIRintoneHistoryCell {
+    
+    @objc func clickSetRingBtn(){
+
+        guard let targetVC = targetVC else { return }
+
+//        if let model = model {
+//            _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: targetVC, urlString: model.response.musicUrl, fileName: model.response.title)
+//        }else if let ringModel = ringModel  {
+//            _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: targetVC, urlString: ringModel.audioUrl, fileName: ringModel.title)
+//            { success in
+//                if success {
+//                    TSMineRintoneHistory.shared.saveModel(model: ringModel)
+//                }
+//            }
+//        }
+        
+        
+//        if let path = TSDownloadManager.getRingLocalURL(urlString: model.ringtone) {
+//            _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:  model.name)
+//        }else{
+//            TSRingLoadingView.shared.showWindow()
+//            _ = TSDownloadManager.downloadFile(urlString:  model.ringtone,missingEx: "mp3") {[weak self] url, error in
+//                guard let self = self else { return }
+//                if let path = url {
+////                  let savePath = TSBusinessFileManager.saveRingPathURL.appendingPathComponent(path.lastPathComponent)
+////                  TSFileManagerTool.copyFileWithOverwrite(from: path, to: savePath)
+//                    _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:model.name)
+//                }
+//                
+//                TSRingLoadingView.shared.remove()
+//            }
+//        }
+        
+        TSBusinessAudioPlayer.shared.stop()
+    }
     
     
+    @objc func clickEidtBtn(){
+
+        guard let targetVC = targetVC else { return }
+        var editRingModel:TSRingModel? = nil
+        if let model = self.ringModel {
+            editRingModel = model
+        }else if let model = self.model {
+            editRingModel = TSRingModel.getTSRingModel(TSActionInfoModel: model)
+        }
+
+        if let editRingModel = editRingModel{
+            let urlString = editRingModel.audioUrl
+            if let url = TSDownloadManager.getRingLocalURL(urlString: urlString) {
+                kPushVC(target: targetVC, modelVC: TSEditAudioVideoBaseVC(ringModel: editRingModel, editOriginalURL: url))
+                TSBusinessAudioPlayer.shared.stop()
+            }else{
+                TSRingLoadingView.shared.showWindow()
+                _ = TSDownloadManager.downloadFile(urlString: urlString,missingEx: "mp3") {[weak self] url, error in
+                    guard let self = self else { return }
+                    TSRingLoadingView.shared.remove()
+                    if let url = url {
+               
+                    }else{
+                        kShowToastDataMissing()
+                    }
+                }
+            }
+        }
+    }
     
     
-    var indexPath:IndexPath? {
-        return setRingBtn.indexPath
+    func pushVCTSEditAudioVideo(targetVC:UIViewController,modelVC:TSEditAudioVideoBaseVC){
+        
+
+        if let fileInfo = TSBusinessAudioPlayer.getAudioFileInfo(path: modelVC.editOriginalURL.path) {
+            if let size = fileInfo.sizeInBytes {
+                modelVC.ringModel.size = Int(size)
+            }
+            
+            if let duration = fileInfo.durationInSeconds {
+                modelVC.ringModel.duration = Int(duration)
+            }
+        }
+        
+        kPushVC(target: targetVC, modelVC: modelVC)
+        TSBusinessAudioPlayer.shared.stop()
+        
     }
     }
+
 }
 }

+ 18 - 1
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift

@@ -108,7 +108,24 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
             return
             return
         }
         }
         audioPlayer.stop()
         audioPlayer.stop()
-        _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+        
+//        if let path = TSDownloadManager.getRingLocalURL(urlString: model.ringtone) {
+//            _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:  model.name)
+//        }else{
+//            TSRingLoadingView.shared.showWindow()
+//            _ = TSDownloadManager.downloadFile(urlString:  model.ringtone,missingEx: "mp3") {[weak self] url, error in
+//                guard let self = self else { return }
+//                if let path = url {
+////                  let savePath = TSBusinessFileManager.saveRingPathURL.appendingPathComponent(path.lastPathComponent)
+////                  TSFileManagerTool.copyFileWithOverwrite(from: path, to: savePath)
+//                    _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:model.name)
+//                }
+//                
+//                TSRingLoadingView.shared.remove()
+//            }
+//        }
+        
+//        _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
     }
     }
     
     
     @objc func clickPlay(){
     @objc func clickPlay(){

+ 19 - 20
AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift

@@ -117,9 +117,8 @@ extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDeleg
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
            let cell = cell as? TSAIRintoneHistoryCell
            let cell = cell as? TSAIRintoneHistoryCell
         {
         {
-            cell.delegate = self
-            cell.setRingBtn.indexPath = indexPath
-            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+            cell.setTargetVC(targetVC: self, indexPath: indexPath)
+//            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
             if let model = itemModel as? TSActionInfoModel {
             if let model = itemModel as? TSActionInfoModel {
                 cell.model = model
                 cell.model = model
             }else if let ringModel = itemModel as? TSRingModel {
             }else if let ringModel = itemModel as? TSRingModel {
@@ -130,23 +129,23 @@ extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDeleg
         return cell
         return cell
     }
     }
 
 
-    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
-        let indexPath = btn.indexPath
-        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
-        let model = sectionModel.list.safeObj(At: indexPath.item){
-            if let model = model as? TSActionInfoModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
-            }else if let ringModel = model as? TSRingModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
-//                { success in
-//                    if success {
-//                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
-//                    }
-//                }
-            }
-        }
-        TSBusinessAudioPlayer.shared.stop()
-    }
+//    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+//        let indexPath = btn.indexPath
+//        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+//        let model = sectionModel.list.safeObj(At: indexPath.item){
+//            if let model = model as? TSActionInfoModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+//            }else if let ringModel = model as? TSRingModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
+////                { success in
+////                    if success {
+////                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
+////                    }
+////                }
+//            }
+//        }
+//        TSBusinessAudioPlayer.shared.stop()
+//    }
     
     
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
         
         

+ 19 - 19
AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/TSDiscoverListVC.swift

@@ -136,8 +136,8 @@ extension TSDiscoverListVC: UICollectionViewDataSource ,UICollectionViewDelegate
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
            let cell = cell as? TSAIRintoneHistoryCell
            let cell = cell as? TSAIRintoneHistoryCell
         {
         {
-            cell.setRingBtn.indexPath = indexPath
-            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+            cell.setTargetVC(targetVC: self, indexPath: indexPath)
+//            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
             if let model = itemModel as? TSActionInfoModel {
             if let model = itemModel as? TSActionInfoModel {
                 cell.model = model
                 cell.model = model
             }else if let ringModel = itemModel as? TSRingModel {
             }else if let ringModel = itemModel as? TSRingModel {
@@ -148,21 +148,21 @@ extension TSDiscoverListVC: UICollectionViewDataSource ,UICollectionViewDelegate
         return cell
         return cell
     }
     }
 
 
-    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
-        let indexPath = btn.indexPath
-        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
-        let itemModel = sectionModel.list.safeObj(At: indexPath.item){
-//            if let model = itemModel as? TSActionInfoModel {
-//                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
-//            }else
-            if let ringModel = itemModel as? TSRingModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){ success in
-                    if success {
-                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
-                    }
-                }
-            }
-        }
-        TSBusinessAudioPlayer.shared.stop()
-    }
+//    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+//        let indexPath = btn.indexPath
+//        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+//        let itemModel = sectionModel.list.safeObj(At: indexPath.item){
+////            if let model = itemModel as? TSActionInfoModel {
+////                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+////            }else
+//            if let ringModel = itemModel as? TSRingModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){ success in
+//                    if success {
+//                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
+//                    }
+//                }
+//            }
+//        }
+//        TSBusinessAudioPlayer.shared.stop()
+//    }
 }
 }

+ 22 - 23
AIRingtone/Business/TSDiscoverVC/TSRingDownVC/TSRingDownVC.swift

@@ -123,9 +123,8 @@ extension TSRingDownVC: UICollectionViewDataSource ,UICollectionViewDelegate,UIC
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
             let itemModel = sectionModel.list.safeObj(At: indexPath.item),
            let cell = cell as? TSAIRintoneHistoryCell
            let cell = cell as? TSAIRintoneHistoryCell
         {
         {
-            cell.delegate = self
-            cell.setRingBtn.indexPath = indexPath
-            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+            cell.setTargetVC(targetVC: self, indexPath: indexPath)
+//            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
             if let model = itemModel as? TSActionInfoModel {
             if let model = itemModel as? TSActionInfoModel {
                 cell.model = model
                 cell.model = model
             }else if let ringModel = itemModel as? TSRingModel {
             }else if let ringModel = itemModel as? TSRingModel {
@@ -137,26 +136,26 @@ extension TSRingDownVC: UICollectionViewDataSource ,UICollectionViewDelegate,UIC
         return cell
         return cell
     }
     }
 
 
-    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
-        let indexPath = btn.indexPath
-        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
-        let model = sectionModel.list.safeObj(At: indexPath.item){
-//            if let model = model as? TSActionInfoModel {
-//                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
-//            }else
-
-            if let ringModel = model as? TSRingModel {
-                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
-//                { success in
-//                    if success {
-//                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
-//                    }
-//                }
-            }
-        }
-        
-        TSBusinessAudioPlayer.shared.stop()
-    }
+//    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+//        let indexPath = btn.indexPath
+//        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+//        let model = sectionModel.list.safeObj(At: indexPath.item){
+////            if let model = model as? TSActionInfoModel {
+////                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+////            }else
+//
+//            if let ringModel = model as? TSRingModel {
+//                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
+////                { success in
+////                    if success {
+////                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
+////                    }
+////                }
+//            }
+//        }
+//        
+//        TSBusinessAudioPlayer.shared.stop()
+//    }
     
     
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
         
         

+ 2 - 2
AIRingtone/Business/TSDiscoverVC/TSRingDownVC/VM/TSRingDownVM.swift

@@ -40,8 +40,8 @@ extension TSRingDownVM {
     func removeModel(model:TSRingModel){
     func removeModel(model:TSRingModel){
         TSMineRintoneHistory.shared.removeModel(model: model)
         TSMineRintoneHistory.shared.removeModel(model: model)
         updateRecentData()
         updateRecentData()
-        
-        if let pathURL = TSCommonTool.getCachedURLString(from: model.audioUrl) {
+        if let pathURL = TSDownloadManager.getRingLocalURL(ringModel: model) {
+//        if let pathURL = TSCommonTool.getCachedURLString(from: model.audioUrl) {
             TSFileManagerTool.removeItem(from: pathURL)
             TSFileManagerTool.removeItem(from: pathURL)
         }
         }
 
 

+ 55 - 0
AIRingtone/Business/TSEditAudioVideoVC/TSEditAudioSliderView.swift

@@ -0,0 +1,55 @@
+//
+//  TSEditAudioSliderView.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/27.
+//
+
+class TSEditAudioSliderView: TSBaseView {
+    
+    lazy var leftLabel: UILabel = {
+        let leftLabel = UILabel.createLabel(text: "00:00s",font: .font(size: 14.0),textColor: .white.withAlphaComponent(0.8),numberOfLines: 2)
+        return leftLabel
+    }()
+    
+    lazy var slider: UISlider = {
+        let slider = UISlider()
+        slider.setThumbImage(UIImage(named: "ic-ring-edit-slider"), for: .normal)
+        slider.thumbTintColor = .themeColor
+        slider.minimumTrackTintColor = .themeColor
+        slider.maximumTrackTintColor = .white.withAlphaComponent(0.1)
+        return slider
+    }()
+    
+    lazy var rightLabel: UILabel = {
+        let leftLabel = UILabel.createLabel(text: "00:00s",font: .font(size: 14.0),textColor: .white.withAlphaComponent(0.8),textAlignment: .right,numberOfLines: 2)
+        return leftLabel
+    }()
+    
+    override func creatUI() {
+        self.frame = CGRectMake(0, 0, k_ScreenWidth, 40)
+        contentView.addSubview(leftLabel)
+        contentView.addSubview(slider)
+        contentView.addSubview(rightLabel)
+        
+        leftLabel.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.centerY.equalToSuperview()
+            make.width.equalTo(62)
+        }
+        
+        slider.snp.makeConstraints { make in
+            make.leading.equalTo(87)
+            make.trailing.equalTo(-57)
+            make.centerY.equalToSuperview()
+        }
+        
+        rightLabel.snp.makeConstraints { make in
+            make.trailing.equalTo(-16)
+            make.centerY.equalToSuperview()
+            make.width.equalTo(40)
+        }
+    }
+    
+    
+}

+ 758 - 371
AIRingtone/Business/TSEditAudioVideoVC/TSEditAudioVideoBaseVC.swift

@@ -4,68 +4,202 @@
 //
 //
 //  Created by 100Years on 2025/3/26.
 //  Created by 100Years on 2025/3/26.
 //
 //
-
+import AVFoundation
 class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelegate {
 class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelegate {
-    
+    //#################################### 数据区域 ####################################//
     var titleName:String = "Edit".localized
     var titleName:String = "Edit".localized
-    override func createView() {
+    
+    var ringModel: TSRingModel
+    var editOriginalURL:URL
+    
+    init(ringModel: TSRingModel,editOriginalURL:URL) {
+        self.ringModel = ringModel
+        self.editOriginalURL = editOriginalURL
+        super.init()
+    }
+    
+    @MainActor required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    
+    //#################################### TSCustomStackView ####################################//
+    lazy var cusStackView: TSCustomStackView = {
+        let cusStackView = TSCustomStackView(axis: .vertical,spacing: 0)
+        return cusStackView
+    }()
+    
+    
+    //#################################### 名字 ####################################//
+    lazy var nameLabel: UILabel = {
+        let nameLabel = UILabel.createLabel(text: ringModel.title, font: .font(size: 14),textColor: .white.withAlphaComponent(0.8),textAlignment: .center)
+        return nameLabel
+    }()
+    
+    weak var nameInputTextField: UITextField?
+    lazy var nameButton: UIButton = {
+        let nameButton = UIButton.createButton(image: UIImage(named: "edit_field")) { [weak self]  in
+            guard let self = self else { return }
+            
+            let alertVC = UIAlertController(title: nil, message: "Ringtone Name".localized, preferredStyle: .alert)
+            alertVC.addTextField { textField in
+                textField.placeholder = "input name".localized
+                textField.font = UIFont.systemFont(ofSize: 16)
+                textField.text = self.ringModel.title
+                self.nameInputTextField = textField
+            }
+            let ok = UIAlertAction(title: "OK".localized, style: .default) { [weak self] _ in
+                guard let self = self else { return }
+                nameLabel.text = nameInputTextField?.text
+                
+                if let text = nameLabel.text{
+                    ringModel.title = text
+                }
+            }
+            let cancel = UIAlertAction(title: "Cancel".localized, style: .cancel)
+            alertVC.addAction(cancel)
+            alertVC.addAction(ok)
+            present(alertVC, animated: true) {
+                self.nameInputTextField?.becomeFirstResponder()
+            }
+            
+        }
+        return nameButton
+    }()
+    
+    lazy var nameView: UIView = {
+        let nameView = UIView()
         
         
-        setPageTitle(titleName)
+        let nameContentView = UIView()
+        nameView.addSubview(nameContentView)
+        nameContentView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
         
         
+        nameContentView.addSubview(nameLabel)
+        nameContentView.addSubview(nameButton)
         
         
+        nameLabel.snp.makeConstraints { make in
+            make.leading.equalToSuperview()
+            make.width.greaterThanOrEqualTo(220*kDesignScale)
+            make.height.equalTo(23)
+            make.top.bottom.equalTo(0)
+        }
+        nameButton.snp.makeConstraints { make in
+            make.trailing.equalToSuperview()
+            make.leading.equalTo(nameLabel.snp.trailing)
+            make.width.height.equalTo(23)
+            make.top.bottom.equalTo(0)
+        }
         
         
-    }
+        return nameView
+    }()
     
     
+    //#################################### track编辑视频 ####################################//\
+    let trackContentViewLeft:CGFloat = 24.0
+    let trackViewH:CGFloat = 210.0
+    lazy var trackViewW:CGFloat = k_ScreenWidth - trackContentViewLeft * 2
+    lazy var trackView: ZHWaveformView = {
+        let waveform = ZHWaveformView(frame: CGRect(x: 0, y: 0, width: trackViewW, height:trackViewH),fileURL: editOriginalURL)
+        waveform.backgroundColor = .clear
+        waveform.beginningPartColor = .white.withAlphaComponent(0.2) // color
+        waveform.endPartColor = .white.withAlphaComponent(0.2)
+        waveform.wavesColor = "#7E57F4".uiColor
+        waveform.trackScale = 0.2// 0 ~ 1
+        waveform.waveformDelegate = self
+        waveform.croppedDelegate = self
+        
+        return waveform
+    }()
     
     
-    var nameLabel: UILabel!
-    var nameButton: UIButton!
-
-    var trackContentView: UIView!
-    var trackBgView: UIView!
-
-    var timeLabel: UILabel!
-    var cutButton: UIButton!
-    var playButton: UIButton!
-    var undoButton: UIButton!
-
-    var fadeinSlider: UISlider!
-    var fadeinTimeLabel: UILabel!
-    var fadeoutSlider: UISlider!
-    var fadeoutTimeLabel: UILabel!
-
-    var doneButton: UIButton!
-
-    var dragLeftView: UIView!
-    var startTimeLabel: UILabel!
-    var dragRightView: UIView!
-    var endTimeLabel: UILabel!
-    var progressLine: UIView!
-    weak var nameInputTextField: UITextField?
-
-    var trackView: ZHWaveformView?
-
-    private lazy var player = TSBusinessAudioPlayer()
-    private lazy var audioTool = AudioTool()
-
-    var ringModel: TSRingModel?
-    lazy var operationCache: [TSRingModel] = []
-    var tempMp3Path: String?
-    var needClearTemp : Bool = false
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        view.backgroundColor = .clear
-//        saveButton.set { [weak self] in
-//            self?.saveButtonClick()
-//        }
+    lazy var trackContentView: UIView = {
+        let trackContentView = UIView(frame: CGRectMake(24, 0, k_ScreenWidth-48, trackViewH+12))
 
 
-        if let ring = ringModel {
-            operationCache.append(ring)
+        trackContentView.addSubview(trackView)
+        trackView.snp.makeConstraints { make in
+            make.top.centerX.equalToSuperview()
+            make.width.equalTo(trackView.width)
+            make.height.equalTo(trackView.height)
         }
         }
-
-
-        setupUI()
-
+        return trackContentView
+    }()
+    
+    
+    func creatDragView()->UIView{
+        let bgView = UIView()
+        
+        let view1 = UIView.creatColor(color: .white)
+        bgView.addSubview(view1)
+        let view2 = UIView.creatColor(color: .white)
+        view2.cornerRadius = 6
+        bgView.addSubview(view2)
+        
+        view1.snp.makeConstraints { make in
+            make.width.equalTo(2)
+            make.height.equalTo(trackViewH)
+            make.centerX.equalToSuperview()
+            make.top.equalToSuperview()
+        }
+        
+        view2.snp.makeConstraints { make in
+            make.top.equalTo(view1.snp.bottom)
+            make.width.height.equalTo(12)
+            make.bottom.equalToSuperview()
+            make.leading.equalTo(12)
+            make.trailing.equalTo(-12)
+        }
+        return bgView
+    }
+    lazy var dragLeftView: UIView = creatDragView()
+    lazy var dragRightView: UIView = creatDragView()
+    var progressLine: UIView = UIView.creatColor(color: .themeColor)
+    
+    
+    lazy var startTimeLabel: UILabel = {
+        let label = UILabel.createLabel(text: "00:00s",font: .font(size: 14.0),textColor: .lesserText)
+        return label
+    }()
+    var endTimeLabel: UILabel = {
+        let label = UILabel.createLabel(text: "10:00s",font: .font(size: 14.0),textColor: .lesserText)
+        return label
+    }()
+    var timeLabel: UILabel = {
+        let label = UILabel.createLabel(text: "00:00",font: .font(size: 26.0),textColor: .lesserText)
+        return label
+    }()
+    
+    
+    lazy var timeView: UIView = {
+        let timeView = UIView()
+        
+        timeView.addSubview(startTimeLabel)
+        timeView.addSubview(endTimeLabel)
+        timeView.addSubview(timeLabel)
+        
+        startTimeLabel.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.top.equalTo(12)
+            make.height.equalTo(20)
+        }
+        
+        endTimeLabel.snp.makeConstraints { make in
+            make.trailing.equalTo(-16)
+            make.top.equalTo(12)
+            make.height.equalTo(20)
+        }
+        
+        timeLabel.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(45)
+            make.height.equalTo(26)
+        }
+        
+        return timeView
+    }()
+    
+    lazy var trackBgView: UIView  = {
+        let trackBgView = UIView()
+        
         let leftPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.leftPanRecognizer(sender:)))
         let leftPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.leftPanRecognizer(sender:)))
         dragLeftView.addGestureRecognizer(leftPanRecognizer)
         dragLeftView.addGestureRecognizer(leftPanRecognizer)
         dragLeftView.isUserInteractionEnabled = true
         dragLeftView.isUserInteractionEnabled = true
@@ -73,8 +207,289 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
         let rightPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.rightPanRecognizer(sender:)))
         let rightPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.rightPanRecognizer(sender:)))
         dragRightView.addGestureRecognizer(rightPanRecognizer)
         dragRightView.addGestureRecognizer(rightPanRecognizer)
         dragRightView.isUserInteractionEnabled = true
         dragRightView.isUserInteractionEnabled = true
-    }
+        
+        let gradientImageView = UIImageView.createImageView(imageName: "trackBg_gradient",contentMode: .scaleToFill)
+        trackContentView.addSubview(gradientImageView)
+        gradientImageView.snp.makeConstraints { make in
+            make.leading.top.trailing.equalToSuperview()
+            make.height.equalTo(trackViewH)
+        }
+
+        trackBgView.addSubview(trackContentView)
+        trackContentView.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.leading.equalTo(trackContentView.x)
+            make.trailing.equalTo(-trackContentView.x)
+            make.height.equalTo(trackViewH)
+        }
+        
+   
+        trackBgView.addSubview(dragLeftView)
+        dragLeftView.snp.makeConstraints { make in
+            make.top.leading.equalToSuperview()
+        }
+        
+        trackBgView.addSubview(dragRightView)
+        dragRightView.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.trailing.equalToSuperview()
+        }
+        progressLine.isHidden = true
+        trackBgView.addSubview(progressLine)
+        progressLine.snp.makeConstraints { make in
+            make.top.leading.equalToSuperview()
+            make.width.equalTo(1)
+            make.height.equalTo(trackViewH)
+        }
+        
+        trackBgView.addSubview(timeView)
+        timeView.snp.makeConstraints { make in
+            make.top.equalTo(trackContentView.bottom)
+            make.trailing.leading.equalToSuperview()
+            make.height.equalTo(71)
+            make.bottom.equalTo(0)
+        }
+        
+        return trackBgView
+    }()
+    
+    
+    //#################################### paly按钮 ####################################//
+    
+    lazy var playButtonView: UIView = {
+        let playButtonView = UIView(frame: CGRectMake(0, 0, k_ScreenWidth, 18+80+18))
+        
+        let playButtonBg = UIView()
+        playButtonBg.backgroundColor = .white.withAlphaComponent(0.1)
+        playButtonBg.cornerRadius = 40
+        playButtonView.addSubview(playButtonBg)
+        playButtonBg.snp.makeConstraints { make in
+            make.width.height.equalTo(80)
+            make.center.equalToSuperview()
+        }
+        
+        playButtonBg.addSubview(playButton)
+        playButton.snp.makeConstraints { make in
+            make.width.height.equalTo(60)
+            make.center.equalToSuperview()
+        }
+        
+        return playButtonView
+    }()
+    
+    
+    lazy var playButton: UIButton = {//editaudio_play //editaudio_pause
+        let playButton = UIButton.createButton(image: UIImage(named: "editaudio_play")){ [weak self]  in
+            guard let self = self else { return }
+            startPlay()
+        }
+        playButton.setImage(UIImage(named: "editaudio_pause"), for: .selected)
+        return playButton
+    }()
+    
+    
+    //#################################### Fade in ####################################//
+    lazy var fadeinSliderView: TSEditAudioSliderView = {
+        let fadeinSliderView = TSEditAudioSliderView()
+        fadeinSliderView.leftLabel.text = "Fade in".localized
+        fadeinSliderView.rightLabel.text = "0s"
+        fadeinSliderView.slider.maximumValue = 5
+//        fadeinSliderView.slider.value = 3
+        return fadeinSliderView
+    }()
+    lazy var fadeinSlider: UISlider = {
+        let slider = fadeinSliderView.slider
+        slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
+        slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
+        slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
+        return slider
+    }()
+    //#################################### Fade out ####################################//
+    
+    lazy var fadeoutSliderView: TSEditAudioSliderView = {
+        let fadeoutSliderView = TSEditAudioSliderView()
+        fadeoutSliderView.leftLabel.text = "Fade out".localized
+        fadeoutSliderView.rightLabel.text = "0s"
+        fadeoutSliderView.slider.maximumValue = 5
+//        fadeoutSliderView.slider.value = 3
+        return fadeoutSliderView
+    }()
+    lazy var fadeoutSlider: UISlider = {
+        let slider = fadeoutSliderView.slider
+        slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
+        slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
+        slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
+        return slider
+    }()
+    //#################################### Output Volume ####################################//
+    lazy var outputVolumeSliderView: TSEditAudioSliderView = {
+        let outputVolumeSliderView = TSEditAudioSliderView()
+        outputVolumeSliderView.leftLabel.text = "Output Volume".localized
+        outputVolumeSliderView.rightLabel.text = "100%"
+        outputVolumeSliderView.slider.minimumValue = 1.0
+        outputVolumeSliderView.slider.maximumValue = 2.0
+//        outputVolumeSliderView.slider.value = 1.5
+        return outputVolumeSliderView
+    }()
+    lazy var outputVolumeSlider: UISlider = {
+        let slider = outputVolumeSliderView.slider
+        slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
+        slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
+        slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
+        return slider
+    }()
+    
+    //#################################### 底部保存按钮等 ####################################//
+    lazy var cutButton: TSAppBtnView = { //保存
+        let cutButton = TSAppBtnView()
+        cutButton.setUpButton(style: .normalBorder, btnFrame: CGRectMake(0, 0, 98*kDesignScale, 48)) { [weak self]  in
+            guard let self = self else { return }
+            saveButtonClick()
+        }
+        cutButton.button.setTitle("Save".localized, for: .normal)
+        return cutButton
+    }()
+    
+    lazy var doneButton: TSAppBtnView = { // set
+        let doneButton = TSAppBtnView()
+        doneButton.setUpButton(style: .normalSet, btnFrame: CGRectMake(0, 0, 233*kDesignScale, 48)) { [weak self]  in
+            guard let self = self else { return }
+            setButtonClick()
+        }
+        doneButton.button.setTitle("Set Now".localized, for: .normal)
+        return doneButton
+    }()
+    
+    
+    var undoButton: UIButton!//暂时没用
+    
+    lazy var bottomBtnView: UIView = {
+        let bottomBtnView = UIView()
+        
+        bottomBtnView.addSubview(cutButton)
+        bottomBtnView.addSubview(doneButton)
+        
+        cutButton.snp.makeConstraints { make in
+            make.top.equalTo(16)
+            make.bottom.equalTo(-16)
+            make.leading.equalTo(16)
+            make.size.equalTo(cutButton.button.size)
+        }
+        
+        doneButton.snp.makeConstraints { make in
+            make.top.equalTo(16)
+            make.bottom.equalTo(-16)
+            make.trailing.equalTo(-16)
+            make.size.equalTo(doneButton.button.size)
+        }
+        return bottomBtnView
+    }()
+    
+    //#################################### viewDidLoad####################################//
+    override func createData() {
 
 
+    }
+    
+    override func createView() {
+        addNormalNavBarView()
+        setPageTitle(titleName)
+        
+        contentView.addSubview(bottomBtnView)
+        bottomBtnView.snp.makeConstraints { make in
+            make.leading.trailing.equalTo(0)
+            make.bottom.equalTo(-k_Height_safeAreaInsetsBottom())
+        }
+        
+        contentView.addSubview(cusStackView)
+        cusStackView.snp.makeConstraints { make in
+            make.top.leading.bottom.trailing.equalTo(0)
+            make.bottom.equalTo(bottomBtnView.snp.top)
+        }
+        setUpCusStackView()
+    }
+    
+    func setUpCusStackView() {
+        cusStackView.addSubviewToStack(nameView)
+        nameView.snp.makeConstraints { make in
+            make.height.equalTo(56)
+            make.width.equalTo(k_ScreenWidth)
+        }
+        cusStackView.addSubviewToStack(trackBgView)
+        trackBgView.snp.makeConstraints { make in
+            make.height.equalTo(300)
+            make.width.equalTo(k_ScreenWidth)
+        }
+        
+        cusStackView.addSubviewToStack(playButtonView)
+        playButtonView.snp.makeConstraints { make in
+            make.height.equalTo(playButtonView.height)
+            make.width.equalTo(playButtonView.width)
+        }
+        
+        cusStackView.addSubviewToStack(fadeinSliderView)
+        fadeinSliderView.snp.makeConstraints { make in
+            make.height.equalTo(fadeinSliderView.height)
+        }
+        
+        let spaceView = UIView()
+        cusStackView.addSubviewToStack(spaceView)
+        spaceView.snp.makeConstraints { make in
+            make.height.equalTo(8)
+        }
+        
+        cusStackView.addSubviewToStack(fadeoutSliderView)
+        fadeoutSliderView.snp.makeConstraints { make in
+            make.height.equalTo(fadeinSliderView.height)
+        }
+        
+        let spaceView1 = UIView()
+        cusStackView.addSubviewToStack(spaceView1)
+        spaceView1.snp.makeConstraints { make in
+            make.height.equalTo(8)
+        }
+        
+        cusStackView.addSubviewToStack(outputVolumeSliderView)
+        outputVolumeSliderView.snp.makeConstraints { make in
+            make.height.equalTo(fadeinSliderView.height)
+            make.bottom.equalToSuperview()
+        }
+    
+        _ = fadeinSlider
+        _ = fadeoutSlider
+        _ = outputVolumeSlider
+    }
+    
+    override func dealThings() {
+        operationCache.append(ringModel)
+        reloadTrackView()
+    }
+    
+    
+    
+    private lazy var player:TSBusinessAudioPlayer = {
+        let player = TSBusinessAudioPlayer()
+        
+        player.currentTimeChangedHandle = { [weak self] current,total in
+            guard let self = self else { return }
+            handlePlayer(progressChanged: current, total: total)
+        }
+        
+        player.stateChangedHandle = { [weak self] state in
+            guard let self = self else { return }
+            handlePlayer(state: state)
+        }
+        
+        return player
+    }()
+    private lazy var audioTool = AudioTool()
+    private var maxVolume:Float{
+        return outputVolume * 0.5
+    }
+    
+    
+    lazy var operationCache: [TSRingModel] = []
+    var tempMp3Path: String?
+    var needClearTemp : Bool = false
 
 
     override func viewDidDisappear(_ animated: Bool) {
     override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
         super.viewDidDisappear(animated)
@@ -83,7 +498,7 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
             deleteTempFiles()
             deleteTempFiles()
         }
         }
     }
     }
-
+    
     func deleteTempFiles() {
     func deleteTempFiles() {
         if let tempMp3Path = tempMp3Path {
         if let tempMp3Path = tempMp3Path {
             let mp3Dir = NSString(string: tempMp3Path).deletingLastPathComponent
             let mp3Dir = NSString(string: tempMp3Path).deletingLastPathComponent
@@ -97,191 +512,184 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
             }
             }
         }
         }
     }
     }
-
+    
     override func viewWillDisappear(_ animated: Bool) {
     override func viewWillDisappear(_ animated: Bool) {
         super.viewWillDisappear(animated)
         super.viewWillDisappear(animated)
+        // 恢复右滑返回手势
+        navigationController?.interactivePopGestureRecognizer?.isEnabled = true
     }
     }
-
+    
     override func viewWillAppear(_ animated: Bool) {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         super.viewWillAppear(animated)
-    }
-
-    lazy var previousBounds: CGRect = .zero
-    override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
+        // 禁用右滑返回手势
+        navigationController?.interactivePopGestureRecognizer?.isEnabled = false
 
 
-        if previousBounds == .zero {
-            previousBounds = view.bounds
-            DispatchQueue.main.async {
-                self.reloadTrackView()
-            }
-        }
     }
     }
-
+    
+    //    lazy var previousBounds: CGRect = .zero
+    //    override func viewDidLayoutSubviews() {
+    //        super.viewDidLayoutSubviews()
+    //
+    //        if previousBounds == .zero {
+    //            previousBounds = view.bounds
+    //            DispatchQueue.main.async {
+    //                self.reloadTrackView()
+    //            }
+    //        }
+    //    }
+    
     deinit {
     deinit {
         dePrint("RingEditViewController 销毁了")
         dePrint("RingEditViewController 销毁了")
     }
     }
-
+    
     func reloadTrackView() {
     func reloadTrackView() {
-        guard let current = ringModel else {
-            return
-        }
-        var ringFilePath: String?
-        if let fileName = current.fileName,
-           let filePath = RingDownloadManager.shared.filePath(with: fileName),
-           FileManager.default.fileExists(atPath: filePath) {
-            ringFilePath = filePath
-        } else if let filePath = RingDownloadManager.shared.filePath(for: current.fileUrl),
-                  FileManager.default.fileExists(atPath: filePath) {
-            ringFilePath = filePath
-        }
-        guard let ringFilePath = ringFilePath else {
-            return
-        }
-
-        tempMp3Path = ringFilePath
+    
+        tempMp3Path = editOriginalURL.path
 
 
-        trackView?.removeFromSuperview()
         startCropRate = 0
         startCropRate = 0
         endCropRate = 1.0
         endCropRate = 1.0
         dragLeftView.centerX = trackContentView.x
         dragLeftView.centerX = trackContentView.x
         dragRightView.centerX = trackContentView.frame.maxX
         dragRightView.centerX = trackContentView.frame.maxX
-
-        let url = URL(fileURLWithPath: ringFilePath)
-
-        let waveform = ZHWaveformView(
-            frame: CGRect(x: 0, y: 20, width: trackContentView.width, height: trackContentView.height - 40),
-            fileURL: url
-        )
-        waveform.backgroundColor = .clear
-
-        // color
-        waveform.beginningPartColor = .white.withAlphaComponent(0.2)
-        waveform.endPartColor = .white.withAlphaComponent(0.2)
-        waveform.wavesColor = "#55A0E9".uiColor
-
-        // 0 ~ 1
-        waveform.trackScale = 0.2
-
-        waveform.waveformDelegate = self
-        waveform.croppedDelegate = self
-
-        trackContentView.insertSubview(waveform, at: 0)
-        trackView = waveform
-
-        timeLabel.text = current.duration.mmss
-        undoButton.isEnabled = operationCache.count > 1
-        cutButton.isEnabled = false
+        
+        
+        timeLabel.text = ringModel.duration.mmss
+//        undoButton.isEna bled = operationCache.count > 1
+        cutButton.setBtnEnabled(isEnabled: false)
     }
     }
-
+    
     func setupUI() {
     func setupUI() {
-        nameLabel.text = ringModel?.title ?? ""
-
-        doneButton.titleLabel?.numberOfLines = 2
-        doneButton.titleLabel?.textAlignment = .center
-        doneButton.setTitle("Export to GarageBand\nSet Ringtones".localized, for: .normal)
-        doneButton.setGradient(colors: AppTheme.gradientColors, index: 0)
-
-        fadeinSlider.setThumbImage(UIImage(named: "ic-ring-edit-slider"), for: .normal)
-        fadeinSlider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
-        fadeinSlider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
-        fadeinSlider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
-
-        fadeoutSlider.setThumbImage(UIImage(named: "ic-ring-edit-slider"), for: .normal)
-        fadeoutSlider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
-        fadeoutSlider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
-        fadeoutSlider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
+        
+        
     }
     }
 
 
+    override func navBarClickLeftAction() {
+        TSCustomAlertController.show(in: self, config: TSCustomAlertController.AlertConfig(
+            message: "As you leave, the changes you have made will be lost.".localized,
+            messageColor: .red,
+            messageFont: .systemFont(ofSize: 15, weight: .medium),
+            
+            cancelTitle: "Leave".localized,
+            cancelColor: .textAssist,
+            
+            confirmTitle: "Stay".localized,
+            confirmColor: .themeColor,
+            
+            cancelAction: {
+                print("用户点击了Leave")
+                super.navBarClickLeftAction()
+            },
+            confirmAction: {
+                print("用户点击了Stay")
+            }
+        ))
+    }
+    //#################################### UI 储存属性 ####################################//
     lazy var fadeInDuration: Int = 0 {
     lazy var fadeInDuration: Int = 0 {
         didSet {
         didSet {
-            fadeinTimeLabel.text = "\(fadeInDuration)s"
+            fadeinSliderView.rightLabel.text = "\(fadeInDuration)s"
         }
         }
     }
     }
-
     lazy var fadeOutDuration: Int = 0 {
     lazy var fadeOutDuration: Int = 0 {
         didSet {
         didSet {
-            fadeoutTimeLabel.text = "\(fadeOutDuration)s"
+            fadeoutSliderView.rightLabel.text = "\(fadeOutDuration)s"
+        }
+    }
+    
+    lazy var outputVolume: Float = 1.0 {
+        didSet {
+            outputVolumeSliderView.rightLabel.text = "\(Int(outputVolume*100.0))%"
+        }
+    }
+    lazy var previousLeftX: CGFloat = 0
+    lazy var startCropRate: CGFloat = 0 {
+        didSet {
+            let time = startDuration
+            cutButton.setBtnEnabled(isEnabled: true)
+            
+            //            print("---start: \(time)")
+            startTimeLabel.text = time.mmss
+            timeLabel.text = (endDuration - startDuration).mmss
+        }
+    }
+    // 拖拽时被暂停,拖拽结束后,继续播放
+    lazy var isSuspendByAction = false
+    lazy var endCropRate: CGFloat = 1.0 {
+        didSet {
+            cutButton.setBtnEnabled(isEnabled: true)
+            let time = endDuration
+            endTimeLabel.text = time.mmss
+            timeLabel.text = (endDuration - startDuration).mmss
         }
         }
     }
     }
+    lazy var previousRightX: CGFloat = 0
+}
 
 
+
+extension TSEditAudioVideoBaseVC {
+    
+
+    
     @objc func sliderBeginTap(_ slider: UISlider) {
     @objc func sliderBeginTap(_ slider: UISlider) {
         suspendPlay()
         suspendPlay()
     }
     }
-
+    
     @objc func sliderEndTap(_ slider: UISlider) {
     @objc func sliderEndTap(_ slider: UISlider) {
         autoPlayAfterMove()
         autoPlayAfterMove()
     }
     }
-
+    
     @objc func sliderValueChanged(_ slider: UISlider) {
     @objc func sliderValueChanged(_ slider: UISlider) {
-//        print("---\(slider.value)")
-        if player.state == .playing {
+        //        print("---\(slider.value)")
+        if player.isPlaying {
             player.pause()
             player.pause()
         }
         }
         if slider == fadeinSlider {
         if slider == fadeinSlider {
             fadeInDuration = Int(slider.value.rounded(.toNearestOrEven))
             fadeInDuration = Int(slider.value.rounded(.toNearestOrEven))
         } else if slider == fadeoutSlider {
         } else if slider == fadeoutSlider {
             fadeOutDuration = Int(slider.value.rounded(.toNearestOrEven))
             fadeOutDuration = Int(slider.value.rounded(.toNearestOrEven))
+        } else if slider == outputVolumeSlider {
+            outputVolume = slider.value
         }
         }
     }
     }
 
 
-    lazy var startCropRate: CGFloat = 0 {
-        didSet {
-            let time = startDuration
-            cutButton.isEnabled = true
-
-//            print("---start: \(time)")
-            startTimeLabel.text = time.mmss
-            timeLabel.text = (endDuration - startDuration).mmss
-        }
-    }
-
+    
     // 裁剪起始时间
     // 裁剪起始时间
     var startDuration: Double {
     var startDuration: Double {
-        guard let model = ringModel else {
-            return 0
-        }
-        return startCropRate * model.duration
+        return startCropRate * CGFloat(ringModel.duration)
     }
     }
+    
 
 
-    lazy var previousLeftX: CGFloat = 0
-
+    
     var startMinCenterX: CGFloat {
     var startMinCenterX: CGFloat {
         return trackContentView.x
         return trackContentView.x
     }
     }
-
+    
     var startMaxCenterX: CGFloat {
     var startMaxCenterX: CGFloat {
-        guard let model = ringModel else {
-            return 0
-        }
-        let ratio = (endCropRate * model.duration - 10) / model.duration
+        let ratio = (endCropRate * CGFloat(ringModel.duration) - 10) / CGFloat(ringModel.duration)
         return ratio * trackContentView.width + trackContentView.x
         return ratio * trackContentView.width + trackContentView.x
     }
     }
-
+    
     var endMinCenterX: CGFloat {
     var endMinCenterX: CGFloat {
-        guard let model = ringModel else {
-            return 0
-        }
-        let ratio = (startCropRate * model.duration + 10) / model.duration
+        let ratio = (startCropRate * CGFloat(ringModel.duration) + 10) / CGFloat(ringModel.duration)
         return ratio * trackContentView.width + trackContentView.x
         return ratio * trackContentView.width + trackContentView.x
     }
     }
-
+    
     var endMaxCenterX: CGFloat {
     var endMaxCenterX: CGFloat {
         return trackContentView.frame.maxX
         return trackContentView.frame.maxX
     }
     }
 
 
-    // 拖拽时被暂停,拖拽结束后,继续播放
-    lazy var isSuspendByAction = false
     @objc private func leftPanRecognizer(sender: UIPanGestureRecognizer) {
     @objc private func leftPanRecognizer(sender: UIPanGestureRecognizer) {
+        
+        dePrint("leftPanRecognizer=\(sender)")
         let limitMinCenterX: CGFloat = startMinCenterX
         let limitMinCenterX: CGFloat = startMinCenterX
         let limitMaxCenterX: CGFloat = startMaxCenterX
         let limitMaxCenterX: CGFloat = startMaxCenterX
         guard limitMaxCenterX > limitMinCenterX else {
         guard limitMaxCenterX > limitMinCenterX else {
             if sender.state == .began {
             if sender.state == .began {
-                THUD.toast("No less than 10s".localized())
+                dePrint("rightPanRecognizer No less than 10s")
+                TSToastShared.showToast(text:"No less than 10s".localized)
             }
             }
             return
             return
         }
         }
-
+        
         if sender.state == .began {
         if sender.state == .began {
             suspendPlay()
             suspendPlay()
         } else if sender.state == .changed {
         } else if sender.state == .changed {
@@ -293,17 +701,17 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
                   center.x < limitMaxCenterX else {
                   center.x < limitMaxCenterX else {
                 // 越界,拖不动了, 最少10s
                 // 越界,拖不动了, 最少10s
                 if endDuration - startDuration < 11 {
                 if endDuration - startDuration < 11 {
-                    THUD.toast("No less than 10s".localized())
+                    TSToastShared.showToast(text:"No less than 10s".localized)
                 }
                 }
                 return
                 return
             }
             }
             dragLeftView.center = center
             dragLeftView.center = center
-
+            
         } else if sender.state == .ended || sender.state == .failed {
         } else if sender.state == .ended || sender.state == .failed {
             previousLeftX = dragLeftView.center.x
             previousLeftX = dragLeftView.center.x
             autoPlayAfterMove()
             autoPlayAfterMove()
         }
         }
-
+        
         // 边界校验
         // 边界校验
         if dragLeftView.centerX < limitMinCenterX {
         if dragLeftView.centerX < limitMinCenterX {
             dragLeftView.centerX = limitMinCenterX
             dragLeftView.centerX = limitMinCenterX
@@ -311,43 +719,32 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
         if dragLeftView.centerX > limitMaxCenterX {
         if dragLeftView.centerX > limitMaxCenterX {
             dragLeftView.centerX = limitMaxCenterX
             dragLeftView.centerX = limitMaxCenterX
         }
         }
-
+        
         let position = dragLeftView.centerX - trackContentView.x
         let position = dragLeftView.centerX - trackContentView.x
-        trackView?.updateLeftCroppedPosition(position)
+        trackView.updateLeftCroppedPosition(position)
         startCropRate = position / trackContentView.width
         startCropRate = position / trackContentView.width
         progressLine.centerX = dragLeftView.centerX
         progressLine.centerX = dragLeftView.centerX
     }
     }
-
-    lazy var endCropRate: CGFloat = 1.0 {
-        didSet {
-            cutButton.isEnabled = true
-
-            let time = endDuration
-//            print("---end: \(time)")
-            endTimeLabel.text = time.mmss
-            timeLabel.text = (endDuration - startDuration).mmss
-        }
-    }
+    
 
 
     var endDuration: Double {
     var endDuration: Double {
-        guard let model = ringModel else {
-            return 0
-        }
-        return endCropRate * model.duration
+        return endCropRate * CGFloat(CGFloat(ringModel.duration))
     }
     }
+    
 
 
-    lazy var previousRightX: CGFloat = 0
     @objc private func rightPanRecognizer(sender: UIPanGestureRecognizer) {
     @objc private func rightPanRecognizer(sender: UIPanGestureRecognizer) {
+        dePrint("rightPanRecognizer=\(sender)")
         let limitMinCenterX: CGFloat = endMinCenterX
         let limitMinCenterX: CGFloat = endMinCenterX
         let limitMaxCenterX: CGFloat = endMaxCenterX
         let limitMaxCenterX: CGFloat = endMaxCenterX
         guard limitMaxCenterX > limitMinCenterX else {
         guard limitMaxCenterX > limitMinCenterX else {
             if sender.state == .began {
             if sender.state == .began {
+                dePrint("rightPanRecognizer No less than 10s")
                 // 越界,拖不动了
                 // 越界,拖不动了
-                THUD.toast("No less than 10s".localized())
+                TSToastShared.showToast(text:"No less than 10s".localized)
             }
             }
             return
             return
         }
         }
-
+        
         if sender.state == .began {
         if sender.state == .began {
             suspendPlay()
             suspendPlay()
         } else if sender.state == .changed {
         } else if sender.state == .changed {
@@ -357,7 +754,7 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
             guard center.x > limitMinCenterX, center.x < limitMaxCenterX else {
             guard center.x > limitMinCenterX, center.x < limitMaxCenterX else {
                 // 越界,拖不动了, 最少10s
                 // 越界,拖不动了, 最少10s
                 if endDuration - startDuration < 11 {
                 if endDuration - startDuration < 11 {
-                    THUD.toast("No less than 10s".localized())
+                    TSToastShared.showToast(text:"No less than 10s".localized)
                 }
                 }
                 return
                 return
             }
             }
@@ -366,7 +763,7 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
             previousRightX = dragRightView.centerX
             previousRightX = dragRightView.centerX
             autoPlayAfterMove()
             autoPlayAfterMove()
         }
         }
-
+        
         // 边界校验
         // 边界校验
         if dragRightView.centerX > limitMaxCenterX {
         if dragRightView.centerX > limitMaxCenterX {
             dragRightView.centerX = limitMaxCenterX
             dragRightView.centerX = limitMaxCenterX
@@ -374,228 +771,218 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
         if dragRightView.centerX < limitMinCenterX {
         if dragRightView.centerX < limitMinCenterX {
             dragRightView.centerX = limitMinCenterX
             dragRightView.centerX = limitMinCenterX
         }
         }
-
+        
         let position = dragRightView.centerX - trackContentView.x
         let position = dragRightView.centerX - trackContentView.x
-        trackView?.updateRightCroppedPosition(position)
+        trackView.updateRightCroppedPosition(position)
         endCropRate = position / trackContentView.width
         endCropRate = position / trackContentView.width
     }
     }
-
+    
     @IBAction func buttonClick(_ sender: UIButton) {
     @IBAction func buttonClick(_ sender: UIButton) {
-        guard let model = ringModel else { return }
-
-        switch sender {
-        case playButton:
-            startPlay()
-        case undoButton:
-            player.pause()
-            operationCache.removeLast()
-            ringModel = operationCache.last
-            reloadTrackView()
-        case cutButton:
-            startCutAudio { [weak self] newModel, errMsg in
-                DispatchQueue.main.async {
-                    if let newModel = newModel {
-                        self?.operationCache.append(newModel)
-                        self?.ringModel = newModel
-                        self?.reloadTrackView()
-                    } else {
-                        THUD.toast(errMsg ?? "Sorry, Edit Failure".localized())
-                    }
-                }
-            }
-        case doneButton:
-            player.pause()
-            startCutAudio { [weak self] result, errMsg in
-                DispatchQueue.main.async {
-                    if let ringModel = result {
-                        THUD.showLoading()
-                        RingDownloadManager.shared.shareBand(with: ringModel) { _ in
-                            DispatchQueue.main.async {
-                                THUD.hide()
-                            }
-                        }
-                    } else {
-                        THUD.toast(errMsg ?? "Sorry, Edit Failure".localized())
-                    }
-                }
-            }
-        case nameButton:
-            let alertVC = UIAlertController(title: nil, message: "Ringtone Name".localized(), preferredStyle: .alert)
-            alertVC.addTextField { textField in
-                textField.placeholder = "input name".localized()
-                textField.font = UIFont.systemFont(ofSize: 16)
-                textField.text = self.ringModel?.title
-                self.nameInputTextField = textField
-            }
-            let ok = UIAlertAction(title: "OK".localized(), style: .default) { [weak self] _ in
-                self?.ringModel?.title = self?.nameInputTextField?.text
-                self?.nameLabel.text = self?.ringModel?.title
-            }
-            let cancel = UIAlertAction(title: "Cancel".localized(), style: .cancel)
-            alertVC.addAction(cancel)
-            alertVC.addAction(ok)
-            present(alertVC, animated: true) {
-                self.nameInputTextField?.becomeFirstResponder()
-            }
-        default:
-            break
-        }
+        let model = ringModel
+        
+        //        switch sender {
+        //        case playButton:
+        //            startPlay()
+        //        case undoButton:
+        //            player.pause()
+        //            operationCache.removeLast()
+        //            ringModel = operationCache.last
+        //            reloadTrackView()
+        //        case cutButton:
+        //            startCutAudio { [weak self] newModel, errMsg in
+        //                DispatchQueue.main.async {
+        //                    if let newModel = newModel {
+        //                        self?.operationCache.append(newModel)
+        //                        self?.ringModel = newModel
+        //                        self?.reloadTrackView()
+        //                    } else {
+        //                        TSToastShared.showToast(text:errMsg ?? "Sorry, Edit Failure".localized)
+        //                    }
+        //                }
+        //            }
+        //        case doneButton:
+        //            player.pause()
+        //            startCutAudio { [weak self] result, errMsg in
+        //                guard let self = self else { return }
+        //                DispatchQueue.main.async {
+        //                    if let ringModel = result {
+        //                        TSToastShared.showLoading(containerView: self.view)
+        ////                        RingDownloadManager.shared.shareBand(with: ringModel) { _ in
+        ////                            DispatchQueue.main.async {
+        ////                                TSToastShared.hideLoading()
+        ////                            }
+        ////                        }
+        //                    } else {
+        //                        TSToastShared.showToast(text:errMsg ?? "Sorry, Edit Failure".localized)
+        //                    }
+        //                }
+        //            }
+        
+        //        default:
+        //            break
+        //        }
     }
     }
-
+    
     // 拖拽,暂停
     // 拖拽,暂停
     func suspendPlay() {
     func suspendPlay() {
-        if player.state == .playing {
+        if player.isPlaying {
             isSuspendByAction = true
             isSuspendByAction = true
             player.pause()
             player.pause()
         }
         }
     }
     }
-
+    
     // 停止拖拽,继续播放
     // 停止拖拽,继续播放
     func autoPlayAfterMove() {
     func autoPlayAfterMove() {
-//        if isSuspendByAction {
         startPlay()
         startPlay()
         isSuspendByAction = false
         isSuspendByAction = false
-//        }
     }
     }
-
+    
     func startPlay() {
     func startPlay() {
-        guard let model = ringModel else { return }
-
+        let model = ringModel
+        
         playButton.isSelected = !playButton.isSelected
         playButton.isSelected = !playButton.isSelected
-        if player.state == .playing {
+        if player.isPlaying {
             player.pause()
             player.pause()
         } else {
         } else {
-            if let playURL = model.playURL {
-                player.play(playURL)
-                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-                    self.player.seek(self.startDuration)
+            
+            player.playUrlString(model.audioUrl,localURL: editOriginalURL)
+//            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                self.player.seek(to:self.startDuration)
+//            }
+            player.setVolume(volume: fadeInDuration > 0 ? 0 : maxVolume)
+
+            if UIApplication.getSystemVolume() < 0.1 {
+                TSToastShared.showToast(text:"Please turn up the volume".localized)
+            }
+            
+        }
+    }
+    
+    func saveButtonClick() {
+        player.pause()
+        // 保存音频
+        startCutAudio { result, errMsg,savePath in
+            if let ringModel = result {
+                // 裁剪的音频,使用本地文件播放,清空网络url
+                TSMineRintoneHistory.shared.saveModel(model: ringModel)
+                DispatchQueue.main.async {
+                    self.pop()
+                    if let window = WindowHelper.getKeyWindow() {
+                        kSaveSuccesswShared.show(atView:window,text: "Saved in “My Ringtone”".localized) { [weak self]  in
+                            guard let self = self else { return }
+                            if let vc = WindowHelper.getCurrentViewController(){
+                                kPushVC(target: vc, modelVC: TSRingDownVC())
+                            }
+                        }
+                    }
                 }
                 }
-                player.set(volum: fadeInDuration > 0 ? 0 : 1)
-
-                if UIApplication.getSystemVolume() < 0.1 {
-                    THUD.toast("Please turn up the volume".localized(), shake: true)
+            } else {
+                DispatchQueue.main.async {
+                    TSToastShared.showToast(text: errMsg ?? "Sorry, Save Failure".localized)
                 }
                 }
             }
             }
         }
         }
     }
     }
-
-    // 裁剪
-    func startCutAudio(completion: ((RingModel?, String?) -> Void)?) {
-        guard let model = ringModel,
-              let filePath = RingDownloadManager.shared.ringFilePath(for: model),
-              let copyModel = model.copy() as? RingModel else {
-            completion?(nil, nil)
-            return
-        }
-        let url = URL(fileURLWithPath: filePath)
-        let asset = AVAsset(url: url)
-
-        let formatter = DateFormatter()
-        formatter.dateFormat = "yyyyMMddHHmmss"
-
-        var editName = model.title ?? formatter.string(from: Date())
-        editName = sanitizeFilePath(editName)
-        if !url.pathExtension.isEmpty {
-            editName.append(".\(url.pathExtension)")
+    
+    func setButtonClick() {
+        player.pause()
+        // 保存音频
+        startCutAudio { result, errMsg,savePath in
+            if let ringModel = result,let savePath = savePath {
+                // 裁剪的音频,使用本地文件播放,清空网络url
+                TSMineRintoneHistory.shared.saveModel(model: ringModel)
+                _ = kPurchaseToolShared.kshareBand(needVip: false, vc: self, fileURL:savePath, fileName: ringModel.title){ success in
+                    if success {
+                        TSMineRintoneHistory.shared.saveModel(model: ringModel)
+                    }
+                }
+            } else {
+                DispatchQueue.main.async {
+                    TSToastShared.showToast(text: errMsg ?? "Sorry, Save Failure".localized)
+                }
+            }
         }
         }
-
-        guard let savePath = RingDownloadManager.shared.getCopyToPath(with: editName) else {
-            completion?(nil, nil)
+    }
+    
+    // 裁剪
+    func startCutAudio(completion: ((TSRingModel?, String?,URL?) -> Void)?) {
+        guard let copyModel = ringModel.copy() as? TSRingModel else {
+            completion?(nil, nil, nil)
             return
             return
         }
         }
+       
+    
+       let savePath = TSDownloadManager.generateRingSaveLocalURL(name: copyModel.title)
 
 
-        copyModel.duration = endDuration - startDuration
-        THUD.showLoading()
-        audioTool.startTansformAudio(url: url.path, from: startDuration, to: endDuration, fadeIn: Double(fadeInDuration), fadeOut: Double(fadeOutDuration), savePath: savePath) { filePath, errMsg in
+        copyModel.duration = Int(endDuration - startDuration)
+        TSRingLoadingView.shared.showWindow()
+        audioTool.startTansformAudio(url:editOriginalURL.path, from: startDuration, to: endDuration, fadeIn: Double(fadeInDuration), fadeOut: Double(fadeOutDuration),addVolume: Double(outputVolume) ,savePath: savePath.path) { filePath, errMsg in
             DispatchQueue.main.async {
             DispatchQueue.main.async {
-                THUD.hide()
+                TSRingLoadingView.shared.remove()
             }
             }
             if let filePath = filePath {
             if let filePath = filePath {
                 let url = URL(fileURLWithPath: filePath)
                 let url = URL(fileURLWithPath: filePath)
-                copyModel.title = url.deletingPathExtension().lastPathComponent.removingPercentEncoding
-                copyModel.fileName = url.lastPathComponent
-                copyModel.size = FileManager.default.getFileSize(url) ?? copyModel.size
-                completion?(copyModel, errMsg)
+                copyModel.documentPath = filePath.documentLastURLString
+                
+                if let fileInfo = TSBusinessAudioPlayer.getAudioFileInfo(path: url.path) {
+                    if let size = fileInfo.sizeInBytes {
+                        copyModel.size = Int(size)
+                    }
+                    if let duration = fileInfo.durationInSeconds {
+                        copyModel.duration = Int(duration)
+                    }
+                }
+    
+                completion?(copyModel, errMsg, savePath)
             } else {
             } else {
-                completion?(nil, errMsg)
+                completion?(nil, errMsg, nil)
             }
             }
         }
         }
     }
     }
+    
+}
+extension TSEditAudioVideoBaseVC {
+    func handlePlayer(state: TSBusinessAudioPlayer.PlayerState) {
+        playButton.isSelected = player.isPlaying
+        progressLine.isHidden = !playButton.isSelected
+        if player.currentPlayerState == .play {
+            progressLine.centerX = dragLeftView.centerX
+        }
+    }
 
 
-    func saveButtonClick() {
-        guard let current = ringModel else { return }
-//        guard operationCache.count > 1 else {
-//            // 只有原音频,意味着未进行裁剪操作,提示
-//            let alertVC = UIAlertController(title: nil, message: "No audio can be saved. Whether to save the original audio?", preferredStyle: .alert)
-//            alertVC.addAction(UIAlertAction(title: "Cancel", style: .cancel))
-//            alertVC.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in
-//                RingDownloadManager.shared.saveEdited(ring: current)
-//                self?.dismiss(animated: true)
-//            }))
-//            present(alertVC, animated: true)
-//            return
-//        }
+    func handlePlayer(progressChanged current: Double, total: Double) {
+        let range = endDuration - startDuration
+        let rangeWidth = trackContentView.width * range / total
+        let progress = max(0, current - startDuration) / range
 
 
-        player.pause()
-        let dismissHandler: (() -> Void)? = { [weak self] in
-            self?.dismiss(animated: true, completion: {
-                /// 编辑完也需要小红点提示
-                RingDownloadManager.shared.hasUnreadRing = true
-                NotificationCenter.default.post(name: .downloadUnreadStateChanged, object: nil)
+        if player.isPlaying {
+            progressLine.centerX = dragLeftView.centerX + progress * rangeWidth
+        }
 
 
-                if let _ = UIApplication.topViewController as? MineRingsViewController {
-                    THUD.toast("Saved successfully".localized(), shake: true)
-                    return
-                }
-                SaveSuccessTipsView.show(at: UIApplication.rootViewController?.topController.view) {
-                    guard let topVC = UIApplication.topViewController else {
-                        return
-                    }
-                    let mineVC = MineRingsViewController()
-                    mineVC.currentType = .edited
-                    topVC.navigationController?.pushViewController(mineVC, animated: true)
-                }
-            })
+        if current >= endDuration {
+            player.pause()
         }
         }
 
 
-//        // 未编辑, 不需要裁剪
-//        guard startCropRate != 0 || endCropRate != 1.0
-//                || fadeInDuration != 0 || fadeOutDuration != 0 else {
-//            // 修改名称,直接保存
-//            RingDownloadManager.shared.saveEdited(ring: current)
-//            dismissHandler?()
-//            return
-//        }
+        // 淡入
+        if fadeInDuration > 0, current - startDuration < Double(fadeInDuration) {
+            let fadeProgress = Float(current - startDuration) / Float(fadeInDuration)
+            let newVolume = min(fadeProgress * maxVolume * 100, 100.0)
+            print("---volume: \(newVolume)")
+            player.setVolume(volume:newVolume / 100)
+        }
 
 
-        // 保存音频
-        startCutAudio { result, errMsg in
-            if let ringModel = result {
-                // 裁剪的音频,使用本地文件播放,清空网络url
-                ringModel.fileUrl = nil
-                RingDownloadManager.shared.saveEdited(ring: ringModel)
-                DispatchQueue.main.async {
-                    dismissHandler?()
-                }
-            } else {
-                DispatchQueue.main.async {
-                    THUD.toast(errMsg ?? "Sorry, Save Failure".localized())
-                }
-            }
+        // 淡出
+        if fadeOutDuration > 0, current > (endDuration - Double(fadeOutDuration)) {
+            let fadeProgress = Float(endDuration - current) / Float(fadeOutDuration)
+            let newVolume = max(fadeProgress * maxVolume * 100, 0.0)
+            print("---volume: \(newVolume)")
+            player.setVolume(volume: newVolume / 100)
         }
         }
     }
     }
 
 
-    override func navigationBarItemClick(_ type: NavigationBarAction) {
-        if case .back = type {
-            guard operationCache.count <= 1 else {
-                let alertVC = UIAlertController(title: nil, message: "As you leave, any changes you have made will not be saved.".localized(), preferredStyle: .alert)
-                alertVC.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel))
-                alertVC.addAction(UIAlertAction(title: "Leave".localized(), style: .default, handler: { [weak self] _ in
-                    self?.dismiss(animated: true)
-                }))
-                present(alertVC, animated: true)
-                return
-            }
-            dismiss(animated: true)
-        }
+    func sanitizeFilePath(_ path: String) -> String {
+        let illegalFileNameCharacters = CharacterSet(charactersIn: "/\\?%*|\"<>: ")
+        let sanitizedPath = path.components(separatedBy: illegalFileNameCharacters).joined(separator: "_")
+        return sanitizedPath
     }
     }
 }
 }

+ 13 - 3
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift

@@ -189,13 +189,23 @@ extension TSThemeBrowseVC {
     
     
     func setUpRingtone() {
     func setUpRingtone() {
         if let ringtone = currentModel?.ringtone {
         if let ringtone = currentModel?.ringtone {
-            TSCommonTool.downloadAndCacheFile(from: ringtone) { path, error in
-                if let path = path {
-                    self.currentRingtone = path
+            
+            _ = TSDownloadManager.downloadFile(urlString: ringtone,missingEx: "mp3") {[weak self] url, error in
+                guard let self = self else { return }
+                if let path = url {
+                    self.currentRingtone = path.path
                 }else{
                 }else{
                     self.currentRingtone = nil
                     self.currentRingtone = nil
                 }
                 }
+                
             }
             }
+//            TSCommonTool.downloadAndCacheFile(from: ringtone) { path, error in
+//                if let path = path {
+//                    self.currentRingtone = path
+//                }else{
+//                    self.currentRingtone = nil
+//                }
+//            }
         }else{
         }else{
             self.currentRingtone = nil
             self.currentRingtone = nil
         }
         }

+ 19 - 3
AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift

@@ -143,7 +143,23 @@ extension TSThemeSetVC {
     @objc func clickSetRing(){
     @objc func clickSetRing(){
         audioPlayer.stop()
         audioPlayer.stop()
 //        if kPurchaseToolShared.kJudgeVip(externalBool: getNeedVip, vc: self){ return }//判断 vip
 //        if kPurchaseToolShared.kJudgeVip(externalBool: getNeedVip, vc: self){ return }//判断 vip
-        _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, urlString: model.ringtone, fileName:  model.name)
+        
+        
+        if let path = TSDownloadManager.getRingLocalURL(urlString: model.ringtone) {
+            _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:  model.name)
+        }else{
+            TSRingLoadingView.shared.showWindow()
+            _ = TSDownloadManager.downloadFile(urlString:  model.ringtone,missingEx: "mp3") {[weak self] url, error in
+                guard let self = self else { return }
+                if let path = url {
+//                  let savePath = TSBusinessFileManager.saveRingPathURL.appendingPathComponent(path.lastPathComponent)
+//                  TSFileManagerTool.copyFileWithOverwrite(from: path, to: savePath)
+                    _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, fileURL: path, fileName:model.name)
+                }
+                
+                TSRingLoadingView.shared.remove()
+            }
+        }
         
         
     }
     }
     
     
@@ -154,7 +170,7 @@ extension TSThemeSetVC {
         if let image = posterView.netWorkImageView.image{
         if let image = posterView.netWorkImageView.image{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
                 if success {
-                    kSavePhotoSuccesswShared.show(atView: self.view)
+                    kSaveSuccesswShared.show(atView: self.view)
                 }else{
                 }else{
                     debugPrint(error)
                     debugPrint(error)
                 }
                 }
@@ -171,7 +187,7 @@ extension TSThemeSetVC {
         guard let photo = avatarImage else { return }
         guard let photo = avatarImage else { return }
         contactsTool.setContactsAvatar(avatarImage: photo) { data, error in
         contactsTool.setContactsAvatar(avatarImage: photo) { data, error in
             if error == nil {
             if error == nil {
-                kSavePhotoSuccesswShared.show(atView: self.view,showViewBtn: false)
+                kSaveSuccesswShared.show(atView: self.view,showViewBtn: false)
             }
             }
         }
         }
     }
     }

+ 50 - 8
AIRingtone/Business/VIewTool/TSButton.swift

@@ -19,7 +19,6 @@ class TSNormalSubmitBtn: TSAppBtn {
     }
     }
 }
 }
 
 
-
 class TSNormalCancelBtn: TSAppBtn {
 class TSNormalCancelBtn: TSAppBtn {
 
 
 }
 }
@@ -41,7 +40,9 @@ class TSAppBtnView: TSBaseView {
         case normal
         case normal
         case generate   //创造类的按钮
         case generate   //创造类的按钮
         case themeSet   //主题页设置按钮
         case themeSet   //主题页设置按钮
-        
+    
+        case normalSet   //普通的设置按钮
+        case normalBorder   //普通的边框
     }
     }
     
     
     var viewH:CGFloat = 64
     var viewH:CGFloat = 64
@@ -52,7 +53,7 @@ class TSAppBtnView: TSBaseView {
         }
         }
     }
     }
     var clickBlock:(()->Void)?
     var clickBlock:(()->Void)?
-    
+    var btnFrame:CGRect?
     //###################################### Button ######################################
     //###################################### Button ######################################
     var button:UIButton = UIButton()
     var button:UIButton = UIButton()
 
 
@@ -64,8 +65,8 @@ class TSAppBtnView: TSBaseView {
         
         
     }
     }
 
 
-    func setUpButton(style:ViewStyle,vipFreeNumType:VipFreeNumType,clickBlock: @escaping () -> Void) {
-        
+    func setUpButton(style:ViewStyle,vipFreeNumType:VipFreeNumType = .none,btnFrame:CGRect? = nil,clickBlock: @escaping () -> Void) {
+        self.btnFrame = btnFrame
         self.style = style
         self.style = style
         self.vipFreeNumType = vipFreeNumType
         self.vipFreeNumType = vipFreeNumType
         self.clickBlock = clickBlock
         self.clickBlock = clickBlock
@@ -78,6 +79,10 @@ class TSAppBtnView: TSBaseView {
             launchVipLogic()
             launchVipLogic()
         case .themeSet:
         case .themeSet:
             setUpThemeSet()
             setUpThemeSet()
+        case .normalSet:
+            setUpNormalSet()
+        case .normalBorder:
+            setUpNormalBorder()
         default:
         default:
             break
             break
         }
         }
@@ -156,10 +161,26 @@ extension TSAppBtnView{
     }
     }
     
     
     func setUpThemeSet() {
     func setUpThemeSet() {
-        button = kCreateNormalSubmitBtn(title: "Set Now".localized, frame: CGRectMake(0, 0, 200, 48),action: { [weak self]  in
+        button = kCreateNormalSubmitBtn(title: "Set Now".localized, frame: self.btnFrame ?? CGRectMake(0, 0, 200, 48),action: { [weak self]  in
+            guard let self = self else { return }
+            clickBlock?()
+        })
+        contentView.addSubview(button)
+        button.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.equalTo(button.width)
+            make.height.equalTo(button.height)
+        }
+   
+    }
+    
+    func setUpNormalSet() {
+        let frame = self.btnFrame ?? CGRectMake(0, 0, 233, 48)
+        button = kCreateNormalSubmitBtn(title: "Set Now".localized, frame: frame,action: { [weak self]  in
             guard let self = self else { return }
             guard let self = self else { return }
             clickBlock?()
             clickBlock?()
         })
         })
+        button.titleLabel?.numberOfLines  = 3
         contentView.addSubview(button)
         contentView.addSubview(button)
         button.snp.makeConstraints { make in
         button.snp.makeConstraints { make in
             make.center.equalToSuperview()
             make.center.equalToSuperview()
@@ -168,6 +189,27 @@ extension TSAppBtnView{
         }
         }
    
    
     }
     }
+    
+    func setUpNormalBorder() {
+        
+        let textColor = UIColor.themeColor
+        let frame = self.btnFrame ?? CGRectMake(0, 0, 98, 48)
+        button = UIButton.createButton(backgroundColor: "#171717".uiColor,font: UIFont.font(size: 16,weight: .regular),titleColor:textColor,corner: frame.size.height/2){ [weak self]  in
+            guard let self = self else { return }
+            clickBlock?()
+        }
+        button.frame = frame
+        button.layer.borderWidth = 1
+        button.layer.borderColor = textColor.cgColor
+
+
+        contentView.addSubview(button)
+        button.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.equalTo(button.width)
+            make.height.equalTo(button.height)
+        }
+    }
 }
 }
 //创造按钮
 //创造按钮
 extension TSAppBtnView{
 extension TSAppBtnView{
@@ -186,9 +228,9 @@ extension TSAppBtnView{
 //常用提交按钮
 //常用提交按钮
 func kCreateNormalSubmitBtn(title:String,frame:CGRect,action: (() -> Void)? = nil) -> UIButton {
 func kCreateNormalSubmitBtn(title:String,frame:CGRect,action: (() -> Void)? = nil) -> UIButton {
     let btn = TSNormalSubmitBtn()
     let btn = TSNormalSubmitBtn()
-    btn.setUpButton(title:title,font: UIFont.font(size: 16,weight: .regular),titleColor:.white,corner: 24,action: action)
-    btn.setTitleImageSpace(spacing: 4)
     btn.frame = frame
     btn.frame = frame
+    btn.setUpButton(title:title,font: UIFont.font(size: 16,weight: .regular),titleColor:.white,corner: frame.height/2,action: action)
+    btn.setTitleImageSpace(spacing: 4)
     btn.addGradientBg(colors: ["#E961F6".uiColor.cgColor,"#7E57F4".uiColor.cgColor])
     btn.addGradientBg(colors: ["#E961F6".uiColor.cgColor,"#7E57F4".uiColor.cgColor])
     return btn
     return btn
 }
 }

+ 13 - 0
AIRingtone/Business/VIewTool/TSRingLoadingView.swift

@@ -9,6 +9,19 @@ import Kingfisher
 
 
 class TSRingLoadingView: TSBaseView {
 class TSRingLoadingView: TSBaseView {
     
     
+    static let shared = TSRingLoadingView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight))
+    
+    func showWindow(){
+        if let window = WindowHelper.getKeyWindow() {
+            window.addSubview(TSRingLoadingView.shared)
+            TSRingLoadingView.shared.isRotating = true
+        }
+    }
+    
+    func remove(){
+        TSRingLoadingView.shared.removeFromSuperview()
+    }
+    
     lazy var animatedImageView: AnimatedImageView = {
     lazy var animatedImageView: AnimatedImageView = {
         let animatedImageView = AnimatedImageView()
         let animatedImageView = AnimatedImageView()
         animatedImageView.autoPlayAnimatedImage = false
         animatedImageView.autoPlayAnimatedImage = false

+ 39 - 3
AIRingtone/Business/VIewTool/TSRingToneCellView.swift

@@ -26,7 +26,29 @@ class TSRingToneCellView: TSBaseView {
     let nameLab = UILabel.createLabel(text: "--",font: .font(size: 14),textColor: .white)
     let nameLab = UILabel.createLabel(text: "--",font: .font(size: 14),textColor: .white)
     let timeLab = UILabel.createLabel(text:"--:--",font: .font(size: 12),textColor: .white.withAlphaComponent(0.6))
     let timeLab = UILabel.createLabel(text:"--:--",font: .font(size: 12),textColor: .white.withAlphaComponent(0.6))
     
     
-
+ 
+    var clickSetUpHandel:(()->Void)?
+    lazy var setRingBtn: TSUIExpandedTouchButton = {
+        let setRingBtn = TSUIExpandedTouchButton()
+        setRingBtn.setUpButton(image: UIImage(named: "ai_setRing_icon")){ [weak self]  in
+            guard let self = self else { return }
+            clickSetUpHandel?()
+        }
+        setRingBtn.isHidden = true
+        return setRingBtn
+    }()
+    
+    var clickEditHandel:(()->Void)?
+    lazy var editBtn: TSUIExpandedTouchButton = {
+        let editBtn = TSUIExpandedTouchButton()
+        editBtn.setUpButton(image: UIImage(named: "edit_ring_icon")){ [weak self]  in
+            guard let self = self else { return }
+            clickEditHandel?()
+        }
+        editBtn.isHidden = true
+        return editBtn
+    }()
+    
     lazy var clearBtn = UIButton.createButton{[weak self]  in
     lazy var clearBtn = UIButton.createButton{[weak self]  in
         guard let self = self else { return }
         guard let self = self else { return }
         clickPlayHandel?(self.isPlay)
         clickPlayHandel?(self.isPlay)
@@ -87,7 +109,7 @@ class TSRingToneCellView: TSBaseView {
 
 
         nameLab.snp.makeConstraints { make in
         nameLab.snp.makeConstraints { make in
             make.leading.equalTo(76)
             make.leading.equalTo(76)
-            make.trailing.equalTo(-60)
+            make.trailing.equalTo(-86)
             make.top.equalTo(17)
             make.top.equalTo(17)
         }
         }
         
         
@@ -110,6 +132,20 @@ class TSRingToneCellView: TSBaseView {
             make.leading.top.bottom.equalToSuperview()
             make.leading.top.bottom.equalToSuperview()
             make.trailing.equalTo(0)
             make.trailing.equalTo(0)
         }
         }
+        
+        contentView.addSubview(setRingBtn)
+        setRingBtn.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(-16)
+            make.width.height.equalTo(20)
+        }
+        
+        contentView.addSubview(editBtn)
+        editBtn.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(setRingBtn.snp.leading).offset(-20)
+            make.width.height.equalTo(20)
+        }
     }
     }
     
     
     
     
@@ -162,7 +198,7 @@ class TSRingToneGenerateView:TSBaseView {
     
     
     
     
     var refreshHandel:(()->Void)?
     var refreshHandel:(()->Void)?
-    
+
     lazy var generateProgressView: UIImageView = {
     lazy var generateProgressView: UIImageView = {
         let generateProgressView = UIImageView.createImageView(imageName: "ringGenerateProgress",contentMode: .scaleToFill)
         let generateProgressView = UIImageView.createImageView(imageName: "ringGenerateProgress",contentMode: .scaleToFill)
         return generateProgressView
         return generateProgressView

+ 18 - 11
AIRingtone/Business/VIewTool/TSSavePhotoSuccessTool.swift

@@ -1,14 +1,16 @@
 //
 //
-//  TSSavePhotoSuccessTool.swift
+//  TSSaveSuccessTool.swift
 //  AIRingtone
 //  AIRingtone
 //
 //
 //  Created by 100Years on 2025/3/5.
 //  Created by 100Years on 2025/3/5.
 //
 //
 
 
-let kSavePhotoSuccesswShared = TSSavePhotoSuccessTool.shared
-class TSSavePhotoSuccessTool {
+let kSaveSuccesswShared = TSSaveSuccessTool.shared
+class TSSaveSuccessTool {
     
     
-    static let shared = TSSavePhotoSuccessTool()
+    static let shared = TSSaveSuccessTool()
+    
+    var clickViewHandle:(()->Void)?
     
     
     private lazy var textLabel:UILabel = {
     private lazy var textLabel:UILabel = {
         let textLabel = UILabel()
         let textLabel = UILabel()
@@ -25,11 +27,16 @@ class TSSavePhotoSuccessTool {
     
     
     private lazy var viewButton:UIView = {
     private lazy var viewButton:UIView = {
         let color = "4FEA9D".uiColor
         let color = "4FEA9D".uiColor
-        let viewButton = UIButton.createButton(title: "View".localized ,backgroundColor: color.withAlphaComponent(0.1),font: UIFont.font(size: 14),titleColor: color,corner: 14) {
-            if let url = URL(string: "photos-redirect://") {
-                if UIApplication.shared.canOpenURL(url) {
-                    UIApplication.shared.open(url, options: [:], completionHandler: nil)
-                    playVibration()
+        let viewButton = UIButton.createButton(title: "View".localized ,backgroundColor: color.withAlphaComponent(0.1),font: UIFont.font(size: 14),titleColor: color,corner: 14) { [weak self]  in
+            guard let self = self else { return }
+            if let clickViewHandle =  clickViewHandle {
+                clickViewHandle()
+            }else {
+                if let url = URL(string: "photos-redirect://") {
+                    if UIApplication.shared.canOpenURL(url) {
+                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
+                        playVibration()
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -86,8 +93,8 @@ class TSSavePhotoSuccessTool {
 
 
     
     
     
     
-    func show(atView:UIView,text:String = "Save Successfully".localized,showViewBtn:Bool = true) {
-        
+    func show(atView:UIView,text:String = "Save Successfully".localized,showViewBtn:Bool = true,clickViewHandle:(()->Void)? = nil) {
+        self.clickViewHandle = clickViewHandle
         kExecuteOnMainThread {
         kExecuteOnMainThread {
             self.textLabel.text = text
             self.textLabel.text = text
             self.viewButton.isHidden = !showViewBtn
             self.viewButton.isHidden = !showViewBtn

+ 31 - 34
AIRingtone/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -83,40 +83,8 @@ extension TSNetworkManager {
     func removeCache(urlType: TSNeURLType) {
     func removeCache(urlType: TSNeURLType) {
         removeCache(urlString: urlType.getUrlString())
         removeCache(urlString: urlType.getUrlString())
     }
     }
-//    /// 通用 POST Stream 请求
-//    /// - Parameters:
-//    ///   - endpoint: 接口路径
-//    ///   - parameters: 请求参数
-//    ///   - responseType: 响应数据模型(可选)
-//    ///   - completion: 请求完成的回调
-//    func postStream<T: TSBaseModel>(
-//        urlType: TSNeURLType,
-//        parameters: [String: Any]? = nil,
-//        responseType: T.Type? = nil,
-//        streamHandler:@escaping (String) -> Void,
-//        completion: @escaping (Result<Any, Error>) -> Void
-//    ) -> StreamPostRequest{
-//        let urlString = urlType.getUrlString()
-//        
-//        let streamRequest = StreamPostRequest(url: URL(string: urlString)!, parameters: parameters) { data in
-//            if let string = String(data: data, encoding: .utf8) {
-//                print("Received chunk: \(string)")
-//                streamHandler(string)
-//            }
-//        } completionHandler: { error in
-//            if let error = error {
-//                print("Request failed with error: \(error)")
-//                completion(.failure(error))
-//            } else {
-//                print("Request completed successfully.")
-//                completion(.success("success"))
-//            }
-//        }
-//        streamRequest.startRequest()
-//        return streamRequest
-//    }
-    
-    
+
+
     /// 通用 POST Stream 请求
     /// 通用 POST Stream 请求
     /// - Parameters:
     /// - Parameters:
     ///   - endpoint: 接口路径
     ///   - endpoint: 接口路径
@@ -162,6 +130,35 @@ extension TSNetworkManager {
         },completion: completion)
         },completion: completion)
         return request
         return request
     }
     }
+    
+    
+    func downloadFile(
+        urlString: String,
+        to destination: URL,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (URL?, Error?) -> Void
+    ) -> DownloadRequest? {
+        let request = self.downloadFile(
+            urlString: urlString,
+            to: destination,
+            progressHandler: { progress in
+                print("下载进度: \(progress * 100)%")
+                progressHandler?(progress)
+            },
+            completion: { result in
+                switch result {
+                case .success(let fileURL):
+                    dePrint("下载完成,文件保存在: \(fileURL.path)")
+                    completion(fileURL,nil)
+                case .failure(let error):
+                    dePrint("下载失败: \(error.localizedDescription)")
+                    completion(nil,error)
+                }
+            }
+        )
+        return request
+    }
+    
 
 
 }
 }
 
 

+ 65 - 0
AIRingtone/Common/NetworkManager/TSNetWork/TSNetworkManager.swift

@@ -306,6 +306,71 @@ extension TSNetworkManager {
         
         
         return request
         return request
     }
     }
+    
+    
+    /// 下载文件
+    /// - Parameters:
+    ///   - url: 下载URL
+    ///   - destination: 目标保存路径 (可选,不传则使用临时目录)
+    ///   - progressHandler: 进度回调 (0.0~1.0)
+    ///   - completion: 完成回调 (返回文件URL或错误)
+    func downloadFile(
+        urlString: String,
+        to destination: URL? = nil,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (Result<URL, Error>) -> Void
+    ) -> DownloadRequest? {
+        
+        
+        guard let url = URL(string: urlString) else {
+            completion(.failure(NSError(domain: "url nil", code: 0)))
+            return nil
+        }
+        
+        // 设置下载目标路径
+        let destination: DownloadRequest.Destination = { temporaryURL, response in
+            // 如果用户指定了目标路径
+            if let destination = destination {
+                // 确保目录存在
+                let directory = destination.deletingLastPathComponent()
+                try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
+                return (destination, [.removePreviousFile, .createIntermediateDirectories])
+            }
+            
+            // 否则使用临时目录
+            let documentsURL = FileManager.default.temporaryDirectory
+            let suggestedFilename = response.suggestedFilename ?? url.lastPathComponent
+            let fileURL = documentsURL.appendingPathComponent(suggestedFilename)
+            
+            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
+        }
+        
+        // 开始下载
+        let request = AF.download(url, to: destination)
+            .downloadProgress { progress in
+                // 主线程回调进度
+                DispatchQueue.main.async {
+                    progressHandler?(progress.fractionCompleted)
+                }
+            }
+            .response { response in
+                // 主线程回调结果
+                DispatchQueue.main.async {
+                    switch response.result {
+                    case .success(let fileURL):
+                        if let fileURL = fileURL {
+                            completion(.success(fileURL))
+                        } else {
+                            completion(.failure(NSError(domain: "DownloadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "文件路径无效"])))
+                        }
+                    case .failure(let error):
+                        completion(.failure(error))
+                    }
+                }
+            }
+        
+        return request
+    }
 
 
 }
 }
 
 

+ 2 - 2
AIRingtone/Common/Purchase/TSPurchaseManager/TSPurchaseTool.swift

@@ -142,7 +142,7 @@ extension TSPurchaseTool{
     
     
     func kshareBand(needVip:Bool,
     func kshareBand(needVip:Bool,
                    vc:UIViewController,
                    vc:UIViewController,
-                    urlString:String,
+                    fileURL:URL,
                     fileName:String,
                     fileName:String,
                     completion:((Bool)->Void)? = nil) -> Bool {
                     completion:((Bool)->Void)? = nil) -> Bool {
         //判断 vip
         //判断 vip
@@ -152,7 +152,7 @@ extension TSPurchaseTool{
             TSPurchaseVC.show(target: vc, closePageBlock: nil)
             TSPurchaseVC.show(target: vc, closePageBlock: nil)
             return true
             return true
         }
         }
-        TSBandRingTool.creatBandRingTool().shareBandVC(vc: vc, fileURLString: urlString, fileName: fileName,completion: completion)
+        TSBandRingTool.creatBandRingTool().shareBandVC(vc: vc, fileURL: fileURL, fileName: fileName,completion: completion)
         return false
         return false
     }
     }
 }
 }

+ 56 - 56
AIRingtone/Common/Tool/NotUse/TSAudioPlayerFileTool.swift

@@ -5,59 +5,59 @@
 //  Created by 100Years on 2025/3/18.
 //  Created by 100Years on 2025/3/18.
 //
 //
 
 
-class TSAudioPlayerFileTool {
-    
-    
-    var downloadOp:DownloadOperation?
-    var urlString:String
-    init(urlString:String) {
-        self.urlString = urlString
-
-    }
-    
-    
-    func playAudio(urlString:String){
-        guard let url = URL(string: urlString) else { return }
-        
-        // 检查本地是否已存在文件
-        if let localFilePath = DownloadManager.localFilePath(for: url) {
-            print("File already exists at: \(localFilePath)")
-            kAudioAVPlayerShared.loadAudio(from: localFilePath)
-            kAudioAVPlayerShared.play()
-        } else {
-            // 开始下载任务
-            downloadOp = DownloadManager.shared.downloadFile(from: url)
-
-            // 设置进度回调
-            downloadOp?.progressHandler = { progress in
-                print("Download progress: \(progress)")
-                
-                kAudioAVPlayerShared.onPlaybackStateChanged?(.loading(progress))
-            }
-
-            // 设置完成回调
-            downloadOp?.completionHandler = { tempURL, error in
-                if let tempURL = tempURL {
-                    print("Download completed, file saved at: \(tempURL)")
-                    kAudioAVPlayerShared.loadAudio(from: tempURL)
-                    kAudioAVPlayerShared.play()
-                    
-                } else if let error = error {
-                    print("Download failed: \(error)")
-                }
-            }
-
-            // 开始下载
-            downloadOp?.startDownload()
-        }
-    }
-    
-    func stopAudio(urlString:String){
-        
-        if let downloadOp = downloadOp {
-            // 暂停下载
-            downloadOp.pause()
-            kAudioAVPlayerShared.stop()
-        }
-    }
-}
+//class TSAudioPlayerFileTool {
+//    
+//    
+//    var downloadOp:DownloadOperation?
+//    var urlString:String
+//    init(urlString:String) {
+//        self.urlString = urlString
+//
+//    }
+//    
+//    
+//    func playAudio(urlString:String){
+//        guard let url = URL(string: urlString) else { return }
+//        
+//        // 检查本地是否已存在文件
+//        if let localFilePath = DownloadManager.localFilePath(for: url) {
+//            print("File already exists at: \(localFilePath)")
+//            kAudioAVPlayerShared.loadAudio(from: localFilePath)
+//            kAudioAVPlayerShared.play()
+//        } else {
+//            // 开始下载任务
+//            downloadOp = DownloadManager.shared.downloadFile(from: url)
+//
+//            // 设置进度回调
+//            downloadOp?.progressHandler = { progress in
+//                print("Download progress: \(progress)")
+//                
+//                kAudioAVPlayerShared.onPlaybackStateChanged?(.loading(progress))
+//            }
+//
+//            // 设置完成回调
+//            downloadOp?.completionHandler = { tempURL, error in
+//                if let tempURL = tempURL {
+//                    print("Download completed, file saved at: \(tempURL)")
+//                    kAudioAVPlayerShared.loadAudio(from: tempURL)
+//                    kAudioAVPlayerShared.play()
+//                    
+//                } else if let error = error {
+//                    print("Download failed: \(error)")
+//                }
+//            }
+//
+//            // 开始下载
+//            downloadOp?.startDownload()
+//        }
+//    }
+//    
+//    func stopAudio(urlString:String){
+//        
+//        if let downloadOp = downloadOp {
+//            // 暂停下载
+//            downloadOp.pause()
+//            kAudioAVPlayerShared.stop()
+//        }
+//    }
+//}

+ 0 - 214
AIRingtone/Common/Tool/NotUse/TSDownloadManager.swift

@@ -1,214 +0,0 @@
-//
-//  TSDownloadManager.swift
-//  AIRingtone
-//
-//  Created by 100Years on 2025/3/18.
-//
-import Foundation
-
-// MARK: - DownloadManager
-/// 下载管理器,负责生成和管理下载任务
-class DownloadManager {
-    static let shared = DownloadManager()
-    
-    private var maxConcurrentDownloads: Int
-    private let downloadQueue: OperationQueue
-    private var activeDownloads: [URL: DownloadOperation] = [:]
-    private var expirationInterval: TimeInterval = 7 * 24 * 60 * 60 // 默认 7 天
-    
-    private init() {
-        self.maxConcurrentDownloads = 3 // 默认最大并发下载数为 3
-        self.downloadQueue = OperationQueue()
-        self.downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads
-    }
-    
-    /// 设置最大并发下载数
-    /// - Parameter count: 最大并发下载数
-    func setMaxConcurrentDownloads(_ count: Int) {
-        maxConcurrentDownloads = count
-        downloadQueue.maxConcurrentOperationCount = count
-    }
-    
-    /// 设置缓存文件的过期时长
-    /// - Parameter interval: 过期时长(秒)
-    func setExpirationInterval(_ interval: TimeInterval) {
-        expirationInterval = interval
-    }
-    
-    /// 创建下载任务
-    /// - Parameter url: 下载文件的 URL
-    /// - Returns: 返回 DownloadOperation,用于控制下载任务
-    func downloadFile(from url: URL) -> DownloadOperation {
-        if let downloadOperation = activeDownloads[url] {
-            return downloadOperation
-        }
-        
-        let downloadOperation = DownloadOperation(url: url)
-        activeDownloads[url] = downloadOperation
-        downloadQueue.addOperation(downloadOperation)
-        return downloadOperation
-    }
-    
-    /// 清理过期的缓存文件
-    func cleanExpiredFiles() {
-        let fileManager = FileManager.default
-        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
-        let expirationDate = Date().addingTimeInterval(-expirationInterval)
-        
-        if let files = try? fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: [.creationDateKey], options: .skipsHiddenFiles) {
-            for file in files {
-                if let attributes = try? fileManager.attributesOfItem(atPath: file.path),
-                   let creationDate = attributes[.creationDate] as? Date,
-                   creationDate < expirationDate {
-                    try? fileManager.removeItem(at: file)
-                }
-            }
-        }
-    }
-    
-    /// 获取本地文件路径(如果存在)
-    /// - Parameter url: 文件的 URL
-    /// - Returns: 本地文件路径(如果存在),否则返回 nil
-    static func localFilePath(for url: URL) -> URL? {
-        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-        let fileName = url.lastPathComponent
-        let fileURL = cacheDirectory.appendingPathComponent(fileName)
-        if FileManager.default.fileExists(atPath: fileURL.path) {
-            return fileURL
-        }
-        return nil
-    }
-    
-    /// 清理所有下载任务
-    func cleanAllTasks() {
-        for (url, downloadOperation) in activeDownloads {
-            downloadOperation.cancel()
-            activeDownloads[url] = nil
-        }
-    }
-    
-    /// 清理某个下载任务
-    /// - Parameter url: 下载任务的 URL
-    func cleanTask(for url: URL) {
-        if let downloadOperation = activeDownloads[url] {
-            downloadOperation.cancel()
-            activeDownloads[url] = nil
-        }
-    }
-}
-
-// MARK: - DownloadOperation
-/// 下载任务,继承自 Operation
-class DownloadOperation: Operation {
-    private let url: URL
-    private var downloadTask: URLSessionDownloadTask?
-    private var resumeData: Data?
-    private var isPaused: Bool = false
-    
-    /// 下载进度回调
-    var progressHandler: ((Double) -> Void)?
-    
-    /// 下载完成回调
-    var completionHandler: ((URL?, Error?) -> Void)?
-    
-    init(url: URL) {
-        self.url = url
-    }
-    
-    /// 开始下载
-    func startDownload() {
-        // 检查本地是否已存在文件
-        if let localFilePath = DownloadManager.localFilePath(for: url) {
-            completionHandler?(localFilePath, nil)
-            return
-        }
-        
-        // 如果没有本地文件,则开始下载
-        let semaphore = DispatchSemaphore(value: 0)
-        
-        // 检查是否有断点续传数据
-        if let resumeData = loadResumeData() {
-            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
-            downloadTask = session.downloadTask(withResumeData: resumeData)
-        } else {
-            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
-            downloadTask = session.downloadTask(with: url)
-        }
-        
-        downloadTask?.resume()
-        semaphore.wait()
-    }
-    
-    /// 暂停下载
-    func pause() {
-        isPaused = true
-        downloadTask?.cancel { [weak self] resumeData in
-            if let resumeData = resumeData {
-                self?.saveResumeData(resumeData)
-            }
-        }
-    }
-    
-    /// 恢复下载
-    func resume() {
-        isPaused = false
-        if let resumeData = loadResumeData() {
-            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
-            downloadTask = session.downloadTask(withResumeData: resumeData)
-            downloadTask?.resume()
-        }
-    }
-    
-    /// 取消下载
-    override func cancel() {
-        downloadTask?.cancel()
-        super.cancel()
-    }
-    
-    /// 保存断点续传数据到文件
-    private func saveResumeData(_ data: Data) {
-        let resumeDataURL = getResumeDataURL()
-        try? data.write(to: resumeDataURL)
-    }
-    
-    /// 从文件加载断点续传数据
-    private func loadResumeData() -> Data? {
-        let resumeDataURL = getResumeDataURL()
-        return try? Data(contentsOf: resumeDataURL)
-    }
-    
-    /// 获取断点续传数据的存储路径
-    private func getResumeDataURL() -> URL {
-        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-        let resumeDataFileName = url.lastPathComponent + ".resumeData"
-        return cacheDirectory.appendingPathComponent(resumeDataFileName)
-    }
-}
-
-// MARK: - URLSessionDownloadDelegate
-extension DownloadOperation: URLSessionDownloadDelegate {
-    /// 下载进度更新
-    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
-        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
-        progressHandler?(progress)
-    }
-    
-    /// 下载完成
-    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
-        if !isPaused {
-            // 将文件移动到缓存目录
-            let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-            let fileName = url.lastPathComponent
-            let destinationURL = cacheDirectory.appendingPathComponent(fileName)
-            try? FileManager.default.moveItem(at: location, to: destinationURL)
-            completionHandler?(destinationURL, nil)
-        }
-    }
-    
-    /// 任务完成(包括成功或失败)
-    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
-        if let error = error {
-            completionHandler?(nil, error)
-        }
-    }
-}

+ 214 - 0
AIRingtone/Common/Tool/NotUse/TSDownloadTool.swift

@@ -0,0 +1,214 @@
+//
+//  TSDownloadTool.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+import Foundation
+
+//// MARK: - TSDownloadTool
+///// 下载管理器,负责生成和管理下载任务
+//class TSDownloadTool {
+//    static let shared = TSDownloadTool()
+//    
+//    private var maxConcurrentDownloads: Int
+//    private let downloadQueue: OperationQueue
+//    private var activeDownloads: [URL: DownloadOperation] = [:]
+//    private var expirationInterval: TimeInterval = 7 * 24 * 60 * 60 // 默认 7 天
+//    
+//    private init() {
+//        self.maxConcurrentDownloads = 3 // 默认最大并发下载数为 3
+//        self.downloadQueue = OperationQueue()
+//        self.downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads
+//    }
+//    
+//    /// 设置最大并发下载数
+//    /// - Parameter count: 最大并发下载数
+//    func setMaxConcurrentDownloads(_ count: Int) {
+//        maxConcurrentDownloads = count
+//        downloadQueue.maxConcurrentOperationCount = count
+//    }
+//    
+//    /// 设置缓存文件的过期时长
+//    /// - Parameter interval: 过期时长(秒)
+//    func setExpirationInterval(_ interval: TimeInterval) {
+//        expirationInterval = interval
+//    }
+//    
+//    /// 创建下载任务
+//    /// - Parameter url: 下载文件的 URL
+//    /// - Returns: 返回 DownloadOperation,用于控制下载任务
+//    func downloadFile(from url: URL) -> DownloadOperation {
+//        if let downloadOperation = activeDownloads[url] {
+//            return downloadOperation
+//        }
+//        
+//        let downloadOperation = DownloadOperation(url: url)
+//        activeDownloads[url] = downloadOperation
+//        downloadQueue.addOperation(downloadOperation)
+//        return downloadOperation
+//    }
+//    
+//    /// 清理过期的缓存文件
+//    func cleanExpiredFiles() {
+//        let fileManager = FileManager.default
+//        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
+//        let expirationDate = Date().addingTimeInterval(-expirationInterval)
+//        
+//        if let files = try? fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: [.creationDateKey], options: .skipsHiddenFiles) {
+//            for file in files {
+//                if let attributes = try? fileManager.attributesOfItem(atPath: file.path),
+//                   let creationDate = attributes[.creationDate] as? Date,
+//                   creationDate < expirationDate {
+//                    try? fileManager.removeItem(at: file)
+//                }
+//            }
+//        }
+//    }
+//    
+//    /// 获取本地文件路径(如果存在)
+//    /// - Parameter url: 文件的 URL
+//    /// - Returns: 本地文件路径(如果存在),否则返回 nil
+//    static func localFilePath(for url: URL) -> URL? {
+//        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+//        let fileName = url.lastPathComponent
+//        let fileURL = cacheDirectory.appendingPathComponent(fileName)
+//        if FileManager.default.fileExists(atPath: fileURL.path) {
+//            return fileURL
+//        }
+//        return nil
+//    }
+//    
+//    /// 清理所有下载任务
+//    func cleanAllTasks() {
+//        for (url, downloadOperation) in activeDownloads {
+//            downloadOperation.cancel()
+//            activeDownloads[url] = nil
+//        }
+//    }
+//    
+//    /// 清理某个下载任务
+//    /// - Parameter url: 下载任务的 URL
+//    func cleanTask(for url: URL) {
+//        if let downloadOperation = activeDownloads[url] {
+//            downloadOperation.cancel()
+//            activeDownloads[url] = nil
+//        }
+//    }
+//}
+//
+//// MARK: - DownloadOperation
+///// 下载任务,继承自 Operation
+//class DownloadOperation: Operation {
+//    private let url: URL
+//    private var downloadTask: URLSessionDownloadTask?
+//    private var resumeData: Data?
+//    private var isPaused: Bool = false
+//    
+//    /// 下载进度回调
+//    var progressHandler: ((Double) -> Void)?
+//    
+//    /// 下载完成回调
+//    var completionHandler: ((URL?, Error?) -> Void)?
+//    
+//    init(url: URL) {
+//        self.url = url
+//    }
+//    
+//    /// 开始下载
+//    func startDownload() {
+//        // 检查本地是否已存在文件
+//        if let localFilePath = TSDownloadTool.localFilePath(for: url) {
+//            completionHandler?(localFilePath, nil)
+//            return
+//        }
+//        
+//        // 如果没有本地文件,则开始下载
+//        let semaphore = DispatchSemaphore(value: 0)
+//        
+//        // 检查是否有断点续传数据
+//        if let resumeData = loadResumeData() {
+//            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+//            downloadTask = session.downloadTask(withResumeData: resumeData)
+//        } else {
+//            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+//            downloadTask = session.downloadTask(with: url)
+//        }
+//        
+//        downloadTask?.resume()
+//        semaphore.wait()
+//    }
+//    
+//    /// 暂停下载
+//    func pause() {
+//        isPaused = true
+//        downloadTask?.cancel { [weak self] resumeData in
+//            if let resumeData = resumeData {
+//                self?.saveResumeData(resumeData)
+//            }
+//        }
+//    }
+//    
+//    /// 恢复下载
+//    func resume() {
+//        isPaused = false
+//        if let resumeData = loadResumeData() {
+//            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+//            downloadTask = session.downloadTask(withResumeData: resumeData)
+//            downloadTask?.resume()
+//        }
+//    }
+//    
+//    /// 取消下载
+//    override func cancel() {
+//        downloadTask?.cancel()
+//        super.cancel()
+//    }
+//    
+//    /// 保存断点续传数据到文件
+//    private func saveResumeData(_ data: Data) {
+//        let resumeDataURL = getResumeDataURL()
+//        try? data.write(to: resumeDataURL)
+//    }
+//    
+//    /// 从文件加载断点续传数据
+//    private func loadResumeData() -> Data? {
+//        let resumeDataURL = getResumeDataURL()
+//        return try? Data(contentsOf: resumeDataURL)
+//    }
+//    
+//    /// 获取断点续传数据的存储路径
+//    private func getResumeDataURL() -> URL {
+//        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+//        let resumeDataFileName = url.lastPathComponent + ".resumeData"
+//        return cacheDirectory.appendingPathComponent(resumeDataFileName)
+//    }
+//}
+//
+//// MARK: - URLSessionDownloadDelegate
+//extension DownloadOperation: URLSessionDownloadDelegate {
+//    /// 下载进度更新
+//    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
+//        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
+//        progressHandler?(progress)
+//    }
+//    
+//    /// 下载完成
+//    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
+//        if !isPaused {
+//            // 将文件移动到缓存目录
+//            let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+//            let fileName = url.lastPathComponent
+//            let destinationURL = cacheDirectory.appendingPathComponent(fileName)
+//            try? FileManager.default.moveItem(at: location, to: destinationURL)
+//            completionHandler?(destinationURL, nil)
+//        }
+//    }
+//    
+//    /// 任务完成(包括成功或失败)
+//    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+//        if let error = error {
+//            completionHandler?(nil, error)
+//        }
+//    }
+//}

+ 2 - 2
AIRingtone/Common/Tool/TSAudioPlayer/TSAudioPlayer.swift

@@ -56,7 +56,7 @@ class TSAudioPlayer: NSObject {
         return player != nil
         return player != nil
     }
     }
     
     
-    var currentTimeChanged: ((Double) -> Void)?
+    var currentTimeChanged: ((Double,Double) -> Void)?
     var audioPlayerDidFinishHandle: ((Bool) -> Void)?
     var audioPlayerDidFinishHandle: ((Bool) -> Void)?
     
     
     /// 初始化播放器
     /// 初始化播放器
@@ -150,7 +150,7 @@ class TSAudioPlayer: NSObject {
         timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
         timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
             guard let self = self else { return }
             guard let self = self else { return }
             let currentTime = CMTimeGetSeconds(time)
             let currentTime = CMTimeGetSeconds(time)
-            self.currentTimeChanged?(currentTime)
+            self.currentTimeChanged?(currentTime,duration)
         }
         }
     }
     }
     
     

+ 110 - 13
AIRingtone/Common/Tool/TSAudioPlayer/TSBusinessAudioPlayer.swift

@@ -5,7 +5,7 @@
 //  Created by 100Years on 2025/3/7.
 //  Created by 100Years on 2025/3/7.
 //
 //
 
 
-
+import AVFoundation
 class TSBusinessAudioPlayer {
 class TSBusinessAudioPlayer {
     
     
     static let shared = TSBusinessAudioPlayer()
     static let shared = TSBusinessAudioPlayer()
@@ -22,6 +22,8 @@ class TSBusinessAudioPlayer {
     private var audioPlayer: TSAudioPlayer?
     private var audioPlayer: TSAudioPlayer?
     
     
     var stateChangedHandle:((PlayerState) -> Void)?
     var stateChangedHandle:((PlayerState) -> Void)?
+    var currentTimeChangedHandle:((Double,Double) -> Void)?
+    
     var currentPlayerState:PlayerState = .stop
     var currentPlayerState:PlayerState = .stop
     var duration:Double{
     var duration:Double{
         if let audioPlayer = audioPlayer {
         if let audioPlayer = audioPlayer {
@@ -53,6 +55,11 @@ class TSBusinessAudioPlayer {
         return 0.0
         return 0.0
     }
     }
     
     
+    /// 跳转到指定时间
+    /// - Parameter time: 目标时间(秒)
+    func seek(to time: Double) {
+        audioPlayer?.seek(to: time)
+    }
     
     
     var playProgress:Double{
     var playProgress:Double{
         let playProgress = currentTime / duration
         let playProgress = currentTime / duration
@@ -68,20 +75,27 @@ class TSBusinessAudioPlayer {
         return false
         return false
     }
     }
     var currentURLString:String = ""
     var currentURLString:String = ""
+    var currentLocalURL:URL? = nil
     var currentIndexPath:IndexPath? = nil
     var currentIndexPath:IndexPath? = nil
     
     
     //加载音乐可能 2-3 秒有结果,停止加载后播放.
     //加载音乐可能 2-3 秒有结果,停止加载后播放.
     private var isStopPlayingAfterLoading:Bool = false
     private var isStopPlayingAfterLoading:Bool = false
 
 
-    func isPlayURLString(string:String,indexPath:IndexPath? = nil) -> Bool {
+    func isPlayURLString(string:String,localURL:URL? = nil,indexPath:IndexPath? = nil) -> Bool {
 
 
         if currentURLString == string {
         if currentURLString == string {
+            
             if let currentIndexPath = currentIndexPath,
             if let currentIndexPath = currentIndexPath,
                let indexPath = indexPath,
                let indexPath = indexPath,
                indexPath != currentIndexPath
                indexPath != currentIndexPath
             {
             {
                 return false
                 return false
-            }else{
+            }else if let currentLocalURL = currentLocalURL,
+                let localURL = localURL,
+                currentLocalURL != localURL
+             {
+                 return false
+             }else{
                 return true
                 return true
             }
             }
         }
         }
@@ -89,7 +103,7 @@ class TSBusinessAudioPlayer {
     }
     }
     
     
 
 
-    func playUrlString(_ urlString:String?,loop:Bool = false,indexPath:IndexPath? = nil) {
+    func playUrlString(_ urlString:String?,localURL:URL? = nil,loop:Bool = false,indexPath:IndexPath? = nil) {
         self.stop()
         self.stop()
         if let urlString = urlString {
         if let urlString = urlString {
             
             
@@ -97,7 +111,9 @@ class TSBusinessAudioPlayer {
 //                self.play()
 //                self.play()
 //                return
 //                return
 //            }
 //            }
+            
             self.currentURLString = urlString
             self.currentURLString = urlString
+            self.currentLocalURL = localURL
             self.currentIndexPath = indexPath
             self.currentIndexPath = indexPath
 
 
             let palyFile:(URL)->Void = { [weak self] url in
             let palyFile:(URL)->Void = { [weak self] url in
@@ -111,11 +127,9 @@ class TSBusinessAudioPlayer {
                     setVolume(volume: 1.0)
                     setVolume(volume: 1.0)
                 }
                 }
                 
                 
-                self.audioPlayer?.currentTimeChanged = { [weak self] currentTime in
+                self.audioPlayer?.currentTimeChanged = { [weak self] currentTime,duration in
                     guard let self = self else { return }
                     guard let self = self else { return }
-                    
-                    
-                    
+                    currentTimeChangedHandle?(currentTime,duration)
                     changePlayerState(.currentTime(currentTime))
                     changePlayerState(.currentTime(currentTime))
                 }
                 }
                 
                 
@@ -131,12 +145,19 @@ class TSBusinessAudioPlayer {
             }
             }
             
             
             isStopPlayingAfterLoading = false
             isStopPlayingAfterLoading = false
-            if let path = TSCommonTool.getCachedURLString(from: urlString,missingEx: "mp3") {
+            
+            if let path = self.currentLocalURL,TSFileManagerTool.fileExists(at: path){
+                palyFile(path) //播放
+                
+            }else if let path = TSDownloadManager.getRingLocalURL(urlString: urlString) {
+//            if let path = TSCommonTool.getCachedURLString(from: urlString,missingEx: "mp3") {
                 palyFile(path) //播放
                 palyFile(path) //播放
             }else{
             }else{
                 self.changePlayerState(.loading(0.0))
                 self.changePlayerState(.loading(0.0))
-                TSCommonTool.downloadAndCacheFile(from: urlString,missingEx: "mp3") { [weak self] path, error in
+                
+                _ = TSDownloadManager.downloadFile(urlString: urlString,missingEx: "mp3") {[weak self] url, error in
                     guard let self = self else { return }
                     guard let self = self else { return }
+                    
                     self.changePlayerState(.loading(1.0))
                     self.changePlayerState(.loading(1.0))
                 
                 
                     if isStopPlayingAfterLoading == true || currentURLString != urlString{
                     if isStopPlayingAfterLoading == true || currentURLString != urlString{
@@ -144,13 +165,30 @@ class TSBusinessAudioPlayer {
                         return
                         return
                     }
                     }
                     
                     
-                    if let path = path {
-                        palyFile(URL(fileURLWithPath: path)) //播放
+                    if let url = url {
+                        palyFile(url) //播放
                     }else{
                     }else{
                         //暂停
                         //暂停
                         self.stop()
                         self.stop()
                     }
                     }
                 }
                 }
+                
+//                TSCommonTool.downloadAndCacheFile(from: urlString,missingEx: "mp3") { [weak self] path, error in
+//                    guard let self = self else { return }
+//                    self.changePlayerState(.loading(1.0))
+//                
+//                    if isStopPlayingAfterLoading == true || currentURLString != urlString{
+//                        isStopPlayingAfterLoading = false
+//                        return
+//                    }
+//                    
+//                    if let path = path {
+//                        palyFile(URL(fileURLWithPath: path)) //播放
+//                    }else{
+//                        //暂停
+//                        self.stop()
+//                    }
+//                }
             }
             }
         }
         }
     }
     }
@@ -196,7 +234,7 @@ class TSBusinessAudioPlayer {
         currentPlayerState = state
         currentPlayerState = state
         kExecuteOnMainThread{
         kExecuteOnMainThread{
             self.stateChangedHandle?(state)
             self.stateChangedHandle?(state)
-            NotificationCenter.default.post(name: .kBusinessAudioStateChange, object: nil, userInfo: ["PlayerState": state])
+//            NotificationCenter.default.post(name: .kBusinessAudioStateChange, object: nil, userInfo: ["PlayerState": state])
         }
         }
     }
     }
      
      
@@ -204,3 +242,62 @@ class TSBusinessAudioPlayer {
         dePrint("TSAudioPlayer TSBusinessAudioPlayer deinit")
         dePrint("TSAudioPlayer TSBusinessAudioPlayer deinit")
     }
     }
 }
 }
+
+
+extension TSBusinessAudioPlayer{
+    struct AudioFileInfo {
+        let sizeInBytes: UInt64?    // 文件大小(字节)
+        let durationInSeconds: Double?  // 音频时长(秒)
+        
+//        // 计算属性:格式化显示
+//        var formattedSize: String {
+//            guard let size = sizeInBytes else { return "未知大小" }
+//            let formatter = ByteCountFormatter()
+//            formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
+//            return formatter.string(fromByteCount: Int64(size))
+//        }
+//        
+//        var formattedDuration: String {
+//            guard let duration = durationInSeconds else { return "未知时长" }
+//            let formatter = DateComponentsFormatter()
+//            formatter.unitsStyle = .positional
+//            formatter.allowedUnits = [.hour, .minute, .second]
+//            formatter.zeroFormattingBehavior = .pad
+//            return formatter.string(from: duration) ?? "00:00"
+//        }
+    }
+
+    static func getAudioFileInfo(path: String) -> AudioFileInfo? {
+        // 1. 检查URL有效性
+        guard let url = URL(string: path) else {
+            print("无效的URL字符串")
+            return nil
+        }
+        
+        // 2. 检查文件是否存在(仅限本地文件)
+        guard FileManager.default.fileExists(atPath: url.path) else {
+            print("文件不存在或不是本地路径")
+            return nil
+        }
+        
+        // 3. 获取文件大小
+        let fileSize: UInt64? = {
+            do {
+                let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
+                return attributes[.size] as? UInt64
+            } catch {
+                print("获取文件大小失败: \(error.localizedDescription)")
+                return nil
+            }
+        }()
+        
+        // 4. 获取音频时长
+        let duration: Double? = {
+            let asset = AVURLAsset(url: url)
+            let seconds = Double(CMTimeGetSeconds(asset.duration))
+            return seconds.isNaN ? nil : seconds
+        }()
+        
+        return AudioFileInfo(sizeInBytes: fileSize, durationInSeconds: duration)
+    }
+}

+ 61 - 104
AIRingtone/Common/Tool/TSBandRingTool/AudioTool.swift

@@ -6,7 +6,7 @@
 //
 //
 
 
 import AVFoundation
 import AVFoundation
-
+import ffmpegkit
 typealias AudioCompletionHandler = (URL?, String?) -> Void
 typealias AudioCompletionHandler = (URL?, String?) -> Void
 
 
 class AudioTool {
 class AudioTool {
@@ -14,132 +14,89 @@ class AudioTool {
     lazy var queue = DispatchQueue(label: "queue_audio_convert")
     lazy var queue = DispatchQueue(label: "queue_audio_convert")
     
     
     /*
     /*
-     剪切开始工作
-     大概流程
-    1、获得视频总时长,处理时间,数组格式返回音频数据
-    2、创建导出会话
-    3、设计导出时间范围,淡出时间范围
-    4、设计新音频配置数据,文件路径,类型等
-    5、开始剪切
-     */
-    
+      剪切开始工作
+      大概流程
+     1、获得视频总时长,处理时间,数组格式返回音频数据
+     2、创建导出会话
+     3、设计导出时间范围,淡出时间范围
+     4、设计新音频配置数据,文件路径,类型等
+     5、开始剪切
+      */
+
     /// 裁剪音频
     /// 裁剪音频
     /// - Parameters:
     /// - Parameters:
     ///   - asset: 音频资源
     ///   - asset: 音频资源
     ///   - startDuration: 开始时间
     ///   - startDuration: 开始时间
     ///   - endDuration: 结束时间
     ///   - endDuration: 结束时间
-    ///   - fadeIn: 淡入时长
-    ///   - fadeOut: 淡出时长
+    ///   - fadeIn: 淡入时长(暂时失效)
+    ///   - fadeOut: 淡出时长(暂时失效)
+    ///   - volume: volume
     ///   - filePath: 导出保存路径
     ///   - filePath: 导出保存路径
     ///   - completionHandler: String: 导出文件存储路径,String: 报错信息
     ///   - completionHandler: String: 导出文件存储路径,String: 报错信息
-    func cutAudio(asset: AVAsset,
-                  from startDuration: Double, to endDuration: Double,
-                  fadeIn: Double, fadeOut: Double,
-                  completionHandler: ((String?, String?) -> Void)?) {
-        
-        let tracks = asset.tracks(withMediaType: .audio)
-        guard let track = tracks.first else {
-            // 如果没有数据,跳出
-            completionHandler?(nil, "Sorry, Unknow Error, Please try another")
-            return
-        }
-        // 2. 创建导出会话
-        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
-            // 创建失败,则跳出
-            completionHandler?(nil, "Sorry, Unknow Error, Please try another")
-            return
-        }
-        
-        // 导出时间范围
-        // create trim time range // CMTimeMake(第几帧, 帧率)
-        let startTime = CMTime(value: CMTimeValue(startDuration), timescale: 1)
-        let endTime = CMTime(value: CMTimeValue(endDuration), timescale: 1)
-        let exportTimeRange = CMTimeRange(start: startTime, end: endTime)
-        
-        // 创建混音
-        let exportMix = AVMutableAudioMix()
-        let inputParameters = AVMutableAudioMixInputParameters(track: track)
-        
-        // 淡入时间
-        if fadeIn > 0, fadeIn < (endDuration - startDuration) {
-            let startFadeInTime = startTime
-            let endFadeInTime = CMTime(value: CMTimeValue(startDuration+fadeIn), timescale: 1)
-            let fadeInTimeRange = CMTimeRange(start: startFadeInTime, end: endFadeInTime)
-            inputParameters.setVolumeRamp(fromStartVolume: 0.0, toEndVolume: 1.0, timeRange: fadeInTimeRange)
-        }
-        // 淡出时间
-        if fadeOut > 0, fadeOut < (endDuration - startDuration) {
-            let startFadeOutTime = CMTime(value: CMTimeValue(endDuration-fadeOut), timescale: 1)
-            let endFadeOutTime = endTime
-            let fadeOutTimeRange = CMTimeRange(start: startFadeOutTime, end: endFadeOutTime)
-            inputParameters.setVolumeRamp(fromStartVolume: 1.0, toEndVolume: 0.0, timeRange: fadeOutTimeRange)
-        }
-        
-        exportMix.inputParameters = [inputParameters]
-        
-        guard let documentURL = TSBandRingTool.ringDirectory else {
-            return
-        }
-        let outputFilePath = NSString(string: documentURL).appendingPathComponent("ringExportM4a.m4a")
+    func cutAudio(inputFilePath: String,
+                  startDuration: Double,
+                  endDuration: Double,
+                  fadeIn: Double,
+                  fadeOut: Double,
+                  addVolume: Double,
+                  outputFilePath: String,
+                  completionHandler: @escaping (String?, String?) -> Void)
+    {
+        let inPath = escapeFilePath(inputFilePath)
+        let outPath = escapeFilePath(outputFilePath)
+        
+//        let command = "-i \(inPath) -vn -ss \(startDuration) -to \(endDuration) -c:a libmp3lame \(outPath)"
+//        let command1 = "-i \(inPath) -vn -ss \(startDuration) -to \(endDuration) -filter:a \"volume=2.0\" -c:a libmp3lame \(outPath)"
+
         
         
-        if FileManager.default.fileExists(atPath: outputFilePath) {
-            try? FileManager.default.removeItem(atPath: outputFilePath)
-        }
+        // 计算淡出开始时间(总时长 - 淡出时长)
+        let fadeOutStartTime = endDuration - fadeOut
         
         
-        // output path 新文件路径
-        exportSession.outputURL = URL(fileURLWithPath: outputFilePath)
-        exportSession.outputFileType = .m4a
-        exportSession.timeRange = exportTimeRange
-        // 新的混音音频
-        exportSession.audioMix = exportMix
+        let command = """
+        -i \(inPath) -vn -ss \(startDuration) -to \(endDuration) \
+        -filter:a "volume=\(addVolume), afade=t=in:ss=\(startDuration):d=\(fadeIn), afade=t=out:st=\(fadeOutStartTime):d=\(fadeOut)" \
+        -c:a libmp3lame \(outPath)
+        """
         
         
-        // 开始导出
-        exportSession.exportAsynchronously {
-            if let error = exportSession.error {
-                debugPrint(error)
-            }
-            switch exportSession.status {
+        FFmpegKit.executeAsync(command) { session in
+
+            let state = session?.getState()
+
+            switch state {
             case .completed:
             case .completed:
-                completionHandler?(outputFilePath, nil)
+                completionHandler(outputFilePath, nil)
             case .failed:
             case .failed:
-                completionHandler?(nil, "Sorry, Export Session Failure")
+                if let error = session?.getFailStackTrace() {
+                    completionHandler(nil, "Sorry, Extract Audio Failure".localized)
+                } else {
+                    completionHandler(nil, "Sorry, Extract Audio Failure".localized)
+                }
             default:
             default:
-                completionHandler?(nil, "Sorry, Export Session Failure")
+                completionHandler(nil, "Sorry, Extract Audio Failure".localized)
             }
             }
         }
         }
     }
     }
     
     
+    func escapeFilePath(_ path: String) -> String {
+        return "\"\(path)\""
+    }
+    
     /*
     /*
      音频格式转换
      音频格式转换
      1. 裁剪后获得m4a
      1. 裁剪后获得m4a
      1. m4a 转 wav
      1. m4a 转 wav
      2. wav 转 mp3
      2. wav 转 mp3
      */
      */
-    func startTansformAudio(asset: AVAsset,
-                  from startDuration: Double, to endDuration: Double,
-                  fadeIn: Double, fadeOut: Double,
-                  savePath filePath: String,
-                  completionHandler: ((String?, String?) -> Void)?) {
-        
-        cutAudio(asset: asset, from: startDuration, to: endDuration, fadeIn: fadeIn, fadeOut: fadeOut) { outputFilePath, errMsg in
-            
-            guard let outputFilePath = outputFilePath,
-                  let documentURL = TSBandRingTool.ringDirectory else {
-                completionHandler?(nil, errMsg)
-                return
-            }
-            let wavFilePath = NSString(string: documentURL).appendingPathComponent("ringExportWav.wav")
-                
-            AudioConverter.convertM4a(toWav: outputFilePath, outPath: wavFilePath) { path, errMsg in
-                
-                guard let path = path else {
-                    completionHandler?(nil, errMsg)
-                    return
-                }
-                AudioConverter.convenrtToMp3(withResult: path, outPath: filePath) { mp3Path, errMsg in
-                    completionHandler?(mp3Path, errMsg)
-                }
-            }
+    func startTansformAudio(url: String,
+                            from startDuration: Double,
+                            to endDuration: Double,
+                            fadeIn: Double,
+                            fadeOut: Double,
+                            addVolume: Double,
+                            savePath filePath: String,
+                            completionHandler: ((String?, String?) -> Void)?) {
+        cutAudio(inputFilePath: url, startDuration: startDuration, endDuration: endDuration, fadeIn: fadeIn, fadeOut: fadeOut,addVolume:addVolume, outputFilePath: filePath) { path, error in
+            completionHandler?(path, error)
         }
         }
     }
     }
     
     

+ 42 - 23
AIRingtone/Common/Tool/TSBandRingTool/TSBandRingTool.swift

@@ -55,7 +55,7 @@ class TSBandRingTool:NSObject {
     }
     }
     
     
     func shareBandVC(vc:UIViewController,
     func shareBandVC(vc:UIViewController,
-        fileURLString: String,
+                     fileURL: URL,
                    fileName:String?,
                    fileName:String?,
                    completion: ((Bool) -> Void)? = nil) {
                    completion: ((Bool) -> Void)? = nil) {
         
         
@@ -63,21 +63,26 @@ class TSBandRingTool:NSObject {
             completion?(false)
             completion?(false)
             return
             return
         }
         }
+        
+        if TSFileManagerTool.fileExists(at: fileURL) == false{
+            completion?(false)
+            return
+        }
+        
+        
         self.targetVC = vc
         self.targetVC = vc
-        if fileURLString.contains("http") {
+//        if fileURLString.contains("http") {
             
             
-//            TSLoadingAnimation.showLoading(in: self.targetVC?.view)
-            if let window = WindowHelper.getKeyWindow() {
-                window.addSubview(ringLoadingView)
-                ringLoadingView.isRotating = true
-            }
-
-            TSCommonTool.downloadAndCacheFile(from: fileURLString) { [weak self] path, error in
-                guard let self = self else { return }
-//                TSLoadingAnimation.hideLoading()
-                ringLoadingView.removeFromSuperview()
-                if let path = path,let url = URL(string: path) {
-                    self.createBand(with: url, fileName: fileName) { bandURL in
+//            if let window = WindowHelper.getKeyWindow() {
+//                window.addSubview(ringLoadingView)
+//                ringLoadingView.isRotating = true
+//            }
+            
+//            _ = TSDownloadManager.downloadFile(urlString: fileURLString,missingEx: "mp3") {[weak self] url, error in
+//                guard let self = self else { return }
+//                ringLoadingView.removeFromSuperview()
+        
+                    self.createBand(with: fileURL, fileName: fileName) { bandURL in
                         if let url = bandURL {
                         if let url = bandURL {
                             completion?(true)
                             completion?(true)
                             self.shareRing(fileUrl: url)
                             self.shareRing(fileUrl: url)
@@ -86,15 +91,29 @@ class TSBandRingTool:NSObject {
                             dePrint("Failed to set, please try another")
                             dePrint("Failed to set, please try another")
                         }
                         }
                     }
                     }
-                }else{
-                    dePrint("downloadAndCacheFile = \(error?.localizedDescription)")
-                    completion?(false)
-                }
-            }
-        }else{
-            dePrint("ringtone no http")
-            completion?(false)
-        }
+//            TSCommonTool.downloadAndCacheFile(from: fileURLString) { [weak self] path, error in
+//                guard let self = self else { return }
+////                TSLoadingAnimation.hideLoading()
+//                ringLoadingView.removeFromSuperview()
+//                if let path = path,let url = URL(string: path) {
+//                    self.createBand(with: url, fileName: fileName) { bandURL in
+//                        if let url = bandURL {
+//                            completion?(true)
+//                            self.shareRing(fileUrl: url)
+//                        }else{
+//                            completion?(false)
+//                            dePrint("Failed to set, please try another")
+//                        }
+//                    }
+//                }else{
+//                    dePrint("downloadAndCacheFile = \(error?.localizedDescription)")
+//                    completion?(false)
+//                }
+//            }
+//        }else{
+//            dePrint("ringtone no http")
+//            completion?(false)
+//        }
                                    
                                    
        
        
     }
     }

+ 7 - 0
AIRingtone/Common/Tool/TSBusinessCommon.swift

@@ -0,0 +1,7 @@
+//
+//  TSBusinessFileManager.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/26.
+//
+

+ 151 - 0
AIRingtone/Common/Tool/TSBusinessFileManager.swift

@@ -0,0 +1,151 @@
+//
+//  TSBusinessFileManager.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/27.
+//
+
+class TSBusinessFileManager {
+    
+    /// 获取 Video 下载后保存的的文件件路径
+    static var saveRingPathURL:URL = {
+        let saveRingPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("ring")
+        return saveRingPathURL
+    }()
+    
+    static var saveCacheAllPathURL:URL = {
+        let saveRingPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("cacheAll")
+        return saveRingPathURL
+    }()
+    
+    public static func generateFileName(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil
+    )->String?{
+        guard let url = URL(string: urlString) else{
+            return nil
+        }
+        
+        var fileName = url.path.md5
+
+        // 使用 URL 的 MD5 哈希值作为缓存文件名,附加 URL 的后缀名
+        var fileExtension = ""
+        if let fileEx = fileEx {
+            fileExtension = fileEx
+        }else{
+            var missingExStr = ""
+            if let missingEx = missingEx {
+                missingExStr = missingEx
+            }
+            fileExtension = url.pathExtension.isEmpty ? missingExStr : url.pathExtension
+        }
+
+        if fileExtension.count > 0 {
+            fileName = url.path.md5 + ".\(fileExtension)"
+        }
+         
+        return fileName
+    }
+    
+    //获取 urlstring 本地的缓存 url path
+    public static func getLocalURL(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil)->URL?{
+        
+        guard let fileName = generateFileName(urlString: urlString,fileEx:fileEx,missingEx: missingEx) else{
+            return nil
+        }
+        
+        //检查文件是否已存在于缓存中
+        let ringFileURL = saveRingPathURL.appendingPathComponent(fileName)
+        if FileManager.default.fileExists(atPath: ringFileURL.path) {
+            print("文件已存在于缓存中: \(ringFileURL)")
+            return ringFileURL
+        }
+        
+        let cachedFileURL = saveCacheAllPathURL.appendingPathComponent(fileName)
+        if FileManager.default.fileExists(atPath: cachedFileURL.path) {
+            print("文件已存在于缓存中: \(cachedFileURL)")
+            return cachedFileURL
+        }
+        
+        return nil
+    }
+    
+
+}
+
+//缓存路径
+extension TSBusinessFileManager {
+    
+    //检查 url 对不对
+    public static func generateCachesURL(
+        from urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil,
+        cacheDirectory:String = "cacheAll",
+        completion:((String?, Error?) -> Void)? = nil
+    )->URL?
+    {
+        guard let url = URL(string: urlString) else{
+            completion?(nil, NSError(domain: "url null", code: 0))
+            return nil
+        }
+        
+        if !urlString.contains("http") && urlString.contains("/"){
+            completion?(urlString.fillCachePath, nil)
+            return nil
+        }
+        
+        let fileManager = FileManager.default
+        
+        // 获取缓存目录下的 `cacheAll` 文件夹路径
+        let cachesDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
+        let cacheAllDirectory = cachesDirectory.appendingPathComponent(cacheDirectory)
+        
+        // 创建 `cacheAll` 文件夹(如果不存在)
+        if !fileManager.fileExists(atPath: cacheAllDirectory.path) {
+            do {
+                try fileManager.createDirectory(at: cacheAllDirectory, withIntermediateDirectories: true, attributes: nil)
+            } catch {
+                completion?(nil, error)
+                return nil
+            }
+        }
+        
+        
+        guard let fileName = generateFileName(urlString: urlString,fileEx:fileEx,missingEx: missingEx) else{
+            completion?(nil, NSError(domain: "url error", code: 0))
+            return nil
+        }
+
+        let cachedFileURL = cacheAllDirectory.appendingPathComponent(fileName)
+        return cachedFileURL
+    }
+    
+    //获取 urlstring 本地的缓存 url path
+    public static func getCachesURL(
+        from urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil,
+        cacheDirectory:String = "cacheAll")->URL?{
+        
+        if let cachedFileURL = generateCachesURL(
+            from: urlString,
+            fileEx: fileEx,
+            missingEx: missingEx,
+            cacheDirectory: cacheDirectory
+        ){
+            //检查文件是否已存在于缓存中
+            if FileManager.default.fileExists(atPath: cachedFileURL.path) {
+                print("文件已存在于缓存中: \(cachedFileURL)")
+                return cachedFileURL
+            }
+        }
+        
+        return nil
+    }
+    
+}

+ 77 - 0
AIRingtone/Common/Tool/TSDownloadManager.swift

@@ -0,0 +1,77 @@
+//
+//  TSPublicContent.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/20.
+//
+
+
+import AVFoundation
+import Alamofire
+
+class TSDownloadManager {
+    
+
+    static func downloadFile(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (URL?, Error?) -> Void
+    ) -> DownloadRequest? {
+        
+        if let fileName = TSBusinessFileManager.getLocalURL(urlString: urlString,fileEx:fileEx,missingEx:missingEx){
+            completion(fileName,nil)
+            return nil
+        }
+    
+        guard let cachesAllPath = TSBusinessFileManager.generateCachesURL(from: urlString, fileEx: fileEx, missingEx: missingEx,completion: { string, error in
+            completion(nil,error)
+        })else { return nil }
+        
+        return TSNetworkShared.downloadFile(urlString: urlString,to: cachesAllPath, progressHandler:progressHandler,completion: completion)
+    }
+    
+}
+
+extension TSDownloadManager {
+    
+    
+    //获取 TSRingModel 本地的缓存
+    public static func getRingLocalURL(ringModel: TSRingModel)->URL?{
+        if ringModel.documentPath.count == 0 {
+            return nil
+        }
+        
+        let documentPath = ringModel.documentPath.fillDocumentURL
+        if TSFileManagerTool.fileExists(at: documentPath) {
+            return documentPath
+        }
+        
+        let urlString = ringModel.audioUrl
+        guard let fileName = TSBusinessFileManager.getLocalURL(urlString: urlString,fileEx:nil,missingEx: "mp3") else{
+            return nil
+        }
+        return fileName
+    }
+    
+    //获取 urlstring 本地的缓存 url path
+    public static func getRingLocalURL(urlString: String)->URL?{
+        guard let fileName = TSBusinessFileManager.getLocalURL(urlString: urlString,fileEx:nil,missingEx: "mp3") else{
+            return nil
+        }
+        return fileName
+    }
+
+    //获取 urlstring 本地的缓存 url path
+    public static func generateRingSaveLocalURL(name:String,timestamp:Bool = true,fileExtension:String = "mp3")->URL{
+        var fileName = name
+        if timestamp {
+            fileName = fileName + String(Date.timestampInt)
+        }
+        
+        fileName = fileName + "." + fileExtension
+        return TSBusinessFileManager.saveRingPathURL.appendingPathComponent(fileName)
+    }
+    
+}

+ 0 - 157
AIRingtone/Common/Tool/TSFileManagerTool.swift

@@ -1,157 +0,0 @@
-//
-//  TSFileManagerTool.swift
-//  TSLiveWallpaper
-//
-//  Created by 100Years on 2024/12/26.
-//
-
-class TSFileManagerTool {
-    
-    /// 获取 Video 下载后保存的的文件件路径
-    static var saveDownVideoPathURL:URL = {
-        let saveVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("saveVideo")
-        return saveVideoPathURL
-    }()
-    
-    /// 获取 Video 临时编辑的文件件路径
-    static var editLiveVideoPathURL:URL = {
-        let editVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("editVideo")
-        return editVideoPathURL
-    }()
-    
-    /// 获取 Video 编辑后保存的的文件件路径
-    static var saveLiveVideoPathURL:URL = {
-        let saveVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("saveVideo")
-        return saveVideoPathURL
-    }()
-    
-
-    /// 获取沙盒 Documents 目录路径
-    static var documentsDirectory: URL {
-        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-    }
-
-    /// 获取沙盒 Cache 目录路径
-    static var cacheDirectory: URL {
-        return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-    }
-
-    /// 获取沙盒 Temporary 目录路径
-    static var temporaryDirectory: URL {
-        return FileManager.default.temporaryDirectory
-    }
-
-    static func copyFileWithOverwrite(from sourceURL: URL, to targetURL: URL) {
-        let fileManager = FileManager.default
-        do {
-            removeItem(from: targetURL)
-            checkFolderAndCreate(from: targetURL)
-            try fileManager.copyItem(at: sourceURL, to: targetURL)
-            debugPrint("文件复制成功!")
-        } catch {
-            debugPrint("文件复制失败: \(error.localizedDescription)")
-        }
-    }
-    
-    static func removeItem(from sourceURL: URL) {
-        let fileManager = FileManager.default
-        do {
-            // 如果目标路径存在同名文件,先删除旧文件
-            if fileManager.fileExists(atPath: sourceURL.path) {
-                try fileManager.removeItem(at: sourceURL)
-            }
-            debugPrint("文件删除成功!")
-        } catch {
-            debugPrint("文件删除失败: \(error.localizedDescription)")
-        }
-    }
-    
-    /// 移动文件的方法(自动创建目标文件夹)
-    /// - Parameters:
-    ///   - sourceURL: 文件的源 URL
-    ///   - destinationURL: 目标 URL
-    /// - Throws: 如果移动失败,会抛出错误
-    static func moveFile(from sourceURL: URL, to destinationURL: URL) {
-        let fileManager = FileManager.default
-        
-        // 检查源文件是否存在
-        guard fileManager.fileExists(atPath: sourceURL.path) else {
-            let error = NSError(domain: "FileMoveError", code: 404, userInfo: [NSLocalizedDescriptionKey: "源文件不存在"])
-            debugPrint(error)
-            return
-        }
-        
-        // 获取目标文件夹的路径
-        let destinationDirectory = destinationURL.deletingLastPathComponent()
-        do {
-            // 如果目标文件夹不存在,创建文件夹
-            if !fileManager.fileExists(atPath: destinationDirectory.path) {
-                try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
-            }
-            
-            // 检查目标路径是否已经存在文件
-            if fileManager.fileExists(atPath: destinationURL.path) {
-                // 如果需要覆盖,可以选择先删除目标文件
-                try fileManager.removeItem(at: destinationURL)
-            }
-            
-            // 尝试移动文件
-            try fileManager.moveItem(at: sourceURL, to: destinationURL)
-        } catch {
-            debugPrint("尝试移动文件失败: \(error.localizedDescription)")
-        }
-    }
-    
-    static func getFileName(from url: URL, includeExtension: Bool = true) -> String {
-        if includeExtension {
-            return url.lastPathComponent
-        } else {
-            return url.deletingPathExtension().lastPathComponent
-        }
-    }
-    
-    static func checkFolderAndCreate(from destinationURL: URL){
-        let fileManager = FileManager.default
-        let destinationDirectory = destinationURL.deletingLastPathComponent()
-        // 如果目标文件夹不存在,创建文件夹
-        if !fileManager.fileExists(atPath: destinationDirectory.path) {
-            do {
-                try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
-            } catch {
-                debugPrint("尝试创建文件夹失败: \(error.localizedDescription)")
-            }
-        }
-    }
-
-    // MARK: - 文件操作方法
-
-    /// 检查文件或文件夹是否存在
-    static func fileExists(at url: URL) -> Bool {
-        return FileManager.default.fileExists(atPath: url.path)
-    }
-
-    /// 创建文件夹
-    static func createDirectory(at url: URL) throws {
-        if !fileExists(at: url) {
-            try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
-        }
-    }
-    
-    //获取缓存目录下文件夹路径
-    static func getCacheSubPath(at url: URL) ->String? {
-        let array = url.path.components(separatedBy:"/Caches/")
-        let cashFilePath = array.last
-        return cashFilePath
-    }
-    
-}
-
-extension String {
-    var fillCachePath:String{
-        return TSFileManagerTool.cacheDirectory.appendingPathComponent(self).path
-    }
-    
-    var fillCacheURL:URL{
-        return TSFileManagerTool.cacheDirectory.appendingPathComponent(self)
-    }
-}

+ 0 - 6
AIRingtone/Common/Tool/TSPublicContent.swift

@@ -1,6 +0,0 @@
-//
-//  TSPublicContent.swift
-//  AIRingtone
-//
-//  Created by 100Years on 2025/3/20.
-//