|
@@ -19,6 +19,7 @@
|
19
|
19
|
|
20
|
20
|
#import <React/RCTBridgeModule.h>
|
21
|
21
|
#import <React/RCTLog.h>
|
|
22
|
+#import <WebRTC/WebRTC.h>
|
22
|
23
|
|
23
|
24
|
typedef enum {
|
24
|
25
|
kAudioModeDefault,
|
|
@@ -26,16 +27,17 @@ typedef enum {
|
26
|
27
|
kAudioModeVideoCall
|
27
|
28
|
} JitsiMeetAudioMode;
|
28
|
29
|
|
29
|
|
-@interface AudioMode : NSObject<RCTBridgeModule>
|
|
30
|
+@interface AudioMode : NSObject<RCTBridgeModule, RTCAudioSessionDelegate>
|
30
|
31
|
|
31
|
32
|
@property(nonatomic, strong) dispatch_queue_t workerQueue;
|
32
|
33
|
|
33
|
34
|
@end
|
34
|
35
|
|
35
|
36
|
@implementation AudioMode {
|
36
|
|
- NSString *_avCategory;
|
37
|
|
- NSString *_avMode;
|
38
|
|
- JitsiMeetAudioMode _mode;
|
|
37
|
+ JitsiMeetAudioMode activeMode;
|
|
38
|
+ RTCAudioSessionConfiguration *defaultConfig;
|
|
39
|
+ RTCAudioSessionConfiguration *audioCallConfig;
|
|
40
|
+ RTCAudioSessionConfiguration *videoCallConfig;
|
39
|
41
|
}
|
40
|
42
|
|
41
|
43
|
RCT_EXPORT_MODULE();
|
|
@@ -55,24 +57,32 @@ RCT_EXPORT_MODULE();
|
55
|
57
|
- (instancetype)init {
|
56
|
58
|
self = [super init];
|
57
|
59
|
if (self) {
|
58
|
|
- _avCategory = nil;
|
59
|
|
- _avMode = nil;
|
60
|
|
- _mode = kAudioModeDefault;
|
61
|
|
-
|
62
|
60
|
dispatch_queue_attr_t attributes =
|
63
|
61
|
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
|
64
|
62
|
QOS_CLASS_USER_INITIATED, -1);
|
65
|
63
|
_workerQueue = dispatch_queue_create("AudioMode.queue", attributes);
|
66
|
64
|
|
67
|
|
- // AVAudioSession is a singleton and other parts of the application such as
|
68
|
|
- // WebRTC may undo the settings. Make sure that the settings are reapplied
|
69
|
|
- // upon undoes.
|
70
|
|
- [[NSNotificationCenter defaultCenter]
|
71
|
|
- addObserver:self
|
72
|
|
- selector:@selector(routeChanged:)
|
73
|
|
- name:AVAudioSessionRouteChangeNotification
|
74
|
|
- object:nil];
|
|
65
|
+ activeMode = kAudioModeDefault;
|
|
66
|
+
|
|
67
|
+ defaultConfig = [[RTCAudioSessionConfiguration alloc] init];
|
|
68
|
+ defaultConfig.category = AVAudioSessionCategoryAmbient;
|
|
69
|
+ defaultConfig.categoryOptions = 0;
|
|
70
|
+ defaultConfig.mode = AVAudioSessionModeDefault;
|
|
71
|
+
|
|
72
|
+ audioCallConfig = [[RTCAudioSessionConfiguration alloc] init];
|
|
73
|
+ audioCallConfig.category = AVAudioSessionCategoryPlayAndRecord;
|
|
74
|
+ audioCallConfig.categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth;
|
|
75
|
+ audioCallConfig.mode = AVAudioSessionModeVoiceChat;
|
|
76
|
+
|
|
77
|
+ videoCallConfig = [[RTCAudioSessionConfiguration alloc] init];
|
|
78
|
+ videoCallConfig.category = AVAudioSessionCategoryPlayAndRecord;
|
|
79
|
+ videoCallConfig.categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth;
|
|
80
|
+ videoCallConfig.mode = AVAudioSessionModeVideoChat;
|
|
81
|
+
|
|
82
|
+ RTCAudioSession *session = [RTCAudioSession sharedInstance];
|
|
83
|
+ [session addDelegate:self];
|
75
|
84
|
}
|
|
85
|
+
|
76
|
86
|
return self;
|
77
|
87
|
}
|
78
|
88
|
|
|
@@ -81,93 +91,74 @@ RCT_EXPORT_MODULE();
|
81
|
91
|
return _workerQueue;
|
82
|
92
|
}
|
83
|
93
|
|
84
|
|
-- (void)routeChanged:(NSNotification*)notification {
|
85
|
|
- NSInteger reason
|
86
|
|
- = [[notification.userInfo
|
87
|
|
- valueForKey:AVAudioSessionRouteChangeReasonKey]
|
88
|
|
- integerValue];
|
89
|
|
-
|
90
|
|
- switch (reason) {
|
91
|
|
- case AVAudioSessionRouteChangeReasonCategoryChange: {
|
92
|
|
- // The category has changed. Check if it's the one we want and adjust as
|
93
|
|
- // needed. This notification is posted on a secondary thread, so make
|
94
|
|
- // sure we switch to our worker thread.
|
95
|
|
- dispatch_async(_workerQueue, ^{
|
96
|
|
- [self setCategory:self->_avCategory mode:self->_avMode error:nil];
|
97
|
|
- });
|
98
|
|
- break;
|
99
|
|
- }
|
100
|
|
- default:
|
101
|
|
- // Do nothing.
|
102
|
|
- break;
|
103
|
|
- }
|
104
|
|
-}
|
105
|
|
-
|
106
|
|
-- (BOOL)setCategory:(NSString *)category
|
107
|
|
- mode:(NSString *)mode
|
108
|
|
- error:(NSError * _Nullable *)outError {
|
109
|
|
- AVAudioSession *session = [AVAudioSession sharedInstance];
|
110
|
|
-
|
111
|
|
- // We don't want to touch the category when setting the default mode.
|
112
|
|
- // This is to play well with other components which could be integrated
|
113
|
|
- // into the final application.
|
114
|
|
- if (_mode == kAudioModeDefault) {
|
115
|
|
- return YES;
|
116
|
|
- }
|
117
|
|
-
|
118
|
|
- // Nothing to do.
|
119
|
|
- if (category == nil && mode == nil) {
|
120
|
|
- return YES;
|
121
|
|
- }
|
122
|
|
-
|
123
|
|
- if (session.category != category
|
124
|
|
- && ![session setCategory:category error:outError]) {
|
125
|
|
- RCTLogError(@"Failed to (re)apply specified AVAudioSession category!");
|
126
|
|
- return NO;
|
127
|
|
- }
|
|
94
|
+- (BOOL)setConfig:(RTCAudioSessionConfiguration *)config
|
|
95
|
+ error:(NSError * _Nullable *)outError {
|
128
|
96
|
|
129
|
|
- if (session.mode != mode && ![session setMode:mode error:outError]) {
|
130
|
|
- RCTLogError(@"Failed to (re)apply specified AVAudioSession mode!");
|
131
|
|
- return NO;
|
132
|
|
- }
|
|
97
|
+ RTCAudioSession *session = [RTCAudioSession sharedInstance];
|
|
98
|
+ [session lockForConfiguration];
|
|
99
|
+ BOOL success = [session setConfiguration:config error:outError];
|
|
100
|
+ [session unlockForConfiguration];
|
133
|
101
|
|
134
|
|
- return YES;
|
|
102
|
+ return success;
|
135
|
103
|
}
|
136
|
104
|
|
|
105
|
+#pragma mark - Exported methods
|
|
106
|
+
|
137
|
107
|
RCT_EXPORT_METHOD(setMode:(int)mode
|
138
|
108
|
resolve:(RCTPromiseResolveBlock)resolve
|
139
|
109
|
reject:(RCTPromiseRejectBlock)reject) {
|
140
|
|
- NSString *avCategory = nil;
|
141
|
|
- NSString *avMode = nil;
|
|
110
|
+ RTCAudioSessionConfiguration *config;
|
142
|
111
|
NSError *error;
|
143
|
112
|
|
144
|
113
|
switch (mode) {
|
145
|
114
|
case kAudioModeAudioCall:
|
146
|
|
- avCategory = AVAudioSessionCategoryPlayAndRecord;
|
147
|
|
- avMode = AVAudioSessionModeVoiceChat;
|
|
115
|
+ config = audioCallConfig;
|
148
|
116
|
break;
|
149
|
117
|
case kAudioModeDefault:
|
|
118
|
+ config = defaultConfig;
|
150
|
119
|
break;
|
151
|
120
|
case kAudioModeVideoCall:
|
152
|
|
- avCategory = AVAudioSessionCategoryPlayAndRecord;
|
153
|
|
- avMode = AVAudioSessionModeVideoChat;
|
|
121
|
+ config = videoCallConfig;
|
154
|
122
|
break;
|
155
|
123
|
default:
|
156
|
124
|
reject(@"setMode", @"Invalid mode", nil);
|
157
|
125
|
return;
|
158
|
126
|
}
|
159
|
127
|
|
160
|
|
- // Save the desired/specified category and mode so that they may be
|
161
|
|
- // reapplied.
|
162
|
|
- _avCategory = avCategory;
|
163
|
|
- _avMode = avMode;
|
164
|
|
- _mode = mode;
|
|
128
|
+ activeMode = mode;
|
165
|
129
|
|
166
|
|
- if (![self setCategory:avCategory mode:avMode error:&error] || error) {
|
167
|
|
- reject(@"setMode", error.localizedDescription, error);
|
168
|
|
- } else {
|
|
130
|
+ if ([self setConfig:config error:&error]) {
|
169
|
131
|
resolve(nil);
|
|
132
|
+ } else {
|
|
133
|
+ reject(@"setMode", error.localizedDescription, error);
|
170
|
134
|
}
|
171
|
135
|
}
|
172
|
136
|
|
|
137
|
+#pragma mark - RTCAudioSessionDelegate
|
|
138
|
+
|
|
139
|
+- (void)audioSessionDidChangeRoute:(RTCAudioSession *)session
|
|
140
|
+ reason:(AVAudioSessionRouteChangeReason)reason
|
|
141
|
+ previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
|
|
142
|
+ if (reason == AVAudioSessionRouteChangeReasonCategoryChange) {
|
|
143
|
+ // The category has changed. Check if it's the one we want and adjust as
|
|
144
|
+ // needed. This notification is posted on a secondary thread, so make
|
|
145
|
+ // sure we switch to our worker thread.
|
|
146
|
+ dispatch_async(_workerQueue, ^{
|
|
147
|
+ // We don't want to touch the category when in default mode.
|
|
148
|
+ // This is to play well with other components which could be integrated
|
|
149
|
+ // into the final application.
|
|
150
|
+ if (self->activeMode != kAudioModeDefault) {
|
|
151
|
+ NSLog(@"Audio route changed, reapplying RTCAudioSession config");
|
|
152
|
+ RTCAudioSessionConfiguration *config
|
|
153
|
+ = self->activeMode == kAudioModeAudioCall ? self->audioCallConfig : self->videoCallConfig;
|
|
154
|
+ [self setConfig:config error:nil];
|
|
155
|
+ }
|
|
156
|
+ });
|
|
157
|
+ }
|
|
158
|
+}
|
|
159
|
+
|
|
160
|
+- (void)audioSession:(RTCAudioSession *)audioSession didSetActive:(BOOL)active {
|
|
161
|
+ NSLog(@"[AudioMode] Audio session didSetActive:%d", active);
|
|
162
|
+}
|
|
163
|
+
|
173
|
164
|
@end
|