123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- //
- // 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
|