瀏覽代碼

[WIP] adds BroadcastService (#8336)

feat(external_api) exposes more events from JS to native and adds the ability to send actions from native to JS.
j8
tmoldovan8x8 4 年之前
父節點
當前提交
5ef60c3a7d
No account linked to committer's email address
共有 25 個文件被更改,包括 680 次插入85 次删除
  1. 3
    3
      android/app/src/main/java/org/jitsi/meet/MainActivity.java
  2. 1
    0
      android/sdk/build.gradle
  3. 5
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
  4. 84
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java
  5. 30
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEmitter.java
  6. 130
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java
  7. 15
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java
  8. 32
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java
  9. 28
    2
      android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java
  10. 80
    25
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java
  11. 85
    26
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
  12. 1
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java
  13. 1
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewListener.java
  14. 1
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
  15. 20
    20
      android/sdk/src/main/java/org/jitsi/meet/sdk/OngoingNotification.java
  16. 13
    0
      ios/app/src/ViewController.m
  17. 4
    0
      ios/sdk/sdk.xcodeproj/project.pbxproj
  18. 1
    1
      ios/sdk/src/AppInfo.m
  19. 24
    0
      ios/sdk/src/ExternalAPI.h
  20. 30
    5
      ios/sdk/src/ExternalAPI.m
  21. 4
    0
      ios/sdk/src/JitsiMeetView.h
  22. 11
    1
      ios/sdk/src/JitsiMeetView.m
  23. 21
    1
      ios/sdk/src/JitsiMeetViewDelegate.h
  24. 1
    1
      react/features/base/toolbox/components/AbstractAudioMuteButton.js
  25. 55
    0
      react/features/mobile/external-api/middleware.js

+ 3
- 3
android/app/src/main/java/org/jitsi/meet/MainActivity.java 查看文件

@@ -38,7 +38,7 @@ import java.lang.reflect.Method;
38 38
 import java.net.MalformedURLException;
39 39
 import java.net.URL;
40 40
 import java.util.Collection;
41
-import java.util.Map;
41
+import java.util.HashMap;
42 42
 
43 43
 /**
44 44
  * The one and only Activity that the Jitsi Meet app needs. The
@@ -183,8 +183,8 @@ public class MainActivity extends JitsiMeetActivity {
183 183
     }
184 184
 
185 185
     @Override
186
-    public void onConferenceTerminated(Map<String, Object> data) {
187
-        Log.d(TAG, "Conference terminated: " + data);
186
+    protected void onConferenceTerminated(HashMap<String, Object> extraData) {
187
+        Log.d(TAG, "Conference terminated: " + extraData);
188 188
     }
189 189
 
190 190
     // Activity lifecycle method overrides

+ 1
- 0
android/sdk/build.gradle 查看文件

@@ -36,6 +36,7 @@ dependencies {
36 36
 
37 37
     implementation 'androidx.appcompat:appcompat:1.2.0'
38 38
     implementation 'androidx.fragment:fragment:1.2.5'
39
+    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
39 40
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
40 41
 
41 42
     //noinspection GradleDynamicVersion

+ 5
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java 查看文件

@@ -99,6 +99,7 @@ public abstract class BaseReactView<ListenerT>
99 99
      * The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
100 100
      * events occurring in Jitsi Meet.
101 101
      */
102
+    @Deprecated
102 103
     private ListenerT listener;
103 104
 
104 105
     /**
@@ -167,6 +168,7 @@ public abstract class BaseReactView<ListenerT>
167 168
      *
168 169
      * @return The listener set on this {@code BaseReactView}.
169 170
      */
171
+    @Deprecated
170 172
     public ListenerT getListener() {
171 173
         return listener;
172 174
     }
@@ -179,8 +181,10 @@ public abstract class BaseReactView<ListenerT>
179 181
      * @param data - The details of the event associated with/specific to the
180 182
      * specified {@code name}.
181 183
      */
184
+    @Deprecated
182 185
     protected abstract void onExternalAPIEvent(String name, ReadableMap data);
183 186
 
187
+    @Deprecated
184 188
     protected void onExternalAPIEvent(
185 189
             Map<String, Method> listenerMethods,
186 190
             String name, ReadableMap data) {
@@ -215,6 +219,7 @@ public abstract class BaseReactView<ListenerT>
215 219
      *
216 220
      * @param listener The listener to set on this {@code BaseReactView}.
217 221
      */
222
+    @Deprecated
218 223
     public void setListener(ListenerT listener) {
219 224
         this.listener = listener;
220 225
     }

+ 84
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java 查看文件

@@ -0,0 +1,84 @@
1
+package org.jitsi.meet.sdk;
2
+
3
+import android.content.Intent;
4
+import android.os.Bundle;
5
+
6
+import com.facebook.react.bridge.WritableNativeMap;
7
+
8
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
9
+
10
+import java.util.HashMap;
11
+
12
+/**
13
+ * Wraps the name and extra data for events that were broadcasted locally.
14
+ */
15
+public class BroadcastAction {
16
+    private static final String TAG = BroadcastAction.class.getSimpleName();
17
+
18
+    private final Type type;
19
+    private final HashMap<String, Object> data;
20
+
21
+    public BroadcastAction(Intent intent) {
22
+        this.type = Type.buildTypeFromAction(intent.getAction());
23
+        this.data = buildDataFromBundle(intent.getExtras());
24
+    }
25
+
26
+    public Type getType() {
27
+        return this.type;
28
+    }
29
+
30
+    public HashMap<String, Object> getData() {
31
+        return this.data;
32
+    }
33
+
34
+    public WritableNativeMap getDataAsWritableNativeMap() {
35
+        WritableNativeMap nativeMap = new WritableNativeMap();
36
+
37
+        for (String key : this.data.keySet()) {
38
+            try {
39
+                // TODO add support for different types of objects
40
+                nativeMap.putString(key, this.data.get(key).toString());
41
+            } catch (Exception e) {
42
+                JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
43
+            }
44
+        }
45
+
46
+        return nativeMap;
47
+    }
48
+
49
+    private static HashMap<String, Object> buildDataFromBundle(Bundle bundle) {
50
+        HashMap<String, Object> map = new HashMap<>();
51
+
52
+        if (bundle != null) {
53
+            for (String key : bundle.keySet()) {
54
+                map.put(key, bundle.get(key));
55
+            }
56
+        }
57
+
58
+        return map;
59
+    }
60
+
61
+    enum Type {
62
+        SET_AUDIO_MUTED("org.jitsi.meet.SET_AUDIO_MUTED"),
63
+        HANG_UP("org.jitsi.meet.HANG_UP");
64
+
65
+        private final String action;
66
+
67
+        Type(String action) {
68
+            this.action = action;
69
+        }
70
+
71
+        public String getAction() {
72
+            return action;
73
+        }
74
+
75
+        private static Type buildTypeFromAction(String action) {
76
+            for (Type type : Type.values()) {
77
+                if (type.action.equalsIgnoreCase(action)) {
78
+                    return type;
79
+                }
80
+            }
81
+            return null;
82
+        }
83
+    }
84
+}

+ 30
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEmitter.java 查看文件

@@ -0,0 +1,30 @@
1
+package org.jitsi.meet.sdk;
2
+
3
+import android.content.Context;
4
+import android.content.Intent;
5
+
6
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
7
+
8
+import com.facebook.react.bridge.ReadableMap;
9
+
10
+/**
11
+ * Class used to emit events through the LocalBroadcastManager, called when events
12
+ * from JS occurred. Takes an action name from JS, builds and broadcasts the {@link BroadcastEvent}
13
+ */
14
+public class BroadcastEmitter {
15
+    private final LocalBroadcastManager localBroadcastManager;
16
+
17
+    public BroadcastEmitter(Context context) {
18
+        localBroadcastManager = LocalBroadcastManager.getInstance(context);
19
+    }
20
+
21
+    public void sendBroadcast(String name, ReadableMap data) {
22
+        BroadcastEvent event = new BroadcastEvent(name, data);
23
+
24
+        Intent intent = event.buildIntent();
25
+
26
+        if (intent != null) {
27
+            localBroadcastManager.sendBroadcast(intent);
28
+        }
29
+    }
30
+}

+ 130
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java 查看文件

@@ -0,0 +1,130 @@
1
+package org.jitsi.meet.sdk;
2
+
3
+import android.content.Intent;
4
+import android.os.Bundle;
5
+
6
+import com.facebook.react.bridge.ReadableMap;
7
+
8
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
9
+
10
+import java.util.HashMap;
11
+
12
+/**
13
+ * Wraps the name and extra data for the events that occur on the JS side and are
14
+ * to be broadcasted.
15
+ */
16
+public class BroadcastEvent {
17
+
18
+    private static final String TAG = BroadcastEvent.class.getSimpleName();
19
+
20
+    private final Type type;
21
+    private final HashMap<String, Object> data;
22
+
23
+    public BroadcastEvent(String name, ReadableMap data) {
24
+        this.type = Type.buildTypeFromName(name);
25
+        this.data = data.toHashMap();
26
+    }
27
+
28
+    public BroadcastEvent(Intent intent) {
29
+        this.type = Type.buildTypeFromAction(intent.getAction());
30
+        this.data = buildDataFromBundle(intent.getExtras());
31
+    }
32
+
33
+    public Type getType() {
34
+        return this.type;
35
+    }
36
+
37
+    public HashMap<String, Object> getData() {
38
+        return this.data;
39
+    }
40
+
41
+    public Intent buildIntent() {
42
+        if (type != null && type.action != null) {
43
+            Intent intent = new Intent(type.action);
44
+
45
+            for (String key : this.data.keySet()) {
46
+                try {
47
+                    intent.putExtra(key, this.data.get(key).toString());
48
+                } catch (Exception e) {
49
+                    JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
50
+                }
51
+            }
52
+
53
+            return intent;
54
+        }
55
+
56
+        return null;
57
+    }
58
+
59
+    private static HashMap<String, Object> buildDataFromBundle(Bundle bundle) {
60
+        if (bundle != null) {
61
+            try {
62
+                HashMap<String, Object> map = new HashMap<>();
63
+
64
+                for (String key : bundle.keySet()) {
65
+                    map.put(key, bundle.get(key));
66
+                }
67
+
68
+                return map;
69
+            } catch (Exception e) {
70
+                JitsiMeetLogger.w(TAG + " invalid extra data", e);
71
+            }
72
+        }
73
+
74
+        return null;
75
+    }
76
+
77
+    public enum Type {
78
+        CONFERENCE_JOINED("org.jitsi.meet.CONFERENCE_JOINED"),
79
+        CONFERENCE_TERMINATED("org.jitsi.meet.CONFERENCE_TERMINATED"),
80
+        CONFERENCE_WILL_JOIN("org.jitsi.meet.CONFERENCE_WILL_JOIN"),
81
+        AUDIO_MUTED_CHANGED("org.jitsi.meet.AUDIO_MUTED_CHANGED"),
82
+        PARTICIPANT_JOINED("org.jitsi.meet.PARTICIPANT_JOINED"),
83
+        PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT");
84
+
85
+        private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
86
+        private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
87
+        private static final String CONFERENCE_TERMINATED_NAME = "CONFERENCE_TERMINATED";
88
+        private static final String AUDIO_MUTED_CHANGED_NAME = "AUDIO_MUTED_CHANGED";
89
+        private static final String PARTICIPANT_JOINED_NAME = "PARTICIPANT_JOINED";
90
+        private static final String PARTICIPANT_LEFT_NAME = "PARTICIPANT_LEFT";
91
+
92
+        private final String action;
93
+
94
+        Type(String action) {
95
+            this.action = action;
96
+        }
97
+
98
+        public String getAction() {
99
+            return action;
100
+        }
101
+
102
+        private static Type buildTypeFromAction(String action) {
103
+            for (Type type : Type.values()) {
104
+                if (type.action.equalsIgnoreCase(action)) {
105
+                    return type;
106
+                }
107
+            }
108
+            return null;
109
+        }
110
+
111
+        private static Type buildTypeFromName(String name) {
112
+            switch (name) {
113
+                case CONFERENCE_WILL_JOIN_NAME:
114
+                    return CONFERENCE_WILL_JOIN;
115
+                case CONFERENCE_JOINED_NAME:
116
+                    return CONFERENCE_JOINED;
117
+                case CONFERENCE_TERMINATED_NAME:
118
+                    return CONFERENCE_TERMINATED;
119
+                case AUDIO_MUTED_CHANGED_NAME:
120
+                    return AUDIO_MUTED_CHANGED;
121
+                case PARTICIPANT_JOINED_NAME:
122
+                    return PARTICIPANT_JOINED;
123
+                case PARTICIPANT_LEFT_NAME:
124
+                    return PARTICIPANT_LEFT;
125
+            }
126
+
127
+            return null;
128
+        }
129
+    }
130
+}

+ 15
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java 查看文件

@@ -0,0 +1,15 @@
1
+package org.jitsi.meet.sdk;
2
+
3
+import android.content.Intent;
4
+
5
+public class BroadcastIntentHelper {
6
+    public static Intent buildSetAudioMutedIntent(boolean muted) {
7
+        Intent intent = new Intent(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
8
+        intent.putExtra("muted", muted);
9
+        return intent;
10
+    }
11
+
12
+    public static Intent buildHangUpIntent() {
13
+        return new Intent(BroadcastAction.Type.HANG_UP.getAction());
14
+    }
15
+}

+ 32
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java 查看文件

@@ -0,0 +1,32 @@
1
+package org.jitsi.meet.sdk;
2
+
3
+import android.content.Context;
4
+import android.content.Intent;
5
+import android.content.IntentFilter;
6
+
7
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
8
+
9
+/**
10
+ * Listens for {@link BroadcastAction}s on LocalBroadcastManager. When one occurs,
11
+ * it emits it to JS.
12
+ */
13
+public class BroadcastReceiver extends android.content.BroadcastReceiver {
14
+
15
+    public BroadcastReceiver(Context context) {
16
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
17
+
18
+        IntentFilter intentFilter = new IntentFilter();
19
+        intentFilter.addAction(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
20
+        intentFilter.addAction(BroadcastAction.Type.HANG_UP.getAction());
21
+
22
+        localBroadcastManager.registerReceiver(this, intentFilter);
23
+    }
24
+
25
+    @Override
26
+    public void onReceive(Context context, Intent intent) {
27
+        BroadcastAction action = new BroadcastAction(intent);
28
+        String actionName = action.getType().getAction();
29
+
30
+        ReactInstanceManagerHolder.emitEvent(actionName, action.getDataAsWritableNativeMap());
31
+    }
32
+}

+ 28
- 2
android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java 查看文件

@@ -1,5 +1,5 @@
1 1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,9 @@ import com.facebook.react.module.annotations.ReactModule;
24 24
 
25 25
 import org.jitsi.meet.sdk.log.JitsiMeetLogger;
26 26
 
27
+import java.util.HashMap;
28
+import java.util.Map;
29
+
27 30
 /**
28 31
  * Module implementing an API for sending events from JavaScript to native code.
29 32
  */
@@ -35,6 +38,9 @@ class ExternalAPIModule
35 38
 
36 39
     private static final String TAG = NAME;
37 40
 
41
+    private final BroadcastEmitter broadcastEmitter;
42
+    private final BroadcastReceiver broadcastReceiver;
43
+
38 44
     /**
39 45
      * Initializes a new module instance. There shall be a single instance of
40 46
      * this module throughout the lifetime of the app.
@@ -44,6 +50,9 @@ class ExternalAPIModule
44 50
      */
45 51
     public ExternalAPIModule(ReactApplicationContext reactContext) {
46 52
         super(reactContext);
53
+
54
+        broadcastEmitter = new BroadcastEmitter(reactContext);
55
+        broadcastReceiver = new BroadcastReceiver(reactContext);
47 56
     }
48 57
 
49 58
     /**
@@ -56,6 +65,22 @@ class ExternalAPIModule
56 65
         return NAME;
57 66
     }
58 67
 
68
+    /**
69
+     * Gets a mapping with the constants this module is exporting.
70
+     *
71
+     * @return a {@link Map} mapping the constants to be exported with their
72
+     * values.
73
+     */
74
+    @Override
75
+    public Map<String, Object> getConstants() {
76
+        Map<String, Object> constants = new HashMap<>();
77
+
78
+        constants.put("SET_AUDIO_MUTED", BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
79
+        constants.put("HANG_UP", BroadcastAction.Type.HANG_UP.getAction());
80
+
81
+        return constants;
82
+    }
83
+
59 84
     /**
60 85
      * Dispatches an event that occurred on the JavaScript side of the SDK to
61 86
      * the specified {@link BaseReactView}'s listener.
@@ -79,7 +104,8 @@ class ExternalAPIModule
79 104
             JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
80 105
             try {
81 106
                 view.onExternalAPIEvent(name, data);
82
-            } catch(Exception e) {
107
+                broadcastEmitter.sendBroadcast(name, data);
108
+            } catch (Exception e) {
83 109
                 JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
84 110
             }
85 111
         }

+ 80
- 25
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java 查看文件

@@ -16,33 +16,41 @@
16 16
 
17 17
 package org.jitsi.meet.sdk;
18 18
 
19
+import android.content.BroadcastReceiver;
19 20
 import android.content.Context;
20 21
 import android.content.Intent;
22
+import android.content.IntentFilter;
21 23
 import android.net.Uri;
22 24
 import android.os.Bundle;
23 25
 
24 26
 import androidx.annotation.Nullable;
25 27
 import androidx.fragment.app.FragmentActivity;
28
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
26 29
 
27 30
 import com.facebook.react.modules.core.PermissionListener;
28 31
 
29 32
 import org.jitsi.meet.sdk.log.JitsiMeetLogger;
30 33
 
31
-import java.util.Map;
32
-
34
+import java.util.HashMap;
33 35
 
34 36
 /**
35 37
  * A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
36 38
  * lifting and wires the remaining Activity lifecycle methods so it works out of the box.
37 39
  */
38 40
 public class JitsiMeetActivity extends FragmentActivity
39
-        implements JitsiMeetActivityInterface, JitsiMeetViewListener {
41
+    implements JitsiMeetActivityInterface {
40 42
 
41 43
     protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
42 44
 
43 45
     private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
44 46
     private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
45 47
 
48
+    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
49
+        @Override
50
+        public void onReceive(Context context, Intent intent) {
51
+            onBroadcastReceived(intent);
52
+        }
53
+    };
46 54
     // Helpers for starting the activity
47 55
     //
48 56
 
@@ -68,8 +76,7 @@ public class JitsiMeetActivity extends FragmentActivity
68 76
 
69 77
         setContentView(R.layout.activity_jitsi_meet);
70 78
 
71
-        // Listen for conference events.
72
-        getJitsiView().setListener(this);
79
+        registerForBroadcastMessages();
73 80
 
74 81
         if (!extraInitialize()) {
75 82
             initialize();
@@ -91,6 +98,8 @@ public class JitsiMeetActivity extends FragmentActivity
91 98
         }
92 99
         JitsiMeetOngoingConferenceService.abort(this);
93 100
 
101
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
102
+
94 103
         super.onDestroy();
95 104
     }
96 105
 
@@ -113,8 +122,8 @@ public class JitsiMeetActivity extends FragmentActivity
113 122
     public void join(@Nullable String url) {
114 123
         JitsiMeetConferenceOptions options
115 124
             = new JitsiMeetConferenceOptions.Builder()
116
-                .setRoom(url)
117
-                .build();
125
+            .setRoom(url)
126
+            .build();
118 127
         join(options);
119 128
     }
120 129
 
@@ -138,7 +147,8 @@ public class JitsiMeetActivity extends FragmentActivity
138 147
         }
139 148
     }
140 149
 
141
-    private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
150
+    private @Nullable
151
+    JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
142 152
         String action = intent.getAction();
143 153
 
144 154
         if (Intent.ACTION_VIEW.equals(action)) {
@@ -157,7 +167,7 @@ public class JitsiMeetActivity extends FragmentActivity
157 167
      * Helper function called during activity initialization. If {@code true} is returned, the
158 168
      * initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
159 169
      * called. In this case, it's up to the subclass to call the initialize method when ready.
160
-     *
170
+     * <p>
161 171
      * This is mainly required so we do some extra initialization in the Jitsi Meet app.
162 172
      *
163 173
      * @return {@code true} if the initialization will be delayed, {@code false} otherwise.
@@ -172,6 +182,37 @@ public class JitsiMeetActivity extends FragmentActivity
172 182
         join(getConferenceOptions(getIntent()));
173 183
     }
174 184
 
185
+    protected void onConferenceJoined(HashMap<String, Object> extraData) {
186
+        JitsiMeetLogger.i("Conference joined: " + extraData);
187
+        // Launch the service for the ongoing notification.
188
+        JitsiMeetOngoingConferenceService.launch(this);
189
+    }
190
+
191
+    protected void onConferenceTerminated(HashMap<String, Object> extraData) {
192
+        JitsiMeetLogger.i("Conference terminated: " + extraData);
193
+        finish();
194
+    }
195
+
196
+    protected void onConferenceWillJoin(HashMap<String, Object> extraData) {
197
+        JitsiMeetLogger.i("Conference will join: " + extraData);
198
+    }
199
+
200
+    protected void onParticipantJoined(HashMap<String, Object> extraData) {
201
+        try {
202
+            JitsiMeetLogger.i("Participant joined: ", extraData);
203
+        } catch (Exception e) {
204
+            JitsiMeetLogger.w("Invalid participant joined extraData", e);
205
+        }
206
+    }
207
+
208
+    protected void onParticipantLeft(HashMap<String, Object> extraData) {
209
+        try {
210
+            JitsiMeetLogger.i("Participant left: ", extraData);
211
+        } catch (Exception e) {
212
+            JitsiMeetLogger.w("Invalid participant left extraData", e);
213
+        }
214
+    }
215
+
175 216
     // Activity lifecycle methods
176 217
     //
177 218
 
@@ -223,24 +264,38 @@ public class JitsiMeetActivity extends FragmentActivity
223 264
         JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
224 265
     }
225 266
 
226
-    // JitsiMeetViewListener
227
-    //
267
+    private void registerForBroadcastMessages() {
268
+        IntentFilter intentFilter = new IntentFilter();
269
+        intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_JOINED.getAction());
270
+        intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_WILL_JOIN.getAction());
271
+        intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_TERMINATED.getAction());
272
+        intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_JOINED.getAction());
273
+        intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_LEFT.getAction());
228 274
 
229
-    @Override
230
-    public void onConferenceJoined(Map<String, Object> data) {
231
-        JitsiMeetLogger.i("Conference joined: " + data);
232
-        // Launch the service for the ongoing notification.
233
-        JitsiMeetOngoingConferenceService.launch(this);
275
+        LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter);
234 276
     }
235 277
 
236
-    @Override
237
-    public void onConferenceTerminated(Map<String, Object> data) {
238
-        JitsiMeetLogger.i("Conference terminated: " + data);
239
-        finish();
240
-    }
241
-
242
-    @Override
243
-    public void onConferenceWillJoin(Map<String, Object> data) {
244
-        JitsiMeetLogger.i("Conference will join: " + data);
278
+    private void onBroadcastReceived(Intent intent) {
279
+        if (intent != null) {
280
+            BroadcastEvent event = new BroadcastEvent(intent);
281
+
282
+            switch (event.getType()) {
283
+                case CONFERENCE_JOINED:
284
+                    onConferenceJoined(event.getData());
285
+                    break;
286
+                case CONFERENCE_WILL_JOIN:
287
+                    onConferenceWillJoin(event.getData());
288
+                    break;
289
+                case CONFERENCE_TERMINATED:
290
+                    onConferenceTerminated(event.getData());
291
+                    break;
292
+                case PARTICIPANT_JOINED:
293
+                    onParticipantJoined(event.getData());
294
+                    break;
295
+                case PARTICIPANT_LEFT:
296
+                    onParticipantLeft(event.getData());
297
+                    break;
298
+            }
299
+        }
245 300
     }
246 301
 }

+ 85
- 26
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java 查看文件

@@ -21,11 +21,13 @@ import android.app.Service;
21 21
 import android.content.ComponentName;
22 22
 import android.content.Context;
23 23
 import android.content.Intent;
24
+import android.content.IntentFilter;
24 25
 import android.os.Build;
25 26
 import android.os.IBinder;
26 27
 
27
-import org.jitsi.meet.sdk.log.JitsiMeetLogger;
28
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
28 29
 
30
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
29 31
 
30 32
 /**
31 33
  * This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -35,19 +37,18 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
35 37
  * See: https://developer.android.com/guide/components/services
36 38
  */
37 39
 public class JitsiMeetOngoingConferenceService extends Service
38
-        implements OngoingConferenceTracker.OngoingConferenceListener {
40
+    implements OngoingConferenceTracker.OngoingConferenceListener {
39 41
     private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
40 42
 
41
-    static final class Actions {
42
-        static final String START = TAG + ":START";
43
-        static final String HANGUP = TAG + ":HANGUP";
44
-    }
43
+    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
44
+
45
+    private boolean isAudioMuted;
45 46
 
46 47
     static void launch(Context context) {
47 48
         OngoingNotification.createOngoingConferenceNotificationChannel();
48 49
 
49 50
         Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
50
-        intent.setAction(Actions.START);
51
+        intent.setAction(Action.START.getName());
51 52
 
52 53
         ComponentName componentName;
53 54
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -70,11 +71,16 @@ public class JitsiMeetOngoingConferenceService extends Service
70 71
         super.onCreate();
71 72
 
72 73
         OngoingConferenceTracker.getInstance().addListener(this);
74
+
75
+        IntentFilter intentFilter = new IntentFilter();
76
+        intentFilter.addAction(BroadcastEvent.Type.AUDIO_MUTED_CHANGED.getAction());
77
+        LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(broadcastReceiver, intentFilter);
73 78
     }
74 79
 
75 80
     @Override
76 81
     public void onDestroy() {
77 82
         OngoingConferenceTracker.getInstance().removeListener(this);
83
+        LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(broadcastReceiver);
78 84
 
79 85
         super.onDestroy();
80 86
     }
@@ -86,26 +92,37 @@ public class JitsiMeetOngoingConferenceService extends Service
86 92
 
87 93
     @Override
88 94
     public int onStartCommand(Intent intent, int flags, int startId) {
89
-        final String action = intent.getAction();
90
-        if (Actions.START.equals(action)) {
91
-            Notification notification = OngoingNotification.buildOngoingConferenceNotification();
92
-            if (notification == null) {
95
+        final String actionName = intent.getAction();
96
+        final Action action = Action.fromName(actionName);
97
+
98
+        switch (action) {
99
+            case UNMUTE:
100
+            case MUTE:
101
+                Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
102
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
103
+                break;
104
+            case START:
105
+                Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
106
+                if (notification == null) {
107
+                    stopSelf();
108
+                    JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
109
+                } else {
110
+                    startForeground(OngoingNotification.NOTIFICATION_ID, notification);
111
+                    JitsiMeetLogger.i(TAG + " Service started");
112
+                }
113
+                break;
114
+            case HANGUP:
115
+                JitsiMeetLogger.i(TAG + " Hangup requested");
116
+
117
+                Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
118
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
119
+
93 120
                 stopSelf();
94
-                JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
95
-            } else {
96
-                startForeground(OngoingNotification.NOTIFICATION_ID, notification);
97
-                JitsiMeetLogger.i(TAG + " Service started");
98
-            }
99
-        } else if (Actions.HANGUP.equals(action)) {
100
-            JitsiMeetLogger.i(TAG + " Hangup requested");
101
-            // Abort all ongoing calls
102
-            if (AudioModeModule.useConnectionService()) {
103
-                ConnectionService.abortConnections();
104
-            }
105
-            stopSelf();
106
-        } else {
107
-            JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
108
-            stopSelf();
121
+                break;
122
+            default:
123
+                JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
124
+                stopSelf();
125
+                break;
109 126
         }
110 127
 
111 128
         return START_NOT_STICKY;
@@ -118,4 +135,46 @@ public class JitsiMeetOngoingConferenceService extends Service
118 135
             JitsiMeetLogger.i(TAG + "Service stopped");
119 136
         }
120 137
     }
138
+
139
+    public enum Action {
140
+        START(TAG + ":START"),
141
+        HANGUP(TAG + ":HANGUP"),
142
+        MUTE(TAG + ":MUTE"),
143
+        UNMUTE(TAG + ":UNMUTE");
144
+
145
+        private final String name;
146
+
147
+        Action(String name) {
148
+            this.name = name;
149
+        }
150
+
151
+        public static Action fromName(String name) {
152
+            for (Action action : Action.values()) {
153
+                if (action.name.equalsIgnoreCase(name)) {
154
+                    return action;
155
+                }
156
+            }
157
+            return null;
158
+        }
159
+
160
+        public String getName() {
161
+            return name;
162
+        }
163
+    }
164
+
165
+    private class BroadcastReceiver extends android.content.BroadcastReceiver {
166
+
167
+        @Override
168
+        public void onReceive(Context context, Intent intent) {
169
+            isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
170
+            Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
171
+            if (notification == null) {
172
+                stopSelf();
173
+                JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
174
+            } else {
175
+                startForeground(OngoingNotification.NOTIFICATION_ID, notification);
176
+                JitsiMeetLogger.i(TAG + " Service started");
177
+            }
178
+        }
179
+    }
121 180
 }

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java 查看文件

@@ -197,6 +197,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
197 197
      * by/associated with the specified {@code name}.
198 198
      */
199 199
     @Override
200
+    @Deprecated
200 201
     protected void onExternalAPIEvent(String name, ReadableMap data) {
201 202
         onExternalAPIEvent(LISTENER_METHODS, name, data);
202 203
     }

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewListener.java 查看文件

@@ -21,6 +21,7 @@ import java.util.Map;
21 21
 /**
22 22
  * Interface for listening to events coming from Jitsi Meet.
23 23
  */
24
+@Deprecated
24 25
 public interface JitsiMeetViewListener {
25 26
     /**
26 27
      * Called when a conference was joined.

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java 查看文件

@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
32 32
  * Utility methods for helping with transforming {@link ExternalAPIModule}
33 33
  * events into listener methods. Used with descendants of {@link BaseReactView}.
34 34
  */
35
+@Deprecated
35 36
 public final class ListenerUtils {
36 37
     /**
37 38
      * Extracts the methods defined in a listener and creates a mapping of this

+ 20
- 20
android/sdk/src/main/java/org/jitsi/meet/sdk/OngoingNotification.java 查看文件

@@ -23,13 +23,13 @@ import android.app.PendingIntent;
23 23
 import android.content.Context;
24 24
 import android.content.Intent;
25 25
 import android.os.Build;
26
+
26 27
 import androidx.core.app.NotificationCompat;
27 28
 
28 29
 import org.jitsi.meet.sdk.log.JitsiMeetLogger;
29 30
 
30 31
 import java.util.Random;
31 32
 
32
-
33 33
 /**
34 34
  * Helper class for creating the ongoing notification which is used with
35 35
  * {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
@@ -43,7 +43,6 @@ class OngoingNotification {
43 43
 
44 44
     static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
45 45
 
46
-
47 46
     static void createOngoingConferenceNotificationChannel() {
48 47
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
49 48
             return;
@@ -56,7 +55,7 @@ class OngoingNotification {
56 55
         }
57 56
 
58 57
         NotificationManager notificationManager
59
-            = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
58
+            = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
60 59
 
61 60
         NotificationChannel channel
62 61
             = notificationManager.getNotificationChannel(CHANNEL_ID);
@@ -73,7 +72,7 @@ class OngoingNotification {
73 72
         notificationManager.createNotificationChannel(channel);
74 73
     }
75 74
 
76
-    static Notification buildOngoingConferenceNotification() {
75
+    static Notification buildOngoingConferenceNotification(boolean isMuted) {
77 76
         Context context = ReactInstanceManagerHolder.getCurrentActivity();
78 77
         if (context == null) {
79 78
             JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
@@ -83,12 +82,7 @@ class OngoingNotification {
83 82
         Intent notificationIntent = new Intent(context, context.getClass());
84 83
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
85 84
 
86
-        NotificationCompat.Builder builder;
87
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
88
-            builder = new NotificationCompat.Builder(context, CHANNEL_ID);
89
-        } else {
90
-            builder = new NotificationCompat.Builder(context);
91
-        }
85
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
92 86
 
93 87
         builder
94 88
             .setCategory(NotificationCompat.CATEGORY_CALL)
@@ -99,21 +93,27 @@ class OngoingNotification {
99 93
             .setOngoing(true)
100 94
             .setAutoCancel(false)
101 95
             .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
102
-            .setUsesChronometer(true)
103 96
             .setOnlyAlertOnce(true)
104 97
             .setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
105 98
 
106
-        // Add a "hang-up" action only if we are using ConnectionService.
107
-        if (AudioModeModule.useConnectionService()) {
108
-            Intent hangupIntent = new Intent(context, JitsiMeetOngoingConferenceService.class);
109
-            hangupIntent.setAction(JitsiMeetOngoingConferenceService.Actions.HANGUP);
110
-            PendingIntent hangupPendingIntent
111
-                = PendingIntent.getService(context, 0, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
112
-            NotificationCompat.Action hangupAction = new NotificationCompat.Action(0, "Hang up", hangupPendingIntent);
99
+        NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, "Hang up");
113 100
 
114
-            builder.addAction(hangupAction);
115
-        }
101
+        JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted
102
+            ? JitsiMeetOngoingConferenceService.Action.UNMUTE : JitsiMeetOngoingConferenceService.Action.MUTE;
103
+        String toggleAudioTitle = isMuted ? "Unmute" : "Mute";
104
+        NotificationCompat.Action audioAction = createAction(context, toggleAudioAction, toggleAudioTitle);
105
+
106
+        builder.addAction(hangupAction);
107
+        builder.addAction(audioAction);
116 108
 
117 109
         return builder.build();
118 110
     }
111
+
112
+    private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, String title) {
113
+        Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
114
+        intent.setAction(action.getName());
115
+        PendingIntent pendingIntent
116
+            = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
117
+        return new NotificationCompat.Action(0, title, pendingIntent);
118
+    }
119 119
 }

+ 13
- 0
ios/app/src/ViewController.m 查看文件

@@ -99,9 +99,22 @@
99 99
 #if 0
100 100
 - (void)enterPictureInPicture:(NSDictionary *)data {
101 101
     [self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];
102
+
102 103
 }
103 104
 #endif
104 105
 
106
+- (void)participantJoined:(NSDictionary *)data {
107
+  NSLog(@"%@%@", @"Participant joined: ", data[@"participantId"]);
108
+}
109
+
110
+- (void)participantLeft:(NSDictionary *)data {
111
+  NSLog(@"%@%@", @"Participant left: ", data[@"participantId"]);
112
+}
113
+
114
+- (void)audioMutedChanged:(NSDictionary *)data {
115
+  NSLog(@"%@%@", @"Audio muted changed: ", data[@"muted"]);
116
+}
117
+
105 118
 #pragma mark - Helpers
106 119
 
107 120
 - (void)terminate {

+ 4
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj 查看文件

@@ -41,6 +41,7 @@
41 41
 		C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
42 42
 		C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
43 43
 		C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
44
+		C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */; };
44 45
 		C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
45 46
 		C8AFD2802462C613000293D2 /* InfoPlistUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */; };
46 47
 		DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; };
@@ -105,6 +106,7 @@
105 106
 		C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
106 107
 		C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
107 108
 		C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
109
+		C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalAPI.h; sourceTree = "<group>"; };
108 110
 		C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = "<group>"; };
109 111
 		C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = "<group>"; };
110 112
 		DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = "<group>"; };
@@ -228,6 +230,7 @@
228 230
 				0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
229 231
 				C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */,
230 232
 				C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */,
233
+				C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */,
231 234
 			);
232 235
 			path = src;
233 236
 			sourceTree = "<group>";
@@ -301,6 +304,7 @@
301 304
 				DE81A2D42316AC4D00AE1940 /* JitsiMeetLogger.h in Headers */,
302 305
 				DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */,
303 306
 				DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */,
307
+				C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */,
304 308
 				C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */,
305 309
 			);
306 310
 			runOnlyForDeploymentPostprocessing = 0;

+ 1
- 1
ios/sdk/src/AppInfo.m 查看文件

@@ -1,5 +1,5 @@
1 1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.

+ 24
- 0
ios/sdk/src/ExternalAPI.h 查看文件

@@ -0,0 +1,24 @@
1
+/* Copyright @ 2021-present 8x8, Inc.
2
+*
3
+* Licensed under the Apache License, Version 2.0 (the "License");
4
+* you may not use this file except in compliance with the License.
5
+* You may obtain a copy of the License at
6
+*
7
+*     http://www.apache.org/licenses/LICENSE-2.0
8
+*
9
+* Unless required by applicable law or agreed to in writing, software
10
+* distributed under the License is distributed on an "AS IS" BASIS,
11
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+* See the License for the specific language governing permissions and
13
+* limitations under the License.
14
+*/
15
+
16
+#import <React/RCTBridgeModule.h>
17
+#import <React/RCTEventEmitter.h>
18
+
19
+@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
20
+
21
+- (void)sendHangUp;
22
+- (void)sendSetAudioMuted: (BOOL)muted;
23
+
24
+@end

+ 30
- 5
ios/sdk/src/ExternalAPI.m 查看文件

@@ -1,5 +1,5 @@
1 1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.
@@ -14,17 +14,24 @@
14 14
  * limitations under the License.
15 15
  */
16 16
 
17
-#import <React/RCTBridgeModule.h>
18
-
17
+#import "ExternalAPI.h"
19 18
 #import "JitsiMeetView+Private.h"
20 19
 
21
-@interface ExternalAPI : NSObject<RCTBridgeModule>
22
-@end
20
+// Events
21
+static NSString * const hangUpEvent = @"org.jitsi.meet.HANG_UP";
22
+static NSString * const setAudioMutedEvent = @"org.jitsi.meet.SET_AUDIO_MUTED";
23 23
 
24 24
 @implementation ExternalAPI
25 25
 
26 26
 RCT_EXPORT_MODULE();
27 27
 
28
+- (NSDictionary *)constantsToExport {
29
+    return @{
30
+        @"HANG_UP": hangUpEvent,
31
+        @"SET_AUDIO_MUTED" : setAudioMutedEvent
32
+    };
33
+};
34
+
28 35
 /**
29 36
  * Make sure all methods in this module are invoked on the main/UI thread.
30 37
  */
@@ -32,6 +39,14 @@ RCT_EXPORT_MODULE();
32 39
     return dispatch_get_main_queue();
33 40
 }
34 41
 
42
++ (BOOL)requiresMainQueueSetup {
43
+    return NO;
44
+}
45
+
46
+- (NSArray<NSString *> *)supportedEvents {
47
+    return @[ hangUpEvent, setAudioMutedEvent ];
48
+}
49
+
35 50
 /**
36 51
  * Dispatches an event that occurred on JavaScript to the view's delegate.
37 52
  *
@@ -87,4 +102,14 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
87 102
    return methodName;
88 103
 }
89 104
 
105
+- (void)sendHangUp {
106
+    [self sendEventWithName:hangUpEvent body:nil];
107
+}
108
+
109
+- (void)sendSetAudioMuted: (BOOL)muted {
110
+    NSDictionary *data = @{ @"muted": [NSNumber numberWithBool:muted]};
111
+
112
+    [self sendEventWithName:setAudioMutedEvent body:data];
113
+}
114
+
90 115
 @end

+ 4
- 0
ios/sdk/src/JitsiMeetView.h 查看文件

@@ -37,4 +37,8 @@
37 37
  */
38 38
 - (void)leave;
39 39
 
40
+- (void)hangUp;
41
+
42
+- (void)setAudioMuted:(BOOL)muted;
43
+
40 44
 @end

+ 11
- 1
ios/sdk/src/JitsiMeetView.m 查看文件

@@ -17,6 +17,7 @@
17 17
 
18 18
 #include <mach/mach_time.h>
19 19
 
20
+#import "ExternalAPI.h"
20 21
 #import "JitsiMeet+Private.h"
21 22
 #import "JitsiMeetConferenceOptions+Private.h"
22 23
 #import "JitsiMeetView+Private.h"
@@ -49,7 +50,6 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
49 50
  * identifiers within the process).
50 51
  */
51 52
 static NSMapTable<NSString *, JitsiMeetView *> *views;
52
-
53 53
 /**
54 54
  * This gets called automagically when the program starts.
55 55
  */
@@ -115,6 +115,16 @@ static void initializeViewsMap() {
115 115
     [self setProps:@{}];
116 116
 }
117 117
 
118
+- (void)hangUp {
119
+    RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
120
+    [[bridge moduleForClass:ExternalAPI.class] sendHangUp];
121
+}
122
+
123
+- (void)setAudioMuted:(BOOL)muted {
124
+    RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
125
+    [[bridge moduleForClass:ExternalAPI.class] sendSetAudioMuted:muted];
126
+}
127
+
118 128
 #pragma mark Private methods
119 129
 
120 130
 /**

+ 21
- 1
ios/sdk/src/JitsiMeetViewDelegate.h 查看文件

@@ -1,5 +1,5 @@
1 1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.
@@ -55,4 +55,24 @@
55 55
  */
56 56
 - (void)enterPictureInPicture:(NSDictionary *)data;
57 57
 
58
+/**
59
+ * Called when a participant has joined the conference.
60
+ *
61
+ * The `data` dictionary contains a `participantId` key with the id of the participant that has joined.
62
+ */
63
+- (void)participantJoined:(NSDictionary *)data;
64
+
65
+/**
66
+ * Called when a participant has left the conference.
67
+ *
68
+ * The `data` dictionary contains a `participantId` key with the id of the participant that has left.
69
+ */
70
+- (void)participantLeft:(NSDictionary *)data;
71
+
72
+/**
73
+ * Called when audioMuted state changed.
74
+ *
75
+ * The `data` dictionary contains a `muted` key with state of the audioMuted for the localParticipant.
76
+ */
77
+- (void)audioMutedChanged:(NSDictionary *)data;
58 78
 @end

+ 1
- 1
react/features/base/toolbox/components/AbstractAudioMuteButton.js 查看文件

@@ -52,7 +52,7 @@ export default class AbstractAudioMuteButton<P: Props, S: *>
52 52
      * Helper function to perform the actual setting of the audio mute / unmute
53 53
      * action.
54 54
      *
55
-     * @param {boolean} audioMuted - Whether video should be muted or not.
55
+     * @param {boolean} audioMuted - Whether audio should be muted or not.
56 56
      * @protected
57 57
      * @returns {void}
58 58
      */

+ 55
- 0
react/features/mobile/external-api/middleware.js 查看文件

@@ -1,5 +1,9 @@
1 1
 // @flow
2 2
 
3
+import { NativeEventEmitter, NativeModules } from 'react-native';
4
+
5
+import { appNavigate } from '../../app/actions';
6
+import { APP_WILL_MOUNT } from '../../base/app/actionTypes';
3 7
 import {
4 8
     CONFERENCE_FAILED,
5 9
     CONFERENCE_JOINED,
@@ -18,7 +22,10 @@ import {
18 22
     JITSI_CONNECTION_URL_KEY,
19 23
     getURLWithoutParams
20 24
 } from '../../base/connection';
25
+import { SET_AUDIO_MUTED } from '../../base/media/actionTypes';
26
+import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants';
21 27
 import { MiddlewareRegistry } from '../../base/redux';
28
+import { muteLocal } from '../../remote-video-menu/actions';
22 29
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
23 30
 
24 31
 import { sendEvent } from './functions';
@@ -29,6 +36,9 @@ import { sendEvent } from './functions';
29 36
  */
30 37
 const CONFERENCE_TERMINATED = 'CONFERENCE_TERMINATED';
31 38
 
39
+const { ExternalAPI } = NativeModules;
40
+const eventEmitter = new NativeEventEmitter(ExternalAPI);
41
+
32 42
 /**
33 43
  * Middleware that captures Redux actions and uses the ExternalAPI module to
34 44
  * turn them into native events so the application knows about them.
@@ -41,6 +51,9 @@ MiddlewareRegistry.register(store => next => action => {
41 51
     const { type } = action;
42 52
 
43 53
     switch (type) {
54
+    case APP_WILL_MOUNT:
55
+        _registerForNativeEvents(store.dispatch);
56
+        break;
44 57
     case CONFERENCE_FAILED: {
45 58
         const { error, ...data } = action;
46 59
 
@@ -111,14 +124,56 @@ MiddlewareRegistry.register(store => next => action => {
111 124
         break;
112 125
     }
113 126
 
127
+    case PARTICIPANT_JOINED:
128
+    case PARTICIPANT_LEFT: {
129
+        const { participant } = action;
130
+
131
+        sendEvent(
132
+            store,
133
+            action.type,
134
+            /* data */ {
135
+                isLocal: participant.local,
136
+                email: participant.email,
137
+                name: participant.name,
138
+                participantId: participant.id
139
+            });
140
+        break;
141
+    }
142
+
114 143
     case SET_ROOM:
115 144
         _maybeTriggerEarlyConferenceWillJoin(store, action);
116 145
         break;
146
+
147
+    case SET_AUDIO_MUTED:
148
+        sendEvent(
149
+            store,
150
+            'AUDIO_MUTED_CHANGED',
151
+            /* data */ {
152
+                muted: action.muted
153
+            });
154
+        break;
117 155
     }
118 156
 
119 157
     return result;
120 158
 });
121 159
 
160
+/**
161
+ * Registers for events sent from the native side via NativeEventEmitter.
162
+ *
163
+ * @param {Dispatch} dispatch - The Redux dispatch function.
164
+ * @private
165
+ * @returns {void}
166
+ */
167
+function _registerForNativeEvents(dispatch) {
168
+    eventEmitter.addListener(ExternalAPI.HANG_UP, () => {
169
+        dispatch(appNavigate(undefined));
170
+    });
171
+
172
+    eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => {
173
+        dispatch(muteLocal(muted === 'true'));
174
+    });
175
+}
176
+
122 177
 /**
123 178
  * Returns a {@code String} representation of a specific error {@code Object}.
124 179
  *

Loading…
取消
儲存