ios: refactor AudioMode to use RTCAudioSession

RTCAudioSession is a thin wrapper around AVAudioSession provided by the WebRTC
framework. It makes some use-cases easier, and leads us closer to manual audio
unit management, which we will likely need in the near future.
This commit is contained in:
Saúl Ibarra Corretgé 2019-02-13 16:55:51 +01:00 committed by Saúl Ibarra Corretgé
parent 7289e59ca9
commit 7e320a5d38
1 changed files with 70 additions and 79 deletions

View File

@ -19,6 +19,7 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
#import <WebRTC/WebRTC.h>
typedef enum {
kAudioModeDefault,
@ -26,16 +27,17 @@ typedef enum {
kAudioModeVideoCall
} JitsiMeetAudioMode;
@interface AudioMode : NSObject<RCTBridgeModule>
@interface AudioMode : NSObject<RCTBridgeModule, RTCAudioSessionDelegate>
@property(nonatomic, strong) dispatch_queue_t workerQueue;
@end
@implementation AudioMode {
NSString *_avCategory;
NSString *_avMode;
JitsiMeetAudioMode _mode;
JitsiMeetAudioMode activeMode;
RTCAudioSessionConfiguration *defaultConfig;
RTCAudioSessionConfiguration *audioCallConfig;
RTCAudioSessionConfiguration *videoCallConfig;
}
RCT_EXPORT_MODULE();
@ -55,24 +57,32 @@ RCT_EXPORT_MODULE();
- (instancetype)init {
self = [super init];
if (self) {
_avCategory = nil;
_avMode = nil;
_mode = kAudioModeDefault;
dispatch_queue_attr_t attributes =
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
QOS_CLASS_USER_INITIATED, -1);
_workerQueue = dispatch_queue_create("AudioMode.queue", attributes);
// AVAudioSession is a singleton and other parts of the application such as
// WebRTC may undo the settings. Make sure that the settings are reapplied
// upon undoes.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(routeChanged:)
name:AVAudioSessionRouteChangeNotification
object:nil];
activeMode = kAudioModeDefault;
defaultConfig = [[RTCAudioSessionConfiguration alloc] init];
defaultConfig.category = AVAudioSessionCategoryAmbient;
defaultConfig.categoryOptions = 0;
defaultConfig.mode = AVAudioSessionModeDefault;
audioCallConfig = [[RTCAudioSessionConfiguration alloc] init];
audioCallConfig.category = AVAudioSessionCategoryPlayAndRecord;
audioCallConfig.categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth;
audioCallConfig.mode = AVAudioSessionModeVoiceChat;
videoCallConfig = [[RTCAudioSessionConfiguration alloc] init];
videoCallConfig.category = AVAudioSessionCategoryPlayAndRecord;
videoCallConfig.categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth;
videoCallConfig.mode = AVAudioSessionModeVideoChat;
RTCAudioSession *session = [RTCAudioSession sharedInstance];
[session addDelegate:self];
}
return self;
}
@ -81,93 +91,74 @@ RCT_EXPORT_MODULE();
return _workerQueue;
}
- (void)routeChanged:(NSNotification*)notification {
NSInteger reason
= [[notification.userInfo
valueForKey:AVAudioSessionRouteChangeReasonKey]
integerValue];
- (BOOL)setConfig:(RTCAudioSessionConfiguration *)config
error:(NSError * _Nullable *)outError {
switch (reason) {
case AVAudioSessionRouteChangeReasonCategoryChange: {
// The category has changed. Check if it's the one we want and adjust as
// needed. This notification is posted on a secondary thread, so make
// sure we switch to our worker thread.
dispatch_async(_workerQueue, ^{
[self setCategory:self->_avCategory mode:self->_avMode error:nil];
});
break;
}
default:
// Do nothing.
break;
}
RTCAudioSession *session = [RTCAudioSession sharedInstance];
[session lockForConfiguration];
BOOL success = [session setConfiguration:config error:outError];
[session unlockForConfiguration];
return success;
}
- (BOOL)setCategory:(NSString *)category
mode:(NSString *)mode
error:(NSError * _Nullable *)outError {
AVAudioSession *session = [AVAudioSession sharedInstance];
// We don't want to touch the category when setting the default mode.
// This is to play well with other components which could be integrated
// into the final application.
if (_mode == kAudioModeDefault) {
return YES;
}
// Nothing to do.
if (category == nil && mode == nil) {
return YES;
}
if (session.category != category
&& ![session setCategory:category error:outError]) {
RCTLogError(@"Failed to (re)apply specified AVAudioSession category!");
return NO;
}
if (session.mode != mode && ![session setMode:mode error:outError]) {
RCTLogError(@"Failed to (re)apply specified AVAudioSession mode!");
return NO;
}
return YES;
}
#pragma mark - Exported methods
RCT_EXPORT_METHOD(setMode:(int)mode
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
NSString *avCategory = nil;
NSString *avMode = nil;
RTCAudioSessionConfiguration *config;
NSError *error;
switch (mode) {
case kAudioModeAudioCall:
avCategory = AVAudioSessionCategoryPlayAndRecord;
avMode = AVAudioSessionModeVoiceChat;
config = audioCallConfig;
break;
case kAudioModeDefault:
config = defaultConfig;
break;
case kAudioModeVideoCall:
avCategory = AVAudioSessionCategoryPlayAndRecord;
avMode = AVAudioSessionModeVideoChat;
config = videoCallConfig;
break;
default:
reject(@"setMode", @"Invalid mode", nil);
return;
}
// Save the desired/specified category and mode so that they may be
// reapplied.
_avCategory = avCategory;
_avMode = avMode;
_mode = mode;
activeMode = mode;
if (![self setCategory:avCategory mode:avMode error:&error] || error) {
reject(@"setMode", error.localizedDescription, error);
} else {
if ([self setConfig:config error:&error]) {
resolve(nil);
} else {
reject(@"setMode", error.localizedDescription, error);
}
}
#pragma mark - RTCAudioSessionDelegate
- (void)audioSessionDidChangeRoute:(RTCAudioSession *)session
reason:(AVAudioSessionRouteChangeReason)reason
previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
if (reason == AVAudioSessionRouteChangeReasonCategoryChange) {
// The category has changed. Check if it's the one we want and adjust as
// needed. This notification is posted on a secondary thread, so make
// sure we switch to our worker thread.
dispatch_async(_workerQueue, ^{
// We don't want to touch the category when in default mode.
// This is to play well with other components which could be integrated
// into the final application.
if (self->activeMode != kAudioModeDefault) {
NSLog(@"Audio route changed, reapplying RTCAudioSession config");
RTCAudioSessionConfiguration *config
= self->activeMode == kAudioModeAudioCall ? self->audioCallConfig : self->videoCallConfig;
[self setConfig:config error:nil];
}
});
}
}
- (void)audioSession:(RTCAudioSession *)audioSession didSetActive:(BOOL)active {
NSLog(@"[AudioMode] Audio session didSetActive:%d", active);
}
@end