Browse Source

设置铃声重构完毕

kln 1 tuần trước cách đây
mục cha
commit
730ff67976

+ 6 - 12
AIPlayRingtones.xcodeproj/project.pbxproj

@@ -55,15 +55,14 @@
 		3DBEA1802DE714A1000C6859 /* ASResourceCacheHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA17F2DE7149E000C6859 /* ASResourceCacheHandler.swift */; };
 		3DBEA1822DE7152C000C6859 /* ASMediaDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1812DE71529000C6859 /* ASMediaDownloader.swift */; };
 		3DBEA1842DE71703000C6859 /* ASMediaPlayerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1832DE71700000C6859 /* ASMediaPlayerController.swift */; };
+		3DBEA1892DE728F8000C6859 /* ASGarageBandHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1882DE728F8000C6859 /* ASGarageBandHelper.m */; };
 		3DCD56F32DDAE3E3004AAB5B /* ASRingToneCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */; };
 		3DCD56F52DDAE42A004AAB5B /* ASViewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */; };
 		3DCD57182DDB1158004AAB5B /* libmp3lame.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DCD57092DDB1158004AAB5B /* libmp3lame.a */; };
 		3DCD571A2DDB1158004AAB5B /* ExtAudioConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD570A2DDB1158004AAB5B /* ExtAudioConverter.m */; };
 		3DCD571B2DDB1158004AAB5B /* AudioConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD57072DDB1158004AAB5B /* AudioConverter.m */; };
-		3DCD571F2DDB1158004AAB5B /* TSBandRingTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD570F2DDB1158004AAB5B /* TSBandRingTool.swift */; };
 		3DCD57222DDB1158004AAB5B /* tutorial-ring.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 3DCD570E2DDB1158004AAB5B /* tutorial-ring.mp4 */; };
 		3DCD57232DDB1158004AAB5B /* null.band in Resources */ = {isa = PBXBuildFile; fileRef = 3DCD57102DDB1158004AAB5B /* null.band */; };
-		3DCD572F2DDB1D8A004AAB5B /* ASRingLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD572E2DDB1D87004AAB5B /* ASRingLoadingView.swift */; };
 		3DCD57312DDB2A66004AAB5B /* ASGenerateRingToRingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD57302DDB2A5D004AAB5B /* ASGenerateRingToRingOperation.swift */; };
 		3DCD57342DDB38E7004AAB5B /* ringAnimation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 3DCD57322DDB38E7004AAB5B /* ringAnimation.gif */; };
 		3DCD57352DDB38E7004AAB5B /* rotatingAnimation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 3DCD57332DDB38E7004AAB5B /* rotatingAnimation.gif */; };
@@ -88,7 +87,6 @@
 		A848F8A92DD6D74A00B746EC /* APAudioToRingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8A82DD6D74900B746EC /* APAudioToRingVC.swift */; };
 		A848F8AB2DD6D75300B746EC /* ASMyRingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8AA2DD6D75200B746EC /* ASMyRingVC.swift */; };
 		A848F8AD2DD6D75900B746EC /* APSettingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8AC2DD6D75800B746EC /* APSettingVC.swift */; };
-		A848F8B32DD6D82300B746EC /* APConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8B22DD6D82300B746EC /* APConfig.swift */; };
 		A848F8B52DD6D83E00B746EC /* APConfig+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8B42DD6D82E00B746EC /* APConfig+Color.swift */; };
 		A848F8B92DD6DF7900B746EC /* ASAppBtnView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8B82DD6DF7700B746EC /* ASAppBtnView.swift */; };
 		A848F8BB2DD6E30600B746EC /* APRingTonesVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A848F8BA2DD6E30000B746EC /* APRingTonesVM.swift */; };
@@ -182,6 +180,8 @@
 		3DBEA17F2DE7149E000C6859 /* ASResourceCacheHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASResourceCacheHandler.swift; sourceTree = "<group>"; };
 		3DBEA1812DE71529000C6859 /* ASMediaDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMediaDownloader.swift; sourceTree = "<group>"; };
 		3DBEA1832DE71700000C6859 /* ASMediaPlayerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMediaPlayerController.swift; sourceTree = "<group>"; };
+		3DBEA1872DE728F8000C6859 /* ASGarageBandHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGarageBandHelper.h; sourceTree = "<group>"; };
+		3DBEA1882DE728F8000C6859 /* ASGarageBandHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGarageBandHelper.m; sourceTree = "<group>"; };
 		3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASRingToneCellView.swift; sourceTree = "<group>"; };
 		3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASViewTool.swift; sourceTree = "<group>"; };
 		3DCD57062DDB1158004AAB5B /* AudioConverter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioConverter.h; sourceTree = "<group>"; };
@@ -191,10 +191,8 @@
 		3DCD570A2DDB1158004AAB5B /* ExtAudioConverter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExtAudioConverter.m; sourceTree = "<group>"; };
 		3DCD570B2DDB1158004AAB5B /* lame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lame.h; sourceTree = "<group>"; };
 		3DCD570E2DDB1158004AAB5B /* tutorial-ring.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "tutorial-ring.mp4"; sourceTree = "<group>"; };
-		3DCD570F2DDB1158004AAB5B /* TSBandRingTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBandRingTool.swift; sourceTree = "<group>"; };
 		3DCD57102DDB1158004AAB5B /* null.band */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = null.band; sourceTree = "<group>"; };
 		3DCD572D2DDB1976004AAB5B /* AIPlayRingtones-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AIPlayRingtones-Bridging-Header.h"; sourceTree = "<group>"; };
-		3DCD572E2DDB1D87004AAB5B /* ASRingLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASRingLoadingView.swift; sourceTree = "<group>"; };
 		3DCD57302DDB2A5D004AAB5B /* ASGenerateRingToRingOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASGenerateRingToRingOperation.swift; sourceTree = "<group>"; };
 		3DCD57322DDB38E7004AAB5B /* ringAnimation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = ringAnimation.gif; sourceTree = "<group>"; };
 		3DCD57332DDB38E7004AAB5B /* rotatingAnimation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = rotatingAnimation.gif; sourceTree = "<group>"; };
@@ -222,7 +220,6 @@
 		A848F8A82DD6D74900B746EC /* APAudioToRingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APAudioToRingVC.swift; sourceTree = "<group>"; };
 		A848F8AA2DD6D75200B746EC /* ASMyRingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMyRingVC.swift; sourceTree = "<group>"; };
 		A848F8AC2DD6D75800B746EC /* APSettingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APSettingVC.swift; sourceTree = "<group>"; };
-		A848F8B22DD6D82300B746EC /* APConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APConfig.swift; sourceTree = "<group>"; };
 		A848F8B42DD6D82E00B746EC /* APConfig+Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APConfig+Color.swift"; sourceTree = "<group>"; };
 		A848F8B82DD6DF7700B746EC /* ASAppBtnView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASAppBtnView.swift; sourceTree = "<group>"; };
 		A848F8BA2DD6E30000B746EC /* APRingTonesVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APRingTonesVM.swift; sourceTree = "<group>"; };
@@ -364,7 +361,8 @@
 		3DCD57172DDB1158004AAB5B /* TSBandRingTool */ = {
 			isa = PBXGroup;
 			children = (
-				3DCD570F2DDB1158004AAB5B /* TSBandRingTool.swift */,
+				3DBEA1872DE728F8000C6859 /* ASGarageBandHelper.h */,
+				3DBEA1882DE728F8000C6859 /* ASGarageBandHelper.m */,
 				3DCD57062DDB1158004AAB5B /* AudioConverter.h */,
 				3DCD57072DDB1158004AAB5B /* AudioConverter.m */,
 				3DCD570C2DDB1158004AAB5B /* libmp3 */,
@@ -392,7 +390,6 @@
 				3DBEA14E2DE6E912000C6859 /* ASTouchBtn.swift */,
 				3DB4D4B12DDF0B940082596A /* FakeBlurView.swift */,
 				3DCD573A2DDB4E5E004AAB5B /* ASPageNullView.swift */,
-				3DCD572E2DDB1D87004AAB5B /* ASRingLoadingView.swift */,
 				3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */,
 				3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */,
 				3DBEA17B2DE70335000C6859 /* ASSaveResultManager.swift */,
@@ -571,7 +568,6 @@
 		A848F8B12DD6D80900B746EC /* APConfig */ = {
 			isa = PBXGroup;
 			children = (
-				A848F8B22DD6D82300B746EC /* APConfig.swift */,
 				A848F8B42DD6D82E00B746EC /* APConfig+Color.swift */,
 			);
 			path = APConfig;
@@ -860,14 +856,12 @@
 				A800FEB82DDAC826009DABDC /* TSNetWork+Error.swift in Sources */,
 				A848F8FA2DD7536700B746EC /*  Notification+Ex.swift in Sources */,
 				A848F8C02DD6E6FA00B746EC /* APRingTonesVC+TextView.swift in Sources */,
-				3DCD572F2DDB1D8A004AAB5B /* ASRingLoadingView.swift in Sources */,
 				3DB4D49D2DDC29AC0082596A /* ASRTRStyleView.swift in Sources */,
 				3DCD57312DDB2A66004AAB5B /* ASGenerateRingToRingOperation.swift in Sources */,
 				A848F8EA2DD74E3A00B746EC /* ASDBActionInfoModel.swift in Sources */,
 				3DCD573D2DDB5A3C004AAB5B /* ASMyRingCell.swift in Sources */,
 				3DBEA1672DE6F445000C6859 /* ASCustomAlert.swift in Sources */,
 				3DBEA11A2DE6B086000C6859 /* CustomTabBarController.m in Sources */,
-				A848F8B32DD6D82300B746EC /* APConfig.swift in Sources */,
 				A848F8FC2DD753A000B746EC /* ASProgressState.swift in Sources */,
 				3DBEA1822DE7152C000C6859 /* ASMediaDownloader.swift in Sources */,
 				A848F8A92DD6D74A00B746EC /* APAudioToRingVC.swift in Sources */,
@@ -883,7 +877,6 @@
 				3DBEA17A2DE6FFA1000C6859 /* ASInputView.swift in Sources */,
 				3DBEA14D2DE6E8AB000C6859 /* UILabel+AS.swift in Sources */,
 				3DB4D4932DDC25C10082596A /* APAudioToRingVM.swift in Sources */,
-				3DCD571F2DDB1158004AAB5B /* TSBandRingTool.swift in Sources */,
 				A800FEBB2DDACAF3009DABDC /* ASRingGeneratorVC.swift in Sources */,
 				A848F8F32DD7528D00B746EC /* ASBaseOperation.swift in Sources */,
 				A848F8BB2DD6E30600B746EC /* APRingTonesVM.swift in Sources */,
@@ -911,6 +904,7 @@
 				3DCD573B2DDB4E61004AAB5B /* ASPageNullView.swift in Sources */,
 				A848F8F12DD7528800B746EC /* ASBaseOperationQueue.swift in Sources */,
 				3DBEA12E2DE6BF19000C6859 /* ASBaseView.m in Sources */,
+				3DBEA1892DE728F8000C6859 /* ASGarageBandHelper.m in Sources */,
 				A848F8CD2DD6EAD300B746EC /* ASPromptTextView.swift in Sources */,
 				A848F8DB2DD7231E00B746EC /* APRingStyleVC.swift in Sources */,
 				A848F8D22DD7149D00B746EC /* ASDurationColView.swift in Sources */,

+ 1 - 1
AIPlayRingtones/AIPlayRingtones-Bridging-Header.h

@@ -23,4 +23,4 @@
 #import "ASColObserver.h"
 #import "ASFileManager.h"
 #import "ASWebViewController.h"
-
+#import "ASGarageBandHelper.h"

+ 0 - 7
AIPlayRingtones/APConfig/APConfig.swift

@@ -1,7 +0,0 @@
-//
-//  APConfig.swift
-//  AIPlayRingtones
-//
-//  Created by 100Years on 2025/5/15.
-//
-

+ 2 - 2
AIPlayRingtones/AppDelegate.swift

@@ -39,8 +39,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 //        if AppDelegate.isFirstInstallApp() {
 //            let bootPageVC = TSBootPageVC { [weak self] in
 //                guard let self = self else { return }
-//                UserDefaults.standard.set("1", forKey: "isFirstInstallApp")
-//                UserDefaults.standard.synchronize()
+                UserDefaults.standard.set("1", forKey: "isFirstInstallApp")
+                UserDefaults.standard.synchronize()
 //                goToTab()
 //            }
 //            let navi = ASBaseNavigationController(rootViewController: bootPageVC)

+ 0 - 2
AIPlayRingtones/AppPage/APLaunchVC/APLaunchVC.swift

@@ -30,8 +30,6 @@ class ASLaunchVC: UIViewController {
         }else {
             self.enterApp()
         }
-        
-        addNotifiy()
     }
 
     func addNotifiy() {

+ 1 - 2
AIPlayRingtones/Common/Common.swift

@@ -349,7 +349,6 @@ func kshareBand(needVip:Bool,
                 fileURL:URL,
                 fileName:String,
                 completion:((Bool)->Void)? = nil) -> Bool {
-
-    TSBandRingTool.creatBandRingTool().shareBandVC(vc: vc, fileURL: fileURL, fileName: fileName,completion: completion)
+    ASGarageBandHelper.create().shareToGarageBand(from: vc, withAudio: fileURL, fileName: fileName, completion: completion)
     return false
 }

+ 43 - 0
AIPlayRingtones/Common/TSBandRingTool/ASGarageBandHelper.h

@@ -0,0 +1,43 @@
+//
+//  ASGarageBandHelper.h
+//  AIPlayRingtones
+//
+//  Created by mini on 2025/5/28.
+//
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+#import <AVKit/AVKit.h>
+#import <AVFoundation/AVFoundation.h>
+NS_ASSUME_NONNULL_BEGIN
+
+@interface AudioTool : NSObject
+
+- (void)convertToBandFrom:(NSURL *)inputURL
+              outputPath:(NSString *)outputPath
+            templatePath:(NSString *)templatePath
+             completion:(void(^)(NSURL * _Nullable))completion;
+
+@end
+
+@interface ASGarageBandHelper : NSObject<AVPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
+
+@property (nonatomic, weak) UIViewController *presentingController;
+@property (nonatomic, strong) AVPlayer *mediaPlayer;
+@property (nonatomic, strong) AVPictureInPictureController *pipController;
+@property (nonatomic, strong) UIView *playerContainer;
+@property (nonatomic, strong) AudioTool *audioProcessor;
+
++ (instancetype)sharedInstance;
++ (instancetype)createHelper;
+
+- (BOOL)verifyGarageBandInstalled;
+- (void)shareToGarageBandFrom:(UIViewController *)controller
+                    withAudio:(NSURL *)fileURL
+                    fileName:(nullable NSString *)fileName
+                  completion:(nullable void(^)(BOOL success))completion;
+
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 288 - 0
AIPlayRingtones/Common/TSBandRingTool/ASGarageBandHelper.m

@@ -0,0 +1,288 @@
+//
+//  ASGarageBandHelper.m
+//  AIPlayRingtones
+//
+//  Created by mini on 2025/5/28.
+//
+
+#import "ASGarageBandHelper.h"
+#import "ExtAudioConverter.h"
+
+
+// AudioTool.m
+@implementation AudioTool
+
+- (void)convertToBandFrom:(NSURL *)inputURL
+              outputPath:(NSString *)outputPath
+            templatePath:(NSString *)templatePath
+             completion:(void(^)(NSURL * _Nullable))completion {
+    
+    // Copy template file
+    [NSFileManager.defaultManager copyItemAtPath:templatePath toPath:outputPath error:nil];
+    
+    if (![NSFileManager.defaultManager fileExistsAtPath:outputPath]) {
+        completion(nil);
+        return;
+    }
+    
+    // Convert audio
+    ExtAudioConverter *converter = [[ExtAudioConverter alloc] init];
+    converter.inputFile = inputURL.path;
+    converter.outputFile = [outputPath stringByAppendingPathComponent:@"/Media/ringtone.aiff"];
+    converter.outputFileType = kAudioFileAIFFType;
+    BOOL success = [converter convert];
+    
+    if (success && [NSFileManager.defaultManager fileExistsAtPath:outputPath]) {
+        completion([NSURL fileURLWithPath:outputPath]);
+    } else {
+        completion(nil);
+    }
+    
+    NSLog(@"=== Band file path: %@", outputPath);
+}
+
+@end
+
+@interface ASGarageBandHelper ()
+@property (nonatomic, strong) NSURL *currentBandURL;
+@end
+static ASGarageBandHelper *instance = nil;
+@implementation ASGarageBandHelper
+
+//+ (instancetype)sharedInstance {
+//  
+//    static dispatch_once_t onceToken;
+//    dispatch_once(&onceToken, ^{
+//        instance = [[ASGarageBandHelper alloc] init];
+//    });
+//    return instance;
+//}
+
++ (instancetype)createHelper {
+    instance = [[ASGarageBandHelper alloc] init];
+    return instance;
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _playerContainer = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
+        _audioProcessor = [[AudioTool alloc] init];
+    }
+    return self;
+}
+
+- (BOOL)verifyGarageBandInstalled {
+    NSURL *appURL = [NSURL URLWithString:@"garageband://"];
+    if ([[UIApplication sharedApplication] canOpenURL:appURL]) {
+        NSLog(@"GarageBand is installed");
+        return YES;
+    }
+    
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
+                                                                message:@"GarageBand is not installed, you need to install it."
+                                                         preferredStyle:UIAlertControllerStyleAlert];
+    
+    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
+                                             style:UIAlertActionStyleCancel
+                                           handler:nil]];
+    
+    [alert addAction:[UIAlertAction actionWithTitle:@"Download"
+                                             style:UIAlertActionStyleDefault
+                                           handler:^(UIAlertAction * _Nonnull action) {
+        NSURL *appStoreURL = [NSURL URLWithString:@"https://apps.apple.com/app/id408709785"];
+        if ([[UIApplication sharedApplication] canOpenURL:appStoreURL]) {
+            [[UIApplication sharedApplication] openURL:appStoreURL options:@{} completionHandler:nil];
+        }
+    }]];
+    
+    [self.presentingController presentViewController:alert animated:YES completion:nil];
+    return NO;
+}
+
+- (void)shareToGarageBandFrom:(UIViewController *)controller
+                    withAudio:(NSURL *)fileURL
+                    fileName:(nullable NSString *)fileName
+                  completion:(nullable void(^)(BOOL success))completion {
+    
+    self.presentingController = controller;
+    
+    if (![self verifyGarageBandInstalled]) {
+        if (completion) completion(NO);
+        return;
+    }
+    
+    if (![NSFileManager.defaultManager fileExistsAtPath:fileURL.path]) {
+        if (completion) completion(NO);
+        return;
+    }
+    
+    [self prepareBandFileWithAudio:fileURL
+                          fileName:fileName
+                       completion:^(NSURL * _Nullable bandURL) {
+        if (bandURL) {
+            if (completion) completion(YES);
+            [self presentSharingForBandFile:bandURL];
+        } else {
+            if (completion) completion(NO);
+            NSLog(@"Failed to create band file");
+        }
+    }];
+}
+
+- (void)prepareBandFileWithAudio:(NSURL *)audioURL
+                       fileName:(NSString *)fileName
+                    completion:(void(^)(NSURL * _Nullable))completion {
+    
+    NSString *fileNameToUse = fileName ?: @"Ringtone";
+    NSURL *templateURL = [self bandTemplatePath];
+    NSURL *outputDirectory = [self bandOutputDirectory];
+    
+    if (!templateURL || !outputDirectory) {
+        completion(nil);
+        return;
+    }
+    
+    NSURL *outputURL = [[outputDirectory URLByAppendingPathComponent:fileNameToUse] URLByAppendingPathExtension:@"band"];
+    
+    [self.audioProcessor convertToBandFrom:audioURL
+                               outputPath:outputURL.path
+                            templatePath:templateURL.path
+                             completion:^(NSURL * _Nullable resultURL) {
+        completion(resultURL);
+    }];
+}
+
+- (void)presentSharingForBandFile:(NSURL *)bandURL {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self setupTutorialPlayer];
+        
+        UIActivityViewController *shareSheet = [[UIActivityViewController alloc] initWithActivityItems:@[bandURL]
+                                                                                applicationActivities:nil];
+        
+        [self.presentingController presentViewController:shareSheet
+                                               animated:YES
+                                             completion:^{
+            [self attemptPictureInPicture];
+        }];
+        
+        // Fallback check
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            if (self.presentingController.presentedViewController != shareSheet) {
+                NSLog(@"Presentation failed");
+                [self.playerContainer removeFromSuperview];
+            }
+        });
+    });
+}
+
+- (void)setupTutorialPlayer {
+    NSURL *videoURL = [NSBundle.mainBundle URLForResource:@"tutorial-ring" withExtension:@"mp4"];
+    if (!videoURL || !AVPictureInPictureController.isPictureInPictureSupported) {
+        return;
+    }
+    
+    NSError *error;
+    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
+                                    withOptions:AVAudioSessionCategoryOptionMixWithOthers
+                                          error:&error];
+    
+    AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:[AVAsset assetWithURL:videoURL]];
+    if (!self.mediaPlayer) {
+        self.mediaPlayer = [AVPlayer playerWithPlayerItem:item];
+    } else {
+        [self.mediaPlayer replaceCurrentItemWithPlayerItem:item];
+    }
+    
+    self.mediaPlayer.allowsExternalPlayback = YES;
+    [self.presentingController.view addSubview:self.playerContainer];
+    
+    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.mediaPlayer];
+    layer.frame = CGRectMake(0, 0, 1, 1);
+    [self.playerContainer.layer addSublayer:layer];
+    
+    self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:layer];
+    self.pipController.delegate = self;
+}
+
+- (void)attemptPictureInPicture {
+    if (self.mediaPlayer.status == AVPlayerStatusReadyToPlay) {
+        [self.pipController startPictureInPicture];
+        [self.mediaPlayer play];
+        return;
+    }
+
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        [self attemptPictureInPicture];
+        
+    });
+
+}
+
+#pragma mark - PiP Delegate
+- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
+    [self.playerContainer removeFromSuperview];
+}
+
+- (BOOL)playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController {
+    return YES;
+}
+
+- (void)playerViewController:(AVPlayerViewController *)playerViewController
+restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        if (!playerViewController.presentingViewController) {
+            [self.presentingController presentViewController:playerViewController
+                                                  animated:NO
+                                                completion:^{
+                completionHandler(YES);
+            }];
+        } else {
+            completionHandler(YES);
+        }
+    });
+}
+- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
+failedToStartPictureInPictureWithError:(NSError *)error {
+    NSLog(@"Failed to start Picture in Picture: %@", error.localizedDescription);
+  
+}
+#pragma mark - File Path Helpers
+- (NSURL *)bandTemplatePath {
+    NSURL *directory = [self bandOutputDirectory];
+    if (!directory) return nil;
+    
+    NSString *path = [directory.path stringByAppendingPathComponent:@"placeHolderBand.band"];
+    
+    if (![NSFileManager.defaultManager fileExistsAtPath:path]) {
+        NSString *localPath = [NSBundle.mainBundle pathForResource:@"null" ofType:@"band"];
+        if (localPath) {
+            [NSFileManager.defaultManager copyItemAtPath:localPath toPath:path error:nil];
+        }
+    }
+    
+    return [NSURL fileURLWithPath:path];
+}
+
+- (NSURL *)bandOutputDirectory {
+    NSURL *documents = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
+    NSURL *ringFolder = [documents URLByAppendingPathComponent:@"ringDirectory"];
+    
+    return [self createDirectoryIfNeeded:ringFolder];
+}
+
+- (NSURL *)createDirectoryIfNeeded:(NSURL *)url {
+    BOOL isDir = NO;
+    if ([NSFileManager.defaultManager fileExistsAtPath:url.path isDirectory:&isDir] && isDir) {
+        return url;
+    }
+    
+    NSError *error;
+    [NSFileManager.defaultManager createDirectoryAtURL:url
+                         withIntermediateDirectories:YES
+                                          attributes:nil
+                                               error:&error];
+    return error ? nil : url;
+}
+
+@end

+ 0 - 288
AIPlayRingtones/Common/TSBandRingTool/TSBandRingTool.swift

@@ -1,288 +0,0 @@
-//
-//  TSBandRingTool.swift
-//  AIRingtone
-//
-//  Created by 100Years on 2025/3/4.
-//
-import AVKit
-import AVFoundation
-class TSBandRingTool:NSObject {
-    static var shared = TSBandRingTool()
-    private var tutorialPlayer: AVPlayer?
-    private var pictureController: AVPictureInPictureController?
-    private lazy var playContentView = UIView(frame: UIScreen.main.bounds)
-    private lazy var audioConvertTool = AudioTool()
-    
-    lazy var ringLoadingView: ASRingLoadingView = {
-        let ringLoadingView = ASRingLoadingView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight))
-        return ringLoadingView
-    }()
-    weak var targetVC:UIViewController?
-
-    static func creatBandRingTool() -> TSBandRingTool{
-        TSBandRingTool.shared = TSBandRingTool()
-        return TSBandRingTool.shared
-    }
-    
-    deinit {
-        logPrint("TSBandRingTool deinit")
-    }
-    func checkGarageBandInstallation() -> Bool {
-        // GarageBand 的 URL Scheme
-        let garageBandScheme = "garageband://"
-
-        if let url = URL(string: garageBandScheme), UIApplication.shared.canOpenURL(url) {
-            print("GarageBand 已安装")
-            return true
-        } else {
-            let ac = UIAlertController(title: nil,
-                                       message: "GarageBand is not installed, you need to install it.", preferredStyle: .alert)
-            ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
-            ac.addAction(UIAlertAction(title: "Download", style: .default, handler: { _ in
-                // GarageBand 在 App Store 的链接
-                let garageBandAppStoreURL = URL(string: "https://apps.apple.com/app/id408709785")!
-                if UIApplication.shared.canOpenURL(garageBandAppStoreURL) {
-                    UIApplication.shared.open(garageBandAppStoreURL, options: [:], completionHandler: nil)
-                }
-            }))
-            targetVC?.present(ac, animated: true)
-          
-            return false
-        }
-    }
-    
-    func shareBandVC(vc:UIViewController,
-                     fileURL: URL,
-                   fileName:String?,
-                   completion: ((Bool) -> Void)? = nil) {
-        self.targetVC = vc
-
-        if checkGarageBandInstallation() == false {
-            completion?(false)
-            return
-        }
-        if ASFileManager.fileExists(at: fileURL) == false{
-            completion?(false)
-            return
-        }
-
-        self.createBand(with: fileURL, fileName: fileName) { [weak self]  bandURL in
-            guard let self = self else { return }
-            if let url = bandURL {
-                completion?(true)
-                self.shareRing(fileUrl: url)
-            }else{
-                completion?(false)
-                logPrint("Failed to set, please try another")
-            }
-        }
-    }
-    
-    // 创建用于分享库乐队的band文件
-    func createBand(with fileURL: URL?, fileName: String?,
-                    completion: ((URL?) -> Void)?) {
-        let fileName = fileName ?? "Ringtone"
-        guard let fileURL = fileURL,
-              let bandPath = Self.bandPlacefolder,
-              let directory = Self.ringDirectory else {
-            completion?(nil)
-            return
-        }
-        
-        let bandURL = URL(fileURLWithPath: bandPath)
-        let outputURL = URL(fileURLWithPath: directory).appendingPathComponent(fileName)
-        
-        audioConvertTool.convertToBand(from: fileURL, to: outputURL.path, bandFileURL: bandURL) { shareBandURL in
-            completion?(shareBandURL)
-        }
-    }
-    
-    func shareRing(fileUrl: URL) {
-        DispatchQueue.main.async {
-            guard let targetVC = self.targetVC else { return }
-            
-            self.showTutorialVideo()
-            
-            let vc = UIActivityViewController(activityItems: [fileUrl], applicationActivities: nil)
-            targetVC.present(vc, animated: true, completion: {
-                self.tryStartPictureInPicture()
-            })
-            
-            // 延迟检查是否真正弹出
-            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
-                if self.targetVC?.presentedViewController != vc {
-                    logPrint("⚠️ present 失败:系统未正确响应")
-                    // 这里可以触发回调或通知
-                    self.playContentView.removeFromSuperview()
-                }
-            }
-        }
-    }
-    
-}
-
-extension TSBandRingTool : AVPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate {
-    
-    func tryStartPictureInPicture() {
-        guard let player = tutorialPlayer else {
-            return
-        }
-        if player.status == .readyToPlay {
-            self.pictureController?.startPictureInPicture()
-            tutorialPlayer?.play()
-            return
-        }
-        DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
-            self.tryStartPictureInPicture()
-        }
-    }
-    
-    func showTutorialVideo() {
-        guard let url = Bundle.main.url(forResource: "tutorial-ring", withExtension: "mp4") else {
-            return
-        }
-        guard AVPictureInPictureController.isPictureInPictureSupported() else {
-            return
-        }
-        do {
-            try AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
-        } catch {
-            
-        }
-        
-        let playerItem = AVPlayerItem(asset: AVAsset(url: url))
-        if tutorialPlayer == nil {
-            tutorialPlayer = AVPlayer(playerItem: playerItem)
-        } else {
-            tutorialPlayer?.replaceCurrentItem(with: playerItem)
-        }
-        tutorialPlayer?.allowsExternalPlayback = true
-//        playContentView.backgroundColor = .black.withAlphaComponent(0.1)
-        targetVC?.view.addSubview(playContentView)
-        
-        let layer = AVPlayerLayer(player: tutorialPlayer)
-        layer.frame = CGRect(x: 0, y: 0, width: 1, height: 1)
-//        layer.backgroundColor = UIColor.yellow.cgColor
-        playContentView.layer.addSublayer(layer)
-
-        let pictureVC = AVPictureInPictureController(playerLayer: layer)
-        pictureVC?.delegate = self
-        pictureController = pictureVC
-        
-    }
-    
-    func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
-        playContentView.removeFromSuperview()
-    }
-    
-    func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
-        
-    }
-    
-    func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_ playerViewController: AVPlayerViewController) -> Bool {
-        return true
-    }
-    
-    func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
-        DispatchQueue.main.async {
-            if playerViewController.presentingViewController == nil {
-                self.targetVC?.present(playerViewController, animated: false)
-            }
-            completionHandler(true)
-        }
-    }
-    
-    
-}
-extension TSBandRingTool {
-    
-    static var bandPlacefolder: String? {
-        guard let cacheDirectory = ringDirectory else {
-            return nil
-        }
-        let path = NSString(string: cacheDirectory).appendingPathComponent("placeHolderBand.band")
-        
-        // 步骤1
-        if !FileManager.default.fileExists(atPath: path),
-            let localFile = Bundle.main.path(forResource: "null.band", ofType: nil) {
-            try? FileManager.default.copyItem(atPath: localFile, toPath: path)
-        }
-        return path
-    }
-    
-    // 铃声文件夹
-    static var ringDirectory: String? {
-        guard let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
-            return nil
-        }
-        let ringDirectory = documentUrl.appendingPathComponent("ringDirectory")
-        
-        // 创建文件夹
-        return FileManager.default.getFolder(at: ringDirectory)?.path
-    }
-}
-
-
-extension FileManager {
-    // 获取文件夹,如果不存在,则创建, 返回空则意味着创建失败
-    func getFolder(at url: URL) -> URL? {
-        guard !isFolderExists(at: url) else {
-            return url
-        }
-        do {
-            try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
-            return url
-        } catch {
-            return nil
-        }
-    }
-    
-    func isFolderExists(at url: URL) -> Bool {
-        var isDirectory: ObjCBool = false
-        if fileExists(atPath: url.path, isDirectory: &isDirectory) && isDirectory.boolValue == true {
-            return true // 文件夹已存在
-        } else {
-            return false // 文件夹不存在或者路径错误
-        }
-    }
-}
-
-
-class AudioTool {
-
-    /// 创建用于分享库乐队的band文件
-    func convertToBand(from fileURL: URL, to filePath: String,
-                       bandFileURL: URL,
-                       completion: ((URL?) -> Void)?) {
-        
-        let copyBandURL = URL(fileURLWithPath: filePath).deletingPathExtension().appendingPathExtension("band")
-        // 步骤3:复制band文件
-        try? FileManager.default.copyItem(atPath: bandFileURL.path, toPath: copyBandURL.path)
-        
-        guard FileManager.default.fileExists(atPath: copyBandURL.path) else {
-            // 复制失败
-            completion?(nil)
-            return
-        }
-        
-        // 步骤4:格式转换为aiff
-        let converter = ExtAudioConverter()
-        converter.inputFile = fileURL.path
-        converter.outputFile = copyBandURL.path.appending("/Media/ringtone.aiff")
-        converter.outputFileType = kAudioFileAIFFType
-        let success = converter.convert()
-        
-        guard success,
-              FileManager.default.fileExists(atPath: copyBandURL.path) else {
-            // 转换失败
-            completion?(nil)
-            return
-        }
-        
-        completion?(copyBandURL)
-        logPrint("===bandPath: \(copyBandURL)")
-    }
-    
-
-
-}

+ 0 - 140
AIPlayRingtones/CommonView/ASRingLoadingView.swift

@@ -1,140 +0,0 @@
-//
-//  ASRingLoadingView.swift
-//  AIPlayRingtones
-//
-//  Created by mini on 2025/5/19.
-//
-
-
-import Kingfisher
-
-class ASRingLoadingView: ASBaseView {
-    
-    static let shared = ASRingLoadingView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight))
-    
-    func showWindow(){
-        let window:UIWindow? = ASWindTool.getKeyWindow()
-        if let window = window {
-            window.addSubview(ASRingLoadingView.shared)
-            ASRingLoadingView.shared.isRotating = true
-        }
-    }
-    
-    func remove(){
-        ASRingLoadingView.shared.removeFromSuperview()
-    }
-    
-    lazy var animatedImageView: AnimatedImageView = {
-        let animatedImageView = AnimatedImageView()
-        animatedImageView.autoPlayAnimatedImage = false
-        if let gifURL = Bundle.main.url(forResource: "ringAnimation", withExtension: "gif") {
-            animatedImageView.kf.setImage(with: gifURL, options: [.cacheOriginalImage]) { result in
-                switch result {
-                case .success(let value):
-                    print("GIF 加载成功: \(value.source.url?.absoluteString ?? "")")
-                case .failure(let error):
-                    print("GIF 加载失败: \(error.localizedDescription)")
-                }
-            }
-        }
-        
-        return animatedImageView
-    }()
-    
-    lazy var textLabel: UILabel = {
-        let textLabel = UILabel.createLabel(text:"Loading...".localized, font: .font(size: 12),textColor: .fromHex("#FFFFFF").withAlphaComponent(0.8) ,textAlignment: .center)
-        return textLabel
-    }()
-    
-    
-    private var targetView: UIView = UIView()
-    
-    override func creatUI() {
-        contentView.backgroundColor = .black
-        contentView.cornerRadius = 16
-        contentView.snp.remakeConstraints { make in
-            make.width.height.equalTo(100)
-            make.center.equalToSuperview()
-        }
-        
-        contentView.addSubview(animatedImageView)
-        animatedImageView.snp.makeConstraints { make in
-            make.width.height.equalTo(60)
-            make.centerX.equalToSuperview()
-            make.top.equalTo(12)
-        }
-  
-        contentView.addSubview(textLabel)
-        textLabel.snp.makeConstraints { make in
-            make.height.equalTo(18.0)
-            make.top.equalTo(animatedImageView.snp.bottom).offset(2)
-            make.leading.trailing.equalToSuperview()
-        }
-        
-    }
-    
-    
-    override func dealThings() {
-        // 监听应用生命周期事件
-        NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(handleAppDidEnterBackground),
-            name: UIApplication.didEnterBackgroundNotification,
-            object: nil
-        )
-        
-        NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(handleAppWillEnterForeground),
-            name: UIApplication.willEnterForegroundNotification,
-            object: nil
-        )
-    }
-    
-    
-    lazy var isRotating = false{
-        didSet{
-            if isRotating == true{
-                startRotating(view: animatedImageView)
-            }else{
-                stopRotating(view: animatedImageView)
-            }
-        }
-    }
-    
-    
-    /// 开始旋转
-    func startRotating(view:UIView) {
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            self.animatedImageView.startAnimating()
-        }
-    }
-
-    /// 停止旋转
-    func stopRotating(view:UIView) {
-        animatedImageView.stopAnimating()
-    }
-    
-    func showLoading(text:String){
-        animatedImageView.isHidden = false
-
-        textLabel.text = text
-        isRotating = true
-    }
-    
-    func showError(text:String){
-        animatedImageView.isHidden = true
-
-        
-        textLabel.text = text
-        isRotating = false
-    }
-    
-    @objc private func handleAppDidEnterBackground() {
-        stopRotating(view: targetView)
-    }
-
-    @objc private func handleAppWillEnterForeground() {
-        isRotating = isRotating
-    }
-}