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:
parent
7289e59ca9
commit
7e320a5d38
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue