summary refs log tree commit diff stats
path: root/libs/CocosDenshion/CDAudioManager.m
diff options
context:
space:
mode:
Diffstat (limited to 'libs/CocosDenshion/CDAudioManager.m')
-rwxr-xr-xlibs/CocosDenshion/CDAudioManager.m887
1 files changed, 887 insertions, 0 deletions
diff --git a/libs/CocosDenshion/CDAudioManager.m b/libs/CocosDenshion/CDAudioManager.m new file mode 100755 index 0000000..0929f3c --- /dev/null +++ b/libs/CocosDenshion/CDAudioManager.m
@@ -0,0 +1,887 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25
26#import "CDAudioManager.h"
27
28NSString * const kCDN_AudioManagerInitialised = @"kCDN_AudioManagerInitialised";
29
30//NSOperation object used to asynchronously initialise
31@implementation CDAsynchInitialiser
32
33-(void) main {
34 [super main];
35 [CDAudioManager sharedManager];
36}
37
38@end
39
40@implementation CDLongAudioSource
41
42@synthesize audioSourcePlayer, audioSourceFilePath, delegate, backgroundMusic;
43
44-(id) init {
45 if ((self = [super init])) {
46 state = kLAS_Init;
47 volume = 1.0f;
48 mute = NO;
49 enabled_ = YES;
50 }
51 return self;
52}
53
54-(void) dealloc {
55 CDLOGINFO(@"Denshion::CDLongAudioSource - deallocating %@", self);
56 [audioSourcePlayer release];
57 [audioSourceFilePath release];
58 [super dealloc];
59}
60
61-(void) load:(NSString*) filePath {
62 //We have alread loaded a file previously, check if we are being asked to load the same file
63 if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) {
64 CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath);
65 //New file
66 if (state != kLAS_Init) {
67 [audioSourceFilePath release];//Release old file path
68 [audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused
69 }
70 audioSourceFilePath = [filePath copy];
71 NSError *error = nil;
72 NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath];
73 audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
74 if (error == nil) {
75 [audioSourcePlayer prepareToPlay];
76 audioSourcePlayer.delegate = self;
77 if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) {
78 //Tell our delegate the file has changed
79 [delegate cdAudioSourceFileDidChange:self];
80 }
81 } else {
82 CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error);
83 }
84 } else {
85 //Same file - just return it to a consistent state
86 [self pause];
87 [self rewind];
88 }
89 audioSourcePlayer.volume = volume;
90 audioSourcePlayer.numberOfLoops = numberOfLoops;
91 state = kLAS_Loaded;
92}
93
94-(void) play {
95 if (enabled_) {
96 self->systemPaused = NO;
97 [audioSourcePlayer play];
98 } else {
99 CDLOGINFO(@"Denshion::CDLongAudioSource long audio source didn't play because it is disabled");
100 }
101}
102
103-(void) stop {
104 [audioSourcePlayer stop];
105}
106
107-(void) pause {
108 [audioSourcePlayer pause];
109}
110
111-(void) rewind {
112 [audioSourcePlayer setCurrentTime:0];
113}
114
115-(void) resume {
116 [audioSourcePlayer play];
117}
118
119-(BOOL) isPlaying {
120 if (state != kLAS_Init) {
121 return [audioSourcePlayer isPlaying];
122 } else {
123 return NO;
124 }
125}
126
127-(void) setVolume:(float) newVolume
128{
129 volume = newVolume;
130 if (state != kLAS_Init && !mute) {
131 audioSourcePlayer.volume = newVolume;
132 }
133}
134
135-(float) volume
136{
137 return volume;
138}
139
140#pragma mark Audio Interrupt Protocol
141-(BOOL) mute
142{
143 return mute;
144}
145
146-(void) setMute:(BOOL) muteValue
147{
148 if (mute != muteValue) {
149 if (mute) {
150 //Turn sound back on
151 audioSourcePlayer.volume = volume;
152 } else {
153 audioSourcePlayer.volume = 0.0f;
154 }
155 mute = muteValue;
156 }
157}
158
159-(BOOL) enabled
160{
161 return enabled_;
162}
163
164-(void) setEnabled:(BOOL)enabledValue
165{
166 if (enabledValue != enabled_) {
167 enabled_ = enabledValue;
168 if (!enabled_) {
169 //"Stop" the sounds
170 [self pause];
171 [self rewind];
172 }
173 }
174}
175
176-(NSInteger) numberOfLoops {
177 return numberOfLoops;
178}
179
180-(void) setNumberOfLoops:(NSInteger) loopCount
181{
182 audioSourcePlayer.numberOfLoops = loopCount;
183 numberOfLoops = loopCount;
184}
185
186- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
187 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player finished");
188#if TARGET_IPHONE_SIMULATOR
189 CDLOGINFO(@"Denshion::CDLongAudioSource - workaround for OpenAL clobbered audio issue");
190 //This is a workaround for an issue in all simulators (tested to 3.1.2). Problem is
191 //that OpenAL audio playback is clobbered when an AVAudioPlayer stops. Workaround
192 //is to keep the player playing on an endless loop with 0 volume and then when
193 //it is played again reset the volume and set loop count appropriately.
194 //NB: this workaround is not foolproof but it is good enough for most situations.
195 player.numberOfLoops = -1;
196 player.volume = 0;
197 [player play];
198#endif
199 if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceDidFinishPlaying:)]) {
200 [delegate cdAudioSourceDidFinishPlaying:self];
201 }
202}
203
204-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
205 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player interrupted");
206}
207
208-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
209 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player resumed");
210 if (self.backgroundMusic) {
211 //Check if background music can play as rules may have changed during
212 //the interruption. This is to address a specific issue in 4.x when
213 //fast task switching
214 if([CDAudioManager sharedManager].willPlayBackgroundMusic) {
215 [player play];
216 }
217 } else {
218 [player play];
219 }
220}
221
222@end
223
224
225@interface CDAudioManager (PrivateMethods)
226-(BOOL) audioSessionSetActive:(BOOL) active;
227-(BOOL) audioSessionSetCategory:(NSString*) category;
228-(void) badAlContextHandler;
229@end
230
231
232@implementation CDAudioManager
233#define BACKGROUND_MUSIC_CHANNEL kASC_Left
234
235@synthesize soundEngine, willPlayBackgroundMusic;
236static CDAudioManager *sharedManager;
237static tAudioManagerState _sharedManagerState = kAMStateUninitialised;
238static tAudioManagerMode configuredMode;
239static BOOL configured = FALSE;
240
241-(BOOL) audioSessionSetActive:(BOOL) active {
242 NSError *activationError = nil;
243 if ([[AVAudioSession sharedInstance] setActive:active error:&activationError]) {
244 _audioSessionActive = active;
245 CDLOGINFO(@"Denshion::CDAudioManager - Audio session set active %i succeeded", active);
246 return YES;
247 } else {
248 //Failed
249 CDLOG(@"Denshion::CDAudioManager - Audio session set active %i failed with error %@", active, activationError);
250 return NO;
251 }
252}
253
254-(BOOL) audioSessionSetCategory:(NSString*) category {
255 NSError *categoryError = nil;
256 if ([[AVAudioSession sharedInstance] setCategory:category error:&categoryError]) {
257 CDLOGINFO(@"Denshion::CDAudioManager - Audio session set category %@ succeeded", category);
258 return YES;
259 } else {
260 //Failed
261 CDLOG(@"Denshion::CDAudioManager - Audio session set category %@ failed with error %@", category, categoryError);
262 return NO;
263 }
264}
265
266// Init
267+ (CDAudioManager *) sharedManager
268{
269 @synchronized(self) {
270 if (!sharedManager) {
271 if (!configured) {
272 //Set defaults here
273 configuredMode = kAMM_FxPlusMusicIfNoOtherAudio;
274 }
275 sharedManager = [[CDAudioManager alloc] init:configuredMode];
276 _sharedManagerState = kAMStateInitialised;//This is only really relevant when using asynchronous initialisation
277 [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AudioManagerInitialised object:nil];
278 }
279 }
280 return sharedManager;
281}
282
283+ (tAudioManagerState) sharedManagerState {
284 return _sharedManagerState;
285}
286
287/**
288 * Call this to set up audio manager asynchronously. Initialisation is finished when sharedManagerState == kAMStateInitialised
289 */
290+ (void) initAsynchronously: (tAudioManagerMode) mode {
291 @synchronized(self) {
292 if (_sharedManagerState == kAMStateUninitialised) {
293 _sharedManagerState = kAMStateInitialising;
294 [CDAudioManager configure:mode];
295 CDAsynchInitialiser *initOp = [[[CDAsynchInitialiser alloc] init] autorelease];
296 NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
297 [opQ addOperation:initOp];
298 }
299 }
300}
301
302+ (id) alloc
303{
304 @synchronized(self) {
305 NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton.");
306 return [super alloc];
307 }
308 return nil;
309}
310
311/*
312 * Call this method before accessing the shared manager in order to configure the shared audio manager
313 */
314+ (void) configure: (tAudioManagerMode) mode {
315 configuredMode = mode;
316 configured = TRUE;
317}
318
319-(BOOL) isOtherAudioPlaying {
320 UInt32 isPlaying = 0;
321 UInt32 varSize = sizeof(isPlaying);
322 AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &varSize, &isPlaying);
323 return (isPlaying != 0);
324}
325
326-(void) setMode:(tAudioManagerMode) mode {
327
328 _mode = mode;
329 switch (_mode) {
330
331 case kAMM_FxOnly:
332 //Share audio with other app
333 CDLOGINFO(@"Denshion::CDAudioManager - Audio will be shared");
334 //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
335 _audioSessionCategory = AVAudioSessionCategoryAmbient;
336 willPlayBackgroundMusic = NO;
337 break;
338
339 case kAMM_FxPlusMusic:
340 //Use audio exclusively - if other audio is playing it will be stopped
341 CDLOGINFO(@"Denshion::CDAudioManager - Audio will be exclusive");
342 //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
343 _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
344 willPlayBackgroundMusic = YES;
345 break;
346
347 case kAMM_MediaPlayback:
348 //Use audio exclusively, ignore mute switch and sleep
349 CDLOGINFO(@"Denshion::CDAudioManager - Media playback mode, audio will be exclusive");
350 //_audioSessionCategory = kAudioSessionCategory_MediaPlayback;
351 _audioSessionCategory = AVAudioSessionCategoryPlayback;
352 willPlayBackgroundMusic = YES;
353 break;
354
355 case kAMM_PlayAndRecord:
356 //Use audio exclusively, ignore mute switch and sleep, has inputs and outputs
357 CDLOGINFO(@"Denshion::CDAudioManager - Play and record mode, audio will be exclusive");
358 //_audioSessionCategory = kAudioSessionCategory_PlayAndRecord;
359 _audioSessionCategory = AVAudioSessionCategoryPlayAndRecord;
360 willPlayBackgroundMusic = YES;
361 break;
362
363 default:
364 //kAudioManagerFxPlusMusicIfNoOtherAudio
365 if ([self isOtherAudioPlaying]) {
366 CDLOGINFO(@"Denshion::CDAudioManager - Other audio is playing audio will be shared");
367 //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
368 _audioSessionCategory = AVAudioSessionCategoryAmbient;
369 willPlayBackgroundMusic = NO;
370 } else {
371 CDLOGINFO(@"Denshion::CDAudioManager - Other audio is not playing audio will be exclusive");
372 //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
373 _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
374 willPlayBackgroundMusic = YES;
375 }
376
377 break;
378 }
379
380 [self audioSessionSetCategory:_audioSessionCategory];
381
382}
383
384/**
385 * This method is used to work around various bugs introduced in 4.x OS versions. In some circumstances the
386 * audio session is interrupted but never resumed, this results in the loss of OpenAL audio when following
387 * standard practices. If we detect this situation then we will attempt to resume the audio session ourselves.
388 * Known triggers: lock the device then unlock it (iOS 4.2 gm), playback a song using MPMediaPlayer (iOS 4.0)
389 */
390- (void) badAlContextHandler {
391 if (_interrupted && alcGetCurrentContext() == NULL) {
392 CDLOG(@"Denshion::CDAudioManager - bad OpenAL context detected, attempting to resume audio session");
393 [self audioSessionResumed];
394 }
395}
396
397- (id) init: (tAudioManagerMode) mode {
398 if ((self = [super init])) {
399
400 //Initialise the audio session
401 AVAudioSession* session = [AVAudioSession sharedInstance];
402 session.delegate = self;
403
404 _mode = mode;
405 backgroundMusicCompletionSelector = nil;
406 _isObservingAppEvents = FALSE;
407 _mute = NO;
408 _resigned = NO;
409 _interrupted = NO;
410 enabled_ = YES;
411 _audioSessionActive = NO;
412 [self setMode:mode];
413 soundEngine = [[CDSoundEngine alloc] init];
414
415 //Set up audioSource channels
416 audioSourceChannels = [[NSMutableArray alloc] init];
417 CDLongAudioSource *leftChannel = [[CDLongAudioSource alloc] init];
418 leftChannel.backgroundMusic = YES;
419 CDLongAudioSource *rightChannel = [[CDLongAudioSource alloc] init];
420 rightChannel.backgroundMusic = NO;
421 [audioSourceChannels insertObject:leftChannel atIndex:kASC_Left];
422 [audioSourceChannels insertObject:rightChannel atIndex:kASC_Right];
423 [leftChannel release];
424 [rightChannel release];
425 //Used to support legacy APIs
426 backgroundMusic = [self audioSourceForChannel:BACKGROUND_MUSIC_CHANNEL];
427 backgroundMusic.delegate = self;
428
429 //Add handler for bad al context messages, these are posted by the sound engine.
430 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(badAlContextHandler) name:kCDN_BadAlContext object:nil];
431
432 }
433 return self;
434}
435
436-(void) dealloc {
437 CDLOGINFO(@"Denshion::CDAudioManager - deallocating");
438 [self stopBackgroundMusic];
439 [soundEngine release];
440 [[NSNotificationCenter defaultCenter] removeObserver:self];
441 [self audioSessionSetActive:NO];
442 [audioSourceChannels release];
443 [super dealloc];
444}
445
446/** Retrieves the audio source for the specified channel */
447-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel
448{
449 return (CDLongAudioSource*)[audioSourceChannels objectAtIndex:channel];
450}
451
452/** Loads the data from the specified file path to the channel's audio source */
453-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel
454{
455 CDLongAudioSource *audioSource = [self audioSourceForChannel:channel];
456 if (audioSource) {
457 [audioSource load:filePath];
458 }
459 return audioSource;
460}
461
462-(BOOL) isBackgroundMusicPlaying {
463 return [self.backgroundMusic isPlaying];
464}
465
466//NB: originally I tried using a route change listener and intended to store the current route,
467//however, on a 3gs running 3.1.2 no route change is generated when the user switches the
468//ringer mute switch to off (i.e. enables sound) therefore polling is the only reliable way to
469//determine ringer switch state
470-(BOOL) isDeviceMuted {
471
472#if TARGET_IPHONE_SIMULATOR
473 //Calling audio route stuff on the simulator causes problems
474 return NO;
475#else
476 CFStringRef newAudioRoute;
477 UInt32 propertySize = sizeof (CFStringRef);
478
479 AudioSessionGetProperty (
480 kAudioSessionProperty_AudioRoute,
481 &propertySize,
482 &newAudioRoute
483 );
484
485 if (newAudioRoute == NULL) {
486 //Don't expect this to happen but playing safe otherwise a null in the CFStringCompare will cause a crash
487 return YES;
488 } else {
489 CFComparisonResult newDeviceIsMuted = CFStringCompare (
490 newAudioRoute,
491 (CFStringRef) @"",
492 0
493 );
494
495 return (newDeviceIsMuted == kCFCompareEqualTo);
496 }
497#endif
498}
499
500#pragma mark Audio Interrupt Protocol
501
502-(BOOL) mute {
503 return _mute;
504}
505
506-(void) setMute:(BOOL) muteValue {
507 if (muteValue != _mute) {
508 _mute = muteValue;
509 [soundEngine setMute:muteValue];
510 for( CDLongAudioSource *audioSource in audioSourceChannels) {
511 audioSource.mute = muteValue;
512 }
513 }
514}
515
516-(BOOL) enabled {
517 return enabled_;
518}
519
520-(void) setEnabled:(BOOL) enabledValue {
521 if (enabledValue != enabled_) {
522 enabled_ = enabledValue;
523 [soundEngine setEnabled:enabled_];
524 for( CDLongAudioSource *audioSource in audioSourceChannels) {
525 audioSource.enabled = enabled_;
526 }
527 }
528}
529
530-(CDLongAudioSource*) backgroundMusic
531{
532 return backgroundMusic;
533}
534
535//Load background music ready for playing
536-(void) preloadBackgroundMusic:(NSString*) filePath
537{
538 [self.backgroundMusic load:filePath];
539}
540
541-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
542{
543 [self.backgroundMusic load:filePath];
544
545 if (!willPlayBackgroundMusic || _mute) {
546 CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted");
547 return;
548 }
549
550 if (loop) {
551 [self.backgroundMusic setNumberOfLoops:-1];
552 } else {
553 [self.backgroundMusic setNumberOfLoops:0];
554 }
555 [self.backgroundMusic play];
556}
557
558-(void) stopBackgroundMusic
559{
560 [self.backgroundMusic stop];
561}
562
563-(void) pauseBackgroundMusic
564{
565 [self.backgroundMusic pause];
566}
567
568-(void) resumeBackgroundMusic
569{
570 if (!willPlayBackgroundMusic || _mute) {
571 CDLOGINFO(@"Denshion::CDAudioManager - resume bgm aborted because audio is not exclusive or sound is muted");
572 return;
573 }
574
575 [self.backgroundMusic resume];
576}
577
578-(void) rewindBackgroundMusic
579{
580 [self.backgroundMusic rewind];
581}
582
583-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector {
584 backgroundMusicCompletionListener = listener;
585 backgroundMusicCompletionSelector = selector;
586}
587
588/*
589 * Call this method to have the audio manager automatically handle application resign and
590 * become active. Pass a tAudioManagerResignBehavior to indicate the desired behavior
591 * for resigning and becoming active again.
592 *
593 * If autohandle is YES then the applicationWillResignActive and applicationDidBecomActive
594 * methods are automatically called, otherwise you must call them yourself at the appropriate time.
595 *
596 * Based on idea of Dominique Bongard
597 */
598-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle {
599
600 if (!_isObservingAppEvents && autoHandle) {
601 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:@"UIApplicationWillResignActiveNotification" object:nil];
602 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:@"UIApplicationDidBecomeActiveNotification" object:nil];
603 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:@"UIApplicationWillTerminateNotification" object:nil];
604 _isObservingAppEvents = TRUE;
605 }
606 _resignBehavior = resignBehavior;
607}
608
609- (void) applicationWillResignActive {
610 self->_resigned = YES;
611
612 //Set the audio sesssion to one that allows sharing so that other audio won't be clobbered on resume
613 [self audioSessionSetCategory:AVAudioSessionCategoryAmbient];
614
615 switch (_resignBehavior) {
616
617 case kAMRBStopPlay:
618
619 for( CDLongAudioSource *audioSource in audioSourceChannels) {
620 if (audioSource.isPlaying) {
621 audioSource->systemPaused = YES;
622 audioSource->systemPauseLocation = audioSource.audioSourcePlayer.currentTime;
623 [audioSource stop];
624 } else {
625 //Music is either paused or stopped, if it is paused it will be restarted
626 //by OS so we will stop it.
627 audioSource->systemPaused = NO;
628 [audioSource stop];
629 }
630 }
631 break;
632
633 case kAMRBStop:
634 //Stop music regardless of whether it is playing or not because if it was paused
635 //then the OS would resume it
636 for( CDLongAudioSource *audioSource in audioSourceChannels) {
637 [audioSource stop];
638 }
639
640 default:
641 break;
642
643 }
644 CDLOGINFO(@"Denshion::CDAudioManager - handled resign active");
645}
646
647//Called when application resigns active only if setResignBehavior has been called
648- (void) applicationWillResignActive:(NSNotification *) notification
649{
650 [self applicationWillResignActive];
651}
652
653- (void) applicationDidBecomeActive {
654
655 if (self->_resigned) {
656 _resigned = NO;
657 //Reset the mode incase something changed with audio while we were inactive
658 [self setMode:_mode];
659 switch (_resignBehavior) {
660
661 case kAMRBStopPlay:
662
663 //Music had been stopped but stop maintains current time
664 //so playing again will continue from where music was before resign active.
665 //We check if music can be played because while we were inactive the user might have
666 //done something that should force music to not play such as starting a track in the iPod
667 if (self.willPlayBackgroundMusic) {
668 for( CDLongAudioSource *audioSource in audioSourceChannels) {
669 if (audioSource->systemPaused) {
670 [audioSource resume];
671 audioSource->systemPaused = NO;
672 }
673 }
674 }
675 break;
676
677 default:
678 break;
679
680 }
681 CDLOGINFO(@"Denshion::CDAudioManager - audio manager handled become active");
682 }
683}
684
685//Called when application becomes active only if setResignBehavior has been called
686- (void) applicationDidBecomeActive:(NSNotification *) notification
687{
688 [self applicationDidBecomeActive];
689}
690
691//Called when application terminates only if setResignBehavior has been called
692- (void) applicationWillTerminate:(NSNotification *) notification
693{
694 CDLOGINFO(@"Denshion::CDAudioManager - audio manager handling terminate");
695 [self stopBackgroundMusic];
696}
697
698/** The audio source completed playing */
699- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource {
700 CDLOGINFO(@"Denshion::CDAudioManager - audio manager got told background music finished");
701 if (backgroundMusicCompletionSelector != nil) {
702 [backgroundMusicCompletionListener performSelector:backgroundMusicCompletionSelector];
703 }
704}
705
706-(void) beginInterruption {
707 CDLOGINFO(@"Denshion::CDAudioManager - begin interruption");
708 [self audioSessionInterrupted];
709}
710
711-(void) endInterruption {
712 CDLOGINFO(@"Denshion::CDAudioManager - end interruption");
713 [self audioSessionResumed];
714}
715
716#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
717-(void) endInterruptionWithFlags:(NSUInteger)flags {
718 CDLOGINFO(@"Denshion::CDAudioManager - interruption ended with flags %i",flags);
719 if (flags == AVAudioSessionInterruptionFlags_ShouldResume) {
720 [self audioSessionResumed];
721 }
722}
723#endif
724
725-(void)audioSessionInterrupted
726{
727 if (!_interrupted) {
728 CDLOGINFO(@"Denshion::CDAudioManager - Audio session interrupted");
729 _interrupted = YES;
730
731 // Deactivate the current audio session
732 [self audioSessionSetActive:NO];
733
734 if (alcGetCurrentContext() != NULL) {
735 CDLOGINFO(@"Denshion::CDAudioManager - Setting OpenAL context to NULL");
736
737 ALenum error = AL_NO_ERROR;
738
739 // set the current context to NULL will 'shutdown' openAL
740 alcMakeContextCurrent(NULL);
741
742 if((error = alGetError()) != AL_NO_ERROR) {
743 CDLOG(@"Denshion::CDAudioManager - Error making context current %x\n", error);
744 }
745 #pragma unused(error)
746 }
747 }
748}
749
750-(void)audioSessionResumed
751{
752 if (_interrupted) {
753 CDLOGINFO(@"Denshion::CDAudioManager - Audio session resumed");
754 _interrupted = NO;
755
756 BOOL activationResult = NO;
757 // Reactivate the current audio session
758 activationResult = [self audioSessionSetActive:YES];
759
760 //This code is to handle a problem with iOS 4.0 and 4.01 where reactivating the session can fail if
761 //task switching is performed too rapidly. A test case that reliably reproduces the issue is to call the
762 //iPhone and then hang up after two rings (timing may vary ;))
763 //Basically we keep waiting and trying to let the OS catch up with itself but the number of tries is
764 //limited.
765 if (!activationResult) {
766 CDLOG(@"Denshion::CDAudioManager - Failure reactivating audio session, will try wait-try cycle");
767 int activateCount = 0;
768 while (!activationResult && activateCount < 10) {
769 [NSThread sleepForTimeInterval:0.5];
770 activationResult = [self audioSessionSetActive:YES];
771 activateCount++;
772 CDLOGINFO(@"Denshion::CDAudioManager - Reactivation attempt %i status = %i",activateCount,activationResult);
773 }
774 }
775
776 if (alcGetCurrentContext() == NULL) {
777 CDLOGINFO(@"Denshion::CDAudioManager - Restoring OpenAL context");
778 ALenum error = AL_NO_ERROR;
779 // Restore open al context
780 alcMakeContextCurrent([soundEngine openALContext]);
781 if((error = alGetError()) != AL_NO_ERROR) {
782 CDLOG(@"Denshion::CDAudioManager - Error making context current%x\n", error);
783 }
784 #pragma unused(error)
785 }
786 }
787}
788
789+(void) end {
790 [sharedManager release];
791 sharedManager = nil;
792}
793
794@end
795
796///////////////////////////////////////////////////////////////////////////////////////
797@implementation CDLongAudioSourceFader
798
799-(void) _setTargetProperty:(float) newVal {
800 ((CDLongAudioSource*)target).volume = newVal;
801}
802
803-(float) _getTargetProperty {
804 return ((CDLongAudioSource*)target).volume;
805}
806
807-(void) _stopTarget {
808 //Pause instead of stop as stop releases resources and causes problems in the simulator
809 [((CDLongAudioSource*)target) pause];
810}
811
812-(Class) _allowableType {
813 return [CDLongAudioSource class];
814}
815
816@end
817///////////////////////////////////////////////////////////////////////////////////////
818@implementation CDBufferManager
819
820-(id) initWithEngine:(CDSoundEngine *) theSoundEngine {
821 if ((self = [super init])) {
822 soundEngine = theSoundEngine;
823 loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START];
824 freedBuffers = [[NSMutableArray alloc] init];
825 nextBufferId = 0;
826 }
827 return self;
828}
829
830-(void) dealloc {
831 [loadedBuffers release];
832 [freedBuffers release];
833 [super dealloc];
834}
835
836-(int) bufferForFile:(NSString*) filePath create:(BOOL) create {
837
838 NSNumber* soundId = (NSNumber*)[loadedBuffers objectForKey:filePath];
839 if(soundId == nil)
840 {
841 if (create) {
842 NSNumber* bufferId = nil;
843 //First try to get a buffer from the free buffers
844 if ([freedBuffers count] > 0) {
845 bufferId = [[[freedBuffers lastObject] retain] autorelease];
846 [freedBuffers removeLastObject];
847 CDLOGINFO(@"Denshion::CDBufferManager reusing buffer id %i",[bufferId intValue]);
848 } else {
849 bufferId = [[NSNumber alloc] initWithInt:nextBufferId];
850 [bufferId autorelease];
851 CDLOGINFO(@"Denshion::CDBufferManager generating new buffer id %i",[bufferId intValue]);
852 nextBufferId++;
853 }
854
855 if ([soundEngine loadBuffer:[bufferId intValue] filePath:filePath]) {
856 //File successfully loaded
857 CDLOGINFO(@"Denshion::CDBufferManager buffer loaded %@ %@",bufferId,filePath);
858 [loadedBuffers setObject:bufferId forKey:filePath];
859 return [bufferId intValue];
860 } else {
861 //File didn't load, put buffer id on free list
862 [freedBuffers addObject:bufferId];
863 return kCDNoBuffer;
864 }
865 } else {
866 //No matching buffer was found
867 return kCDNoBuffer;
868 }
869 } else {
870 return [soundId intValue];
871 }
872}
873
874-(void) releaseBufferForFile:(NSString *) filePath {
875 int bufferId = [self bufferForFile:filePath create:NO];
876 if (bufferId != kCDNoBuffer) {
877 [soundEngine unloadBuffer:bufferId];
878 [loadedBuffers removeObjectForKey:filePath];
879 NSNumber *freedBufferId = [[NSNumber alloc] initWithInt:bufferId];
880 [freedBufferId autorelease];
881 [freedBuffers addObject:freedBufferId];
882 }
883}
884@end
885
886
887