From 7e320a5d3837cd948839a9f1ce09d16531cb8a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Wed, 13 Feb 2019 16:55:51 +0100 Subject: [PATCH] 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. --- ios/sdk/src/AudioMode.m | 149 +++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 79 deletions(-) diff --git a/ios/sdk/src/AudioMode.m b/ios/sdk/src/AudioMode.m index b0b086bf4..be1e402db 100644 --- a/ios/sdk/src/AudioMode.m +++ b/ios/sdk/src/AudioMode.m @@ -19,6 +19,7 @@ #import #import +#import typedef enum { kAudioModeDefault, @@ -26,16 +27,17 @@ typedef enum { kAudioModeVideoCall } JitsiMeetAudioMode; -@interface AudioMode : NSObject +@interface AudioMode : NSObject @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