├── AudioShareSDK.h ├── AudioShareSDK.m └── README.md /AudioShareSDK.h: -------------------------------------------------------------------------------- 1 | // AudioShareSDK.h 2 | // Copyright (C)2012 Jonatan Liljedahl 3 | 4 | #import 5 | #import 6 | 7 | @interface AudioShare : NSObject 8 | 9 | typedef void(^AudioShareImportBlock)(NSString *path); 10 | 11 | + (AudioShare*) sharedInstance; 12 | 13 | // Export to AudioShare 14 | - (BOOL) addSoundFromPath:(NSString*)path withName:(NSString*)name; 15 | - (BOOL) addSoundFromURL:(NSURL*)url withName:(NSString*)name; 16 | - (BOOL) addSoundFromData:(NSData*)data withName:(NSString*)name; 17 | 18 | // Import from AudioShare 19 | - (BOOL) initiateSoundImport; 20 | - (BOOL) checkPendingImport:(NSURL*)url withBlock:(AudioShareImportBlock)block; 21 | 22 | // Convenience utility that returns filename extension for path if it's an audio or midi file 23 | + (NSString*)findFileType:(NSString*)path; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /AudioShareSDK.m: -------------------------------------------------------------------------------- 1 | // AudioShareSDK.h 2 | // Copyright (C)2012 Jonatan Liljedahl 3 | 4 | #import "AudioShareSDK.h" 5 | #import 6 | #import 7 | 8 | #define BM_CLIPBOARD_CHUNK_SIZE (5 * 1024 * 1024) 9 | 10 | @implementation AudioShare 11 | 12 | + (AudioShare*) sharedInstance { 13 | static AudioShare *a = nil; 14 | if(!a) 15 | a = [[AudioShare alloc] init]; 16 | return a; 17 | } 18 | 19 | + (void)showAlertController:(UIAlertController*)alertController { 20 | UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController; 21 | 22 | while(presentingViewController.presentedViewController != nil) { 23 | presentingViewController = presentingViewController.presentedViewController; 24 | } 25 | 26 | [presentingViewController presentViewController:alertController animated:YES completion:nil]; 27 | } 28 | 29 | + (void)openURL:(NSURL *)URL { 30 | UIApplication *application = [UIApplication sharedApplication]; 31 | 32 | if ([application respondsToSelector:@selector(openURL:options:completionHandler:)]) { 33 | [application openURL:URL options:@{} 34 | completionHandler:nil]; 35 | } else { 36 | [application openURL:URL]; 37 | } 38 | } 39 | 40 | - (NSString*)escapeString:(NSString*)string { 41 | NSString *s = [string stringByAddingPercentEncodingWithAllowedCharacters: 42 | [NSCharacterSet URLQueryAllowedCharacterSet]]; 43 | return s; 44 | } 45 | 46 | - (BOOL)addSoundFromData:(NSData*)data withName:(NSString*)name { 47 | UIPasteboard *board = [UIPasteboard generalPasteboard]; 48 | if (!data) { 49 | 50 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Sorry" 51 | message:@"Something went wrong. Could not export audio!" 52 | preferredStyle:UIAlertControllerStyleAlert]; 53 | 54 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 55 | style:UIAlertActionStyleDefault 56 | handler:^(UIAlertAction *action){}]; 57 | [alertController addAction:okAction]; 58 | [AudioShare showAlertController:alertController]; 59 | 60 | return NO; 61 | } 62 | NSUInteger sz = [data length]; 63 | NSUInteger chunkNumbers = (sz / BM_CLIPBOARD_CHUNK_SIZE) + 1; 64 | NSMutableArray *items = [NSMutableArray arrayWithCapacity:chunkNumbers]; 65 | NSRange curRange; 66 | for (NSUInteger i = 0; i < chunkNumbers; i++) { 67 | curRange.location = i * BM_CLIPBOARD_CHUNK_SIZE; 68 | curRange.length = MIN(BM_CLIPBOARD_CHUNK_SIZE, sz - curRange.location); 69 | NSData *subData = [data subdataWithRange:curRange]; 70 | NSDictionary *dict = [NSDictionary dictionaryWithObject:subData forKey:(NSString *)kUTTypeAudio]; 71 | [items addObject:dict]; 72 | } 73 | board.items = items; 74 | 75 | name = [self escapeString:name]; 76 | NSURL *asURL = [NSURL URLWithString:[NSString stringWithFormat:@"audiosharecmd://addFromPaste?%@",name]]; 77 | if(![[UIApplication sharedApplication] canOpenURL:asURL]) { 78 | 79 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AudioShare" 80 | message:@"Audio was copied to pasteboard and can now be pasted in other apps.\n\nInstall AudioShare for easy storage and management of all your soundfiles, and more copy/paste functionality. You can get it on the App Store." 81 | preferredStyle:UIAlertControllerStyleAlert]; 82 | 83 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel action") 84 | style:UIAlertActionStyleCancel 85 | handler:^(UIAlertAction *action){}]; 86 | 87 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 88 | style:UIAlertActionStyleDefault 89 | handler:^(UIAlertAction *action) 90 | { 91 | [AudioShare openURL:[NSURL URLWithString:@"http://kymatica.com/audioshare/download.php"]]; 92 | }]; 93 | 94 | [alertController addAction:cancelAction]; 95 | [alertController addAction:okAction]; 96 | 97 | [AudioShare showAlertController:alertController]; 98 | 99 | return NO; 100 | } 101 | [AudioShare openURL:asURL]; 102 | return YES; 103 | } 104 | 105 | - (BOOL)addSoundFromURL:(NSURL*)url withName:(NSString*)name { 106 | NSString *srcPath = [url path]; 107 | return [self addSoundFromPath:srcPath withName:name]; 108 | } 109 | 110 | - (BOOL)addSoundFromPath:(NSString*)path withName:(NSString*)name { 111 | if(![[NSFileManager defaultManager] fileExistsAtPath:path]) { 112 | 113 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Sorry" 114 | message:@"The file does not exist!" 115 | preferredStyle:UIAlertControllerStyleAlert]; 116 | 117 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 118 | style:UIAlertActionStyleDefault 119 | handler:^(UIAlertAction *action){}]; 120 | 121 | [alertController addAction:okAction]; 122 | 123 | [AudioShare showAlertController:alertController]; 124 | 125 | return NO; 126 | } 127 | NSData *dataFile = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:path] options:NSDataReadingMappedIfSafe error:NULL]; 128 | return [self addSoundFromData:dataFile withName:name]; 129 | } 130 | 131 | - (NSString*)findCallbackScheme { 132 | NSBundle* mainBundle = [NSBundle mainBundle]; 133 | NSArray* cfBundleURLTypes = [mainBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]; 134 | if ([cfBundleURLTypes isKindOfClass:[NSArray class]] && [cfBundleURLTypes lastObject]) { 135 | for(NSDictionary* cfBundleURLTypes0 in cfBundleURLTypes) { 136 | if ([cfBundleURLTypes0 isKindOfClass:[NSDictionary class]]) { 137 | NSArray* cfBundleURLSchemes = [cfBundleURLTypes0 objectForKey:@"CFBundleURLSchemes"]; 138 | if ([cfBundleURLSchemes isKindOfClass:[NSArray class]]) { 139 | for (NSString* scheme in cfBundleURLSchemes) { 140 | if ([scheme isKindOfClass:[NSString class]] && [scheme hasSuffix:@".audioshare"]) { 141 | return scheme; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | return nil; 149 | } 150 | 151 | - (BOOL)initiateSoundImport { 152 | NSString *callback = [self findCallbackScheme]; 153 | if(!callback) { 154 | 155 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Missing callback URL" 156 | message:@"Hello developer. This app does not expose the needed appname.audioshare:// callback URL!" 157 | preferredStyle:UIAlertControllerStyleAlert]; 158 | 159 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 160 | style:UIAlertActionStyleDefault 161 | handler:^(UIAlertAction *action){}]; 162 | 163 | [alertController addAction:okAction]; 164 | 165 | [AudioShare showAlertController:alertController]; 166 | 167 | return NO; 168 | } 169 | NSURL *asURL = [NSURL URLWithString:[NSString stringWithFormat:@"audioshare.import://%@",callback]]; 170 | if(![[UIApplication sharedApplication] canOpenURL:asURL]) { 171 | UIPasteboard *board = [UIPasteboard generalPasteboard]; 172 | NSArray *typeArray = @[(NSString *) kUTTypeAudio]; 173 | NSIndexSet *set = [board itemSetWithPasteboardTypes:typeArray]; 174 | BOOL hasAudio = [board containsPasteboardTypes:typeArray inItemSet:set]; 175 | 176 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AudioShare" 177 | message:[hasAudio?@"Audio was pasted from pasteboard.":@"No audio in pasteboard." stringByAppendingString:@"\n\nInstall AudioShare for easy storage and management of all your soundfiles, and more copy/paste functionality. You can get it on the App Store."] 178 | preferredStyle:UIAlertControllerStyleAlert]; 179 | 180 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel action") 181 | style:UIAlertActionStyleCancel 182 | handler:^(UIAlertAction *action){}]; 183 | 184 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 185 | style:UIAlertActionStyleDefault 186 | handler:^(UIAlertAction *action) 187 | { 188 | [AudioShare openURL:[NSURL URLWithString:@"http://kymatica.com/audioshare/download.php"]]; 189 | }]; 190 | 191 | [alertController addAction:cancelAction]; 192 | [alertController addAction:okAction]; 193 | 194 | [AudioShare showAlertController:alertController]; 195 | 196 | if(hasAudio) 197 | asURL = [NSURL URLWithString:[callback stringByAppendingString:@"://PastedAudio"]]; 198 | else 199 | return NO; 200 | } 201 | [AudioShare openURL:asURL]; 202 | return YES; 203 | } 204 | 205 | - (NSString*)writeSoundImport:(NSString*)filename { 206 | UIPasteboard *board = [UIPasteboard generalPasteboard]; 207 | NSArray *typeArray = [NSArray arrayWithObject:(NSString *) kUTTypeAudio]; 208 | NSIndexSet *set = [board itemSetWithPasteboardTypes:typeArray]; 209 | if (!set) 210 | return nil; 211 | NSArray *items = [board dataForPasteboardType:(NSString *) kUTTypeAudio inItemSet:set]; 212 | if (items) { 213 | NSUInteger cnt = [items count]; 214 | if (!cnt) 215 | return nil; 216 | NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; 217 | [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; 218 | if (![[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil]) 219 | return nil; 220 | NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:path]; 221 | if (!handle) 222 | return nil; 223 | for (NSUInteger i = 0; i < cnt; i++) 224 | [handle writeData:[items objectAtIndex:i]]; 225 | [handle closeFile]; 226 | 227 | if(![[filename pathExtension] length]) { 228 | NSString *ext = [AudioShare findFileType:path]; 229 | if(ext) { 230 | NSString *newPath = [path stringByAppendingPathExtension:ext]; 231 | if([[NSFileManager defaultManager] moveItemAtPath:path toPath:newPath error:nil]) 232 | path = newPath; 233 | } 234 | } 235 | 236 | return path; 237 | } 238 | return nil; 239 | } 240 | 241 | - (BOOL)checkPendingImport:(NSURL *)url withBlock:(AudioShareImportBlock)block { 242 | NSString *scheme = [self findCallbackScheme]; 243 | if([[url scheme] isEqualToString:scheme]) { 244 | NSString *name = [url host]; 245 | NSString *path = [self writeSoundImport:name]; 246 | if(!path) { 247 | 248 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Sorry" 249 | message:@"There was a problem trying to import a sound from AudioShare!" 250 | preferredStyle:UIAlertControllerStyleAlert]; 251 | 252 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 253 | style:UIAlertActionStyleDefault 254 | handler:^(UIAlertAction *action){}]; 255 | 256 | [alertController addAction:okAction]; 257 | 258 | [AudioShare showAlertController:alertController]; 259 | 260 | return NO; 261 | } 262 | block(path); 263 | return YES; 264 | } else { 265 | return NO; 266 | } 267 | } 268 | 269 | + (void)load { 270 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidLaunch) name:UIApplicationDidFinishLaunchingNotification object:nil]; 271 | } 272 | 273 | + (void)appDidLaunch { 274 | NSBundle *bundle = [NSBundle mainBundle]; 275 | NSDictionary *info = bundle.infoDictionary; 276 | NSArray *list = info[@"LSApplicationQueriesSchemes"]; 277 | if(!([list containsObject:@"audioshare.import"] && [list containsObject:@"audiosharecmd"])) { 278 | 279 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Missing URL schemes in whitelist!" 280 | message:@"Hello developer. This app does not whitelist the AudioShare URL schemes, needed for iOS 9. See instructions at http://github.com/lijon/AudioShareSDK" 281 | preferredStyle:UIAlertControllerStyleAlert]; 282 | 283 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") 284 | style:UIAlertActionStyleDefault 285 | handler:^(UIAlertAction *action){}]; 286 | 287 | [alertController addAction:okAction]; 288 | 289 | [AudioShare showAlertController:alertController]; 290 | } 291 | } 292 | 293 | + (BOOL)fileIsMIDI:(NSString*)path { 294 | const char *cpath = [path fileSystemRepresentation]; 295 | FILE *fp = fopen(cpath,"r"); 296 | if(fp) { 297 | char buf[5]; 298 | fread(buf,1,4,fp); 299 | fclose(fp); 300 | buf[4]='\0'; 301 | if(strcmp(buf, "MThd")==0) 302 | return YES; 303 | } 304 | return NO; 305 | } 306 | 307 | + (NSString*)findFileType:(NSString*)path { 308 | if([self fileIsMIDI:path]) 309 | return @"mid"; 310 | NSURL *audioFileURL = [NSURL fileURLWithPath:path]; 311 | AudioFileID af = NULL; 312 | AudioFileOpenURL((__bridge CFURLRef)audioFileURL, kAudioFileReadPermission, 0, &af); 313 | if(!af) return nil; 314 | AudioFileTypeID typeID; 315 | UInt32 size = sizeof(typeID); 316 | AudioFileGetProperty(af, kAudioFilePropertyFileFormat, &size, &typeID); 317 | AudioFileClose(af); 318 | switch(typeID) { 319 | case kAudioFileAIFFType: return @"aiff"; 320 | case kAudioFileAIFCType: return @"aifc"; 321 | case kAudioFileWAVEType: return @"wav"; 322 | case kAudioFileSoundDesigner2Type: return @"sd2"; 323 | case kAudioFileNextType: return @"nxt"; 324 | case kAudioFileMP3Type: return @"mp3"; 325 | case kAudioFileMP2Type: return @"mp2"; 326 | case kAudioFileMP1Type: return @"mp1"; 327 | case kAudioFileAC3Type: return @"ac3"; 328 | case kAudioFileAAC_ADTSType: return @"adts"; 329 | case kAudioFileMPEG4Type: return @"mp4"; 330 | case kAudioFileM4AType: return @"m4a"; 331 | case kAudioFileCAFType: return @"caf"; 332 | case kAudioFile3GPType: return @"3gp"; 333 | case kAudioFile3GP2Type: return @"3gp2"; 334 | case kAudioFileAMRType: return @"amr"; 335 | default: 336 | return nil; 337 | } 338 | } 339 | 340 | @end 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AudioShare SDK 2 | ============== 3 | 4 | This SDK will let you export audio to, or import audio from, the AudioShare app for iPhone, iPad and iPod touch. 5 | 6 | AudioShare - audio document manager, is a simple tool to manage all your sounds in a single 7 | place on your device, with easy transferring of sounds between AudioShare and other apps or 8 | your computer. Read more about it here: http://kymatica.com/audioshare 9 | 10 | Usage 11 | ----- 12 | First, copy the files `AudioShareSDK.h` and `AudioShareSDK.m` into your project. 13 | 14 | Don't forget to import the header: 15 | 16 | #import "AudioShareSDK.h" 17 | 18 | You also need to add the MobileCoreServices framework to your project. 19 | 20 | iOS 9 Support 21 | ------------- 22 | Since iOS 9, apps must whitelist URL schemes to allow querying them with canOpenURL, which this SDK does. 23 | 24 | For your app to work with this SDK on iOS 9, you *must* add the following entry to your `Info.plist`: 25 | 26 | LSApplicationQueriesSchemes 27 | 28 | audioshare.import 29 | audiosharecmd 30 | 31 | 32 | You *must* also add the `MobileCoreServices.framework` to your project. Otherwise, your build will fail with errors stating that `_kUTTypeAudio` is an undefined symbol. 33 | 34 | AudioShare SDK now uses ARC 35 | --------------------------- 36 | If your project is not ARC-enabled, you'll need to enable ARC for `AudioShareSDK.m`: 37 | 38 | 1. Select the project in the left-hand Project Navigator tree. 39 | 40 | 2. Click on the "Build Phases" tab at the top. 41 | 42 | 3. Click on "Compile Sources" category to show the enclosed files. 43 | 44 | 4. Select to highlight the `AudioShareSDK.m` file. 45 | 46 | 5. Double-click on the right-hand side of the selected row, under the "Compiler Flags" column. 47 | 48 | 6. Type: `-fobjc-arc` into the field, and press the return key to commit the new compiler flag. 49 | 50 | Export to AudioShare 51 | -------------------- 52 | The typical usage is to have a button or menu item labelled "Export to AudioShare" that 53 | transfers a soundfile from your own app into AudioShare. Just put the following line in 54 | the code that gets called when the user taps the button: 55 | 56 | [[AudioShare sharedInstance] addSoundFromPath:thePathToYourFile withName:@"My Sound"]; 57 | 58 | In case you have your sound in memory, you can send an NSData instead: 59 | 60 | [[AudioShare sharedInstance] addSoundFromData:yourSoundData withName:@"My Sound"]; 61 | 62 | These methods will check that a recent enough version of AudioShare is installed, and otherwise 63 | present the user with the option to view the app on the App Store. If AudioShare was installed, 64 | it will open AudioShare and import the audio, and return YES if successfull. 65 | 66 | If AudioShare was not installed, it will just copy the audio to the general pasteboard. 67 | 68 | Import from AudioShare 69 | ---------------------- 70 | Since AudioShare version 2.5, you can also easily import sound from AudioShare into your own app. 71 | 72 | 1. First, declare a new URL type in your Info.plist with the scheme `yourAppName.audioshare`. Replace yourAppName with a unique name for your app, but keep the `.audioshare` suffix. 73 | 74 | 2. Then, in your app delegates openURL handler method, call `checkPendingImport:withBlock:` to handle the import: 75 | 76 | if([[AudioShare sharedInstance] checkPendingImport:url withBlock:^(NSString *path) { 77 | 78 | // Move the temporary file into our Documents folder 79 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 80 | NSString *documentsDirectory = [paths objectAtIndex:0]; 81 | NSString *destination = [documentsDirectory stringByAppendingPathComponent:[path lastPathComponent]]; 82 | [[NSFileManager defaultManager] moveItemAtPath:path toPath:destination error:nil]; 83 | 84 | // Load the imported file 85 | [mySoundEngine loadSample:destination]; 86 | 87 | }]) { 88 | return YES; 89 | } 90 | 91 | 3. To initiate the import from your app, add a button named "Import from AudioShare" which simply calls: 92 | 93 | [[AudioShare sharedInstance] initiateSoundImport]; 94 | 95 | This will launch AudioShare (if version 2.5 or later is installed), which will display an "Import into app: YourAppName" button. When the user taps this button in AudioShare, it will launch your application where the call to `checkPendingImport:withBlock:` will grab the imported soundfile. 96 | 97 | If AudioShare was not installed, it will just paste audio from the general pasteboard, and then call your callback URL. The pasted audio will thus be handled in your `checkPendingImport` as usual. The filename will be "PastedAudio" with suitable path extension added depending on the file type. 98 | 99 | Multithreading 100 | -------------- 101 | The addSoundFrom* and initiateSoundImport methods should always be called from the main thread. If you need to call it from another thread, you must wrap the call in a block dispatched on the main thread. Example: 102 | 103 | dispatch_async(dispatch_get_main_queue(), ^(void) { 104 | [[AudioShare sharedInstance] addSoundFromURL:theUrlToYourFile 105 | withName:@"My Sound"]; 106 | }); 107 | 108 | Supported formats 109 | ----------------- 110 | 111 | AudioShare supports all soundfile formats, bit depths and rates, that has built-in support in iOS: AIFF, AIFC, WAVE, SoundDesigner2, Next, MP3, MP2, MP1, AC3, AAC_ADTS, MPEG4, M4A, CAF, 3GP, 3GP2, AMR. Additionally, it supports standard MIDI files. 112 | 113 | License 114 | ------- 115 | 116 | You are free to incorporate this code in your own application, for the purpose of launching 117 | and/or transferring sounds to/from the AudioShare app. 118 | 119 | If you do, please drop me a message at info@kymatica.com so that I can add your app to the list of implementing apps on http://kymatica.com/audioshare 120 | 121 | Copyright (C)2012-2015 Jonatan Liljedahl 122 | 123 | http://kymatica.com 124 | --------------------------------------------------------------------------------