浏览代码

[RN] Add Picture-in-Picture support (Coding style: naming, consistency)

master
Lyubo Marinov 7 年前
父节点
当前提交
b8de5bbfc3

+ 30
- 29
android/README.md 查看文件

@@ -118,17 +118,17 @@ public class MainActivity extends AppCompatActivity {
118 118
     }
119 119
 
120 120
     @Override
121
-    protected void onStop() {
122
-        super.onStop();
121
+    protected void onResume() {
122
+        super.onResume();
123 123
 
124
-        JitsiMeetView.onHostPause(this);
124
+        JitsiMeetView.onHostResume(this);
125 125
     }
126 126
 
127 127
     @Override
128
-    protected void onResume() {
129
-        super.onResume();
128
+    protected void onStop() {
129
+        super.onStop();
130 130
 
131
-        JitsiMeetView.onHostResume(this);
131
+        JitsiMeetView.onHostPause(this);
132 132
     }
133 133
 }
134 134
 ```
@@ -142,9 +142,9 @@ which displays a single `JitsiMeetView`.
142 142
 
143 143
 See JitsiMeetView.getDefaultURL.
144 144
 
145
-#### getPictureInPictureAvailable()
145
+#### getPictureInPictureEnabled()
146 146
 
147
-See JitsiMeetView.getPictureInPictureAvailable.
147
+See JitsiMeetView.getPictureInPictureEnabled.
148 148
 
149 149
 #### getWelcomePageEnabled()
150 150
 
@@ -158,9 +158,9 @@ See JitsiMeetView.loadURL.
158 158
 
159 159
 See JitsiMeetView.setDefaultURL.
160 160
 
161
-#### setPictureInPictureAvailable(Boolean)
161
+#### setPictureInPictureEnabled(boolean)
162 162
 
163
-See JitsiMeetView.setPictureInPictureAvailable.
163
+See JitsiMeetView.setPictureInPictureEnabled.
164 164
 
165 165
 #### setWelcomePageEnabled(boolean)
166 166
 
@@ -187,11 +187,11 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
187 187
 
188 188
 Returns the `JitsiMeetViewListener` instance attached to the view.
189 189
 
190
-#### getPictureInPictureAvailable()
190
+#### getPictureInPictureEnabled()
191 191
 
192
-turns true if Picture-in-Picture is available, false otherwise. If the user
193
-doesn't explicitly set it, it will default to true if the platform supports it,
194
-false otherwise. See the Picture-in-Picture section.
192
+Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
193
+explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
194
+`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
195 195
 
196 196
 #### getWelcomePageEnabled()
197 197
 
@@ -234,26 +234,30 @@ view.loadURLObject(urlObject);
234 234
 
235 235
 Sets the default URL. See `getDefaultURL` for more information.
236 236
 
237
-NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
237
+NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
238
+effect.
238 239
 
239 240
 #### setListener(listener)
240 241
 
241 242
 Sets the given listener (class implementing the `JitsiMeetViewListener`
242 243
 interface) on the view.
243 244
 
244
-#### setPictureInPictureAvailable(Boolean)
245
+#### setPictureInPictureEnabled(boolean)
245 246
 
246
-Sets wether Picture-in-Picture is available. When set to `null` if will be
247
-detected at runtime based on platform support.
247
+Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
248
+automatically enables/disables Picture-in-Picture based on native platform
249
+support.
248 250
 
249
-NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
251
+NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
252
+effect.
250 253
 
251 254
 #### setWelcomePageEnabled(boolean)
252 255
 
253 256
 Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
254 257
 information.
255 258
 
256
-NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
259
+NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
260
+effect.
257 261
 
258 262
 #### onBackPressed()
259 263
 
@@ -416,13 +420,10 @@ rules file:
416 420
 
417 421
 ## Picture-in-Picture
418 422
 
419
-The Jitsi Meet app and SDK will enable Android's native Picture-in-Picture mode
420
-iff the platform is supported: for Android >= Oreo.
421
-
422
-If the SDK is integrated in an application which calls
423
-`enterPictureInPictureMode` for the Jitsi Meet activity, the it will self-adjust
424
-by removing some UI elements.
425
-
426
-Alternatively, this can be explicitly disabled with the
427
-`setPctureInPictureAvailable` methods in the Jitsi Meet view or activity.
423
+`JitsiMeetView` will automatically adjust its UI when presented in a
424
+Picture-in-Picture style scenario, in a rectangle too small to accommodate its
425
+"full" UI.
428 426
 
427
+Jitsi Meet SDK automatically enables (unless explicitly disabled by a
428
+`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
429
+mode iff the platform is supported i.e. Android >= Oreo.

+ 1
- 1
android/app/src/main/AndroidManifest.xml 查看文件

@@ -7,7 +7,7 @@
7 7
       android:label="@string/app_name"
8 8
       android:theme="@style/AppTheme">
9 9
     <activity
10
-        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
10
+        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
11 11
         android:label="@string/app_name"
12 12
         android:launchMode="singleTask"
13 13
         android:name=".MainActivity"

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

@@ -97,9 +97,8 @@ public class MainActivity extends JitsiMeetActivity {
97 97
         // As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
98 98
         // want to enable some options.
99 99
 
100
-        // The welcome page defaults to disabled in the
101
-        // SDK at the time of this writing but it is clearer to be explicit
102
-        // about what we want anyway.
100
+        // The welcome page defaults to disabled in the SDK at the time of this
101
+        // writing but it is clearer to be explicit about what we want anyway.
103 102
         setWelcomePageEnabled(true);
104 103
 
105 104
         super.onCreate(savedInstanceState);

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

@@ -69,10 +69,10 @@ public class JitsiMeetActivity extends AppCompatActivity {
69 69
     private JitsiMeetView view;
70 70
 
71 71
     /**
72
-     * Whether Picture-in-Picture is available. The value is used only while
72
+     * Whether Picture-in-Picture is enabled. The value is used only while
73 73
      * {@link #view} equals {@code null}.
74 74
      */
75
-    private Boolean pipAvailable;
75
+    private Boolean pictureInPictureEnabled;
76 76
 
77 77
     /**
78 78
      * Whether the Welcome page is enabled. The value is used only while
@@ -98,11 +98,13 @@ public class JitsiMeetActivity extends AppCompatActivity {
98 98
 
99 99
     /**
100 100
      *
101
-     * @see JitsiMeetView#getPictureInPictureAvailable()
101
+     * @see JitsiMeetView#getPictureInPictureEnabled()
102 102
      */
103
-    public Boolean getPictureInPictureAvailable() {
104
-        return view == null
105
-            ? pipAvailable : view.getPictureInPictureAvailable();
103
+    public boolean getPictureInPictureEnabled() {
104
+        return
105
+            view == null
106
+                ? pictureInPictureEnabled
107
+                : view.getPictureInPictureEnabled();
106 108
     }
107 109
 
108 110
     /**
@@ -137,7 +139,10 @@ public class JitsiMeetActivity extends AppCompatActivity {
137 139
         // XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
138 140
         // is documented to need such an order in order to take effect:
139 141
         view.setDefaultURL(defaultURL);
140
-        view.setPictureInPictureAvailable(pipAvailable);
142
+        if (pictureInPictureEnabled != null) {
143
+            view.setPictureInPictureEnabled(
144
+                pictureInPictureEnabled.booleanValue());
145
+        }
141 146
         view.setWelcomePageEnabled(welcomePageEnabled);
142 147
 
143 148
         view.loadURL(null);
@@ -273,13 +278,14 @@ public class JitsiMeetActivity extends AppCompatActivity {
273 278
 
274 279
     /**
275 280
      *
276
-     * @see JitsiMeetView#setPictureInPictureAvailable(Boolean)
281
+     * @see JitsiMeetView#setPictureInPictureEnabled(boolean)
277 282
      */
278
-    public void setPictureInPictureAvailable(Boolean pipAvailable) {
283
+    public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
279 284
         if (view == null) {
280
-            this.pipAvailable = pipAvailable;
285
+            this.pictureInPictureEnabled
286
+                = Boolean.valueOf(pictureInPictureEnabled);
281 287
         } else {
282
-            view.setPictureInPictureAvailable(pipAvailable);
288
+            view.setPictureInPictureEnabled(pictureInPictureEnabled);
283 289
         }
284 290
     }
285 291
 

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

@@ -300,10 +300,11 @@ public class JitsiMeetView extends FrameLayout {
300 300
     private JitsiMeetViewListener listener;
301 301
 
302 302
     /**
303
-     * Whether Picture-in-Picture is available. If {@code null}  it will default
304
-     * to {@code true} iff the platform supports it.
303
+     * Whether Picture-in-Picture is enabled. If {@code null}, defaults to
304
+     * {@code true} iff the Android platform supports Picture-in-Picture
305
+     * natively.
305 306
      */
306
-    private Boolean pipAvailable;
307
+    private Boolean pictureInPictureEnabled;
307 308
 
308 309
     /**
309 310
      * React Native root view.
@@ -370,14 +371,18 @@ public class JitsiMeetView extends FrameLayout {
370 371
     }
371 372
 
372 373
     /**
373
-     * Gets whether Picture-in-Picture is currently available. It's only
374
-     * supported on Android API >= 26 (Oreo), so it should not be enabled on
375
-     * older platform versions.
374
+     * Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
375
+     * natively supported on Android API >= 26 (Oreo), so it should not be
376
+     * enabled on older platform versions.
376 377
      *
377
-     * @return {@code true} if PiP is available, {@code false} otherwise.
378
+     * @return If Picture-in-Picture is enabled, {@code true}; {@code false},
379
+     * otherwise.
378 380
      */
379
-    public Boolean getPictureInPictureAvailable() {
380
-        return pipAvailable;
381
+    public boolean getPictureInPictureEnabled() {
382
+        return
383
+            PictureInPictureModule.isPictureInPictureSupported()
384
+                && (pictureInPictureEnabled == null
385
+                    || pictureInPictureEnabled.booleanValue());
381 386
     }
382 387
 
383 388
     /**
@@ -425,15 +430,10 @@ public class JitsiMeetView extends FrameLayout {
425 430
         // externalAPIScope
426 431
         props.putString("externalAPIScope", externalAPIScope);
427 432
 
428
-        // pipAvailable
429
-        boolean pipAvailable_;
430
-        if (pipAvailable == null) {
431
-            // set it based on platform availability
432
-            pipAvailable_ = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
433
-        } else {
434
-            pipAvailable_ = pipAvailable.booleanValue();
435
-        }
436
-        props.putBoolean("pipAvailable", pipAvailable_);
433
+        // pictureInPictureEnabled
434
+        props.putBoolean(
435
+            "pictureInPictureEnabled",
436
+            getPictureInPictureEnabled());
437 437
 
438 438
         // url
439 439
         if (urlObject != null) {
@@ -457,7 +457,9 @@ public class JitsiMeetView extends FrameLayout {
457 457
         if (reactRootView == null) {
458 458
             reactRootView = new ReactRootView(getContext());
459 459
             reactRootView.startReactApplication(
460
-                reactInstanceManager, "App", props);
460
+                reactInstanceManager,
461
+                "App",
462
+                props);
461 463
             reactRootView.setBackgroundColor(BACKGROUND_COLOR);
462 464
             addView(reactRootView);
463 465
         } else {
@@ -528,13 +530,15 @@ public class JitsiMeetView extends FrameLayout {
528 530
     }
529 531
 
530 532
     /**
531
-     * Sets whether Picture-in-Picture is currently available.
533
+     * Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
534
+     * natively supported only since certain platform versions, specifying
535
+     * {@code true} will have no effect on unsupported platform versions.
532 536
      *
533
-     * @param pipAvailable {@code true} if PiP is available, {@code false}
534
-     * otherwise.
537
+     * @param pictureInPictureEnabled To enable Picture-in-Picture,
538
+     * {@code true}; otherwise, {@code false}.
535 539
      */
536
-    public void setPictureInPictureAvailable(Boolean pipAvailable) {
537
-        this.pipAvailable = pipAvailable;
540
+    public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
541
+        this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
538 542
     }
539 543
 
540 544
     /**

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

@@ -14,48 +14,54 @@ import com.facebook.react.bridge.ReactMethod;
14 14
 public class PictureInPictureModule extends ReactContextBaseJavaModule {
15 15
     private final static String TAG = "PictureInPicture";
16 16
 
17
-    public PictureInPictureModule(ReactApplicationContext reactContext) {
18
-        super(reactContext);
17
+    static boolean isPictureInPictureSupported() {
18
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
19 19
     }
20 20
 
21
-    @Override
22
-    public String getName() {
23
-        return TAG;
21
+    public PictureInPictureModule(ReactApplicationContext reactContext) {
22
+        super(reactContext);
24 23
     }
25 24
 
26 25
     /**
27
-     * Enters Picture-in-Picture mode for the current activity. This is only
28
-     * supported in Android API >= 26.
26
+     * Enters Picture-in-Picture (mode) for the current {@link Activity}.
27
+     * Supported on Android API >= 26 (Oreo) only.
29 28
      *
30 29
      * @param promise a {@code Promise} which will resolve with a {@code null}
31
-     *                value in case of success, and an error otherwise.
30
+     * value upon success, and an {@link Exception} otherwise.
32 31
      */
33 32
     @ReactMethod
34
-    public void enterPictureInPictureMode(Promise promise) {
35
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
36
-            final Activity currentActivity = getCurrentActivity();
33
+    public void enterPictureInPicture(Promise promise) {
34
+        if (isPictureInPictureSupported()) {
35
+            Activity currentActivity = getCurrentActivity();
37 36
 
38 37
             if (currentActivity == null) {
39 38
                 promise.reject(new Exception("No current Activity!"));
40 39
                 return;
41 40
             }
42 41
 
43
-            Log.d(TAG, "Entering PiP mode");
42
+            Log.d(TAG, "Entering Picture-in-Picture");
43
+
44
+            PictureInPictureParams.Builder builder
45
+                = new PictureInPictureParams.Builder()
46
+                    .setAspectRatio(new Rational(1, 1));
47
+            boolean r
48
+                = currentActivity.enterPictureInPictureMode(builder.build());
44 49
 
45
-            final PictureInPictureParams.Builder pipParamsBuilder
46
-                = new PictureInPictureParams.Builder();
47
-            pipParamsBuilder.setAspectRatio(new Rational(1, 1)).build();
48
-            final boolean r
49
-                = currentActivity.enterPictureInPictureMode(pipParamsBuilder.build());
50 50
             if (r) {
51 51
                 promise.resolve(null);
52 52
             } else {
53
-                promise.reject(new Exception("Error entering PiP mode"));
53
+                promise.reject(
54
+                    new Exception("Failed to enter Picture-in-Picture"));
54 55
             }
55 56
 
56 57
             return;
57 58
         }
58 59
 
59
-        promise.reject(new Exception("PiP not supported"));
60
+        promise.reject(new Exception("Picture-in-Picture not supported"));
61
+    }
62
+
63
+    @Override
64
+    public String getName() {
65
+        return TAG;
60 66
     }
61 67
 }

+ 28
- 22
ios/README.md 查看文件

@@ -53,21 +53,24 @@ partial URL (e.g. a room name only) is specified to
53 53
 `loadURLString:`/`loadURLObject:`. If not set or if set to `nil`, the default
54 54
 built in JavaScript is used: https://meet.jit.si.
55 55
 
56
-NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
56
+NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
57
+effect.
57 58
 
58
-#### pipAvailable
59
+#### pictureInPictureEnabled
59 60
 
60
-Property to get / set wether a Picture-in-Picture mode is available. This must
61
-be implemented by the application at the moment.
61
+Property to get / set whether Picture-in-Picture is enabled. Defaults to `YES`
62
+if `delegate` implements `enterPictureInPicture:`; otherwise, `NO`.
62 63
 
63
-NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
64
+NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
65
+effect.
64 66
 
65 67
 #### welcomePageEnabled
66 68
 
67 69
 Property to get/set whether the Welcome page is enabled. If `NO`, a black empty
68 70
 view will be rendered when not in a conference. Defaults to `NO`.
69 71
 
70
-NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
72
+NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
73
+effect.
71 74
 
72 75
 #### loadURL:NSURL
73 76
 
@@ -177,6 +180,16 @@ Called before a conference is left.
177 180
 
178 181
 The `data` dictionary contains a "url" key with the conference URL.
179 182
 
183
+#### enterPictureInPicture
184
+
185
+Called when entering Picture-in-Picture is requested by the user. The app should
186
+now activate its Picture-in-Picture implementation (and resize the associated
187
+`JitsiMeetView`. The latter will automatically detect its new size and adjust
188
+its user interface to a variant appropriate for the small size ordinarily
189
+associated with Picture-in-Picture.)
190
+
191
+The `data` dictionary is empty.
192
+
180 193
 #### loadConfigError
181 194
 
182 195
 Called when loading the main configuration file from the Jitsi Meet deployment
@@ -186,23 +199,16 @@ The `data` dictionary contains an "error" key with the error and a "url" key
186 199
 with the conference URL which necessitated the loading of the configuration
187 200
 file.
188 201
 
189
-#### requestPipMode
190
-
191
-Called when the user requested Picture-in-Picture mode to be entered. At this
192
-point the application should resize the SDK view to a smaller size if it so
193
-desires.
194
-
195 202
 ### Picture-in-Picture
196 203
 
197
-The Jitsi Meet SDK implements a "reduced UI mode" which will automatically
198
-adjust the UI when presented in a Picture-in-Picture style scenario. Enabling
199
-a native Picture-in-Picture mode on iOS is not currently implemented on the SDK
200
-so applications need to do it themselves.
201
-
202
-When `pipAvailable` is set to `YES` or the `requestPipMode` delegate method is
203
-implemented, the in-call toolbar will show a button to enter PiP mode. It's up
204
-to the application to reduce the size of the SDK view and put it in such mode.
204
+`JitsiMeetView` will automatically adjust its UI when presented in a
205
+Picture-in-Picture style scenario, in a rectangle too small to accommodate its
206
+"full" UI.
205 207
 
206
-Once PiP mode has been entered, the SDK will automatically adjust its UI
207
-elements.
208
+Jitsi Meet SDK does not currently implement native Picture-in-Picture on iOS. If
209
+desired, apps need to implement non-native Picture-in-Picture themselves and
210
+resize `JitsiMeetView`.
208 211
 
212
+If `pictureInPictureEnabled` is set to `YES` or `delegate` implements
213
+`enterPictureInPicture:`, the in-call toolbar will render a button to afford the
214
+user to request entering Picture-in-Picture.

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

@@ -25,7 +25,7 @@
25 25
 
26 26
 @property (copy, nonatomic, nullable) NSURL *defaultURL;
27 27
 
28
-@property (nonatomic) BOOL pipAvailable;
28
+@property (nonatomic) BOOL pictureInPictureEnabled;
29 29
 
30 30
 @property (nonatomic) BOOL welcomePageEnabled;
31 31
 

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

@@ -110,10 +110,10 @@ void registerFatalErrorHandler() {
110 110
 @end
111 111
 
112 112
 @implementation JitsiMeetView {
113
-    NSNumber *_pipAvailable;
113
+    NSNumber *_pictureInPictureEnabled;
114 114
 }
115 115
 
116
-@dynamic pipAvailable;
116
+@dynamic pictureInPictureEnabled;
117 117
 
118 118
 static RCTBridgeWrapper *bridgeWrapper;
119 119
 
@@ -269,7 +269,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
269 269
     }
270 270
 
271 271
     props[@"externalAPIScope"] = externalAPIScope;
272
-    props[@"pipAvailable"] = @(self.pipAvailable);
272
+    props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
273 273
     props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
274 274
 
275 275
     // XXX If urlObject is nil, then it must appear as undefined in the
@@ -320,19 +320,26 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
320 320
     [self loadURLObject:urlString ? @{ @"url": urlString } : nil];
321 321
 }
322 322
 
323
-#pragma pipAvailable getter / setter
323
+#pragma pictureInPictureEnabled getter / setter
324 324
 
325
-- (void) setPipAvailable:(BOOL)pipAvailable {
326
-    _pipAvailable = [NSNumber numberWithBool:pipAvailable];
325
+- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
326
+    _pictureInPictureEnabled
327
+        = [NSNumber numberWithBool:pictureInPictureEnabled];
327 328
 }
328 329
 
329
-- (BOOL) pipAvailable {
330
-    if (_pipAvailable == nil) {
331
-        return self.delegate
332
-            && [self.delegate respondsToSelector:@selector(requestPipMode:)];
330
+- (BOOL) pictureInPictureEnabled {
331
+    if (_pictureInPictureEnabled) {
332
+        return [_pictureInPictureEnabled boolValue];
333 333
     }
334 334
 
335
-    return [_pipAvailable boolValue];
335
+    // The SDK/JitsiMeetView client/consumer did not explicitly enable/disable
336
+    // Picture-in-Picture. However, we may automatically deduce their
337
+    // intentions: we need the support of the client in order to implement
338
+    // Picture-in-Picture on iOS (in contrast to Android) so if the client
339
+    // appears to have provided the support then we can assume that they did it
340
+    // with the intention to have Picture-in-Picture enabled.
341
+    return self.delegate
342
+        && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
336 343
 }
337 344
 
338 345
 #pragma mark Private methods

+ 11
- 9
ios/sdk/src/JitsiMeetViewDelegate.h 查看文件

@@ -55,6 +55,17 @@
55 55
  */
56 56
 - (void)conferenceWillLeave:(NSDictionary *)data;
57 57
 
58
+/**
59
+ * Called when entering Picture-in-Picture is requested by the user. The app
60
+ * should now activate its Picture-in-Picture implementation (and resize the
61
+ * associated `JitsiMeetView`. The latter will automatically detect its new size
62
+ * and adjust its user interface to a variant appropriate for the small size
63
+ * ordinarily associated with Picture-in-Picture.)
64
+ *
65
+ * The `data` dictionary is empty.
66
+ */
67
+- (void)enterPictureInPicture:(NSDictionary *)data;
68
+
58 69
 /**
59 70
  * Called when loading the main configuration file from the Jitsi Meet
60 71
  * deployment file.
@@ -65,13 +76,4 @@
65 76
  */
66 77
 - (void)loadConfigError:(NSDictionary *)data;
67 78
 
68
-/**
69
- * Called when Picture-in-Picture mode is requested. The app should now resize
70
- * iself to a PiP style and then use the JitsiMeetView.onPipModeChanged to
71
- * notify the JavaScript side about its action.
72
- *
73
- * The `data` dictionary is currently empty.
74
- */
75
-- (void)requestPipMode:(NSDictionary *)data;
76
-
77 79
 @end

+ 4
- 3
react/features/app/components/App.native.js 查看文件

@@ -38,10 +38,11 @@ export class App extends AbstractApp {
38 38
         ...AbstractApp.propTypes,
39 39
 
40 40
         /**
41
-         * Whether Picture-in-Picture is available. If available, a button will
42
-         * be shown in the {@link Conference} view so the user can enter it.
41
+         * Whether Picture-in-Picture is enabled. If {@code true}, a toolbar
42
+         * button is rendered in the {@link Conference} view to afford entering
43
+         * Picture-in-Picture.
43 44
          */
44
-        pipAvailable: PropTypes.bool,
45
+        pictureInPictureEnabled: PropTypes.bool,
45 46
 
46 47
         /**
47 48
          * Whether the Welcome page is enabled. If {@code true}, the Welcome

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

@@ -13,8 +13,7 @@ import {
13 13
 import { LOAD_CONFIG_ERROR } from '../../base/config';
14 14
 import { MiddlewareRegistry } from '../../base/redux';
15 15
 import { toURLString } from '../../base/util';
16
-
17
-import { REQUEST_PIP_MODE } from '../picture-in-picture';
16
+import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
18 17
 
19 18
 /**
20 19
  * Middleware that captures Redux actions and uses the ExternalAPI module to
@@ -55,6 +54,10 @@ MiddlewareRegistry.register(store => next => action => {
55 54
         _sendConferenceEvent(store, action);
56 55
         break;
57 56
 
57
+    case ENTER_PICTURE_IN_PICTURE:
58
+        _sendEvent(store, _getSymbolDescription(action.type), /* data */ {});
59
+        break;
60
+
58 61
     case LOAD_CONFIG_ERROR: {
59 62
         const { error, locationURL, type } = action;
60 63
 
@@ -64,10 +67,6 @@ MiddlewareRegistry.register(store => next => action => {
64 67
         });
65 68
         break;
66 69
     }
67
-
68
-    case REQUEST_PIP_MODE:
69
-        _sendEvent(store, _getSymbolDescription(action.type), /* data */ {});
70
-
71 70
     }
72 71
 
73 72
     return result;

+ 11
- 9
react/features/mobile/picture-in-picture/actionTypes.js 查看文件

@@ -1,22 +1,24 @@
1 1
 /**
2
- * The type of redux action to set the PiP related event listeners.
2
+ * The type of redux action to enter (or rather initiate entering)
3
+ * picture-in-picture.
3 4
  *
4 5
  * {
5
- *     type: _SET_PIP_MODE_LISTENER,
6
- *     listeners: Array|undefined
6
+ *      type: ENTER_PICTURE_IN_PICTURE
7 7
  * }
8 8
  *
9
- * @protected
9
+ * @public
10 10
  */
11
-export const _SET_PIP_LISTENERS = Symbol('_SET_PIP_LISTENERS');
11
+export const ENTER_PICTURE_IN_PICTURE = Symbol('ENTER_PICTURE_IN_PICTURE');
12 12
 
13 13
 /**
14
- * The type of redux action which signals that the PiP mode is requested.
14
+ * The type of redux action to set the {@code EventEmitter} subscriptions
15
+ * utilized by the feature picture-in-picture.
15 16
  *
16 17
  * {
17
- *      type: REQUEST_PIP_MODE
18
+ *     type: _SET_EMITTER_SUBSCRIPTIONS,
19
+ *     emitterSubscriptions: Array|undefined
18 20
  * }
19 21
  *
20
- * @public
22
+ * @protected
21 23
  */
22
-export const REQUEST_PIP_MODE = Symbol('REQUEST_PIP_MODE');
24
+export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');

+ 41
- 18
react/features/mobile/picture-in-picture/actions.js 查看文件

@@ -1,37 +1,60 @@
1 1
 // @flow
2 2
 
3
+import { NativeModules } from 'react-native';
4
+
3 5
 import {
4
-    _SET_PIP_LISTENERS,
5
-    REQUEST_PIP_MODE
6
+    ENTER_PICTURE_IN_PICTURE,
7
+    _SET_EMITTER_SUBSCRIPTIONS
6 8
 } from './actionTypes';
7 9
 
8 10
 /**
9
- * Sets the listeners for the PiP related events.
11
+ * Enters (or rather initiates entering) picture-in-picture.
12
+ * Helper function to enter PiP mode. This is triggered by user request
13
+ * (either pressing the button in the toolbox or the home button on Android)
14
+ * ans this triggers the PiP mode, iff it's available and we are in a
15
+ * conference.
10 16
  *
11
- * @param {Array} listeners - Array of listeners to be set.
12
- * @protected
13
- * @returns {{
14
- *     type: _SET_PIP_LISTENERS,
15
- *     listeners: Array
16
- * }}
17
+ * @public
18
+ * @returns {Function}
17 19
  */
18
-export function _setListeners(listeners: ?Array<any>) {
19
-    return {
20
-        type: _SET_PIP_LISTENERS,
21
-        listeners
20
+export function enterPictureInPicture() {
21
+    return (dispatch: Dispatch, getState: Function) => {
22
+        const state = getState();
23
+        const { app } = state['features/app'];
24
+        const { conference, joining } = state['features/base/conference'];
25
+
26
+        if (app
27
+                && app.props.pictureInPictureEnabled
28
+                && (conference || joining)) {
29
+            const { PictureInPicture } = NativeModules;
30
+            const p
31
+                = PictureInPicture
32
+                    ? PictureInPicture.enterPictureInPicture()
33
+                    : Promise.reject(
34
+                        new Error('Picture-in-Picture not supported'));
35
+
36
+            p.then(
37
+                () => dispatch({ type: ENTER_PICTURE_IN_PICTURE }),
38
+                e => console.warn(`Error entering PiP mode: ${e}`));
39
+        }
22 40
     };
23 41
 }
24 42
 
25 43
 /**
26
- * Requests Picture-in-Picture mode.
44
+ * Sets the {@code EventEmitter} subscriptions utilized by the feature
45
+ * picture-in-picture.
27 46
  *
28
- * @public
47
+ * @param {Array<Object>} emitterSubscriptions - The {@code EventEmitter}
48
+ * subscriptions to be set.
49
+ * @protected
29 50
  * @returns {{
30
- *     type: REQUEST_PIP_MODE
51
+ *     type: _SET_EMITTER_SUBSCRIPTIONS,
52
+ *     emitterSubscriptions: Array<Object>
31 53
  * }}
32 54
  */
33
-export function requestPipMode() {
55
+export function _setEmitterSubscriptions(emitterSubscriptions: ?Array<Object>) {
34 56
     return {
35
-        type: REQUEST_PIP_MODE
57
+        type: _SET_EMITTER_SUBSCRIPTIONS,
58
+        emitterSubscriptions
36 59
     };
37 60
 }

+ 112
- 0
react/features/mobile/picture-in-picture/components/EnterPictureInPictureToolbarButton.js 查看文件

@@ -0,0 +1,112 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { ToolbarButton } from '../../../toolbox';
7
+
8
+import { enterPictureInPicture } from '../actions';
9
+
10
+/**
11
+ * The type of {@link EnterPictureInPictureToobarButton}'s React
12
+ * {@code Component} props.
13
+ */
14
+type Props = {
15
+
16
+    /**
17
+     * Enters (or rather initiates entering) picture-in-picture.
18
+     *
19
+     * @protected
20
+     */
21
+    _onEnterPictureInPicture: Function,
22
+
23
+    /**
24
+     * The indicator which determines whether Picture-in-Picture is enabled.
25
+     *
26
+     * @protected
27
+     */
28
+    _pictureInPictureEnabled: boolean
29
+};
30
+
31
+/**
32
+ * Implements a {@link ToolbarButton} to enter Picture-in-Picture.
33
+ */
34
+class EnterPictureInPictureToolbarButton extends Component<Props> {
35
+    /**
36
+     * Implements React's {@link Component#render()}.
37
+     *
38
+     * @inheritdoc
39
+     * @returns {ReactElement}
40
+     */
41
+    render() {
42
+        const {
43
+            _onEnterPictureInPicture,
44
+            _pictureInPictureEnabled,
45
+            ...props
46
+        } = this.props;
47
+
48
+        if (!_pictureInPictureEnabled) {
49
+            return null;
50
+        }
51
+
52
+        return (
53
+            <ToolbarButton
54
+                iconName = { 'menu-down' }
55
+                onClick = { _onEnterPictureInPicture }
56
+                { ...props } />
57
+        );
58
+    }
59
+}
60
+
61
+/**
62
+ * Maps redux actions to {@link EnterPictureInPictureToolbarButton}'s React
63
+ * {@code Component} props.
64
+ *
65
+ * @param {Function} dispatch - The redux action {@code dispatch} function.
66
+ * @returns {{
67
+ * }}
68
+ * @private
69
+ */
70
+function _mapDispatchToProps(dispatch) {
71
+    return {
72
+
73
+        /**
74
+         * Requests Picture-in-Picture mode.
75
+         *
76
+         * @private
77
+         * @returns {void}
78
+         * @type {Function}
79
+         */
80
+        _onEnterPictureInPicture() {
81
+            dispatch(enterPictureInPicture());
82
+        }
83
+    };
84
+}
85
+
86
+/**
87
+ * Maps (parts of) the redux state to
88
+ * {@link EnterPictureInPictureToolbarButton}'s React {@code Component} props.
89
+ *
90
+ * @param {Object} state - The redux store/state.
91
+ * @private
92
+ * @returns {{
93
+ * }}
94
+ */
95
+function _mapStateToProps(state) {
96
+    const { app } = state['features/app'];
97
+
98
+    return {
99
+
100
+        /**
101
+         * The indicator which determines whether Picture-in-Picture is enabled.
102
+         *
103
+         * @protected
104
+         * @type {boolean}
105
+         */
106
+        _pictureInPictureEnabled:
107
+            Boolean(app && app.props.pictureInPictureEnabled)
108
+    };
109
+}
110
+
111
+export default connect(_mapStateToProps, _mapDispatchToProps)(
112
+    EnterPictureInPictureToolbarButton);

+ 2
- 0
react/features/mobile/picture-in-picture/components/index.js 查看文件

@@ -0,0 +1,2 @@
1
+export { default as EnterPictureInPictureToolbarButton }
2
+    from './EnterPictureInPictureToolbarButton';

+ 0
- 19
react/features/mobile/picture-in-picture/functions.js 查看文件

@@ -1,19 +0,0 @@
1
-// @flow
2
-
3
-import { NativeModules } from 'react-native';
4
-
5
-const pip = NativeModules.PictureInPicture;
6
-
7
-/**
8
- * Tells the application to enter the Picture-in-Picture mode, if supported.
9
- *
10
- * @returns {Promise} A promise which is fulfilled when PiP mode was entered, or
11
- * rejected in case there was a problem or it isn't supported.
12
- */
13
-export function enterPictureInPictureMode(): Promise<void> {
14
-    if (pip) {
15
-        return pip.enterPictureInPictureMode();
16
-    }
17
-
18
-    return Promise.reject(new Error('PiP not supported'));
19
-}

+ 1
- 1
react/features/mobile/picture-in-picture/index.js 查看文件

@@ -1,6 +1,6 @@
1 1
 export * from './actions';
2 2
 export * from './actionTypes';
3
-export * from './functions';
3
+export * from './components';
4 4
 
5 5
 import './middleware';
6 6
 import './reducer';

+ 26
- 56
react/features/mobile/picture-in-picture/middleware.js 查看文件

@@ -5,9 +5,8 @@ import { DeviceEventEmitter } from 'react-native';
5 5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
6 6
 import { MiddlewareRegistry } from '../../base/redux';
7 7
 
8
-import { _setListeners } from './actions';
9
-import { _SET_PIP_LISTENERS, REQUEST_PIP_MODE } from './actionTypes';
10
-import { enterPictureInPictureMode } from './functions';
8
+import { enterPictureInPicture, _setEmitterSubscriptions } from './actions';
9
+import { _SET_EMITTER_SUBSCRIPTIONS } from './actionTypes';
11 10
 
12 11
 /**
13 12
  * Middleware that handles Picture-in-Picture requests. Currently it enters
@@ -18,30 +17,28 @@ import { enterPictureInPictureMode } from './functions';
18 17
  */
19 18
 MiddlewareRegistry.register(store => next => action => {
20 19
     switch (action.type) {
21
-    case _SET_PIP_LISTENERS: {
22
-        // Remove the current/old listeners.
23
-        const { listeners } = store.getState()['features/pip'];
24
-
25
-        if (listeners) {
26
-            for (const listener of listeners) {
27
-                listener.remove();
28
-            }
29
-        }
30
-        break;
31
-    }
32
-
33 20
     case APP_WILL_MOUNT:
34
-        _appWillMount(store);
35
-        break;
21
+        return _appWillMount(store, next, action);
36 22
 
37 23
     case APP_WILL_UNMOUNT:
38
-        store.dispatch(_setListeners(undefined));
24
+        store.dispatch(_setEmitterSubscriptions(undefined));
39 25
         break;
40 26
 
41
-    case REQUEST_PIP_MODE:
42
-        _enterPictureInPicture(store);
27
+    case _SET_EMITTER_SUBSCRIPTIONS: {
28
+        // Remove the current/old EventEmitter subscriptions.
29
+        const { emitterSubscriptions } = store.getState()['features/pip'];
30
+
31
+        if (emitterSubscriptions) {
32
+            for (const emitterSubscription of emitterSubscriptions) {
33
+                // XXX We may be removing an EventEmitter subscription which is
34
+                // in both the old and new Array of EventEmitter subscriptions!
35
+                // Thankfully, we don't have such a practical use case at the
36
+                // time of this writing.
37
+                emitterSubscription.remove();
38
+            }
39
+        }
43 40
         break;
44
-
41
+    }
45 42
     }
46 43
 
47 44
     return next(action);
@@ -58,43 +55,16 @@ MiddlewareRegistry.register(store => next => action => {
58 55
  * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
59 56
  * being dispatched in the specified {@code store}.
60 57
  * @private
61
- * @returns {*}
58
+ * @returns {*} The value returned by {@code next(action)}.
62 59
  */
63
-function _appWillMount({ dispatch, getState }) {
64
-    const context = {
65
-        dispatch,
66
-        getState
67
-    };
68
-
69
-    const listeners = [
60
+function _appWillMount({ dispatch }, next, action) {
61
+    dispatch(_setEmitterSubscriptions([
70 62
 
71 63
         // Android's onUserLeaveHint activity lifecycle callback
72
-        DeviceEventEmitter.addListener('onUserLeaveHint', () => {
73
-            _enterPictureInPicture(context);
74
-        })
75
-    ];
76
-
77
-    dispatch(_setListeners(listeners));
78
-}
79
-
80
-/**
81
- * Helper function to enter PiP mode. This is triggered by user request
82
- * (either pressing the button in the toolbox or the home button on Android)
83
- * ans this triggers the PiP mode, iff it's available and we are in a
84
- * conference.
85
- *
86
- * @param {Object} store - Redux store.
87
- * @private
88
- * @returns {void}
89
- */
90
-function _enterPictureInPicture({ getState }) {
91
-    const state = getState();
92
-    const { app } = state['features/app'];
93
-    const { conference, joining } = state['features/base/conference'];
64
+        DeviceEventEmitter.addListener(
65
+            'onUserLeaveHint',
66
+            () => dispatch(enterPictureInPicture()))
67
+    ]));
94 68
 
95
-    if (app.props.pipAvailable && (conference || joining)) {
96
-        enterPictureInPictureMode().catch(e => {
97
-            console.warn(`Error entering PiP mode: ${e}`);
98
-        });
99
-    }
69
+    return next(action);
100 70
 }

+ 5
- 3
react/features/mobile/picture-in-picture/reducer.js 查看文件

@@ -1,13 +1,15 @@
1
+// @flow
2
+
1 3
 import { ReducerRegistry } from '../../base/redux';
2 4
 
3
-import { _SET_PIP_LISTENERS } from './actionTypes';
5
+import { _SET_EMITTER_SUBSCRIPTIONS } from './actionTypes';
4 6
 
5 7
 ReducerRegistry.register('features/pip', (state = {}, action) => {
6 8
     switch (action.type) {
7
-    case _SET_PIP_LISTENERS:
9
+    case _SET_EMITTER_SUBSCRIPTIONS:
8 10
         return {
9 11
             ...state,
10
-            listeners: action.listeners
12
+            emitterSubscriptions: action.emitterSubscriptions
11 13
         };
12 14
     }
13 15
 

+ 82
- 106
react/features/toolbox/components/Toolbox.native.js 查看文件

@@ -1,4 +1,5 @@
1
-import PropTypes from 'prop-types';
1
+// @flow
2
+
2 3
 import React, { Component } from 'react';
3 4
 import { View } from 'react-native';
4 5
 import { connect } from 'react-redux';
@@ -23,7 +24,9 @@ import {
23 24
     makeAspectRatioAware
24 25
 } from '../../base/responsive-ui';
25 26
 import { ColorPalette } from '../../base/styles';
26
-import { requestPipMode } from '../../mobile/picture-in-picture';
27
+import {
28
+    EnterPictureInPictureToolbarButton
29
+} from '../../mobile/picture-in-picture';
27 30
 import { beginRoomLockRequest } from '../../room-lock';
28 31
 import { beginShareRoom } from '../../share-room';
29 32
 
@@ -47,91 +50,81 @@ import ToolbarButton from './ToolbarButton';
47 50
 const _SHARE_ROOM_TOOLBAR_BUTTON = true;
48 51
 
49 52
 /**
50
- * Implements the conference toolbox on React Native.
53
+ * The type of {@link Toolbox}'s React {@code Component} props.
51 54
  */
52
-class Toolbox extends Component {
55
+type Props = {
56
+
53 57
     /**
54
-     * Toolbox component's property types.
55
-     *
56
-     * @static
58
+     * Flag showing that audio is muted.
57 59
      */
58
-    static propTypes = {
59
-        /**
60
-         * Flag showing that audio is muted.
61
-         */
62
-        _audioMuted: PropTypes.bool,
63
-
64
-        /**
65
-         * Flag showing whether the audio-only mode is in use.
66
-         */
67
-        _audioOnly: PropTypes.bool,
60
+    _audioMuted: boolean,
68 61
 
69
-        /**
70
-         * The indicator which determines whether the toolbox is enabled.
71
-         */
72
-        _enabled: PropTypes.bool,
62
+    /**
63
+     * Flag showing whether the audio-only mode is in use.
64
+     */
65
+    _audioOnly: boolean,
73 66
 
74
-        /**
75
-         * Flag showing whether room is locked.
76
-         */
77
-        _locked: PropTypes.bool,
67
+    /**
68
+     * The indicator which determines whether the toolbox is enabled.
69
+     */
70
+    _enabled: boolean,
78 71
 
79
-        /**
80
-         * Handler for hangup.
81
-         */
82
-        _onHangup: PropTypes.func,
72
+    /**
73
+     * Flag showing whether room is locked.
74
+     */
75
+    _locked: boolean,
83 76
 
84
-        /**
85
-         * Requests Picture-in-Picture mode.
86
-         */
87
-        _onPipRequest: PropTypes.func,
77
+    /**
78
+     * Handler for hangup.
79
+     */
80
+    _onHangup: Function,
88 81
 
89
-        /**
90
-         * Sets the lock i.e. password protection of the conference/room.
91
-         */
92
-        _onRoomLock: PropTypes.func,
82
+    /**
83
+     * Sets the lock i.e. password protection of the conference/room.
84
+     */
85
+    _onRoomLock: Function,
93 86
 
94
-        /**
95
-         * Begins the UI procedure to share the conference/room URL.
96
-         */
97
-        _onShareRoom: PropTypes.func,
87
+    /**
88
+     * Begins the UI procedure to share the conference/room URL.
89
+     */
90
+    _onShareRoom: Function,
98 91
 
99
-        /**
100
-         * Toggles the audio-only flag of the conference.
101
-         */
102
-        _onToggleAudioOnly: PropTypes.func,
92
+    /**
93
+     * Toggles the audio-only flag of the conference.
94
+     */
95
+    _onToggleAudioOnly: Function,
103 96
 
104
-        /**
105
-         * Switches between the front/user-facing and back/environment-facing
106
-         * cameras.
107
-         */
108
-        _onToggleCameraFacingMode: PropTypes.func,
97
+    /**
98
+     * Switches between the front/user-facing and back/environment-facing
99
+     * cameras.
100
+     */
101
+    _onToggleCameraFacingMode: Function,
109 102
 
110
-        /**
111
-         * Flag showing whether Picture-in-Picture is available.
112
-         */
113
-        _pipAvailable: PropTypes.bool,
103
+    /**
104
+     * Flag showing whether video is muted.
105
+     */
106
+    _videoMuted: boolean,
114 107
 
115
-        /**
116
-         * Flag showing whether video is muted.
117
-         */
118
-        _videoMuted: PropTypes.bool,
108
+    /**
109
+     * Flag showing whether toolbar is visible.
110
+     */
111
+    _visible: boolean,
119 112
 
120
-        /**
121
-         * Flag showing whether toolbar is visible.
122
-         */
123
-        _visible: PropTypes.bool,
113
+    dispatch: Function
114
+};
124 115
 
125
-        dispatch: PropTypes.func
126
-    };
127 116
 
117
+/**
118
+ * Implements the conference toolbox on React Native.
119
+ */
120
+class Toolbox extends Component<Props> {
128 121
     /**
129 122
      * Initializes a new {@code Toolbox} instance.
130 123
      *
131
-     * @param {Object} props - The read-only React {@code Component} props with
124
+     * @param {Props} props - The read-only React {@code Component} props with
132 125
      * which the new instance is to be initialized.
133 126
      */
134
-    constructor(props) {
127
+    constructor(props: Props) {
135 128
         super(props);
136 129
 
137 130
         // Bind event handlers so they are only bound once per instance.
@@ -183,22 +176,26 @@ class Toolbox extends Component {
183 176
         let style;
184 177
 
185 178
         if (this.props[`_${mediaType}Muted`]) {
186
-            iconName = this[`${mediaType}MutedIcon`];
179
+            iconName = `${mediaType}MutedIcon`;
187 180
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
188 181
             style = styles.whitePrimaryToolbarButton;
189 182
         } else {
190
-            iconName = this[`${mediaType}Icon`];
183
+            iconName = `${mediaType}Icon`;
191 184
             iconStyle = styles.primaryToolbarButtonIcon;
192 185
             style = styles.primaryToolbarButton;
193 186
         }
194 187
 
195 188
         return {
196
-            iconName,
189
+
190
+            // $FlowExpectedError
191
+            iconName: this[iconName],
197 192
             iconStyle,
198 193
             style
199 194
         };
200 195
     }
201 196
 
197
+    _onToggleAudio: () => void;
198
+
202 199
     /**
203 200
      * Dispatches an action to toggle the mute state of the audio/microphone.
204 201
      *
@@ -226,6 +223,8 @@ class Toolbox extends Component {
226 223
                 /* ensureTrack */ true));
227 224
     }
228 225
 
226
+    _onToggleVideo: () => void;
227
+
229 228
     /**
230 229
      * Dispatches an action to toggle the mute state of the video/camera.
231 230
      *
@@ -307,7 +306,6 @@ class Toolbox extends Component {
307 306
         const underlayColor = 'transparent';
308 307
         const {
309 308
             _audioOnly: audioOnly,
310
-            _pipAvailable: pipAvailable,
311 309
             _videoMuted: videoMuted
312 310
         } = this.props;
313 311
 
@@ -317,15 +315,6 @@ class Toolbox extends Component {
317 315
             <View
318 316
                 key = 'secondaryToolbar'
319 317
                 style = { styles.secondaryToolbar }>
320
-                {
321
-                    pipAvailable
322
-                        && <ToolbarButton
323
-                            iconName = { 'menu-down' }
324
-                            iconStyle = { iconStyle }
325
-                            onClick = { this.props._onPipRequest }
326
-                            style = { style }
327
-                            underlayColor = { underlayColor } />
328
-                }
329 318
                 {
330 319
                     AudioRouteButton
331 320
                         && <AudioRouteButton
@@ -364,6 +353,10 @@ class Toolbox extends Component {
364 353
                             style = { style }
365 354
                             underlayColor = { underlayColor } />
366 355
                 }
356
+                <EnterPictureInPictureToolbarButton
357
+                    iconStyle = { iconStyle }
358
+                    style = { style }
359
+                    underlayColor = { underlayColor } />
367 360
             </View>
368 361
         );
369 362
 
@@ -390,6 +383,7 @@ class Toolbox extends Component {
390 383
  * TODO As soon as we have common font sets for web and native, this will no
391 384
  * longer be required.
392 385
  */
386
+// $FlowExpectedError
393 387
 Object.assign(Toolbox.prototype, {
394 388
     audioIcon: 'microphone',
395 389
     audioMutedIcon: 'mic-disabled',
@@ -398,31 +392,20 @@ Object.assign(Toolbox.prototype, {
398 392
 });
399 393
 
400 394
 /**
401
- * Maps actions to React component props.
395
+ * Maps redux actions to {@link Toolbox}'s React {@code Component} props.
402 396
  *
403
- * @param {Function} dispatch - Redux action dispatcher.
397
+ * @param {Function} dispatch - The redux action {@code dispatch} function.
398
+ * @private
404 399
  * @returns {{
405 400
  *     _onRoomLock: Function,
406 401
  *     _onToggleAudioOnly: Function,
407 402
  *     _onToggleCameraFacingMode: Function,
408 403
  * }}
409
- * @private
410 404
  */
411 405
 function _mapDispatchToProps(dispatch) {
412 406
     return {
413 407
         ...abstractMapDispatchToProps(dispatch),
414 408
 
415
-        /**
416
-         * Requests Picture-in-Picture mode.
417
-         *
418
-         * @private
419
-         * @returns {void}
420
-         * @type {Function}
421
-         */
422
-        _onPipRequest() {
423
-            dispatch(requestPipMode());
424
-        },
425
-
426 409
         /**
427 410
          * Sets the lock i.e. password protection of the conference/room.
428 411
          *
@@ -471,19 +454,20 @@ function _mapDispatchToProps(dispatch) {
471 454
 }
472 455
 
473 456
 /**
474
- * Maps part of Redux store to React component props.
457
+ * Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
458
+ * props.
475 459
  *
476
- * @param {Object} state - Redux store.
460
+ * @param {Object} state - The redux store/state.
461
+ * @private
477 462
  * @returns {{
478 463
  *     _audioOnly: boolean,
464
+ *     _enabled: boolean,
479 465
  *     _locked: boolean
480 466
  * }}
481
- * @private
482 467
  */
483 468
 function _mapStateToProps(state) {
484 469
     const conference = state['features/base/conference'];
485 470
     const { enabled } = state['features/toolbox'];
486
-    const { app } = state['features/app'];
487 471
 
488 472
     return {
489 473
         ...abstractMapStateToProps(state),
@@ -512,15 +496,7 @@ function _mapStateToProps(state) {
512 496
          * @protected
513 497
          * @type {boolean}
514 498
          */
515
-        _locked: Boolean(conference.locked),
516
-
517
-        /**
518
-         * The indicator which determines if Picture-in-Picture is available.
519
-         *
520
-         * @protected
521
-         * @type {boolean}
522
-         */
523
-        _pipAvailable: Boolean(app && app.props.pipAvailable)
499
+        _locked: Boolean(conference.locked)
524 500
     };
525 501
 }
526 502
 

+ 4
- 4
react/features/toolbox/functions.native.js 查看文件

@@ -1,21 +1,21 @@
1
-/* @flow */
2
-
3
-import type { Dispatch } from 'redux';
1
+// @flow
4 2
 
5 3
 import { appNavigate } from '../app';
6 4
 import { MEDIA_TYPE } from '../base/media';
7 5
 import { isLocalTrackMuted } from '../base/tracks';
8 6
 
7
+import type { Dispatch } from 'redux';
8
+
9 9
 /**
10 10
  * Maps redux actions to {@link Toolbox} (React {@code Component}) props.
11 11
  *
12 12
  * @param {Function} dispatch - The redux {@code dispatch} function.
13
+ * @private
13 14
  * @returns {{
14 15
  *     _onHangup: Function,
15 16
  *     _onToggleAudio: Function,
16 17
  *     _onToggleVideo: Function
17 18
  * }}
18
- * @private
19 19
  */
20 20
 export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
21 21
     return {

+ 5
- 1
react/features/toolbox/functions.web.js 查看文件

@@ -7,7 +7,11 @@ import getDefaultButtons from './defaultToolbarButtons';
7 7
 
8 8
 declare var interfaceConfig: Object;
9 9
 
10
-export { abstractMapStateToProps, getButton } from './functions.native';
10
+export {
11
+    abstractMapDispatchToProps,
12
+    abstractMapStateToProps,
13
+    getButton
14
+} from './functions.native';
11 15
 
12 16
 /**
13 17
  * Returns an object which contains the default buttons for the primary and

正在加载...
取消
保存