Detecting when an iOS 4 iPhone Headphone or Docking Connector is Unplugged (Xcode 4)

Follow Techotopia on Twitter Bookmark and Share

From Techotopia

Jump to: navigation, search
PreviousTable of Contents
Recording Audio on an iOS 4 iPhone with AVAudioRecorder (Xcode 4)


Purchase the fully updated iOS 7 edition of this book in eBook ($12.99) or Print ($32.99) format

iOS 7 App Development Essentails Print and eBook (ePub/PDF/Kindle) editions contain 89 chapters.

Buy eBook
Buy Print


The iPhone provides three options for the playback of audio. These consist of the built-in speakers, a connection to the headphone socket or via a device attached to the docking connector. Apple’s human interface guidelines for the implementation of iPhone applications recommend that when either the docking connector or headphones are unplugged during audio playback that the audio be automatically paused and then resumed when the connection is reestablished.

In this chapter, therefore, we will look at how to detect when either the headphones or a device attached to the docking connector are unplugged from an iPhone, a concept referred to by Apple as an audio hardware route change.

Contents



[edit] Detecting a Change to the Audio Hardware Route

In order to detect that a connection to either the iPhone headphone or docking connector has been unplugged or reconnected it is necessary to configure a property listener on the kAudioSessionProperty_AudioRouteChange property of the current audio session and, in so doing, specify a callback to be triggered when a change to this property occurs.

The kAudioSessionProperty_AudioRouteChange property is actually an object (of type CFDictionary) from which it is possible to identify details such as the reason for the property change and the old route (for example if audio was playing through the speakers or the headphones prior to the route change). For example, when the headphone or dock connector is unplugged, the reason for the route change will be represented by a kAudioSessionRouteChangeReason_OldDeviceUnavailable value in the dictionary of the kAudioSessionProperty_AudioRouteChange property. Conversely, the detection of a new device is represented by kAudioSessionRouteChangeReason_NewDeviceAvailable.

The old route value is stored as a string value representing one of the audio output options, namely “Headphone”, “Speaker” or “LineOut” (the latter representing the dock connector).

[edit] An Example iPhone Headphone and Dock Connector Detection Application

The concepts involved in detecting audio route changes in iOS are actually quite simple and are, perhaps, best explained by demonstration. For the purposes of this tutorial we will be adding functionality to the audio application created in the chapter entitled Playing Audio on an iPhone using AVAudioPlayer. Begin, therefore, by loading the audio project created in that chapter in Xcode.

[edit] Adding the AudioToolBox Framework to the Project

The code used in this project will make use of the AudioToolBox framework. The first step, therefore, is to ensure this is included in the project. This can be achieved by selecting the product target entry from the project navigator panel (the top item named location) and clicking on the Build Phases tab in the main panel. In the Link Binary with Libraries section click on the ‘+’ button, select the AudioToolBox.framework entry from the resulting panel and click on the Add button.

It will also be necessary to import the <AudioToolBox/AudioToolBox.h> file into the application code. To do so, select the audioViewController.h file and modify it to add the import directive and create an outlet for the audioPlayer object as follows:

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

@interface audioViewController : UIViewController
     <AVAudioPlayerDelegate>
{
     AVAudioPlayer *audioPlayer;
     UISlider *volumeControl;
}
@property (nonatomic, retain) IBOutlet UISlider *volumeControl;
@property (nonatomic, retain) AVAudioPlayer *audioPlayer;
-(IBAction) playAudio;
-(IBAction) stopAudio;
-(IBAction) adjustVolume;
@end

[edit] Configuring the Property Listener

As previously discussed, the application code needs to set up a property listener and specify a callback to be triggered when the audio route changes. This requires that the view controller declare itself as the delegate for the audio session. In order to implement this in our audio project we need to add some code to the viewDidLoad method of the audioViewController.m file:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *url = [NSURL fileURLWithPath:
      [[NSBundle mainBundle]
      pathForResource:@"Kalimba"
      ofType:@"mp3"]];

    [[AVAudioSession sharedInstance] setDelegate: self];

    AudioSessionAddPropertyListener (
      kAudioSessionProperty_AudioRouteChange,
      audioRouteChangeListenerCallback,
      self);

   NSError *error;
   audioPlayer = [[AVAudioPlayer alloc]
          initWithContentsOfURL:url
          error:&error];

   if (error)
   {
         NSLog(@"Error in audioPlayer: %@", 
          [error localizedDescription]);
   } else {
         audioPlayer.delegate = self;
         [audioPlayer prepareToPlay];
   }
}

[edit] Writing the Property Listener Callback

The property listener callback is actually a C function as opposed to an Objective-C method. As such extra steps need to be taken in the function to access the audioViewController object instance.

The function is declared as follows:

void audioRouteChangeListenerCallback (
void *inUserData,
AudioSessionPropertyID inPropertyID,
UInt32 inPropertyValueSize,
const void *inPropertyValue) 
{
 // Code here
}

The first step is to check why the callback was called. For the purposes of this example we are only interested in acting if the reason for the call was due to a change to the audio route, otherwise the function should simply return:

if (inPropertyID != kAudioSessionProperty_AudioRouteChange) 
   return;

The next step is to establish a reference to the view controller handling the audio playback. This information can be obtained from the inUserData argument passed through to the callback:

audioViewController *controller = (audioViewController *) inUserData;

Having obtained a reference to the view controller we can now extract information about the reason for the callback being triggered and also the previous audio route:

CFDictionaryRef routeChangeDictionary = inPropertyValue;
CFNumberRef routeChangeReasonRef =
     CFDictionaryGetValue (
     routeChangeDictionary,
     CFSTR (kAudioSession_AudioRouteChangeKey_Reason));

SInt32 routeChangeReason;

CFNumberGetValue (
     routeChangeReasonRef,
     kCFNumberSInt32Type,
     &routeChangeReason);

CFStringRef oldRouteRef =
     CFDictionaryGetValue (
         routeChangeDictionary,
         CFSTR (kAudioSession_AudioRouteChangeKey_OldRoute));

NSString *oldRouteString = (NSString *)oldRouteRef;

On completion of execution, oldRouteString references a string containing the previous audio route and the routeChangeReason variable contains an integer value representing the reason for the change. Now that we have information on why the callback was triggered and what the previous audio route was all we need to do is write some simple conditional code to pause and resume audio playback depending on this data:

if (routeChangeReason == kAudioSessionRouteChangeReason_NewDeviceAvailable)
{
    if ([oldRouteString isEqualToString:@"Speaker"])
    {
        [controller.audioPlayer play];
    }
}

if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {

    if ((controller.audioPlayer.playing == YES) &&
       (([oldRouteString isEqualToString:@"Headphone"]) ||
       ([oldRouteString isEqualToString:@"LineOut"])))
    {
         [controller.audioPlayer pause];
    }
}

Bringing this code all together gives us a callback function that reads as follows:

void audioRouteChangeListenerCallback (
void *inUserData,
AudioSessionPropertyID inPropertyID,
UInt32 inPropertyValueSize,
const void *inPropertyValue) 
{
  if (inPropertyID != kAudioSessionProperty_AudioRouteChange) 
     return;

  audioViewController *controller = 
     (audioViewController *) inUserData;

  CFDictionaryRef routeChangeDictionary = inPropertyValue;

  CFNumberRef routeChangeReasonRef =
     CFDictionaryGetValue (
     routeChangeDictionary,
     CFSTR (kAudioSession_AudioRouteChangeKey_Reason));

  SInt32 routeChangeReason;

  CFNumberGetValue (
     routeChangeReasonRef,
     kCFNumberSInt32Type,
     &routeChangeReason);

  CFStringRef oldRouteRef =
     CFDictionaryGetValue (
         routeChangeDictionary,
         CFSTR (kAudioSession_AudioRouteChangeKey_OldRoute));

  NSString *oldRouteString = (NSString *)oldRouteRef;

  if (routeChangeReason == kAudioSessionRouteChangeReason_NewDeviceAvailable)
  {
    if ([oldRouteString isEqualToString:@"Speaker"])
    {
        [controller.audioPlayer play];
    }
  }

  if (routeChangeReason ==  
    kAudioSessionRouteChangeReason_OldDeviceUnavailable) 
  {
    if ((controller.audioPlayer.playing == YES) &&
       (([oldRouteString isEqualToString:@"Headphone"]) ||
       ([oldRouteString isEqualToString:@"LineOut"])))
    {
         [controller.audioPlayer pause];
    }
  }
}

[edit] Testing the Application

In order to test the application it will be necessary to load it onto a physical device since the iOS Simulator does not provide a mechanism for simulating changes to the status of the headphone or dock connectors. If the audio project is not already provisioned to run on a device, follow the steps outlined in Testing iOS 4 Apps on the iPhone – Developer Certificates and Provisioning Profiles to build and install the application onto an iPhone device with headphones attached. Once the application has launched, begin audio playback and then unplug the headphones. At this point, playback should pause. Reconnecting the headphones should then resume playback. The same behavior should also be detected when performed with a docking connector attached to an audio device.


Purchase the fully updated iOS 7 edition of this book in eBook ($12.99) or Print ($32.99) format

iOS 7 App Development Essentails Print and eBook (ePub/PDF/Kindle) editions contain 89 chapters.

Buy eBook
Buy Print



PreviousTable of Contents
Recording Audio on an iOS 4 iPhone with AVAudioRecorder (Xcode 4)
Views
Personal tools

Find us on Facebook