Compare commits

..

6 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
64ed41880e chore(rn,versions) bump app and SDK versions 2022-05-30 14:54:37 +02:00
Saúl Ibarra Corretgé
f1f912f25a fix(ios) fix not using the loudspeaker by default
Fixes: https://github.com/jitsi/jitsi-meet/issues/11563
2022-05-30 14:52:59 +02:00
Calin Chitu
44319b377d fix(chat/native) we need to dispatch close and open chat 2022-05-13 18:09:02 +03:00
Calin Chitu
8deebebe47 feat(mobile/navigation) changed navigation container background color 2022-05-13 10:58:54 +03:00
Calinteodor
b6ef7716bc fix(chat/native) private message replies (#11521)
Fixes issue #11516
2022-05-10 18:30:58 +03:00
Calin Chitu
171c6c6ebf fix(video-menu/native) wrong import path 2022-05-10 15:08:57 +03:00
377 changed files with 5861 additions and 9138 deletions

View File

@@ -1,7 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
<item name="android:windowDisablePreview">true</item>
</style>

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=99.0.0
sdkVersion=99.0.0
appVersion=22.2.1
sdkVersion=5.1.1

View File

@@ -32,7 +32,6 @@
android:name=".JitsiMeetActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:theme="@style/JitsiMeetActivityStyle"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize"/>

View File

@@ -0,0 +1,240 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReadableMap;
import com.rnimmersive.RNImmersiveModule;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
/**
* Base class for all views which are backed by a React Native view.
*/
public abstract class BaseReactView<ListenerT>
extends FrameLayout {
/**
* Background color used by {@code BaseReactView} and the React Native root
* view.
*/
protected static int BACKGROUND_COLOR = 0xFF111111;
/**
* The collection of all existing {@code BaseReactView}s. Used to find the
* {@code BaseReactView} when delivering events coming from
* {@link ExternalAPIModule}.
*/
static final Set<BaseReactView> views
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
/**
* Finds a {@code BaseReactView} which matches a specific external API
* scope.
*
* @param externalAPIScope - The external API scope associated with the
* {@code BaseReactView} to find.
* @return The {@code BaseReactView}, if any, associated with the specified
* {@code externalAPIScope}; otherwise, {@code null}.
*/
public static BaseReactView findViewByExternalAPIScope(
String externalAPIScope) {
synchronized (views) {
for (BaseReactView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
}
return null;
}
/**
* Gets all registered React views.
*
* @return An {@link ArrayList} containing all views currently held by React.
*/
static ArrayList<BaseReactView> getViews() {
return new ArrayList<>(views);
}
/**
* The unique identifier of this {@code BaseReactView} within the process
* for the purposes of {@link ExternalAPIModule}. The name scope was
* inspired by postis which we use on Web for the similar purposes of the
* iframe-based external API.
*/
protected String externalAPIScope;
/**
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
* events occurring in Jitsi Meet.
*/
@Deprecated
private ListenerT listener;
/**
* React Native root view.
*/
private ReactRootView reactRootView;
public BaseReactView(@NonNull Context context) {
super(context);
initialize((Activity)context);
}
public BaseReactView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize((Activity)context);
}
public BaseReactView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize((Activity)context);
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.
*
* @param appName - The name of the "app" (in React Native terms) to load.
* @param props - The React Component props to pass to the app.
*/
public void createReactRootView(String appName, @Nullable Bundle props) {
if (props == null) {
props = new Bundle();
}
props.putString("externalAPIScope", externalAPIScope);
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
ReactInstanceManagerHolder.getReactInstanceManager(),
appName,
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* MUST be called when the {@link Activity} holding this view is destroyed,
* typically in the {@code onDestroy} method.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
}
}
/**
* Gets the listener set on this {@code BaseReactView}.
*
* @return The listener set on this {@code BaseReactView}.
*/
@Deprecated
public ListenerT getListener() {
return listener;
}
/**
* Abstract method called by {@link ExternalAPIModule} when an event is
* received for this view.
*
* @param name - The name of the event.
* @param data - The details of the event associated with/specific to the
* specified {@code name}.
*/
@Deprecated
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
@Deprecated
protected void onExternalAPIEvent(
Map<String, Method> listenerMethods,
String name, ReadableMap data) {
ListenerT listener = getListener();
if (listener != null) {
ListenerUtils.runListenerMethod(
listener, listenerMethods, name, data);
}
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
immersive.emitImmersiveStateChangeEvent();
}
}
/**
* Sets a specific listener on this {@code BaseReactView}.
*
* @param listener The listener to set on this {@code BaseReactView}.
*/
@Deprecated
public void setListener(ListenerT listener) {
this.listener = listener;
}
private void initialize(Activity activity) {
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager(activity);
// Hook this BaseReactView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();
synchronized (views) {
views.add(this);
}
}
}

View File

@@ -76,8 +76,7 @@ public class BroadcastAction {
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED"),
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED");
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED");
private final String action;

View File

@@ -48,10 +48,4 @@ public class BroadcastIntentHelper {
intent.putExtra("muted", muted);
return intent;
}
public static Intent buildSetClosedCaptionsEnabledIntent(boolean enabled) {
Intent intent = new Intent(BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
intent.putExtra("enabled", enabled);
return intent;
}
}

View File

@@ -95,25 +95,37 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
constants.put("SET_CLOSED_CAPTIONS_ENABLED", BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
return constants;
}
/**
* Dispatches an event that occurred on the JavaScript side of the SDK to
* the native side.
* the specified {@link BaseReactView}'s listener.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
* @param scope
*/
@ReactMethod
public void sendEvent(String name, ReadableMap data) {
public void sendEvent(String name, ReadableMap data, String scope) {
// Keep track of the current ongoing conference.
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
broadcastEmitter.sendBroadcast(name, data);
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native BaseReactView which hosts it.
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
if (view != null) {
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
try {
view.onExternalAPIEvent(name, data);
broadcastEmitter.sendBroadcast(name, data);
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
}
}
}
}

View File

@@ -167,9 +167,12 @@ public class JitsiMeetActivity extends AppCompatActivity
}
}
protected void leave() {
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
public void leave() {
if (this.jitsiView != null) {
this.jitsiView .leave();
} else {
JitsiMeetLogger.w("Cannot leave, view is null");
}
}
private @Nullable
@@ -210,7 +213,7 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onConferenceJoined(HashMap<String, Object> extraData) {
JitsiMeetLogger.i("Conference joined: " + extraData);
// Launch the service for the ongoing notification.
JitsiMeetOngoingConferenceService.launch(this, extraData);
JitsiMeetOngoingConferenceService.launch(this);
}
protected void onConferenceTerminated(HashMap<String, Object> extraData) {

View File

@@ -0,0 +1,82 @@
/*
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and
* the Fragment lifecycle methods already implemented.
*
* In this fragment we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifecycle methods associated with this Fragment are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetActivityDelegate} static methods.
*
* @deprecated use {@link JitsiMeetActivity} or directly {@link JitsiMeetView}
*/
@Deprecated
public class JitsiMeetFragment extends Fragment {
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return this.view = new JitsiMeetView(getActivity());
}
public JitsiMeetView getJitsiView() {
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
}
@Override
public void onResume() {
super.onResume();
JitsiMeetActivityDelegate.onHostResume(getActivity());
}
@Override
public void onStop() {
super.onStop();
JitsiMeetActivityDelegate.onHostPause(getActivity());
}
}

View File

@@ -17,22 +17,18 @@
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
@@ -43,22 +39,16 @@ import java.util.HashMap;
public class JitsiMeetOngoingConferenceService extends Service
implements OngoingConferenceTracker.OngoingConferenceListener {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
private static final String EXTRA_DATA_KEY = "extraDataKey";
private static final String EXTRA_DATA_BUNDLE_KEY = "extraDataBundleKey";
private static final String IS_AUDIO_MUTED_KEY = "isAudioMuted";
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private boolean isAudioMuted;
public static void launch(Context context, HashMap<String, Object> extraData) {
static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
Bundle extraDataBundle = new Bundle();
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
intent.setAction(Action.START.getName());
ComponentName componentName;
@@ -80,7 +70,7 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
public static void abort(Context context) {
static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@@ -89,15 +79,6 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
OngoingConferenceTracker.getInstance().addListener(this);
IntentFilter intentFilter = new IntentFilter();
@@ -120,45 +101,37 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
}
}
final String actionName = intent.getAction();
final Action action = Action.fromName(actionName);
// When starting the service, there is no action passed in the intent
if (action != null) {
switch (action) {
case UNMUTE:
case MUTE:
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
break;
case HANGUP:
JitsiMeetLogger.i(TAG + " Hangup requested");
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
switch (action) {
case UNMUTE:
case MUTE:
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
break;
case START:
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
break;
default:
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
break;
}
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
break;
case HANGUP:
JitsiMeetLogger.i(TAG + " Hangup requested");
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
stopSelf();
break;
default:
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
stopSelf();
break;
}
return START_NOT_STICKY;
@@ -174,6 +147,7 @@ public class JitsiMeetOngoingConferenceService extends Service
}
public enum Action {
START(TAG + ":START"),
HANGUP(TAG + ":HANGUP"),
MUTE(TAG + ":MUTE"),
UNMUTE(TAG + ":UNMUTE");
@@ -198,15 +172,6 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
private Boolean tryParseIsAudioMuted(Intent intent) {
try {
HashMap<String, Object> extraData = (HashMap<String, Object>) intent.getBundleExtra(EXTRA_DATA_BUNDLE_KEY).getSerializable(EXTRA_DATA_KEY);
return Boolean.parseBoolean((String) extraData.get(IS_AUDIO_MUTED_KEY));
} catch (Exception ignored) {
}
return null;
}
private class BroadcastReceiver extends android.content.BroadcastReceiver {
@Override
@@ -215,12 +180,10 @@ public class JitsiMeetOngoingConferenceService extends Service
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
}

View File

@@ -16,33 +16,36 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView;
import com.rnimmersive.RNImmersiveModule;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.lang.reflect.Method;
import java.util.Map;
public class JitsiMeetView extends FrameLayout {
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
implements OngoingConferenceTracker.OngoingConferenceListener {
/**
* Background color used by {@code BaseReactView} and the React Native root
* view.
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final int BACKGROUND_COLOR = 0xFF111111;
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
/**
* React Native root view.
* The URL of the current conference.
*/
private ReactRootView reactRootView;
// XXX Currently, one thread writes and one thread reads, so it should be
// fine to have this field volatile without additional synchronization.
private volatile String url;
/**
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
@@ -106,19 +109,10 @@ public class JitsiMeetView extends FrameLayout {
initialize(context);
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* MUST be called when the {@link Activity} holding this view is destroyed,
* typically in the {@code onDestroy} method.
*/
@Override
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
}
OngoingConferenceTracker.getInstance().removeListener(this);
super.dispose();
}
/**
@@ -136,7 +130,8 @@ public class JitsiMeetView extends FrameLayout {
PictureInPictureModule.class);
if (pipModule != null
&& pipModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()) {
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
@@ -156,40 +151,10 @@ public class JitsiMeetView extends FrameLayout {
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.
*
* @param appName - The name of the "app" (in React Native terms) to load.
* @param props - The React Component props to pass to the app.
* Leaves the currently active conference.
*/
private void createReactRootView(String appName, @Nullable Bundle props) {
if (props == null) {
props = new Bundle();
}
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
ReactInstanceManagerHolder.getReactInstanceManager(),
appName,
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
private void initialize(@NonNull Context context) {
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
public void leave() {
setProps(new Bundle());
}
/**
@@ -214,27 +179,46 @@ public class JitsiMeetView extends FrameLayout {
createReactRootView("App", props);
}
/**
* Handler for {@link OngoingConferenceTracker} events.
* @param conferenceUrl
*/
@Override
public void onCurrentConferenceChanged(String conferenceUrl) {
// This property was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
this.url = conferenceUrl;
}
/**
* Handler for {@link ExternalAPIModule} events.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
*/
@Override
@Deprecated
protected void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
@Override
protected void onDetachedFromWindow() {
dispose();
super.onDetachedFromWindow();
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
immersive.emitImmersiveStateChangeEvent();
private void initialize(@NonNull Context context) {
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
OngoingConferenceTracker.getInstance().addListener(this);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import java.util.Map;
/**
* Interface for listening to events coming from Jitsi Meet.
*/
@Deprecated
public interface JitsiMeetViewListener {
/**
* Called when a conference was joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceJoined(Map<String, Object> data);
/**
* Called when the active conference ends, be it because of user choice or
* because of a failure.
*
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL. If the conference finished gracefully no `error`
* key will be present. The possible values for "error" are described here:
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
*/
void onConferenceTerminated(Map<String, Object> data);
/**
* Called before the conference is joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillJoin(Map<String, Object> data);
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.UiThreadUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility methods for helping with transforming {@link ExternalAPIModule}
* events into listener methods. Used with descendants of {@link BaseReactView}.
*/
@Deprecated
public final class ListenerUtils {
/**
* Extracts the methods defined in a listener and creates a mapping of this
* form: event name -> method.
*
* @param listener - The listener whose methods we want to slurp.
* @return A mapping with event names - methods.
*/
public static Map<String, Method> mapListenerMethods(Class listener) {
Map<String, Method> methods = new HashMap<>();
// Figure out the mapping between the listener methods
// and the events i.e. redux action types.
Pattern onPattern = Pattern.compile("^on[A-Z]+");
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
for (Method method : listener.getDeclaredMethods()) {
// * The method must be public (because it is declared by an
// interface).
// * The method must be/return void.
if (!Modifier.isPublic(method.getModifiers())
|| !Void.TYPE.equals(method.getReturnType())) {
continue;
}
// * The method name must start with "on" followed by a
// capital/uppercase letter (in agreement with the camelcase
// coding style customary to Java in general and the projects of
// the Jitsi community in particular).
String name = method.getName();
if (!onPattern.matcher(name).find()) {
continue;
}
// * The method must accept/have exactly 1 parameter of a type
// assignable from HashMap.
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
continue;
}
// Convert the method name to an event name.
name
= camelcasePattern.matcher(name.substring(2))
.replaceAll("$1_$2")
.toUpperCase(Locale.ROOT);
methods.put(name, method);
}
return methods;
}
/**
* Executes the right listener method for the given event.
* NOTE: This function will run asynchronously on the UI thread.
*
* @param listener - The listener on which the method will be called.
* @param listenerMethods - Mapping with event names and the matching
* methods.
* @param eventName - Name of the event.
* @param eventData - Data associated with the event.
*/
public static void runListenerMethod(
final Object listener,
final Map<String, Method> listenerMethods,
final String eventName,
final ReadableMap eventData) {
// Make sure listener methods are invoked on the UI thread. It
// was requested by SDK consumers.
if (UiThreadUtil.isOnUiThread()) {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
} else {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
}
});
}
}
/**
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
* in the UI thread.
*/
private static void runListenerMethodOnUiThread(
Object listener,
Map<String, Method> listenerMethods,
String eventName,
ReadableMap eventData) {
UiThreadUtil.assertOnUiThread();
Method method = listenerMethods.get(eventName);
if (method != null) {
try {
method.invoke(listener, toHashMap(eventData));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* Initializes a new {@code HashMap} instance with the key-value
* associations of a specific {@code ReadableMap}.
*
* @param readableMap the {@code ReadableMap} specifying the key-value
* associations with which the new {@code HashMap} instance is to be
* initialized.
* @return a new {@code HashMap} instance initialized with the key-value
* associations of the specified {@code readableMap}.
*/
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
HashMap<String, Object> hashMap = new HashMap<>();
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
i.hasNextKey();) {
String key = i.nextKey();
hashMap.put(key, readableMap.getString(key));
}
return hashMap;
}
}

View File

@@ -43,7 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
private static final String TAG = NAME;
private static boolean isSupported;
private boolean isEnabled;
private boolean isDisabled;
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
@@ -84,7 +84,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isEnabled) {
if (isDisabled) {
return;
}
@@ -132,8 +132,8 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void setPictureInPictureEnabled(Boolean enabled) {
this.isEnabled = enabled;
public void setPictureInPictureDisabled(Boolean disabled) {
this.isDisabled = disabled;
}
public boolean isPictureInPictureSupported() {

View File

@@ -1,3 +0,0 @@
<resources>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
</resources>

View File

@@ -923,35 +923,6 @@ export default {
: isVideoMutedByUser(APP.store);
},
/**
* Verify if there is an ongoing system audio sharing session and apply to the provided track
* as a AudioMixer effect.
*
* @param {*} localAudioTrack - track to which system audio track will be applied as an effect, most likely
* microphone local audio track.
*/
async _maybeApplyAudioMixerEffect(localAudioTrack) {
// At the time of writing this comment there were two separate flows for toggling screen-sharing
// and system audio sharing, the first is the legacy method using the functionality from conference.js
// the second is used when both sendMultipleVideoStreams and sourceNameSignaling flags are set to true.
// The second flow uses functionality from base/conference/middleware.web.js.
// We check if system audio sharing was done using the first flow by verifying this._desktopAudioStream and
// for the second by checking 'features/screen-share' state.
const { desktopAudioTrack } = APP.store.getState()['features/screen-share'];
const currentDesktopAudioTrack = this._desktopAudioStream || desktopAudioTrack;
// If system audio is already being sent, mix it with the provided audio track.
if (currentDesktopAudioTrack) {
// In case system audio sharing was done in the absence of an initial mic audio track, there is no
// AudioMixerEffect so we have to remove system audio track from the room before setting it as an effect.
await room.replaceTrack(currentDesktopAudioTrack, null);
this._mixerEffect = new AudioMixerEffect(currentDesktopAudioTrack);
logger.debug('Mixing new audio track with existing screen audio track.');
await localAudioTrack.setEffect(this._mixerEffect);
}
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* @param {boolean} mute true for mute and false for unmute.
@@ -1005,11 +976,7 @@ export default {
// Rollback the audio muted status by using null track
return null;
})
.then(async audioTrack => {
await this._maybeApplyAudioMixerEffect(audioTrack);
this.useAudioStream(audioTrack);
});
.then(audioTrack => this.useAudioStream(audioTrack));
} else {
muteLocalAudio(mute);
}
@@ -1650,26 +1617,12 @@ export default {
let promise = _prevMutePresenterVideo = _prevMutePresenterVideo.then(() => {
// mute the presenter track if it exists.
if (this.localPresenterVideo) {
return (
this.localPresenterVideo.dispose().then(() => {
APP.store.dispatch(trackRemoved(this.localPresenterVideo));
this.localPresenterVideo = null;
})
.then(() => {
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
// This is needed only for setting the correct muted state in features/base/media.
// NOTE: It is important to be executed after we have disposed and removed the presenter track.
// This way all the side effects won't be executed and we won't start additional O/A cycle for
// replacing the track with video with the one without video. This O/A cycle is not needed since
// we are trying to destroy all tracks. Also due to the current async nature of muting the
// presenter, the final removal of the screen sharing track (see the code at the end of the
// function) can be executed between the removal of the stream with video and adding the
// original screen sharing stream to the peer connection. This will lead to a failure to remove
// the screen sharing track, compromising the screen sharing state in jitsi-meet and the user
// won't be able to turn off the screen sharing.
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
})
);
return this.localPresenterVideo.dispose().then(() => {
APP.store.dispatch(trackRemoved(this.localPresenterVideo));
this.localPresenterVideo = null;
});
}
});
@@ -1759,12 +1712,12 @@ export default {
return Promise.reject('Cannot toggle screen sharing: not supported.');
}
if (this.isAudioOnly()) {
APP.store.dispatch(setAudioOnly(false));
}
if (toggle) {
try {
await this._switchToScreenSharing(options);
if (this.isAudioOnly()) {
APP.store.dispatch(setAudioOnly(false));
}
return;
} catch (err) {
@@ -2619,7 +2572,13 @@ export default {
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
// In case screen sharing audio is also shared we mix it with new input stream. The old _mixerEffect
// will be cleaned up when the existing track is replaced.
if (this._mixerEffect) {
this._mixerEffect = new AudioMixerEffect(this._desktopAudioStream);
await stream.setEffect(this._mixerEffect);
}
return this.useAudioStream(stream);
})
@@ -2653,9 +2612,13 @@ export default {
// muteVideo logic in such case.
const tracks = APP.store.getState()['features/base/tracks'];
const isTrackInRedux
= Boolean(tracks.find(track => track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.VIDEO));
= Boolean(
tracks.find(
track => track.jitsiTrack
&& track.jitsiTrack.getType() === 'video'));
if (isTrackInRedux && !this.isSharingScreen) {
if (isTrackInRedux) {
this.muteVideo(audioOnly);
}
@@ -3094,12 +3057,34 @@ export default {
* @param email {string} the new email
*/
changeLocalEmail(email = '') {
const localParticipant = getLocalParticipant(APP.store.getState());
const formattedEmail = String(email).trim();
if (formattedEmail === localParticipant.email) {
return;
}
const localId = localParticipant.id;
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id: localId,
local: true,
email: formattedEmail
}));
APP.store.dispatch(updateSettings({
email: formattedEmail
}));
APP.API.notifyEmailChanged(localId, {
email: formattedEmail
});
sendData(commands.EMAIL, formattedEmail);
},
@@ -3108,12 +3093,29 @@ export default {
* @param url {string} the new url
*/
changeLocalAvatarUrl(url = '') {
const { avatarURL, id } = getLocalParticipant(APP.store.getState());
const formattedUrl = String(url).trim();
if (formattedUrl === avatarURL) {
return;
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
avatarURL: formattedUrl
}));
APP.store.dispatch(updateSettings({
avatarURL: formattedUrl
}));
sendData(commands.AVATAR_URL, url);
},
@@ -3154,6 +3156,23 @@ export default {
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname = getNormalizedDisplayName(nickname);
const { id, name } = getLocalParticipant(APP.store.getState());
if (formattedNickname === name) {
return;
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
name: formattedNickname
}));
APP.store.dispatch(updateSettings({
displayName: formattedNickname

166
config.js
View File

@@ -143,7 +143,6 @@ var config = {
// Disable measuring of audio levels.
// disableAudioLevels: false,
// audioLevelsInterval: 200,
// Enabling this will run the lib-jitsi-meet no audio detection module which
@@ -272,9 +271,8 @@ var config = {
// Recording
// DEPRECATED. Use recordingService.enabled instead.
// Whether to enable file recording or not.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>' // Specify your app key here.
@@ -284,77 +282,38 @@ var config = {
// redirectURI:
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html'
// },
// recordingService: {
// // When integrations like dropbox are enabled only that will be shown,
// // by enabling fileRecordingsServiceEnabled, we show both the integrations
// // and the generic recording service (its configuration and storage type
// // depends on jibri configuration)
// enabled: false,
// // Whether to show the possibility to share file recording with other people
// // (e.g. meeting participants), based on the actual implementation
// // on the backend.
// sharingEnabled: false,
// // Hide the warning that says we only store the recording for 24 hours.
// hideStorageWarning: false
// },
// DEPRECATED. Use recordingService.enabled instead.
// When integrations like dropbox are enabled only that will be shown,
// by enabling fileRecordingsServiceEnabled, we show both the integrations
// and the generic recording service (its configuration and storage type
// depends on jibri configuration)
// fileRecordingsServiceEnabled: false,
// DEPRECATED. Use recordingService.sharingEnabled instead.
// Whether to show the possibility to share file recording with other people
// (e.g. meeting participants), based on the actual implementation
// on the backend.
// fileRecordingsServiceSharingEnabled: false,
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
// Local recording configuration.
// localRecording: {
// // Whether to disable local recording or not.
// disable: false,
// // Whether to notify all participants when a participant is recording locally.
// notifyAllParticipants: false
// },
// DEPRECATED. Use transcription.enabled instead.
// Transcription (in interface_config,
// subtitles and buttons can be configured)
// transcribingEnabled: false,
// DEPRECATED. Use transcription.useAppLanguage instead.
// If true transcriber will use the application language.
// The application language is either explicitly set by participants in their settings or automatically
// detected based on the environment, e.g. if the app is opened in a chrome instance which is using french as its
// default language then transcriptions for that participant will be in french.
// Defaults to true.
// transcribeWithAppLanguage: true,
// DEPRECATED. Use transcription.preferredLanguage instead.
// Transcriber language. This settings will only work if "transcribeWithAppLanguage" is explicitly set to false.
// Available languages can be found in
// ./src/react/features/transcribing/transcriber-langs.json.
// preferredTranscribeLanguage: 'en-US',
// DEPRECATED. Use transcription.autoCaptionOnRecord instead.
// Enables automatic turning on captions when recording is started
// autoCaptionOnRecord: false,
// Transcription options.
// transcription: {
// // Whether the feature should be enabled or not.
// enabled: false,
// // If true transcriber will use the application language.
// // The application language is either explicitly set by participants in their settings or automatically
// // detected based on the environment, e.g. if the app is opened in a chrome instance which
// // is using french as its default language then transcriptions for that participant will be in french.
// // Defaults to true.
// useAppLanguage: true,
// // Transcriber language. This settings will only work if "useAppLanguage"
// // is explicitly set to false.
// // Available languages can be found in
// // ./src/react/features/transcribing/transcriber-langs.json.
// preferredLanguage: 'en-US',
// // Disable start transcription for all participants.
// disableStartForAll: false,
// // Enables automatic turning on captions when recording is started
// autoCaptionOnRecord: false
// },
// Misc
// Default value for the channel "last N" attribute. -1 for unlimited.
@@ -416,7 +375,7 @@ var config = {
// // This will result in Safari not being able to decode video from endpoints sending VP9 video.
// // When set to false, the conference falls back to VP8 whenever there is an endpoint that doesn't support the
// // preferred codec and goes back to the preferred codec when that endpoint leaves.
// enforcePreferredCodec: false,
// // enforcePreferredCodec: false,
//
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
@@ -584,8 +543,8 @@ var config = {
// enableFeaturesBasedOnToken: false,
// When enabled the password used for locking a room is restricted to up to the number of digits specified
// default: roomPasswordNumberOfDigits: false,
// roomPasswordNumberOfDigits: 10,
// default: roomPasswordNumberOfDigits: false,
// Message to show the users. Example: 'The service will be down for
// maintenance at 01:00 AM GMT,
@@ -600,10 +559,6 @@ var config = {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through
// // either the jwt or the userInfo from the iframe api init object in order for this to have an effect.
// hideDisplayName: false,
// // List of buttons to hide from the extra join options dropdown.
// hideExtraJoinButtons: ['no-audio', 'by-phone']
// },
@@ -663,7 +618,6 @@ var config = {
// 'chat',
// 'closedcaptions',
// 'desktop',
// 'dock-iframe'
// 'download',
// 'embedmeeting',
// 'etherpad',
@@ -677,6 +631,8 @@ var config = {
// 'linktosalesforce',
// 'livestreaming',
// 'microphone',
// 'mute-everyone',
// 'mute-video-everyone',
// 'participants-pane',
// 'profile',
// 'raisehand',
@@ -690,7 +646,6 @@ var config = {
// 'stats',
// 'tileview',
// 'toggle-camera',
// 'undock-iframe',
// 'videoquality',
// '__end'
// ],
@@ -817,7 +772,7 @@ var config = {
// enableEmailInStats: false,
// faceLandmarks: {
// // Enables sharing your face coordinates. Used for centering faces within a video.
// // Enables sharing your face cordinates. Used for centering faces within a video.
// enableFaceCentering: false,
// // Enables detecting face expressions and sharing data with other participants
@@ -826,14 +781,11 @@ var config = {
// // Enables displaying face expressions in speaker stats
// enableDisplayFaceExpressions: false,
// // Enable rtc stats for face landmarks
// enableRTCStats: false,
// // Minimum required face movement percentage threshold for sending new face centering coordinates data.
// faceCenteringThreshold: 10,
// // Milliseconds for processing a new image capture in order to detect face coordinates if they exist.
// captureInterval: 1000
// // Miliseconds for processing a new image capture in order to detect face coordinates if they exist.
// captureInterval: 100
// },
// Controls the percentage of automatic feedback shown to participants when callstats is enabled.
@@ -913,10 +865,6 @@ var config = {
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>'
// Obfuscates room name sent to analytics (amplitude, rtcstats)
// Default value is false.
// obfuscateRoomName: false,
// Configuration for the rtcstats server:
// By enabling rtcstats server every time a conference is joined the rtcstats
// module connects to the provided rtcstatsEndpoint and sends statistics regarding
@@ -991,22 +939,33 @@ var config = {
// chromeExtensionBanner: {
// // The chrome extension to be installed address
// url: 'https://chrome.google.com/webstore/detail/jitsi-meetings/kglhbbefdnlheedjiejgomgmfplipfeb',
// edgeUrl: 'https://microsoftedge.microsoft.com/addons/detail/jitsi-meetings/eeecajlpbgjppibfledfihobcabccihn',
// // Extensions info which allows checking if they are installed or not
// chromeExtensionsInfo: [
// {
// id: 'kglhbbefdnlheedjiejgomgmfplipfeb',
// path: 'jitsi-logo-48x48.png'
// },
// // Edge extension info
// {
// id: 'eeecajlpbgjppibfledfihobcabccihn',
// path: 'jitsi-logo-48x48.png'
// }
// ]
// },
// Local Recording
//
// localRecording: {
// Enables local recording.
// Additionally, 'localrecording' (all lowercase) needs to be added to
// the `toolbarButtons`-array for the Local Recording button to show up
// on the toolbar.
//
// enabled: true,
//
// The recording format, can be one of 'ogg', 'flac' or 'wav'.
// format: 'flac'
//
// },
// e2ee: {
// labels,
// externallyManagedKey: false
@@ -1052,8 +1011,7 @@ var config = {
// Disables all invite functions from the app (share, invite, dial out...etc)
// disableInviteFunctions: true,
// Disables storing the room name to the recents list. When in an iframe this is ignored and
// the room is never stored in the recents list.
// Disables storing the room name to the recents list
// doNotStoreRoom: true,
// Deployment specific URLs.
@@ -1150,24 +1108,22 @@ var config = {
*/
// dynamicBrandingUrl: '',
// Options related to the participants pane.
// participantsPane: {
// // Hides the moderator settings tab.
// hideModeratorSettingsTab: false,
// // Hides the more actions button.
// hideMoreActionsButton: false,
// // Hides the mute all button.
// hideMuteAllButton: false
// },
// Options related to the breakout rooms feature.
// breakoutRooms: {
// // Hides the add breakout room button. This replaces `hideAddRoomButton`.
// hideAddRoomButton: false,
// // Hides the auto assign participants button.
// hideAutoAssignButton: false,
// // Hides the participants pane footer menu.
// hideFooterMenu: false,
// // Hides the join breakout room button.
// hideJoinRoomButton: false
// hideJoinRoomButton: false,
// // Hides the moderator settings tab.
// hideModeratorSettingsTab: false,
// // Hides the more actions button.
// hideMoreActionsButton: false,
// // Hides the mute all button.
// hideMuteAllButton: false
// },
// When true the user cannot add more images to be used as virtual background.
@@ -1206,8 +1162,7 @@ var config = {
// 'transcribing',
// 'video-quality',
// 'insecure-room',
// 'highlight-moment',
// 'top-panel-toggle'
// 'highlight-moment'
// ]
// },
@@ -1343,6 +1298,7 @@ var config = {
// 'liveStreaming.unavailableTitle', // shown when livestreaming service is not reachable
// 'lobby.joinRejectedMessage', // shown when while in a lobby, user's request to join is rejected
// 'lobby.notificationTitle', // shown when lobby is toggled and when join requests are allowed / denied
// 'localRecording.localRecording', // shown when a local recording is started
// 'notify.chatMessages', // shown when receiving chat messages while the chat window is closed
// 'notify.disconnected', // shown when a participant has left
// 'notify.connectedOneMember', // show when a participant joined
@@ -1388,9 +1344,6 @@ var config = {
// 'transcribing.failedToStart' // shown when transcribing fails to start
// ],
// List of notifications to be disabled. Works in tandem with the above setting.
// disabledNotifications: [],
// Prevent the filmstrip from autohiding when screen width is under a certain threshold
// disableFilmstripAutohiding: false,
@@ -1401,14 +1354,7 @@ var config = {
// // Disables the stage filmstrip
// // (displaying multiple participants on stage besides the vertical filmstrip)
// disableStageFilmstrip: false,
// // Disables the top panel (only shown when a user is sharing their screen).
// disableTopPanel: false,
// // The minimum number of participants that must be in the call for
// // the top panel layout to be used.
// minParticipantCountForTopPanel: 50
// disableStageFilmstrip: false
// },
// Tile view related config options.

View File

@@ -8,8 +8,7 @@ import { LoginDialog } from './react/features/authentication/components';
import { isTokenAuthEnabled } from './react/features/authentication/functions';
import {
connectionEstablished,
connectionFailed,
constructOptions
connectionFailed
} from './react/features/base/connection/actions';
import { openDialog } from './react/features/base/dialog/actions';
import { setJWT } from './react/features/base/jwt';
@@ -20,9 +19,7 @@ import {
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
import { getCustomerDetails } from './react/features/jaas/actions.any';
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
import {
setPrejoinDisplayNameRequired
} from './react/features/prejoin/actions';
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
const logger = Logger.getLogger(__filename);
/**
@@ -84,10 +81,12 @@ function checkForAttachParametersAndConnect(id, password, connection) {
* Try to open connection using provided credentials.
* @param {string} [id]
* @param {string} [password]
* @param {string} [roomName]
* @returns {Promise<JitsiConnection>} connection if
* everything is ok, else error.
*/
export async function connect(id, password) {
export async function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const state = APP.store.getState();
let { jwt } = state['features/base/jwt'];
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
@@ -101,7 +100,19 @@ export async function connect(id, password) {
}
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
serviceUrl += `?room=${roomName}`;
connectionConfig.serviceUrl = serviceUrl;
if (connectionConfig.websocketKeepAliveUrl) {
connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
if (config.iAmRecorder) {
connection.addFeature(DISCO_JIBRI_FEATURE);

View File

@@ -19,34 +19,11 @@
font-size: 14px;
margin-left: 16px;
}
&.space-top {
margin-top: 10px;
}
}
.recording-header-line {
border-top: 1px solid #5e6d7a;
padding-top: 16px;
margin-top: 16px;
}
.local-recording-warning {
margin-top: 8px;
display: block;
font-size: 14px;
line-height: 20px;
padding: 8px 16px;
&.text {
color: #fff;
background-color: #3D3D3D;
}
&.notification {
color: #040404;
background-color: #F8AE1A;
}
padding-top: 32px;
}
.recording-switch-disabled {
@@ -63,7 +40,7 @@
border-radius: 4px;
height: 40px;
justify-content: center;
width: 42px;
width: 56px;
}
.cloud-content-recording-icon-container {
@@ -75,7 +52,7 @@
}
.jitsi-recording-header {
margin-bottom: 16px;
margin-bottom: 32px;
}
.jitsi-content-recording-icon-container-with-switch {

View File

@@ -5,6 +5,8 @@
.remote-videos {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
overscroll-behavior: contain;
}

View File

@@ -1,4 +1,5 @@
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';
@import 'prejoin-dialog';
@import 'prejoin-third-party';

View File

@@ -0,0 +1,118 @@
.prejoin-dialog {
background: #1C2025;
box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.5);
border-radius: 5px;
color: #fff;
height: 400px;
width: 375px;
&--small {
height: 300;
width: 400;
}
&-label {
font-size: 15px;
line-height: 24px;
&-num {
background: #2b3b4b;
border: 1px solid #A4B8D1;
border-radius: 50%;
color: #fff;
display: inline-block;
height: 24px;
margin-right: 8px;
width: 24px;
}
}
&-container {
align-items: center;
background: rgba(0,0,0,0.6);
display: flex;
height: 100vh;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100vw;
z-index: 3;
}
&-flag {
display: inline-block;
margin-right: 8px;
transform: scale(1.2);
}
&-title {
display: inline-block;
font-size: 24px;
line-height: 32px;
}
&-icon {
cursor: pointer;
> svg {
fill: #A4B8D1;
}
}
&-btn {
width: 309px;
}
&-dialin-container {
text-align: center;
}
&-delimiter {
background: #5f6266;
border: 0;
height: 1px;
margin: 0;
padding: 0;
width: 100%;
&-container {
margin: 16px 0 24px 0;
position: relative;
}
&-txt-container {
position: absolute;
text-align: center;
top: -8px;
width: 100%;
}
&-txt {
background: #1C2025;
color: #5f6266;
font-size: 11px;
text-transform: uppercase;
padding: 0 8px;
}
}
.prejoin-dialog-btn.primary,
.action-btn.prejoin-dialog-btn.text {
width: 310px;
}
}
.prejoin-dialog-callout {
padding: 16px;
&-header {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
}
&-picker {
margin: 8px 0 16px 0;
}
}

View File

@@ -3,25 +3,6 @@
width: 100%;
}
&-avatar {
margin: 8px auto 16px;
&-name {
color: white;
font-size: 16px;
font-weight: 600;
line-height: 26px;
margin-bottom: 32px;
text-align: center;
}
&-container {
align-items: center;
display: flex;
flex-direction: column;
}
}
&-error {
background-color: #E04757;
border-radius: 6px;

4
debian/control vendored
View File

@@ -33,7 +33,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody (>= 0.11.0) | prosody-trunk | prosody-0.12 | prosody-0.11, lua-sec, lua-basexx, lua-luaossl, lua-cjson
Depends: openssl, prosody (>= 0.11.0) | prosody-trunk | prosody-0.12 | prosody-0.11, lua-sec
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
@@ -47,7 +47,7 @@ Description: Prosody configuration for Jitsi Meet
Package: jitsi-meet-tokens
Architecture: all
Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.2), jitsi-meet-prosody
Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody, git, lua-basexx
Description: Prosody token authentication plugin for Jitsi Meet
Package: jitsi-meet-turnserver

View File

@@ -48,6 +48,11 @@ case "$1" in
db_stop
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
# Install luajwt (also on update, to make sure we get the latest version).
if ! luarocks install luajwtjitsi 3.0-0; then
echo "Failed to install luajwtjitsi - try installing it manually"
fi
# search for the token auth, if this is not enabled this is the
# first time we install tokens package and needs a config change
if ! egrep -q '^\s*authentication\s*=\s*"token"' "$PROSODY_HOST_CONFIG"; then

View File

@@ -138,11 +138,11 @@ Component "lobby.jitmeet.example.com" "muc"
"polls";
}
-- Enables dial-in for Jitsi meet components customers
-- Enabled dial-in for JaaS customers
-- Note: make sure you have the following packages installed: lua-basexx, liblua5.3-dev, libssl-dev, luarocks
-- and execute $ sudo luarocks install luajwtjitsi 3.0-0
VirtualHost "jigasi.meet.jitsi"
enabled = false -- Jitsi meet components customers remove this line
enabled = false -- JaaS customers remove this line
modules_enabled = {
"ping";
"bosh";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

View File

@@ -144,7 +144,7 @@ var interfaceConfig = {
RECENT_LIST_ENABLED: true,
REMOTE_THUMBNAIL_RATIO: 1, // 1:1
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds', 'more' ],
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds' ],
/**
* Specify which sharing features should be displayed. If the value is not set

View File

@@ -9,9 +9,9 @@ install! 'cocoapods', :deterministic_uuids => false
target 'JitsiMeet' do
project 'app/app.xcodeproj'
pod 'Firebase/Analytics', '~> 8.0'
pod 'Firebase/Crashlytics', '~> 8.0'
pod 'Firebase/DynamicLinks', '~> 8.0'
pod 'Firebase/Analytics', '~> 6.33.0'
pod 'Firebase/Crashlytics', '~> 6.33.0'
pod 'Firebase/DynamicLinks', '~> 6.33.0'
end
target 'JitsiMeetSDK' do

View File

@@ -21,60 +21,50 @@ PODS:
- React-Core (= 0.68.1)
- React-jsi (= 0.68.1)
- ReactCommon/turbomodule/core (= 0.68.1)
- Firebase/Analytics (8.15.0):
- Firebase/Analytics (6.33.0):
- Firebase/Core
- Firebase/Core (8.15.0):
- Firebase/Core (6.33.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 8.15.0)
- Firebase/CoreOnly (8.15.0):
- FirebaseCore (= 8.15.0)
- Firebase/Crashlytics (8.15.0):
- FirebaseAnalytics (= 6.8.3)
- Firebase/CoreOnly (6.33.0):
- FirebaseCore (= 6.10.3)
- Firebase/Crashlytics (6.33.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 8.15.0)
- Firebase/DynamicLinks (8.15.0):
- FirebaseCrashlytics (~> 4.6.1)
- Firebase/DynamicLinks (6.33.0):
- Firebase/CoreOnly
- FirebaseDynamicLinks (~> 8.15.0)
- FirebaseAnalytics (8.15.0):
- FirebaseAnalytics/AdIdSupport (= 8.15.0)
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- FirebaseAnalytics/AdIdSupport (8.15.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleAppMeasurement (= 8.15.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- FirebaseCore (8.15.0):
- FirebaseCoreDiagnostics (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- FirebaseCoreDiagnostics (8.15.0):
- GoogleDataTransport (~> 9.1)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- nanopb (~> 2.30908.0)
- FirebaseCrashlytics (8.15.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleDataTransport (~> 9.1)
- GoogleUtilities/Environment (~> 7.7)
- nanopb (~> 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- FirebaseDynamicLinks (8.15.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (8.15.0):
- FirebaseCore (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7)
- PromisesObjC (< 3.0, >= 1.2)
- FirebaseDynamicLinks (~> 4.3.1)
- FirebaseAnalytics (6.8.3):
- FirebaseCore (~> 6.10)
- FirebaseInstallations (~> 1.6)
- GoogleAppMeasurement (= 6.8.3)
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
- GoogleUtilities/MethodSwizzler (~> 6.7)
- GoogleUtilities/Network (~> 6.7)
- "GoogleUtilities/NSData+zlib (~> 6.7)"
- nanopb (~> 1.30906.0)
- FirebaseCore (6.10.3):
- FirebaseCoreDiagnostics (~> 1.6)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- FirebaseCoreDiagnostics (1.7.0):
- GoogleDataTransport (~> 7.4)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- nanopb (~> 1.30906.0)
- FirebaseCrashlytics (4.6.2):
- FirebaseCore (~> 6.10)
- FirebaseInstallations (~> 1.6)
- GoogleDataTransport (~> 7.2)
- nanopb (~> 1.30906.0)
- PromisesObjC (~> 1.2)
- FirebaseDynamicLinks (4.3.1):
- FirebaseCore (~> 6.10)
- FirebaseInstallations (1.7.0):
- FirebaseCore (~> 6.10)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/UserDefaults (~> 6.7)
- PromisesObjC (~> 1.2)
- fmt (6.2.1)
- Giphy (2.1.20):
- libwebp
@@ -82,52 +72,36 @@ PODS:
- Giphy (= 2.1.20)
- React-Core
- glog (0.3.5)
- GoogleAppMeasurement (8.15.0):
- GoogleAppMeasurement/AdIdSupport (= 8.15.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (8.15.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 8.15.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (8.15.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleDataTransport (9.1.4):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleAppMeasurement (6.8.3):
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
- GoogleUtilities/MethodSwizzler (~> 6.7)
- GoogleUtilities/Network (~> 6.7)
- "GoogleUtilities/NSData+zlib (~> 6.7)"
- nanopb (~> 1.30906.0)
- GoogleDataTransport (7.5.1):
- nanopb (~> 1.30906.0)
- GoogleSignIn (6.0.2):
- AppAuth (~> 1.4)
- GTMAppAuth (~> 1.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleUtilities/AppDelegateSwizzler (7.7.0):
- GoogleUtilities/AppDelegateSwizzler (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.7.0):
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.7.0):
- GoogleUtilities/Environment (6.7.2):
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (7.7.0):
- GoogleUtilities/MethodSwizzler (6.7.2):
- GoogleUtilities/Logger
- GoogleUtilities/Network (7.7.0):
- GoogleUtilities/Network (6.7.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.7.0)"
- GoogleUtilities/Reachability (7.7.0):
- "GoogleUtilities/NSData+zlib (6.7.2)"
- GoogleUtilities/Reachability (6.7.2):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.7.0):
- GoogleUtilities/UserDefaults (6.7.2):
- GoogleUtilities/Logger
- GTMAppAuth (1.2.2):
- AppAuth/Core (~> 1.4)
@@ -142,13 +116,13 @@ PODS:
- libwebp/mux (1.2.1):
- libwebp/demux
- libwebp/webp (1.2.1)
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
- nanopb/decode (2.30908.0)
- nanopb/encode (2.30908.0)
- nanopb (1.30906.0):
- nanopb/decode (= 1.30906.0)
- nanopb/encode (= 1.30906.0)
- nanopb/decode (1.30906.0)
- nanopb/encode (1.30906.0)
- ObjectiveDropboxOfficial (6.2.3)
- PromisesObjC (2.1.1)
- PromisesObjC (1.2.12)
- RCT-Folly (2021.06.28.00-v2):
- boost
- DoubleConversion
@@ -379,7 +353,7 @@ PODS:
- react-native-video/Video (= 5.2.0)
- react-native-video/Video (5.2.0):
- React-Core
- react-native-webrtc (1.100.1):
- react-native-webrtc (1.100.0):
- React-Core
- react-native-webview (11.15.1):
- React-Core
@@ -467,7 +441,7 @@ PODS:
- React-Core
- RNReanimated (1.13.4):
- React-Core
- RNScreens (3.13.1):
- RNScreens (3.10.1):
- React-Core
- React-RCTImage
- RNSound (0.11.1):
@@ -488,9 +462,9 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- Firebase/Analytics (~> 8.0)
- Firebase/Crashlytics (~> 8.0)
- Firebase/DynamicLinks (~> 8.0)
- Firebase/Analytics (~> 6.33.0)
- Firebase/Crashlytics (~> 6.33.0)
- Firebase/DynamicLinks (~> 6.33.0)
- "giphy-react-native-sdk (from `../node_modules/@giphy/react-native-sdk`)"
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- ObjectiveDropboxOfficial (= 6.2.3)
@@ -700,27 +674,27 @@ SPEC CHECKSUMS:
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
FBLazyVector: 2c76493a346ef8cacf1f442926a39f805fffec1f
FBReactNativeSpec: 371350f24afa87b6aba606972ec959dcd4a95c9a
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
FirebaseCoreDiagnostics: 92e07a649aeb66352b319d43bdd2ee3942af84cb
FirebaseCrashlytics: feb07e4e9187be3c23c6a846cce4824e5ce2dd0b
FirebaseDynamicLinks: 1dc816ef789c5adac6fede0b46d11478175c70e4
FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
FirebaseAnalytics: 5dd088bd2e67bb9d13dbf792d1164ceaf3052193
FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
FirebaseCrashlytics: 1a747c9cc084a24dc6d9511c991db1cd078154eb
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
Giphy: b6d5087521d251bb8c99cdc0eb07bbdf86d142d5
giphy-react-native-sdk: 7abccf2b52123a0f30ce99da895ab6288023680c
glog: 476ee3e89abb49e07f822b48323c51c57124b572
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
GoogleSignIn: fd381840dbe7c1137aa6dc30849a5c3e070c034a
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89
GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
RCTRequired: 00581111c53531e39e3c6346ef0d2c0cf52a5a37
RCTTypeSafety: 07e03ee7800e7dd65cba8e52ad0c2edb06c96604
@@ -744,7 +718,7 @@ SPEC CHECKSUMS:
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: a4c2635d0802f983594b7057e1bce8f442f0ad28
react-native-webrtc: 206a0ac12a5633d2ec4605174d7c9f12f0d674b2
react-native-webrtc: b8f2769386d51a6a8c89778478618fe311226bc3
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
React-perflogger: 30ab8d6db10e175626069e742eead3ebe8f24fd5
React-RCTActionSheet: 4b45da334a175b24dabe75f856b98fed3dfd6201
@@ -767,12 +741,12 @@ SPEC CHECKSUMS:
RNGestureHandler: e5c7cab5f214503dcefd6b2b0cefb050e1f51c4a
RNGoogleSignin: c4381751eefd73c552b923ba347a9bfc6f18771c
RNReanimated: c1b56d030d1616239861534d9adb531f8cffab68
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
RNScreens: 522705f2e5c9d27efb17f24aceb2bf8335bc7b8e
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNWatch: 99637948ec9b5c9ec5a41920642594ad5ba07e80
Yoga: 17cd9a50243093b547c1e539c749928dd68152da
PODFILE CHECKSUM: 0e8826a5cb9ee147354a83321ecb3104132f510b
PODFILE CHECKSUM: bef1335067eaa4e8c558b1248f8ab3948de855bc
COCOAPODS: 1.11.3
COCOAPODS: 1.11.2

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>22.2.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>22.2.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>22.2.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>22.2.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -24,14 +24,8 @@
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */; };
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */; };
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */; };
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */; };
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */; settings = {ATTRIBUTES = (Public, ); }; };
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */; };
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */; };
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */; };
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
@@ -85,15 +79,9 @@
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewController.h; sourceTree = "<group>"; };
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetViewController.m; sourceTree = "<group>"; };
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetRenderingView.h; sourceTree = "<group>"; };
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetRenderingView.m; sourceTree = "<group>"; };
4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiAudioSession.h; sourceTree = "<group>"; };
4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiAudioSession.m; sourceTree = "<group>"; };
4ED4FFF52721BAE10074E620 /* JitsiAudioSession+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiAudioSession+Private.h"; sourceTree = "<group>"; };
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JitsiMeetView+Private.m"; sourceTree = "<group>"; };
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.release.xcconfig"; sourceTree = "<group>"; };
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
@@ -106,6 +94,7 @@
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitListener.swift; sourceTree = "<group>"; };
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalAPI.h; sourceTree = "<group>"; };
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = "<group>"; };
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = "<group>"; };
@@ -205,17 +194,12 @@
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */,
DE81A2DD2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */,
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */,
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */,
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */,
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */,
DE81A2D72316AC7600AE1940 /* LogBridge.m */,
DE65AAC92317FFCD00290BEC /* LogUtils.h */,
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
DEAFA778229EAD520033A7FA /* RNRootView.m */,
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
C6A3426B204F127900E062DD /* picture-in-picture */,
@@ -300,14 +284,11 @@
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */,
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */,
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
DE81A2D42316AC4D00AE1940 /* JitsiMeetLogger.h in Headers */,
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */,
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */,
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */,
C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */,
C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */,
@@ -468,7 +449,6 @@
files = (
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
DE81A2DF2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m in Sources */,
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */,
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */,
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
DE81A2D92316AC7600AE1940 /* LogBridge.m in Sources */,
@@ -485,11 +465,9 @@
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */,
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,

View File

@@ -16,8 +16,6 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
- (void)sendHangUp;
@@ -29,6 +27,5 @@ static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
- (void)closeChat;
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
- (void)sendSetVideoMuted:(BOOL)muted;
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
@end

View File

@@ -15,6 +15,7 @@
*/
#import "ExternalAPI.h"
#import "JitsiMeetView+Private.h"
// Events
static NSString * const hangUpAction = @"org.jitsi.meet.HANG_UP";
@@ -26,7 +27,6 @@ static NSString * const openChatAction = @"org.jitsi.meet.OPEN_CHAT";
static NSString * const closeChatAction = @"org.jitsi.meet.CLOSE_CHAT";
static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSAGE";
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
static NSString * const setClosedCaptionsEnabledAction = @"org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED";
@implementation ExternalAPI
@@ -49,8 +49,7 @@ RCT_EXPORT_MODULE();
@"OPEN_CHAT": openChatAction,
@"CLOSE_CHAT": closeChatAction,
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
@"SET_VIDEO_MUTED" : setVideoMutedAction,
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction
@"SET_VIDEO_MUTED" : setVideoMutedAction
};
};
@@ -74,8 +73,7 @@ RCT_EXPORT_MODULE();
openChatAction,
closeChatAction,
sendChatMessageAction,
setVideoMutedAction,
setClosedCaptionsEnabledAction
setVideoMutedAction
];
}
@@ -88,15 +86,33 @@ RCT_EXPORT_MODULE();
* @param scope
*/
RCT_EXPORT_METHOD(sendEvent:(NSString *)name
data:(NSDictionary *)data) {
data:(NSDictionary *)data
scope:(NSString *)scope) {
// The JavaScript App needs to provide uniquely identifying information to
// the native ExternalAPI module so that the latter may match the former
// to the native JitsiMeetView which hosts it.
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
if (!view) {
return;
}
id delegate = view.delegate;
if (!delegate) {
return;
}
if ([name isEqual: @"PARTICIPANTS_INFO_RETRIEVED"]) {
[self onParticipantsInfoRetrieved: data];
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:sendEventNotificationName
object:nil
userInfo:@{@"name": name, @"data": data}];
SEL sel = NSSelectorFromString([self methodNameFromEventName:name]);
if (sel && [delegate respondsToSelector:sel]) {
[delegate performSelector:sel withObject:data];
}
}
- (void) onParticipantsInfoRetrieved:(NSDictionary *)data {
@@ -108,6 +124,28 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
[participantInfoCompletionHandlers removeObjectForKey:completionHandlerId];
}
/**
* Converts a specific event name i.e. redux action type description to a
* method name.
*
* @param eventName The event name to convert to a method name.
* @return A method name constructed from the specified `eventName`.
*/
- (NSString *)methodNameFromEventName:(NSString *)eventName {
NSMutableString *methodName
= [NSMutableString stringWithCapacity:eventName.length];
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
if (c.length) {
[methodName appendString:
methodName.length ? c.capitalizedString : c.lowercaseString];
}
}
[methodName appendString:@":"];
return methodName;
}
- (void)sendHangUp {
[self sendEventWithName:hangUpAction body:nil];
}
@@ -167,10 +205,5 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
[self sendEventWithName:setVideoMutedAction body:data];
}
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled {
NSDictionary *data = @{ @"enabled": [NSNumber numberWithBool:enabled]};
[self sendEventWithName:setClosedCaptionsEnabledAction body:data];
}
@end

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>5.1.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -15,8 +15,6 @@
*/
#import <Intents/Intents.h>
#import <RNGoogleSignin/RNGoogleSignin.h>
#import <WebRTC/RTCLogging.h>
#import "Dropbox.h"
#import "JitsiMeet+Private.h"
@@ -27,6 +25,9 @@
#import "RNSplashScreen.h"
#import "ScheenshareEventEmiter.h"
#import <RNGoogleSignin/RNGoogleSignin.h>
#import <WebRTC/RTCLogging.h>
@implementation JitsiMeet {
RCTBridgeWrapper *_bridgeWrapper;
NSDictionary *_launchOptions;
@@ -86,12 +87,8 @@
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *))restorationHandler {
JitsiMeetConferenceOptions *options = [self optionsFromUserActivity:userActivity];
if (options) {
[JitsiMeetView updateProps:[options asProps]];
return true;
}
return false;
return options && [JitsiMeetView setPropsInViews:[options asProps]];
}
- (BOOL)application:(UIApplication *)app
@@ -115,9 +112,8 @@
JitsiMeetConferenceOptions *conferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.room = [url absoluteString];
}];
[JitsiMeetView updateProps:[conferenceOptions asProps]];
return true;
return [JitsiMeetView setPropsInViews:[conferenceOptions asProps]];
}
#pragma mark - Utility methods

View File

@@ -1,30 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <UIKit/UIKit.h>
#import "JitsiMeetViewDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface JitsiMeetRenderingView : UIView
@property (nonatomic, assign) BOOL isPiPEnabled;
- (void)setProps:(NSDictionary *_Nonnull)newProps;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,83 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <mach/mach_time.h>
#import "JitsiMeetRenderingView.h"
#import "ReactUtils.h"
#import "RNRootView.h"
#import "JitsiMeet+Private.h"
/**
* Backwards compatibility: turn the boolean prop into a feature flag.
*/
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
@interface JitsiMeetRenderingView ()
@end
@implementation JitsiMeetRenderingView {
/**
* React Native view where the entire content will be rendered.
*/
RNRootView *rootView;
}
/**
* Passes the given props to the React Native application. The props which we pass
* are a combination of 3 different sources:
*
* - JitsiMeet.defaultConferenceOptions
* - This function's parameters
* - Some extras which are added by this function
*/
- (void)setProps:(NSDictionary *_Nonnull)newProps {
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
// Set the PiP flag if it wasn't manually set.
NSMutableDictionary *featureFlags = props[@"flags"];
// TODO: temporary implementation
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
featureFlags[PiPEnabledFeatureFlag] = @(self.isPiPEnabled);
}
// This method is supposed to be imperative i.e. a second
// invocation with one and the same URL is expected to join the respective
// conference again if the first invocation was followed by leaving the
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented below introduces imperativeness
// in React Component props by defining a unique value per invocation.
props[@"timestamp"] = @(mach_absolute_time());
if (rootView) {
// Update props with the new URL.
rootView.appProperties = props;
} else {
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
rootView = [[RNRootView alloc] initWithBridge:bridge
moduleName:@"App"
initialProperties:props];
rootView.backgroundColor = self.backgroundColor;
// Add rootView as a subview which completely covers this one.
[rootView setFrame:[self bounds]];
rootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:rootView];
}
}
@end

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2022-present 8x8, Inc.
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +15,11 @@
* limitations under the License.
*/
#import <JitsiMeetSDK/JitsiMeetSDK.h>
#import "JitsiMeetView.h"
NS_ASSUME_NONNULL_BEGIN
@interface JitsiMeetView ()
static NSString * const updateViewPropsNotificationName = @"org.jitsi.meet.UpdateViewProps";
@interface JitsiMeetView (Private)
+ (void)updateProps:(NSDictionary *_Nonnull)newProps;
+ (instancetype _Nullable)viewForExternalAPIScope:(NSString *_Nonnull)externalAPIScope;
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,25 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "JitsiMeetView+Private.h"
@implementation JitsiMeetView (Private)
+ (void)updateProps:(NSDictionary *_Nonnull)newProps {
[[NSNotificationCenter defaultCenter] postNotificationName:updateViewPropsNotificationName object:nil userInfo:@{@"props": newProps}];
}
@end

View File

@@ -45,6 +45,5 @@
- (void)closeChat;
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
- (void)setVideoMuted:(BOOL)muted;
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
@end

View File

@@ -20,22 +20,43 @@
#import "ExternalAPI.h"
#import "JitsiMeet+Private.h"
#import "JitsiMeetConferenceOptions+Private.h"
#import "JitsiMeetView.h"
#import "JitsiMeetViewController.h"
#import "JitsiMeetView+Private.h"
#import "ReactUtils.h"
#import "RNRootView.h"
@interface JitsiMeetView ()
@property (nonatomic, strong) JitsiMeetViewController *jitsiMeetViewController;
@property (nonatomic, strong) UINavigationController *navController;
@property (nonatomic, readonly) BOOL isPiPEnabled;
/**
* Backwards compatibility: turn the boolean prop into a feature flag.
*/
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
@end
@implementation JitsiMeetView
@implementation JitsiMeetView {
/**
* The unique identifier of this `JitsiMeetView` within the process for the
* purposes of `ExternalAPI`. The name scope was inspired by postis which we
* use on Web for the similar purposes of the iframe-based external API.
*/
NSString *externalAPIScope;
@dynamic isPiPEnabled;
/**
* React Native view where the entire content will be rendered.
*/
RNRootView *rootView;
}
/**
* The `JitsiMeetView`s associated with their `ExternalAPI` scopes (i.e. unique
* identifiers within the process).
*/
static NSMapTable<NSString *, JitsiMeetView *> *views;
/**
* This gets called automagically when the program starts.
*/
__attribute__((constructor))
static void initializeViewsMap() {
views = [NSMapTable strongToWeakObjectsMapTable];
}
#pragma mark Initializers
@@ -66,10 +87,6 @@
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
/**
* Internal initialization:
*
@@ -77,105 +94,145 @@
* - initializes the external API scope
*/
- (void)initWithXXX {
self.jitsiMeetViewController = [[JitsiMeetViewController alloc] init];
self.jitsiMeetViewController.view.frame = [self bounds];
[self addSubview:self.jitsiMeetViewController.view];
[self registerObservers];
// Hook this JitsiMeetView into ExternalAPI.
externalAPIScope = [NSUUID UUID].UUIDString;
[views setObject:self forKey:externalAPIScope];
// Set a background color which is in accord with the JavaScript and Android
// parts of the application and causes less perceived visual flicker than
// the default background color.
self.backgroundColor
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
}
#pragma mark API
- (void)join:(JitsiMeetConferenceOptions *)options {
[self.jitsiMeetViewController join:options withPiP:self.isPiPEnabled];
[self setProps:options == nil ? @{} : [options asProps]];
}
- (void)leave {
[self.jitsiMeetViewController leave];
[self setProps:@{}];
}
- (void)hangUp {
[self.jitsiMeetViewController hangUp];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendHangUp];
}
- (void)setAudioMuted:(BOOL)muted {
[self.jitsiMeetViewController setAudioMuted:muted];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendSetAudioMuted:muted];
}
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
[self.jitsiMeetViewController sendEndpointTextMessage:message :to];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendEndpointTextMessage:message :to];
}
- (void)toggleScreenShare:(BOOL)enabled {
[self.jitsiMeetViewController toggleScreenShare:enabled];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI toggleScreenShare:enabled];
}
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
[self.jitsiMeetViewController retrieveParticipantsInfo:completionHandler];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI retrieveParticipantsInfo:completionHandler];
}
- (void)openChat:(NSString*)to {
[self.jitsiMeetViewController openChat:to];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI openChat:to];
}
- (void)closeChat {
[self.jitsiMeetViewController closeChat];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI closeChat];
}
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
[self.jitsiMeetViewController sendChatMessage:message :to];
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendChatMessage:message :to];
}
- (void)setVideoMuted:(BOOL)muted {
[self.jitsiMeetViewController setVideoMuted:muted];
}
- (void)setClosedCaptionsEnabled:(BOOL)enabled {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendSetClosedCaptionsEnabled:enabled];
[externalAPI sendSetVideoMuted:muted];
}
#pragma mark Private
#pragma mark Private methods
- (BOOL)isPiPEnabled {
return self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
}
/**
* Passes the given props to the React Native application. The props which we pass
* are a combination of 3 different sources:
*
* - JitsiMeet.defaultConferenceOptions
* - This function's parameters
* - Some extras which are added by this function
*/
- (void)setProps:(NSDictionary *_Nonnull)newProps {
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
- (void)registerObservers {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSendEventNotification:) name:sendEventNotificationName object:nil];
}
// Set the PiP flag if it wasn't manually set.
NSMutableDictionary *featureFlags = props[@"flags"];
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
featureFlags[PiPEnabledFeatureFlag]
= [NSNumber numberWithBool:
self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]];
}
- (void)handleSendEventNotification:(NSNotification *)notification {
NSString *eventName = notification.userInfo[@"name"];
NSString *eventData = notification.userInfo[@"data"];
SEL sel = NSSelectorFromString([self methodNameFromEventName:eventName]);
props[@"externalAPIScope"] = externalAPIScope;
if (sel && [self.delegate respondsToSelector:sel]) {
[self.delegate performSelector:sel withObject:eventData];
// This method is supposed to be imperative i.e. a second
// invocation with one and the same URL is expected to join the respective
// conference again if the first invocation was followed by leaving the
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented below introduces imperativeness
// in React Component props by defining a unique value per invocation.
props[@"timestamp"] = @(mach_absolute_time());
if (rootView) {
// Update props with the new URL.
rootView.appProperties = props;
} else {
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
rootView
= [[RNRootView alloc] initWithBridge:bridge
moduleName:@"App"
initialProperties:props];
rootView.backgroundColor = self.backgroundColor;
// Add rootView as a subview which completely covers this one.
[rootView setFrame:[self bounds]];
rootView.autoresizingMask
= UIViewAutoresizingFlexibleWidth
| UIViewAutoresizingFlexibleHeight;
[self addSubview:rootView];
}
}
/**
* Converts a specific event name i.e. redux action type description to a
* method name.
*
* @param eventName The event name to convert to a method name.
* @return A method name constructed from the specified `eventName`.
*/
- (NSString *)methodNameFromEventName:(NSString *)eventName {
NSMutableString *methodName
= [NSMutableString stringWithCapacity:eventName.length];
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps {
BOOL handled = NO;
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
if (c.length) {
[methodName appendString:
methodName.length ? c.capitalizedString : c.lowercaseString];
}
}
[methodName appendString:@":"];
if (views) {
for (NSString *externalAPIScope in views) {
JitsiMeetView *view
= [self viewForExternalAPIScope:externalAPIScope];
return methodName;
if (view) {
[view setProps:newProps];
handled = YES;
}
}
}
return handled;
}
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
return [views objectForKey:externalAPIScope];
}
@end

View File

@@ -1,38 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <UIKit/UIKit.h>
#import "JitsiMeetConferenceOptions.h"
NS_ASSUME_NONNULL_BEGIN
@interface JitsiMeetViewController : UIViewController
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP;
- (void)leave;
- (void)hangUp;
- (void)setAudioMuted:(BOOL)muted;
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
- (void)toggleScreenShare:(BOOL)enabled;
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
- (void)openChat:(NSString*)to;
- (void)closeChat;
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
- (void)setVideoMuted:(BOOL)muted;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,127 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "JitsiMeetViewController.h"
#import "JitsiMeet+Private.h"
#import "JitsiMeetConferenceOptions+Private.h"
#import "JitsiMeetRenderingView.h"
#import "JitsiMeetView+Private.h"
@interface JitsiMeetViewController ()
@property (strong, nonatomic) JitsiMeetRenderingView *view;
@end
@implementation JitsiMeetViewController
@dynamic view;
- (instancetype)init {
self = [super init];
if (self) {
[self registerObservers];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)loadView {
[super loadView];
self.view = [[JitsiMeetRenderingView alloc] init];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set a background color which is in accord with the JavaScript and Android
// parts of the application and causes less perceived visual flicker than
// the default background color.
self.view.backgroundColor = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
}
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP {
self.view.isPiPEnabled = enablePiP;
[self.view setProps:options == nil ? @{} : [options asProps]];
}
- (void)leave {
[self.view setProps:@{}];
}
- (void)hangUp {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendHangUp];
}
- (void)setAudioMuted:(BOOL)muted {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendSetAudioMuted:muted];
}
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendEndpointTextMessage:message :to];
}
- (void)toggleScreenShare:(BOOL)enabled {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI toggleScreenShare:enabled];
}
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI retrieveParticipantsInfo:completionHandler];
}
- (void)openChat:(NSString*)to {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI openChat:to];
}
- (void)closeChat {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI closeChat];
}
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendChatMessage:message :to];
}
- (void)setVideoMuted:(BOOL)muted {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendSetVideoMuted:muted];
}
#pragma mark Private
- (void)registerObservers {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdateViewPropsNotification:) name:updateViewPropsNotificationName object:nil];
}
- (void)handleUpdateViewPropsNotification:(NSNotification *)notification {
NSDictionary *props = [notification.userInfo objectForKey:@"props"];
[self.view setProps:props];
}
@end

View File

@@ -17,14 +17,14 @@
import UIKit
final class DragGestureController {
var insets: UIEdgeInsets = UIEdgeInsets.zero
var currentPosition: PiPViewCoordinator.Position? = nil
private var frameBeforeDragging: CGRect = CGRect.zero
private weak var view: UIView?
private lazy var panGesture: UIPanGestureRecognizer = {
UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gesture:)))
return UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gesture:)))
}()
func startDragListener(inView view: UIView) {
@@ -40,7 +40,7 @@ final class DragGestureController {
}
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
guard let view = view else { return }
guard let view = self.view else { return }
let translation = gesture.translation(in: view.superview)
let velocity = gesture.velocity(in: view.superview)
@@ -65,7 +65,7 @@ final class DragGestureController {
let velocityMagnitude = magnitude(vector: velocity)
let animationDuration = 0.5
let initialSpringVelocity =
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
@@ -75,7 +75,8 @@ final class DragGestureController {
initialSpringVelocity: initialSpringVelocity,
options: .curveLinear,
animations: {
view.frame = frame })
view.frame = frame
}, completion: nil)
default:
break
@@ -84,11 +85,9 @@ final class DragGestureController {
private func calculateFinalPosition() -> CGPoint {
guard
let view = view,
let view = self.view,
let bounds = view.superview?.frame
else {
return CGPoint.zero
}
else { return CGPoint.zero }
let currentSize = view.frame.size
let adjustedBounds = bounds.inset(by: insets)
@@ -110,26 +109,19 @@ final class DragGestureController {
goUp = location.y < bounds.midY
}
if (goLeft && goUp) {
currentPosition = .upperLeftCorner
}
let finalPosX: CGFloat =
goLeft
? adjustedBounds.origin.x
: bounds.size.width - insets.right - currentSize.width
let finalPosY: CGFloat =
goUp
? adjustedBounds.origin.y
: bounds.size.height - insets.bottom - currentSize.height
if (!goLeft && goUp) {
currentPosition = .upperRightCorner
}
if (!goLeft && !goUp) {
currentPosition = .lowerRightCorner
}
if (goLeft && !goUp) {
currentPosition = .lowerLeftCorner
}
return currentPosition!.getOriginIn(bounds: adjustedBounds, size: currentSize)
return CGPoint(x: finalPosX, y: finalPosY)
}
private func magnitude(vector: CGPoint) -> CGFloat {
sqrt(pow(vector.x, 2) + pow(vector.y, 2))
return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
}
}

View File

@@ -19,23 +19,16 @@ import UIKit
public typealias AnimationCompletion = (Bool) -> Void
public protocol PiPViewCoordinatorDelegate: class {
func exitPictureInPicture()
}
/// Coordinates the view state of a specified view to allow
/// to be presented in full screen or in a custom Picture in Picture mode.
/// This object will also provide the drag and tap interactions of the view
/// when is presented in Picture in Picture mode.
/// when is presented in Picure in Picture mode.
public class PiPViewCoordinator {
public enum Position {
case lowerRightCorner
case upperRightCorner
case lowerLeftCorner
case upperLeftCorner
}
/// Limits the boundaries of view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
left: 5,
@@ -46,15 +39,23 @@ public class PiPViewCoordinator {
}
}
public let initialPositionInSuperView: Position = .lowerRightCorner
public enum Position {
case lowerRightCorner
case upperRightCorner
case lowerLeftCorner
case upperLeftCorner
}
public var initialPositionInSuperview = Position.lowerRightCorner
// Unused. Remove on the next major release.
@available(*, deprecated, message: "The PiP window size is now fixed to 150px.")
public var c: CGFloat = 0.0
public weak var delegate: PiPViewCoordinatorDelegate?
private(set) var isInPiP: Bool = false // true if view is in PiP mode
private(set) var view: UIView
private var currentBounds: CGRect = CGRect.zero
@@ -65,13 +66,6 @@ public class PiPViewCoordinator {
public init(withView view: UIView) {
self.view = view
// Required because otherwise the view will not rotate correctly.
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Otherwise the enter/exit pip animation looks odd
// when pip window is bottom left, top left or top right,
// because the jitsi view content does not animate, but jumps to the new size immediately.
view.clipsToBounds = true
}
/// Configure the view to be always on top of all the contents
@@ -80,9 +74,7 @@ public class PiPViewCoordinator {
public func configureAsStickyView(withParentView parentView: UIView? = nil) {
guard
let parentView = parentView ?? UIApplication.shared.keyWindow
else {
return
}
else { return }
parentView.addSubview(view)
currentBounds = parentView.bounds
@@ -117,9 +109,6 @@ public class PiPViewCoordinator {
/// around screen, and add a button of top of the view to be able to exit mode
public func enterPictureInPicture() {
isInPiP = true
// Resizing is done by hand when in pip.
view.autoresizingMask = []
animateViewChange()
dragController.startDragListener(inView: view)
dragController.insets = dragBoundInsets
@@ -136,9 +125,6 @@ public class PiPViewCoordinator {
/// exit pip button, and disable the drag gesture
@objc public func exitPictureInPicture() {
isInPiP = false
// Enable autoresizing again, which got disabled for pip.
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
animateViewChange()
dragController.stopDragListener()
@@ -150,7 +136,7 @@ public class PiPViewCoordinator {
let exitSelector = #selector(toggleExitPiP)
tapGestureRecognizer?.removeTarget(self, action: exitSelector)
tapGestureRecognizer = nil
delegate?.exitPictureInPicture()
}
@@ -158,12 +144,6 @@ public class PiPViewCoordinator {
/// screen size changes
public func resetBounds(bounds: CGRect) {
currentBounds = bounds
// Is required because otherwise the pip window is buggy when rotating the device.
// When not in pip then autoresize will do the job.
if (isInPiP) {
view.frame = changeViewRect()
}
}
/// Stop the dragging gesture of the root view
@@ -189,6 +169,7 @@ public class PiPViewCoordinator {
}
// MARK: - Interactions
@objc private func toggleExitPiP() {
if exitPiPButton == nil {
// show button
@@ -205,43 +186,31 @@ public class PiPViewCoordinator {
}
}
func animateViewChange() {
// MARK: - Size calculation
private func animateViewChange() {
UIView.animate(withDuration: 0.25) {
self.view.frame = self.changeViewRect()
self.view.setNeedsLayout()
}
}
private func changeViewRect() -> CGRect {
let bounds = currentBounds
if !isInPiP {
guard isInPiP else {
return bounds
}
// resize to suggested ratio and position to the bottom right
let adjustedBounds = bounds.inset(by: dragBoundInsets)
let size = CGSize(width: 150, height: 150)
let origin = (dragController.currentPosition ?? initialPositionInSuperView).getOriginIn(bounds: adjustedBounds, size: size)
let origin = initialPositionFor(pipSize: size, bounds: adjustedBounds)
return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
}
// MARK: - Animation helpers
private func animateTransition(animations: @escaping () -> Void,
completion: AnimationCompletion?) {
UIView.animate(withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: animations,
completion: completion)
}
}
// MARK: -
extension PiPViewCoordinator.Position {
func getOriginIn(bounds: CGRect, size: CGSize) -> CGPoint {
switch self {
private func initialPositionFor(pipSize size: CGSize, bounds: CGRect) -> CGPoint {
switch initialPositionInSuperview {
case .lowerLeftCorner:
return CGPoint(x: bounds.minX, y: bounds.maxY - size.height)
case .lowerRightCorner:
@@ -252,4 +221,16 @@ extension PiPViewCoordinator.Position {
return CGPoint(x: bounds.maxX - size.width, y: bounds.minY)
}
}
// MARK: - Animation helpers
private func animateTransition(animations: @escaping () -> Void,
completion: AnimationCompletion?) {
UIView.animate(withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: animations,
completion: completion)
}
}

View File

@@ -116,7 +116,6 @@
},
"chromeExtensionBanner": {
"buttonText": "نزِّل إضافة متصفح كروم",
"buttonTextEdge": "قم بتثبيت ملحق Edge",
"close": "إغلق",
"dontShowAgain": "لا ترني هذه مرة أخرى",
"installExtensionText": "نزِّل الإضافة للدمج مع رزنامة غوغل ورزنامة أوفيس 365"
@@ -208,9 +207,6 @@
"selectADevice": "اختر جهازًا",
"testAudio": "اختبر الصوت"
},
"dialIn": {
"screenTitle": "ملخص الطلب"
},
"dialOut": {
"statusMessage": "{{status}} الآن"
},
@@ -661,8 +657,6 @@
"linkToSalesforceKey": "ربط هذا المُلتقى",
"linkToSalesforceProgress": "جارٍ ربط الاجتماع بـ Salesforce ...",
"linkToSalesforceSuccess": "تم ربط الاجتماع بـ Salesforce",
"localRecordingStarted": "بدأ {{name}} تسجيلًا محليًا.",
"localRecordingStopped": "أوقف {{name}} التسجيل المحلي.",
"me": "أنا",
"moderationInEffectCSDescription": "يرجى رفع اليد إذا كنت تريد مشاركة شاشتك.",
"moderationInEffectCSTitle": "تم حظر مشاركة الشاشة من قبل المشرف",
@@ -818,7 +812,6 @@
"initiated": "بدأ الاتصال",
"joinAudioByPhone": "انضم مع صوت من الجوال",
"joinMeeting": "انضم للمُلتقى",
"joinMeetingInLowBandwidthMode": "الانضمام في وضع النطاق الترددي المنخفض",
"joinWithoutAudio": "انضم دون صوت",
"keyboardShortcuts": "تفعيل اختصارات لوحة المفاتيح",
"linkCopied": "نُسِخ الرابط",
@@ -894,7 +887,6 @@
"limitNotificationDescriptionWeb": "نظرًا للضغط الكبير، سيقيَّد التسجيل إلى {{limit}} د، ولكن إن أردت التسجيل لمدة مفتوحة، جرِّب <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "لقد أنشأنا رابطًا لتسجيلك.",
"live": "مباشر",
"localRecordingWarning": "تأكد من تحديد علامة التبويب الحالية لاستخدام الفيديو والصوت الصحيحين. التسجيل محدود حاليًا بـ 1 كيغابايت ، أي حوالي 100 دقيقة.",
"loggedIn": "مُسجَّل باسم {{userName}}",
"off": "أوقِف التسجيل",
"offBy": "أوقَف {{name}} التسجيل",
@@ -902,7 +894,6 @@
"onBy": "بدأ {{name}} التسجيل",
"pending": "التحضير لتسجيل المُلتقى...",
"rec": "تسجيل",
"saveLocalRecording": "حفظ ملف التسجيل محليا",
"serviceDescription": "ستحفظ خدمة التسجيل الفيديو المستجل",
"serviceDescriptionCloud": "تسجيل سحابي",
"serviceDescriptionCloudInfo": "يتم مسح المُلتقيات المسجلة تلقائيًا بعد 24 ساعة من وقت التسجيل.",
@@ -910,7 +901,6 @@
"sessionAlreadyActive": "هذه الجلسة قيد التسجيل أو البث المباشر.",
"signIn": "دخول",
"signOut": "خروج",
"surfaceError": "الرجاء تحديد علامة التبويب الحالية.",
"unavailable": "عجبًا! {{serviceName}} غير متاحة حاليًا. نعمل على حل المشكلة. حاول مرة أخرى لاحقًا.",
"unavailableTitle": "التسجيل غير متاح",
"uploadToCloud": "تحميل إلى السحابة"
@@ -953,7 +943,6 @@
"playSounds": "تشغيل الصوت عند:",
"reactions": "ردود فعل المُلتقى",
"sameAsSystem": "مثل النظام ({{label}})",
"screenTitle": "إعدادات",
"selectAudioOutput": "خرج الصوت",
"selectCamera": "الكاميرا",
"selectMic": "المجهار (المايكروفون)",
@@ -979,7 +968,6 @@
"disableCrashReportingWarning": "أمتأكد من تعطيل تقارير الأعطال التقنية؟ ستسري الإعدادات الجديدة بعد إعادة تشغيل التطبيق",
"disableP2P": "تعطيل وضع واحد شخص-لشخص",
"displayName": "عرض الاسم",
"displayNamePlaceholderText": "على سبيل المثال: علي الحيدري",
"email": "البريد الإلكتروني",
"header": "الإعدادات",
"profileSection": "الملف الشخصي",
@@ -1038,7 +1026,6 @@
"chat": "اظهِر/اخفِ نافذة الدردشة",
"clap": "تصفيق",
"collapse": "قلّص",
"dock": "إرساء في النافذة الرئيسية",
"document": "اظهِر/اخفِ الملف المشارك",
"download": "نزِّل التطبيق",
"embedMeeting": "ضمِّن المُلتقى",
@@ -1089,7 +1076,6 @@
"tileView": "اظهِر/اخفِ عرض العنوان",
"toggleCamera": "بدِّل الكاميرا",
"toggleFilmstrip": "بدِّل وضع الشريط السينمائي (filmstrip)",
"undock": "فك في نافذة منفصلة",
"videoblur": "استعمل/اخرج من وضع تغبيش خلفية الفيديو",
"videomute": "بدِّل وضع اخفاء الفيديو"
},
@@ -1106,7 +1092,6 @@
"closeChat": "أغلق الدردشة",
"closeReactionsMenu": "إغلاق قائمة ردود الفعل",
"disableReactionSounds": "يمكنك تعطيل أصوات ردود الفعل لهذا المُلتقى",
"dock": "إرساء في النافذة الرئيسية",
"documentClose": "أغلق الملف المشارك",
"documentOpen": "افتح الملف المشارك",
"download": "نزِّل التطبيق",
@@ -1175,7 +1160,6 @@
"talkWhileMutedPopup": "أتحاول التحدث؟ المجهار مكتوم لديك.",
"tileViewToggle": "بدِّل عنوان العرض",
"toggleCamera": "بدِّل الكاميرا",
"undock": "فك في نافذة منفصلة",
"videoSettings": "اعدادات الفيديو",
"videomute": "استعمل / أوقف الكاميرا"
},

View File

@@ -31,7 +31,6 @@
},
"audioDevices": {
"bluetooth": "Bluetooth",
"car": "Àudio del cotxe",
"headphones": "Auriculars",
"none": "No hi ha disponible cap aparell d'àudio",
"phone": "Telèfon",
@@ -40,6 +39,9 @@
"audioOnly": {
"audioOnly": "Poca amplada de banda"
},
"blankPage": {
"meetingEnded": "La reunió ha acabat"
},
"breakoutRooms": {
"actions": {
"add": "Afegeix una sala de descans",
@@ -77,22 +79,10 @@
"refresh": "Actualitza l'agenda",
"today": "Avui"
},
"carmode": {
"actions": {
"leaveMeeting": "Abandona la reunió",
"selectSoundDevice": "Seleccioneu l'aparell d'àudio"
},
"labels": {
"buttonLabel": "Mode cotxe",
"title": "Mode conducció segura",
"videoStopped": "El vídeo està aturat"
}
},
"chat": {
"enter": "Entra a la sala",
"error": "Error: no s'ha enviat el missatge. Raó: {{error}}",
"fieldPlaceHolder": "Escriviu aquí el missatge",
"lobbyChatMessageTo": "Envia un missatge en la sala d'espera a {{recipient}}",
"message": "Missatge",
"messageAccessibleTitle": "{{user}} diu:",
"messageAccessibleTitleMe": "jo dic:",
@@ -218,15 +208,13 @@
"Remove": "Elimina",
"Share": "Comparteix",
"Submit": "Tramet",
"WaitForHostMsg": "La conferència encara no ha començat. Si en sou l'amfitrió, autentiqueu-vos. Altrament, espereu que arribi l'amfitrió.",
"WaitForHostMsg": "La conferència encara no ha començat. Si en sou l'amfitrió autentiqueu-vos. Altrament, espereu que arribi l'amfitrió.",
"WaitingForHostTitle": "S'està esperant l'amfitrió...",
"Yes": "Sí",
"accessibilityLabel": {
"liveStreaming": "Transmissió en directe"
},
"add": "Afegeix",
"addMeetingNote": "Afegiu una nota sobre aquesta reunió",
"addOptionalNote": "Afegeix una nota (opcional):",
"allow": "Permet",
"alreadySharedVideoMsg": "Un altre participant està compartint un vídeo. Aquesta conferència només permet compartir un vídeo a la vegada.",
"alreadySharedVideoTitle": "Només es permet un vídeo compartit a la vegada",
@@ -278,8 +266,6 @@
"kickParticipantDialog": "Esteu segur que voleu expulsar aquest participant?",
"kickParticipantTitle": "Voleu expulsar aquest participant?",
"kickTitle": "Ep! {{participantDisplayName}} us ha expulsat de la reunió",
"linkMeeting": "Enllaça la reunió",
"linkMeetingTitle": "Enllaça la reunió a Salesforce",
"liveStreaming": "Transmissió en directe",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "No és possible mentre l'enregistrament estigui actiu",
"liveStreamingDisabledTooltip": "No es pot iniciar la transmissió en directe.",
@@ -334,7 +320,6 @@
"popupError": "El vostre navegador bloca les finestres emergents d'aquest lloc. Habiliteu les finestres emergents a la configuració de seguretat del navegador i torneu-ho a intentar.",
"popupErrorTitle": "Finestres emergents blocades",
"readMore": "més",
"recentlyUsedObjects": "Els objectes que heu usat recentment",
"recording": "Enregistrament",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "No és possible mentre hi ha una transmissió en directe activa",
"recordingDisabledTooltip": "No es pot enregistrar.",
@@ -357,12 +342,6 @@
"screenSharingFailed": "Ep! Alguna cosa ha anat malament, no hem pogut iniciar la compartició de pantalla!",
"screenSharingFailedTitle": "La compartició de pantalla ha fallat!",
"screenSharingPermissionDeniedError": "Ep! Alguna cosa ha anat malament amb els permisos de compartició de pantalla. Torna a carregar-la i prova-ho una altra vegada.",
"searchInSalesforce": "Cerca a Salesforce",
"searchResults": "Resultats de la cerca({{count}})",
"searchResultsDetailsError": "Alguna cosa ha anat malament en recuperar les dades del propietari.",
"searchResultsError": "Alguna cosa ha anat malament en recuperar les dades.",
"searchResultsNotFound": "No s'ha trobat cap resultat.",
"searchResultsTryAgain": "Proveu usant paraules clau alternatives.",
"sendPrivateMessage": "Fa poc que heu rebut un missatge privat. Voleu respondre'l de forma privada, o voleu enviar el missatge al grup?",
"sendPrivateMessageCancel": "Envia'l al grup",
"sendPrivateMessageOk": "Envia'l en privat",
@@ -372,22 +351,20 @@
"sessionRestarted": "La trucada s'ha reiniciat a causa d'un problema de connexió.",
"shareAudio": "Continua",
"shareAudioTitle": "Com compartir l'àudio",
"shareAudioWarningD1": "cal que atureu la compartició de pantalla abans de compartir l'àudio.",
"shareAudioWarningD2": "cal que reinicieu la compartició de pantalla i marqueu l'opció «Comparteix l'àudio».",
"shareAudioWarningD1": "heu d'aturar la compartició de pantalla abans de compartir l'àudio.",
"shareAudioWarningD2": "heu de reiniciar la compartició de pantalla i marcar l'opció «Comparteix l'àudio».",
"shareAudioWarningH1": "Si voleu compartir només l'àudio:",
"shareAudioWarningTitle": "Cal que atureu la compartició de pantalla abans de compartir l'àudio",
"shareAudioWarningTitle": "Heu d'aturar la compartició de pantalla abans de compartir l'àudio",
"shareMediaWarningGenericH2": "Si voleu compartir la pantalla i l'àudio",
"shareScreenWarningD1": "heu d'aturar l'ús compartit d'àudio abans de compartir la pantalla.",
"shareScreenWarningD2": "heu d'aturar l'ús compartit d'àudio, iniciar l'ús compartit de la pantalla i marcar l'opció «Comparteix l'àudio».",
"shareScreenWarningH1": "Si només voleu compartir la pantalla:",
"shareScreenWarningTitle": "Cal que atureu l'ús compartit d'àudio abans de compartir la pantalla",
"shareScreenWarningTitle": "Heu d'aturar l'ús compartit d'àudio abans de compartir la pantalla",
"shareVideoLinkError": "Proporcioneu un enllaç de vídeo correcte.",
"shareVideoTitle": "Comparteix el vídeo",
"shareYourScreen": "Comparteix la pantalla",
"shareYourScreenDisabled": "S'ha inhabilitat la compartició de pantalla.",
"sharedVideoDialogError": "Error: URL no vàlid",
"sharedVideoLinkPlaceholder": "Enllaç de YouTube o enllaç directe del vídeo",
"start": "Inicia",
"startLiveStreaming": "Inicia la transmissió en directe",
"startRecording": "Inicia l'enregistrament",
"startRemoteControlErrorMessage": "S'ha produït un error en intentar iniciar la sessió de control remot!",
@@ -430,10 +407,6 @@
"veryBad": "Molt dolenta",
"veryGood": "Molt bona"
},
"giphy": {
"noResults": "No s'ha trobat cap resultat :(",
"search": "Cerca a GIPHY"
},
"helpView": {
"header": "Centre d'ajuda"
},
@@ -500,7 +473,6 @@
"focusLocal": "Focus al vostre vídeo",
"focusRemote": "Focus en el vídeo d'una altra persona",
"fullScreen": "Entra o surt de la pantalla completa",
"giphyMenu": "Mostra o amaga el menú GIPHY",
"keyboardShortcuts": "Dreceres de teclat",
"localRecording": "Mostra o amaga els controls d'enregistrament local",
"mute": "Silencia o activa el so",
@@ -555,7 +527,6 @@
"admitAll": "Admet tothom",
"allow": "Permet",
"backToKnockModeButton": "Demaneu per a unir-vos",
"chat": "Xat",
"dialogTitle": "Mode de sala d'espera",
"disableDialogContent": "El mode de sala d'espera es troba activat. Aquesta funcionalitat evita que els participants no desitjats puguin unir-se a la reunió. Voleu desactivar-ho?",
"disableDialogSubmit": "Desactiva",
@@ -568,7 +539,6 @@
"errorMissingPassword": "Introduïu la contrasenya de la reunió",
"invalidPassword": "La contrasenya no és vàlida",
"joinRejectedMessage": "La vostra sol·licitud ha estat rebutjada per un moderador.",
"joinRejectedTitle": "S'ha rebutjat la petició d'unir-s'hi.",
"joinTitle": "Entra a la reunió",
"joinWithPasswordMessage": "S'està intentant unir-s'hi amb contrasenya, espereu...",
"joiningMessage": "Us unireu a la reunió de seguida que algú accepti la sol·licitud",
@@ -577,8 +547,6 @@
"knockButton": "Demana d'unir-se",
"knockTitle": "Algú vol unir-se a la reunió",
"knockingParticipantList": "Llista de participants que piquen per a entrar",
"lobbyChatStartedNotification": "{{moderator}} ha començat un xat en la sala d'espera amb {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} ha començat un xat en la sala d'espera amb vós.",
"nameField": "Introduïu el vostre nom",
"notificationLobbyAccessDenied": "{{originParticipantName}} ha rebutjat l'entrada de {{targetParticipantName}}",
"notificationLobbyAccessGranted": "{{originParticipantName}} ha acceptat l'entrada de {{targetParticipantName}}",
@@ -641,7 +609,6 @@
"displayNotifications": "Mostra les notificacions sobre",
"focus": "Focus de la conferència",
"focusFail": "{{component}} no és disponible, torneu a intentar en {{ms}} segons",
"gifsMenu": "GIPHY",
"groupTitle": "Notificacions",
"hostAskedUnmute": "El moderador vol que parleu",
"invitedOneMember": "S'ha convidat {{name}}",
@@ -651,12 +618,6 @@
"leftOneMember": "{{name}} ha sortit de la reunió",
"leftThreePlusMembers": "{{name}} i molts d'altres han sortit de la reunió",
"leftTwoMembers": "{{first}} i {{second}} han sortit de la reunió",
"linkToSalesforce": "Enllaç a Salesforce",
"linkToSalesforceDescription": "Podeu enllaçar el resum de la reunió a un objecte Salesforce.",
"linkToSalesforceError": "No s'ha pogut enllaçar la reunió a Salesforce",
"linkToSalesforceKey": "Enllaça aquesta reunió",
"linkToSalesforceProgress": "S'està enllaçant la reunió a Salesforce...",
"linkToSalesforceSuccess": "La reunió s'ha enllaçat a Salesforce",
"me": "Jo",
"moderationInEffectCSDescription": "Aixequeu la mà si voleu compartir la pantalla.",
"moderationInEffectCSTitle": "El moderador ha blocat la compartició de pantalla",
@@ -680,8 +641,6 @@
"oldElectronClientDescription1": "Sembla que useu una versió antiga del client Jitsi Meet, que té vulnerabilitats de seguretat conegudes. Assegureu-vos d'actualitzar-lo",
"oldElectronClientDescription2": "última construcció",
"oldElectronClientDescription3": "ara!",
"participantWantsToJoin": "Vol unir-se a la reunió",
"participantsWantToJoin": "Volen unir-se a la reunió",
"passwordRemovedRemotely": "Un altre participant ha eliminat $t(lockRoomPasswordUppercase)",
"passwordSetRemotely": "Un altre participant ha establert la $t(lockRoomPassword)",
"raiseHandAction": "Aixeca la mà",
@@ -689,7 +648,7 @@
"raisedHands": "{{participantName}} i {{raisedHands}} persones més",
"reactionSounds": "Desactiva el so",
"reactionSoundsForAll": "Desactiva el so per a tothom",
"screenShareNoAudio": "No s'ha marcat la compartició d'àudio en la pantalla de selecció de la finestra.",
"screenShareNoAudio": "La casella Comparteix l'àudio no s'ha marcat a la pantalla de selecció de la finestra.",
"screenShareNoAudioTitle": "No s'ha pogut compartir l'àudio del sistema!",
"selfViewTitle": "Sempre podeu activar la vista pròpia des de la configuració",
"somebody": "Algú",
@@ -701,9 +660,7 @@
"videoMutedRemotelyDescription": "Sempre la podeu activar de nou.",
"videoMutedRemotelyTitle": "{{participantDisplayName}} us ha apagat el vídeo",
"videoUnmuteBlockedDescription": "L'activació de la càmera i la compartició d'escriptori s'han blocat temporalment per limitacions del sistema.",
"videoUnmuteBlockedTitle": "L'activació de la càmera i la compartició d'escriptori estan blocades!",
"viewLobby": "Mostra la sala d'espera",
"waitingParticipants": "{{waitingParticipants}} persones"
"videoUnmuteBlockedTitle": "L'activació de la càmera i la compartició d'escriptori estan blocades!"
},
"participantsPane": {
"actions": {
@@ -735,7 +692,6 @@
},
"passwordDigitsOnly": "Fins a {{number}} dígits",
"passwordSetRemotely": "Establerta per un altre participant",
"pinnedParticipant": "El participant està fixat",
"polls": {
"answer": {
"skip": "Omet",
@@ -792,7 +748,7 @@
"veryPoorConnection": "És esperable que la qualitat de la trucada sigui realment terrible.",
"videoFreezing": "És esperable que el vídeo es congeli, passi a negre i aparegui pixelat.",
"videoHighQuality": "És esperable que el vídeo tingui una bona qualitat.",
"videoLowQuality": "És esperable que el vídeo sigui de poca qualitat en termes de fotogrames per segon i resolució.",
"videoLowQuality": "És esperable que el vídeo sigui de poca qualitat en termes de marcs per segon i resolució.",
"videoTearing": "És esperable que el vídeo aparegui pixelat o amb defectes visuals."
},
"copyAndShare": "Copia i comparteix l'enllaç de la reunió",
@@ -851,18 +807,6 @@
},
"raisedHand": "Vull parlar",
"raisedHandsLabel": "Nombre de mans aixecades",
"record": {
"already": {
"linked": "La reunió ja està enllaçada amb aquest objecte de Salesforce."
},
"type": {
"account": "Compte",
"contact": "Contacte",
"lead": "Principal",
"opportunity": "Oportunitat",
"owner": "Propietari"
}
},
"recording": {
"authDropboxText": "Puja a Dropbox",
"availableSpace": "Espai disponible: {{spaceLeft}} MB (aproximadament {{duration}} minuts d'enregistrament)",
@@ -876,12 +820,7 @@
"expandedOn": "S'està enregistrant la reunió.",
"expandedPending": "S'ha iniciat l'enregistrament...",
"failedToStart": "No s'ha pogut iniciar l'enregistrament",
"fileSharingdescription": "Compartiu l'enllaç de l'enregistrament de la reunió amb els participants",
"highlight": "Destaca",
"highlightMoment": "Destaca el moment",
"highlightMomentDisabled": "Podeu destacar moment en iniciar-se l'enregistrament",
"highlightMomentSuccess": "Moment destacat",
"highlightMomentSucessDescription": "S'ha afegit el moment destacat al resum de la reunió.",
"fileSharingdescription": "Comparteix l'enregistrament amb els participants de la reunió",
"inProgress": "L'enregistrament o la transmissió en directe és en progrés",
"limitNotificationDescriptionNative": "A causa de la gran demanda, el vostre enregistrament es limitarà a {{limit}} min. Per a enregistraments il·limitats, proveu <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "A causa de la gran demanda, l'enregistrament es limitarà a {{limit}} min. Per a enregistraments il·limitats, proveu <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -896,7 +835,6 @@
"rec": "ENREG",
"serviceDescription": "El servei d'enregistrament desarà el vostre enregistrament",
"serviceDescriptionCloud": "Enregistrament al núvol",
"serviceDescriptionCloudInfo": "Les reunions enregistrades s'esborren automàticament 24 hores després de l'enregistrament.",
"serviceName": "Servei d'enregistrament",
"sessionAlreadyActive": "Aquesta sessió ja s'està enregistrant o emetent en directe.",
"signIn": "Inicia la sessió",
@@ -905,7 +843,6 @@
"unavailableTitle": "L'enregistrament no és disponible",
"uploadToCloud": "Puja al núvol"
},
"screenshareDisplayName": "Pantalla de: {{name}}",
"sectionList": {
"pullToRefresh": "Estireu per a actualitzar"
},
@@ -925,14 +862,13 @@
},
"desktopShareFramerate": "Velocitat de fotogrames en la compartició d'escriptori",
"desktopShareHighFpsWarning": "Una velocitat de fotogrames més alta per a compartir escriptori pot afectar l'amplada de banda. Heu de reiniciar la compartició de pantalla perquè la nova configuració tingui efecte.",
"desktopShareWarning": "Cal que reinicieu la compartició de pantalla perquè la nova configuració tingui efecte.",
"desktopShareWarning": "Heu de reiniciar la compartició de pantalla perquè la nova configuració tingui efecte.",
"devices": "Aparells",
"followMe": "Tothom que em segueix",
"framesPerSecond": "fotogrames per segon",
"framesPerSecond": "marcs per segon",
"incomingMessage": "Missatge entrant",
"language": "Llengua",
"loggedIn": "Sessió iniciada com a {{name}}",
"maxStageParticipants": "El nombre màxim de participants que es poden fixar en la escena principal",
"microphones": "Micròfons",
"moderator": "Moderador",
"more": "Més",
@@ -985,7 +921,6 @@
"speakerStats": {
"angry": "Enuig",
"disgusted": "Disgust",
"displayEmotions": "Mostra les emocions",
"fearful": "Temor",
"happy": "Felicitat",
"hours": "{{count}}h",
@@ -1020,19 +955,16 @@
"boo": "Esbroncada",
"breakoutRoom": "Entra o surt de la sala de descans",
"callQuality": "Gestiona la qualitat de la trucada",
"carmode": "Mode cotxe",
"cc": "Activa o desactiva els subtítols",
"chat": "Obre o tanca el xat",
"clap": "Picament de mans",
"collapse": "Col·lapsa",
"dock": "Acobla a la finestra principal",
"document": "Activa o desactiva el document compartit",
"download": "Baixeu les nostres aplicacions",
"embedMeeting": "Insereix la reunió",
"expand": "Expandeix",
"feedback": "Deixa comentaris",
"fullScreen": "Activa o desactiva la pantalla completa",
"giphy": "Mostra o amaga el menú GIPHY",
"grantModerator": "Concedir drets de moderador",
"hangup": "Surt de la reunió",
"help": "Ajuda",
@@ -1040,7 +972,6 @@
"kick": "Expulsa el participant",
"laugh": "Riure",
"like": "Polzes amunt",
"linkToSalesforce": "Enllaç a Salesforce",
"lobbyButton": "Activa o desactiva la sala d'espera",
"localRecording": "Activa o desactiva els controls d'enregistrament local",
"lockRoom": "Activa o desactiva la contrasenya de la reunió",
@@ -1063,11 +994,10 @@
"remoteVideoMute": "Desactiva la càmera del participant",
"security": "Opcions de seguretat",
"selectBackground": "Trieu un fons",
"selfView": "Mostra o amaga la visualització d'un mateix",
"shareRoom": "Convida-hi algú",
"shareYourScreen": "Inicia o atura la compartició de pantalla",
"shareaudio": "Comparteix l'àudio",
"sharedvideo": "Mostra o amaga la compartició de vídeo",
"sharedvideo": "Activa o desactiva la compartició de vídeo",
"shortcuts": "Activa o desactiva les dreceres",
"show": "Mostra-ho en l'escena",
"silence": "Silenci",
@@ -1076,7 +1006,6 @@
"tileView": "Activa o desactiva el mode mosaic",
"toggleCamera": "Activa o desactiva la càmera",
"toggleFilmstrip": "Mostra o amaga la cinta",
"undock": "Desacobla en una finestra separada",
"videoblur": "Activa o desactiva el desenfocament del vídeo",
"videomute": "Activa o desactiva la càmera"
},
@@ -1093,7 +1022,6 @@
"closeChat": "Tanca el xat",
"closeReactionsMenu": "Tanca el menú de reaccions",
"disableReactionSounds": "Podeu desactivar els sons de reacció per a aquesta reunió",
"dock": "Acobla en la finestra principal",
"documentClose": "Tanca el document compartit",
"documentOpen": "Obre el document compartit",
"download": "Baixeu les nostres aplicacions",
@@ -1104,7 +1032,6 @@
"exitFullScreen": "Surt de la pantalla completa",
"exitTileView": "Surt del mode mosaic",
"feedback": "Deixa comentaris",
"giphy": "Mostra o amaga el menú GIPHY",
"hangup": "Surt la reunió",
"help": "Ajuda",
"invite": "Convida-hi persones",
@@ -1112,7 +1039,6 @@
"laugh": "Riure",
"leaveBreakoutRoom": "Surt de la sala de descans",
"like": "Polzes amunt",
"linkToSalesforce": "Enllaç a Salesforce",
"lobbyButtonDisable": "Desactiva el mode de sala d'espera",
"lobbyButtonEnable": "Activa el mode de sala d'espera",
"login": "Inicia sessió",
@@ -1162,7 +1088,6 @@
"talkWhileMutedPopup": "Intenteu parlar? Esteu silenciat.",
"tileViewToggle": "Activa o desactiva el mode mosaic",
"toggleCamera": "Activa o desactiva la càmera",
"undock": "Desacobla en una finestra principal",
"videoSettings": "Paràmetres de vídeo",
"videomute": "Inicia o atura la càmera"
},
@@ -1233,12 +1158,8 @@
"moderator": "Moderador",
"mute": "El participant està silenciat",
"muted": "Silenciat",
"pinToStage": "Fixa a l'escena",
"remoteControl": "Inicia o atura el control remot",
"screenSharing": "El participant està compartint la pantalla",
"show": "Mostra-ho en l'escena",
"showSelfView": "Mostra la visualització d'un mateix",
"unpinFromStage": "Deixa de fixar",
"videoMuted": "La càmera està desactivada",
"videomute": "El participant ha aturat la càmera"
},

View File

@@ -9,13 +9,13 @@
"countryNotSupported": "Non supportiamo ancora questa destinazione.",
"countryReminder": "Stai chiamando fuori dagli Stati Uniti? Assicurati d'inserire il prefisso internazionale!",
"defaultEmail": "Tua email di default",
"disabled": "Non puoi invitare partecipanti.",
"failedToAdd": "L'aggiunta di nuovi partecipanti è fallita",
"disabled": "Non puoi invitare persone.",
"failedToAdd": "L'aggiunta di nuove persone è fallita",
"footerText": "La chiamata all'esterno è disabilitata.",
"googleEmail": "Email Google",
"inviteMoreHeader": "Sei l'unico presente nella riunione",
"inviteMoreMailSubject": "Unisciti alla riunione {{appName}}",
"inviteMorePrompt": "Invita altri partecipanti",
"inviteMorePrompt": "Invita altre persone",
"linkCopied": "Collegamento copiato negli appunti",
"noResults": "Nessun risultato corrispondente",
"outlookEmail": "Email Outlook",
@@ -26,7 +26,7 @@
"shareStream": "Condividi il collegamento alla diretta",
"sipAddresses": "indirizzi SIP",
"telephone": "Telefono: {{number}}",
"title": "Invita partecipanti a questa riunione",
"title": "Invita persone a questa riunione",
"yahooEmail": "Email Yahoo"
},
"audioDevices": {
@@ -77,17 +77,6 @@
"refresh": "Aggiorna calendario",
"today": "Oggi"
},
"carmode": {
"actions": {
"leaveMeeting": " Lascia riunione",
"selectSoundDevice": "Scegli audio"
},
"labels": {
"buttonLabel": "Modalità in auto",
"title": "Modalità guida sicura",
"videoStopped": "Il tuo video è fermo"
}
},
"chat": {
"enter": "Entra nella conversazione",
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
@@ -112,7 +101,7 @@
},
"title": "Conversazione",
"titleWithPolls": "Conversazione",
"you": "te"
"you": "tu"
},
"chromeExtensionBanner": {
"buttonText": "Installa l'estensione Chrome",
@@ -315,10 +304,10 @@
"muteEveryonesVideoDialogOk": "Spegni",
"muteEveryonesVideoTitle": "Vuoi spegnere le videocamere di tutti?",
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Spegni audio",
"muteParticipantButton": "Spegni microfono",
"muteParticipantsVideoBody": "Una volta spenta la videocamera non potrai riaccenderla, ma lui potrà riattivarla in qualsiasi momento.",
"muteParticipantsVideoBodyModerationOn": "Non potrai riaccendere le videocamere, né potranno loro.",
"muteParticipantsVideoButton": "Spegni video",
"muteParticipantsVideoButton": "Spegni videocamera",
"muteParticipantsVideoDialog": "Sei sicuro di voler spegnere la videocamera di questo partecipante? Lui potrà riattivarla in ogni momento.",
"muteParticipantsVideoDialogModerationOn": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on and neither will they.",
"muteParticipantsVideoTitle": "Vuoi spegnere la videocamera di questo partecipante?",
@@ -474,10 +463,10 @@
"moreNumbers": "Più numeri",
"noNumbers": "Nessun numero da chiamare.",
"noPassword": "Nessuna",
"noRoom": "Non è stata specificata nessuna riunione da chiamare.",
"noRoom": "Non è stata specificata nessuna stanza da chiamare.",
"numbers": "Numeri da chiamare",
"password": "$t(lockRoomPasswordUppercase):",
"sip": "Indirizzo SIP",
"sip": "SIP address",
"title": "Condividi",
"tooltip": "Invia il collegamento e i numeri telefonici di questa riunione"
},
@@ -629,21 +618,21 @@
"me": "io",
"notify": {
"OldElectronAPPTitle": "Falla di sicurezza!",
"allowAction": "Autorizza",
"allowAction": "Permetti",
"allowedUnmute": "Puoi accendere il microfono, avviare la videocamera, o condividere il tuo schermo.",
"audioUnmuteBlockedDescription": "Lo sblocco dei microfoni è stato temporaneamente bloccato per limiti del sistema.",
"audioUnmuteBlockedDescription": "Lo sblocco dei microfoni è stato temporaneament bloccato per limiti del sistema.",
"audioUnmuteBlockedTitle": "Riattivazione dei microfoni bloccata!",
"chatMessages": "Messaggi delle conversazioni",
"connectedOneMember": "{{name}} è entrato in riunione",
"connectedThreePlusMembers": "{{name}} e altri {{count}} sono entrati in riunione",
"connectedTwoMembers": "{{first}} e {{second}} sono entrati in riunione",
"connectedOneMember": "{{name}} si è connesso",
"connectedThreePlusMembers": "{{name}} e altri {{count}} si sono connessi",
"connectedTwoMembers": "{{first}} e {{second}} si sono connessi",
"disconnected": "disconnesso",
"displayNotifications": "Mostra le notifiche per",
"focus": "Focus su riunione",
"focusFail": "{{component}} non disponibile - riprovo tra {{ms}} sec",
"focusFail": "{{component}} non disponibile - riprova in {{ms}} sec",
"gifsMenu": "GIPHY",
"groupTitle": "Notifiche",
"hostAskedUnmute": "Il moderatore ti dice di accendere il microfono",
"hostAskedUnmute": "Il moderatore vorrebbe che tu parlassi",
"invitedOneMember": "{{displayName}} è stato invitato",
"invitedThreePlusMembers": "Hai invitato {{name}} e altri {{count}}",
"invitedTwoMembers": "Hai invitato {{first}} e {{second}}",
@@ -657,8 +646,6 @@
"linkToSalesforceKey": "Link this meeting",
"linkToSalesforceProgress": "Linking meeting to Salesforce...",
"linkToSalesforceSuccess": "The meeting was linked to Salesforce",
"localRecordingStarted": "{{name}} ha cominciato a registrare.",
"localRecordingStopped": "{{name}} ha smesso di registrare.",
"me": "Io",
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo, per favore.",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal moderatore",
@@ -672,10 +659,10 @@
"moderationStoppedTitle": "Moderazione interrotta",
"moderationToggleDescription": "da {{participantDisplayName}}",
"moderator": "Impostati i permessi di moderatore!",
"muted": "Hai iniziato la conversazione con audio disattivato.",
"muted": "Hai iniziato la conversazione con l'audio disattivato.",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono, quando vuoi parlare. Spegni il microfono quando hai finito, per non introdurre rumori di fondo nella riunione.",
"mutedRemotelyTitle": "Ti è stato disattivato l'audio da {{participantDisplayName}}!",
"mutedTitle": "Hai audio disattivato!",
"mutedTitle": "Hai l'audio disattivato!",
"newDeviceAction": "OK, usala",
"newDeviceAudioTitle": "Trovata nuova origine audio",
"newDeviceCameraTitle": "Trovata nuova videocamera",
@@ -711,10 +698,10 @@
"actions": {
"allow": "Permetti ai partecipanti di:",
"allowVideo": "Autorizza video",
"askUnmute": "Chiedi di accendere microfono",
"askUnmute": "Chiedi di riattivare audio",
"audioModeration": "Riattivare audio",
"blockEveryoneMicCamera": "Blocca audio e video a tutti",
"invite": "Invita partecipanti",
"invite": "Invita persone",
"moreModerationActions": "Altre opzioni di moderazione",
"moreModerationControls": "Altri controlli di moderazione",
"moreParticipantOptions": "Altre opzioni partecipanti",
@@ -727,7 +714,7 @@
"videoModeration": "Riavviare videocamera"
},
"close": "Chiudi",
"header": "Partecipanti e sala d'attesa",
"header": "Partecipanti",
"headings": {
"lobby": "Sala d'attesa ({{count}})",
"participantsList": "Partecipanti alla riunione ({{count}})",
@@ -889,7 +876,6 @@
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Abbiamo generato un collegamento alla tua registrazione.",
"live": "DIRETTA",
"localRecordingWarning": "Assicurati di aver selezionato la scheda corrente, per regitrare gli audio e video corretti. La registrazione è limitata ad 1GB, cioè circa 100 minuti.",
"loggedIn": "Accesso effettuato come {{userName}}",
"off": "Registrazione interrotta",
"offBy": "{{name}} ha interrotto la registrazione",
@@ -897,7 +883,6 @@
"onBy": "Registrazione iniziata da {{name}}",
"pending": "In preparazione alla registrazione della riunione",
"rec": "REC",
"saveLocalRecording": "Salva localmente il file della registrazione",
"serviceDescription": "La tua registrazione verrà salvata dal servizio di registrazione che hai scelto",
"serviceDescriptionCloud": "Registrazione in rete",
"serviceDescriptionCloudInfo": "Le riunioni registrate vengono automaticamente cancellate 24 ore dopo la registrazione.",
@@ -905,17 +890,15 @@
"sessionAlreadyActive": "Questa sessione è già in corso di registrazione o trasmissione in diretta.",
"signIn": "Entra",
"signOut": "Esci",
"surfaceError": "Selezionare la scheda corrente, per favore.",
"unavailable": "Ops! Il {{serviceName}} non è al momento disponibile. Stiamo lavorando per risolvere il problema. Riprova più tardi.",
"unavailableTitle": "Registrazione non disponibile",
"uploadToCloud": "Carica in cloud"
},
"screenshareDisplayName": "Schermo di {{name}}",
"sectionList": {
"pullToRefresh": "Trascina per aggiornare"
},
"security": {
"about": "Puoi aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"about": "Puoi aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"aboutReadOnly": "I moderatori della riunione possono aggiungere $t(lockRoomPassword). I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"insecureRoomNameWarning": "Il nome della riunione non è sicuro. Dei partecipanti indesiderati potrebbero unirsi alla riunione. Puoi proteggere l'accesso alla riunione col bottone sicurezza.",
"securityOptions": "Impostazioni di sicurezza"
@@ -947,7 +930,7 @@
"participantLeft": "Partecipante Uscito",
"playSounds": "Suoni attivati",
"reactions": "Reazioni riunione",
"sameAsSystem": "Stesso del computer ({{label}})",
"sameAsSystem": "Come nel sistema ({{label}})",
"selectAudioOutput": "Uscita audio",
"selectCamera": "Videocamera",
"selectMic": "Microfono",
@@ -978,8 +961,8 @@
"profileSection": "Profilo",
"serverURL": "URL del server",
"showAdvanced": "Impostazioni avanzate",
"startWithAudioMuted": "Inizia con audio disattivato",
"startWithVideoMuted": "Inizia con video disattivato",
"startWithAudioMuted": "Inizia con l'audio disattivato",
"startWithVideoMuted": "Avvia con il video disattivato",
"version": "Versione"
},
"share": {
@@ -1020,18 +1003,16 @@
"toolbar": {
"Settings": "Impostazioni",
"accessibilityLabel": {
"Settings": "Attiva/Disattiva impostazioni",
"Settings": "Attiva/disattiva impostazioni",
"audioOnly": "Spegni/Accendi audio",
"audioRoute": "Scegli l'uscita audio",
"boo": "Boo",
"breakoutRoom": "Entra/Lascia sottogruppo",
"callQuality": "Imposta qualità della chiamata",
"carmode": "Modalità in auto",
"cc": "Avvia/Ferma sottotitoli",
"chat": "Entra/Esci da conversazione",
"clap": "Applaudi",
"collapse": "Riduci",
"dock": "Aggancia alla finestra principale",
"document": "Apri/Chiudi documenti condivisi",
"download": "Scarica le nostre app",
"embedMeeting": "Incorpora riunione altrove",
@@ -1042,51 +1023,50 @@
"grantModerator": "Autorizza moderatore",
"hangup": "Lascia la riunione",
"help": "Aiuto",
"invite": "Invita partecipanti",
"invite": "Invita persone",
"kick": "Espelli partecipante",
"laugh": "Ridi",
"like": "Mi piace",
"linkToSalesforce": "Collega a Salesforce",
"lobbyButton": "Attiva/Disattiva sala d'attesa",
"localRecording": "Abilita/Disattiva controlli di registrazione locale",
"lobbyButton": "Attiva/disattiva sala d'attesa",
"localRecording": "Abilita/disattiva controlli di registrazione locale",
"lockRoom": "Attiva o disattiva password",
"moreActions": "Attiva o disattiva menu avanzato",
"moreActionsMenu": "Menu avanzato",
"moreOptions": "Altre opzioni",
"mute": "Attiva/Disattiva audio",
"moreOptions": "Più opzioni",
"mute": "Attiva/disattiva audio",
"muteEveryone": "Spegni i microfoni a tutti",
"muteEveryoneElse": "Spegni i microfoni di tutti gli altri",
"muteEveryoneElsesVideo": "Spegni le videocamere di tutti gli altri",
"muteEveryonesVideo": "Spegni le videocamere a tutti",
"participants": "Partecipanti",
"pip": "Attiva/Disattiva immagine nellimmagine",
"pip": "Attiva/disattiva immagine nellimmagine",
"privateMessage": "Invia messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza/Abbassa la mano",
"reactionsMenu": "Apri/Chiudi menù delle reaction",
"recording": "Avvia/Ferma registrazione",
"reactionsMenu": "Apri/chiudi menù delle reaction",
"recording": "Avvia/ferma registrazione",
"remoteMute": "Spegni microfono al partecipante",
"remoteVideoMute": "Spegni videocamera del partecipante",
"security": "Impostazioni di sicurezza",
"selectBackground": "Scegli sfondo",
"selfView": "Mostra tua immagine",
"shareRoom": "Invita qualcuno",
"shareYourScreen": "Attiva/Disattiva condivisione schermo",
"shareYourScreen": "Attiva/disattiva condivisione schermo",
"shareaudio": "Condividi audio",
"sharedvideo": "Attiva/Disattiva condivisione",
"shortcuts": "Attiva/Disattiva scorciatoie",
"sharedvideo": "Attiva/disattiva condivisione",
"shortcuts": "Attiva/disattiva scorciatoie",
"show": "Mostra in primo piano",
"silence": "Silenzio",
"speakerStats": "Attiva/Disattiva statistiche relatore",
"speakerStats": "Attiva/disattiva statistiche relatore",
"surprised": "Sorpreso",
"tileView": "Vedi tutti i partecipanti, o uno solo",
"tileView": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"toggleFilmstrip": "Attiva/Disattiva pellicola",
"undock": "Sgancia in una finestra separata",
"toggleFilmstrip": "Attiva/disattiva pellicola",
"videoblur": "Sfoca video",
"videomute": "Attiva/Disattiva videocamera"
"videomute": "Attiva/disattiva videocamera"
},
"addPeople": "Aggiungi partecipanti alla chiamata",
"addPeople": "Aggiungi persone alla chiamata",
"audioOnlyOff": "Disabilita modalità per banda limitata",
"audioOnlyOn": "Abilita modalità per banda limitata",
"audioRoute": "Scegli l'uscita audio",
@@ -1094,12 +1074,11 @@
"authenticate": "Autenticazione",
"boo": "Boo",
"callQuality": "Imposta qualità video",
"chat": "Apri/Chiudi conversazione",
"chat": "Apri / Chiudi conversazione",
"clap": "Applaudi",
"closeChat": "Chiudi conversazione",
"closeReactionsMenu": "Chiudi il menù reazioni",
"disableReactionSounds": "Puoi disattivare i suoni delle reaction, in questa riunione",
"dock": "Aggancia nella finestra principale",
"documentClose": "Chiudi documento condiviso",
"documentOpen": "Apri documento condiviso",
"download": "Scarica le nostre app",
@@ -1113,7 +1092,7 @@
"giphy": "Menù GIPHY",
"hangup": "Butta giù",
"help": "Aiuto",
"invite": "Invita partecipanti",
"invite": "Invita persone",
"joinBreakoutRoom": "Entra in sottogruppo",
"laugh": "Ridi",
"leaveBreakoutRoom": "Lascia breakout room",
@@ -1124,9 +1103,9 @@
"login": "Accedi",
"logout": "Scollegati",
"lowerYourHand": "Abbassa la mano",
"moreActions": "Altre azioni",
"moreOptions": "Altre opzioni",
"mute": "Attiva/Disattiva microfono",
"moreActions": "Più azioni",
"moreOptions": "Più opzioni",
"mute": "Attiva / Disattiva microfono",
"muteEveryone": "Spegni audio a tutti",
"muteEveryonesVideo": "Spegni videocamera di tutti",
"noAudioSignalDesc": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a cambiare dispositivo di input.",
@@ -1142,7 +1121,7 @@
"pip": "Abilita visualizzazione immagine nell'immagine",
"privateMessage": "invia un messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza/Abbassa la mano",
"raiseHand": "Alza / Abbassa la mano",
"raiseYourHand": "Alza la mano",
"reactionBoo": "Invia boo",
"reactionClap": "Invia applauso",
@@ -1168,12 +1147,11 @@
"talkWhileMutedPopup": "Stai provando a parlare? Il microfono è disattivato.",
"tileViewToggle": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"undock": "Sgancia in una finestra separata",
"videoSettings": "Impostazioni video",
"videomute": "Attiva/Disattiva videocamera"
"videomute": "Attiva / Disattiva videocamera"
},
"transcribing": {
"ccButtonTooltip": "Inizia/Ferma i sottotitoli",
"ccButtonTooltip": "Inizia / Ferma i sottotitoli",
"error": "Trascrizione fallita. Prova di nuovo.",
"expandedLabel": "La trascrizione della riunione è attiva",
"failedToStart": "C'è stato un errore nell'avvio del servizio di trascrizione.",
@@ -1198,12 +1176,12 @@
},
"videoSIPGW": {
"busy": "Stiamo lavorando per liberare le risorse. Riprova tra qualche minuto.",
"busyTitle": "Il servizio Riunione al momento è occupato",
"busyTitle": "Il servizio Stanza al momento è occupato",
"errorAlreadyInvited": "{{displayName}} già invitato",
"errorInvite": "Riunione non ancora stabilita. Riprova più tardi.",
"errorInviteFailed": "Stiamo lavorando per risolvere il problema. Riprova più tardi.",
"errorInviteFailedTitle": "Invito a {{displayName}} fallito",
"errorInviteTitle": "Errore nell'invito alla riunione",
"errorInviteTitle": "Errore nell'invito alla stanza",
"pending": "{{displayName}} è stato invitato"
},
"videoStatus": {
@@ -1240,7 +1218,7 @@
"mute": "Il partecipante ha il microfono spento",
"muted": "Audio disattivato",
"pinToStage": "Aggiungi agli oratori",
"remoteControl": "Avvia/Ferma il controllo remoto",
"remoteControl": "Avvia/ferma il controllo remoto",
"screenSharing": "Il partecipante sta condividendo lo schermo",
"show": "Mostra tra gli oratori",
"showSelfView": "Mostra tua immagine",
@@ -1259,7 +1237,7 @@
"image1": "Spiaggia",
"image2": "Parete neutra bianca",
"image3": "Stanza bianca vuota",
"image4": "Lampada da pavimento nera",
"image4": "Lampanda da pavimento nera",
"image5": "Montagna",
"image6": "Foresta",
"image7": "Alba",
@@ -1270,13 +1248,13 @@
"title": "Sfondi",
"uploadedImage": "Carica immagine {{index}}",
"webAssemblyWarning": "Il WebAssembly non è supportato",
"webAssemblyWarningDescription": "Il WebAssembly è disabilitato o non è supportato da questo browser"
"webAssemblyWarningDescription": "Il WebAssembly è disabilitato o non è supportat da questo browser"
},
"volumeSlider": "Sbarra volume",
"welcomepage": {
"accessibilityLabel": {
"join": "Tocca per accedere",
"roomname": "Inserisci nome riunione"
"roomname": "Inserisci nome stanza"
},
"addMeetingName": "Aggiungi Nome riunione",
"appDescription": "Avvia una videochiamata con tutto il team. Invita tutti quelli che conosci. {{app}} è una soluzione per effettuare videoconferenze totalmente crittografata, 100% open source, che puoi usare sempre, ogni giorno, gratuitamente senza bisogno di un account.",
@@ -1295,7 +1273,7 @@
"headerTitle": "Jitsi Meet",
"info": "Informazioni chiamata",
"jitsiOnMobile": "Jitsi su mobile scarica le nostre app e dai inizio ad una riunione dovunque tu sia",
"join": "CREA/UNISCITI",
"join": "CREA / UNISCITI",
"logo": {
"calendar": "Logo calendario",
"desktopPreviewThumbnail": "Icona anteprima desktop",
@@ -1311,11 +1289,11 @@
"privacy": "Riservatezza",
"recentList": "Recenti",
"recentListDelete": "Cancella",
"recentListEmpty": "La lista delle riunioni recenti è vuota. Partecipa almeno ad una riunione e potrai riavviarla in seguito.",
"recentListEmpty": "La tua lista è vuota. Conversa con qualcuno del tuo team e lo vedrai apparire nella lista delle riunioni recenti.",
"reducedUIText": "Benvenuto in {{app}}!",
"roomNameAllowedChars": "Il nome della riunione non deve contenere questi caratteri: ?, &, :, ', \", %, #.",
"roomname": "Inserisci il nome della riunione",
"roomnameHint": "Inserisci il nome o l'URL della alla quale vuoi accedere. Puoi anche inventarti un nome, assicurati solo che le persone che vuoi contattare lo conoscano, così che possano inserire lo stesso nome.",
"roomname": "Inserisci il nome della stanza",
"roomnameHint": "Inserisci il nome o l'URL della stanza alla quale vuoi accedere. Puoi anche inventarti un nome, assicurati solo che le persone che vuoi contattare lo conoscano, così che possano inserire lo stesso nome.",
"sendFeedback": "Invia feedback",
"startMeeting": "Inizia riunione",
"terms": "Termini di utilizzo",

View File

@@ -77,17 +77,6 @@
"refresh": "Atualizar calendário",
"today": "Hoje"
},
"carmode": {
"actions": {
"leaveMeeting": " Deixar a reunião",
"selectSoundDevice": "Seleccionar dispositivo de som"
},
"labels": {
"buttonLabel": "Modo carro",
"title": "Modo de condução segura",
"videoStopped": "O seu vídeo está parado"
}
},
"chat": {
"enter": "Entrar na sala",
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
@@ -116,7 +105,6 @@
},
"chromeExtensionBanner": {
"buttonText": "Instalar extensão do Chrome",
"buttonTextEdge": "Instalar extensão do Edge",
"close": "Fechar",
"dontShowAgain": "Não me mostre isto outra vez",
"installExtensionText": "Instalar a extensão para a integração Google Calendar e Office 365"
@@ -195,7 +183,7 @@
"unsupportedBrowser": "Parece que está a usar um browser que não suportamos."
},
"defaultLink": "ex.: {{url}}",
"defaultNickname": "ex.: João Dias",
"defaultNickname": "ex.: João Pedro",
"deviceError": {
"cameraError": "Falha ao aceder à sua câmara",
"cameraPermission": "Erro ao obter permissão para a câmara",
@@ -208,9 +196,6 @@
"selectADevice": "Selecione um dispositivo",
"testAudio": "Tocar um som de teste"
},
"dialIn": {
"screenTitle": "Resumo da marcação"
},
"dialOut": {
"statusMessage": "está agora {{status}}"
},
@@ -292,7 +277,7 @@
"lockRoom": "Adicionar reunião $t(lockRoomPassword)",
"lockTitle": "Bloqueio falhado",
"login": "Entrar",
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e sair da conferência?",
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e interromper a conferência?",
"logoutTitle": "Sair",
"maxUsersLimitReached": "O limite para o número máximo de participantes foi atingido. A conferência está cheia. Por favor contacte o proprietário da reunião ou tente novamente mais tarde!",
"maxUsersLimitReachedTitle": "Limite máximo de participantes atingido",
@@ -361,7 +346,7 @@
"screenSharingFailed": "Oops! Algo correu mal, não fomos capazes de começar a partilhar o ecrã!",
"screenSharingFailedTitle": "A partilha de ecrã falhou!",
"screenSharingPermissionDeniedError": "Oops! Alguma coisa correu mal com as vossas permissões de partilha de ecrã. Por favor, volte a carregar e tente novamente.",
"searchInSalesforce": "Pesquisar na Salesforce",
"searchInSalesforce": "Pesquisar na Força de Vendas",
"searchResults": "Resultados da pesquisa({{count}})",
"searchResultsDetailsError": "Algo correu mal enquanto se recuperava os dados do proprietário.",
"searchResultsError": "Alguma coisa correu mal durante a recuperação de dados.",
@@ -655,14 +640,12 @@
"leftOneMember": "{{name}} deixou a reunião",
"leftThreePlusMembers": "{{name}} e muitos outros deixaram a reunião",
"leftTwoMembers": "{{first}} e {{second}} deixaram a reunião",
"linkToSalesforce": "Link para a Salesforce",
"linkToSalesforceDescription": "Pode ligar o resumo da reunião a um objecto da Salesforce.",
"linkToSalesforceError": "Falha na ligação da reunião à Salesforce",
"linkToSalesforce": "Link para a Força de Vendas",
"linkToSalesforceDescription": "Pode ligar o resumo da reunião a um objecto da Força de Vendas.",
"linkToSalesforceError": "Falha na ligação da reunião à Força de Vendas",
"linkToSalesforceKey": "Ligar esta reunião",
"linkToSalesforceProgress": "A ligar a reunião à Salesforce...",
"linkToSalesforceSuccess": "A reunião foi ligada à Salesforce",
"localRecordingStarted": "{{name}} iniciou uma gravação local.",
"localRecordingStopped": "{{name}} parou uma gravação local.",
"linkToSalesforceProgress": "A ligar a reunião à Força de Vendas...",
"linkToSalesforceSuccess": "A reunião foi ligada à Força de Vendas",
"me": "Eu",
"moderationInEffectCSDescription": "Por favor, levantem a mão se quiserem partilhar o vosso ecrã.",
"moderationInEffectCSTitle": "A partilha de ecrã é bloqueada pelo moderador",
@@ -818,7 +801,6 @@
"initiated": "Chamada iniciada",
"joinAudioByPhone": "Entrar com o áudio do telefone",
"joinMeeting": "Entrar na reunião",
"joinMeetingInLowBandwidthMode": "Entrar em modo de baixa largura de banda",
"joinWithoutAudio": "Entrar sem áudio",
"keyboardShortcuts": "Ativar os atalhos de teclado",
"linkCopied": "Link copiado para a área de transferência",
@@ -894,7 +876,6 @@
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua gravação será limitada a {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Gerámos um link para a sua gravação.",
"live": "DIRETO",
"localRecordingWarning": "Certifique-se de seleccionar o separador actual a fim de utilizar o vídeo e áudio correctos. A gravação está actualmente limitada a 1 GB, o que é cerca de 100 minutos.",
"loggedIn": "Conectado como {{userName}}",
"off": "Gravação parada",
"offBy": "{{name}} parou a gravação",
@@ -902,7 +883,6 @@
"onBy": "{{name}} iniciou a gravação",
"pending": "Preparando para gravar a reunião...",
"rec": "REC",
"saveLocalRecording": "Guardar ficheiro de gravação localmente",
"serviceDescription": "Sua gravação será salva pelo serviço de gravação",
"serviceDescriptionCloud": "Gravação na nuvem",
"serviceDescriptionCloudInfo": "As reuniões gravadas são automaticamente apagadas 24h após a hora de gravação.",
@@ -910,12 +890,11 @@
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
"signIn": "Entrar",
"signOut": "Sair",
"surfaceError": "Por favor, seleccione o separador actual.",
"unavailable": "Oops! O {{serviceName}} está indisponível. Estamos trabalhando para resolver o problema. Por favor, tente mais tarde.",
"unavailableTitle": "Gravação indisponível",
"uploadToCloud": "Enviar para a nuvem"
},
"screenshareDisplayName": "Ecrã de {{name}}",
"screenshareDisplayName": "ecrã do {{name}}",
"sectionList": {
"pullToRefresh": "Puxe para atualizar"
},
@@ -953,7 +932,6 @@
"playSounds": "Reproduzir som quando",
"reactions": "Expressarem uma reação",
"sameAsSystem": "O mesmo que o sistema ({{label}})",
"screenTitle": "Definições",
"selectAudioOutput": "Saída de áudio",
"selectCamera": "Câmara",
"selectMic": "Microfone",
@@ -979,7 +957,6 @@
"disableCrashReportingWarning": "Tem a certeza de que quer desativar o relatório de falhas? A configuração será aplicada depois de reiniciar a aplicação.",
"disableP2P": "Desactivar o modo Peer-To-Peer",
"displayName": "Nome de exibição",
"displayNamePlaceholderText": "Ex: João Dias",
"email": "Email",
"header": "Configurações",
"profileSection": "Perfil",
@@ -1033,12 +1010,10 @@
"boo": "Vaia",
"breakoutRoom": "Entrar/Sair salas instantâneas",
"callQuality": "Gerir a qualidade do vídeo",
"carmode": "Modo carro",
"cc": "Mudar legendas",
"chat": "Abrir / Fechar chat",
"clap": "Aplausos",
"collapse": "Colapsar",
"dock": "Encaixar na janela principal",
"document": "Mudar para documento partilhado",
"download": "Descarregar as nossas aplicações",
"embedMeeting": "Reunião incorporada",
@@ -1053,7 +1028,7 @@
"kick": "Remover participante",
"laugh": "Risos",
"like": "Aprovado",
"linkToSalesforce": "Link para a Salesforce",
"linkToSalesforce": "Link para a Força de Vendas",
"lobbyButton": "Ativar/desativar sala de espera",
"localRecording": "Mudar os controlos locais de gravação",
"lockRoom": "Mudar palavra-chave de reunião",
@@ -1089,7 +1064,6 @@
"tileView": "Mudar a vista em quadrícula",
"toggleCamera": "Mudar a câmara",
"toggleFilmstrip": "Mudar a película de filme",
"undock": "Soltar numa janela separada",
"videoblur": "Mudar o desfoque de vídeo",
"videomute": "Iniciar / Parar câmara"
},
@@ -1106,7 +1080,6 @@
"closeChat": "Fechar chat",
"closeReactionsMenu": "Fechar menu de reações",
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
"dock": "Encaixar na janela principal",
"documentClose": "Fechar documento partilhado",
"documentOpen": "Abrir documento partilhado",
"download": "Descarregar as nossas aplicações",
@@ -1125,7 +1098,7 @@
"laugh": "Risos",
"leaveBreakoutRoom": "Sair da sala",
"like": "Aprovado",
"linkToSalesforce": "Link para a Salesforce",
"linkToSalesforce": "Link para a Força de Vendas",
"lobbyButtonDisable": "Desativar sala de espera",
"lobbyButtonEnable": "Ativar sala de espera",
"login": "Iniciar sessão",
@@ -1175,7 +1148,6 @@
"talkWhileMutedPopup": "Está a tentar falar? Está com o microfone desativado.",
"tileViewToggle": "Mudar para vista em quadrícula",
"toggleCamera": "Mudar a câmara",
"undock": "Soltar numa janela separada",
"videoSettings": "Definições de vídeo",
"videomute": "Iniciar / Parar câmara"
},

View File

@@ -116,7 +116,6 @@
},
"chromeExtensionBanner": {
"buttonText": "Install Chrome Extension",
"buttonTextEdge": "Install Edge Extension",
"close": "Close",
"dontShowAgain": "Dont show me this again",
"installExtensionText": "Install the extension for Google Calendar and Office 365 integration"
@@ -208,9 +207,6 @@
"selectADevice": "Select a device",
"testAudio": "Play a test sound"
},
"dialIn": {
"screenTitle": "Dial-in summary"
},
"dialOut": {
"statusMessage": "is now {{status}}"
},
@@ -292,7 +288,7 @@
"lockRoom": "Add meeting $t(lockRoomPassword)",
"lockTitle": "Lock failed",
"login": "Login",
"logoutQuestion": "Are you sure you want to logout and leave the conference?",
"logoutQuestion": "Are you sure you want to logout and stop the conference?",
"logoutTitle": "Logout",
"maxUsersLimitReached": "The limit for maximum number of participants has been reached. The conference is full. Please contact the meeting owner or try again later!",
"maxUsersLimitReachedTitle": "Maximum participants limit reached",
@@ -661,8 +657,6 @@
"linkToSalesforceKey": "Link this meeting",
"linkToSalesforceProgress": "Linking meeting to Salesforce...",
"linkToSalesforceSuccess": "The meeting was linked to Salesforce",
"localRecordingStarted": "{{name}} has started a local recording.",
"localRecordingStopped": "{{name}} has stopped a local recording.",
"me": "Me",
"moderationInEffectCSDescription": "Please raise hand if you want to share your screen.",
"moderationInEffectCSTitle": "Screen sharing is blocked by the moderator",
@@ -818,7 +812,6 @@
"initiated": "Call initiated",
"joinAudioByPhone": "Join with phone audio",
"joinMeeting": "Join meeting",
"joinMeetingInLowBandwidthMode": "Join in low bandwidth mode",
"joinWithoutAudio": "Join without audio",
"keyboardShortcuts": "Enable Keyboard shortcuts",
"linkCopied": "Link copied to clipboard",
@@ -894,23 +887,13 @@
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "We have generated a link to your recording.",
"live": "LIVE",
"localRecordingNoNotificationWarning": "The recording will not be announced to other participants. You will need to let them know that the meeting is recorded.",
"localRecordingNoVideo": "Video is not being recorded",
"localRecordingStartWarning": "Please make sure you stop the recording before exiting the meeting in order to save it.",
"localRecordingStartWarningTitle": "Stop the recording to save it",
"localRecordingVideoStop": "Stopping your video will also stop the local recording. Are you sure you want to continue?",
"localRecordingVideoWarning": "To record your video you must have it on when starting the recording",
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
"loggedIn": "Logged in as {{userName}}",
"noStreams": "No audio or video stream detected.",
"off": "Recording stopped",
"offBy": "{{name}} stopped the recording",
"on": "Recording started",
"onBy": "{{name}} started the recording",
"onlyRecordSelf": "Record only my audio and video streams",
"pending": "Preparing to record the meeting...",
"rec": "REC",
"saveLocalRecording": "Save recording file locally",
"serviceDescription": "Your recording will be saved by the recording service",
"serviceDescriptionCloud": "Cloud recording",
"serviceDescriptionCloudInfo": "Recorded meetings are automatically cleared 24h after their recording time.",
@@ -918,7 +901,6 @@
"sessionAlreadyActive": "This session is already being recorded or live streamed.",
"signIn": "Sign in",
"signOut": "Sign out",
"surfaceError": "Please select the current tab.",
"unavailable": "Oops! The {{serviceName}} is currently unavailable. We're working on resolving the issue. Please try again later.",
"unavailableTitle": "Recording unavailable",
"uploadToCloud": "Upload to the cloud"
@@ -957,12 +939,10 @@
"name": "Name",
"noDevice": "None",
"participantJoined": "Participant Joined",
"participantKnocking": "Participant entered lobby",
"participantLeft": "Participant Left",
"playSounds": "Play sound on",
"reactions": "Meeting reactions",
"sameAsSystem": "Same as system ({{label}})",
"screenTitle": "Settings",
"selectAudioOutput": "Audio output",
"selectCamera": "Camera",
"selectMic": "Microphone",
@@ -988,7 +968,6 @@
"disableCrashReportingWarning": "Are you sure you want to disable crash reporting? The setting will be applied after you restart the app.",
"disableP2P": "Disable Peer-To-Peer mode",
"displayName": "Display name",
"displayNamePlaceholderText": "Eg: John Doe",
"email": "Email",
"header": "Settings",
"profileSection": "Profile",
@@ -1033,7 +1012,6 @@
"termsView": {
"header": "Terms"
},
"toggleTopPanelLabel": "Toggle top panel",
"toolbar": {
"Settings": "Settings",
"accessibilityLabel": {
@@ -1048,7 +1026,6 @@
"chat": "Open / Close chat",
"clap": "Clap",
"collapse": "Collapse",
"dock": "Dock in main window",
"document": "Toggle shared document",
"download": "Download our apps",
"embedMeeting": "Embed meeting",
@@ -1099,7 +1076,6 @@
"tileView": "Toggle tile view",
"toggleCamera": "Toggle camera",
"toggleFilmstrip": "Toggle filmstrip",
"undock": "Undock into separate window",
"videoblur": "Toggle video blur",
"videomute": "Start / Stop camera"
},
@@ -1116,7 +1092,6 @@
"closeChat": "Close chat",
"closeReactionsMenu": "Close reactions menu",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"dock": "Dock in main window",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
@@ -1185,7 +1160,6 @@
"talkWhileMutedPopup": "Trying to speak? You are muted.",
"tileViewToggle": "Toggle tile view",
"toggleCamera": "Toggle camera",
"undock": "Undock into separate window",
"videoSettings": "Video settings",
"videomute": "Start / Stop camera"
},

View File

@@ -40,8 +40,7 @@ import {
isParticipantModerator,
isLocalParticipantModerator,
hasRaisedHand,
grantModerator,
overwriteParticipantsNames
grantModerator
} from '../../react/features/base/participants';
import { updateSettings } from '../../react/features/base/settings';
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
@@ -429,11 +428,6 @@ function initCommands() {
logger.error('Failed sending endpoint text message', err);
}
},
'overwrite-names': participantList => {
logger.debug('Overwrite names command received');
APP.store.dispatch(overwriteParticipantsNames(participantList));
},
'toggle-e2ee': enabled => {
logger.debug('Toggle E2EE key command received');
APP.store.dispatch(toggleE2EE(enabled));
@@ -1461,22 +1455,6 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the iframe
* docked state has been changed. The responsibility for implementing
* the dock / undock functionality lies with the external application.
*
* @param {boolean} docked - Whether or not the iframe has been set to
* be docked or undocked.
* @returns {void}
*/
notifyIframeDockStateChanged(docked: boolean) {
this._sendEvent({
name: 'iframe-dock-state-changed',
docked
});
}
/**
* Notify external application of a participant, remote or local, being
* removed from the conference by another participant.
@@ -1682,32 +1660,6 @@ class API {
});
}
/**
* Notify external application that the breakout rooms changed.
*
* @param {Array} rooms - Array of breakout rooms.
* @returns {void}
*/
notifyBreakoutRoomsUpdated(rooms) {
this._sendEvent({
name: 'breakout-rooms-updated',
rooms
});
}
/**
* Notify the external application that the state of the participants pane changed.
*
* @param {boolean} open - Wether the panel is open or not.
* @returns {void}
*/
notifyParticipantsPaneToggled(open) {
this._sendEvent({
name: 'participants-pane-toggled',
open
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -46,7 +46,6 @@ const commands = {
kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone',
overwriteConfig: 'overwrite-config',
overwriteNames: 'overwrite-names',
password: 'password',
pinParticipant: 'pin-participant',
rejectParticipant: 'reject-participant',
@@ -95,7 +94,6 @@ const events = {
'avatar-changed': 'avatarChanged',
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'breakout-rooms-updated': 'breakoutRoomsUpdated',
'browser-support': 'browserSupport',
'camera-error': 'cameraError',
'chat-updated': 'chatUpdated',
@@ -110,7 +108,6 @@ const events = {
'feedback-submitted': 'feedbackSubmitted',
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
'filmstrip-display-changed': 'filmstripDisplayChanged',
'iframe-dock-state-changed': 'iframeDockStateChanged',
'incoming-message': 'incomingMessage',
'knocking-participant': 'knockingParticipant',
'log': 'log',
@@ -126,7 +123,6 @@ const events = {
'participant-kicked-out': 'participantKickedOut',
'participant-left': 'participantLeft',
'participant-role-changed': 'participantRoleChanged',
'participants-pane-toggled': 'participantsPaneToggled',
'password-required': 'passwordRequired',
'proxy-connection-event': 'proxyConnectionEvent',
'raise-hand-updated': 'raiseHandUpdated',
@@ -302,7 +298,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* the participant opening the meeting.
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
* THIS IS EXPERIMENTAL.
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
*/
constructor(domain, ...args) {
super();
@@ -319,8 +314,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
invitees,
devices,
userInfo,
e2eeKey,
release
e2eeKey
} = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
@@ -335,8 +329,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
userInfo,
appData: {
localStorageContent
},
release
}
});
this._createIFrame(height, width, onload);
this._transport = new Transport({

432
package-lock.json generated
View File

@@ -34,7 +34,7 @@
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/rtcstats": "9.2.0",
"@jitsi/rtcstats": "9.0.1",
"@material-ui/core": "4.11.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
@@ -49,7 +49,7 @@
"@react-navigation/elements": "1.2.1",
"@react-navigation/material-top-tabs": "6.0.6",
"@react-navigation/native": "6.0.6",
"@react-navigation/stack": "6.2.2",
"@react-navigation/stack": "6.0.11",
"@svgr/webpack": "4.3.2",
"@tensorflow/tfjs-backend-wasm": "3.13.0",
"@tensorflow/tfjs-core": "3.13.0",
@@ -72,9 +72,8 @@
"jquery": "3.5.1",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1466.0.0+2682b4ec/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1431.0.0+742232c9/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.2",
"moment-duration-format": "2.2.2",
@@ -94,7 +93,7 @@
"react-native-collapsible": "1.6.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "8.4.8",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-dialog": "9.2.1",
"react-native-gesture-handler": "2.1.0",
"react-native-get-random-values": "1.7.2",
"react-native-immersive": "2.0.0",
@@ -104,7 +103,7 @@
"react-native-performance": "2.1.0",
"react-native-reanimated": "https://git@github.com/software-mansion/react-native-reanimated#c4a6b6f687ede090f6081064abe83a2ef9a05784",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "3.13.1",
"react-native-screens": "3.10.1",
"react-native-sound": "0.11.1",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "12.1.0",
@@ -113,7 +112,7 @@
"react-native-url-polyfill": "1.3.0",
"react-native-video": "https://git@github.com/jitsi/react-native-video#4f6dad990d17ce42894df993780b5386a9c11b85",
"react-native-watch-connectivity": "1.0.4",
"react-native-webrtc": "1.100.1",
"react-native-webrtc": "1.100.0",
"react-native-webview": "11.15.1",
"react-native-youtube-iframe": "2.2.1",
"react-redux": "7.1.0",
@@ -125,12 +124,10 @@
"redux-thunk": "2.2.0",
"resemblejs": "4.0.0",
"rnnoise-wasm": "https://git@github.com/jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
"seamless-scroll-polyfill": "2.1.8",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "8.3.2",
"wasm-check": "2.0.1",
"webm-duration-fix": "1.0.4",
"windows-iana": "^3.1.0",
"zxcvbn": "4.4.2"
},
@@ -143,11 +140,7 @@
"@babel/preset-react": "7.16.0",
"@babel/runtime": "7.16.0",
"@jitsi/eslint-config": "4.0.0",
"@types/lodash": "4.14.182",
"@types/react": "17.0.14",
"@types/react-native": "0.67.6",
"@types/react-redux": "7.1.24",
"@types/uuid": "8.3.4",
"babel-loader": "8.2.3",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -170,7 +163,7 @@
"style-loader": "0.19.0",
"traverse": "0.6.6",
"ts-loader": "9.2.6",
"typescript": "4.6.4",
"typescript": "4.3.5",
"unorm": "1.6.0",
"webpack": "5.57.1",
"webpack-bundle-analyzer": "4.4.2",
@@ -3553,13 +3546,37 @@
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
},
"node_modules/@jitsi/rtcstats": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.2.0.tgz",
"integrity": "sha512-bGQRLFio25++bi3s0em0xKD+WIqhDwg8OQ71K4BXZNOVL3eVX3R/bxbSEok6UYjFQxwoVjfdM4J8cHUu7RwrQw==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.0.1.tgz",
"integrity": "sha512-kImbDneVzU3pBDyY0vOruG96iYnMp2aOeHxYvnHgXGCTQfqF4dcPGtWMucSb5Dz7KEY6+U6G77Kay5C9p9lFjw==",
"dependencies": {
"@jitsi/js-utils": "^2.0.0",
"@jitsi/js-utils": "1.0.0",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
"uuid": "3.1.0"
}
},
"node_modules/@jitsi/rtcstats/node_modules/@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"dependencies": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"node_modules/@jitsi/rtcstats/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/@jitsi/rtcstats/node_modules/uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/@jitsi/sdp-interop": {
@@ -5035,12 +5052,12 @@
}
},
"node_modules/@react-navigation/stack": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.2.2.tgz",
"integrity": "sha512-P9ZfmluOXNmbs7YdG1UWS1fAh87Yse9aX8TgqOz4FlHEm5q7g5eaM35QgWByt+wif3UiqE40D8wXpqRQvMgPWg==",
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.0.11.tgz",
"integrity": "sha512-Osc2mXi0Zh/u92HRCceDqVfVnypTa2sZgYMJDU+vDhHz38negtbCG+cjje6nApSjwC5WTVhYP4OoD5WBSh51+g==",
"dependencies": {
"@react-navigation/elements": "^1.3.4",
"color": "^4.2.3",
"@react-navigation/elements": "^1.2.1",
"color": "^3.1.3",
"warn-once": "^0.1.0"
},
"peerDependencies": {
@@ -5052,45 +5069,6 @@
"react-native-screens": ">= 3.0.0"
}
},
"node_modules/@react-navigation/stack/node_modules/@react-navigation/elements": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.4.tgz",
"integrity": "sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g==",
"peerDependencies": {
"@react-navigation/native": "^6.0.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 3.0.0"
}
},
"node_modules/@react-navigation/stack/node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/@react-navigation/stack/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@react-navigation/stack/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -5417,16 +5395,6 @@
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/http-proxy": {
"version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@@ -5474,12 +5442,6 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"node_modules/@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@@ -5529,9 +5491,9 @@
"dev": true
},
"node_modules/@types/react": {
"version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
"version": "17.0.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz",
"integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -5547,18 +5509,6 @@
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"dev": true,
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
@@ -5611,12 +5561,6 @@
"@types/node": "*"
}
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"node_modules/@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
@@ -7925,9 +7869,9 @@
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"dependencies": {
"ms": "2.1.2"
},
@@ -8379,11 +8323,6 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"node_modules/ebml-block": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/ebml-block/-/ebml-block-1.1.2.tgz",
"integrity": "sha512-HgNlIsRFP6D9VKU5atCeHRJY7XkJP8bOe8yEhd8NB7B3b4++VWTyauz6g650iiPmLfPLGlVpoJmGSgMfXDYusg=="
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -10718,14 +10657,6 @@
"css-in-js-utils": "^2.0.0"
}
},
"node_modules/int64-buffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.0.1.tgz",
"integrity": "sha512-+3azY4pXrjAupJHU1V9uGERWlhoqNswJNji6aD/02xac7oxol508AsMC5lxKhEqyZeDFy3enq5OGWXF4u75hiw==",
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/internal-slot": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
@@ -11688,11 +11619,6 @@
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.6.1.tgz",
"integrity": "sha512-lyUTXOqMEaA9mm38mHxbTo83WsYAvMJm850kxJcRno3T2qL+e40B2G89E0/4r9TdAeB3jN0TdSVp/VHNI6/WyQ=="
},
"node_modules/js-sha512": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
"integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -12206,8 +12132,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1466.0.0+2682b4ec/lib-jitsi-meet.tgz",
"integrity": "sha512-p6PWid1e5pDw/Y9NYU32Xik6UPJ9QFUq3DawHNMPdo3tXc+5ogWKF2LT7ieA2cl5RbINnnE7oNXTkrDsQwsWiA==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1431.0.0+742232c9/lib-jitsi-meet.tgz",
"integrity": "sha512-M2tybHP1w4AXcsUH33aiTgPlr/I0uqcwfHYUo2/zpmhI7gaxas7KxAy7VU7cqJNZh3qTiT46eYZQEeMaNffuCw==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
@@ -14871,6 +14797,11 @@
"node": ">=0.10.0"
}
},
"node_modules/postis": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/postis/-/postis-2.2.0.tgz",
"integrity": "sha1-3F4yN2WYXd/cv9r8MUGpVprvdak="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -15460,10 +15391,9 @@
}
},
"node_modules/react-native-dialog": {
"version": "9.2.2",
"resolved": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A==",
"license": "MIT",
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.2.1.tgz",
"integrity": "sha512-UNnGFTpH0cX16cJZLFq9/TAZH1+B2vzJrQL1mUaSqZjV+sFTpUB1WvghJZxPwi52v587kJpfKN7oPfWaXAu+YQ==",
"peerDependencies": {
"react-native": ">=0.63.0"
}
@@ -15572,9 +15502,9 @@
}
},
"node_modules/react-native-screens": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.13.1.tgz",
"integrity": "sha512-xcrnuUs0qUrGpc2gOTDY4VgHHADQwp80mwR1prU/Q0JqbZN5W3koLhuOsT6FkSRKjR5t40l+4LcjhHdpqRB2HA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.10.1.tgz",
"integrity": "sha512-ZF/XHnRsuinvDY1XiCWLXxoUoSf+NgsAes2SZfX9rFQQcv128zmh/+19SSavGrSf6rQNzqytEMdRGI6yr4Gbjw==",
"dependencies": {
"react-freeze": "^1.0.0",
"warn-once": "^0.1.0"
@@ -16083,9 +16013,9 @@
}
},
"node_modules/react-native-webrtc": {
"version": "1.100.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.100.1.tgz",
"integrity": "sha512-2dF8HrZWdfrbm1NLso424xgNeGFf4WK5Jlqs7ecVDvCMWHTYVHVwyNt52zx1yiOMEqQWk4edOd4xhWJfUiOvMg==",
"version": "1.100.0",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.100.0.tgz",
"integrity": "sha512-sBLl8Ihj3xfYn0NZJdDxelB/dZ422FWg/kcLdSw6Wk4eM3MeNW3iFwVsqg2dLzeDnuoQ06i7hOdtP7pGAiKsug==",
"hasInstallScript": true,
"dependencies": {
"base64-js": "1.5.1",
@@ -16870,11 +16800,6 @@
"sdp-verify": "checker.js"
}
},
"node_modules/seamless-scroll-polyfill": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/seamless-scroll-polyfill/-/seamless-scroll-polyfill-2.1.8.tgz",
"integrity": "sha512-cF92Op90//vEpHphRx25rttJGXIgxcTB1WR5y0ODQhN7O4d0lSEOp5+l3sQDx0aAZ2MfXCqFEb/rG/3ghvVDIQ=="
},
"node_modules/seedrandom": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
@@ -18718,9 +18643,9 @@
}
},
"node_modules/typescript": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -19198,40 +19123,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"node_modules/webm-duration-fix": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz",
"integrity": "sha512-kvhmSmEnuohtK+j+mJswqCCM2ViKb9W8Ch0oAxcaeUvpok5CsMORQLnea+CYKDXPG6JH12H0CbRK85qhfeZLew==",
"dependencies": {
"buffer": "^6.0.3",
"ebml-block": "^1.1.2",
"events": "^3.3.0",
"int64-buffer": "^1.0.1"
}
},
"node_modules/webm-duration-fix/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/webpack": {
"version": "5.57.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.1.tgz",
@@ -22737,13 +22628,35 @@
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
},
"@jitsi/rtcstats": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.2.0.tgz",
"integrity": "sha512-bGQRLFio25++bi3s0em0xKD+WIqhDwg8OQ71K4BXZNOVL3eVX3R/bxbSEok6UYjFQxwoVjfdM4J8cHUu7RwrQw==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.0.1.tgz",
"integrity": "sha512-kImbDneVzU3pBDyY0vOruG96iYnMp2aOeHxYvnHgXGCTQfqF4dcPGtWMucSb5Dz7KEY6+U6G77Kay5C9p9lFjw==",
"requires": {
"@jitsi/js-utils": "^2.0.0",
"@jitsi/js-utils": "1.0.0",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
"uuid": "3.1.0"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
}
}
},
"@jitsi/sdp-interop": {
@@ -23805,42 +23718,13 @@
}
},
"@react-navigation/stack": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.2.2.tgz",
"integrity": "sha512-P9ZfmluOXNmbs7YdG1UWS1fAh87Yse9aX8TgqOz4FlHEm5q7g5eaM35QgWByt+wif3UiqE40D8wXpqRQvMgPWg==",
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.0.11.tgz",
"integrity": "sha512-Osc2mXi0Zh/u92HRCceDqVfVnypTa2sZgYMJDU+vDhHz38negtbCG+cjje6nApSjwC5WTVhYP4OoD5WBSh51+g==",
"requires": {
"@react-navigation/elements": "^1.3.4",
"color": "^4.2.3",
"@react-navigation/elements": "^1.2.1",
"color": "^3.1.3",
"warn-once": "^0.1.0"
},
"dependencies": {
"@react-navigation/elements": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.4.tgz",
"integrity": "sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g=="
},
"color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"requires": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
}
}
},
"@sideway/address": {
@@ -24112,16 +23996,6 @@
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/http-proxy": {
"version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@@ -24169,12 +24043,6 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@@ -24224,9 +24092,9 @@
"dev": true
},
"@types/react": {
"version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
"version": "17.0.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz",
"integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -24242,18 +24110,6 @@
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
@@ -24306,12 +24162,6 @@
"@types/node": "*"
}
},
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
@@ -26152,9 +26002,9 @@
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"requires": {
"ms": "2.1.2"
}
@@ -26498,11 +26348,6 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"ebml-block": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/ebml-block/-/ebml-block-1.1.2.tgz",
"integrity": "sha512-HgNlIsRFP6D9VKU5atCeHRJY7XkJP8bOe8yEhd8NB7B3b4++VWTyauz6g650iiPmLfPLGlVpoJmGSgMfXDYusg=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -28328,11 +28173,6 @@
"css-in-js-utils": "^2.0.0"
}
},
"int64-buffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.0.1.tgz",
"integrity": "sha512-+3azY4pXrjAupJHU1V9uGERWlhoqNswJNji6aD/02xac7oxol508AsMC5lxKhEqyZeDFy3enq5OGWXF4u75hiw=="
},
"internal-slot": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
@@ -29022,11 +28862,6 @@
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.6.1.tgz",
"integrity": "sha512-lyUTXOqMEaA9mm38mHxbTo83WsYAvMJm850kxJcRno3T2qL+e40B2G89E0/4r9TdAeB3jN0TdSVp/VHNI6/WyQ=="
},
"js-sha512": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
"integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -29441,8 +29276,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1466.0.0+2682b4ec/lib-jitsi-meet.tgz",
"integrity": "sha512-p6PWid1e5pDw/Y9NYU32Xik6UPJ9QFUq3DawHNMPdo3tXc+5ogWKF2LT7ieA2cl5RbINnnE7oNXTkrDsQwsWiA==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1431.0.0+742232c9/lib-jitsi-meet.tgz",
"integrity": "sha512-M2tybHP1w4AXcsUH33aiTgPlr/I0uqcwfHYUo2/zpmhI7gaxas7KxAy7VU7cqJNZh3qTiT46eYZQEeMaNffuCw==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -31573,6 +31408,11 @@
}
}
},
"postis": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/postis/-/postis-2.2.0.tgz",
"integrity": "sha1-3F4yN2WYXd/cv9r8MUGpVprvdak="
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -32013,8 +31853,9 @@
"integrity": "sha512-92676ZWHZHsPM/EW1ulgb2MuVfjYfMWRTWMbLcrCsipkcMaZ9Traz5mpsnCS7KZpsOksnvUinzDIjsct2XGc6Q=="
},
"react-native-dialog": {
"version": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A=="
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.2.1.tgz",
"integrity": "sha512-UNnGFTpH0cX16cJZLFq9/TAZH1+B2vzJrQL1mUaSqZjV+sFTpUB1WvghJZxPwi52v587kJpfKN7oPfWaXAu+YQ=="
},
"react-native-gesture-handler": {
"version": "2.1.0",
@@ -32090,9 +31931,9 @@
"integrity": "sha512-yOwiiPJ1rk+/nfK13eafbpW6sKW0jOnsRem2C1LPJjM3tfTof6hlvV5eWHATye3XOpu2cJ7N+HdkUvUDGwFD2Q=="
},
"react-native-screens": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.13.1.tgz",
"integrity": "sha512-xcrnuUs0qUrGpc2gOTDY4VgHHADQwp80mwR1prU/Q0JqbZN5W3koLhuOsT6FkSRKjR5t40l+4LcjhHdpqRB2HA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.10.1.tgz",
"integrity": "sha512-ZF/XHnRsuinvDY1XiCWLXxoUoSf+NgsAes2SZfX9rFQQcv128zmh/+19SSavGrSf6rQNzqytEMdRGI6yr4Gbjw==",
"requires": {
"react-freeze": "^1.0.0",
"warn-once": "^0.1.0"
@@ -32384,9 +32225,9 @@
}
},
"react-native-webrtc": {
"version": "1.100.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.100.1.tgz",
"integrity": "sha512-2dF8HrZWdfrbm1NLso424xgNeGFf4WK5Jlqs7ecVDvCMWHTYVHVwyNt52zx1yiOMEqQWk4edOd4xhWJfUiOvMg==",
"version": "1.100.0",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.100.0.tgz",
"integrity": "sha512-sBLl8Ihj3xfYn0NZJdDxelB/dZ422FWg/kcLdSw6Wk4eM3MeNW3iFwVsqg2dLzeDnuoQ06i7hOdtP7pGAiKsug==",
"requires": {
"base64-js": "1.5.1",
"event-target-shim": "6.0.2",
@@ -32954,11 +32795,6 @@
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
},
"seamless-scroll-polyfill": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/seamless-scroll-polyfill/-/seamless-scroll-polyfill-2.1.8.tgz",
"integrity": "sha512-cF92Op90//vEpHphRx25rttJGXIgxcTB1WR5y0ODQhN7O4d0lSEOp5+l3sQDx0aAZ2MfXCqFEb/rG/3ghvVDIQ=="
},
"seedrandom": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
@@ -34438,9 +34274,9 @@
}
},
"typescript": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"ua-parser-js": {
@@ -34785,28 +34621,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"webm-duration-fix": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz",
"integrity": "sha512-kvhmSmEnuohtK+j+mJswqCCM2ViKb9W8Ch0oAxcaeUvpok5CsMORQLnea+CYKDXPG6JH12H0CbRK85qhfeZLew==",
"requires": {
"buffer": "^6.0.3",
"ebml-block": "^1.1.2",
"events": "^3.3.0",
"int64-buffer": "^1.0.1"
},
"dependencies": {
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
}
}
},
"webpack": {
"version": "5.57.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.1.tgz",

View File

@@ -39,7 +39,7 @@
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/rtcstats": "9.2.0",
"@jitsi/rtcstats": "9.0.1",
"@material-ui/core": "4.11.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
@@ -54,7 +54,7 @@
"@react-navigation/elements": "1.2.1",
"@react-navigation/material-top-tabs": "6.0.6",
"@react-navigation/native": "6.0.6",
"@react-navigation/stack": "6.2.2",
"@react-navigation/stack": "6.0.11",
"@svgr/webpack": "4.3.2",
"@tensorflow/tfjs-backend-wasm": "3.13.0",
"@tensorflow/tfjs-core": "3.13.0",
@@ -77,9 +77,8 @@
"jquery": "3.5.1",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1466.0.0+2682b4ec/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1431.0.0+742232c9/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.2",
"moment-duration-format": "2.2.2",
@@ -99,7 +98,7 @@
"react-native-collapsible": "1.6.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "8.4.8",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-dialog": "9.2.1",
"react-native-gesture-handler": "2.1.0",
"react-native-get-random-values": "1.7.2",
"react-native-immersive": "2.0.0",
@@ -109,7 +108,7 @@
"react-native-performance": "2.1.0",
"react-native-reanimated": "https://git@github.com/software-mansion/react-native-reanimated#c4a6b6f687ede090f6081064abe83a2ef9a05784",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "3.13.1",
"react-native-screens": "3.10.1",
"react-native-sound": "0.11.1",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "12.1.0",
@@ -118,7 +117,7 @@
"react-native-url-polyfill": "1.3.0",
"react-native-video": "https://git@github.com/jitsi/react-native-video#4f6dad990d17ce42894df993780b5386a9c11b85",
"react-native-watch-connectivity": "1.0.4",
"react-native-webrtc": "1.100.1",
"react-native-webrtc": "1.100.0",
"react-native-webview": "11.15.1",
"react-native-youtube-iframe": "2.2.1",
"react-redux": "7.1.0",
@@ -130,12 +129,10 @@
"redux-thunk": "2.2.0",
"resemblejs": "4.0.0",
"rnnoise-wasm": "https://git@github.com/jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
"seamless-scroll-polyfill": "2.1.8",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "8.3.2",
"wasm-check": "2.0.1",
"webm-duration-fix": "1.0.4",
"windows-iana": "^3.1.0",
"zxcvbn": "4.4.2"
},
@@ -148,11 +145,7 @@
"@babel/preset-react": "7.16.0",
"@babel/runtime": "7.16.0",
"@jitsi/eslint-config": "4.0.0",
"@types/lodash": "4.14.182",
"@types/react": "17.0.14",
"@types/react-native": "0.67.6",
"@types/react-redux": "7.1.24",
"@types/uuid": "8.3.4",
"babel-loader": "8.2.3",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -175,7 +168,7 @@
"style-loader": "0.19.0",
"traverse": "0.6.6",
"ts-loader": "9.2.6",
"typescript": "4.6.4",
"typescript": "4.3.5",
"unorm": "1.6.0",
"webpack": "5.57.1",
"webpack-bundle-analyzer": "4.4.2",
@@ -191,12 +184,8 @@
"lint": "eslint --max-warnings 0 .",
"lang-sort": "./resources/lang-sort.sh",
"lint-fix": "eslint --max-warnings 0 --fix .",
"postinstall": "patch-package --error-on-fail && jetify",
"postinstall": "patch-package && jetify",
"validate": "npm ls",
"start": "make dev"
},
"resolutions": {
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14"
}
}

View File

@@ -0,0 +1,118 @@
diff --git a/node_modules/react-native-dialog/lib/Button.js b/node_modules/react-native-dialog/lib/Button.js
index 19eeb22..b8a66f4 100644
--- a/node_modules/react-native-dialog/lib/Button.js
+++ b/node_modules/react-native-dialog/lib/Button.js
@@ -50,7 +50,7 @@ const buildStyles = (isDark) => StyleSheet.create({
backgroundColor: "transparent",
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "link_text_dark" : "link_text_dark_light"}`),
+ color: isDark ? '#BFC7C7C7' : '#BF727272',
textAlign: "center",
backgroundColor: "transparent",
padding: 8,
diff --git a/node_modules/react-native-dialog/lib/CodeInput.js b/node_modules/react-native-dialog/lib/CodeInput.js
index eceae56..cc4339d 100644
--- a/node_modules/react-native-dialog/lib/CodeInput.js
+++ b/node_modules/react-native-dialog/lib/CodeInput.js
@@ -97,7 +97,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 20,
},
default: {},
@@ -107,7 +107,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 14,
},
default: {},
diff --git a/node_modules/react-native-dialog/lib/Container.js b/node_modules/react-native-dialog/lib/Container.js
index 69e3764..d7569fb 100644
--- a/node_modules/react-native-dialog/lib/Container.js
+++ b/node_modules/react-native-dialog/lib/Container.js
@@ -82,7 +82,7 @@ DialogContainer.propTypes = {
useNativeDriver: PropTypes.bool,
children: PropTypes.node.isRequired,
};
-const buildStyles = () => StyleSheet.create({
+const buildStyles = (isDark) => StyleSheet.create({
centeredView: {
marginTop: 22,
},
@@ -103,7 +103,7 @@ const buildStyles = () => StyleSheet.create({
overflow: "hidden",
},
android: {
- backgroundColor: PlatformColor("?attr/colorBackgroundFloating"),
+ backgroundColor: isDark ? '#212121' : '#FFFFFF',
flexDirection: "column",
borderRadius: 3,
padding: 16,
diff --git a/node_modules/react-native-dialog/lib/Description.js b/node_modules/react-native-dialog/lib/Description.js
index 2da9ed3..248ac2f 100644
--- a/node_modules/react-native-dialog/lib/Description.js
+++ b/node_modules/react-native-dialog/lib/Description.js
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
marginTop: 4,
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "secondary_text_dark" : "secondary_text_light"}`),
+ color: isDark ? '#C7C7C7' : '#727272',
fontSize: 16,
marginTop: 10,
},
diff --git a/node_modules/react-native-dialog/lib/Input.js b/node_modules/react-native-dialog/lib/Input.js
index b33a1a0..063d7f8 100644
--- a/node_modules/react-native-dialog/lib/Input.js
+++ b/node_modules/react-native-dialog/lib/Input.js
@@ -48,7 +48,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 14,
},
default: {},
@@ -58,7 +58,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
marginLeft: -4,
paddingLeft: 4,
},
diff --git a/node_modules/react-native-dialog/lib/Switch.js b/node_modules/react-native-dialog/lib/Switch.js
index 26a05ca..05114fa 100644
--- a/node_modules/react-native-dialog/lib/Switch.js
+++ b/node_modules/react-native-dialog/lib/Switch.js
@@ -52,7 +52,7 @@ const buildStyles = (isDark) => StyleSheet.create({
flex: 1,
paddingRight: 8,
fontSize: 16,
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
},
default: {},
}),
diff --git a/node_modules/react-native-dialog/lib/Title.js b/node_modules/react-native-dialog/lib/Title.js
index 1c6fd87..b5511cc 100644
--- a/node_modules/react-native-dialog/lib/Title.js
+++ b/node_modules/react-native-dialog/lib/Title.js
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
fontWeight: "600",
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontWeight: "500",
fontSize: 18,
},

View File

@@ -2,7 +2,6 @@
import { API_ID } from '../../../modules/API/constants';
import { getName as getAppName } from '../app/functions';
import { getAnalyticsRoomName } from '../base/conference';
import {
checkChromeExtensionsInstalled,
isMobileBrowser
@@ -13,7 +12,6 @@ import JitsiMeetJS, {
} from '../base/lib-jitsi-meet';
import { isAnalyticsEnabled } from '../base/lib-jitsi-meet/functions';
import { getJitsiMeetGlobalNS, loadScript, parseURIString } from '../base/util';
import { inIframe } from '../base/util/iframeUtils';
import { AmplitudeHandler, MatomoHandler } from './handlers';
import logger from './logger';
@@ -156,9 +154,7 @@ export async function createHandlers({ getState }: { getState: Function }) {
* @param {Array<Object>} handlers - The analytics handlers.
* @returns {void}
*/
export function initAnalytics(store: Store, handlers: Array<Object>) {
const { getState, dispatch } = store;
export function initAnalytics({ getState }: { getState: Function }, handlers: Array<Object>) {
if (!isAnalyticsEnabled(getState) || handlers.length === 0) {
return;
}
@@ -169,6 +165,7 @@ export function initAnalytics(store: Store, handlers: Array<Object>) {
deploymentInfo
} = config;
const { group, server } = state['features/base/jwt'];
const roomName = state['features/base/conference'].room;
const { locationURL = {} } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const permanentProperties = {};
@@ -206,7 +203,7 @@ export function initAnalytics(store: Store, handlers: Array<Object>) {
}
analytics.addPermanentProperties(permanentProperties);
analytics.setConferenceName(getAnalyticsRoomName(state, dispatch));
analytics.setConferenceName(roomName);
// Set the handlers last, since this triggers emptying of the cache
analytics.setAnalyticsHandlers(handlers);
@@ -224,6 +221,24 @@ export function initAnalytics(store: Store, handlers: Array<Object>) {
}
}
/**
* Checks whether we are loaded in iframe.
*
* @returns {boolean} Returns {@code true} if loaded in iframe.
* @private
*/
export function inIframe() {
if (navigator.product === 'ReactNative') {
return false;
}
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
/**
* Tries to load the scripts for the external analytics handlers and creates them.
*

View File

@@ -1,4 +1,6 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
// @flow
import { ReducerRegistry } from '../base/redux';
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
@@ -28,22 +30,6 @@ const DEFAULT_STATE = {
}
};
interface Value {
startedTime: number,
value: number
}
export interface IAnalyticsState {
localTracksDuration: {
audio: Value,
video: {
camera: Value,
desktop: Value
},
conference: Value
}
}
/**
* Listen for actions which changes the state of the analytics feature.
*
@@ -52,7 +38,7 @@ export interface IAnalyticsState {
* @param {string} action.type - Type of action.
* @returns {Object}
*/
ReducerRegistry.register('features/analytics', (state: IAnalyticsState = DEFAULT_STATE, action: any) => {
ReducerRegistry.register('features/analytics', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case UPDATE_LOCAL_TRACKS_DURATION:
return {

View File

@@ -15,17 +15,11 @@ import { connect, disconnect, setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks';
import {
appendURLParam,
getBackendSafeRoomName,
parseURIString,
parseURLParams,
toURLString
} from '../base/util';
import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
import {
goBackToRoot,
navigateRoot
} from '../mobile/navigation/rootNavigationContainerRef';
import { navigateRoot } from '../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { setFatalError } from '../overlay';
@@ -92,11 +86,7 @@ export function appNavigate(uri: ?string) {
let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment.
room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
const { release } = parseURLParams(location, true, 'search');
release && (url = appendURLParam(url, 'release', release));
room && (url += `?room=${getBackendSafeRoomName(room)}`);
let config;
@@ -138,15 +128,7 @@ export function appNavigate(uri: ?string) {
if (room) {
dispatch(createDesiredLocalTracks());
if (isPrejoinPageEnabled(getState())) {
navigateRoot(screen.preJoin);
} else {
dispatch(connect());
navigateRoot(screen.conference.root);
}
} else {
goBackToRoot(getState(), dispatch);
dispatch(connect());
}
};
}

View File

@@ -15,10 +15,8 @@ import {
import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet/functions.web';
import {
appendURLParam,
getBackendSafeRoomName,
parseURIString,
parseURLParams
parseURIString
} from '../base/util';
import { isVpaasMeeting } from '../jaas/functions';
import {
@@ -95,11 +93,7 @@ export function appNavigate(uri: ?string) {
let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment.
room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
const { release } = parseURLParams(location, true, 'search');
release && (url = appendURLParam(url, 'release', release));
room && (url += `?room=${getBackendSafeRoomName(room)}`);
let config;

View File

@@ -1,13 +1,14 @@
// @flow
import React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import SplashScreen from 'react-native-splash-screen';
import { DialogContainer } from '../../base/dialog';
import BottomSheetContainer from '../../base/dialog/components/native/BottomSheetContainer';
import { updateFlags } from '../../base/flags/actions';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions';
import { Platform } from '../../base/react';
import { DimensionsDetector, clientResized, setSafeAreaInsets } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import { _getRouteToRender } from '../getRouteToRender.native';
@@ -22,15 +23,16 @@ import '../reducers';
declare var __DEV__;
const DialogContainerWrapper = Platform.select({
default: View
});
/**
* The type of React {@code Component} props of {@link App}.
*/
type Props = AbstractAppProps & {
/**
* Identifier for this app on the native side.
*/
externalAPIScope: string,
/**
* An object with the feature flags.
*/
@@ -238,12 +240,7 @@ export class App extends AbstractApp {
*/
_renderDialogContainer() {
return (
<DialogContainerWrapper
pointerEvents = 'box-none'
style = { StyleSheet.absoluteFill }>
<BottomSheetContainer />
<DialogContainer />
</DialogContainerWrapper>
<DialogContainer />
);
}
}

View File

@@ -7,8 +7,7 @@ import { toState } from '../base/redux';
import { Conference } from '../conference';
import { getDeepLinkingPage } from '../deep-linking';
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
import { BlankPage, WelcomePage } from '../welcome';
import { isWelcomePageEnabled } from '../welcome/functions';
import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
/**
* Determines which route is to be rendered in order to depict a specific Redux
@@ -73,7 +72,7 @@ function _getWebConferenceRoute(state) {
function _getWebWelcomePageRoute(state) {
const route = _getEmptyRoute();
if (isWelcomePageEnabled(state)) {
if (isWelcomePageUserEnabled(state)) {
if (isSupportedBrowser()) {
route.component = WelcomePage;
} else {

View File

@@ -2,6 +2,7 @@
import {
createConnectionEvent,
inIframe,
sendAnalytics
} from '../analytics';
import { SET_ROOM } from '../base/conference';
@@ -11,7 +12,6 @@ import {
getURLWithoutParams
} from '../base/connection';
import { MiddlewareRegistry } from '../base/redux';
import { inIframe } from '../base/util/iframeUtils';
import { reloadNow } from './actions';
import { _getRouteToRender } from './getRouteToRender';

View File

@@ -36,7 +36,6 @@ import '../large-video/middleware';
import '../lobby/middleware';
import '../notifications/middleware';
import '../overlay/middleware';
import '../participants-pane/middleware';
import '../polls/middleware';
import '../reactions/middleware';
import '../recent-list/middleware';

View File

@@ -1,7 +1,6 @@
// @flow
import '../authentication/middleware';
import '../dynamic-branding/middleware';
import '../gifs/middleware';
import '../mobile/audio-mode/middleware';
import '../mobile/background/middleware';

View File

@@ -3,7 +3,6 @@
import '../authentication/middleware';
import '../base/i18n/middleware';
import '../base/devices/middleware';
import '../base/media/middleware';
import '../dynamic-branding/middleware';
import '../e2ee/middleware';
import '../external-api/middleware';

View File

@@ -1,22 +0,0 @@
import { IAnalyticsState } from "../analytics/reducer"
import { IAuthenticationState } from "../authentication/reducer"
import { IAVModerationState } from "../av-moderation/reducer"
import { IAppState } from "../base/app/reducer"
import { IAudioOnlyState } from "../base/audio-only/reducer"
import { IConferenceState } from "../base/conference/reducer"
import { IConfig } from "../base/config/configType"
export interface IStore {
getState: Function,
dispatch: Function
}
export interface IState {
'features/analytics': IAnalyticsState,
'features/authentication': IAuthenticationState,
'features/av-moderation': IAVModerationState,
'features/base/app': IAppState,
'features/base/audio-only': IAudioOnlyState,
'features/base/conference': IConferenceState,
'features/base/config': IConfig,
}

View File

@@ -29,11 +29,14 @@ export function authenticateAndUpgradeRole(
id: string,
password: string,
conference: Object) {
return (dispatch: Dispatch<any>) => {
return (dispatch: Dispatch<any>, getState: Function) => {
const { password: roomPassword }
= getState()['features/base/conference'];
const process
= conference.authenticateAndUpgradeRole({
id,
password,
roomPassword,
onLoginSuccessful() {
// When the login succeeds, the process has completed half

View File

@@ -1,15 +1,23 @@
/* @flow */
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import Dialog from 'react-native-dialog';
import { connect as reduxConnect } from 'react-redux';
import type { Dispatch } from 'redux';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { toJid } from '../../../base/connection';
import { connect } from '../../../base/connection/actions.native';
import { _abstractMapStateToProps } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
import type { StyleType } from '../../../base/styles';
import { authenticateAndUpgradeRole, cancelLogin } from '../../actions.native';
// Register styles.
import './styles';
/**
* The type of the React {@link Component} props of {@link LoginDialog}.
*/
@@ -36,12 +44,22 @@ type Props = {
*/
_error: Object,
/**
* Extra handler for cancel functionality.
*/
_onCancel: Function,
/**
* The progress in the floating range between 0 and 1 of the authenticating
* and upgrading the role of the local participant/user.
*/
_progress: number,
/**
* The color-schemed stylesheet of this feature.
*/
_styles: StyleType,
/**
* Redux store dispatch method.
*/
@@ -50,7 +68,12 @@ type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function
t: Function,
/**
* Override the default visibility.
*/
visible: boolean
};
/**
@@ -97,6 +120,10 @@ type State = {
* of the configuration parameters.
*/
class LoginDialog extends Component<Props, State> {
static defaultProps = {
visible: true
};
/**
* Initializes a new LoginDialog instance.
*
@@ -127,40 +154,42 @@ class LoginDialog extends Component<Props, State> {
render() {
const {
_connecting: connecting,
t
t,
visible
} = this.props;
return (
<Dialog.Container
coverScreen = { false }
visible = { true }>
<Dialog.Title>
{ t('dialog.login') }
</Dialog.Title>
<Dialog.Input
autoCapitalize = { 'none' }
autoCorrect = { false }
onChangeText = { this._onUsernameChange }
placeholder = { 'user@domain.com' }
spellCheck = { false }
value = { this.state.username } />
<Dialog.Input
autoCapitalize = { 'none' }
onChangeText = { this._onPasswordChange }
placeholder = { t('dialog.userPassword') }
secureTextEntry = { true }
value = { this.state.password } />
<Dialog.Description>
{ this._renderMessage() }
</Dialog.Description>
<Dialog.Button
label = { t('dialog.Cancel') }
onPress = { this._onCancel } />
<Dialog.Button
disabled = { connecting }
label = { t('dialog.Ok') }
onPress = { this._onLogin } />
</Dialog.Container>
<View>
<Dialog.Container
visible = { visible }>
<Dialog.Title>
{ t('dialog.login') }
</Dialog.Title>
<Dialog.Input
autoCapitalize = { 'none' }
autoCorrect = { false }
onChangeText = { this._onUsernameChange }
placeholder = { 'user@domain.com' }
spellCheck = { false }
value = { this.state.username } />
<Dialog.Input
autoCapitalize = { 'none' }
onChangeText = { this._onPasswordChange }
placeholder = { t('dialog.userPassword') }
secureTextEntry = { true }
value = { this.state.password } />
<Dialog.Description>
{ this._renderMessage() }
</Dialog.Description>
<Dialog.Button
label = { t('dialog.Cancel') }
onPress = { this._onCancel } />
<Dialog.Button
disabled = { connecting }
label = { t('dialog.Ok') }
onPress = { this._onLogin } />
</Dialog.Container>
</View>
);
}
@@ -175,10 +204,12 @@ class LoginDialog extends Component<Props, State> {
_connecting: connecting,
_error: error,
_progress: progress,
_styles: styles,
t
} = this.props;
let messageKey;
let messageIsError = false;
const messageOptions = {};
if (progress && progress < 1) {
@@ -199,22 +230,34 @@ class LoginDialog extends Component<Props, State> {
this.props._configHosts)
&& credentials.password === this.state.password) {
messageKey = 'dialog.incorrectPassword';
messageIsError = true;
}
} else if (name) {
messageKey = 'dialog.connectErrorWithMsg';
messageOptions.msg = `${name} ${error.message}`;
messageIsError = true;
}
} else if (connecting) {
messageKey = 'connection.CONNECTING';
}
if (messageKey) {
return t(messageKey, messageOptions);
const message = t(messageKey, messageOptions);
const messageStyles
= messageIsError ? styles.errorMessage : styles.progressMessage;
return (
<Text style = { messageStyles }>
{ message }
</Text>
);
}
return null;
}
_onUsernameChange: (string) => void;
/**
* Called when user edits the username.
*
@@ -228,6 +271,8 @@ class LoginDialog extends Component<Props, State> {
});
}
_onPasswordChange: (string) => void;
/**
* Called when user edits the password.
*
@@ -241,6 +286,8 @@ class LoginDialog extends Component<Props, State> {
});
}
_onCancel: () => void;
/**
* Notifies this LoginDialog that it has been dismissed by cancel.
*
@@ -248,9 +295,14 @@ class LoginDialog extends Component<Props, State> {
* @returns {void}
*/
_onCancel() {
this.props.dispatch(cancelLogin());
const { _onCancel, dispatch } = this.props;
_onCancel && _onCancel();
dispatch(cancelLogin());
}
_onLogin: () => void;
/**
* Notifies this LoginDialog that the login button (OK) has been pressed by
* the user.
@@ -303,7 +355,8 @@ function _mapStateToProps(state) {
_configHosts: configHosts,
_connecting: Boolean(connecting) || Boolean(thenableWithCancel),
_error: connectionError || authenticateAndUpgradeRoleError,
_progress: progress
_progress: progress,
_styles: ColorSchemeRegistry.get(state, 'LoginDialog')
};
}

View File

@@ -1,10 +1,14 @@
// @flow
import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { openLoginDialog, cancelWaitForOwner } from '../../actions.native';
import { cancelWaitForOwner } from '../../actions.native';
import LoginDialog from './LoginDialog';
/**
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
@@ -38,9 +42,14 @@ class WaitForOwnerDialog extends Component<Props> {
constructor(props) {
super(props);
this.state = {
showLoginDialog: false
};
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onLogin = this._onLogin.bind(this);
this._onLoginDialogCancel = this._onLoginDialogCancel.bind(this);
}
/**
@@ -56,10 +65,17 @@ class WaitForOwnerDialog extends Component<Props> {
confirmLabel = 'dialog.IamHost'
descriptionKey = 'dialog.WaitForHostMsg'
onCancel = { this._onCancel }
onSubmit = { this._onLogin } />
onSubmit = { this._onLogin }>
<LoginDialog
// eslint-disable-next-line react/jsx-handler-names
_onCancel = { this._onLoginDialogCancel }
visible = { this.state.showLoginDialog } />
</ConfirmDialog>
);
}
_onCancel: () => void;
/**
* Called when the cancel button is clicked.
*
@@ -70,6 +86,8 @@ class WaitForOwnerDialog extends Component<Props> {
this.props.dispatch(cancelWaitForOwner());
}
_onLogin: () => void;
/**
* Called when the OK button is clicked.
*
@@ -77,7 +95,17 @@ class WaitForOwnerDialog extends Component<Props> {
* @returns {void}
*/
_onLogin() {
this.props.dispatch(openLoginDialog());
this.setState({ showLoginDialog: true });
}
/**
* Called when the nested login dialog is cancelled.
*
* @private
* @returns {void}
*/
_onLoginDialogCancel() {
this.setState({ showLoginDialog: false });
}
}

View File

@@ -0,0 +1,41 @@
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel } from '../../../base/styles';
/**
* The styles of the authentication feature.
*/
ColorSchemeRegistry.register('LoginDialog', {
/**
* The style of {@code Text} rendered by the {@code Dialog}s of the
* feature authentication.
*/
dialogText: {
margin: BoxModel.margin,
marginTop: BoxModel.margin * 2
},
/**
* The style used when an error message is rendered.
*/
errorMessage: {
color: schemeColor('errorText')
},
/**
* The style of {@code LoginDialog}.
*/
loginDialog: {
flex: 0,
flexDirection: 'column',
marginBottom: BoxModel.margin,
marginTop: BoxModel.margin
},
/**
* The style used then a progress message is rendered.
*/
progressMessage: {
color: schemeColor('text')
}
});

View File

@@ -51,10 +51,9 @@ MiddlewareRegistry.register(store => next => action => {
dispatch(hideLoginDialog());
const { authRequired, conference } = getState()['features/base/conference'];
const { passwordRequired } = getState()['features/base/connection'];
// Only end the meeting if we are not already inside and trying to upgrade.
if ((authRequired && !conference) || passwordRequired) {
if (authRequired && !conference) {
dispatch(maybeRedirectToWelcomePage());
}
}

View File

@@ -1,6 +1,6 @@
// @ts-ignore
import { assign } from '../base/redux/functions';
import ReducerRegistry from '../base/redux/ReducerRegistry';
// @flow
import { assign, ReducerRegistry } from '../base/redux';
import {
CANCEL_LOGIN,
@@ -10,13 +10,6 @@ import {
WAIT_FOR_OWNER
} from './actionTypes';
export interface IAuthenticationState {
error?: Object|undefined;
progress?: number|undefined;
thenableWithCancel?: Object|undefined;
waitForOwnerTimeoutID?: number;
}
/**
* Listens for actions which change the state of the authentication feature.
*
@@ -25,7 +18,7 @@ export interface IAuthenticationState {
* @param {string} action.type - Type of action.
* @returns {Object}
*/
ReducerRegistry.register('features/authentication', (state: IAuthenticationState = {}, action: any) => {
ReducerRegistry.register('features/authentication', (state = {}, action) => {
switch (action.type) {
case CANCEL_LOGIN:
return assign(state, {

View File

@@ -1,9 +1,11 @@
// @flow
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
/**
* Mapping between a media type and the witelist reducer key.
*/
export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: { [key: string]: string } = {
export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: {[key: MediaType]: string} = {
[MEDIA_TYPE.AUDIO]: 'audioWhitelist',
[MEDIA_TYPE.VIDEO]: 'videoWhitelist'
};
@@ -11,7 +13,7 @@ export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: { [key: string]: string } = {
/**
* Mapping between a media type and the pending reducer key.
*/
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: {[key: string]: string} = {
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: {[key: MediaType]: string} = {
[MEDIA_TYPE.AUDIO]: 'pendingAudio',
[MEDIA_TYPE.VIDEO]: 'pendingVideo'
};

View File

@@ -2,7 +2,6 @@
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
import { isLocalParticipantModerator } from '../base/participants/functions';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { MEDIA_TYPE_TO_WHITELIST_STORE_KEY, MEDIA_TYPE_TO_PENDING_STORE_KEY } from './constants';
@@ -50,7 +49,7 @@ export const isEnabled = (mediaType: MediaType) => (state: Object) => isEnabledF
export const isSupported = () => (state: Object) => {
const { conference } = state['features/base/conference'];
return Boolean(!isInBreakoutRoom(state) && conference?.isAVModerationSupported());
return conference ? conference.isAVModerationSupported() : false;
};
/**

View File

@@ -80,8 +80,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
let descriptionKey;
let titleKey;
let uid;
const localParticipant = getLocalParticipant(getState);
const raisedHand = hasRaisedHand(localParticipant);
switch (action.mediaType) {
case MEDIA_TYPE.AUDIO: {
@@ -104,7 +102,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
dispatch(showNotification({
customActionNameKey: [ 'notify.raiseHandAction' ],
customActionHandler: [ () => batch(() => {
!raisedHand && dispatch(raiseHand(true));
dispatch(raiseHand(true));
dispatch(hideNotification(uid));
}) ],
descriptionKey,

View File

@@ -1,11 +1,12 @@
/* @flow */
import { MEDIA_TYPE } from '../base/media/constants';
import type { MediaType } from '../base/media/constants';
import {
PARTICIPANT_LEFT,
PARTICIPANT_UPDATED
// @ts-ignore
} from '../base/participants';
import ReducerRegistry from '../base/redux/ReducerRegistry';
import { ReducerRegistry } from '../base/redux';
import {
DISABLE_MODERATION,
@@ -28,19 +29,8 @@ const initialState = {
pendingVideo: []
};
export interface IAVModerationState {
audioModerationEnabled: boolean;
videoModerationEnabled: boolean;
audioWhitelist: { [id: string]: boolean };
videoWhitelist: { [id: string]: boolean };
pendingAudio: Array<{ id: string }>;
pendingVideo: Array<{ id: string }>;
audioUnmuteApproved?: boolean|undefined;
videoUnmuteApproved?: boolean|undefined;
}
/**
* Updates a participant in the state for the specified media type.
Updates a participant in the state for the specified media type.
*
* @param {MediaType} mediaType - The media type.
* @param {Object} participant - Information about participant to be modified.
@@ -48,11 +38,11 @@ export interface IAVModerationState {
* @private
* @returns {boolean} - Whether state instance was modified.
*/
function _updatePendingParticipant(mediaType: MediaType, participant: any, state: any = {}) {
function _updatePendingParticipant(mediaType: MediaType, participant, state: Object = {}) {
let arrayItemChanged = false;
const storeKey = MEDIA_TYPE_TO_PENDING_STORE_KEY[mediaType];
const arr = state[storeKey];
const newArr = arr.map((pending: { id: string} ) => {
const newArr = arr.map(pending => {
if (pending.id === participant.id) {
arrayItemChanged = true;
@@ -74,7 +64,7 @@ function _updatePendingParticipant(mediaType: MediaType, participant: any, state
return false;
}
ReducerRegistry.register('features/av-moderation', (state: IAVModerationState = initialState, action: any) => {
ReducerRegistry.register('features/av-moderation', (state = initialState, action) => {
switch (action.type) {
case DISABLE_MODERATION: {

View File

@@ -1,12 +1,10 @@
import ReducerRegistry from '../redux/ReducerRegistry';
// @flow
import { ReducerRegistry } from '../redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
export interface IAppState {
app?: Object|undefined;
}
ReducerRegistry.register('features/base/app', (state: IAppState = {}, action) => {
ReducerRegistry.register('features/base/app', (state = {}, action) => {
switch (action.type) {
case APP_WILL_MOUNT: {
const { app } = action;

View File

@@ -1,17 +1,16 @@
import ReducerRegistry from '../redux/ReducerRegistry';
// @flow
import { ReducerRegistry } from '../redux';
import { SET_AUDIO_ONLY } from './actionTypes';
export interface IAudioOnlyState {
enabled: boolean
}
const DEFAULT_STATE = {
enabled: false
};
ReducerRegistry.register('features/base/audio-only', (state: IAudioOnlyState = DEFAULT_STATE, action) => {
ReducerRegistry.register('features/base/audio-only', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_AUDIO_ONLY:
return {

View File

@@ -29,7 +29,11 @@ export default {
inviteButtonBackground: 'rgb(0, 119, 225)',
onVideoText: 'white'
},
'Dialog': {},
'Dialog': {
border: 'rgba(0, 3, 6, 0.6)',
buttonBackground: ColorPalette.blue,
buttonLabel: ColorPalette.white
},
'Header': {
background: ColorPalette.blue,
icon: ColorPalette.white,
@@ -37,6 +41,10 @@ export default {
statusBarContent: ColorPalette.white,
text: ColorPalette.white
},
'Modal': {},
'LargeVideo': {
background: '#040404'
},
'Toolbox': {
button: 'rgb(255, 255, 255)',
buttonToggled: 'rgb(38, 58, 76)',

View File

@@ -206,16 +206,6 @@ export const SEND_TONES = 'SEND_TONES';
*/
export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
/**
* The type of (redux) action which sets the obfuscated room name.
*
* {
* type: SET_OBFUSCATED_ROOM,
* obfuscatedRoom: string
* }
*/
export const SET_OBFUSCATED_ROOM = 'SET_OBFUSCATED_ROOM';
/**
* The type of (redux) action which updates the current known status of the
* Mute Reactions Sound feature.

View File

@@ -56,7 +56,6 @@ import {
P2P_STATUS_CHANGED,
SEND_TONES,
SET_FOLLOW_ME,
SET_OBFUSCATED_ROOM,
SET_PASSWORD,
SET_PASSWORD_FAILED,
SET_ROOM,
@@ -805,24 +804,6 @@ export function setPassword(
};
}
/**
* Sets the obfuscated room name of the conference to be joined.
*
* @param {(string)} obfuscatedRoom - Obfuscated room name.
* @param {(string)} obfuscatedRoomSource - The room name that was obfuscated.
* @returns {{
* type: SET_OBFUSCATED_ROOM,
* room: string
* }}
*/
export function setObfuscatedRoom(obfuscatedRoom: string, obfuscatedRoomSource: string) {
return {
type: SET_OBFUSCATED_ROOM,
obfuscatedRoom,
obfuscatedRoomSource
};
}
/**
* Sets (the name of) the room of the conference to be joined.
*

View File

@@ -1,6 +1,5 @@
// @flow
import { sha512_256 as sha512 } from 'js-sha512';
import _ from 'lodash';
import { getName } from '../../app/functions';
@@ -20,7 +19,6 @@ import {
safeDecodeURIComponent
} from '../util';
import { setObfuscatedRoom } from './actions';
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
@@ -300,47 +298,6 @@ export function getRoomName(state: Object): string {
return getConferenceState(state).room;
}
/**
* Get an obfuscated room name or create and persist it if it doesn't exists.
*
* @param {Object} state - The current state of the app.
* @param {Function} dispatch - The Redux dispatch function.
* @returns {string} - Obfuscated room name.
*/
export function getOrCreateObfuscatedRoomName(state: Object, dispatch: Function) {
let { obfuscatedRoom } = getConferenceState(state);
const { obfuscatedRoomSource } = getConferenceState(state);
const room = getRoomName(state);
// On native mobile the store doesn't clear when joining a new conference so we might have the obfuscatedRoom
// stored even though a different room was joined.
// Check if the obfuscatedRoom was already computed for the current room.
if (!obfuscatedRoom || (obfuscatedRoomSource !== room)) {
obfuscatedRoom = sha512(room);
dispatch(setObfuscatedRoom(obfuscatedRoom, room));
}
return obfuscatedRoom;
}
/**
* Analytics may require an obfuscated room name, this functions decides based on a config if the normal or
* obfuscated room name should be returned.
*
* @param {Object} state - The current state of the app.
* @param {Function} dispatch - The Redux dispatch function.
* @returns {string} - Analytics room name.
*/
export function getAnalyticsRoomName(state: Object, dispatch: Function) {
const { analysis: { obfuscateRoomName = false } = {} } = state['features/base/config'];
if (obfuscateRoomName) {
return getOrCreateObfuscatedRoomName(state, dispatch);
}
return getRoomName(state);
}
/**
* Returns the result of getWiFiStats from the global NS or does nothing
* (returns empty result).

View File

@@ -1,6 +1,6 @@
// @flow
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
import { setAudioOnly } from '../audio-only';
import JitsiMeetJS from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
@@ -41,7 +41,7 @@ function _toggleScreenSharing(enabled, store) {
}
} else {
dispatch(destroyLocalDesktopTrackIfExists());
setPictureInPictureEnabled(true);
setPictureInPictureDisabled(false);
}
}
@@ -54,7 +54,7 @@ function _toggleScreenSharing(enabled, store) {
* @returns {void}
*/
function _startScreenSharing(dispatch, state) {
setPictureInPictureEnabled(false);
setPictureInPictureDisabled(true);
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
.then(tracks => {
@@ -73,6 +73,6 @@ function _startScreenSharing(dispatch, state) {
.catch(error => {
console.log('ERROR creating ScreeSharing stream ', error);
setPictureInPictureEnabled(true);
setPictureInPictureDisabled(false);
});
}

View File

@@ -44,11 +44,9 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case CONFERENCE_FAILED: {
const errorName = action.error?.name;
if (enableForcedReload && errorName === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
dispatch(setSkipPrejoinOnReload(true));
}
enableForcedReload
&& action.error?.name === JitsiConferenceErrors.CONFERENCE_RESTARTED
&& dispatch(setSkipPrejoinOnReload(true));
break;
}

View File

@@ -1,12 +1,9 @@
// @ts-ignore
// @flow
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
// @ts-ignore
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection';
// @ts-ignore
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
// @ts-ignore
import { assign, set } from '../redux';
import ReducerRegistry from '../redux/ReducerRegistry';
import { assign, ReducerRegistry, set } from '../redux';
import {
AUTH_STATUS_CHANGED,
@@ -21,14 +18,12 @@ import {
LOCK_STATE_CHANGED,
P2P_STATUS_CHANGED,
SET_FOLLOW_ME,
SET_OBFUSCATED_ROOM,
SET_PASSWORD,
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_START_MUTED_POLICY,
SET_START_REACTIONS_MUTED
} from './actionTypes';
// @ts-ignore
import { isRoomValid } from './functions';
const DEFAULT_STATE = {
@@ -42,26 +37,13 @@ const DEFAULT_STATE = {
passwordRequired: undefined
};
export interface IConferenceState {
conference: Object|undefined;
e2eeSupported: boolean|undefined;
joining: Object|undefined;
leaving: Object|undefined;
locked: string|undefined;
membersOnly: boolean|undefined;
password: string|undefined;
passwordRequired: boolean|undefined;
authEnabled?: boolean|undefined;
authLogin?: string|undefined;
}
/**
* Listen for actions that contain the conference object, so that it can be
* stored for use by other action creators.
*/
ReducerRegistry.register(
'features/base/conference',
(state: IConferenceState = DEFAULT_STATE, action: any) => {
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case AUTH_STATUS_CHANGED:
return _authStatusChanged(state, action);
@@ -106,12 +88,6 @@ ReducerRegistry.register(
case SET_LOCATION_URL:
return set(state, 'room', undefined);
case SET_OBFUSCATED_ROOM:
return { ...state,
obfuscatedRoom: action.obfuscatedRoom,
obfuscatedRoomSource: action.obfuscatedRoomSource
};
case SET_PASSWORD:
return _setPassword(state, action);
@@ -142,7 +118,7 @@ ReducerRegistry.register(
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _authStatusChanged(state: any, { authEnabled, authLogin }: {authEnabled: boolean, authLogin: string}) {
function _authStatusChanged(state, { authEnabled, authLogin }) {
return assign(state, {
authEnabled,
authLogin
@@ -159,7 +135,7 @@ function _authStatusChanged(state: any, { authEnabled, authLogin }: {authEnabled
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _conferenceFailed(state: any, { conference, error }: {conference: Object, error: any}) {
function _conferenceFailed(state, { conference, error }) {
// The current (similar to getCurrentConference in
// base/conference/functions.any.js) conference which is joining or joined:
const conference_ = state.conference || state.joining;
@@ -225,7 +201,7 @@ function _conferenceFailed(state: any, { conference, error }: {conference: Objec
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _conferenceJoined(state: any, { conference }: {conference: any}) {
function _conferenceJoined(state, { conference }) {
// FIXME The indicator which determines whether a JitsiConference is locked
// i.e. password-protected is private to lib-jitsi-meet. However, the
// library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
@@ -271,7 +247,7 @@ function _conferenceJoined(state: any, { conference }: {conference: any}) {
* @returns {Object} The next/new state of the feature base/conference after the
* reduction of the specified action.
*/
function _conferenceLeftOrWillLeave(state: any, { conference, type }: {conference: Object, type: string}) {
function _conferenceLeftOrWillLeave(state, { conference, type }) {
const nextState = { ...state };
// The redux action CONFERENCE_LEFT is the last time that we should be
@@ -325,7 +301,7 @@ function _conferenceLeftOrWillLeave(state: any, { conference, type }: {conferenc
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _conferenceWillJoin(state: any, { conference }: {conference: Object}) {
function _conferenceWillJoin(state, { conference }) {
return assign(state, {
error: undefined,
joining: conference
@@ -342,7 +318,7 @@ function _conferenceWillJoin(state: any, { conference }: {conference: Object}) {
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _lockStateChanged(state: any, { conference, locked }: {conference: Object, locked: boolean}) {
function _lockStateChanged(state, { conference, locked }) {
if (state.conference !== conference) {
return state;
}
@@ -363,7 +339,7 @@ function _lockStateChanged(state: any, { conference, locked }: {conference: Obje
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _p2pStatusChanged(state: any, action: any) {
function _p2pStatusChanged(state, action) {
return set(state, 'p2p', action.p2p);
}
@@ -376,7 +352,7 @@ function _p2pStatusChanged(state: any, action: any) {
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setPassword(state: any, { conference, method, password }: {conference: any, method: Object, password: string}) {
function _setPassword(state, { conference, method, password }) {
switch (method) {
case conference.join:
return assign(state, {
@@ -423,7 +399,7 @@ function _setPassword(state: any, { conference, method, password }: {conference:
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setRoom(state: any, action: any) {
function _setRoom(state, action) {
let { room } = action;
if (!isRoomValid(room)) {

View File

@@ -1,467 +0,0 @@
type ToolbarButtons = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
'dock-iframe' |
'download' |
'embedmeeting' |
'etherpad' |
'feedback' |
'filmstrip' |
'fullscreen' |
'hangup' |
'help' |
'highlight' |
'invite' |
'linktosalesforce' |
'livestreaming' |
'microphone' |
'participants-pane' |
'profile' |
'raisehand' |
'recording' |
'security' |
'select-background' |
'settings' |
'shareaudio' |
'sharedvideo' |
'shortcuts' |
'stats' |
'tileview' |
'toggle-camera' |
'undock-iframe' |
'videoquality' |
'__end';
type ButtonsWithNotifyClick = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
'download' |
'embedmeeting' |
'etherpad' |
'feedback' |
'filmstrip' |
'fullscreen' |
'hangup' |
'help' |
'invite' |
'livestreaming' |
'microphone' |
'mute-everyone' |
'mute-video-everyone' |
'participants-pane' |
'profile' |
'raisehand' |
'recording' |
'security' |
'select-background' |
'settings' |
'shareaudio' |
'sharedvideo' |
'shortcuts' |
'stats' |
'tileview' |
'toggle-camera' |
'videoquality' |
'add-passcode' |
'__end';
type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
'E2EE_OFF_SOUND' |
'E2EE_ON_SOUND' |
'INCOMING_MSG_SOUND' |
'KNOCKING_PARTICIPANT_SOUND' |
'LIVE_STREAMING_OFF_SOUND' |
'LIVE_STREAMING_ON_SOUND' |
'NO_AUDIO_SIGNAL_SOUND' |
'NOISY_AUDIO_INPUT_SOUND' |
'OUTGOING_CALL_EXPIRED_SOUND' |
'OUTGOING_CALL_REJECTED_SOUND' |
'OUTGOING_CALL_RINGING_SOUND' |
'OUTGOING_CALL_START_SOUND' |
'PARTICIPANT_JOINED_SOUND' |
'PARTICIPANT_LEFT_SOUND' |
'RAISE_HAND_SOUND' |
'REACTION_SOUND' |
'RECORDING_OFF_SOUND' |
'RECORDING_ON_SOUND' |
'TALK_WHILE_MUTED_SOUND';
export interface IConfig {
hosts?: {
domain: string;
anonymousdomain?: string;
authdomain?: string;
focus?: string;
muc: string;
};
bosh?: string;
websocket?: string;
focusUserJid?: string;
testing?: {
disableE2EE?: boolean;
enableThumbnailReordering?: boolean;
mobileXmppWsThreshold?: number;
p2pTestMode?: boolean;
testMode?: boolean;
noAutoPlayVideo?: boolean;
capScreenshareBitrate?: number;
setScreenSharingResolutionConstraints?: boolean;
callStatsThreshold?: number;
};
flags?: {
sourceNameSignaling?: boolean;
sendMultipleVideoStreams?: boolean;
};
disableModeratorIndicator?: boolean;
disableReactions?: boolean;
disableReactionsModeration?: boolean;
disablePolls?: boolean;
disableSelfView?: boolean;
disableSelfViewSettings?: boolean;
screenshotCapture?: {
enabled?: boolean;
mode?: 'always' | 'recording';
};
webrtcIceUdpDisable?: boolean;
webrtcIceTcpDisable?: boolean;
enableUnifiedOnChrome?: boolean;
disableAudioLevels?: boolean;
audioLevelsInterval?: number;
enableNoAudioDetection?: boolean;
enableSaveLogs?: boolean;
disableShowMoreStats?: boolean;
enableNoisyMicDetection?: boolean;
startAudioOnly?: boolean;
startAudioMuted?: boolean;
startWithAudioMuted?: boolean;
startSilent?: boolean;
enableOpusRed?: boolean;
audioQuality?: {
stereo?: boolean;
opusMaxAverageBitrate?: number|null;
};
stereo?: boolean;
opusMaxAverageBitrate?: number;
resolution?: number;
disableRemoveRaisedHandOnFocus?: boolean;
disableSpeakerStatsSearch?: boolean;
speakerStatsOrder?: Array<'role'|'name'|'hasLeft'>;
maxFullResolutionParticipants?: number;
constraints?: {
video?: {
height?: {
ideal?: number;
max?: number;
min?: number;
}
}
};
disableSimulcast?: boolean;
enableLayerSuspension?: boolean;
startVideoMuted?: number;
startWithVideoMuted?: boolean;
preferH264?: boolean;
disableH264?: boolean;
desktopSharingFrameRate?: {
min?: number;
max?: number;
};
startScreenSharing?: boolean;
fileRecordingsEnabled?: boolean;
dropbox?: {
appKey: string;
redirectURI?: string;
};
recordingService?: {
enabled?: boolean;
sharingEnabled?: boolean;
hideStorageWarning?: boolean;
};
fileRecordingsServiceEnabled?: boolean;
fileRecordingsServiceSharingEnabled?: boolean;
liveStreamingEnabled?: boolean;
localRecording?: {
disable?: boolean;
notifyAllParticipants?: boolean;
};
transcribingEnabled?: boolean;
transcribeWithAppLanguage?: boolean;
preferredTranscribeLanguage?: string;
autoCaptionOnRecord?: boolean;
transcription?: {
enabled?: boolean;
useAppLanguage?: boolean;
preferredLanguage?: string;
disableStartForAll?: boolean;
autoCaptionOnRecord?: boolean;
};
channelLastN?: number;
connectionIndicators?: {
autoHide?: boolean;
autoHideTimeout?: number;
disabled?: boolean;
disableDetails?: boolean;
inactiveDisabled?: boolean;
};
startLastN?: number;
lastNLimits?: {
[key: number]: number;
};
useNewBandwidthAllocationStrategy?: boolean;
videoQuality?: {
disabledCodec?: string;
preferredCodec?: string;
enforcePreferredCodec?: boolean;
maxBitratesVideo?: {
[key: string]: {
low?: number;
standard?: number;
high?: number;
}
};
minHeightForQualityLvl: {
[key: number]: string;
};
resizeDesktopForPresenter?: boolean;
};
notificationTimeouts?: {
short?: number;
medium?: number;
long?: number;
};
recordingLimit?: {
limit?: number;
appName?: string;
appURL?: string;
};
disableRtx?: boolean;
disableBeforeUnloadHandlers?: boolean;
enableTcc?: boolean;
enableRemb?: boolean;
enableIceRestart?: boolean;
enableForcedReload?: boolean;
useTurnUdp?: boolean;
enableEncodedTransformSupport?: boolean;
disableResponsiveTiles?: boolean;
hideLobbyButton?: boolean;
autoKnockLobby?: boolean;
enableLobbyChat?: boolean;
hideAddRoomButton?: boolean;
requireDisplayName?: boolean;
enableWelcomePage?: boolean;
disableShortcuts?: boolean;
disableInitialGUM?: boolean;
enableClosePage?: boolean;
disable1On1Mode?: boolean|null;
defaultLocalDisplayName?: string;
defaultRemoteDisplayName?: string;
hideDisplayName?: boolean;
hideDominantSpeakerBadge?: boolean;
defaultLanguage?: string;
disableProfile?: boolean;
hideEmailInSettings?: boolean;
enableFeaturesBasedOnToken?: boolean;
roomPasswordNumberOfDigits?: number;
noticeMessage?: string;
enableCalendarIntegration?: boolean;
prejoinConfig?: {
enabled?: boolean;
hideDisplayName?: boolean;
hideExtraJoinButtons?: Array<string>;
};
prejoinPageEnabled?: boolean;
readOnlyName?: boolean;
openSharedDocumentOnJoin?: boolean;
enableInsecureRoomNameWarning?: boolean;
enableAutomaticUrlCopy?: boolean;
corsAvatarURLs?: Array<string>;
gravatarBaseURL?: string;
gravatar?: {
baseUrl?: string;
disabled?: boolean;
};
inviteAppName?: string|null;
toolbarButtons?: Array<ToolbarButtons>;
toolbarConfig?: {
initialTimeout?: number;
timeout?: number;
alwaysVisible?: boolean;
autoHideWhileChatIsOpen?: boolean;
};
buttonsWithNotifyClick?: Array<ButtonsWithNotifyClick | { key: ButtonsWithNotifyClick; preventExecution: boolean }>;
hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>;
gatherStats?: boolean;
pcStatsInterval?: number;
callStatsID?: string;
callStatsSecret?: string;
callStatsConfigParams?: {
disableBeforeUnloadHandler?: boolean;
applicationVersion?: string;
disablePrecalltest?: boolean;
siteID?: string;
additionalIDs?: {
customerID?: string;
tenantID?: string;
productName?: string;
meetingsName?: string;
serverName?: string;
pbxID?: string;
pbxExtensionID?: string;
fqExtensionID?: string;
sessionID?: string;
};
collectLegacyStats?: boolean;
collectIP?: boolean;
};
enableDisplayNameInStats?: boolean;
enableEmailInStats?: boolean;
faceLandmarks?: {
enableFaceCentering?: boolean;
enableFaceExpressionsDetection?: boolean;
enableDisplayFaceExpressions?: boolean;
enableRTCStats?: boolean;
faceCenteringThreshold?: number;
captureInterval?: number;
};
feedbackPercentage?: number;
disableThirdPartyRequests?: boolean;
p2p?: {
enabled?: boolean;
enableUnifiedOnChrome?: boolean;
iceTransportPolicy?: string;
preferH264?: boolean;
preferredCodec?: string;
disableH264?: boolean;
disabledCodec?: string;
backToP2PDelay?: number;
stunServers?: Array<{urls: string}>;
};
analytics?: {
disabled?: boolean;
googleAnalyticsTrackingId?: string;
matomoEndpoint?: string;
matomoSiteID?: string;
amplitudeAPPKey?: string;
obfuscateRoomName?: boolean;
rtcstatsEnabled?: boolean;
rtcstatsEndpoint?: string;
rtcstatsPolIInterval?: number;
scriptURLs?: Array<string>;
};
apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>;
deploymentInfo?: {
shard?: string;
region?: string;
userRegion?: string;
};
disabledSounds?: Array<Sounds>;
disableRecordAudioNotification?: boolean;
disableJoinLeaveSounds?: boolean;
disableIncomingMessageSound?: boolean;
chromeExtensionBanner?: {
url?: string;
edgeUrl?: string;
chromeExtensionsInfo?: Array<{id: string; path: string}>;
};
e2ee?: {
labels?: {
tooltip?: string;
description?: string;
label?: string;
warning?: string;
};
externallyManagedKey?: boolean;
e2eeLabels?: {
tooltip?: string;
description?: string;
label?: string;
warning?: string;
};
};
e2eeLabels?: {
tooltip?: string;
description?: string;
label?: string;
warning?: string;
};
e2eping?: {
enabled?: boolean;
numRequests?: number;
maxConferenceSize?: number;
maxMessagesPerSecond?: number;
};
_desktopSharingSourceDevice?: string;
disableDeepLinking?: boolean;
disableLocalVideoFlip?: boolean;
doNotFlipLocalVideo?: boolean;
disableInviteFunctions?: boolean;
doNotStoreRoom?: boolean;
deploymentUrls?: {
userDocumentationURL?: string;
downloadAppsUrl?: string;
};
remoteVideoMenu?: {
disabled?: boolean;
disableKick?: boolean;
disableGrantModerator?: boolean;
disablePrivateChat?: boolean;
};
salesforceUrl?: string;
disableRemoteMute?: boolean;
enableLipSync?: boolean;
dynamicBrandingUrl?: string;
participantsPane?: {
hideModeratorSettingsTab?: boolean;
hideMoreActionsButton?: boolean;
hideMuteAllButton?: boolean;
};
breakoutRooms?: {
hideAddRoomButton?: boolean;
hideAutoAssignButton?: boolean;
hideJoinRoomButton?: boolean;
};
disableAddingBackgroundImages?: boolean;
disableScreensharingVirtualBackground?: boolean;
backgroundAlpha?: number;
moderatedRoomServiceUrl?: string;
disableTileView?: boolean;
disableTileEnlargement?: boolean;
conferenceInfo?: {
alwaysVisible?: Array<string>;
autoHide?: Array<string>;
};
hideConferenceSubject?: boolean;
hideConferenceTimer?: boolean;
hideRecordingLabel?: boolean;
hideParticipantsStats?: boolean;
subject?: string;
localSubject?: string;
useHostPageLocalStorage?: boolean;
etherpad_base?: string;
dialInNumbersUrl?: string;
dialInConfCodeUrl?: string;
brandingRoomAlias?: string;
mouseMoveCallbackInterval?: number;
notifications?: Array<string>;
disabledNotifications?: Array<string>;
disableFilmstripAutohiding?: boolean;
filmstrip?: {
disableResizable?: boolean;
disableStageFilmstrip?: boolean;
disableTopPanel?: boolean;
minParticipantCountForTopPanel?: number;
};
tileView?: {
numberOfVisibleTiles?: number;
};
disableChatSmileys?: boolean;
giphy?: {
enabled?: boolean;
sdkKey?: '';
displayMode?: 'all' | 'tile' | 'chat';
tileTime?: number;
};
locationURL?: string;
}

View File

@@ -83,7 +83,6 @@ export default [
'debugAudioLevels',
'defaultLocalDisplayName',
'defaultRemoteDisplayName',
'deploymentUrls',
'desktopSharingFrameRate',
'desktopSharingSources',
'disable1On1Mode',
@@ -95,7 +94,6 @@ export default [
'disableBeforeUnloadHandlers',
'disableChatSmileys',
'disableDeepLinking',
'disabledNotifications',
'disabledSounds',
'disableFilmstripAutohiding',
'disableInitialGUM',
@@ -193,7 +191,6 @@ export default [
'openSharedDocumentOnJoin',
'opusMaxAverageBitrate',
'p2p',
'participantsPane',
'pcStatsInterval',
'preferH264',
'preferredCodec',
@@ -222,7 +219,6 @@ export default [
'toolbarConfig',
'tileView',
'transcribingEnabled',
'transcription',
'useHostPageLocalStorage',
'useTurnUdp',
'videoQuality',

View File

@@ -1,9 +1,9 @@
// @flow
import _ from 'lodash';
import { CONFERENCE_INFO } from '../../conference/components/constants';
// @ts-ignore
import { equals } from '../redux';
import ReducerRegistry from '../redux/ReducerRegistry';
import { equals, ReducerRegistry } from '../redux';
import {
UPDATE_CONFIG,
@@ -12,11 +12,9 @@ import {
SET_CONFIG,
OVERWRITE_CONFIG
} from './actionTypes';
import { IConfig } from './configType';
// @ts-ignore
import { _cleanupConfig } from './functions';
declare var interfaceConfig: any;
declare var interfaceConfig: Object;
/**
* The initial state of the feature base/config when executing in a
@@ -28,7 +26,7 @@ declare var interfaceConfig: any;
*
* @type {Object}
*/
const INITIAL_NON_RN_STATE: IConfig = {
const INITIAL_NON_RN_STATE = {
};
/**
@@ -40,7 +38,7 @@ const INITIAL_NON_RN_STATE: IConfig = {
*
* @type {Object}
*/
const INITIAL_RN_STATE: IConfig = {
const INITIAL_RN_STATE = {
analytics: {},
// FIXME The support for audio levels in lib-jitsi-meet polls the statistics
@@ -63,14 +61,14 @@ const INITIAL_RN_STATE: IConfig = {
* Mapping between old configs controlling the conference info headers visibility and the
* new configs. Needed in order to keep backwards compatibility.
*/
const CONFERENCE_HEADER_MAPPING: any = {
const CONFERENCE_HEADER_MAPPING = {
hideConferenceTimer: [ 'conference-timer' ],
hideConferenceSubject: [ 'subject' ],
hideParticipantsStats: [ 'participants-count' ],
hideRecordingLabel: [ 'recording' ]
};
ReducerRegistry.register('features/base/config', (state: IConfig = _getInitialState(), action: any) => {
ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
switch (action.type) {
case UPDATE_CONFIG:
return _updateConfig(state, action);
@@ -141,12 +139,12 @@ function _getInitialState() {
* Reduces a specific Redux action SET_CONFIG of the feature
* base/lib-jitsi-meet.
*
* @param {IConfig} state - The Redux state of the feature base/config.
* @param {Object} state - The Redux state of the feature base/config.
* @param {Action} action - The Redux action SET_CONFIG to reduce.
* @private
* @returns {Object} The new state after the reduction of the specified action.
*/
function _setConfig(state: IConfig, { config }: {config: IConfig}) {
function _setConfig(state, { config }) {
// The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
// does not download them at runtime from the deployment on which it will
// join a conference. The downloading is planned for implementation in the
@@ -189,10 +187,10 @@ function _setConfig(state: IConfig, { config }: {config: IConfig}) {
/**
* Processes the conferenceInfo object against the defaults.
*
* @param {IConfig} config - The old config.
* @param {Object} config - The old config.
* @returns {Object} The processed conferenceInfo object.
*/
function _getConferenceInfo(config: IConfig) {
function _getConferenceInfo(config) {
const { conferenceInfo } = config;
if (conferenceInfo) {
@@ -209,7 +207,11 @@ function _getConferenceInfo(config: IConfig) {
/**
* Constructs a new config {@code Object}, if necessary, out of a specific
* interface_config {@code Object} which is in the latest format supported by jitsi-meet.
* config {@code Object} which is in the latest format supported by jitsi-meet.
* Such a translation from an old config format to a new/the latest config
* format is necessary because the mobile app bundles jitsi-meet and
* lib-jitsi-meet at build time and does not download them at runtime from the
* deployment on which it will join a conference.
*
* @param {Object} oldValue - The config {@code Object} which may or may not be
* in the latest form supported by jitsi-meet and from which a new config
@@ -217,11 +219,11 @@ function _getConferenceInfo(config: IConfig) {
* @returns {Object} A config {@code Object} which is in the latest format
* supported by jitsi-meet.
*/
function _translateInterfaceConfig(oldValue: IConfig) {
function _translateLegacyConfig(oldValue: Object) {
const newValue = oldValue;
if (!Array.isArray(oldValue.toolbarButtons)
&& typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
&& typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
}
@@ -229,7 +231,6 @@ function _translateInterfaceConfig(oldValue: IConfig) {
oldValue.toolbarConfig = {};
}
newValue.toolbarConfig = oldValue.toolbarConfig || {};
if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean'
&& typeof interfaceConfig === 'object'
&& typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') {
@@ -248,80 +249,39 @@ function _translateInterfaceConfig(oldValue: IConfig) {
newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT;
}
if (!oldValue.connectionIndicators
&& typeof interfaceConfig === 'object'
&& (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
newValue.connectionIndicators = {
disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT
};
}
if (oldValue.disableModeratorIndicator === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
}
if (oldValue.defaultLocalDisplayName === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
}
if (oldValue.defaultRemoteDisplayName === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
}
return newValue;
}
/**
* Constructs a new config {@code Object}, if necessary, out of a specific
* config {@code Object} which is in the latest format supported by jitsi-meet.
* Such a translation from an old config format to a new/the latest config
* format is necessary because the mobile app bundles jitsi-meet and
* lib-jitsi-meet at build time and does not download them at runtime from the
* deployment on which it will join a conference.
*
* @param {Object} oldValue - The config {@code Object} which may or may not be
* in the latest form supported by jitsi-meet and from which a new config
* {@code Object} is to be constructed if necessary.
* @returns {Object} A config {@code Object} which is in the latest format
* supported by jitsi-meet.
*/
function _translateLegacyConfig(oldValue: IConfig) {
const newValue = _translateInterfaceConfig(oldValue);
// Translate deprecated config values to new config values.
const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key as keyof IConfig]);
const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key]);
if (filteredConferenceInfo.length) {
newValue.conferenceInfo = _getConferenceInfo(oldValue);
filteredConferenceInfo.forEach(key => {
newValue.conferenceInfo = oldValue.conferenceInfo ?? {};
// hideRecordingLabel does not mean not render it at all, but autoHide it
if (key === 'hideRecordingLabel') {
newValue.conferenceInfo.alwaysVisible
= (newValue.conferenceInfo?.alwaysVisible ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
= newValue.conferenceInfo.alwaysVisible.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
newValue.conferenceInfo.autoHide
= _.union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
} else {
newValue.conferenceInfo.alwaysVisible
= (newValue.conferenceInfo.alwaysVisible ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
= newValue.conferenceInfo.alwaysVisible.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
newValue.conferenceInfo.autoHide
= (newValue.conferenceInfo.autoHide ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
= newValue.conferenceInfo.autoHide.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
}
});
}
if (!oldValue.connectionIndicators
&& typeof interfaceConfig === 'object'
&& (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
newValue.connectionIndicators = {
disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT
};
}
newValue.prejoinConfig = oldValue.prejoinConfig || {};
if (oldValue.hasOwnProperty('prejoinPageEnabled')
&& !newValue.prejoinConfig.hasOwnProperty('enabled')
@@ -355,15 +315,33 @@ function _translateLegacyConfig(oldValue: IConfig) {
};
}
if (oldValue.disableModeratorIndicator === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
}
newValue.e2ee = newValue.e2ee || {};
if (oldValue.e2eeLabels) {
newValue.e2ee.e2eeLabels = oldValue.e2eeLabels;
}
if (oldValue.defaultLocalDisplayName === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
}
newValue.defaultLocalDisplayName
= newValue.defaultLocalDisplayName || 'me';
if (oldValue.defaultRemoteDisplayName === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
}
if (oldValue.hideAddRoomButton) {
newValue.breakoutRooms = {
/* eslint-disable-next-line no-extra-parens */
@@ -375,46 +353,6 @@ function _translateLegacyConfig(oldValue: IConfig) {
newValue.defaultRemoteDisplayName
= newValue.defaultRemoteDisplayName || 'Fellow Jitster';
newValue.transcription = newValue.transcription || {};
if (oldValue.transcribingEnabled !== undefined) {
newValue.transcription = {
...newValue.transcription,
enabled: oldValue.transcribingEnabled
};
}
if (oldValue.transcribeWithAppLanguage !== undefined) {
newValue.transcription = {
...newValue.transcription,
useAppLanguage: oldValue.transcribeWithAppLanguage
};
}
if (oldValue.preferredTranscribeLanguage !== undefined) {
newValue.transcription = {
...newValue.transcription,
preferredLanguage: oldValue.preferredTranscribeLanguage
};
}
if (oldValue.autoCaptionOnRecord !== undefined) {
newValue.transcription = {
...newValue.transcription,
autoCaptionOnRecord: oldValue.autoCaptionOnRecord
};
}
newValue.recordingService = newValue.recordingService || {};
if (oldValue.fileRecordingsServiceEnabled !== undefined) {
newValue.recordingService = {
...newValue.recordingService,
enabled: oldValue.fileRecordingsServiceEnabled
};
}
if (oldValue.fileRecordingsServiceSharingEnabled !== undefined) {
newValue.recordingService = {
...newValue.recordingService,
sharingEnabled: oldValue.fileRecordingsServiceSharingEnabled
};
}
return newValue;
}
@@ -426,7 +364,7 @@ function _translateLegacyConfig(oldValue: IConfig) {
* @private
* @returns {Object} The new state after the reduction of the specified action.
*/
function _updateConfig(state: IConfig, { config }: {config: IConfig}) {
function _updateConfig(state, { config }) {
const newState = _.merge({}, state, config);
_cleanupConfig(newState);

View File

@@ -1,71 +0,0 @@
import _ from 'lodash';
import {
appendURLParam,
getBackendSafeRoomName,
parseURIString
} from '../util';
import logger from './logger';
/**
* Constructs options to be passed to the constructor of {@code JitsiConnection}
* based on the redux state.
*
* @param {Object} state - The redux state.
* @returns {Object} The options to be passed to the constructor of
* {@code JitsiConnection}.
*/
export function constructOptions(state) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options = _.cloneDeep(state['features/base/config']);
let { bosh, websocket } = options;
// TESTING: Only enable WebSocket for some percentage of users.
if (websocket && navigator.product === 'ReactNative') {
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
websocket = undefined;
}
}
// Normalize the BOSH URL.
if (bosh && !websocket) {
const { locationURL } = state['features/base/connection'];
if (bosh.startsWith('//')) {
// By default our config.js doesn't include the protocol.
bosh = `${locationURL.protocol}${bosh}`;
} else if (bosh.startsWith('/')) {
// Handle relative URLs, which won't work on mobile.
const {
protocol,
host,
contextRoot
} = parseURIString(locationURL.href);
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
}
}
// WebSocket is preferred over BOSH.
const serviceUrl = websocket || bosh;
logger.log(`Using service URL ${serviceUrl}`);
// Append room to the URL's search.
const { room } = state['features/base/conference'];
if (serviceUrl && room) {
const roomName = getBackendSafeRoomName(room);
options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName);
if (options.websocketKeepAliveUrl) {
options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName);
}
}
return options;
}

View File

@@ -1,10 +1,15 @@
// @flow
import _ from 'lodash';
import type { Dispatch } from 'redux';
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
import { getCurrentConference } from '../conference/functions';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import {
getBackendSafeRoomName,
parseURIString
} from '../util';
import {
CONNECTION_DISCONNECTED,
@@ -13,12 +18,9 @@ import {
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL
} from './actionTypes';
import { constructOptions } from './actions.any';
import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger';
export * from './actions.any';
/**
* The error structure passed to the {@link connectionFailed} action.
*
@@ -76,7 +78,7 @@ export type ConnectionFailedError = {
export function connect(id: ?string, password: ?string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const options = constructOptions(state);
const options = _constructOptions(state);
const { locationURL } = state['features/base/connection'];
const { jwt } = state['features/base/jwt'];
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
@@ -260,6 +262,69 @@ function _connectionWillConnect(connection) {
};
}
/**
* Constructs options to be passed to the constructor of {@code JitsiConnection}
* based on the redux state.
*
* @param {Object} state - The redux state.
* @returns {Object} The options to be passed to the constructor of
* {@code JitsiConnection}.
*/
function _constructOptions(state) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options = _.cloneDeep(state['features/base/config']);
let { bosh, websocket } = options;
// TESTING: Only enable WebSocket for some percentage of users.
if (websocket) {
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
websocket = undefined;
}
}
// Normalize the BOSH URL.
if (bosh && !websocket) {
const { locationURL } = state['features/base/connection'];
if (bosh.startsWith('//')) {
// By default our config.js doesn't include the protocol.
bosh = `${locationURL.protocol}${bosh}`;
} else if (bosh.startsWith('/')) {
// Handle relative URLs, which won't work on mobile.
const {
protocol,
host,
contextRoot
} = parseURIString(locationURL.href);
// eslint-disable-next-line max-len
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
}
}
// WebSocket is preferred over BOSH.
const serviceUrl = websocket || bosh;
logger.log(`Using service URL ${serviceUrl}`);
// Append room to the URL's search.
const { room } = state['features/base/conference'];
if (serviceUrl && room) {
const roomName = getBackendSafeRoomName(room);
options.serviceUrl = `${serviceUrl}?room=${roomName}`;
if (options.websocketKeepAliveUrl) {
options.websocketKeepAliveUrl += `?room=${roomName}`;
}
}
return options;
}
/**
* Closes connection.
*

View File

@@ -16,8 +16,6 @@ export {
} from './actions.native';
import logger from './logger';
export * from './actions.any';
/**
* Opens new connection.
*

View File

@@ -7,15 +7,6 @@
*/
export const HIDE_DIALOG = 'HIDE_DIALOG';
/**
* The type of Redux action which closes a sheet.
*
* {
* type: HIDE_SHEET
* }
*/
export const HIDE_SHEET = 'HIDE_SHEET';
/**
* The type of Redux action which begins a request to open a dialog.
*
@@ -27,15 +18,3 @@ export const HIDE_SHEET = 'HIDE_SHEET';
*
*/
export const OPEN_DIALOG = 'OPEN_DIALOG';
/**
* The type of Redux action which begins a request to open a sheet.
*
* {
* type: OPEN_SHEET,
* component: React.Component,
* props: PropTypes
* }
*
*/
export const OPEN_SHEET = 'OPEN_SHEET';

View File

@@ -2,12 +2,7 @@
import type { Dispatch } from 'redux';
import {
HIDE_DIALOG,
HIDE_SHEET,
OPEN_DIALOG,
OPEN_SHEET
} from './actionTypes';
import { HIDE_DIALOG, OPEN_DIALOG } from './actionTypes';
import { isDialogOpen } from './functions';
/**
@@ -29,19 +24,6 @@ export function hideDialog(component: ?Object) {
};
}
/**
* Closes the active sheet.
*
* @returns {{
* type: HIDE_SHEET,
* }}
*/
export function hideSheet() {
return {
type: HIDE_SHEET
};
}
/**
* Signals Dialog to open dialog.
*
@@ -62,26 +44,6 @@ export function openDialog(component: Object, componentProps: ?Object) {
};
}
/**
* Opens the requested sheet.
*
* @param {Object} component - The component to display as a sheet.
* @param {Object} [componentProps] - The React {@code Component} props of the
* specified {@code component}.
* @returns {{
* type: OPEN_SHEET,
* component: React.Component,
* componentProps: (Object | undefined)
* }}
*/
export function openSheet(component: Object, componentProps: ?Object) {
return {
type: OPEN_SHEET,
component,
componentProps
};
}
/**
* Signals Dialog to open a dialog with the specified component if the component
* is not already open. If it is open, then Dialog is signaled to close its

View File

@@ -1,4 +1,7 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import Dialog from 'react-native-dialog';
import { translate } from '../../../i18n';
@@ -43,16 +46,16 @@ class AlertDialog extends AbstractDialog<Props> {
: renderHTML(t(contentKey.key, contentKey.params));
return (
<Dialog.Container
coverScreen = { false }
visible = { true }>
<Dialog.Description>
{ content }
</Dialog.Description>
<Dialog.Button
label = { t('dialog.Ok') }
onPress = { this._onSubmit } />
</Dialog.Container>
<View>
<Dialog.Container visible = { true }>
<Dialog.Description>
{ content }
</Dialog.Description>
<Dialog.Button
label = { t('dialog.Ok') }
onPress = { this._onSubmit } />
</Dialog.Container>
</View>
);
}

View File

@@ -1,17 +1,40 @@
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, ScrollView, View } from 'react-native';
// @flow
import React, { PureComponent, type Node } from 'react';
import { PanResponder, SafeAreaView, ScrollView, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { SlidingView } from '../../../react';
import { connect } from '../../../redux';
import { hideSheet } from '../../actions';
import { StyleType } from '../../../styles';
import { bottomSheetStyles as styles } from './styles';
/**
* Minimal distance that needs to be moved by the finger to consider it a swipe.
*/
const GESTURE_DISTANCE_THRESHOLD = 5;
/**
* The minimal speed needed to be achieved by the finger to consider it as a swipe.
*/
const GESTURE_SPEED_THRESHOLD = 0.2;
/**
* The type of {@code BottomSheet}'s React {@code Component} prop types.
*/
type Props = {
/**
* The height of the screen.
*/
_height: number,
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Whether to add padding to scroll view.
*/
@@ -22,17 +45,17 @@ type Props = {
*/
children: Node,
/**
* Redux Dispatch function.
*/
dispatch: Function,
/**
* Handler for the cancel event, which happens when the user dismisses
* the sheet.
*/
onCancel: ?Function,
/**
* Callback to be attached to the custom swipe event of the BottomSheet.
*/
onSwipe?: Function,
/**
* Function to render a bottom sheet header element, if necessary.
*/
@@ -58,6 +81,8 @@ type Props = {
* A component emulating Android's BottomSheet.
*/
class BottomSheet extends PureComponent<Props> {
panResponder: Object;
/**
* Default values for {@code BottomSheet} component's properties.
*
@@ -69,28 +94,18 @@ class BottomSheet extends PureComponent<Props> {
};
/**
* Initializes a new instance.
* Instantiates a new component.
*
* @param {Props} props - The React {@code Component} props to initialize
* the new instance with.
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onCancel = this._onCancel.bind(this);
}
/**
* Handles the cancel event, when the user dismissed the sheet. By default we close it.
*
* @returns {void}
*/
_onCancel() {
if (this.props.onCancel) {
this.props.onCancel();
} else {
this.props.dispatch(hideSheet());
}
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._onShouldSetResponder.bind(this),
onMoveShouldSetPanResponder: this._onShouldSetResponder.bind(this),
onPanResponderRelease: this._onGestureEnd.bind(this)
});
}
/**
@@ -101,6 +116,8 @@ class BottomSheet extends PureComponent<Props> {
*/
render() {
const {
_height,
_styles,
addScrollViewPadding,
renderHeader,
renderFooter,
@@ -112,7 +129,7 @@ class BottomSheet extends PureComponent<Props> {
<SlidingView
accessibilityRole = 'menu'
accessibilityViewIsModal = { true }
onHide = { this._onCancel }
onHide = { this.props.onCancel }
position = 'bottom'
show = { showSlidingView }>
<View
@@ -126,16 +143,20 @@ class BottomSheet extends PureComponent<Props> {
style = { [
styles.sheetItemContainer,
renderHeader
? styles.sheetHeader
: styles.sheet,
renderFooter && styles.sheetFooter,
style
] }>
? _styles.sheetHeader
: _styles.sheet,
renderFooter && _styles.sheetFooter,
style,
{
maxHeight: _height - 100
}
] }
{ ...this.panResponder.panHandlers }>
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false }
style = { [
renderFooter && styles.sheet,
renderFooter && _styles.sheet,
addScrollViewPadding && styles.scrollView
] } >
{ this.props.children }
@@ -146,6 +167,63 @@ class BottomSheet extends PureComponent<Props> {
</SlidingView>
);
}
/**
* Callback to handle a gesture end event.
*
* @param {Object} evt - The native gesture event.
* @param {Object} gestureState - The gesture state.
* @returns {void}
*/
_onGestureEnd(evt, gestureState) {
const verticalSwipe = Math.abs(gestureState.vy) > Math.abs(gestureState.vx)
&& Math.abs(gestureState.vy) > GESTURE_SPEED_THRESHOLD;
if (verticalSwipe) {
const direction = gestureState.vy > 0 ? 'down' : 'up';
const { onCancel, onSwipe } = this.props;
let isSwipeHandled = false;
if (onSwipe) {
isSwipeHandled = onSwipe(direction);
}
if (direction === 'down' && !isSwipeHandled) {
// Swipe down is a special gesture that can be used to close the
// BottomSheet, so if the swipe is not handled by the parent
// component, we consider it as a request to close.
onCancel && onCancel();
}
}
}
/**
* Returns true if the pan responder should activate, false otherwise.
*
* @param {Object} evt - The native gesture event.
* @param {Object} gestureState - The gesture state.
* @returns {boolean}
*/
_onShouldSetResponder({ nativeEvent }, gestureState) {
return nativeEvent.touches.length === 1
&& Math.abs(gestureState.dx) > GESTURE_DISTANCE_THRESHOLD
&& Math.abs(gestureState.dy) > GESTURE_DISTANCE_THRESHOLD;
}
}
export default connect()(BottomSheet);
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: StyleType
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'BottomSheet'),
_height: state['features/base/responsive-ui'].clientHeight
};
}
export default connect(_mapStateToProps)(BottomSheet);

View File

@@ -1,20 +0,0 @@
import React, { Fragment } from 'react';
import { useSelector } from 'react-redux';
const BottomSheetContainer: () => JSX.Element = () => {
const { sheet, sheetProps } = useSelector(state => state['features/base/dialog']);
const { reducedUI } = useSelector(state => state['features/base/responsive-ui']);
if (!sheet || reducedUI) {
return null;
}
return (
<Fragment>
{ React.createElement(sheet, sheetProps) }
</Fragment>
);
}
export default BottomSheetContainer;

Some files were not shown because too many files have changed in this diff Show More