avoid Headset plugout stops AVAudioPlayer in iOS
Asked Answered
O

7

6

In my iPhone app I am using AVAudioPlayer to play the songs...But when I plug out or plugin the headset during song playing, It automatically stops the AVAudioPlayer... I need to run audio player even though these changes occur.. any ideas will be appreciated.Thanks in advance.

Olfe answered 14/6, 2013 at 7:20 Comment(2)
Did you find a solution for this? I'm facing same problem. I'm using audioRouteChangeListenerCallback, don't specifically stop/pause audio, and don't recall having this issue in older versions of iOS. I use AVAudioPlayer and AVAudioRecorder for playing and recording at same time. I previously ported just playback of streams to a streamer class that uses AudioQueue and audio plays fine with that when plugging and unplugging headphones repeatedly.Hutment
Weird, on the contrary...I'm using AVAudioPlayer too in my app, and try to find why the audio play is not paused when taking off the headphones, like airPod.Emblematize
O
-1

I found the answer.

Just we have to Import the followings

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

and write this code

//Play the Event in Background
NSError *setCategoryErr = nil;
NSError *activationErr  = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryErr];
[[AVAudioSession sharedInstance] setActive: YES error: &activationErr];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];

Now its continuously playing even I plugged in&out the Ear phone.

Olfe answered 26/9, 2013 at 7:52 Comment(3)
Could you also explain what this code does or changes? It's not really apparent...Alisun
didn't solve the problem for me. any other suggestions?Tazza
Not a very good help. Your code seems to be unrelated to your problem. Could you explain?Aisne
A
10

First, you have to tell AVAudioSession the audio behaviour of your app. Apple name this the audio session category, an can be set by

[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&setCategoryErr];

For example, AVAudioSessionCategoryPlayback :

When using this category, your app audio continues with the Silent switch set to silent or when the screen locks. (The switch is called the Ring/Silent switch on iPhone.)

This category normally prevents audio from other apps from mixing with your app's audio. To allow mixing for this category, use the kAudioSessionProperty_OverrideCategoryMixWithOthers property.

Then, once the audio session set, the app will respond to some audio notifications, like AVAudioSessionInterruptionNotification or AVAudioSessionRouteChangeNotification

To answer, the original question, AVAudioSessionRouteChangeNotification is called when the audio route has been changed (ex: headset plug-out/plug-in, but also bluetooth device turning off, ...). With a bit of code, we can find the route change reason. And, in our case, start the player again il the headset has been unplugged.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSError *setCategoryErr;
    [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&setCategoryErr];
    
    // Detects when the audio route changes (ex: jack unplugged)
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioHardwareRouteChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
    // Don't forget to remove notification in dealloc method!!
}

- (void)audioHardwareRouteChanged:(NSNotification *)notification {
    NSInteger routeChangeReason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] integerValue];
    if (routeChangeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        // if we're here, the player has been stopped, so play again!
        [self.player play];
    }
}

To conclude, also think about a user, in a boring meeting, who accidentaly plug-out his headset. He would not have this kind of behaviour, whose would make the device suddently scream in the room!

Aisne answered 4/5, 2016 at 7:24 Comment(2)
In swift: NSNotification.Name.AVAudioSessionRouteChangeLunseth
[player play],i am geeting this error No known class method for selector 'play'Forging
P
4

Swift 3

Setup your player - play audio (even on silent mode) and silence other music / podcasts:

let audioSession = AVAudioSession.sharedInstance()
_ = try? audioSession.setCategory(AVAudioSessionCategoryPlayback, with: .duckOthers)
_ = try? audioSession.setActive(true)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(audioRouteChanged), name: .AVAudioSessionRouteChange, object: nil)

Route change observer (fix for unplugging headphones during playback):

func audioRouteChanged(note: Notification) {
  if let userInfo = note.userInfo {
    if let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int {
      if reason == AVAudioSessionRouteChangeReason.oldDeviceUnavailable.rawValue {
        // headphones plugged out
        player.play()
      }
    }
  }
}

Swift 2

let audioSession = AVAudioSession.sharedInstance()
_ = try? audioSession.setCategory(AVAudioSessionCategoryPlayback, withOptions: .DuckOthers)
_ = try? audioSession.setActive(true)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(audioRouteChanged), name: AVAudioSessionRouteChangeNotification, object: nil)

Route change observer:

func audioRouteChanged(note: NSNotification) {
  if let userInfo = note.userInfo {
    if let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int {
      if reason == AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue {
        // headphones plugged out -> continue playback
        player.play()
      }
    }
  }
}
Phanotron answered 11/3, 2017 at 3:20 Comment(3)
Should be rawValue instead of hashValue?Snowball
@Snowball I can't test at the moment but the above worked when I needed it (as far as I know). If you end up testing it, please let us know if it's still valid.Phanotron
I tested with Swift 5 and rawValue worked for me instead of hashValue, don't know if hashValue is working in Swift 3.Snowball
O
2

I know this is old post but i did some research about this. @Martin answer was correct and i am using NSNotificationCenter but i am using Swift3 so these are things you can get from notification.userInfo

case AVAudioSessionInterruptionNotificationKey
/* value is an NSNumber representing an AVAudioSessionInterruptionType */

case AVAudioSessionInterruptionOptionsKey */
/* Only present for end interruption events.  Value is of type AVAudioSessionInterruptionOptions.*/

case AVAudioSessionRouteChangeReasonKey */
/* value is an NSNumber representing an AVAudioSessionRouteChangeReason */
    case unknown
    case newDeviceAvailable
    case oldDeviceUnavailable
    case categoryChange
    case override 
    case wakeFromSleep
    case noSuitableRouteForCategory
    case routeConfigurationChange

case AVAudioSessionRouteChangePreviousRouteKey * */
/* value is AVAudioSessionRouteDescription * */
    case input
    case output

case AVAudioSessionSilenceSecondaryAudioHintTypeKey */
/* value is an NSNumber representing an AVAudioSessionSilenceSecondaryAudioHintType */

Here is method in swift3

func audioSessionRouteChange(notification: NSNotification) {

    if let userInfo = notification.userInfo {

        print("Notification: AVAudioSessionInterruptionTypeKey = \(userInfo[AVAudioSessionInterruptionTypeKey])")
        print("Notification: AVAudioSessionInterruptionOptionKey = \(userInfo[AVAudioSessionInterruptionOptionKey])")
        print("Notification: AVAudioSessionSilenceSecondaryAudioHintTypeKey = \(userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey])")

        if let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int {

            print("Notification: AVAudioSessionRouteChangeReasonOldDeviceUnavailable")

            if reason == AVAudioSessionRouteChangeReason.oldDeviceUnavailable.hashValue {

                print("Notification: Headphones out")
            }

            if reason == AVAudioSessionRouteChangeReason.newDeviceAvailable.hashValue {

                print("Notification: Headphones in")
            }
        }

        if let description = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {

            // here you can check previous input and output 
            // po description.outputs[0].portType == AVAudioSessionPortBuiltInSpeaker

            print("Notification: AVAudioSessionRouteChangePreviousRouteKey Inputs: \(description.inputs)")
            print("Notification: AVAudioSessionRouteChangePreviousRouteKey Outputs: \(description.outputs)")
        }
    }
}
Ottoman answered 14/12, 2016 at 11:55 Comment(0)
S
1

@Martin is almost right except when we get AVAudioSessionRouteChangeNotification notification, the audio may still play, you must check player's rate property. If it is zero, play it, otherwise you should observe rate, when it change to zero, play it. Check the link

Another note is that AVAudioSessionRouteChangeNotification is posted on a background thread (not main thread), you should dispatch it to the main thread if needed.

Solferino answered 30/8, 2018 at 6:47 Comment(0)
C
1

thanks to @budidino Swift 5 and above

let audioSession = AVAudioSession.sharedInstance()
_ = try? audioSession.setCategory(AVAudioSession.Category.playback, options: .duckOthers)
 _ = try? audioSession.setActive(true)
            
NotificationCenter.default.addObserver(self, selector: #selector(audioRouteChanged), name: AVAudioSession.routeChangeNotification, object: nil)


@objc func audioRouteChanged(note: Notification) {
      if let userInfo = note.userInfo {
        if let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int {
            if reason == AVAudioSession.RouteChangeReason.oldDeviceUnavailable.rawValue {
            // headphones plugged out
            self.avPlayer?.play()
          }
        }
      }
    }
Calefacient answered 17/11, 2020 at 18:28 Comment(0)
P
0

This is the best tutorial dealing this issue: (working well also on iOS7)

http://www.techotopia.com/index.php/Detecting_when_an_iPhone_Headphone_or_Docking_Connector_is_Unplugged_(iOS_4)

Pyrogallate answered 18/8, 2014 at 9:21 Comment(0)
O
-1

I found the answer.

Just we have to Import the followings

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

and write this code

//Play the Event in Background
NSError *setCategoryErr = nil;
NSError *activationErr  = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryErr];
[[AVAudioSession sharedInstance] setActive: YES error: &activationErr];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];

Now its continuously playing even I plugged in&out the Ear phone.

Olfe answered 26/9, 2013 at 7:52 Comment(3)
Could you also explain what this code does or changes? It's not really apparent...Alisun
didn't solve the problem for me. any other suggestions?Tazza
Not a very good help. Your code seems to be unrelated to your problem. Could you explain?Aisne

© 2022 - 2024 — McMap. All rights reserved.