How can I detect, from within my application, if an external microphone is plugged in the device?
Try this:
let route = AVAudioSession.sharedInstance().currentRoute
for port in route.outputs {
if port.portType == AVAudioSessionPortHeadphones {
// Headphones located
}
}
EDIT: Post OP change in question -
When app is running you need to register for AVAudioSessionRouteChangeNotification to listen to the changes like this:
NSNotificationCenter.defaultCenter().addObserver(self, selector:"audioRouteChangeListener:", name: AVAudioSessionRouteChangeNotification, object: nil)
dynamic private func audioRouteChangeListener(notification:NSNotification) {
let audioRouteChangeReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as UInt
switch audioRouteChangeReason {
case AVAudioSessionRouteChangeReason.NewDeviceAvailable.rawValue:
println("headphone plugged in")
case AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue:
println("headphone pulled out")
default:
break
}
}
swift3:
NotificationCenter.default.addObserver(self, selector: #selector(audioRouteChangeListener(notification:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
#objc private func audioRouteChangeListener(notification: Notification) {
let rawReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as! UInt
let reason = AVAudioSessionRouteChangeReason(rawValue: rawReason)!
switch reason {
case .newDeviceAvailable:
print("headphone plugged in")
case .oldDeviceUnavailable:
print("headphone pulled out")
default:
break
}
}
With swift 2.0 this code works
func audioRouteChangeListenerCallback (notif: NSNotification){
let userInfo:[NSObject:AnyObject] = notif.userInfo!
let routChangeReason = UInt((userInfo[AVAudioSessionRouteChangeReasonKey]?.integerValue)!)
switch routChangeReason {
case AVAudioSessionRouteChangeReason.NewDeviceAvailable.rawValue:
print("Connected");
break;
case AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue:
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker)
} catch _ {
}
print("Connected");
break;
case AVAudioSessionRouteChangeReason.CategoryChange.rawValue:
break;
default:
break;
}
}
With AVAudioSession you can list the availableInputs
let session = AVAudioSession.sharedInstance()
_ = try? session.setCategory(AVAudioSessionCategoryRecord, withOptions: [])
print(AVAudioSession.sharedInstance().availableInputs)
It return an array of AVAudioSessionPortDescription. And you can have the portType "wired or builtIn" microphone type.
PS : It only work on real device not on simulator.
Related
I have a project where I am trying to get my bluetooth headphones to work. In my view controller I add routeChangeNotification to see what is going on and it fires twice (both with case .categoryChange. The first time it shows my bluetooth headphones and then the second time it fires it reverts back to device microphone.
In AppDelegate I have:
let session = AVAudioSession.sharedInstance()
do {
// https://stackoverflow.com/questions/51010390/avaudiosession-setcategory-swift-4-2-ios-12-play-sound-on-silent
if #available(iOS 10.0, *) {
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth])
} else {
session.perform(NSSelectorFromString("setCategory:withOptions:error:"), with: AVAudioSession.Category.playAndRecord, with: [
AVAudioSession.CategoryOptions.allowBluetooth,
AVAudioSession.CategoryOptions.defaultToSpeaker]
)
try session.setMode(.default)
}
try session.setActive(true)
} catch {
print(error)
}
In my ViewController I have:
#objc private func didRouteChangeNotification(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
debugPrint("newDeviceAvailable")
case .oldDeviceUnavailable:
debugPrint("oldDeviceUnavailable")
case .categoryChange:
debugPrint("catgoryChange")
default: ()
}
}
On iOS app with GoogleInteractiveMediaAds integrated "Skip Ad" button doesn't work. Meanwhile manual call adsManager.skip() works perfectly. The button itself reacts to the tuches because it changes bounds and seems highlighted. Unfortunately, I haven't found anything according to handle tap manually, so maybe somebody has already been in this situation and could help with it.
guard
let adInformation = delegate?.latestAdInformation(), let url = adInformation.urlForIMA,
let adContainer = delegate?.videoAdDisplayContainerView()
else { return }
switch adInformation.adsType {
case .interstitials, .none:
self.play(ignoreAds: true)
return
case .prerolls, .all:
fallthrough
#unknown default:
break
}
let adDisplayContainer = IMAAdDisplayContainer(adContainer: adContainer, companionSlots: nil)
let request = IMAAdsRequest(
adTagUrl: url,
adDisplayContainer: adDisplayContainer,
contentPlayhead: contentPlayhead,
userContext: nil)
adsLoader.requestAds(with: request)
func adsLoader(_ loader: IMAAdsLoader!, adsLoadedWith adsLoadedData: IMAAdsLoadedData!) {
// Grab the instance of the IMAAdsManager and set ourselves as the delegate
adsManager = adsLoadedData.adsManager
adsManager?.delegate = self
// Create ads rendering settings and tell the SDK to use the in-app browser.
let adsRenderingSettings = IMAAdsRenderingSettings()
if let vc = delegate?.adWebControllerPreferredOpenViewController() {
adsRenderingSettings.webOpenerPresentingController = vc
}
// Initialize the ads manager.
adsManager?.initialize(with: adsRenderingSettings)
}
func adsLoader(_ loader: IMAAdsLoader!, failedWith adErrorData: IMAAdLoadingErrorData!) {
self.play(ignoreAds: true)
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive event: IMAAdEvent!) {
if event.type == IMAAdEventType.LOADED {
adsManager.start()
}
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive error: IMAAdError!) {
self.play(ignoreAds: true)
}
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager!) {
self.pause(ignoreAds: true)
}
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager!) {
self.play(ignoreAds: true)
}
i have a reachability class which is checked from within my VC code as below:
func setupReachability (hostName:String?, useClosures: Bool) {
let reachability = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
self.reachability = reachability
try! self.reachability?.startNotifier()
if useClosures {
reachability?.whenReachable = { reachability in
DispatchQueue.main.async {
self.connected = true
print("Reachable....")
}
}
reachability?.whenUnreachable = { reachability in
DispatchQueue.main.async {
self.connected = false
print("Not Connected....")
}
}
} else {
NotificationCenter.default.addObserver(self, selector: Selector(("reachabilityChanged:")), name: ReachabilityChangedNotification, object: reachability)
}
}
this is initialised from within viewDidLoad:
setupReachability(hostName: nil, useClosures: true)
print(self.connected)
the problem i am having is that it always returns false and i need this to load with enough time that it returns true (if it is really connected) at this point. I'm struggling to figure out where to place my setupReachability function and initialise it so that it can be accessed from anywhere in the app and return its connected state. Ive tried placing this in a separate class but can't seem to get it working or access the connected state. Any tips on how to achieve this?
I coding a VPN tool, using the NetworkExtension framework. I can connect IPSec through NEVPNManager.sharedManager, and can grab the notification when VPN connect status changed. But when I kill the app, and reopen it, the NEVPNManager.Connect.Status always Zero, than means can't display the correct connect state. How to solve it?
William Sterling comment does make sense, & it works for me,
Before adding observer for NEVPNStatusDidChange load preferences for VPN Manager object like bellow,
override func viewDidLoad() {
super.viewDidLoad()
self.vpnManager.loadFromPreferences { (error) in
if error != nil {
print(error.debugDescription)
}
else{
print("No error from loading VPN viewDidLoad")
}
}
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.VPNStatusDidChange(_:)), name: NSNotification.Name.NEVPNStatusDidChange, object: nil)
}
Try this:
func viewDidLoad() {
// Register to be notified of changes in the status. These notifications only work when app is in foreground.
notificationObserver = NSNotificationCenter.defaultCenter().addObserverForName(NEVPNStatusDidChangeNotification, object: nil , queue: nil) {
notification in
print("received NEVPNStatusDidChangeNotification")
let nevpnconn = notification.object as! NEVPNConnection
let status = nevpnconn.status
self.checkNEStatus(status)
}
}
func checkNEStatus( status:NEVPNStatus ) {
switch status {
case NEVPNStatus.Invalid:
print("NEVPNConnection: Invalid")
case NEVPNStatus.Disconnected:
print("NEVPNConnection: Disconnected")
case NEVPNStatus.Connecting:
print("NEVPNConnection: Connecting")
case NEVPNStatus.Connected:
print("NEVPNConnection: Connected")
case NEVPNStatus.Reasserting:
print("NEVPNConnection: Reasserting")
case NEVPNStatus.Disconnecting:
print("NEVPNConnection: Disconnecting")
}
}
The above code should generate the following messages when running the app with VPN already connected:
checkNEStatus: NEVPNConnection: Invalid
viewDidLoad: received NEVPNStatusDidChangeNotification
checkNEStatus: NEVPNConnection: Connected
Developing an app for an iPhone with audio files that need to be listened too through headphones.
How do I check if headphones aren't plugged in so I can tell the user to plug in headphones.
I have the following code from another thread but the audioSessionGetProperty method is deprecated. Anyone know how to alter the following code to make this work OR have there own code/solution.
Thanks.
- (BOOL)isHeadsetPluggedIn {
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
//Maybe changing it to something like the following would work for iOS7?
//AVAudioSession* session = [AVAudioSession sharedInstance];
//OSStatus error = [session setCategory:kAudioSessionProperty_AudioRoute...?
//the line below is whats giving me the warning
OSStatus error = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
&routeSize,
&route);
/* Known values of route:
* "Headset"
* "Headphone"
* "Speaker"
* "SpeakerAndMicrophone"
* "HeadphonesAndMicrophone"
* "HeadsetInOut"
* "ReceiverAndMicrophone"
* "Lineout"
*/
if (!error && (route != NULL)) {
NSString* routeStr = (__bridge NSString*)route;
NSRange headphoneRange = [routeStr rangeOfString : #"Head"];
if (headphoneRange.location != NSNotFound) return YES;
}
return NO;
}
This should work, but I cannot test it right now, I'll do in the evening.
- (BOOL)isHeadsetPluggedIn {
AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
for (AVAudioSessionPortDescription* desc in [route outputs]) {
if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])
return YES;
}
return NO;
}
Just to extend #Antonio's answer. If you need to detect whether the user has pulled out or plugged in the headphone.
#import <AVFoundation/AVFoundation.h>
// [AVAudioSession sharedInstance]; // #Boris edited: you may need it if there is no `AVAudioSession instance` created before. If doesn't work, uncomment this line.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
// don't forget to `removeObserver:`
// If the user pulls out he headphone jack, stop playing.
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification
{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
NSLog(#"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
NSLog(#"Headphone/Line plugged in");
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
NSLog(#"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
NSLog(#"Headphone/Line was pulled. Stopping player....");
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(#"AVAudioSessionRouteChangeReasonCategoryChange");
break;
}
}
Swift 3:
To check if headphones are connected
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
Then you can just print("isHeadphones connected: \(AVAudioSession.isHeadphonesConnected)")
Listening to Changes
In Swift 3 the syntax is this:
func handleRouteChange(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let reasonRaw = userInfo[AVAudioSessionRouteChangeReasonKey] as? NSNumber,
let reason = AVAudioSessionRouteChangeReason(rawValue: reasonRaw.uintValue)
else { fatalError("Strange... could not get routeChange") }
switch reason {
case .oldDeviceUnavailable:
print("oldDeviceUnavailable")
case .newDeviceAvailable:
print("newDeviceAvailable")
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
case .routeConfigurationChange:
print("routeConfigurationChange")
case .categoryChange:
print("categoryChange")
default:
print("not handling reason")
}
}
func listenForNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
}
Notice use of:
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
#Warif's code in Swift 2.0 with little changes ...
func audioRouteChangeListenerCallback (notif: NSNotification){
let userInfo:[NSObject:AnyObject] = notif.userInfo!
println("\(userInfo)")
let routChangeReason = UInt((userInfo[AVAudioSessionRouteChangeReasonKey]?.integerValue)!)
switch routChangeReason {
case AVAudioSessionRouteChangeReason.NewDeviceAvailable.rawValue:
self.println("Headphone/Line plugged in");
break;
case AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue:
//If the headphones was pulled move to speaker
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker)
} catch _ {
}
self.println("Headphone/Line was pulled. Stopping player....");
break;
case AVAudioSessionRouteChangeReason.CategoryChange.rawValue:
// called at start - also when other audio wants to play
self.println("AVAudioSessionRouteChangeReasonCategoryChange");
break;
default:
break;
}
}
:D
In Swift (as of 1.2):
func headsetPluggedIn() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
return (route.outputs as! [AVAudioSessionPortDescription]).filter({ $0.portType == AVAudioSessionPortHeadphones }).count > 0
}
Swift 3.0 version
Method to check if headphones are plugged or any Bluetooth device with audio output connected
func bluetoothOrHeadphonesConnected() -> Bool {
let outputs = AVAudioSession.sharedInstance().currentRoute.outputs
for output in outputs{
if output.portType == AVAudioSessionPortBluetoothA2DP ||
output.portType == AVAudioSessionPortBluetoothHFP ||
output.portType == AVAudioSessionPortBluetoothLE ||
output.portType == AVAudioSessionPortHeadphones {
return true
}
}
return false
}
It's important to check if the headphones are plugged out while you listen any audio.
private func setupObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(self.audioRouteChangeListener), name: .AVAudioSessionRouteChange, object: nil)
}
func audioRouteChangeListener(notification: Notification) {
guard let audioRouteChangeReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as? Int else { return }
switch audioRouteChangeReason {
case AVAudioSessionRouteChangeReason.oldDeviceUnavailable.hashValue:
//plugged out
default:
break
}
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(plugout:) name:AVAudioSessionRouteChangeNotification object:nil];
-(void)plugout:(NSNotification*)notification
{
isRemovedHeadset = YES;
}
and handle your code using this isRemovedHeadset boolean in your
if (moviePlayer.playbackState == MPMoviePlaybackStatePaused)
{
if(isRemovedHeadset)
{
isRemovedHeadset = NO;
[moviePlayer prepareToPlay];
[moviePlayer play];
return;
}
}
#Sajjon solution on Swift 5 with RxSwift
func handleRouteChange(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let reasonRaw = userInfo[AVAudioSessionRouteChangeReasonKey] as? NSNumber,
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonRaw.uintValue)
else { fatalError("Strange... could not get routeChange") }
switch reason {
case .oldDeviceUnavailable:
print("oldDeviceUnavailable")
case .newDeviceAvailable:
print("newDeviceAvailable")
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
case .routeConfigurationChange:
print("routeConfigurationChange")
case .categoryChange:
print("categoryChange")
default:
print("not handling reason")
}
}
func listenForNotifications() {
NotificationCenter.default.rx
.notification(AVAudioSession.routeChangeNotification)
.subscribe(onNext: { (n) in
self.handleRouteChange(n)
})
.disposed(by: disposeBag)
}