[RN] Add Picture-in-Picture support (Coding style: naming, consistency)
This commit is contained in:
parent
b3683068d4
commit
b8de5bbfc3
|
@ -117,19 +117,19 @@ public class MainActivity extends AppCompatActivity {
|
|||
JitsiMeetView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetView.onHostResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -142,9 +142,9 @@ which displays a single `JitsiMeetView`.
|
|||
|
||||
See JitsiMeetView.getDefaultURL.
|
||||
|
||||
#### getPictureInPictureAvailable()
|
||||
#### getPictureInPictureEnabled()
|
||||
|
||||
See JitsiMeetView.getPictureInPictureAvailable.
|
||||
See JitsiMeetView.getPictureInPictureEnabled.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
|
||||
|
@ -158,9 +158,9 @@ See JitsiMeetView.loadURL.
|
|||
|
||||
See JitsiMeetView.setDefaultURL.
|
||||
|
||||
#### setPictureInPictureAvailable(Boolean)
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
See JitsiMeetView.setPictureInPictureAvailable.
|
||||
See JitsiMeetView.setPictureInPictureEnabled.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
|
@ -187,11 +187,11 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
|||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### getPictureInPictureAvailable()
|
||||
#### getPictureInPictureEnabled()
|
||||
|
||||
turns true if Picture-in-Picture is available, false otherwise. If the user
|
||||
doesn't explicitly set it, it will default to true if the platform supports it,
|
||||
false otherwise. See the Picture-in-Picture section.
|
||||
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
|
||||
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
|
||||
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
|
||||
|
@ -234,26 +234,30 @@ view.loadURLObject(urlObject);
|
|||
|
||||
Sets the default URL. See `getDefaultURL` for more information.
|
||||
|
||||
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
|
||||
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### setListener(listener)
|
||||
|
||||
Sets the given listener (class implementing the `JitsiMeetViewListener`
|
||||
interface) on the view.
|
||||
|
||||
#### setPictureInPictureAvailable(Boolean)
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
Sets wether Picture-in-Picture is available. When set to `null` if will be
|
||||
detected at runtime based on platform support.
|
||||
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
|
||||
automatically enables/disables Picture-in-Picture based on native platform
|
||||
support.
|
||||
|
||||
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
|
||||
information.
|
||||
|
||||
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### onBackPressed()
|
||||
|
||||
|
@ -416,13 +420,10 @@ rules file:
|
|||
|
||||
## Picture-in-Picture
|
||||
|
||||
The Jitsi Meet app and SDK will enable Android's native Picture-in-Picture mode
|
||||
iff the platform is supported: for Android >= Oreo.
|
||||
|
||||
If the SDK is integrated in an application which calls
|
||||
`enterPictureInPictureMode` for the Jitsi Meet activity, the it will self-adjust
|
||||
by removing some UI elements.
|
||||
|
||||
Alternatively, this can be explicitly disabled with the
|
||||
`setPctureInPictureAvailable` methods in the Jitsi Meet view or activity.
|
||||
`JitsiMeetView` will automatically adjust its UI when presented in a
|
||||
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
|
||||
"full" UI.
|
||||
|
||||
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
|
||||
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
|
||||
mode iff the platform is supported i.e. Android >= Oreo.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -97,9 +97,8 @@ public class MainActivity extends JitsiMeetActivity {
|
|||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
|
||||
// want to enable some options.
|
||||
|
||||
// The welcome page defaults to disabled in the
|
||||
// SDK at the time of this writing but it is clearer to be explicit
|
||||
// about what we want anyway.
|
||||
// The welcome page defaults to disabled in the SDK at the time of this
|
||||
// writing but it is clearer to be explicit about what we want anyway.
|
||||
setWelcomePageEnabled(true);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
|
@ -69,10 +69,10 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
|||
private JitsiMeetView view;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is available. The value is used only while
|
||||
* Whether Picture-in-Picture is enabled. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private Boolean pipAvailable;
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled. The value is used only while
|
||||
|
@ -98,11 +98,13 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
|||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getPictureInPictureAvailable()
|
||||
* @see JitsiMeetView#getPictureInPictureEnabled()
|
||||
*/
|
||||
public Boolean getPictureInPictureAvailable() {
|
||||
return view == null
|
||||
? pipAvailable : view.getPictureInPictureAvailable();
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.getPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,7 +139,10 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
|||
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
|
||||
// is documented to need such an order in order to take effect:
|
||||
view.setDefaultURL(defaultURL);
|
||||
view.setPictureInPictureAvailable(pipAvailable);
|
||||
if (pictureInPictureEnabled != null) {
|
||||
view.setPictureInPictureEnabled(
|
||||
pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
|
||||
view.loadURL(null);
|
||||
|
@ -273,13 +278,14 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
|||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setPictureInPictureAvailable(Boolean)
|
||||
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
|
||||
*/
|
||||
public void setPictureInPictureAvailable(Boolean pipAvailable) {
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
if (view == null) {
|
||||
this.pipAvailable = pipAvailable;
|
||||
this.pictureInPictureEnabled
|
||||
= Boolean.valueOf(pictureInPictureEnabled);
|
||||
} else {
|
||||
view.setPictureInPictureAvailable(pipAvailable);
|
||||
view.setPictureInPictureEnabled(pictureInPictureEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -300,10 +300,11 @@ public class JitsiMeetView extends FrameLayout {
|
|||
private JitsiMeetViewListener listener;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is available. If {@code null} it will default
|
||||
* to {@code true} iff the platform supports it.
|
||||
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
|
||||
* {@code true} iff the Android platform supports Picture-in-Picture
|
||||
* natively.
|
||||
*/
|
||||
private Boolean pipAvailable;
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
|
@ -370,14 +371,18 @@ public class JitsiMeetView extends FrameLayout {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is currently available. It's only
|
||||
* supported on Android API >= 26 (Oreo), so it should not be enabled on
|
||||
* older platform versions.
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return {@code true} if PiP is available, {@code false} otherwise.
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public Boolean getPictureInPictureAvailable() {
|
||||
return pipAvailable;
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -425,15 +430,10 @@ public class JitsiMeetView extends FrameLayout {
|
|||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// pipAvailable
|
||||
boolean pipAvailable_;
|
||||
if (pipAvailable == null) {
|
||||
// set it based on platform availability
|
||||
pipAvailable_ = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
} else {
|
||||
pipAvailable_ = pipAvailable.booleanValue();
|
||||
}
|
||||
props.putBoolean("pipAvailable", pipAvailable_);
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
getPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
|
@ -457,7 +457,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
reactInstanceManager, "App", props);
|
||||
reactInstanceManager,
|
||||
"App",
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
|
@ -528,13 +530,15 @@ public class JitsiMeetView extends FrameLayout {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets whether Picture-in-Picture is currently available.
|
||||
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
|
||||
* natively supported only since certain platform versions, specifying
|
||||
* {@code true} will have no effect on unsupported platform versions.
|
||||
*
|
||||
* @param pipAvailable {@code true} if PiP is available, {@code false}
|
||||
* otherwise.
|
||||
* @param pictureInPictureEnabled To enable Picture-in-Picture,
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
public void setPictureInPictureAvailable(Boolean pipAvailable) {
|
||||
this.pipAvailable = pipAvailable;
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,48 +14,54 @@ import com.facebook.react.bridge.ReactMethod;
|
|||
public class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
private final static String TAG = "PictureInPicture";
|
||||
|
||||
static boolean isPictureInPictureSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters Picture-in-Picture mode for the current activity. This is only
|
||||
* supported in Android API >= 26.
|
||||
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
|
||||
* Supported on Android API >= 26 (Oreo) only.
|
||||
*
|
||||
* @param promise a {@code Promise} which will resolve with a {@code null}
|
||||
* value in case of success, and an error otherwise.
|
||||
* value upon success, and an {@link Exception} otherwise.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void enterPictureInPictureMode(Promise promise) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
final Activity currentActivity = getCurrentActivity();
|
||||
public void enterPictureInPicture(Promise promise) {
|
||||
if (isPictureInPictureSupported()) {
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
|
||||
if (currentActivity == null) {
|
||||
promise.reject(new Exception("No current Activity!"));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Entering PiP mode");
|
||||
Log.d(TAG, "Entering Picture-in-Picture");
|
||||
|
||||
PictureInPictureParams.Builder builder
|
||||
= new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(1, 1));
|
||||
boolean r
|
||||
= currentActivity.enterPictureInPictureMode(builder.build());
|
||||
|
||||
final PictureInPictureParams.Builder pipParamsBuilder
|
||||
= new PictureInPictureParams.Builder();
|
||||
pipParamsBuilder.setAspectRatio(new Rational(1, 1)).build();
|
||||
final boolean r
|
||||
= currentActivity.enterPictureInPictureMode(pipParamsBuilder.build());
|
||||
if (r) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
promise.reject(new Exception("Error entering PiP mode"));
|
||||
promise.reject(
|
||||
new Exception("Failed to enter Picture-in-Picture"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
promise.reject(new Exception("PiP not supported"));
|
||||
promise.reject(new Exception("Picture-in-Picture not supported"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,21 +53,24 @@ partial URL (e.g. a room name only) is specified to
|
|||
`loadURLString:`/`loadURLObject:`. If not set or if set to `nil`, the default
|
||||
built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
|
||||
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
|
||||
effect.
|
||||
|
||||
#### pipAvailable
|
||||
#### pictureInPictureEnabled
|
||||
|
||||
Property to get / set wether a Picture-in-Picture mode is available. This must
|
||||
be implemented by the application at the moment.
|
||||
Property to get / set whether Picture-in-Picture is enabled. Defaults to `YES`
|
||||
if `delegate` implements `enterPictureInPicture:`; otherwise, `NO`.
|
||||
|
||||
NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
|
||||
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
|
||||
effect.
|
||||
|
||||
#### welcomePageEnabled
|
||||
|
||||
Property to get/set whether the Welcome page is enabled. If `NO`, a black empty
|
||||
view will be rendered when not in a conference. Defaults to `NO`.
|
||||
|
||||
NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
|
||||
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
|
||||
effect.
|
||||
|
||||
#### loadURL:NSURL
|
||||
|
||||
|
@ -177,6 +180,16 @@ Called before a conference is left.
|
|||
|
||||
The `data` dictionary contains a "url" key with the conference URL.
|
||||
|
||||
#### enterPictureInPicture
|
||||
|
||||
Called when entering Picture-in-Picture is requested by the user. The app should
|
||||
now activate its Picture-in-Picture implementation (and resize the associated
|
||||
`JitsiMeetView`. The latter will automatically detect its new size and adjust
|
||||
its user interface to a variant appropriate for the small size ordinarily
|
||||
associated with Picture-in-Picture.)
|
||||
|
||||
The `data` dictionary is empty.
|
||||
|
||||
#### loadConfigError
|
||||
|
||||
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
|
|||
with the conference URL which necessitated the loading of the configuration
|
||||
file.
|
||||
|
||||
#### requestPipMode
|
||||
|
||||
Called when the user requested Picture-in-Picture mode to be entered. At this
|
||||
point the application should resize the SDK view to a smaller size if it so
|
||||
desires.
|
||||
|
||||
### Picture-in-Picture
|
||||
|
||||
The Jitsi Meet SDK implements a "reduced UI mode" which will automatically
|
||||
adjust the UI when presented in a Picture-in-Picture style scenario. Enabling
|
||||
a native Picture-in-Picture mode on iOS is not currently implemented on the SDK
|
||||
so applications need to do it themselves.
|
||||
`JitsiMeetView` will automatically adjust its UI when presented in a
|
||||
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
|
||||
"full" UI.
|
||||
|
||||
When `pipAvailable` is set to `YES` or the `requestPipMode` delegate method is
|
||||
implemented, the in-call toolbar will show a button to enter PiP mode. It's up
|
||||
to the application to reduce the size of the SDK view and put it in such mode.
|
||||
|
||||
Once PiP mode has been entered, the SDK will automatically adjust its UI
|
||||
elements.
|
||||
Jitsi Meet SDK does not currently implement native Picture-in-Picture on iOS. If
|
||||
desired, apps need to implement non-native Picture-in-Picture themselves and
|
||||
resize `JitsiMeetView`.
|
||||
|
||||
If `pictureInPictureEnabled` is set to `YES` or `delegate` implements
|
||||
`enterPictureInPicture:`, the in-call toolbar will render a button to afford the
|
||||
user to request entering Picture-in-Picture.
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic) BOOL pipAvailable;
|
||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
|
|
|
@ -110,10 +110,10 @@ void registerFatalErrorHandler() {
|
|||
@end
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
NSNumber *_pipAvailable;
|
||||
NSNumber *_pictureInPictureEnabled;
|
||||
}
|
||||
|
||||
@dynamic pipAvailable;
|
||||
@dynamic pictureInPictureEnabled;
|
||||
|
||||
static RCTBridgeWrapper *bridgeWrapper;
|
||||
|
||||
|
@ -269,7 +269,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
}
|
||||
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
props[@"pipAvailable"] = @(self.pipAvailable);
|
||||
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
||||
|
||||
// XXX If urlObject is nil, then it must appear as undefined in the
|
||||
|
@ -320,19 +320,26 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
|
||||
}
|
||||
|
||||
#pragma pipAvailable getter / setter
|
||||
#pragma pictureInPictureEnabled getter / setter
|
||||
|
||||
- (void) setPipAvailable:(BOOL)pipAvailable {
|
||||
_pipAvailable = [NSNumber numberWithBool:pipAvailable];
|
||||
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
|
||||
_pictureInPictureEnabled
|
||||
= [NSNumber numberWithBool:pictureInPictureEnabled];
|
||||
}
|
||||
|
||||
- (BOOL) pipAvailable {
|
||||
if (_pipAvailable == nil) {
|
||||
- (BOOL) pictureInPictureEnabled {
|
||||
if (_pictureInPictureEnabled) {
|
||||
return [_pictureInPictureEnabled boolValue];
|
||||
}
|
||||
|
||||
// The SDK/JitsiMeetView client/consumer did not explicitly enable/disable
|
||||
// Picture-in-Picture. However, we may automatically deduce their
|
||||
// intentions: we need the support of the client in order to implement
|
||||
// Picture-in-Picture on iOS (in contrast to Android) so if the client
|
||||
// appears to have provided the support then we can assume that they did it
|
||||
// with the intention to have Picture-in-Picture enabled.
|
||||
return self.delegate
|
||||
&& [self.delegate respondsToSelector:@selector(requestPipMode:)];
|
||||
}
|
||||
|
||||
return [_pipAvailable boolValue];
|
||||
&& [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
|
|
@ -55,6 +55,17 @@
|
|||
*/
|
||||
- (void)conferenceWillLeave:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when entering Picture-in-Picture is requested by the user. The app
|
||||
* should now activate its Picture-in-Picture implementation (and resize the
|
||||
* associated `JitsiMeetView`. The latter will automatically detect its new size
|
||||
* and adjust its user interface to a variant appropriate for the small size
|
||||
* ordinarily associated with Picture-in-Picture.)
|
||||
*
|
||||
* The `data` dictionary is empty.
|
||||
*/
|
||||
- (void)enterPictureInPicture:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when loading the main configuration file from the Jitsi Meet
|
||||
* deployment file.
|
||||
|
@ -65,13 +76,4 @@
|
|||
*/
|
||||
- (void)loadConfigError:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when Picture-in-Picture mode is requested. The app should now resize
|
||||
* iself to a PiP style and then use the JitsiMeetView.onPipModeChanged to
|
||||
* notify the JavaScript side about its action.
|
||||
*
|
||||
* The `data` dictionary is currently empty.
|
||||
*/
|
||||
- (void)requestPipMode:(NSDictionary *)data;
|
||||
|
||||
@end
|
||||
|
|
|
@ -38,10 +38,11 @@ export class App extends AbstractApp {
|
|||
...AbstractApp.propTypes,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is available. If available, a button will
|
||||
* be shown in the {@link Conference} view so the user can enter it.
|
||||
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar
|
||||
* button is rendered in the {@link Conference} view to afford entering
|
||||
* Picture-in-Picture.
|
||||
*/
|
||||
pipAvailable: PropTypes.bool,
|
||||
pictureInPictureEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
|
|
|
@ -13,8 +13,7 @@ import {
|
|||
import { LOAD_CONFIG_ERROR } from '../../base/config';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { toURLString } from '../../base/util';
|
||||
|
||||
import { REQUEST_PIP_MODE } from '../picture-in-picture';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
|
||||
/**
|
||||
* Middleware that captures Redux actions and uses the ExternalAPI module to
|
||||
|
@ -55,6 +54,10 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
_sendConferenceEvent(store, action);
|
||||
break;
|
||||
|
||||
case ENTER_PICTURE_IN_PICTURE:
|
||||
_sendEvent(store, _getSymbolDescription(action.type), /* data */ {});
|
||||
break;
|
||||
|
||||
case LOAD_CONFIG_ERROR: {
|
||||
const { error, locationURL, type } = action;
|
||||
|
||||
|
@ -64,10 +67,6 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_PIP_MODE:
|
||||
_sendEvent(store, _getSymbolDescription(action.type), /* data */ {});
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
/**
|
||||
* The type of redux action to set the PiP related event listeners.
|
||||
* The type of redux action to enter (or rather initiate entering)
|
||||
* picture-in-picture.
|
||||
*
|
||||
* {
|
||||
* type: _SET_PIP_MODE_LISTENER,
|
||||
* listeners: Array|undefined
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_PIP_LISTENERS = Symbol('_SET_PIP_LISTENERS');
|
||||
|
||||
/**
|
||||
* The type of redux action which signals that the PiP mode is requested.
|
||||
*
|
||||
* {
|
||||
* type: REQUEST_PIP_MODE
|
||||
* type: ENTER_PICTURE_IN_PICTURE
|
||||
* }
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const REQUEST_PIP_MODE = Symbol('REQUEST_PIP_MODE');
|
||||
export const ENTER_PICTURE_IN_PICTURE = Symbol('ENTER_PICTURE_IN_PICTURE');
|
||||
|
||||
/**
|
||||
* The type of redux action to set the {@code EventEmitter} subscriptions
|
||||
* utilized by the feature picture-in-picture.
|
||||
*
|
||||
* {
|
||||
* type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
* emitterSubscriptions: Array|undefined
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');
|
||||
|
|
|
@ -1,37 +1,60 @@
|
|||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import {
|
||||
_SET_PIP_LISTENERS,
|
||||
REQUEST_PIP_MODE
|
||||
ENTER_PICTURE_IN_PICTURE,
|
||||
_SET_EMITTER_SUBSCRIPTIONS
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the listeners for the PiP related events.
|
||||
* Enters (or rather initiates entering) picture-in-picture.
|
||||
* Helper function to enter PiP mode. This is triggered by user request
|
||||
* (either pressing the button in the toolbox or the home button on Android)
|
||||
* ans this triggers the PiP mode, iff it's available and we are in a
|
||||
* conference.
|
||||
*
|
||||
* @param {Array} listeners - Array of listeners to be set.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* type: _SET_PIP_LISTENERS,
|
||||
* listeners: Array
|
||||
* }}
|
||||
* @public
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function _setListeners(listeners: ?Array<any>) {
|
||||
return {
|
||||
type: _SET_PIP_LISTENERS,
|
||||
listeners
|
||||
export function enterPictureInPicture() {
|
||||
return (dispatch: Dispatch, getState: Function) => {
|
||||
const state = getState();
|
||||
const { app } = state['features/app'];
|
||||
const { conference, joining } = state['features/base/conference'];
|
||||
|
||||
if (app
|
||||
&& app.props.pictureInPictureEnabled
|
||||
&& (conference || joining)) {
|
||||
const { PictureInPicture } = NativeModules;
|
||||
const p
|
||||
= PictureInPicture
|
||||
? PictureInPicture.enterPictureInPicture()
|
||||
: Promise.reject(
|
||||
new Error('Picture-in-Picture not supported'));
|
||||
|
||||
p.then(
|
||||
() => dispatch({ type: ENTER_PICTURE_IN_PICTURE }),
|
||||
e => console.warn(`Error entering PiP mode: ${e}`));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests Picture-in-Picture mode.
|
||||
* Sets the {@code EventEmitter} subscriptions utilized by the feature
|
||||
* picture-in-picture.
|
||||
*
|
||||
* @public
|
||||
* @param {Array<Object>} emitterSubscriptions - The {@code EventEmitter}
|
||||
* subscriptions to be set.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* type: REQUEST_PIP_MODE
|
||||
* type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
* emitterSubscriptions: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
export function requestPipMode() {
|
||||
export function _setEmitterSubscriptions(emitterSubscriptions: ?Array<Object>) {
|
||||
return {
|
||||
type: REQUEST_PIP_MODE
|
||||
type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
emitterSubscriptions
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ToolbarButton } from '../../../toolbox';
|
||||
|
||||
import { enterPictureInPicture } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of {@link EnterPictureInPictureToobarButton}'s React
|
||||
* {@code Component} props.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Enters (or rather initiates entering) picture-in-picture.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onEnterPictureInPicture: Function,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_pictureInPictureEnabled: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a {@link ToolbarButton} to enter Picture-in-Picture.
|
||||
*/
|
||||
class EnterPictureInPictureToolbarButton extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_onEnterPictureInPicture,
|
||||
_pictureInPictureEnabled,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
if (!_pictureInPictureEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconName = { 'menu-down' }
|
||||
onClick = { _onEnterPictureInPicture }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux actions to {@link EnterPictureInPictureToolbarButton}'s React
|
||||
* {@code Component} props.
|
||||
*
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @returns {{
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
|
||||
/**
|
||||
* Requests Picture-in-Picture mode.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onEnterPictureInPicture() {
|
||||
dispatch(enterPictureInPicture());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to
|
||||
* {@link EnterPictureInPictureToolbarButton}'s React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { app } = state['features/app'];
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* The indicator which determines whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pictureInPictureEnabled:
|
||||
Boolean(app && app.props.pictureInPictureEnabled)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(
|
||||
EnterPictureInPictureToolbarButton);
|
|
@ -0,0 +1,2 @@
|
|||
export { default as EnterPictureInPictureToolbarButton }
|
||||
from './EnterPictureInPictureToolbarButton';
|
|
@ -1,19 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const pip = NativeModules.PictureInPicture;
|
||||
|
||||
/**
|
||||
* Tells the application to enter the Picture-in-Picture mode, if supported.
|
||||
*
|
||||
* @returns {Promise} A promise which is fulfilled when PiP mode was entered, or
|
||||
* rejected in case there was a problem or it isn't supported.
|
||||
*/
|
||||
export function enterPictureInPictureMode(): Promise<void> {
|
||||
if (pip) {
|
||||
return pip.enterPictureInPictureMode();
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('PiP not supported'));
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
|
|
@ -5,9 +5,8 @@ import { DeviceEventEmitter } from 'react-native';
|
|||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import { _setListeners } from './actions';
|
||||
import { _SET_PIP_LISTENERS, REQUEST_PIP_MODE } from './actionTypes';
|
||||
import { enterPictureInPictureMode } from './functions';
|
||||
import { enterPictureInPicture, _setEmitterSubscriptions } from './actions';
|
||||
import { _SET_EMITTER_SUBSCRIPTIONS } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware that handles Picture-in-Picture requests. Currently it enters
|
||||
|
@ -18,30 +17,28 @@ import { enterPictureInPictureMode } from './functions';
|
|||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case _SET_PIP_LISTENERS: {
|
||||
// Remove the current/old listeners.
|
||||
const { listeners } = store.getState()['features/pip'];
|
||||
|
||||
if (listeners) {
|
||||
for (const listener of listeners) {
|
||||
listener.remove();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_MOUNT:
|
||||
_appWillMount(store);
|
||||
break;
|
||||
return _appWillMount(store, next, action);
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch(_setListeners(undefined));
|
||||
store.dispatch(_setEmitterSubscriptions(undefined));
|
||||
break;
|
||||
|
||||
case REQUEST_PIP_MODE:
|
||||
_enterPictureInPicture(store);
|
||||
break;
|
||||
case _SET_EMITTER_SUBSCRIPTIONS: {
|
||||
// Remove the current/old EventEmitter subscriptions.
|
||||
const { emitterSubscriptions } = store.getState()['features/pip'];
|
||||
|
||||
if (emitterSubscriptions) {
|
||||
for (const emitterSubscription of emitterSubscriptions) {
|
||||
// XXX We may be removing an EventEmitter subscription which is
|
||||
// in both the old and new Array of EventEmitter subscriptions!
|
||||
// Thankfully, we don't have such a practical use case at the
|
||||
// time of this writing.
|
||||
emitterSubscription.remove();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
@ -58,43 +55,16 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*}
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _appWillMount({ dispatch, getState }) {
|
||||
const context = {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
|
||||
const listeners = [
|
||||
function _appWillMount({ dispatch }, next, action) {
|
||||
dispatch(_setEmitterSubscriptions([
|
||||
|
||||
// Android's onUserLeaveHint activity lifecycle callback
|
||||
DeviceEventEmitter.addListener('onUserLeaveHint', () => {
|
||||
_enterPictureInPicture(context);
|
||||
})
|
||||
];
|
||||
DeviceEventEmitter.addListener(
|
||||
'onUserLeaveHint',
|
||||
() => dispatch(enterPictureInPicture()))
|
||||
]));
|
||||
|
||||
dispatch(_setListeners(listeners));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to enter PiP mode. This is triggered by user request
|
||||
* (either pressing the button in the toolbox or the home button on Android)
|
||||
* ans this triggers the PiP mode, iff it's available and we are in a
|
||||
* conference.
|
||||
*
|
||||
* @param {Object} store - Redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _enterPictureInPicture({ getState }) {
|
||||
const state = getState();
|
||||
const { app } = state['features/app'];
|
||||
const { conference, joining } = state['features/base/conference'];
|
||||
|
||||
if (app.props.pipAvailable && (conference || joining)) {
|
||||
enterPictureInPictureMode().catch(e => {
|
||||
console.warn(`Error entering PiP mode: ${e}`);
|
||||
});
|
||||
}
|
||||
return next(action);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../../base/redux';
|
||||
|
||||
import { _SET_PIP_LISTENERS } from './actionTypes';
|
||||
import { _SET_EMITTER_SUBSCRIPTIONS } from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/pip', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_PIP_LISTENERS:
|
||||
case _SET_EMITTER_SUBSCRIPTIONS:
|
||||
return {
|
||||
...state,
|
||||
listeners: action.listeners
|
||||
emitterSubscriptions: action.emitterSubscriptions
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -23,7 +24,9 @@ import {
|
|||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { requestPipMode } from '../../mobile/picture-in-picture';
|
||||
import {
|
||||
EnterPictureInPictureToolbarButton
|
||||
} from '../../mobile/picture-in-picture';
|
||||
import { beginRoomLockRequest } from '../../room-lock';
|
||||
import { beginShareRoom } from '../../share-room';
|
||||
|
||||
|
@ -47,91 +50,81 @@ import ToolbarButton from './ToolbarButton';
|
|||
const _SHARE_ROOM_TOOLBAR_BUTTON = true;
|
||||
|
||||
/**
|
||||
* Implements the conference toolbox on React Native.
|
||||
* The type of {@link Toolbox}'s React {@code Component} props.
|
||||
*/
|
||||
class Toolbox extends Component {
|
||||
/**
|
||||
* Toolbox component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Flag showing that audio is muted.
|
||||
*/
|
||||
_audioMuted: PropTypes.bool,
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Flag showing whether the audio-only mode is in use.
|
||||
*/
|
||||
_audioOnly: PropTypes.bool,
|
||||
_audioOnly: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the toolbox is enabled.
|
||||
*/
|
||||
_enabled: PropTypes.bool,
|
||||
_enabled: boolean,
|
||||
|
||||
/**
|
||||
* Flag showing whether room is locked.
|
||||
*/
|
||||
_locked: PropTypes.bool,
|
||||
_locked: boolean,
|
||||
|
||||
/**
|
||||
* Handler for hangup.
|
||||
*/
|
||||
_onHangup: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Requests Picture-in-Picture mode.
|
||||
*/
|
||||
_onPipRequest: PropTypes.func,
|
||||
_onHangup: Function,
|
||||
|
||||
/**
|
||||
* Sets the lock i.e. password protection of the conference/room.
|
||||
*/
|
||||
_onRoomLock: PropTypes.func,
|
||||
_onRoomLock: Function,
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*/
|
||||
_onShareRoom: PropTypes.func,
|
||||
_onShareRoom: Function,
|
||||
|
||||
/**
|
||||
* Toggles the audio-only flag of the conference.
|
||||
*/
|
||||
_onToggleAudioOnly: PropTypes.func,
|
||||
_onToggleAudioOnly: Function,
|
||||
|
||||
/**
|
||||
* Switches between the front/user-facing and back/environment-facing
|
||||
* cameras.
|
||||
*/
|
||||
_onToggleCameraFacingMode: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Flag showing whether Picture-in-Picture is available.
|
||||
*/
|
||||
_pipAvailable: PropTypes.bool,
|
||||
_onToggleCameraFacingMode: Function,
|
||||
|
||||
/**
|
||||
* Flag showing whether video is muted.
|
||||
*/
|
||||
_videoMuted: PropTypes.bool,
|
||||
_videoMuted: boolean,
|
||||
|
||||
/**
|
||||
* Flag showing whether toolbar is visible.
|
||||
*/
|
||||
_visible: PropTypes.bool,
|
||||
_visible: boolean,
|
||||
|
||||
dispatch: PropTypes.func
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements the conference toolbox on React Native.
|
||||
*/
|
||||
class Toolbox extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code Toolbox} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React {@code Component} props with
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
|
@ -183,22 +176,26 @@ class Toolbox extends Component {
|
|||
let style;
|
||||
|
||||
if (this.props[`_${mediaType}Muted`]) {
|
||||
iconName = this[`${mediaType}MutedIcon`];
|
||||
iconName = `${mediaType}MutedIcon`;
|
||||
iconStyle = styles.whitePrimaryToolbarButtonIcon;
|
||||
style = styles.whitePrimaryToolbarButton;
|
||||
} else {
|
||||
iconName = this[`${mediaType}Icon`];
|
||||
iconName = `${mediaType}Icon`;
|
||||
iconStyle = styles.primaryToolbarButtonIcon;
|
||||
style = styles.primaryToolbarButton;
|
||||
}
|
||||
|
||||
return {
|
||||
iconName,
|
||||
|
||||
// $FlowExpectedError
|
||||
iconName: this[iconName],
|
||||
iconStyle,
|
||||
style
|
||||
};
|
||||
}
|
||||
|
||||
_onToggleAudio: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the audio/microphone.
|
||||
*
|
||||
|
@ -226,6 +223,8 @@ class Toolbox extends Component {
|
|||
/* ensureTrack */ true));
|
||||
}
|
||||
|
||||
_onToggleVideo: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the video/camera.
|
||||
*
|
||||
|
@ -307,7 +306,6 @@ class Toolbox extends Component {
|
|||
const underlayColor = 'transparent';
|
||||
const {
|
||||
_audioOnly: audioOnly,
|
||||
_pipAvailable: pipAvailable,
|
||||
_videoMuted: videoMuted
|
||||
} = this.props;
|
||||
|
||||
|
@ -317,15 +315,6 @@ class Toolbox extends Component {
|
|||
<View
|
||||
key = 'secondaryToolbar'
|
||||
style = { styles.secondaryToolbar }>
|
||||
{
|
||||
pipAvailable
|
||||
&& <ToolbarButton
|
||||
iconName = { 'menu-down' }
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this.props._onPipRequest }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
}
|
||||
{
|
||||
AudioRouteButton
|
||||
&& <AudioRouteButton
|
||||
|
@ -364,6 +353,10 @@ class Toolbox extends Component {
|
|||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
}
|
||||
<EnterPictureInPictureToolbarButton
|
||||
iconStyle = { iconStyle }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
@ -390,6 +383,7 @@ class Toolbox extends Component {
|
|||
* TODO As soon as we have common font sets for web and native, this will no
|
||||
* longer be required.
|
||||
*/
|
||||
// $FlowExpectedError
|
||||
Object.assign(Toolbox.prototype, {
|
||||
audioIcon: 'microphone',
|
||||
audioMutedIcon: 'mic-disabled',
|
||||
|
@ -398,31 +392,20 @@ Object.assign(Toolbox.prototype, {
|
|||
});
|
||||
|
||||
/**
|
||||
* Maps actions to React component props.
|
||||
* Maps redux actions to {@link Toolbox}'s React {@code Component} props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onRoomLock: Function,
|
||||
* _onToggleAudioOnly: Function,
|
||||
* _onToggleCameraFacingMode: Function,
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
...abstractMapDispatchToProps(dispatch),
|
||||
|
||||
/**
|
||||
* Requests Picture-in-Picture mode.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onPipRequest() {
|
||||
dispatch(requestPipMode());
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the lock i.e. password protection of the conference/room.
|
||||
*
|
||||
|
@ -471,19 +454,20 @@ function _mapDispatchToProps(dispatch) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Maps part of Redux store to React component props.
|
||||
* Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - Redux store.
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _enabled: boolean,
|
||||
* _locked: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const conference = state['features/base/conference'];
|
||||
const { enabled } = state['features/toolbox'];
|
||||
const { app } = state['features/app'];
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
@ -512,15 +496,7 @@ function _mapStateToProps(state) {
|
|||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_locked: Boolean(conference.locked),
|
||||
|
||||
/**
|
||||
* The indicator which determines if Picture-in-Picture is available.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pipAvailable: Boolean(app && app.props.pipAvailable)
|
||||
_locked: Boolean(conference.locked)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
/* @flow */
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
// @flow
|
||||
|
||||
import { appNavigate } from '../app';
|
||||
import { MEDIA_TYPE } from '../base/media';
|
||||
import { isLocalTrackMuted } from '../base/tracks';
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
/**
|
||||
* Maps redux actions to {@link Toolbox} (React {@code Component}) props.
|
||||
*
|
||||
* @param {Function} dispatch - The redux {@code dispatch} function.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onHangup: Function,
|
||||
* _onToggleAudio: Function,
|
||||
* _onToggleVideo: Function
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
|
||||
return {
|
||||
|
|
|
@ -7,7 +7,11 @@ import getDefaultButtons from './defaultToolbarButtons';
|
|||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
export { abstractMapStateToProps, getButton } from './functions.native';
|
||||
export {
|
||||
abstractMapDispatchToProps,
|
||||
abstractMapStateToProps,
|
||||
getButton
|
||||
} from './functions.native';
|
||||
|
||||
/**
|
||||
* Returns an object which contains the default buttons for the primary and
|
||||
|
|
Loading…
Reference in New Issue