SoundManager.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. //
  2. // SoundManager.m
  3. //
  4. // Version 1.3.1
  5. //
  6. // Created by Nick Lockwood on 29/01/2011.
  7. // Copyright 2010 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib license
  10. // Get the latest version from either of these locations:
  11. //
  12. // http://charcoaldesign.co.uk/source/cocoa#soundmanager
  13. // https://github.com/nicklockwood/SoundManager
  14. //
  15. // This software is provided 'as-is', without any express or implied
  16. // warranty. In no event will the authors be held liable for any damages
  17. // arising from the use of this software.
  18. //
  19. // Permission is granted to anyone to use this software for any purpose,
  20. // including commercial applications, and to alter it and redistribute it
  21. // freely, subject to the following restrictions:
  22. //
  23. // 1. The origin of this software must not be misrepresented; you must not
  24. // claim that you wrote the original software. If you use this software
  25. // in a product, an acknowledgment in the product documentation would be
  26. // appreciated but is not required.
  27. //
  28. // 2. Altered source versions must be plainly marked as such, and must not be
  29. // misrepresented as being the original software.
  30. //
  31. // 3. This notice may not be removed or altered from any source distribution.
  32. //
  33. #import "SoundManager.h"
  34. #pragma mark Sound class
  35. NSString *const SoundDidFinishPlayingNotification = @"SoundDidFinishPlayingNotification";
  36. #ifdef SM_USE_AV_AUDIO_PLAYER
  37. @interface Sound() <AVAudioPlayerDelegate>
  38. #else
  39. @interface Sound() <NSSoundDelegate>
  40. #endif
  41. @property (nonatomic, assign) float startVolume;
  42. @property (nonatomic, assign) float targetVolume;
  43. @property (nonatomic, assign) NSTimeInterval fadeTime;
  44. @property (nonatomic, assign) NSTimeInterval fadeStart;
  45. @property (nonatomic, strong) NSTimer *timer;
  46. @property (nonatomic, strong) Sound *selfReference;
  47. @property (nonatomic, strong) SM_SOUND *sound;
  48. - (void)prepareToPlay;
  49. @end
  50. @implementation Sound
  51. @synthesize baseVolume;
  52. @synthesize startVolume;
  53. @synthesize targetVolume;
  54. @synthesize fadeTime;
  55. @synthesize fadeStart;
  56. @synthesize timer;
  57. @synthesize selfReference;
  58. @synthesize url;
  59. @synthesize sound;
  60. @synthesize completionHandler;
  61. + (Sound *)soundNamed:(NSString *)name
  62. {
  63. NSString *path = name;
  64. if (![path isAbsolutePath])
  65. {
  66. if ([[name pathExtension] isEqualToString:@""])
  67. {
  68. name = [name stringByAppendingPathExtension:@"caf"];
  69. }
  70. path = [[NSBundle mainBundle] pathForResource:name ofType:@""];
  71. }
  72. return [self soundWithContentsOfFile:path];
  73. }
  74. + (Sound *)soundWithContentsOfFile:(NSString *)path
  75. {
  76. return AH_AUTORELEASE([[self alloc] initWithContentsOfFile:path]);
  77. }
  78. + (Sound *)soundWithContentsOfURL:(NSURL *)url
  79. {
  80. return AH_AUTORELEASE([[self alloc] initWithContentsOfURL:url]);
  81. }
  82. - (Sound *)initWithContentsOfFile:(NSString *)path;
  83. {
  84. return [self initWithContentsOfURL:path? [NSURL fileURLWithPath:path]: nil];
  85. }
  86. - (Sound *)initWithContentsOfURL:(NSURL *)_url;
  87. {
  88. #ifdef DEBUG
  89. if ([_url isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[_url path]])
  90. {
  91. NSLog(@"Sound file '%@' does not exist", [_url path]);
  92. }
  93. #endif
  94. if ((self = [super init]))
  95. {
  96. url = AH_RETAIN(_url);
  97. baseVolume = 1.0f;
  98. #ifdef SM_USE_AV_AUDIO_PLAYER
  99. sound = [[AVAudioPlayer alloc] initWithContentsOfURL:_url error:NULL];
  100. #else
  101. sound = [[NSSound alloc] initWithContentsOfURL:_url byReference:YES];
  102. #endif
  103. self.volume = 1.0f;
  104. }
  105. return self;
  106. }
  107. - (void)prepareToPlay
  108. {
  109. //avoid overhead from repeated calls
  110. static BOOL prepared = NO;
  111. if (prepared) return;
  112. prepared = YES;
  113. #ifdef SM_USE_AV_AUDIO_PLAYER
  114. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  115. [AVAudioSession sharedInstance];
  116. #endif
  117. [sound prepareToPlay];
  118. #else
  119. [sound setVolume:0.0f];
  120. [self play];
  121. [self performSelector:@selector(stop) withObject:nil afterDelay:0.0];
  122. #endif
  123. }
  124. - (NSString *)name
  125. {
  126. return [[url path] lastPathComponent];
  127. }
  128. - (void)setbaseVolume:(float)_baseVolume
  129. {
  130. _baseVolume = fminf(1.0f, fmaxf(0.0f, _baseVolume));
  131. if (baseVolume != _baseVolume)
  132. {
  133. float previousVolume = self.volume;
  134. baseVolume = _baseVolume;
  135. self.volume = previousVolume;
  136. }
  137. }
  138. - (float)volume
  139. {
  140. if (timer)
  141. {
  142. return targetVolume / baseVolume;
  143. }
  144. else
  145. {
  146. return [sound volume] / baseVolume;
  147. }
  148. }
  149. - (void)setVolume:(float)volume
  150. {
  151. volume = fminf(1.0f, fmaxf(0.0f, volume));
  152. if (timer)
  153. {
  154. targetVolume = volume * baseVolume;
  155. }
  156. else
  157. {
  158. [sound setVolume:volume * baseVolume];
  159. }
  160. }
  161. - (BOOL)isLooping
  162. {
  163. #ifdef SM_USE_AV_AUDIO_PLAYER
  164. return [sound numberOfLoops] == -1;
  165. #else
  166. return [sound loops];
  167. #endif
  168. }
  169. - (void)setLooping:(BOOL)looping
  170. {
  171. #ifdef SM_USE_AV_AUDIO_PLAYER
  172. [sound setNumberOfLoops:looping? -1: 0];
  173. #else
  174. [sound setLoops:looping];
  175. #endif
  176. }
  177. - (BOOL)isPlaying
  178. {
  179. return [sound isPlaying];
  180. }
  181. - (void)play
  182. {
  183. if (!self.playing)
  184. {
  185. self.selfReference = self;
  186. [sound setDelegate:self];
  187. //play sound
  188. [sound play];
  189. }
  190. }
  191. - (void)stop
  192. {
  193. if (self.playing)
  194. {
  195. //stop playing
  196. [sound stop];
  197. //stop timer
  198. [timer invalidate];
  199. self.timer = nil;
  200. //fire events
  201. if (completionHandler) completionHandler(NO);
  202. [[NSNotificationCenter defaultCenter] postNotificationName:SoundDidFinishPlayingNotification object:self];
  203. //set to nil on next runloop update so sound is not released unexpectedly
  204. [self performSelector:@selector(setSelfReference:) withObject:nil afterDelay:0.0];
  205. }
  206. }
  207. #ifdef SM_USE_AV_AUDIO_PLAYER
  208. - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)finishedPlaying
  209. #else
  210. - (void)sound:(NSSound *)_sound didFinishPlaying:(BOOL)finishedPlaying
  211. #endif
  212. {
  213. //stop timer
  214. [timer invalidate];
  215. self.timer = nil;
  216. //fire events
  217. if (completionHandler) completionHandler(NO);
  218. [[NSNotificationCenter defaultCenter] postNotificationName:SoundDidFinishPlayingNotification object:self];
  219. //set to nil on next runloop update so sound is not released unexpectedly
  220. [self performSelector:@selector(setSelfReference:) withObject:nil afterDelay:0.0];
  221. }
  222. - (void)fadeTo:(float)volume duration:(NSTimeInterval)duration
  223. {
  224. startVolume = [sound volume];
  225. targetVolume = volume * baseVolume;
  226. fadeTime = duration;
  227. fadeStart = [[NSDate date] timeIntervalSinceReferenceDate];
  228. if (timer == nil)
  229. {
  230. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
  231. target:self
  232. selector:@selector(tick)
  233. userInfo:nil
  234. repeats:YES];
  235. }
  236. }
  237. - (void)fadeIn:(NSTimeInterval)duration
  238. {
  239. [sound setVolume:0.0f];
  240. [self fadeTo:1.0f duration:duration];
  241. }
  242. - (void)fadeOut:(NSTimeInterval)duration
  243. {
  244. [self fadeTo:0.0f duration:duration];
  245. }
  246. - (void)tick
  247. {
  248. NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
  249. float delta = (now - fadeStart)/fadeTime * (targetVolume - startVolume);
  250. [sound setVolume:(startVolume + delta) * baseVolume];
  251. if ((delta > 0.0f && [sound volume] >= targetVolume) ||
  252. (delta < 0.0f && [sound volume] <= targetVolume))
  253. {
  254. [sound setVolume:targetVolume * baseVolume];
  255. [timer invalidate];
  256. self.timer = nil;
  257. if ([sound volume] == 0.0f)
  258. {
  259. [self stop];
  260. }
  261. }
  262. }
  263. - (void)dealloc
  264. {
  265. [timer invalidate];
  266. AH_RELEASE(timer);
  267. AH_RELEASE(url);
  268. AH_RELEASE(sound);
  269. AH_RELEASE(completionHandler);
  270. AH_SUPER_DEALLOC;
  271. }
  272. @end
  273. #pragma mark SoundManager class
  274. @interface SoundManager ()
  275. @property (nonatomic, strong) Sound *currentMusic;
  276. @property (nonatomic, strong) NSMutableArray *currentSounds;
  277. @end
  278. @implementation SoundManager
  279. @synthesize currentMusic;
  280. @synthesize currentSounds;
  281. @synthesize allowsBackgroundMusic;
  282. @synthesize soundVolume;
  283. @synthesize musicVolume;
  284. @synthesize soundFadeDuration;
  285. @synthesize musicFadeDuration;
  286. + (SoundManager *)sharedManager
  287. {
  288. static SoundManager *sharedManager = nil;
  289. if (sharedManager == nil)
  290. {
  291. sharedManager = [[self alloc] init];
  292. }
  293. return sharedManager;
  294. }
  295. - (SoundManager *)init
  296. {
  297. if ((self = [super init]))
  298. {
  299. soundVolume = 1.0f;
  300. musicVolume = 1.0f;
  301. soundFadeDuration = 1.0;
  302. musicFadeDuration = 1.0;
  303. currentSounds = [[NSMutableArray alloc] init];
  304. }
  305. return self;
  306. }
  307. - (void)setAllowsBackgroundMusic:(BOOL)allow
  308. {
  309. if (allowsBackgroundMusic != allow)
  310. {
  311. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  312. allowsBackgroundMusic = allow;
  313. AVAudioSession *session = [AVAudioSession sharedInstance];
  314. [session setCategory:allow? AVAudioSessionCategoryAmbient: AVAudioSessionCategorySoloAmbient error:NULL];
  315. #endif
  316. }
  317. }
  318. - (void)prepareToPlayWithSound:(id)soundOrName
  319. {
  320. Sound *sound = [soundOrName isKindOfClass:[Sound class]]? soundOrName: [Sound soundNamed:soundOrName];
  321. [sound prepareToPlay];
  322. }
  323. - (void)prepareToPlay
  324. {
  325. @autoreleasepool
  326. {
  327. NSArray *extensions = [NSArray arrayWithObjects:@"caf", @"m4a", @"mp4", @"mp3", @"wav", @"aif", nil];
  328. NSArray *paths = nil;
  329. BOOL foundSound = NO;
  330. for (NSString *extension in extensions)
  331. {
  332. paths = [[NSBundle mainBundle] pathsForResourcesOfType:extension inDirectory:nil];
  333. if ([paths count])
  334. {
  335. [self prepareToPlayWithSound:[paths objectAtIndex:0]];
  336. NSLog(@"SoundManager prepareToPlay:%@", [paths objectAtIndex:0]);
  337. foundSound = YES;
  338. break;
  339. }
  340. }
  341. if (!foundSound)
  342. {
  343. NSLog(@"SoundManager prepareToPlay failed to find sound in application bundle. Use prepareToPlayWithSound: instead to specify a suitable sound file.");
  344. }
  345. }
  346. }
  347. - (void)playMusic:(id)soundOrName looping:(BOOL)looping fadeIn:(BOOL)fadeIn
  348. {
  349. Sound *music = [soundOrName isKindOfClass:[Sound class]]? soundOrName: [Sound soundNamed:soundOrName];
  350. if (![music.url isEqual:currentMusic.url])
  351. {
  352. if (currentMusic && currentMusic.playing)
  353. {
  354. [currentMusic fadeOut:musicFadeDuration];
  355. }
  356. self.currentMusic = music;
  357. [[NSNotificationCenter defaultCenter] addObserver:self
  358. selector:@selector(musicFinished:)
  359. name:SoundDidFinishPlayingNotification
  360. object:music];
  361. currentMusic.looping = looping;
  362. currentMusic.volume = fadeIn? 0.0f: musicVolume;
  363. [currentMusic play];
  364. if (fadeIn)
  365. {
  366. [currentMusic fadeTo:musicVolume duration:musicFadeDuration];
  367. }
  368. }
  369. }
  370. - (void)playMusic:(id)soundOrName looping:(BOOL)looping
  371. {
  372. [self playMusic:soundOrName looping:looping fadeIn:YES];
  373. }
  374. - (void)playMusic:(id)soundOrName
  375. {
  376. [self playMusic:soundOrName looping:YES fadeIn:YES];
  377. }
  378. - (void)stopMusic:(BOOL)fadeOut
  379. {
  380. if (fadeOut)
  381. {
  382. [currentMusic fadeOut:musicFadeDuration];
  383. }
  384. else
  385. {
  386. [currentMusic stop];
  387. }
  388. self.currentMusic = nil;
  389. }
  390. - (void)stopMusic
  391. {
  392. [self stopMusic:YES];
  393. }
  394. - (void)playSound:(id)soundOrName looping:(BOOL)looping fadeIn:(BOOL)fadeIn
  395. {
  396. Sound *sound = [soundOrName isKindOfClass:[Sound class]]? soundOrName: [Sound soundNamed:soundOrName];
  397. if (![currentSounds containsObject:sound])
  398. {
  399. [currentSounds addObject:sound];
  400. [[NSNotificationCenter defaultCenter] addObserver:self
  401. selector:@selector(soundFinished:)
  402. name:SoundDidFinishPlayingNotification
  403. object:sound];
  404. }
  405. sound.looping = looping;
  406. sound.volume = fadeIn? 0.0f: soundVolume;
  407. [sound play];
  408. if (fadeIn)
  409. {
  410. [sound fadeTo:soundVolume duration:soundFadeDuration];
  411. }
  412. }
  413. - (void)playSound:(id)soundOrName looping:(BOOL)looping
  414. {
  415. [self playSound:soundOrName looping:looping fadeIn:NO];
  416. }
  417. - (void)playSound:(id)soundOrName
  418. {
  419. [self playSound:soundOrName looping:NO fadeIn:NO];
  420. }
  421. - (void)stopSound:(id)soundOrName fadeOut:(BOOL)fadeOut
  422. {
  423. if ([soundOrName isKindOfClass:[Sound class]])
  424. {
  425. if (fadeOut)
  426. {
  427. [(Sound *)soundOrName fadeOut:soundFadeDuration];
  428. }
  429. else
  430. {
  431. [(Sound *)soundOrName stop];
  432. }
  433. //[currentSounds removeObject:soundOrName];
  434. return;
  435. }
  436. if ([[soundOrName pathExtension] isEqualToString:@""])
  437. {
  438. soundOrName = [soundOrName stringByAppendingPathExtension:@"mp3"];
  439. }
  440. for (Sound *sound in [currentSounds reverseObjectEnumerator])
  441. {
  442. if ([sound.name isEqualToString:soundOrName] || [[sound.url path] isEqualToString:soundOrName])
  443. {
  444. if (fadeOut)
  445. {
  446. [sound fadeOut:soundFadeDuration];
  447. }
  448. else
  449. {
  450. [sound stop];
  451. }
  452. //[currentSounds removeObject:sound];
  453. }
  454. }
  455. }
  456. - (void)stopSound:(id)soundOrName
  457. {
  458. [self stopSound:soundOrName fadeOut:YES];
  459. }
  460. - (void)stopAllSounds:(BOOL)fadeOut
  461. {
  462. for (Sound *sound in currentSounds)
  463. {
  464. [self stopSound:sound fadeOut:YES];
  465. }
  466. [currentSounds removeAllObjects];
  467. }
  468. - (void)stopAllSounds
  469. {
  470. [self stopAllSounds:YES];
  471. }
  472. - (BOOL)isPlayingMusic
  473. {
  474. return currentMusic != nil;
  475. }
  476. - (void)setSoundVolume:(float)newVolume
  477. {
  478. soundVolume = newVolume;
  479. for (Sound *sound in currentSounds)
  480. {
  481. sound.volume = soundVolume;
  482. }
  483. }
  484. - (void)setMusicVolume:(float)newVolume
  485. {
  486. musicVolume = newVolume;
  487. currentMusic.volume = musicVolume;
  488. }
  489. - (void)soundFinished:(NSNotification *)notification
  490. {
  491. Sound *sound = [notification object];
  492. [currentSounds removeObject:sound];
  493. [[NSNotificationCenter defaultCenter] removeObserver:self
  494. name:SoundDidFinishPlayingNotification
  495. object:sound];
  496. }
  497. - (void)musicFinished:(NSNotification *)notification
  498. {
  499. Sound *sound = [notification object];
  500. if (sound == currentMusic)
  501. {
  502. self.currentMusic = nil;
  503. [[NSNotificationCenter defaultCenter] removeObserver:self
  504. name:SoundDidFinishPlayingNotification
  505. object:sound];
  506. }
  507. }
  508. - (void)dealloc
  509. {
  510. [[NSNotificationCenter defaultCenter] removeObserver:self];
  511. AH_RELEASE(currentMusic);
  512. AH_RELEASE(currentSounds);
  513. AH_SUPER_DEALLOC;
  514. }
  515. @end