391 lines
13 KiB
Objective-C
391 lines
13 KiB
Objective-C
//
|
|
// AudioSessionManager.m
|
|
//
|
|
// Copyright 2011 Jawbone Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#import <AudioToolbox/AudioToolbox.h>
|
|
|
|
#import "AudioSessionManager.h"
|
|
|
|
@interface AudioSessionManager () { // private
|
|
NSString *mMode;
|
|
|
|
BOOL mBluetoothDeviceAvailable;
|
|
BOOL mHeadsetDeviceAvailable;
|
|
|
|
NSArray *mAvailableAudioDevices;
|
|
}
|
|
|
|
@property (nonatomic, assign) BOOL bluetoothDeviceAvailable;
|
|
@property (nonatomic, assign) BOOL headsetDeviceAvailable;
|
|
@property (nonatomic, strong) NSArray *availableAudioDevices;
|
|
|
|
@end
|
|
|
|
NSString *kAudioSessionManagerMode_Record = @"AudioSessionManagerMode_Record";
|
|
NSString *kAudioSessionManagerMode_Playback = @"AudioSessionManagerMode_Playback";
|
|
|
|
NSString *kAudioSessionManagerDevice_Headset = @"AudioSessionManagerDevice_Headset";
|
|
NSString *kAudioSessionManagerDevice_Bluetooth = @"AudioSessionManagerDevice_Bluetooth";
|
|
NSString *kAudioSessionManagerDevice_Phone = @"AudioSessionManagerDevice_Phone";
|
|
NSString *kAudioSessionManagerDevice_Speaker = @"AudioSessionManagerDevice_Speaker";
|
|
|
|
// use normal logging if custom macros don't exist
|
|
#ifndef NSLogWarn
|
|
#define NSLogWarn NSLog
|
|
#endif
|
|
|
|
#ifndef NSLogError
|
|
#define NSLogError NSLog
|
|
#endif
|
|
|
|
#ifndef NSLogDebug
|
|
#define LOG_LEVEL 3
|
|
#define NSLogDebug(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
|
|
#endif
|
|
|
|
@implementation AudioSessionManager
|
|
|
|
@synthesize headsetDeviceAvailable = mHeadsetDeviceAvailable;
|
|
@synthesize bluetoothDeviceAvailable = mBluetoothDeviceAvailable;
|
|
@synthesize availableAudioDevices = mAvailableAudioDevices;
|
|
|
|
#pragma mark -
|
|
#pragma mark Singleton
|
|
|
|
#pragma mark - Singleton
|
|
|
|
#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) \
|
|
+ (classname*)sharedInstance { \
|
|
static classname* __sharedInstance; \
|
|
static dispatch_once_t onceToken; \
|
|
dispatch_once(&onceToken, ^{ \
|
|
__sharedInstance = [[classname alloc] init]; \
|
|
}); \
|
|
return __sharedInstance; \
|
|
}
|
|
|
|
SYNTHESIZE_SINGLETON_FOR_CLASS(AudioSessionManager);
|
|
|
|
- (id)init
|
|
{
|
|
if ((self = [super init])) {
|
|
mMode = kAudioSessionManagerMode_Playback;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark private functions
|
|
|
|
- (BOOL)configureAudioSessionWithDesiredAudioRoute:(NSString *)desiredAudioRoute
|
|
{
|
|
NSLogDebug(@"current mode: %@", mMode);
|
|
|
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
NSError *err;
|
|
|
|
// close down our current session...
|
|
[audioSession setActive:NO error:nil];
|
|
|
|
if ((mMode == kAudioSessionManagerMode_Record) && !audioSession.inputAvailable) {
|
|
NSLogWarn(@"device does not support recording");
|
|
return NO;
|
|
}
|
|
|
|
/*
|
|
* Need to always use AVAudioSessionCategoryPlayAndRecord to redirect output audio per
|
|
* the "Audio Session Programming Guide", so we only use AVAudioSessionCategoryPlayback when
|
|
* !inputIsAvailable - which should only apply to iPod Touches without external mics.
|
|
*/
|
|
NSString *audioCat = ((mMode == kAudioSessionManagerMode_Playback) && !audioSession.inputAvailable) ?
|
|
AVAudioSessionCategoryPlayback : AVAudioSessionCategoryPlayAndRecord;
|
|
|
|
if (![audioSession setCategory:audioCat withOptions:((desiredAudioRoute == kAudioSessionManagerDevice_Bluetooth) ? AVAudioSessionCategoryOptionAllowBluetooth : 0) error:&err]) {
|
|
NSLogWarn(@"unable to set audioSession category: %@", err);
|
|
return NO;
|
|
}
|
|
|
|
// Set our session to active...
|
|
if (![audioSession setActive:YES error:&err]) {
|
|
NSLogWarn(@"unable to set audio session active: %@", err);
|
|
return NO;
|
|
}
|
|
|
|
if (desiredAudioRoute == kAudioSessionManagerDevice_Speaker) {
|
|
// replace AudiosessionSetProperty (deprecated from iOS7) with AVAudioSession overrideOutputAudioPort
|
|
[audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&err];
|
|
}
|
|
|
|
// Display our current route...
|
|
NSLogDebug(@"current route: %@", self.audioRoute);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)detectAvailableDevices
|
|
{
|
|
// called on startup to initialize the devices that are available...
|
|
NSLogDebug(@"detectAvailableDevices");
|
|
|
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
NSError *err;
|
|
|
|
// close down our current session...
|
|
[audioSession setActive:NO error:nil];
|
|
|
|
// start a new audio session. Without activation, the default route will always be (inputs: null, outputs: Speaker)
|
|
[audioSession setActive:YES error:nil];
|
|
|
|
// Open a session and see what our default is...
|
|
if (![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&err]) {
|
|
NSLogWarn(@"unable to set audioSession category: %@", err);
|
|
return NO;
|
|
}
|
|
|
|
// Check for a wired headset...
|
|
AVAudioSessionRouteDescription *currentRoute = [audioSession currentRoute];
|
|
for (AVAudioSessionPortDescription *output in currentRoute.outputs) {
|
|
if ([[output portType] isEqualToString:AVAudioSessionPortHeadphones]) {
|
|
self.headsetDeviceAvailable = YES;
|
|
} else if ([self isBluetoothDevice:[output portType]]) {
|
|
self.bluetoothDeviceAvailable = YES;
|
|
}
|
|
}
|
|
// In case both headphones and bluetooth are connected, detect bluetooth by inputs
|
|
// Condition: iOS7 and Bluetooth input available
|
|
if ([audioSession respondsToSelector:@selector(availableInputs)]) {
|
|
for (AVAudioSessionPortDescription *input in [audioSession availableInputs]){
|
|
if ([self isBluetoothDevice:[input portType]]){
|
|
self.bluetoothDeviceAvailable = YES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.headsetDeviceAvailable) {
|
|
NSLogDebug(@"Found Headset");
|
|
}
|
|
|
|
if (self.bluetoothDeviceAvailable) {
|
|
NSLogDebug(@"Found Bluetooth");
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)currentRouteChanged:(NSNotification *)notification
|
|
{
|
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
|
|
NSInteger changeReason = [[notification.userInfo objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
|
|
|
|
AVAudioSessionRouteDescription *oldRoute = [notification.userInfo objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
|
|
NSString *oldOutput = [[oldRoute.outputs objectAtIndex:0] portType];
|
|
AVAudioSessionRouteDescription *newRoute = [audioSession currentRoute];
|
|
NSString *newOutput = [[newRoute.outputs objectAtIndex:0] portType];
|
|
|
|
switch (changeReason) {
|
|
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
|
|
{
|
|
if ([oldOutput isEqualToString:AVAudioSessionPortHeadphones]) {
|
|
|
|
self.headsetDeviceAvailable = NO;
|
|
// Special Scenario:
|
|
// when headphones are plugged in before the call and plugged out during the call
|
|
// route will change to {input: MicrophoneBuiltIn, output: Receiver}
|
|
// manually refresh session and support all devices again.
|
|
[audioSession setActive:NO error:nil];
|
|
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
|
|
[audioSession setMode:AVAudioSessionModeVoiceChat error:nil];
|
|
[audioSession setActive:YES error:nil];
|
|
|
|
|
|
} else if ([self isBluetoothDevice:oldOutput]) {
|
|
|
|
BOOL showBluetooth = NO;
|
|
// Additional checking for iOS7 devices (more accurate)
|
|
// when multiple blutooth devices connected, one is no longer available does not mean no bluetooth available
|
|
if ([audioSession respondsToSelector:@selector(availableInputs)]) {
|
|
NSArray *inputs = [audioSession availableInputs];
|
|
for (AVAudioSessionPortDescription *input in inputs){
|
|
if ([self isBluetoothDevice:[input portType]]){
|
|
showBluetooth = YES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!showBluetooth) {
|
|
self.bluetoothDeviceAvailable = NO;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
|
|
{
|
|
if ([self isBluetoothDevice:newOutput]) {
|
|
self.bluetoothDeviceAvailable = YES;
|
|
} else if ([newOutput isEqualToString:AVAudioSessionPortHeadphones]) {
|
|
self.headsetDeviceAvailable = YES;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AVAudioSessionRouteChangeReasonOverride:
|
|
{
|
|
if ([self isBluetoothDevice:oldOutput]) {
|
|
if ([audioSession respondsToSelector:@selector(availableInputs)]) {
|
|
BOOL showBluetooth = NO;
|
|
NSArray *inputs = [audioSession availableInputs];
|
|
for (AVAudioSessionPortDescription *input in inputs){
|
|
if ([self isBluetoothDevice:[input portType]]){
|
|
showBluetooth = YES;
|
|
break;
|
|
}
|
|
}
|
|
if (!showBluetooth) {
|
|
self.bluetoothDeviceAvailable = NO;
|
|
}
|
|
} else if ([newOutput isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
|
|
|
|
self.bluetoothDeviceAvailable = NO;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (BOOL)isBluetoothDevice:(NSString*)portType {
|
|
|
|
return ([portType isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
|
|
[portType isEqualToString:AVAudioSessionPortBluetoothHFP]);
|
|
}
|
|
|
|
#pragma mark public methods
|
|
|
|
- (void)start
|
|
{
|
|
[self detectAvailableDevices];
|
|
|
|
[self configureAudioSessionWithDesiredAudioRoute:kAudioSessionManagerDevice_Bluetooth];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(currentRouteChanged:)
|
|
name:AVAudioSessionRouteChangeNotification object:nil];
|
|
}
|
|
|
|
#pragma mark public methods/properties
|
|
|
|
- (BOOL)changeMode:(NSString *)value
|
|
{
|
|
if (mMode == value)
|
|
return YES;
|
|
|
|
mMode = value;
|
|
|
|
return [self configureAudioSessionWithDesiredAudioRoute:kAudioSessionManagerDevice_Bluetooth];
|
|
}
|
|
|
|
- (NSString *)audioRoute
|
|
{
|
|
AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute];
|
|
NSString *output = [[currentRoute.outputs objectAtIndex:0] portType];
|
|
|
|
if ([output isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
|
|
return kAudioSessionManagerDevice_Phone;
|
|
} else if ([output isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
|
|
return kAudioSessionManagerDevice_Speaker;
|
|
} else if ([output isEqualToString:AVAudioSessionPortHeadphones]) {
|
|
return kAudioSessionManagerDevice_Headset;
|
|
} else if ([self isBluetoothDevice:output]) {
|
|
return kAudioSessionManagerDevice_Bluetooth;
|
|
} else {
|
|
return @"Unknown Device";
|
|
}
|
|
}
|
|
|
|
- (void)setBluetoothDeviceAvailable:(BOOL)value
|
|
{
|
|
if (mBluetoothDeviceAvailable == value) {
|
|
return;
|
|
}
|
|
|
|
mBluetoothDeviceAvailable = value;
|
|
|
|
self.availableAudioDevices = nil;
|
|
}
|
|
|
|
- (void)setHeadsetDeviceAvailable:(BOOL)value
|
|
{
|
|
if (mHeadsetDeviceAvailable == value) {
|
|
return;
|
|
}
|
|
|
|
mHeadsetDeviceAvailable = value;
|
|
|
|
self.availableAudioDevices = nil;
|
|
}
|
|
|
|
- (void)setAudioRoute:(NSString *)audioRoute
|
|
{
|
|
if ([self audioRoute] == audioRoute) {
|
|
return;
|
|
}
|
|
|
|
[self configureAudioSessionWithDesiredAudioRoute:audioRoute];
|
|
}
|
|
|
|
- (BOOL)phoneDeviceAvailable
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)speakerDeviceAvailable
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (NSArray *)availableAudioDevices
|
|
{
|
|
if (!mAvailableAudioDevices) {
|
|
NSMutableArray *devices = [[NSMutableArray alloc] initWithCapacity:4];
|
|
|
|
if (self.bluetoothDeviceAvailable)
|
|
[devices addObject:kAudioSessionManagerDevice_Bluetooth];
|
|
|
|
if (self.headsetDeviceAvailable)
|
|
[devices addObject:kAudioSessionManagerDevice_Headset];
|
|
|
|
if (self.speakerDeviceAvailable)
|
|
[devices addObject:kAudioSessionManagerDevice_Speaker];
|
|
|
|
if (self.phoneDeviceAvailable)
|
|
[devices addObject:kAudioSessionManagerDevice_Phone];
|
|
|
|
self.availableAudioDevices = devices;
|
|
}
|
|
|
|
return mAvailableAudioDevices;
|
|
}
|
|
|
|
@end
|
|
|