瀏覽代碼

[iOS] Allow multiple JitsiMeetViews

j8
Lyubo Marinov 8 年之前
父節點
當前提交
10e5e0fdf5

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

@@ -19,10 +19,10 @@ package org.jitsi.meet.sdk;
19 19
 import android.content.Intent;
20 20
 import android.net.Uri;
21 21
 import android.os.Build;
22
+import android.os.Bundle;
22 23
 import android.provider.Settings;
23 24
 import android.support.annotation.Nullable;
24 25
 import android.support.v7.app.AppCompatActivity;
25
-import android.os.Bundle;
26 26
 
27 27
 import java.net.URL;
28 28
 

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

@@ -16,9 +16,10 @@
16 16
 
17 17
 #import "RCTBridgeModule.h"
18 18
 
19
-#import "JitsiMeetView.h"
19
+#import "JitsiMeetView+Private.h"
20 20
 
21 21
 @interface ExternalAPI : NSObject<RCTBridgeModule>
22
+
22 23
 @end
23 24
 
24 25
 @implementation ExternalAPI
@@ -31,26 +32,40 @@ RCT_EXPORT_MODULE();
31 32
  * - name: name of the event.
32 33
  * - data: dictionary (JSON object in JS) with data associated with the event.
33 34
  */
34
-RCT_EXPORT_METHOD(sendEvent:(NSString*)name data:(NSDictionary *) data) {
35
-    JitsiMeetView *view = [JitsiMeetView getInstance];
36
-    id delegate = view != nil ? view.delegate : nil;
35
+RCT_EXPORT_METHOD(sendEvent:(NSString *)name
36
+                       data:(NSDictionary *)data
37
+                      scope:(NSString *)scope) {
38
+    // The JavaScript App needs to provide uniquely identifying information to
39
+    // the native ExternalAPI module so that the latter may match the former
40
+    // to the native JitsiMeetView which hosts it.
41
+    JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
42
+
43
+    if (!view) {
44
+        return;
45
+    }
46
+
47
+    id delegate = view.delegate;
37 48
 
38
-    if (delegate == nil) {
49
+    if (!delegate) {
39 50
         return;
40 51
     }
41 52
 
42 53
     if ([name isEqualToString:@"CONFERENCE_FAILED"]
43 54
             && [delegate respondsToSelector:@selector(conferenceFailed:)]) {
44 55
         [delegate conferenceFailed:data];
56
+
45 57
     } else if ([name isEqualToString:@"CONFERENCE_JOINED"]
46 58
             && [delegate respondsToSelector:@selector(conferenceJoined:)]) {
47 59
         [delegate conferenceJoined:data];
60
+
48 61
     } else if ([name isEqualToString:@"CONFERENCE_LEFT"]
49 62
             && [delegate respondsToSelector:@selector(conferenceLeft:)]) {
50 63
         [delegate conferenceLeft:data];
64
+
51 65
     } else if ([name isEqualToString:@"CONFERENCE_WILL_JOIN"]
52 66
             && [delegate respondsToSelector:@selector(conferenceWillJoin:)]) {
53 67
         [delegate conferenceWillJoin:data];
68
+
54 69
     } else if ([name isEqualToString:@"CONFERENCE_WILL_LEAVE"]
55 70
             && [delegate respondsToSelector:@selector(conferenceWillLeave:)]) {
56 71
         [delegate conferenceWillLeave:data];

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

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

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

@@ -34,8 +34,6 @@
34 34
   sourceApplication:(NSString *)sourceApplication
35 35
          annotation:(id)annotation;
36 36
 
37
-+ (instancetype)getInstance;
38
-
39 37
 - (void)loadURL:(nullable NSURL *)url;
40 38
 
41 39
 @end

+ 90
- 84
ios/sdk/src/JitsiMeetView.m 查看文件

@@ -20,7 +20,7 @@
20 20
 #import <React/RCTLinkingManager.h>
21 21
 #import <React/RCTRootView.h>
22 22
 
23
-#import "JitsiMeetView.h"
23
+#import "JitsiMeetView+Private.h"
24 24
 #import "RCTBridgeWrapper.h"
25 25
 
26 26
 /**
@@ -46,7 +46,61 @@ RCTFatalHandler _RCTFatal = ^(NSError *error) {
46 46
     }
47 47
 };
48 48
 
49
+/**
50
+ * Helper function to dynamically load custom fonts. The UIAppFonts key in the
51
+ * plist file doesn't work for frameworks, so fonts have to be manually loaded.
52
+ */
53
+void loadCustomFonts(Class clazz) {
54
+    NSBundle *bundle = [NSBundle bundleForClass:clazz];
55
+    NSArray *fonts = [bundle objectForInfoDictionaryKey:@"JitsiMeetFonts"];
56
+
57
+    for (NSString *item in fonts) {
58
+        NSString *fontName = [item stringByDeletingPathExtension];
59
+        NSString *fontExt = [item pathExtension];
60
+        NSString *fontPath = [bundle pathForResource:fontName ofType:fontExt];
61
+        NSData *inData = [NSData dataWithContentsOfFile:fontPath];
62
+        CFErrorRef error;
63
+        CGDataProviderRef provider
64
+            = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
65
+        CGFontRef font = CGFontCreateWithDataProvider(provider);
66
+
67
+        if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
68
+            CFStringRef errorDescription = CFErrorCopyDescription(error);
69
+
70
+            NSLog(@"Failed to load font: %@", errorDescription);
71
+            CFRelease(errorDescription);
72
+        }
73
+        CFRelease(font);
74
+        CFRelease(provider);
75
+    }
76
+}
77
+
78
+/**
79
+ * Helper function to register a fatal error handler for React. Our handler
80
+ * won't kill the process, it will swallow JS errors and print stack traces
81
+ * instead.
82
+ */
83
+void registerFatalErrorHandler() {
84
+#if !DEBUG
85
+    // In the Release configuration, React Native will (intentionally) raise
86
+    // an unhandled NSException for an unhandled JavaScript error. This will
87
+    // effectively kill the application. In accord with the Web, do not kill
88
+    // the application.
89
+    if (!RCTGetFatalHandler()) {
90
+        RCTSetFatalHandler(_RCTFatal);
91
+    }
92
+#endif
93
+}
94
+
49 95
 @interface JitsiMeetView() {
96
+    /**
97
+     * The unique identifier of this {@code JitsiMeetView} within the process
98
+     * for the purposes of {@link ExternalAPI}. The name scope was inspired by
99
+     * postis which we use on Web for the similar purposes of the iframe-based
100
+     * external API.
101
+     */
102
+    NSString *externalAPIScope;
103
+
50 104
     RCTRootView *rootView;
51 105
 }
52 106
 
@@ -55,7 +109,12 @@ RCTFatalHandler _RCTFatal = ^(NSError *error) {
55 109
 @implementation JitsiMeetView
56 110
 
57 111
 static RCTBridgeWrapper *bridgeWrapper;
58
-static JitsiMeetView *instance;
112
+
113
+/**
114
+ * The {@code JitsiMeetView}s associated with their {@code ExternalAPI} scopes
115
+ * (i.e. unique identifiers within the process).
116
+ */
117
+static NSMapTable<NSString *, JitsiMeetView *> *views;
59 118
 
60 119
 #pragma mark Linking delegate helpers
61 120
 // https://facebook.github.io/react-native/docs/linking.html
@@ -79,12 +138,12 @@ static JitsiMeetView *instance;
79 138
                                annotation:annotation];
80 139
 }
81 140
 
82
-#pragma mark initializers
141
+#pragma mark Initializers
83 142
 
84 143
 - (instancetype)initWithCoder:(NSCoder *)coder {
85 144
     self = [super initWithCoder:coder];
86 145
     if (self) {
87
-        [self initialize];
146
+        [self initWithXXX];
88 147
     }
89 148
 
90 149
     return self;
@@ -93,7 +152,7 @@ static JitsiMeetView *instance;
93 152
 - (instancetype)initWithFrame:(CGRect)frame {
94 153
     self = [super initWithFrame:frame];
95 154
     if (self) {
96
-        [self initialize];
155
+        [self initWithXXX];
97 156
     }
98 157
 
99 158
     return self;
@@ -101,13 +160,15 @@ static JitsiMeetView *instance;
101 160
 
102 161
 #pragma mark API
103 162
 
104
-/*
163
+/**
105 164
  * Loads the given URL and joins the specified conference. If the specified URL
106 165
  * is null, the welcome page is shown.
107 166
  */
108 167
 - (void)loadURL:(NSURL *)url {
109 168
     NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
110 169
 
170
+    // externalAPIScope
171
+    [props setObject:externalAPIScope forKey:@"externalAPIScope"];
111 172
     // url
112 173
     if (url) {
113 174
         [props setObject:url.absoluteString forKey:@"url"];
@@ -131,99 +192,44 @@ static JitsiMeetView *instance;
131 192
     }
132 193
 }
133 194
 
134
-#pragma mark private methods
195
+#pragma mark Private methods
135 196
 
136
-+ (instancetype)getInstance {
137
-    return instance;
197
++ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
198
+    return [views objectForKey:externalAPIScope];
138 199
 }
139 200
 
140
-/*
201
+/**
141 202
  * Internal initialization:
142 203
  *
143
- *   - sets the backgroudn color
144
- *   - creates the React bridge
145
- *   - loads the necessary custom fonts
146
- *   - registers a custom fatal error error handler for React
204
+ * - sets the backgroudn color
205
+ * - creates the React bridge
206
+ * - loads the necessary custom fonts
207
+ * - registers a custom fatal error error handler for React
147 208
  */
148
-- (void)initialize {
149
-    static dispatch_once_t onceToken;
150
-
151
-    /*
152
-     * TODO: Only allow a single instance for now. All React Native modules are
153
-     * kinda singletons so global state would be broken since we have a single
154
-     * bridge. Once we have that sorted out multiple instances of JitsiMeetView
155
-     * will be allowed.
156
-     */
157
-    if (instance != nil) {
158
-        @throw [NSException
159
-            exceptionWithName:@"RuntimeError"
160
-                       reason:@"Only a single instance is currently allowed"
161
-                     userInfo:nil];
162
-    }
163
-    instance = self;
209
+- (void)initWithXXX {
210
+    static dispatch_once_t dispatchOncePredicate;
164 211
 
165
-    dispatch_once(&onceToken, ^{
166
-        // Set a background color which is in accord with the JavaScript and
167
-        // Android parts of the application and causes less perceived visual
168
-        // flicker than the default background color.
169
-        self.backgroundColor
170
-            = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
171
-
172
-        // Initialize the React bridge.
212
+    dispatch_once(&dispatchOncePredicate, ^{
213
+        // Initialize the static state of JitsiMeetView.
173 214
         bridgeWrapper = [[RCTBridgeWrapper alloc] init];
215
+        views = [NSMapTable strongToWeakObjectsMapTable];
174 216
 
175 217
         // Dynamically load custom bundled fonts.
176
-        [self loadCustomFonts];
218
+        loadCustomFonts(self.class);
177 219
 
178 220
         // Register a fatal error handler for React.
179
-        [self registerFatalErrorHandler];
221
+        registerFatalErrorHandler();
180 222
     });
181
-}
182
-
183
-/*
184
- * Helper function to dynamically load custom fonts. The UIAppFonts key in the
185
- * plist file doesn't work for frameworks, so fonts have to be manually loaded.
186
- */
187
-- (void)loadCustomFonts {
188
-    NSBundle *bundle = [NSBundle bundleForClass:self.class];
189
-    NSArray *fonts = [bundle objectForInfoDictionaryKey:@"JitsiMeetFonts"];
190
-
191
-    for (NSString *item in fonts) {
192
-        NSString *fontName = [item stringByDeletingPathExtension];
193
-        NSString *fontExt = [item pathExtension];
194
-        NSString *fontPath = [bundle pathForResource:fontName ofType:fontExt];
195
-        NSData *inData = [NSData dataWithContentsOfFile:fontPath];
196
-        CFErrorRef error;
197
-        CGDataProviderRef provider
198
-            = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
199
-        CGFontRef font = CGFontCreateWithDataProvider(provider);
200 223
 
201
-        if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
202
-            CFStringRef errorDescription = CFErrorCopyDescription(error);
203
-
204
-            NSLog(@"Failed to load font: %@", errorDescription);
205
-            CFRelease(errorDescription);
206
-        }
207
-        CFRelease(font);
208
-        CFRelease(provider);
209
-    }
210
-}
224
+    // Hook this JitsiMeetView into ExternalAPI.
225
+    externalAPIScope = [NSUUID UUID].UUIDString;
226
+    [views setObject:self forKey:externalAPIScope];
211 227
 
212
-/*
213
- * Helper function to register a fatal error handler for React. Our handler
214
- * won't kill the process, it will swallow JS errors and print stack traces
215
- * instead.
216
- */
217
-- (void)registerFatalErrorHandler {
218
-#if !DEBUG
219
-    // In the Release configuration, React Native will (intentionally) raise
220
-    // an unhandled NSException for an unhandled JavaScript error. This will
221
-    // effectively kill the application. In accord with the Web, do not kill
222
-    // the application.
223
-    if (!RCTGetFatalHandler()) {
224
-        RCTSetFatalHandler(_RCTFatal);
225
-    }
226
-#endif
228
+    // Set a background color which is in accord with the JavaScript and
229
+    // Android parts of the application and causes less perceived visual
230
+    // flicker than the default background color.
231
+    self.backgroundColor
232
+        = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
227 233
 }
228 234
 
229 235
 @end

+ 22
- 18
ios/sdk/src/JitsiMeetViewDelegate.h 查看文件

@@ -18,41 +18,45 @@
18 18
 
19 19
 @optional
20 20
 
21
-/*
22
- * Called when a joining a conference was unsuccessful or when there was an error
23
- * while in a conference.
21
+/**
22
+ * Called when a joining a conference was unsuccessful or when there was an
23
+ * error while in a conference.
24 24
  *
25
- * The `data` dictionary contains an "error" key describing the error and a "url"
26
- * key with the conference URL.
25
+ * The {@code data} dictionary contains an "error" key describing the error and
26
+ * a {@code url} key with the conference URL.
27 27
  */
28
-- (void) conferenceFailed:(NSDictionary *) data;
28
+- (void) conferenceFailed:(NSDictionary *)data;
29 29
 
30
-/*
30
+/**
31 31
  * Called when a conference was joined.
32 32
  *
33
- * The `data` dictionary contains a "url" key with the conference URL.
33
+ * The {@code data} dictionary contains a {@code url} key with the conference
34
+ * URL.
34 35
  */
35
-- (void) conferenceJoined:(NSDictionary *) data;
36
+- (void) conferenceJoined:(NSDictionary *)data;
36 37
 
37
-/*
38
+/**
38 39
  * Called when a conference was left.
39 40
  *
40
- * The `data` dictionary contains a "url" key with the conference URL.
41
+ * The {@code data} dictionary contains a {@code url} key with the conference
42
+ * URL.
41 43
  */
42
-- (void) conferenceLeft:(NSDictionary *) data;
44
+- (void) conferenceLeft:(NSDictionary *)data;
43 45
 
44
-/*
46
+/**
45 47
  * Called before a conference is joined.
46 48
  *
47
- * The `data` dictionary contains a "url" key with the conference URL.
49
+ * The {@code data} dictionary contains a {@code url} key with the conference
50
+ * URL.
48 51
  */
49
-- (void) conferenceWillJoin:(NSDictionary *) data;
52
+- (void) conferenceWillJoin:(NSDictionary *)data;
50 53
 
51
-/*
54
+/**
52 55
  * Called before a conference is left.
53 56
  *
54
- * The `data` dictionary contains a "url" key with the conference URL.
57
+ * The {@code data} dictionary contains a {@code url} key with the conference
58
+ * URL.
55 59
  */
56
-- (void) conferenceWillLeave:(NSDictionary *) data;
60
+- (void) conferenceWillLeave:(NSDictionary *)data;
57 61
 
58 62
 @end

+ 3
- 3
ios/sdk/src/RCTBridgeWrapper.m 查看文件

@@ -16,7 +16,7 @@
16 16
 
17 17
 #include "RCTBridgeWrapper.h"
18 18
 
19
-/*
19
+/**
20 20
  * Wrapper around RCTBridge which also implements the RCTBridgeDelegate methods,
21 21
  * allowing us to specify where the bundles are loaded from.
22 22
  */
@@ -56,9 +56,9 @@ static NSURL *serverRootWithHost(NSString *host) {
56 56
 
57 57
 - (NSString *)guessPackagerHost {
58 58
     static NSString *ipGuess;
59
-    static dispatch_once_t onceToken;
59
+    static dispatch_once_t dispatchOncePredicate;
60 60
 
61
-    dispatch_once(&onceToken, ^{
61
+    dispatch_once(&dispatchOncePredicate, ^{
62 62
         NSString *ipPath
63 63
             = [[NSBundle bundleForClass:self.class] pathForResource:@"ip"
64 64
                                                              ofType:@"txt"];

+ 32
- 5
react/features/mobile/external-api/middleware.js 查看文件

@@ -2,6 +2,7 @@
2 2
 
3 3
 import { NativeModules } from 'react-native';
4 4
 
5
+import { Platform } from '../../base/react';
5 6
 import {
6 7
     CONFERENCE_FAILED,
7 8
     CONFERENCE_JOINED,
@@ -41,8 +42,8 @@ MiddlewareRegistry.register(store => next => action => {
41 42
             // of locationURL at the time of CONFERENCE_WILL_LEAVE and
42 43
             // CONFERENCE_LEFT will not be the value with which the
43 44
             // JitsiConference instance being left.)
44
-            const { locationURL }
45
-                = store.getState()['features/base/connection'];
45
+            const state = store.getState();
46
+            const { locationURL } = state['features/base/connection'];
46 47
 
47 48
             if (!locationURL) {
48 49
                 // The (redux) action cannot be fully converted to an (external
@@ -63,7 +64,14 @@ MiddlewareRegistry.register(store => next => action => {
63 64
             name = name.slice(7, -1);
64 65
         }
65 66
 
66
-        _sendEvent(name, data);
67
+        // The polyfill es6-symbol that we use does not appear to comply with
68
+        // the Symbol standard and, merely, adds @@ at the beginning of the
69
+        // description.
70
+        if (name.startsWith('@@')) {
71
+            name = name.slice(2);
72
+        }
73
+
74
+        _sendEvent(store, name, data);
67 75
         break;
68 76
     }
69 77
     }
@@ -76,12 +84,31 @@ MiddlewareRegistry.register(store => next => action => {
76 84
  * apps may listen to such events via the mechanisms provided by the (native)
77 85
  * mobile Jitsi Meet SDK.
78 86
  *
87
+ * @param {Object} store - The redux store associated with the need to send the
88
+ * specified event.
79 89
  * @param {string} name - The name of the event to send.
80 90
  * @param {Object} data - The details/specifics of the event to send determined
81 91
  * by/associated with the specified {@code name}.
82 92
  * @private
83 93
  * @returns {void}
84 94
  */
85
-function _sendEvent(name: string, data: Object) {
86
-    NativeModules.ExternalAPI.sendEvent(name, data);
95
+function _sendEvent(store: Object, name: string, data: Object) {
96
+    // The JavaScript App needs to provide uniquely identifying information
97
+    // to the native ExternalAPI module so that the latter may match the former
98
+    // to the native JitsiMeetView which hosts it.
99
+    const state = store.getState();
100
+    const { app } = state['features/app'];
101
+
102
+    if (app) {
103
+        const { externalAPIScope } = app.props;
104
+
105
+        // TODO Lift the restriction on the JitsiMeetView instance count on
106
+        // Android as well.
107
+        if (externalAPIScope) {
108
+            NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
109
+        } else if (Platform.OS === 'android') {
110
+            NativeModules.ExternalAPI.sendEvent(name, data);
111
+            console.warn(name);
112
+        }
113
+    }
87 114
 }

Loading…
取消
儲存