瀏覽代碼

[Android] Allow multiple JitsiMeetViews

master
Lyubo Marinov 8 年之前
父節點
當前提交
4b2add7aa6

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

@@ -30,6 +30,10 @@ import com.facebook.react.ReactRootView;
30 30
 import com.facebook.react.common.LifecycleState;
31 31
 
32 32
 import java.net.URL;
33
+import java.util.Collections;
34
+import java.util.Set;
35
+import java.util.UUID;
36
+import java.util.WeakHashMap;
33 37
 
34 38
 public class JitsiMeetView extends FrameLayout {
35 39
     /**
@@ -39,18 +43,131 @@ public class JitsiMeetView extends FrameLayout {
39 43
     private static final int BACKGROUND_COLOR = 0xFF111111;
40 44
 
41 45
     /**
42
-     * Reference to the single instance of this class we currently allow. It's
43
-     * currently used for fetching the instance from the listener's callbacks.
46
+     * React Native bridge. The instance manager allows embedding applications
47
+     * to create multiple root views off the same JavaScript bundle.
48
+     */
49
+    private static ReactInstanceManager reactInstanceManager;
50
+
51
+    private static final Set<JitsiMeetView> views
52
+        = Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
53
+
54
+    public static JitsiMeetView findViewByExternalAPIScope(
55
+            String externalAPIScope) {
56
+        for (JitsiMeetView view : views) {
57
+            if (view.externalAPIScope.equals(externalAPIScope)) {
58
+                return view;
59
+            }
60
+        }
61
+
62
+        return null;
63
+    }
64
+
65
+    /**
66
+     * Internal method to initialize the React Native instance manager. We
67
+     * create a single instance in order to load the JavaScript bundle a single
68
+     * time. All <tt>ReactRootView</tt> instances will be tied to the one and
69
+     * only <tt>ReactInstanceManager</tt>.
44 70
      *
45
-     * TODO: Lift this limitation.
71
+     * @param application - <tt>Application</tt> instance which is running.
46 72
      */
47
-    private static JitsiMeetView instance;
73
+    private static void initReactInstanceManager(Application application) {
74
+        reactInstanceManager
75
+            = ReactInstanceManager.builder()
76
+                .setApplication(application)
77
+                .setBundleAssetName("index.android.bundle")
78
+                .setJSMainModuleName("index.android")
79
+                .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
80
+                .addPackage(new com.facebook.react.shell.MainReactPackage())
81
+                .addPackage(new com.oblador.vectoricons.VectorIconsPackage())
82
+                .addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
83
+                .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
84
+                .addPackage(new com.rnimmersive.RNImmersivePackage())
85
+                .addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage())
86
+                .addPackage(
87
+                        new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
88
+                .addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage())
89
+                .setUseDeveloperSupport(BuildConfig.DEBUG)
90
+                .setInitialLifecycleState(LifecycleState.RESUMED)
91
+                .build();
92
+    }
48 93
 
49 94
     /**
50
-     * React Native bridge. The instance manager allows embedding applications
51
-     * to create multiple root views off the same JavaScript bundle.
95
+     * Activity lifecycle method which should be called from
96
+     * <tt>Activity.onBackPressed</tt> so we can do the required internal
97
+     * processing.
98
+     *
99
+     * @return - true if the back-press was processed, false otherwise. In case
100
+     * false is returned the application should call the parent's
101
+     * implementation.
52 102
      */
53
-    private static ReactInstanceManager reactInstanceManager;
103
+    public static boolean onBackPressed() {
104
+        if (reactInstanceManager != null) {
105
+            reactInstanceManager.onBackPressed();
106
+            return true;
107
+        } else {
108
+            return false;
109
+        }
110
+    }
111
+
112
+    /**
113
+     * Activity lifecycle method which should be called from
114
+     * <tt>Activity.onDestroy</tt> so we can do the required internal
115
+     * processing.
116
+     *
117
+     * @param activity - <tt>Activity</tt> being destroyed.
118
+     */
119
+    public static void onHostDestroy(Activity activity) {
120
+        if (reactInstanceManager != null) {
121
+            reactInstanceManager.onHostDestroy(activity);
122
+        }
123
+    }
124
+
125
+    /**
126
+     * Activity lifecycle method which should be called from
127
+     * <tt>Activity.onPause</tt> so we can do the required internal processing.
128
+     *
129
+     * @param activity - <tt>Activity</tt> being paused.
130
+     */
131
+    public static void onHostPause(Activity activity) {
132
+        if (reactInstanceManager != null) {
133
+            reactInstanceManager.onHostPause(activity);
134
+        }
135
+    }
136
+
137
+    /**
138
+     * Activity lifecycle method which should be called from
139
+     * <tt>Activity.onResume</tt> so we can do the required internal processing.
140
+     *
141
+     * @param activity - <tt>Activity</tt> being resumed.
142
+     */
143
+    public static void onHostResume(Activity activity) {
144
+        if (reactInstanceManager != null) {
145
+            reactInstanceManager.onHostResume(activity, null);
146
+        }
147
+    }
148
+
149
+    /**
150
+     * Activity lifecycle method which should be called from
151
+     * <tt>Activity.onNewIntent</tt> so we can do the required internal
152
+     * processing. Note that this is only needed if the activity's "launchMode"
153
+     * was set to "singleTask". This is required for deep linking to work once
154
+     * the application is already running.
155
+     *
156
+     * @param intent - <tt>Intent</tt> instance which was received.
157
+     */
158
+    public static void onNewIntent(Intent intent) {
159
+        if (reactInstanceManager != null) {
160
+            reactInstanceManager.onNewIntent(intent);
161
+        }
162
+    }
163
+
164
+    /**
165
+     * The unique identifier of this {@code JitsiMeetView} within the process
166
+     * for the purposes of {@link ExternalAPI}. The name scope was inspired by
167
+     * postis which we use on Web for the similar purposes of the iframe-based
168
+     * external API.
169
+     */
170
+    private final String externalAPIScope;
54 171
 
55 172
     /**
56 173
      * {@link JitsiMeetViewListener} instance for reporting events occurring in
@@ -71,33 +188,15 @@ public class JitsiMeetView extends FrameLayout {
71 188
     public JitsiMeetView(@NonNull Context context) {
72 189
         super(context);
73 190
 
74
-        if (instance != null) {
75
-            throw new RuntimeException(
76
-                    "Only a single instance is currently allowed");
77
-        }
78
-
79
-        /*
80
-         * TODO: Only allow a single instance for now. All React Native modules
81
-         * are kinda singletons so global state would be broken since we have a
82
-         * single bridge. Once we have that sorted out multiple instances of
83
-         * JitsiMeetView will be allowed.
84
-         */
85
-        instance = this;
86
-
87 191
         setBackgroundColor(BACKGROUND_COLOR);
88 192
 
89 193
         if (reactInstanceManager == null) {
90 194
             initReactInstanceManager(((Activity) context).getApplication());
91 195
         }
92
-    }
93 196
 
94
-    /**
95
-     * Returns the only instance of this class we currently allow creating.
96
-     *
97
-     * @return The {@code JitsiMeetView} instance.
98
-     */
99
-    public static JitsiMeetView getInstance() {
100
-        return instance;
197
+        // Hook this JitsiMeetView into ExternalAPI.
198
+        externalAPIScope = UUID.randomUUID().toString();
199
+        views.add(this);
101 200
     }
102 201
 
103 202
     /**
@@ -121,33 +220,6 @@ public class JitsiMeetView extends FrameLayout {
121 220
         return welcomePageEnabled;
122 221
     }
123 222
 
124
-    /**
125
-     * Internal method to initialize the React Native instance manager. We
126
-     * create a single instance in order to load the JavaScript bundle a single
127
-     * time. All <tt>ReactRootView</tt> instances will be tied to the one and
128
-     * only <tt>ReactInstanceManager</tt>.
129
-     *
130
-     * @param application - <tt>Application</tt> instance which is running.
131
-     */
132
-    private static void initReactInstanceManager(Application application) {
133
-        reactInstanceManager = ReactInstanceManager.builder()
134
-            .setApplication(application)
135
-            .setBundleAssetName("index.android.bundle")
136
-            .setJSMainModuleName("index.android")
137
-            .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
138
-            .addPackage(new com.facebook.react.shell.MainReactPackage())
139
-            .addPackage(new com.oblador.vectoricons.VectorIconsPackage())
140
-            .addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
141
-            .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
142
-            .addPackage(new com.rnimmersive.RNImmersivePackage())
143
-            .addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage())
144
-            .addPackage(new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
145
-            .addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage())
146
-            .setUseDeveloperSupport(BuildConfig.DEBUG)
147
-            .setInitialLifecycleState(LifecycleState.RESUMED)
148
-            .build();
149
-    }
150
-
151 223
     /**
152 224
      * Loads the given URL and displays the conference. If the specified URL is
153 225
      * null, the welcome page is displayed instead.
@@ -157,6 +229,8 @@ public class JitsiMeetView extends FrameLayout {
157 229
     public void loadURL(@Nullable URL url) {
158 230
         Bundle props = new Bundle();
159 231
 
232
+        // externalAPIScope
233
+        props.putString("externalAPIScope", externalAPIScope);
160 234
         // url
161 235
         if (url != null) {
162 236
             props.putString("url", url.toString());
@@ -199,74 +273,4 @@ public class JitsiMeetView extends FrameLayout {
199 273
     public void setWelcomePageEnabled(boolean welcomePageEnabled) {
200 274
         this.welcomePageEnabled = welcomePageEnabled;
201 275
     }
202
-
203
-    /**
204
-     * Activity lifecycle method which should be called from
205
-     * <tt>Activity.onBackPressed</tt> so we can do the required internal
206
-     * processing.
207
-     *
208
-     * @return - true if the back-press was processed, false otherwise. In case
209
-     * false is returned the application should call the parent's
210
-     * implementation.
211
-     */
212
-    public static boolean onBackPressed() {
213
-        if (reactInstanceManager != null) {
214
-            reactInstanceManager.onBackPressed();
215
-            return true;
216
-        } else {
217
-            return false;
218
-        }
219
-    }
220
-
221
-    /**
222
-     * Activity lifecycle method which should be called from
223
-     * <tt>Activity.onDestroy</tt> so we can do the required internal
224
-     * processing.
225
-     *
226
-     * @param activity - <tt>Activity</tt> being destroyed.
227
-     */
228
-    public static void onHostDestroy(Activity activity) {
229
-        if (reactInstanceManager != null) {
230
-            reactInstanceManager.onHostDestroy(activity);
231
-        }
232
-    }
233
-
234
-    /**
235
-     * Activity lifecycle method which should be called from
236
-     * <tt>Activity.onPause</tt> so we can do the required internal processing.
237
-     *
238
-     * @param activity - <tt>Activity</tt> being paused.
239
-     */
240
-    public static void onHostPause(Activity activity) {
241
-        if (reactInstanceManager != null) {
242
-            reactInstanceManager.onHostPause(activity);
243
-        }
244
-    }
245
-
246
-    /**
247
-     * Activity lifecycle method which should be called from
248
-     * <tt>Activity.onResume</tt> so we can do the required internal processing.
249
-     *
250
-     * @param activity - <tt>Activity</tt> being resumed.
251
-     */
252
-    public static void onHostResume(Activity activity) {
253
-        if (reactInstanceManager != null) {
254
-            reactInstanceManager.onHostResume(activity, null);
255
-        }
256
-    }
257
-
258
-    /**
259
-     * Activity lifecycle method which should be called from
260
-     * <tt>Activity.onNewIntent</tt> so we can do the required internal
261
-     * processing. Note that this is only needed if the activity's "launchMode"
262
-     * was set to "singleTask". This is required for deep linking to work once
263
-     * the application is already running.
264
-     *
265
-     * @param intent - <tt>Intent</tt> instance which was received.
266
-     */
267
-    public static void onNewIntent(Intent intent) {
268
-        if (reactInstanceManager != null) {
269
-            reactInstanceManager.onNewIntent(intent);
270
-        }
271
-    }
272 276
 }

+ 18
- 9
android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIModule.java 查看文件

@@ -62,22 +62,31 @@ public class ExternalAPIModule extends ReactContextBaseJavaModule {
62 62
     /**
63 63
      * Dispatches an event that occurred on JavaScript to the view's listener.
64 64
      *
65
-     * @param name - Event name.
66
-     * @param data - Ancillary data for the event.
65
+     * @param name The name of the event.
66
+     * @param data The details/specifics of the event to send determined
67
+     * by/associated with the specified {@code name}.
68
+     * @param scope
67 69
      */
68 70
     @ReactMethod
69
-    public void sendEvent(final String name, ReadableMap data) {
70
-        JitsiMeetView view = JitsiMeetView.getInstance();
71
-        JitsiMeetViewListener listener
72
-            = view != null ? view.getListener() : null;
71
+    public void sendEvent(String name, ReadableMap data, String scope) {
72
+        // The JavaScript App needs to provide uniquely identifying information
73
+        // to the native ExternalAPI module so that the latter may match the
74
+        // former to the native JitsiMeetView which hosts it.
75
+        JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
76
+
77
+        if (view == null) {
78
+            return;
79
+        }
80
+
81
+        JitsiMeetViewListener listener = view.getListener();
73 82
 
74 83
         if (listener == null) {
75 84
             return;
76 85
         }
77 86
 
78
-        // TODO: Sigh, converting a ReadableMap to a HashMap is not supported
79
-        // until React Native 0.46.
80
-        final HashMap<String, Object> dataMap = new HashMap<>();
87
+        // TODO Converting a ReadableMap to a HashMap is not supported until
88
+        // React Native 0.46.
89
+        HashMap<String, Object> dataMap = new HashMap<>();
81 90
 
82 91
         switch (name) {
83 92
         case "CONFERENCE_FAILED":

+ 4
- 2
ios/sdk/src/ExternalAPI.m 查看文件

@@ -29,8 +29,10 @@ RCT_EXPORT_MODULE();
29 29
 /**
30 30
  * Dispatches an event that occurred on JavaScript to the view's delegate.
31 31
  *
32
- * - name: name of the event.
33
- * - data: dictionary (JSON object in JS) with data associated with the event.
32
+ * @param name The name of the event.
33
+ * @param data The details/specifics of the event to send determined
34
+ * by/associated with the specified {@code name}.
35
+ * @param scope
34 36
  */
35 37
 RCT_EXPORT_METHOD(sendEvent:(NSString *)name
36 38
                        data:(NSDictionary *)data

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

@@ -2,7 +2,6 @@
2 2
 
3 3
 import { NativeModules } from 'react-native';
4 4
 
5
-import { Platform } from '../../base/react';
6 5
 import {
7 6
     CONFERENCE_FAILED,
8 7
     CONFERENCE_JOINED,
@@ -102,13 +101,8 @@ function _sendEvent(store: Object, name: string, data: Object) {
102 101
     if (app) {
103 102
         const { externalAPIScope } = app.props;
104 103
 
105
-        // TODO Lift the restriction on the JitsiMeetView instance count on
106
-        // Android as well.
107 104
         if (externalAPIScope) {
108 105
             NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
109
-        } else if (Platform.OS === 'android') {
110
-            NativeModules.ExternalAPI.sendEvent(name, data);
111
-            console.warn(name);
112 106
         }
113 107
     }
114 108
 }

Loading…
取消
儲存