Przeglądaj źródła

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

master
Lyubo Marinov 7 lat temu
rodzic
commit
b8de5bbfc3

+ 30
- 29
android/README.md Wyświetl plik

118
     }
118
     }
119
 
119
 
120
     @Override
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
     @Override
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
 
142
 
143
 See JitsiMeetView.getDefaultURL.
143
 See JitsiMeetView.getDefaultURL.
144
 
144
 
145
-#### getPictureInPictureAvailable()
145
+#### getPictureInPictureEnabled()
146
 
146
 
147
-See JitsiMeetView.getPictureInPictureAvailable.
147
+See JitsiMeetView.getPictureInPictureEnabled.
148
 
148
 
149
 #### getWelcomePageEnabled()
149
 #### getWelcomePageEnabled()
150
 
150
 
158
 
158
 
159
 See JitsiMeetView.setDefaultURL.
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
 #### setWelcomePageEnabled(boolean)
165
 #### setWelcomePageEnabled(boolean)
166
 
166
 
187
 
187
 
188
 Returns the `JitsiMeetViewListener` instance attached to the view.
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
 #### getWelcomePageEnabled()
196
 #### getWelcomePageEnabled()
197
 
197
 
234
 
234
 
235
 Sets the default URL. See `getDefaultURL` for more information.
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
 #### setListener(listener)
240
 #### setListener(listener)
240
 
241
 
241
 Sets the given listener (class implementing the `JitsiMeetViewListener`
242
 Sets the given listener (class implementing the `JitsiMeetViewListener`
242
 interface) on the view.
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
 #### setWelcomePageEnabled(boolean)
254
 #### setWelcomePageEnabled(boolean)
252
 
255
 
253
 Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
256
 Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
254
 information.
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
 #### onBackPressed()
262
 #### onBackPressed()
259
 
263
 
416
 
420
 
417
 ## Picture-in-Picture
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 Wyświetl plik

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

+ 2
- 3
android/app/src/main/java/org/jitsi/meet/MainActivity.java Wyświetl plik

97
         // As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
97
         // As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
98
         // want to enable some options.
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
         setWelcomePageEnabled(true);
102
         setWelcomePageEnabled(true);
104
 
103
 
105
         super.onCreate(savedInstanceState);
104
         super.onCreate(savedInstanceState);

+ 17
- 11
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java Wyświetl plik

69
     private JitsiMeetView view;
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
      * {@link #view} equals {@code null}.
73
      * {@link #view} equals {@code null}.
74
      */
74
      */
75
-    private Boolean pipAvailable;
75
+    private Boolean pictureInPictureEnabled;
76
 
76
 
77
     /**
77
     /**
78
      * Whether the Welcome page is enabled. The value is used only while
78
      * Whether the Welcome page is enabled. The value is used only while
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
         // XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
139
         // XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
138
         // is documented to need such an order in order to take effect:
140
         // is documented to need such an order in order to take effect:
139
         view.setDefaultURL(defaultURL);
141
         view.setDefaultURL(defaultURL);
140
-        view.setPictureInPictureAvailable(pipAvailable);
142
+        if (pictureInPictureEnabled != null) {
143
+            view.setPictureInPictureEnabled(
144
+                pictureInPictureEnabled.booleanValue());
145
+        }
141
         view.setWelcomePageEnabled(welcomePageEnabled);
146
         view.setWelcomePageEnabled(welcomePageEnabled);
142
 
147
 
143
         view.loadURL(null);
148
         view.loadURL(null);
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
         if (view == null) {
284
         if (view == null) {
280
-            this.pipAvailable = pipAvailable;
285
+            this.pictureInPictureEnabled
286
+                = Boolean.valueOf(pictureInPictureEnabled);
281
         } else {
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 Wyświetl plik

300
     private JitsiMeetViewListener listener;
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
      * React Native root view.
310
      * React Native root view.
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
         // externalAPIScope
430
         // externalAPIScope
426
         props.putString("externalAPIScope", externalAPIScope);
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
         // url
438
         // url
439
         if (urlObject != null) {
439
         if (urlObject != null) {
457
         if (reactRootView == null) {
457
         if (reactRootView == null) {
458
             reactRootView = new ReactRootView(getContext());
458
             reactRootView = new ReactRootView(getContext());
459
             reactRootView.startReactApplication(
459
             reactRootView.startReactApplication(
460
-                reactInstanceManager, "App", props);
460
+                reactInstanceManager,
461
+                "App",
462
+                props);
461
             reactRootView.setBackgroundColor(BACKGROUND_COLOR);
463
             reactRootView.setBackgroundColor(BACKGROUND_COLOR);
462
             addView(reactRootView);
464
             addView(reactRootView);
463
         } else {
465
         } else {
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 Wyświetl plik

14
 public class PictureInPictureModule extends ReactContextBaseJavaModule {
14
 public class PictureInPictureModule extends ReactContextBaseJavaModule {
15
     private final static String TAG = "PictureInPicture";
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
      * @param promise a {@code Promise} which will resolve with a {@code null}
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
     @ReactMethod
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
             if (currentActivity == null) {
37
             if (currentActivity == null) {
39
                 promise.reject(new Exception("No current Activity!"));
38
                 promise.reject(new Exception("No current Activity!"));
40
                 return;
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
             if (r) {
50
             if (r) {
51
                 promise.resolve(null);
51
                 promise.resolve(null);
52
             } else {
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
             return;
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 Wyświetl plik

53
 `loadURLString:`/`loadURLObject:`. If not set or if set to `nil`, the default
53
 `loadURLString:`/`loadURLObject:`. If not set or if set to `nil`, the default
54
 built in JavaScript is used: https://meet.jit.si.
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
 #### welcomePageEnabled
67
 #### welcomePageEnabled
66
 
68
 
67
 Property to get/set whether the Welcome page is enabled. If `NO`, a black empty
69
 Property to get/set whether the Welcome page is enabled. If `NO`, a black empty
68
 view will be rendered when not in a conference. Defaults to `NO`.
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
 #### loadURL:NSURL
75
 #### loadURL:NSURL
73
 
76
 
177
 
180
 
178
 The `data` dictionary contains a "url" key with the conference URL.
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
 #### loadConfigError
193
 #### loadConfigError
181
 
194
 
182
 Called when loading the main configuration file from the Jitsi Meet deployment
195
 Called when loading the main configuration file from the Jitsi Meet deployment
186
 with the conference URL which necessitated the loading of the configuration
199
 with the conference URL which necessitated the loading of the configuration
187
 file.
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
 ### Picture-in-Picture
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 Wyświetl plik

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

+ 18
- 11
ios/sdk/src/JitsiMeetView.m Wyświetl plik

110
 @end
110
 @end
111
 
111
 
112
 @implementation JitsiMeetView {
112
 @implementation JitsiMeetView {
113
-    NSNumber *_pipAvailable;
113
+    NSNumber *_pictureInPictureEnabled;
114
 }
114
 }
115
 
115
 
116
-@dynamic pipAvailable;
116
+@dynamic pictureInPictureEnabled;
117
 
117
 
118
 static RCTBridgeWrapper *bridgeWrapper;
118
 static RCTBridgeWrapper *bridgeWrapper;
119
 
119
 
269
     }
269
     }
270
 
270
 
271
     props[@"externalAPIScope"] = externalAPIScope;
271
     props[@"externalAPIScope"] = externalAPIScope;
272
-    props[@"pipAvailable"] = @(self.pipAvailable);
272
+    props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
273
     props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
273
     props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
274
 
274
 
275
     // XXX If urlObject is nil, then it must appear as undefined in the
275
     // XXX If urlObject is nil, then it must appear as undefined in the
320
     [self loadURLObject:urlString ? @{ @"url": urlString } : nil];
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
 #pragma mark Private methods
345
 #pragma mark Private methods

+ 11
- 9
ios/sdk/src/JitsiMeetViewDelegate.h Wyświetl plik

55
  */
55
  */
56
 - (void)conferenceWillLeave:(NSDictionary *)data;
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
  * Called when loading the main configuration file from the Jitsi Meet
70
  * Called when loading the main configuration file from the Jitsi Meet
60
  * deployment file.
71
  * deployment file.
65
  */
76
  */
66
 - (void)loadConfigError:(NSDictionary *)data;
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
 @end
79
 @end

+ 4
- 3
react/features/app/components/App.native.js Wyświetl plik

38
         ...AbstractApp.propTypes,
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
          * Whether the Welcome page is enabled. If {@code true}, the Welcome
48
          * Whether the Welcome page is enabled. If {@code true}, the Welcome

+ 5
- 6
react/features/mobile/external-api/middleware.js Wyświetl plik

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

+ 11
- 9
react/features/mobile/picture-in-picture/actionTypes.js Wyświetl plik

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 Wyświetl plik

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { NativeModules } from 'react-native';
4
+
3
 import {
5
 import {
4
-    _SET_PIP_LISTENERS,
5
-    REQUEST_PIP_MODE
6
+    ENTER_PICTURE_IN_PICTURE,
7
+    _SET_EMITTER_SUBSCRIPTIONS
6
 } from './actionTypes';
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
  * @returns {{
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
     return {
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 Wyświetl plik

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 Wyświetl plik

1
+export { default as EnterPictureInPictureToolbarButton }
2
+    from './EnterPictureInPictureToolbarButton';

+ 0
- 19
react/features/mobile/picture-in-picture/functions.js Wyświetl plik

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 Wyświetl plik

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

+ 26
- 56
react/features/mobile/picture-in-picture/middleware.js Wyświetl plik

5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
6
 import { MiddlewareRegistry } from '../../base/redux';
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
  * Middleware that handles Picture-in-Picture requests. Currently it enters
12
  * Middleware that handles Picture-in-Picture requests. Currently it enters
18
  */
17
  */
19
 MiddlewareRegistry.register(store => next => action => {
18
 MiddlewareRegistry.register(store => next => action => {
20
     switch (action.type) {
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
     case APP_WILL_MOUNT:
20
     case APP_WILL_MOUNT:
34
-        _appWillMount(store);
35
-        break;
21
+        return _appWillMount(store, next, action);
36
 
22
 
37
     case APP_WILL_UNMOUNT:
23
     case APP_WILL_UNMOUNT:
38
-        store.dispatch(_setListeners(undefined));
24
+        store.dispatch(_setEmitterSubscriptions(undefined));
39
         break;
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
         break;
40
         break;
44
-
41
+    }
45
     }
42
     }
46
 
43
 
47
     return next(action);
44
     return next(action);
58
  * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
55
  * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
59
  * being dispatched in the specified {@code store}.
56
  * being dispatched in the specified {@code store}.
60
  * @private
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
         // Android's onUserLeaveHint activity lifecycle callback
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 Wyświetl plik

1
+// @flow
2
+
1
 import { ReducerRegistry } from '../../base/redux';
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
 ReducerRegistry.register('features/pip', (state = {}, action) => {
7
 ReducerRegistry.register('features/pip', (state = {}, action) => {
6
     switch (action.type) {
8
     switch (action.type) {
7
-    case _SET_PIP_LISTENERS:
9
+    case _SET_EMITTER_SUBSCRIPTIONS:
8
         return {
10
         return {
9
             ...state,
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 Wyświetl plik

1
-import PropTypes from 'prop-types';
1
+// @flow
2
+
2
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
3
 import { View } from 'react-native';
4
 import { View } from 'react-native';
4
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
23
     makeAspectRatioAware
24
     makeAspectRatioAware
24
 } from '../../base/responsive-ui';
25
 } from '../../base/responsive-ui';
25
 import { ColorPalette } from '../../base/styles';
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
 import { beginRoomLockRequest } from '../../room-lock';
30
 import { beginRoomLockRequest } from '../../room-lock';
28
 import { beginShareRoom } from '../../share-room';
31
 import { beginShareRoom } from '../../share-room';
29
 
32
 
47
 const _SHARE_ROOM_TOOLBAR_BUTTON = true;
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
      * Initializes a new {@code Toolbox} instance.
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
      * which the new instance is to be initialized.
125
      * which the new instance is to be initialized.
133
      */
126
      */
134
-    constructor(props) {
127
+    constructor(props: Props) {
135
         super(props);
128
         super(props);
136
 
129
 
137
         // Bind event handlers so they are only bound once per instance.
130
         // Bind event handlers so they are only bound once per instance.
183
         let style;
176
         let style;
184
 
177
 
185
         if (this.props[`_${mediaType}Muted`]) {
178
         if (this.props[`_${mediaType}Muted`]) {
186
-            iconName = this[`${mediaType}MutedIcon`];
179
+            iconName = `${mediaType}MutedIcon`;
187
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
180
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
188
             style = styles.whitePrimaryToolbarButton;
181
             style = styles.whitePrimaryToolbarButton;
189
         } else {
182
         } else {
190
-            iconName = this[`${mediaType}Icon`];
183
+            iconName = `${mediaType}Icon`;
191
             iconStyle = styles.primaryToolbarButtonIcon;
184
             iconStyle = styles.primaryToolbarButtonIcon;
192
             style = styles.primaryToolbarButton;
185
             style = styles.primaryToolbarButton;
193
         }
186
         }
194
 
187
 
195
         return {
188
         return {
196
-            iconName,
189
+
190
+            // $FlowExpectedError
191
+            iconName: this[iconName],
197
             iconStyle,
192
             iconStyle,
198
             style
193
             style
199
         };
194
         };
200
     }
195
     }
201
 
196
 
197
+    _onToggleAudio: () => void;
198
+
202
     /**
199
     /**
203
      * Dispatches an action to toggle the mute state of the audio/microphone.
200
      * Dispatches an action to toggle the mute state of the audio/microphone.
204
      *
201
      *
226
                 /* ensureTrack */ true));
223
                 /* ensureTrack */ true));
227
     }
224
     }
228
 
225
 
226
+    _onToggleVideo: () => void;
227
+
229
     /**
228
     /**
230
      * Dispatches an action to toggle the mute state of the video/camera.
229
      * Dispatches an action to toggle the mute state of the video/camera.
231
      *
230
      *
307
         const underlayColor = 'transparent';
306
         const underlayColor = 'transparent';
308
         const {
307
         const {
309
             _audioOnly: audioOnly,
308
             _audioOnly: audioOnly,
310
-            _pipAvailable: pipAvailable,
311
             _videoMuted: videoMuted
309
             _videoMuted: videoMuted
312
         } = this.props;
310
         } = this.props;
313
 
311
 
317
             <View
315
             <View
318
                 key = 'secondaryToolbar'
316
                 key = 'secondaryToolbar'
319
                 style = { styles.secondaryToolbar }>
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
                     AudioRouteButton
319
                     AudioRouteButton
331
                         && <AudioRouteButton
320
                         && <AudioRouteButton
364
                             style = { style }
353
                             style = { style }
365
                             underlayColor = { underlayColor } />
354
                             underlayColor = { underlayColor } />
366
                 }
355
                 }
356
+                <EnterPictureInPictureToolbarButton
357
+                    iconStyle = { iconStyle }
358
+                    style = { style }
359
+                    underlayColor = { underlayColor } />
367
             </View>
360
             </View>
368
         );
361
         );
369
 
362
 
390
  * TODO As soon as we have common font sets for web and native, this will no
383
  * TODO As soon as we have common font sets for web and native, this will no
391
  * longer be required.
384
  * longer be required.
392
  */
385
  */
386
+// $FlowExpectedError
393
 Object.assign(Toolbox.prototype, {
387
 Object.assign(Toolbox.prototype, {
394
     audioIcon: 'microphone',
388
     audioIcon: 'microphone',
395
     audioMutedIcon: 'mic-disabled',
389
     audioMutedIcon: 'mic-disabled',
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
  * @returns {{
399
  * @returns {{
405
  *     _onRoomLock: Function,
400
  *     _onRoomLock: Function,
406
  *     _onToggleAudioOnly: Function,
401
  *     _onToggleAudioOnly: Function,
407
  *     _onToggleCameraFacingMode: Function,
402
  *     _onToggleCameraFacingMode: Function,
408
  * }}
403
  * }}
409
- * @private
410
  */
404
  */
411
 function _mapDispatchToProps(dispatch) {
405
 function _mapDispatchToProps(dispatch) {
412
     return {
406
     return {
413
         ...abstractMapDispatchToProps(dispatch),
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
          * Sets the lock i.e. password protection of the conference/room.
410
          * Sets the lock i.e. password protection of the conference/room.
428
          *
411
          *
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
  * @returns {{
462
  * @returns {{
478
  *     _audioOnly: boolean,
463
  *     _audioOnly: boolean,
464
+ *     _enabled: boolean,
479
  *     _locked: boolean
465
  *     _locked: boolean
480
  * }}
466
  * }}
481
- * @private
482
  */
467
  */
483
 function _mapStateToProps(state) {
468
 function _mapStateToProps(state) {
484
     const conference = state['features/base/conference'];
469
     const conference = state['features/base/conference'];
485
     const { enabled } = state['features/toolbox'];
470
     const { enabled } = state['features/toolbox'];
486
-    const { app } = state['features/app'];
487
 
471
 
488
     return {
472
     return {
489
         ...abstractMapStateToProps(state),
473
         ...abstractMapStateToProps(state),
512
          * @protected
496
          * @protected
513
          * @type {boolean}
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 Wyświetl plik

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

+ 5
- 1
react/features/toolbox/functions.web.js Wyświetl plik

7
 
7
 
8
 declare var interfaceConfig: Object;
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
  * Returns an object which contains the default buttons for the primary and
17
  * Returns an object which contains the default buttons for the primary and

Ładowanie…
Anuluj
Zapisz