AudioSessionInitialize Workarounds

Core Audio

Yesterday I decided to go after one of my audio bugs by take a look how AVAudioPlayer does it right, and I discovered a little thing about Audio Session.

Here are two workarounds for AudioSessionInitialize.

I never liked AudioSessionInitialize because you can set the InterruptionListener and it’s ClientData just once:

/*
 * Discussion
 * Your application must call this function before making any other Audio
 * Session Services calls. You may activate and deactivate your audio session
 * as needed (see AudioSessionSetActive), but should initialize it only once.
 */

OSStatus AudioSessionInitialize (
   CFRunLoopRef                        inRunLoop,
   CFStringRef                         inRunLoopMode,
   AudioSessionInterruptionListener    inInterruptionListener,
   void                                *inClientData
);

So you need a helper class like this where you have to set the active AudioPlayer:

#import "AudioInterruptionListener.h"

void interruptionListenerCallback (
                                   void *inUserData,
                                   UInt32   interruptionState
) {
    AudioInterruptionListener *listener = (AudioInterruptionListener *) inUserData;
    DoItYourselfPlayer *player = listener.doItYourselfPlayer;
    
    if (!player)
        return;
    
    if (interruptionState == kAudioSessionBeginInterruption) {
        [player performSelectorOnMainThread:@selector(audioStreamPlayerBeginInterruption)
                                 withObject:nil
                              waitUntilDone:YES];
    } else if ((interruptionState == kAudioSessionEndInterruption)) {
        [player performSelectorOnMainThread:@selector(audioStreamPlayerEndInterruption)
                                 withObject:nil
                              waitUntilDone:YES];
    }
}

@implementation AudioInterruptionListener

static AudioInterruptionListener *sharedAudioInterruptionListener;

@synthesize doItYourselfPlayer;

+ (void)initialize {
    if (!sharedAudioInterruptionListener) {
        sharedAudioInterruptionListener = [[AudioInterruptionListener alloc] init];
        AudioSessionInitialize (
                                NULL,
                                NULL,
                                interruptionListenerCallback,
                                sharedAudioInterruptionListener
                                );
    }
}

+ (AudioInterruptionListener *)sharedAudioInterruptionListener
{
    return sharedAudioInterruptionListener;
}

@end

This is fine, but I was no big fan of this helper class when I wrote it.

AVAudioPlayer works without such helper class, but how? Well don’t do this at home, because ‘intr’ is not documented in “Audio Session Services Property Identifiers.”, but you can also add an InterruptionListener with AudioSessionAddPropertyListener, the code should look something along this lines:

- (BOOL)prepareToPlay {
    pthread_once(&interruptionListenerOneTimeInit, interruptionListenerInit);
    …
    AudioSessionAddPropertyListener(’intr’, interruptionListenerCallback, self);
}

- (void)stop {
    AudioSessionRemovePropertyListenerWithUserData(’intr’, interruptionListenerCallback, self);
    …
}

#pragma mark -
#pragma mark Audio Session Implementations

pthread_once_t          interruptionListenerOneTimeInit = PTHREAD_ONCE_INIT;

void interruptionListenerInit(void)
{
    AudioSessionInitialize(NULL, NULL, NULL, NULL);
}

void interruptionListenerCallback (
                                    void                      *inClientData,
                                    AudioSessionPropertyID    inID,
                                    UInt32                    inDataSize,
                                    const void                *inData
) {
    DoItYourselfPlayer *player = inClientData;
    if (inID == ‘intr’) {
        UInt32 *interruptionState = (UInt32 *)inData;
        
        if (*interruptionState == kAudioSessionBeginInterruption) {
              [player performSelectorOnMainThread:@selector(audioStreamPlayerBeginInterruption)
                                 withObject:nil
                              waitUntilDone:YES];
        } else if (*interruptionState == kAudioSessionEndInterruption) {
              [player performSelectorOnMainThread:@selector(audioStreamPlayerEndInterruption)
                                 withObject:nil
                              waitUntilDone:YES];
            }
        }
    }
}

If you agree that Apple should expose this functionality to third parties, please submit a duplicate for Radar ID# 6467253.

Happy Holidays hacking!

del.icio.us digg

Leave a Reply