浏览代码

[iOS] Fix syncing muted state with CallKit

Fix the "mute ping pong" for once and for all. This patch takes a new approach
to the problem: it keeps track of the user generated CallKit transaction ations
and avoids calling the delegate method in those cases.

This results in a much cleaner and easier to understand handling of the flow: if
the delegate method is called it means the user tapped on the mute button. When
we sync the muted state in JS with CallKit the delegate method won't be called
at all, thus avoiding the ping-pong altogether.

In addition, make sure all CallKit methods run in the UI thread. CallKit will
call our delegate methods in the UI thread too, thsu there is no need to
synchronize access to the listener / pending action sets.
j8
Saúl Ibarra Corretgé 6 年前
父节点
当前提交
47aa14e9f6

+ 5
- 0
ios/sdk/src/callkit/CallKit.m 查看文件

@@ -72,6 +72,11 @@ RCT_EXTERN void RCTRegisterModule(Class);
72 72
     [JMCallKitProxy removeListener:self];
73 73
 }
74 74
 
75
+- (dispatch_queue_t)methodQueue {
76
+    // Make sure all our methods run in the main thread.
77
+    return dispatch_get_main_queue();
78
+}
79
+
75 80
 // End call
76 81
 RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
77 82
                   resolve:(RCTPromiseResolveBlock)resolve

+ 19
- 21
ios/sdk/src/callkit/JMCallKitEmitter.swift 查看文件

@@ -22,6 +22,7 @@ import Foundation
22 22
 internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
23 23
 
24 24
     private var listeners = Set<JMCallKitEventListenerWrapper>()
25
+    private var pendingMuteActions = Set<UUID>()
25 26
 
26 27
     internal override init() {}
27 28
 
@@ -29,13 +30,10 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
29 30
 
30 31
     func addListener(_ listener: JMCallKitListener) {
31 32
         let wrapper = JMCallKitEventListenerWrapper(listener: listener)
32
-        objc_sync_enter(listeners)
33 33
         listeners.insert(wrapper)
34
-        objc_sync_exit(listeners)
35 34
     }
36 35
 
37 36
     func removeListener(_ listener: JMCallKitListener) {
38
-        objc_sync_enter(listeners)
39 37
         // XXX Constructing a new JMCallKitEventListenerWrapper instance in
40 38
         // order to remove the specified listener from listeners is (1) a bit
41 39
         // funny (though may make a statement about performance) and (2) not
@@ -58,76 +56,76 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
58 56
                 listeners.remove($0)
59 57
             }
60 58
         }
61
-        objc_sync_exit(listeners)
59
+    }
60
+
61
+    // MARK: - Add mute action
62
+
63
+    func addMuteAction(_ actionUUID: UUID) {
64
+        pendingMuteActions.insert(actionUUID)
62 65
     }
63 66
 
64 67
     // MARK: - CXProviderDelegate
65 68
 
66 69
     func providerDidReset(_ provider: CXProvider) {
67
-        objc_sync_enter(listeners)
68 70
         listeners.forEach { $0.listener?.providerDidReset?() }
69
-        objc_sync_exit(listeners)
71
+        pendingMuteActions.removeAll()
70 72
     }
71 73
 
72 74
     func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
73
-        objc_sync_enter(listeners)
74 75
         listeners.forEach {
75 76
             $0.listener?.performAnswerCall?(UUID: action.callUUID)
76 77
         }
77
-        objc_sync_exit(listeners)
78 78
 
79 79
         action.fulfill()
80 80
     }
81 81
 
82 82
     func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
83
-        objc_sync_enter(listeners)
84 83
         listeners.forEach {
85 84
             $0.listener?.performEndCall?(UUID: action.callUUID)
86 85
         }
87
-        objc_sync_exit(listeners)
88 86
 
89 87
         action.fulfill()
90 88
     }
91 89
 
92 90
     func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
93
-        objc_sync_enter(listeners)
94
-        listeners.forEach {
95
-            $0.listener?.performSetMutedCall?(UUID: action.callUUID,
96
-                                              isMuted: action.isMuted)
91
+        let uuid = pendingMuteActions.remove(action.uuid)
92
+
93
+        // XXX avoid mute actions ping-pong: if the mute action was caused by
94
+        // the JS side (we requested a transaction) don't call the delegate
95
+        // method. If it was called by the provder itself (when the user presses
96
+        // the mute button in the CallKit view) then call the delegate method.
97
+        if (uuid == nil) {
98
+            listeners.forEach {
99
+                $0.listener?.performSetMutedCall?(UUID: action.callUUID,
100
+                                                isMuted: action.isMuted)
101
+            }
97 102
         }
98
-        objc_sync_exit(listeners)
99 103
 
100 104
         action.fulfill()
101 105
     }
102 106
 
103 107
     func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
104
-        objc_sync_enter(listeners)
105 108
         listeners.forEach {
106 109
             $0.listener?.performStartCall?(UUID: action.callUUID,
107 110
                                            isVideo: action.isVideo)
108 111
         }
109
-        objc_sync_exit(listeners)
110 112
 
111 113
         action.fulfill()
112 114
     }
113 115
 
114 116
     func provider(_ provider: CXProvider,
115 117
                   didActivate audioSession: AVAudioSession) {
116
-        objc_sync_enter(listeners)
117 118
         listeners.forEach {
118 119
             $0.listener?.providerDidActivateAudioSession?(session: audioSession)
119 120
         }
120
-        objc_sync_exit(listeners)
121 121
     }
122 122
 
123 123
     func provider(_ provider: CXProvider,
124 124
                   didDeactivate audioSession: AVAudioSession) {
125
-        objc_sync_enter(listeners)
126 125
         listeners.forEach {
127 126
             $0.listener?.providerDidDeactivateAudioSession?(
128 127
                 session: audioSession)
129 128
         }
130
-        objc_sync_exit(listeners)
131 129
     }
132 130
 }
133 131
 

+ 10
- 0
ios/sdk/src/callkit/JMCallKitProxy.swift 查看文件

@@ -18,6 +18,8 @@ import CallKit
18 18
 import Foundation
19 19
 
20 20
 /// JitsiMeet CallKit proxy
21
+// NOTE: The methods this class exposes are meant to be called in the UI thread.
22
+// All delegate methods called by JMCallKitEmitter will be called in the UI thread.
21 23
 @available(iOS 10.0, *)
22 24
 @objc public final class JMCallKitProxy: NSObject {
23 25
 
@@ -151,6 +153,14 @@ import Foundation
151 153
             completion: @escaping (Error?) -> Swift.Void) {
152 154
         guard enabled else { return }
153 155
 
156
+        // XXX keep track of muted actions to avoid "ping-pong"ing. See
157
+        // JMCallKitEmitter for details on the CXSetMutedCallAction handling.
158
+        for action in transaction.actions {
159
+            if (action as? CXSetMutedCallAction) != nil {
160
+                emitter.addMuteAction(action.uuid)
161
+            }
162
+        }
163
+
154 164
         callController.request(transaction, completion: completion)
155 165
     }
156 166
 

+ 4
- 13
react/features/mobile/callkit/middleware.js 查看文件

@@ -286,23 +286,14 @@ function _onPerformEndCallAction({ callUUID }) {
286 286
  * {@code performSetMutedCallAction}.
287 287
  * @returns {void}
288 288
  */
289
-function _onPerformSetMutedCallAction({ callUUID, muted: newValue }) {
289
+function _onPerformSetMutedCallAction({ callUUID, muted }) {
290 290
     const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
291 291
     const conference = getCurrentConference(getState);
292 292
 
293 293
     if (conference && conference.callUUID === callUUID) {
294
-        // Break the loop. Audio can be muted from both CallKit and Jitsi Meet.
295
-        // We must keep them in sync, but at some point the loop needs to be
296
-        // broken. We are doing it here, on the CallKit handler.
297
-        const tracks = getState()['features/base/tracks'];
298
-        const oldValue = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
299
-
300
-        newValue = Boolean(newValue); // eslint-disable-line no-param-reassign
301
-
302
-        if (oldValue !== newValue) {
303
-            sendAnalytics(createTrackMutedEvent('audio', 'callkit', newValue));
304
-            dispatch(setAudioMuted(newValue, /* ensureTrack */ true));
305
-        }
294
+        muted = Boolean(muted); // eslint-disable-line no-param-reassign
295
+        sendAnalytics(createTrackMutedEvent('audio', 'callkit', muted));
296
+        dispatch(setAudioMuted(muted, /* ensureTrack */ true));
306 297
     }
307 298
 }
308 299
 

正在加载...
取消
保存