/* * PhoneGap is available under *either* the terms of the modified BSD license *or* the * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. */ #import "Sound.h" #import "PhonegapDelegate.h" #define DOCUMENTS_SCHEME_PREFIX @"documents://" #define HTTP_SCHEME_PREFIX @"http://" @implementation Sound // Maps a url to the original resource path - (NSString*) resourceForUrl:(NSURL*)url { NSBundle* mainBundle = [NSBundle mainBundle]; NSString* urlString = [url description]; NSString* retVal = @""; NSString* wwwPath = [mainBundle pathForResource:[PhoneGapDelegate wwwFolderName] ofType:@"" inDirectory:@""]; NSString* wwwUrl = [[NSURL fileURLWithPath:wwwPath] description]; NSString* documentsUrl = [[NSURL fileURLWithPath:[PhoneGapDelegate applicationDocumentsDirectory]] description]; if ([urlString hasPrefix:wwwUrl]) { retVal = [urlString substringFromIndex:[wwwUrl length]]; } else if ([urlString hasPrefix:HTTP_SCHEME_PREFIX]) { retVal = urlString; } else if ([urlString hasPrefix:documentsUrl]) { retVal = [NSString stringWithFormat:@"%@%@", DOCUMENTS_SCHEME_PREFIX, [urlString substringFromIndex:[documentsUrl length]]]; } else { NSLog(@"Cannot map url '%@' to a resource path.", urlString); } return retVal; } // Maps a url for a resource path // "Naked" resource paths are assumed to be from the www folder as its base - (NSURL*) urlForResource:(NSString*)resourcePath { NSURL* resourceURL = nil; // attempt to find file path NSString* filePath = [PhoneGapDelegate pathForResource:resourcePath]; if (filePath == nil) { // if it is a http url, use it if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX]){ NSLog(@"Will use resource '%@' from the Internet.", resourcePath); resourceURL = [NSURL URLWithString:resourcePath]; } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { NSLog(@"Will use resource '%@' from the documents folder.", resourcePath); resourceURL = [NSURL URLWithString:resourcePath]; NSString* recordingPath = [NSString stringWithFormat:@"%@/%@", [PhoneGapDelegate applicationDocumentsDirectory], [resourceURL host]]; resourceURL = [NSURL fileURLWithPath:recordingPath]; } else { NSLog(@"Unknown resource '%@'", resourcePath); } } else { NSLog(@"Found resource '%@' in the web folder.", resourcePath); // it's a file url, use it resourceURL = [NSURL fileURLWithPath:filePath]; } return resourceURL; } // Creates or gets the cached audio file resource object - (AudioFile*) audioFileForResource:(NSString*) resourcePath { NSURL* resourceURL = [self urlForResource:resourcePath]; if([resourcePath isEqualToString:@""]){ NSLog(@"Cannot play empty URI"); return nil; } if (resourceURL == nil) { NSLog(@"Cannot use audio file from resource '%@'", resourcePath); return nil; } if (soundCache == nil) { soundCache = [[NSMutableDictionary alloc] initWithCapacity:4]; } AudioFile* audioFile = [soundCache objectForKey:resourcePath]; if (audioFile == nil) { NSError *error; audioFile = [[[AudioFile alloc] init] autorelease]; audioFile.resourcePath = resourcePath; audioFile.resourceURL = resourceURL; if ([resourceURL isFileURL]) { audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:resourceURL error:&error]; } else { NSData* data = [NSData dataWithContentsOfURL:resourceURL]; audioFile.player = [[ AVAudioPlayer alloc ] initWithData:data error:&error]; } if (error != nil) { NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error); audioFile.player = nil; } } return audioFile; } - (void) prepare:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; if (audioFile == nil) { return; } NSUInteger argc = [arguments count]; if (argc > 1) audioFile.successCallback = [arguments objectAtIndex:1]; if (argc > 2) audioFile.errorCallback = [arguments objectAtIndex:2]; if (argc > 3) audioFile.downloadCompleteCallback = [arguments objectAtIndex:3]; [soundCache setObject:audioFile forKey:audioFile.resourcePath]; if (audioFile.player != nil) { NSLog(@"Prepared audio sample '%@' for playback.", audioFile.resourcePath); if (audioFile.downloadCompleteCallback) { NSLog(@"dl complete cb: %@", audioFile.downloadCompleteCallback); NSString* jsString = [NSString stringWithFormat:@"(%@)();", audioFile.downloadCompleteCallback]; [super writeJavascript:jsString]; } audioFile.player.delegate = self; [audioFile.player prepareToPlay]; } audFile = audioFile; } - (void) play:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; NSNumber* loopOption = [options objectForKey:@"numberOfLoops"]; NSInteger numberOfLoops = 0; if (loopOption != nil) { numberOfLoops = [loopOption intValue] - 1; } if (audioFile != nil) { if (audioFile.player != nil) { NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); audioFile.player.numberOfLoops = numberOfLoops; if(audioFile.player.isPlaying){ [audioFile.player stop]; audioFile.player.currentTime = 0; } [audioFile.player play]; } else { NSError* error; // try loading it one more time, in case the file was recorded previously audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:audioFile.resourceURL error:&error]; if (error != nil) { NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error); audioFile.player = nil; } else { NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); audioFile.player.numberOfLoops = numberOfLoops; [audioFile.player play]; } } } } - (void) stop:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; if (audioFile != nil) { if (audioFile.player != nil) { NSLog(@"Stopped playing audio sample '%@'", audioFile.resourcePath); [audioFile.player stop]; audioFile.player.currentTime = 0; } } } - (void) pause:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; if (audioFile != nil) { if (audioFile.player != nil) { NSLog(@"Paused playing audio sample '%@'", audioFile.resourcePath); [audioFile.player pause]; } } } #ifdef __IPHONE_3_0 - (void) startAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; if (audioFile == nil) { return; } NSError* error = nil; if (audioFile.recorder != nil) { [audioFile.recorder stop]; audioFile.recorder = nil; } audioFile.recorder = [[AVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error]; if (error != nil) { NSLog(@"Failed to initialize AVAudioRecorder: %@\n", error); audioFile.recorder = nil; } else { audioFile.recorder.delegate = self; [audioFile.recorder record]; NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath); } } - (void) stopAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { AudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:0]]; if (audioFile == nil) { return; } if (audioFile.recorder != nil) { NSLog(@"Stopped recording audio sample '%@'", audioFile.resourcePath); [audioFile.recorder stop]; } } - (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag { NSString* resourcePath = [self resourceForUrl:recorder.url]; AudioFile* audioFile = [self audioFileForResource:[recorder.url path]]; NSLog(@"Finished recording audio sample '%@'", resourcePath); if (audioFile != nil) { if (flag){ if (audioFile.successCallback) { NSString* jsString = [NSString stringWithFormat:@"(%@)(\"%@\");", audioFile.successCallback, resourcePath]; [super writeJavascript:jsString]; } } else { if (audioFile.errorCallback) { NSString* jsString = [NSString stringWithFormat:@"(%@)(\"%@\");", audioFile.errorCallback, resourcePath]; [super writeJavascript:jsString]; } } } } #endif - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag { NSString* resourcePath = [self resourceForUrl:player.url]; AudioFile* audioFile = audFile; NSLog(@"Finished playing audio sample '%@'", resourcePath); if (audioFile != nil) { if (flag){ if (audioFile.successCallback) { NSString* jsString = [NSString stringWithFormat:@"(%@)(\"%@\");", audioFile.successCallback, resourcePath]; [super writeJavascript:jsString]; } } else { if (audioFile.errorCallback) { NSString* jsString = [NSString stringWithFormat:@"(%@)(\"%@\");", audioFile.errorCallback, resourcePath]; [super writeJavascript:jsString]; } } } else { NSLog(@"audio file nil"); } } - (void) clearCaches { [soundCache removeAllObjects]; [soundCache release]; soundCache = nil; [super clearCaches]; } @end @implementation AudioFile @synthesize resourcePath; @synthesize resourceURL; @synthesize successCallback; @synthesize errorCallback; @synthesize downloadCompleteCallback; @synthesize player; #ifdef __IPHONE_3_0 @synthesize recorder; #endif - (void) dealloc { self.player = nil; self.successCallback = nil; self.errorCallback = nil; self.downloadCompleteCallback = nil; [super dealloc]; } @end