Compare commits
65 Commits
saghul-pat
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ac86cf979 | ||
|
|
1303040e17 | ||
|
|
5e2f745407 | ||
|
|
dc6861d5a4 | ||
|
|
7e5833bed2 | ||
|
|
55af587836 | ||
|
|
f59174d6ce | ||
|
|
c83c4488bf | ||
|
|
38280c358f | ||
|
|
f7c1500bc0 | ||
|
|
ae90e96a3e | ||
|
|
c05a49567d | ||
|
|
96fe34e6c7 | ||
|
|
c9f6de1371 | ||
|
|
60a995d654 | ||
|
|
1b053b0ff2 | ||
|
|
6293b586f1 | ||
|
|
f615b0133c | ||
|
|
06f509b475 | ||
|
|
47d261e45c | ||
|
|
961dbd81c7 | ||
|
|
190755126b | ||
|
|
f21ebf63ba | ||
|
|
beb30c5224 | ||
|
|
2253393ac8 | ||
|
|
965a6981db | ||
|
|
214c53220f | ||
|
|
c5af0ba621 | ||
|
|
22eff7fa21 | ||
|
|
0f57928585 | ||
|
|
7fee0297e8 | ||
|
|
df81e0fe53 | ||
|
|
c58de5c690 | ||
|
|
cf1fa52426 | ||
|
|
e58cad0464 | ||
|
|
2bf982452e | ||
|
|
891278d0b3 | ||
|
|
056226b8af | ||
|
|
f8dae86798 | ||
|
|
309f23ba94 | ||
|
|
3f93a81818 | ||
|
|
83196cda10 | ||
|
|
f3670ce86d | ||
|
|
5fd5804245 | ||
|
|
c58e557019 | ||
|
|
50515e0143 | ||
|
|
05f082ef06 | ||
|
|
20ef19ecf1 | ||
|
|
65450e36ef | ||
|
|
1b7a81afa5 | ||
|
|
470e987fad | ||
|
|
419db67267 | ||
|
|
ff70025429 | ||
|
|
7a305ef96e | ||
|
|
28efcea9d8 | ||
|
|
3927f3423f | ||
|
|
0c3b5139b1 | ||
|
|
ec9fcdf1cb | ||
|
|
d335438f12 | ||
|
|
69b536cfb9 | ||
|
|
edb4555699 | ||
|
|
8b6728c65d | ||
|
|
d3336192c3 | ||
|
|
10eadbb7e6 | ||
|
|
36a5282823 |
@@ -82,7 +82,7 @@ dependencies {
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
// Sync with react-native-google-signin
|
||||
implementation 'com.google.android.gms:play-services-auth:19.0.2'
|
||||
implementation 'com.google.android.gms:play-services-auth:19.2.0'
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
|
||||
@@ -74,7 +74,7 @@ dependencies {
|
||||
}
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation project(':react-native-get-random-values')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-immersive-mode')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-orientation-locker')
|
||||
implementation project(':react-native-pager-view')
|
||||
|
||||
@@ -77,7 +77,8 @@ public class BroadcastAction {
|
||||
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_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED"),
|
||||
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ public class BroadcastEvent {
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CONFERENCE_BLURRED("org.jitsi.meet.CONFERENCE_BLURRED"),
|
||||
CONFERENCE_FOCUSED("org.jitsi.meet.CONFERENCE_FOCUSED"),
|
||||
CONFERENCE_JOINED("org.jitsi.meet.CONFERENCE_JOINED"),
|
||||
CONFERENCE_TERMINATED("org.jitsi.meet.CONFERENCE_TERMINATED"),
|
||||
CONFERENCE_WILL_JOIN("org.jitsi.meet.CONFERENCE_WILL_JOIN"),
|
||||
@@ -89,6 +91,8 @@ public class BroadcastEvent {
|
||||
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"),
|
||||
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE");
|
||||
|
||||
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
|
||||
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
|
||||
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
|
||||
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
|
||||
private static final String CONFERENCE_TERMINATED_NAME = "CONFERENCE_TERMINATED";
|
||||
@@ -124,6 +128,10 @@ public class BroadcastEvent {
|
||||
|
||||
private static Type buildTypeFromName(String name) {
|
||||
switch (name) {
|
||||
case CONFERENCE_BLURRED_NAME:
|
||||
return CONFERENCE_BLURRED;
|
||||
case CONFERENCE_FOCUSED_NAME:
|
||||
return CONFERENCE_FOCUSED;
|
||||
case CONFERENCE_WILL_JOIN_NAME:
|
||||
return CONFERENCE_WILL_JOIN;
|
||||
case CONFERENCE_JOINED_NAME:
|
||||
|
||||
@@ -54,4 +54,14 @@ public class BroadcastIntentHelper {
|
||||
intent.putExtra("enabled", enabled);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildRetrieveParticipantsInfo(String requestId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
|
||||
intent.putExtra("requestId", requestId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleCameraIntent() {
|
||||
return new Intent(BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
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());
|
||||
constants.put("TOGGLE_CAMERA", BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@@ -229,22 +228,4 @@ public class JitsiMeetView extends FrameLayout {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class ReactInstanceManagerHolder {
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.swmansion.gesturehandler.RNGestureHandlerPackage(),
|
||||
new org.linusu.RNGetRandomValuesPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.rnimmersivemode.RNImmersiveModePackage(),
|
||||
new com.swmansion.rnscreens.RNScreensPackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
||||
|
||||
@@ -27,8 +27,8 @@ include ':react-native-giphy'
|
||||
project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-immersive-mode'
|
||||
project(':react-native-immersive-mode').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive-mode/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
include ':react-native-orientation-locker'
|
||||
|
||||
@@ -1646,10 +1646,14 @@ export default {
|
||||
APP.store.dispatch(conferenceUniqueIdSet(room, ...args));
|
||||
});
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled, authLogin) =>
|
||||
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
// we want to ignore this event in case of tokenAuthUrl config
|
||||
// we are deprecating this and at some point will get rid of it
|
||||
if (!config.tokenAuthUrl) {
|
||||
room.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled, authLogin) =>
|
||||
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
}
|
||||
|
||||
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED, user => {
|
||||
APP.store.dispatch(updateRemoteParticipantFeatures(user));
|
||||
|
||||
80
config.js
80
config.js
@@ -826,6 +826,42 @@ var config = {
|
||||
// 'whiteboard',
|
||||
// ],
|
||||
|
||||
// Participant context menu buttons which have their click/tap event exposed through the API on
|
||||
// `participantMenuButtonClick`. Passing a string for the button key will
|
||||
// prevent execution of the click/tap routine; passing an object with `key` and
|
||||
// `preventExecution` flag on false will not prevent execution of the click/tap
|
||||
// routine. Below array with mixed mode for passing the buttons.
|
||||
// participantMenuButtonsWithNotifyClick: [
|
||||
// 'allow-video',
|
||||
// {
|
||||
// key: 'ask-unmute',
|
||||
// preventExecution: false
|
||||
// },
|
||||
// 'conn-status',
|
||||
// 'flip-local-video',
|
||||
// 'grant-moderator',
|
||||
// {
|
||||
// key: 'kick',
|
||||
// preventExecution: true
|
||||
// },
|
||||
// {
|
||||
// key: 'hide-self-view',
|
||||
// preventExecution: false
|
||||
// },
|
||||
// 'mute',
|
||||
// 'mute-others',
|
||||
// 'mute-others-video',
|
||||
// 'mute-video',
|
||||
// 'pinToStage',
|
||||
// 'privateMessage',
|
||||
// {
|
||||
// key: 'remote-control',
|
||||
// preventExecution: false
|
||||
// },
|
||||
// 'send-participant-to-room',
|
||||
// 'verify',
|
||||
// ],
|
||||
|
||||
// List of pre meeting screens buttons to hide. The values must be one or more of the 5 allowed buttons:
|
||||
// 'microphone', 'camera', 'select-background', 'invite', 'settings'
|
||||
// hiddenPremeetingButtons: [],
|
||||
@@ -1008,6 +1044,11 @@ var config = {
|
||||
// "libs/analytics-ga.min.js", // google-analytics
|
||||
// "https://example.com/my-custom-analytics.js",
|
||||
// ],
|
||||
|
||||
// By enabling watchRTCEnabled option you would want to use watchRTC feature
|
||||
// This would also require to configure watchRTCConfigParams.
|
||||
// Please remember to keep rtcstatsEnabled disabled for watchRTC to work.
|
||||
// watchRTCEnabled: false,
|
||||
},
|
||||
|
||||
// Logs that should go be passed through the 'log' event if a handler is defined for it
|
||||
@@ -1306,6 +1347,9 @@ var config = {
|
||||
// hideJoinRoomButton: false,
|
||||
// },
|
||||
|
||||
// When true, virtual background feature will be disabled.
|
||||
// disableVirtualBackground: false,
|
||||
|
||||
// When true the user cannot add more images to be used as virtual background.
|
||||
// Only the default ones from will be available.
|
||||
// disableAddingBackgroundImages: false,
|
||||
@@ -1403,6 +1447,8 @@ var config = {
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
tokenAuthUrl
|
||||
tokenAuthUrlAutoRedirect
|
||||
tokenLogoutUrl
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -1599,6 +1645,40 @@ var config = {
|
||||
// // https://github.com/jitsi/excalidraw-backend
|
||||
// collabServerBaseUrl: 'https://excalidraw-backend.example.com',
|
||||
// },
|
||||
|
||||
// The watchRTC initialize config params as described :
|
||||
// https://testrtc.com/docs/installing-the-watchrtc-javascript-sdk/#h-set-up-the-sdk
|
||||
// https://www.npmjs.com/package/@testrtc/watchrtc-sdk
|
||||
// watchRTCConfigParams: {
|
||||
// /** Watchrtc api key */
|
||||
// rtcApiKey: string;
|
||||
// /** Identifier for the session */
|
||||
// rtcRoomId?: string;
|
||||
// /** Identifier for the current peer */
|
||||
// rtcPeerId?: string;
|
||||
// /**
|
||||
// * ["tag1", "tag2", "tag3"]
|
||||
// * @deprecated use 'keys' instead
|
||||
// */
|
||||
// rtcTags?: string[];
|
||||
// /** { "key1": "value1", "key2": "value2"} */
|
||||
// keys?: any;
|
||||
// /** Enables additional logging */
|
||||
// debug?: boolean;
|
||||
// rtcToken?: string;
|
||||
// /**
|
||||
// * @deprecated No longer needed. Use "proxyUrl" instead.
|
||||
// */
|
||||
// wsUrl?: string;
|
||||
// proxyUrl?: string;
|
||||
// console?: {
|
||||
// level: string;
|
||||
// override: boolean;
|
||||
// };
|
||||
// allowBrowserLogCollection?: boolean;
|
||||
// collectionInterval?: number;
|
||||
// logGetStats?: boolean;
|
||||
// },
|
||||
};
|
||||
|
||||
// Temporary backwards compatibility with old mobile clients.
|
||||
|
||||
@@ -166,7 +166,7 @@ form {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, .5);
|
||||
background: #3D3D3D;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,11 +39,6 @@
|
||||
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
|
||||
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
|
||||
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// CallKit has started to create problems starting with the iOS 16 simulator.
|
||||
// Disable it since it never worked in the simulator anyway.
|
||||
[builder setFeatureFlag:@"call-integration.enabled" withBoolean:NO];
|
||||
#endif
|
||||
}];
|
||||
|
||||
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
@@ -70,10 +70,7 @@ RCT_EXPORT_MODULE();
|
||||
= [[NSBundle bundleForClass:self.class] infoDictionary];
|
||||
NSString *sdkVersion = sdkInfoDictionary[@"CFBundleShortVersionString"];
|
||||
if (sdkVersion == nil) {
|
||||
sdkVersion = sdkInfoDictionary[@"CFBundleVersion"];
|
||||
if (sdkVersion == nil) {
|
||||
sdkVersion = @"";
|
||||
}
|
||||
sdkVersion = @"";
|
||||
}
|
||||
|
||||
// build number
|
||||
|
||||
@@ -30,5 +30,6 @@ static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendSetVideoMuted:(BOOL)muted;
|
||||
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,7 @@ 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";
|
||||
static NSString * const toggleCameraAction = @"org.jitsi.meet.TOGGLE_CAMERA";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -50,7 +51,8 @@ RCT_EXPORT_MODULE();
|
||||
@"CLOSE_CHAT": closeChatAction,
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction,
|
||||
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction
|
||||
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction,
|
||||
@"TOGGLE_CAMERA": toggleCameraAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,7 +77,8 @@ RCT_EXPORT_MODULE();
|
||||
closeChatAction,
|
||||
sendChatMessageAction,
|
||||
setVideoMutedAction,
|
||||
setClosedCaptionsEnabledAction
|
||||
setClosedCaptionsEnabledAction,
|
||||
toggleCameraAction
|
||||
];
|
||||
}
|
||||
|
||||
@@ -173,4 +176,8 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:setClosedCaptionsEnabledAction body:data];
|
||||
}
|
||||
|
||||
- (void)toggleCamera {
|
||||
[self sendEventWithName:toggleCameraAction body:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -46,5 +46,6 @@
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
|
||||
@end
|
||||
|
||||
@@ -138,6 +138,11 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
[externalAPI sendSetClosedCaptionsEnabled:enabled];
|
||||
}
|
||||
|
||||
- (void)toggleCamera {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleCamera];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
"leaveBreakoutRoom": "Breakout-Raum verlassen",
|
||||
"more": "Mehr",
|
||||
"remove": "Entfernen",
|
||||
"rename": "Umbenennen",
|
||||
"renameBreakoutRoom": "Breakout-Raum umbenennen",
|
||||
"sendToBreakoutRoom": "Anwesende in Breakout-Raum verschieben:"
|
||||
},
|
||||
"defaultName": "Breakout-Raum #{{index}}",
|
||||
@@ -370,8 +372,6 @@
|
||||
"permissionCameraRequiredError": "Der Zugriff auf die Kamera wird benötigt, um in Videokonferenzen teilzunehmen. Bitte in den Einstellungen zulassen",
|
||||
"permissionErrorTitle": "Berechtigung benötigt",
|
||||
"permissionMicRequiredError": "Der Zugriff auf das Mikrofon wird benötigt, um an Konferenzen mit Ton teilzunehmen. Bitte in den Einstellungen zulassen",
|
||||
"popupError": "Ihr Browser blockiert Pop-ups von dieser Website. Bitte aktivieren Sie Pop-ups in den Sicherheitseinstellungen des Browsers und versuchen Sie es erneut.",
|
||||
"popupErrorTitle": "Pop-up blockiert",
|
||||
"readMore": "mehr",
|
||||
"recentlyUsedObjects": "Ihre zuletzt verwendeten Objekte",
|
||||
"recording": "Aufnahme",
|
||||
@@ -388,6 +388,8 @@
|
||||
"removePassword": "$t(lockRoomPasswordUppercase) entfernen",
|
||||
"removeSharedVideoMsg": "Sind Sie sicher, dass Sie das geteilte Video entfernen möchten?",
|
||||
"removeSharedVideoTitle": "Freigegebenes Video entfernen",
|
||||
"renameBreakoutRoomLabel": "Raumname",
|
||||
"renameBreakoutRoomTitle": "Breakout-Raum umbenennen",
|
||||
"reservationError": "Fehler im Reservierungssystem",
|
||||
"reservationErrorMsg": "Fehler, Nummer: {{code}}, Nachricht: {{msg}}",
|
||||
"retry": "Wiederholen",
|
||||
@@ -439,6 +441,7 @@
|
||||
"token": "Token",
|
||||
"tokenAuthFailed": "Sie sind nicht berechtigt, dieser Konferenz beizutreten.",
|
||||
"tokenAuthFailedTitle": "Authentifizierung fehlgeschlagen",
|
||||
"tokenAuthUnsupported": "Token-Authentifizierung wird nicht unterstützt.",
|
||||
"transcribing": "Wird transkribiert",
|
||||
"unlockRoom": "Konferenz$t(lockRoomPassword) entfernen",
|
||||
"user": "Anmeldename",
|
||||
@@ -742,7 +745,6 @@
|
||||
"newDeviceCameraTitle": "Neue Kamera erkannt",
|
||||
"noiseSuppressionDesktopAudioDescription": "Die Rauschunterdrückung kann nicht genutzt werden, wenn der Computersound geteilt wird, bitte zuerst deaktivieren und dann nochmals versuchen.",
|
||||
"noiseSuppressionFailedTitle": "Rauschunterdrückung konnte nicht gestartet werden",
|
||||
"noiseSuppressionNoTrackDescription": "Bitte eigenes Mikrofon zuerst aktivieren.",
|
||||
"noiseSuppressionStereoDescription": "Rauschunterdrückung unterstützt aktuell keinen Stereoton.",
|
||||
"oldElectronClientDescription1": "Sie scheinen eine alte Version des Jitsi-Meet-Clients zu nutzen. Diese hat bekannte Schwachstellen. Bitte aktualisieren Sie auf unsere ",
|
||||
"oldElectronClientDescription2": "aktuelle Version",
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
"leaveBreakoutRoom": "Sair da sala",
|
||||
"more": "Mais",
|
||||
"remove": "Eliminar sala",
|
||||
"rename": "Renomear",
|
||||
"renameBreakoutRoom": "Renomear sala",
|
||||
"sendToBreakoutRoom": "Enviar participante para:"
|
||||
},
|
||||
"defaultName": "Sala #{{index}}",
|
||||
@@ -370,8 +372,6 @@
|
||||
"permissionCameraRequiredError": "É necessária a autorização da câmara para participar em conferências com vídeo. Por favor, conceda-a em Definições",
|
||||
"permissionErrorTitle": "Permissão necessária",
|
||||
"permissionMicRequiredError": "É necessária a permissão do microfone para participar em conferências com áudio. Por favor, conceda-a em Definições",
|
||||
"popupError": "O seu navegador está a bloquear janelas pop-up a partir deste site. Por favor, active os pop-ups nas definições de segurança do seu browser e tente novamente.",
|
||||
"popupErrorTitle": "Pop-up bloqueado",
|
||||
"readMore": "mais",
|
||||
"recentlyUsedObjects": "Os seus objetos recentemente utilizados",
|
||||
"recording": "A gravar",
|
||||
@@ -388,6 +388,8 @@
|
||||
"removePassword": "Remover $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": "Tem a certeza de que gostaria de remover o seu vídeo partilhado?",
|
||||
"removeSharedVideoTitle": "Remover vídeo partilhado",
|
||||
"renameBreakoutRoomLabel": "Nome da sala",
|
||||
"renameBreakoutRoomTitle": "Mudar o nome da sala",
|
||||
"reservationError": "Erro no sistema de reservas",
|
||||
"reservationErrorMsg": "Código de erro: {{code}}, mensagem: {{msg}}",
|
||||
"retry": "Tentativa",
|
||||
@@ -439,6 +441,7 @@
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Desculpe, não está autorizado a juntar-se a esta chamada.",
|
||||
"tokenAuthFailedTitle": "A autenticação falhou",
|
||||
"tokenAuthUnsupported": "O URL de token não é suportado.",
|
||||
"transcribing": "Transcrição",
|
||||
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
|
||||
"user": "Utilizador",
|
||||
@@ -674,6 +677,8 @@
|
||||
"sessionToken": "Token de Sessão",
|
||||
"start": "Iniciar gravação",
|
||||
"stop": "Parar gravação",
|
||||
"stopping": "A parar a gravação",
|
||||
"wait": "Aguarde enquanto guardamos a sua gravação",
|
||||
"yes": "Sim"
|
||||
},
|
||||
"lockRoomPassword": "senha",
|
||||
@@ -742,7 +747,6 @@
|
||||
"newDeviceCameraTitle": "Nova câmara detetada",
|
||||
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído não pode ser ativada enquanto se partilha o áudio do ambiente de trabalho, por favor desative-o e tente novamente.",
|
||||
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído",
|
||||
"noiseSuppressionNoTrackDescription": "Por favor, ligue primeiro o seu microfone.",
|
||||
"noiseSuppressionStereoDescription": "A supressão do ruído de áudio estéreo não é atualmente suportada.",
|
||||
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
|
||||
"oldElectronClientDescription2": "compilação mais recente",
|
||||
@@ -1073,13 +1077,14 @@
|
||||
"links": "Links",
|
||||
"privacy": "Privacidade",
|
||||
"profileSection": "Perfil",
|
||||
"sdkVersion": "Versão do SDK",
|
||||
"serverURL": "URL do servidor",
|
||||
"showAdvanced": "Mostrar definições avançadas",
|
||||
"startCarModeInLowBandwidthMode": "Iniciar o modo de condução em modo de baixa largura de banda",
|
||||
"startWithAudioMuted": "Iniciar sem áudio",
|
||||
"startWithVideoMuted": "Iniciar sem vídeo",
|
||||
"terms": "Termos",
|
||||
"version": "Versão"
|
||||
"version": "Versão da App"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nDeseja apenas discar no seu telefone?\n\n{{defaultDialInNumber}}Clique neste link para ver os números de telefone para esta reunião\n{{dialInfoPageUrl}}",
|
||||
@@ -1124,7 +1129,7 @@
|
||||
"audioOnly": "Mudar para apenas áudio",
|
||||
"audioRoute": "Selecionar o dispositivo de som",
|
||||
"boo": "Vaia",
|
||||
"breakoutRoom": "Entrar/Sair salas instantâneas",
|
||||
"breakoutRoom": "Entrar/Sair da sala",
|
||||
"callQuality": "Gerir a qualidade do vídeo",
|
||||
"carmode": "Modo de condução",
|
||||
"cc": "Mudar legendas",
|
||||
@@ -1240,7 +1245,7 @@
|
||||
"help": "Ajuda",
|
||||
"hideWhiteboard": "Esconder quadro branco",
|
||||
"invite": "Convidar pessoas",
|
||||
"joinBreakoutRoom": "Entrar na sala",
|
||||
"joinBreakoutRoom": "Entar na sala",
|
||||
"laugh": "Risos",
|
||||
"leaveBreakoutRoom": "Sair da sala",
|
||||
"leaveConference": "Sair da reunião",
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"defaultEmail": "Seu email padrão",
|
||||
"disabled": "Você não pode convidar pessoas.",
|
||||
"failedToAdd": "Falha em adicionar participantes",
|
||||
"footerText": "Discagem está desativada.",
|
||||
"googleEmail": "Email Google",
|
||||
"inviteMoreHeader": "Você é o único na reunião",
|
||||
"inviteMoreMailSubject": "Entre na reunião {{appName}}",
|
||||
@@ -31,6 +30,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"car": "Áudio do carro",
|
||||
"headphones": "Fones de ouvido",
|
||||
"none": "Sem dispositivos de áudio disponível",
|
||||
"phone": "Celular",
|
||||
@@ -39,6 +39,25 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Largura de banda baixa"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "Adicionar sala de apoio",
|
||||
"autoAssign": "Atribuir a salas de apoio automaticamente",
|
||||
"close": "Fechar",
|
||||
"join": "Participar",
|
||||
"leaveBreakoutRoom": "Sair da sala de apoio",
|
||||
"more": "Mais",
|
||||
"remove": "Remover",
|
||||
"sendToBreakoutRoom": "Enviar participante para:"
|
||||
},
|
||||
"defaultName": "Sala de apoio #{{index}}",
|
||||
"mainRoom": "Sala principal",
|
||||
"notifications": {
|
||||
"joined": "Entrando na sala de apoio \"{{name}}\"",
|
||||
"joinedMainRoom": "Entrando na sala principal",
|
||||
"joinedTitle": "Salas de apoio"
|
||||
}
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Adicionar um link da reunião",
|
||||
"confirmAddLink": "Gostaria de adicionar um link do Jitsi a esse evento?",
|
||||
@@ -57,15 +76,27 @@
|
||||
"refresh": "Atualizar calendário",
|
||||
"today": "Hoje"
|
||||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"selectSoundDevice": "Selecionar dispositivo de som"
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "Modo carro",
|
||||
"title": "Modo carro",
|
||||
"videoStopped": "Seu vídeo está parado"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Entrar no bate-papo",
|
||||
"error": "Erro: sua mensagem não foi enviada. Motivo: {{error}}",
|
||||
"fieldPlaceHolder": "Digite sua mensagem aqui",
|
||||
"lobbyChatMessageTo": "Mensagem para {{recipient}}",
|
||||
"message": "Mensagem",
|
||||
"messageAccessibleTitle": "{{user}} disse:",
|
||||
"messageAccessibleTitleMe": "Você disse:",
|
||||
"messageTo": "Mensagem privada para {{recipient}}",
|
||||
"messagebox": "Digite uma mensagem",
|
||||
"newMessages": "Novas mensagens",
|
||||
"nickname": {
|
||||
"popover": "Escolha um apelido",
|
||||
"title": "Digite um apelido para usar o bate-papo",
|
||||
@@ -85,6 +116,7 @@
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Instalar extensão do Chrome",
|
||||
"buttonTextEdge": "Instalar extensão do Edge",
|
||||
"close": "Fechar",
|
||||
"dontShowAgain": "Não me mostre isso de novo",
|
||||
"installExtensionText": "Instale a extensão para integrar com Google Calendar e Office 365"
|
||||
@@ -115,6 +147,7 @@
|
||||
"bridgeCount": "Servidores: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Conectado a:",
|
||||
"e2eeVerified": "Verificado via E2EE",
|
||||
"framerate": "Taxa de quadros:",
|
||||
"less": "Mostrar menos",
|
||||
"localaddress": "Endereço local:",
|
||||
@@ -123,6 +156,7 @@
|
||||
"localport_plural": "Portas locais:",
|
||||
"maxEnabledResolution": "envio máx",
|
||||
"more": "Mostrar mais",
|
||||
"no": "não",
|
||||
"packetloss": "Perda de pacote:",
|
||||
"participant_id": "Id participante:",
|
||||
"quality": {
|
||||
@@ -141,7 +175,8 @@
|
||||
"status": "Conexão:",
|
||||
"transport": "Transporte:",
|
||||
"transport_plural": "Transportes:",
|
||||
"video_ssrc": "Video SSRC:"
|
||||
"video_ssrc": "Video SSRC:",
|
||||
"yes": "sim"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Mais cedo",
|
||||
@@ -151,14 +186,23 @@
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "Você precisa do aplicativo móvel {{app}} para participar da reunião no seu telefone.",
|
||||
"description": "Nada acontece? Estamos tentando iniciar sua reunião no aplicativo desktop {{app}}. Tente novamente ou inicie ele na aplicação web {{app}}.",
|
||||
"descriptionNew": "Nada aconteceu? Tentamos iniciar sua reunião no aplicativo de desktop {{app}}. <br /><br /> Você pode tentar novamente ou abrir pela web.",
|
||||
"descriptionWithoutWeb": "Nada aconteceu? Tentamos iniciar sua reunião no aplicativo de desktop {{app}}.",
|
||||
"downloadApp": "Baixe o Aplicativo",
|
||||
"downloadMobileApp": "Baixar o app",
|
||||
"ifDoNotHaveApp": "Se você não tem o app ainda:",
|
||||
"ifHaveApp": "Se você já tem o app:",
|
||||
"joinInApp": "Entrar na reunião usando o app",
|
||||
"joinInAppNew": "Entrar usando o app",
|
||||
"joinInBrowser": "Entrar usando o navegador",
|
||||
"launchMeetingLabel": "Como deseja entrar nesta reunião?",
|
||||
"launchWebButton": "Iniciar na web",
|
||||
"noMobileApp": "Não tem o app?",
|
||||
"termsAndConditions": "Ao continuar você estará aceitando nossos <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>termos e condições.</a>",
|
||||
"title": "Iniciando sua reunião no {{app}}...",
|
||||
"tryAgainButton": "Tente novamente no desktop"
|
||||
"titleNew": "Iniciando sua reunião ...",
|
||||
"tryAgainButton": "Tente novamente no desktop",
|
||||
"unsupportedBrowser": "Parece que você está usando um navegador ao qual não temos suporte."
|
||||
},
|
||||
"defaultLink": "ex.: {{url}}",
|
||||
"defaultNickname": "ex.: João Pedro",
|
||||
@@ -169,11 +213,20 @@
|
||||
"microphonePermission": "Erro ao obter permissão para o microfone"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"hid": {
|
||||
"callControl": "Controle de chamada",
|
||||
"connectedDevices": "Dispositivos conectados:",
|
||||
"deleteDevice": "Remover dispositivo",
|
||||
"pairDevice": "Emparelhar dispositivo"
|
||||
},
|
||||
"noPermission": "Permissão não concedida",
|
||||
"previewUnavailable": "Visualização indisponível",
|
||||
"selectADevice": "Selecione um dispositivo",
|
||||
"testAudio": "Tocar um som de teste"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "Sumário de discagem"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "está agora {{status}}"
|
||||
},
|
||||
@@ -189,9 +242,13 @@
|
||||
"WaitingForHostTitle": "Esperando o anfitrião...",
|
||||
"Yes": "Sim",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Transmissão ao vivo"
|
||||
"close": "Fechar janela",
|
||||
"liveStreaming": "Transmissão ao vivo",
|
||||
"sharingTabs": "Opções de compartilhamento"
|
||||
},
|
||||
"add": "Adicionar",
|
||||
"addMeetingNote": "Adicionar uma anotação para esta reunião",
|
||||
"addOptionalNote": "Adicionar uma anotação (opcional):",
|
||||
"allow": "Permitir",
|
||||
"alreadySharedVideoMsg": "Outro participante já está compartilhando um vídeo. Esta conferência permite apenas um vídeo compartilhado por vez.",
|
||||
"alreadySharedVideoTitle": "Somente um vídeo compartilhado é permitido por vez",
|
||||
@@ -233,6 +290,7 @@
|
||||
"gracefulShutdown": "Nosso serviço está em manutenção. Tente novamente mais tarde.",
|
||||
"grantModeratorDialog": "Tem certeza que quer participar como moderador da reunião?",
|
||||
"grantModeratorTitle": "Permitir moderador",
|
||||
"hide": "Ocultar",
|
||||
"hideShareAudioHelper": "Não mostre este diálogo novamente",
|
||||
"incorrectPassword": "Usuário ou senha incorretos",
|
||||
"incorrectRoomLockPassword": "Senha incorreta",
|
||||
@@ -243,9 +301,10 @@
|
||||
"kickParticipantDialog": "Tem certeza de que deseja remover este participante?",
|
||||
"kickParticipantTitle": "Remover este participante?",
|
||||
"kickTitle": "{{participantDisplayName}} removeu você da reunião",
|
||||
"linkMeeting": "Vincular reunião",
|
||||
"linkMeetingTitle": "Vincular reunião ao Salesforce",
|
||||
"liveStreaming": "Transmissão ao Vivo",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Não é possível transmitir enquanto a gravação está ativa",
|
||||
"liveStreamingDisabledTooltip": "Iniciar transmissão ao vivo desativada.",
|
||||
"localUserControls": "Controles de usuários locais",
|
||||
"lockMessage": "Falha ao bloquear a conferência.",
|
||||
"lockRoom": "Adicionar reunião $t(lockRoomPasswordUppercase)",
|
||||
@@ -279,11 +338,11 @@
|
||||
"muteEveryonesVideoTitle": "Desativar a câmera de todos?",
|
||||
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
|
||||
"muteParticipantButton": "Mudo",
|
||||
"muteParticipantDialog": "Tem certeza de que deseja silenciar este participante? Você não poderá desfazer isso, mas o participante pode reabilitar o áudio a qualquer momento.",
|
||||
"muteParticipantTitle": "Deixar mudo este participante?",
|
||||
"muteParticipantsVideoBody": "Você não poderá reativar posteriormente, mas o participante pode ativar sua própria câmera a qualquer momento.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Você e o participante não poderão reativar a câmera posteriormente.",
|
||||
"muteParticipantsVideoButton": "Desativar a câmera",
|
||||
"muteParticipantsVideoDialog": "Tem certeza de que deseja desativar a câmera deste participante? Você não poderá reativar posteriormente, mas o participante pode ativar sua própria câmera a qualquer momento.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Tem certeza de que deseja desativar a câmera deste participante? Você e o participante não poderão reativar posteriormente.",
|
||||
"muteParticipantsVideoTitle": "Desativar a câmera deste participante?",
|
||||
"noDropboxToken": "Nenhum token do Dropbox válido",
|
||||
"password": "Senha",
|
||||
@@ -297,9 +356,9 @@
|
||||
"popupError": "Seu navegador está bloqueando janelas popup deste site. Habilite os popups nas configurações de segurança no seu navegador e tente novamente.",
|
||||
"popupErrorTitle": "Popup bloqueado",
|
||||
"readMore": "mais...",
|
||||
"recentlyUsedObjects": "Seus objetos usados recentemente",
|
||||
"recording": "Gravando",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Não é possível transmitir enquanto a gravação está ativa",
|
||||
"recordingDisabledTooltip": "Iniciar gravação desativada.",
|
||||
"rejoinNow": "Reconectar agora",
|
||||
"remoteControlAllowedMessage": "{{user}} aceitou sua requisição de controle remoto!",
|
||||
"remoteControlDeniedMessage": "{{user}} rejeitou sua requisição de controle remoto!",
|
||||
@@ -319,6 +378,12 @@
|
||||
"screenSharingFailed": "Oops! Alguma coisa de errado aconteceu, não é possível habilitar o compartilhamento de tela!",
|
||||
"screenSharingFailedTitle": "Falha ao compartilhar a tela!",
|
||||
"screenSharingPermissionDeniedError": "Oops! Alguma coisa está errada com suas permissões de compartilhamento de tela. Recarregue e tente de novo.",
|
||||
"searchInSalesforce": "Pesquisar no Salesforce",
|
||||
"searchResults": "Resultados da pesquisa({{count}})",
|
||||
"searchResultsDetailsError": "Algo de errado ocorreu ao recuperar os dados do proprietário.",
|
||||
"searchResultsError": "Algo de errado ocorreu ao recuperar os dados.",
|
||||
"searchResultsNotFound": "A pesquisa não trouxe resultados.",
|
||||
"searchResultsTryAgain": "Tente usar termos diferentes.",
|
||||
"sendPrivateMessage": "Você enviou uma mensagem privada recentemente. Tem intenção de responder em privado, ou deseja enviar sua mensagem para o grupo?",
|
||||
"sendPrivateMessageCancel": "Enviar para o grupo",
|
||||
"sendPrivateMessageOk": "Enviar em privado",
|
||||
@@ -341,7 +406,10 @@
|
||||
"shareVideoTitle": "Compartilhar um vídeo",
|
||||
"shareYourScreen": "Compartilhar sua tela",
|
||||
"shareYourScreenDisabled": "Compartilhamento de tela desativada.",
|
||||
"sharedVideoDialogError": "Erro: URL inválida",
|
||||
"sharedVideoLinkPlaceholder": "link do YouTube ou link direto de vídeo",
|
||||
"show": "Exibir",
|
||||
"start": "Iniciar ",
|
||||
"startLiveStreaming": "Iniciar transmissão ao vivo",
|
||||
"startRecording": "Iniciar gravação",
|
||||
"startRemoteControlErrorMessage": "Um erro ocorreu enquanto tentava iniciar uma sessão de controle remoto!",
|
||||
@@ -359,6 +427,10 @@
|
||||
"user": "Usuário",
|
||||
"userIdentifier": "identificação do usuário",
|
||||
"userPassword": "senha do usuário",
|
||||
"verifyParticipantConfirm": "Coincidem",
|
||||
"verifyParticipantDismiss": "Não coincidem",
|
||||
"verifyParticipantQuestion": "EXPERIMENTAL: Pergunta ao participante {{participantName}} se ele vê o mesmo conteúdo na mesma ordem.",
|
||||
"verifyParticipantTitle": "Verificação de usuário",
|
||||
"videoLink": "Link do vídeo",
|
||||
"viewUpgradeOptions": "Ver opções de atualização",
|
||||
"viewUpgradeOptionsContent": "Para obter acesso ilimitado a recursos premium tais como gravação, transcrição, streaming RTMP e muito mais, você precisa atualizar seu plano.",
|
||||
@@ -384,8 +456,14 @@
|
||||
"veryBad": "Muito ruim",
|
||||
"veryGood": "Muito boa"
|
||||
},
|
||||
"helpView": {
|
||||
"title": "Centro de ajuda"
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Miniaturas de vídeo"
|
||||
}
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "Nenhum resultado encontrado :(",
|
||||
"search": "Buscar GIPHY"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Responder",
|
||||
@@ -427,9 +505,11 @@
|
||||
"noRoom": "Nenhuma sala foi especificada para entrar.",
|
||||
"numbers": "Números de discagem",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"reachedLimit": "Você atingiu o limite do seu plano.",
|
||||
"sip": "endereço SIP",
|
||||
"title": "Compartilhar",
|
||||
"tooltip": "Compartilhar link e discagem para esta reunião"
|
||||
"tooltip": "Compartilhar link e discagem para esta reunião",
|
||||
"upgradeOptions": "Por favor, verifique as opções de upgrade em"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Tivemos um pequeno problema.",
|
||||
@@ -450,6 +530,7 @@
|
||||
"focusLocal": "Focar no seu vídeo",
|
||||
"focusRemote": "Focar no vídeo de outro participante",
|
||||
"fullScreen": "Entrar ou sair da tela cheia",
|
||||
"giphyMenu": "Alternar menu do GIPHY",
|
||||
"keyboardShortcuts": "Atalhos de teclado",
|
||||
"localRecording": "Mostrar ou ocultar controles de gravação local",
|
||||
"mute": "Deixar mudo ou não o microfone",
|
||||
@@ -463,6 +544,10 @@
|
||||
"toggleShortcuts": "Mostrar ou ocultar atalhos de teclado",
|
||||
"videoMute": "Iniciar ou parar sua câmera"
|
||||
},
|
||||
"largeVideo": {
|
||||
"screenIsShared": "Você está compartilhando sua tela",
|
||||
"showMeWhatImSharing": "Me mostre o que estou compartilhando"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Estamos trabalhando para liberar os recursos de transmissão. Tente novamente em alguns minutos.",
|
||||
"busyTitle": "Todas as transmissões estão atualmente ocupadas",
|
||||
@@ -479,6 +564,7 @@
|
||||
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
|
||||
"getStreamKeyManually": "Não conseguimos buscar nenhuma transmissão ao vivo. Tente obter sua chave de transmissão ao vivo no YouTube.",
|
||||
"googlePrivacyPolicy": "Política de Privacidade do Google",
|
||||
"inProgress": "Gravação ou live streaming em andamento",
|
||||
"invalidStreamKey": "A senha para transmissão ao vivo pode estar incorreta.",
|
||||
"limitNotificationDescriptionNative": "Sua transmissão será limitada a {{limit}} minutos. Para transmissão ilimitada tente {{app}}.",
|
||||
"limitNotificationDescriptionWeb": "Devido a alta demanda sua transmissão será limitada a {{limit}} minutos. Para transmissão ilimitada tente <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
@@ -488,6 +574,7 @@
|
||||
"onBy": "{{name}} iniciou a transmissão ao vivo",
|
||||
"pending": "Iniciando Transmissão ao Vivo...",
|
||||
"serviceName": "Serviço de Transmissão ao Vivo",
|
||||
"sessionAlreadyActive": "Esta sessão já está sendo gravada ou em live streaming.",
|
||||
"signIn": "Faça login no Google",
|
||||
"signInCTA": "Faça login ou insira sua chave de transmissão ao vivo do YouTube.",
|
||||
"signOut": "Sair",
|
||||
@@ -501,8 +588,8 @@
|
||||
"lobby": {
|
||||
"admit": "Aceitar",
|
||||
"admitAll": "Aceitar todos",
|
||||
"allow": "Permitir",
|
||||
"backToKnockModeButton": "Sem senha, peça para se juntar",
|
||||
"chat": "Chat",
|
||||
"dialogTitle": "Modo sala de espera",
|
||||
"disableDialogContent": "O modo sala de espera está habilitado. Este recurso evita que particpantes não convidados juntem-se à sua conferência. Deseja desabilitar?",
|
||||
"disableDialogSubmit": "Desabilitar",
|
||||
@@ -515,6 +602,7 @@
|
||||
"errorMissingPassword": "Por favor informe a senha da conferência",
|
||||
"invalidPassword": "Senha inválida",
|
||||
"joinRejectedMessage": "Sua solicitação de participação foi rejeitada pelo moderador.",
|
||||
"joinRejectedTitle": "Solicitação de participação recusada",
|
||||
"joinTitle": "Junte-se à conferência",
|
||||
"joinWithPasswordMessage": "Tentando entrar com a senha, por favor aguarde...",
|
||||
"joiningMessage": "Você se juntará à conferência tão logo alguém aprove sua solicitação",
|
||||
@@ -523,6 +611,8 @@
|
||||
"knockButton": "Peça para participar",
|
||||
"knockTitle": "Alguém deseja participar da conferência",
|
||||
"knockingParticipantList": "Remover lista de participantes",
|
||||
"lobbyChatStartedNotification": "{{moderator}} iniciou uma conversa na sala de espera com {{attendee}}",
|
||||
"lobbyChatStartedTitle": "{{moderator}} iniciou uma conversa na sala de espera com você.",
|
||||
"nameField": "Informe seu nome",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} foi rejeitado por {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} foi aceito por {{originParticipantName}}",
|
||||
@@ -560,6 +650,7 @@
|
||||
"no": "Não",
|
||||
"participant": "Participante",
|
||||
"participantStats": "Estatísticas dos Participantes",
|
||||
"selectTabTitle": "🎥 Por favor selecione esta aba para gravar",
|
||||
"sessionToken": "Token de Sessão",
|
||||
"start": "Iniciar gravação",
|
||||
"stop": "Parar a Gravação",
|
||||
@@ -576,18 +667,39 @@
|
||||
"OldElectronAPPTitle": "Vulnerabilidade de segurança!",
|
||||
"allowAction": "Permitir",
|
||||
"allowedUnmute": "Você pode religar seu microfone, ativar sua câmera ou compartilhar sua tela.",
|
||||
"audioUnmuteBlockedDescription": "A liberação do microfone foi temporariamente bloqueada devido a limites do sistema.",
|
||||
"audioUnmuteBlockedTitle": "Microfone bloqueado!",
|
||||
"chatMessages": "Mensagens do chat",
|
||||
"connectedOneMember": "{{name}} entrou na reunião",
|
||||
"connectedThreePlusMembers": "{{name}} e outros {{count}} entraram na reunião",
|
||||
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
|
||||
"dataChannelClosed": "Qualidade do vídeo prejudicada",
|
||||
"dataChannelClosedDescription": "O canal da ponte foi desconectado, assim a qualidade do vídeo foi limitada a sua configuração mais baixa.",
|
||||
"disabledIframe": "Incorporação destina-se apenas a fins de demonstração, assim esta chamada será desconectada em {{timeout}} minutos.",
|
||||
"disconnected": "desconectado",
|
||||
"displayNotifications": "Exibir notificações para",
|
||||
"dontRemindMe": "Não me lembrar",
|
||||
"focus": "Foco da conferência",
|
||||
"focusFail": "{{component}} não disponível - tente em {{ms}} seg",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notificações",
|
||||
"hostAskedUnmute": "O anfitrião deseja que você ative o som",
|
||||
"invitedOneMember": "{{name}} foi convidado(a)",
|
||||
"invitedThreePlusMembers": "{{name}} e {{count}} outros foram convidados",
|
||||
"invitedTwoMembers": "{{first}} e {{second}} foram convidados",
|
||||
"joinMeeting": "Participar",
|
||||
"kickParticipant": "{{kicked}} foi removido por {{kicker}}",
|
||||
"leftOneMember": "{{name}} saiu da reunião",
|
||||
"leftThreePlusMembers": "{{name}} e muitos outros saíram da reunião",
|
||||
"leftTwoMembers": "{{first}} e {{second}} saíram da reunião",
|
||||
"linkToSalesforce": "Link para Salesforce",
|
||||
"linkToSalesforceDescription": "Você pode vincular o sumário da reunião a um objeto do Salesforce.",
|
||||
"linkToSalesforceError": "Falha ao vincular reunião ao Salesforce",
|
||||
"linkToSalesforceKey": "Vincular esta reunião",
|
||||
"linkToSalesforceProgress": "Vinculando reunião ao Salesforce...",
|
||||
"linkToSalesforceSuccess": "A reunião foi vinculada ao Salesforce",
|
||||
"localRecordingStarted": "{{name}} iniciou uma gravação local.",
|
||||
"localRecordingStopped": "{{name}} parou uma gravação local.",
|
||||
"me": "Eu",
|
||||
"moderationInEffectCSDescription": "Levante a mão se quiser compartilhar seu vídeo",
|
||||
"moderationInEffectCSTitle": "O compartilhamento de conteúdo foi desativado pelo moderador",
|
||||
@@ -608,16 +720,27 @@
|
||||
"newDeviceAction": "Usar",
|
||||
"newDeviceAudioTitle": "Novo dispositivo de áudio detectado",
|
||||
"newDeviceCameraTitle": "Nova câmera detectada",
|
||||
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído não pode ser habilitada enquanto compartilha o áudio da área de trabalho. Por favor desabilite e tente novamente.",
|
||||
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído",
|
||||
"noiseSuppressionNoTrackDescription": "Por favor ative o microfone antes.",
|
||||
"noiseSuppressionStereoDescription": "Supressão de ruído de áudio estéreo não é suportado no momento.",
|
||||
"oldElectronClientDescription1": "Você está usando um versão antiga do cliente Jitsi Meet que possui uma conhecida vulnerabilidade de segurança. Por favor tenha certeza de atulizar para a nossa ",
|
||||
"oldElectronClientDescription2": "última versão",
|
||||
"oldElectronClientDescription3": " agora!",
|
||||
"participantWantsToJoin": "Deseja entrar na reunião",
|
||||
"participantsWantToJoin": "Desejam entrar na reunião",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
|
||||
"raiseHandAction": "Levantar a mão",
|
||||
"raisedHand": "{{name}} gostaria de falar.",
|
||||
"raisedHands": "{{participantName}} e {{raisedHands}} outras pessoas",
|
||||
"reactionSounds": "Desabilitar sons",
|
||||
"reactionSoundsForAll": "Desabilitar sons para todos",
|
||||
"screenShareNoAudio": "O compartilhamento de áudio não foi selecionado na tela de escolha de janela.",
|
||||
"screenShareNoAudioTitle": "Compartilhamento de áudio não selecionado",
|
||||
"screenSharingAudioOnlyDescription": "Por favor perceba que ao compartilhar sua tela você estará afetando o modo de \"Melhor performance\" e irá usar mais banda de rede.",
|
||||
"screenSharingAudioOnlyTitle": "Modo de \"Melhor performance\"",
|
||||
"selfViewTitle": "Você pode habilitar a auto-visualização nas configurações a qualquer momento",
|
||||
"somebody": "Alguém",
|
||||
"startSilentDescription": "Volte à reunião para habilitar o áudio",
|
||||
"startSilentTitle": "Você entrou sem saída de áudio!",
|
||||
@@ -625,7 +748,11 @@
|
||||
"suboptimalExperienceTitle": "Alerta do navegador",
|
||||
"unmute": "Ativar som",
|
||||
"videoMutedRemotelyDescription": "Você pode ativar sua câmera a qualquer momento.",
|
||||
"videoMutedRemotelyTitle": "Sua câmera foi desativada por {{participantDisplayName}}!"
|
||||
"videoMutedRemotelyTitle": "Sua câmera foi desativada por {{participantDisplayName}}!",
|
||||
"videoUnmuteBlockedDescription": "A liberação da câmera e compartilhamento de tela foram temporariamente bloqueados devido a limites do sistema.",
|
||||
"videoUnmuteBlockedTitle": "Câmera e compartilhamento de tela bloqueados!",
|
||||
"viewLobby": "Ver sala de espera",
|
||||
"waitingParticipants": "{{waitingParticipants}} pessoas"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
@@ -635,6 +762,9 @@
|
||||
"audioModeration": "Reativarem seus sons",
|
||||
"blockEveryoneMicCamera": "Bloquear microfone e câmera de todos",
|
||||
"invite": "Convidar alguém",
|
||||
"moreModerationActions": "Mais opções de moderação",
|
||||
"moreModerationControls": "Mais controles de moderação",
|
||||
"moreParticipantOptions": "Mais opções de participante",
|
||||
"mute": "Silenciar",
|
||||
"muteAll": "Silenciar todos",
|
||||
"muteEveryoneElse": "Silenciar todos os demais",
|
||||
@@ -647,17 +777,22 @@
|
||||
"headings": {
|
||||
"lobby": "Sala de espera ({{count}})",
|
||||
"participantsList": "Participantes da reunião ({{count}})",
|
||||
"visitors": "Visitantes ({{count}})",
|
||||
"waitingLobby": "Aguardando na sala de espera ({{count}})"
|
||||
},
|
||||
"search": "Buscar participantes",
|
||||
"title": "Participantes"
|
||||
},
|
||||
"passwordDigitsOnly": "Até {{number}} dígitos",
|
||||
"passwordSetRemotely": "Definido por outro participante",
|
||||
"pinParticipant": "{{participantName}} - Fixar",
|
||||
"pinnedParticipant": "O participante está fixado",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"skip": "Desistir",
|
||||
"submit": "Enviar"
|
||||
},
|
||||
"by": "Por {{ name }}",
|
||||
"create": {
|
||||
"addOption": "Adicionar opção",
|
||||
"answerPlaceholder": "Opção {{index}}",
|
||||
@@ -728,15 +863,18 @@
|
||||
"initiated": "Chamada iniciada",
|
||||
"joinAudioByPhone": "Participar com o áudio via ligação",
|
||||
"joinMeeting": "Participar da reunião",
|
||||
"joinMeetingInLowBandwidthMode": "Participar no modo de banda baixa",
|
||||
"joinWithoutAudio": "Participar sem áudio",
|
||||
"keyboardShortcuts": "Habilitar atalhos de teclado",
|
||||
"linkCopied": "Link copiado para a área de transferência",
|
||||
"lookGood": "Seu microfone está funcionando corretamente",
|
||||
"or": "ou",
|
||||
"premeeting": "Pré-reunião",
|
||||
"proceedAnyway": "Prosseguir mesmo assim",
|
||||
"screenSharingError": "Erro de compartilhamento de tela:",
|
||||
"showScreen": "Habilitar tela pré-reunião",
|
||||
"startWithPhone": "Iniciar com o áudio da ligação",
|
||||
"unsafeRoomConsent": "Eu entendo os riscos, desejo ingressar na reunião",
|
||||
"videoOnlyError": "Erro de vídeo:",
|
||||
"videoTrackError": "Não é possível criar faixa de vídeo.",
|
||||
"viewAllNumbers": "veja todos os números"
|
||||
@@ -763,6 +901,19 @@
|
||||
"title": "Perfil"
|
||||
},
|
||||
"raisedHand": "Gostaria de falar",
|
||||
"raisedHandsLabel": "Número de mãos levantadas",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "A reunião já está vinculada a este objeto do Salesforce"
|
||||
},
|
||||
"type": {
|
||||
"account": "Conta",
|
||||
"contact": "Contato",
|
||||
"lead": "Lead",
|
||||
"opportunity": "Oportunidade",
|
||||
"owner": "Proprietário"
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "Enviar para o Dropbox",
|
||||
"availableSpace": "Espaço disponível: {{spaceLeft}} MB (aproximadamente {{duration}} minutos de gravação)",
|
||||
@@ -777,37 +928,66 @@
|
||||
"expandedPending": "Iniciando gravação...",
|
||||
"failedToStart": "Falha ao iniciar a gravação",
|
||||
"fileSharingdescription": "Compartilhar gravação com participantes da reunião",
|
||||
"highlight": "Destaque",
|
||||
"highlightMoment": "Momento de destaque",
|
||||
"highlightMomentDisabled": "Você pode destacar momentos quando a gravação começar",
|
||||
"highlightMomentSuccess": "Momento destacado",
|
||||
"highlightMomentSucessDescription": "Seu momento destacado será adicionado ao sumário da reunião.",
|
||||
"inProgress": "Gravação ou live streaming em andamento",
|
||||
"limitNotificationDescriptionNative": "Devido a demanda, sua gravação ficará limitada a {{limit}} minutos. Para gravação ilimitada tente <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Devido a demanda, sua gravação ficará limitada a {{limit}} minutos. Para gravação ilimitada tente <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "Geramos um link para sua gravação.",
|
||||
"live": "AOVIVO",
|
||||
"localRecordingNoNotificationWarning": "A gravação não será anunciada aos outros participantes. Você precisará avisá-los que a reunião está sendo gravada.",
|
||||
"localRecordingNoVideo": "O vídeo não está sendo gravado",
|
||||
"localRecordingStartWarning": "Por favor, certifique-se de ter parado a gravação antes de sair da reunião para garantir que será salva.",
|
||||
"localRecordingStartWarningTitle": "Parar a gravação para salvá-la",
|
||||
"localRecordingVideoStop": "Ao parar o seu vídeo a gravação local também será parada. Tem certeza que deseja continuar?",
|
||||
"localRecordingVideoWarning": "Para gravar o seu vídeo você precisa ativá-lo antes de inicar a gravação",
|
||||
"localRecordingWarning": "Tenha certeza de selecionar a aba atual para usar o áudio e vídeo corretos. A gravação está atualmente limitada a 1GB, que corresponde a aproximadamente 100 minutos.",
|
||||
"loggedIn": "Conectado como {{userName}}",
|
||||
"noMicPermission": "Trilha para o microfone não pôde ser criada. Por favor conceda permissão para usar o microfone.",
|
||||
"noStreams": "Nenhum fluxo de áudio ou vídeo detectado.",
|
||||
"off": "Gravação parada",
|
||||
"offBy": "{{name}} parou a gravação",
|
||||
"on": "Gravando",
|
||||
"onBy": "{{name}} iniciou a gravação",
|
||||
"onlyRecordSelf": "Gravar apenas o meu fluxo de áudio e vídeo",
|
||||
"pending": "Preparando para gravar a reunião...",
|
||||
"rec": "REC",
|
||||
"saveLocalRecording": "Salvar o arquivo de gravação localmente (Beta)",
|
||||
"serviceDescription": "Sua gravação será salva pelo serviço de gravação",
|
||||
"serviceDescriptionCloud": "Gravação na nuvem",
|
||||
"serviceDescriptionCloudInfo": "Reuniões gravadas são removidas automaticamente 24h após a hora da gravação.",
|
||||
"serviceName": "Serviço de gravação",
|
||||
"sessionAlreadyActive": "Esta sessão já está sendo gravada ou em live streaming",
|
||||
"signIn": "Entrar",
|
||||
"signOut": "Sair",
|
||||
"surfaceError": "Por favor selecione a aba atual.",
|
||||
"title": "Gravando",
|
||||
"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": "Tela: {{name}}",
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Puxe para atualizar"
|
||||
},
|
||||
"security": {
|
||||
"about": "Voce pode adicionar $t(lockRoomPassword) a sua reunião. Participantes terão que fornecer $t(lockRoomPassword) antes de entrar na reunião.",
|
||||
"about": "Você pode adicionar $t(lockRoomPassword) a sua reunião. Participantes terão que fornecer $t(lockRoomPassword) antes de entrar na reunião.",
|
||||
"aboutReadOnly": "Moderadores podem adicionar $t(lockRoomPassword) à reunião. Participantes terão que fornecer $t(lockRoomPassword) antes de entrar na reunião.",
|
||||
"insecureRoomNameWarning": "A sala é insegura. Participantes não desejados podem entrar na reunião. Considere adicionar seguança no botão segurança.",
|
||||
"securityOptions": "Opções de segurança"
|
||||
"insecureRoomNameWarningNative": "O nome da sala é não é seguro. Participantes não desejados podem ingressar na reunião. {{recommendAction}} Saiba mais sobre como proteger sua reunião ",
|
||||
"insecureRoomNameWarningWeb": "O nome da sala é não é seguro. Participantes não desejados podem ingressar na reunião. {{recommendAction}} Saiba mais sobre como proteger sua reunião <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">aqui</a>.",
|
||||
"title": "Opções de segurança",
|
||||
"unsafeRoomActions": {
|
||||
"meeting": "Considere usar o botão de segurança para proteger sua reunião.",
|
||||
"prejoin": "Considere usar um nome de reunião não-comum.",
|
||||
"welcome": "Considere usar um nome de reunião não-comum ou selecione um das sugestões."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"audio": "Áudio",
|
||||
"buttonLabel": "Configurações",
|
||||
"calendar": {
|
||||
"about": "A integração do calendário {{appName}} é usada para acessar com segurança o seu calendário para que ele possa ler os próximos eventos.",
|
||||
"disconnect": "Desconectar",
|
||||
@@ -824,25 +1004,32 @@
|
||||
"incomingMessage": "Mensagem recebida",
|
||||
"language": "Idioma",
|
||||
"loggedIn": "Conectado como {{name}}",
|
||||
"maxStageParticipants": "Número máximo de participantes que podem ser fixados no palco principal (EXPERIMENTAL)",
|
||||
"microphones": "Microfones",
|
||||
"moderator": "Moderador",
|
||||
"moderatorOptions": "Opções de moderador",
|
||||
"more": "Mais",
|
||||
"name": "Nome",
|
||||
"noDevice": "Nenhum",
|
||||
"participantJoined": "Participante Entrou",
|
||||
"participantLeft": "Participante Saiu",
|
||||
"notifications": "Notificações",
|
||||
"participantJoined": "Participante entrou",
|
||||
"participantKnocking": "Participante entrou na sala de espera",
|
||||
"participantLeft": "Participante saiu",
|
||||
"playSounds": "Tocar sons",
|
||||
"reactions": "Reações da reunião",
|
||||
"sameAsSystem": "Igual ao sistema ({{label}})",
|
||||
"selectAudioOutput": "Saída de áudio",
|
||||
"selectCamera": "Câmera",
|
||||
"selectMic": "Microfone",
|
||||
"sounds": "Sons",
|
||||
"selfView": "Auto-visualização",
|
||||
"shortcuts": "Atalhos",
|
||||
"speakers": "Alto-faltantes",
|
||||
"startAudioMuted": "Todos iniciam mudos",
|
||||
"startReactionsMuted": "Silenciar sons de reações para todos",
|
||||
"startVideoMuted": "Todos iniciam ocultos",
|
||||
"talkWhileMuted": "Falar mesmo silenciado",
|
||||
"title": "Configurações"
|
||||
"title": "Configurações",
|
||||
"video": "Vídeo"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Avançado",
|
||||
@@ -857,13 +1044,21 @@
|
||||
"disableCrashReportingWarning": "Tem certeza eue quer desabilitar o aviso de falha? A opção será habilitada após reiniciar o app.",
|
||||
"disableP2P": "Desativar modo ponto a ponto",
|
||||
"displayName": "Nome de exibição",
|
||||
"displayNamePlaceholderText": "Ex: João Silva",
|
||||
"email": "Email",
|
||||
"emailPlaceholderText": "email@exemplo.com.br",
|
||||
"goTo": "Ir para",
|
||||
"header": "Configurações",
|
||||
"help": "Ajuda",
|
||||
"links": "Links",
|
||||
"privacy": "Privacidade",
|
||||
"profileSection": "Perfil",
|
||||
"serverURL": "URL do servidor",
|
||||
"showAdvanced": "Mostrar configurações avançadas",
|
||||
"startCarModeInLowBandwidthMode": "Iniciar modo carro em modo de banda baixa",
|
||||
"startWithAudioMuted": "Iniciar sem áudio",
|
||||
"startWithVideoMuted": "Iniciar sem vídeo",
|
||||
"terms": "Termos",
|
||||
"version": "Versão"
|
||||
},
|
||||
"share": {
|
||||
@@ -872,13 +1067,21 @@
|
||||
},
|
||||
"speaker": "Alto-falantes",
|
||||
"speakerStats": {
|
||||
"angry": "Zangado",
|
||||
"disgusted": "Com nojo",
|
||||
"displayEmotions": "Exibir emoções",
|
||||
"fearful": "Com medo",
|
||||
"happy": "Feliz",
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Nome",
|
||||
"neutral": "Neutro",
|
||||
"sad": "Triste",
|
||||
"search": "Busca",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Estatísticas do Apresentador",
|
||||
"speakerTime": "Tempo do Apresentador"
|
||||
"speakerStats": "Estatísticas do apresentador",
|
||||
"speakerTime": "Tempo do apresentador",
|
||||
"surprised": "Surpreso"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"genericTitle": "A reunião precisa usar seu microfone e câmera.",
|
||||
@@ -890,6 +1093,10 @@
|
||||
"text": "Pressione o botão <i>Reentrar</i> para reconectar.",
|
||||
"title": "Sua chamada de vídeo foi interrompida, porque seu computador foi dormir."
|
||||
},
|
||||
"termsView": {
|
||||
"title": "Termos"
|
||||
},
|
||||
"toggleTopPanelLabel": "Alternar painel superior",
|
||||
"toolbar": {
|
||||
"Settings": "Configurações",
|
||||
"accessibilityLabel": {
|
||||
@@ -897,60 +1104,89 @@
|
||||
"audioOnly": "Alternar para apenas áudio",
|
||||
"audioRoute": "Selecionar o dispositivo de som",
|
||||
"boo": "Vaia",
|
||||
"breakoutRoom": "Entrar/sair da sala de apoio",
|
||||
"callQuality": "Gerenciar qualidade do vídeo",
|
||||
"carmode": "Modo carro",
|
||||
"cc": "Alternar legendas",
|
||||
"chat": "Alternar para janela de chat",
|
||||
"clap": "Aplauso",
|
||||
"closeChat": "Fechar chat",
|
||||
"closeMoreActions": "Fechar o menu de mais ações",
|
||||
"closeParticipantsPane": "Fechar painel de participantes",
|
||||
"collapse": "Recolher",
|
||||
"document": "Alternar para documento compartilhado",
|
||||
"documentClose": "Fechar documento compartilhado",
|
||||
"documentOpen": "Abrir documento compartilhado",
|
||||
"download": "Baixe nossos aplicativos",
|
||||
"embedMeeting": "Reunião em formato compacto",
|
||||
"endConference": "Terminar para todos",
|
||||
"enterFullScreen": "Ver em tela-cheia",
|
||||
"enterTileView": "Entrar na visualização em blocos",
|
||||
"exitFullScreen": "Sair da tela-cheia",
|
||||
"exitTileView": "Sair da visualização em blocos",
|
||||
"expand": "Expandir",
|
||||
"feedback": "Deixar feedback",
|
||||
"fullScreen": "Alternar para tela cheia",
|
||||
"giphy": "Alternar menu do GIPHY",
|
||||
"grantModerator": "Atribuir Moderador",
|
||||
"hangup": "Sair da chamada",
|
||||
"heading": "Barra de ferramentas",
|
||||
"help": "Ajuda",
|
||||
"hideWhiteboard": "Ocultar quadro branco",
|
||||
"invite": "Convidar pessoas",
|
||||
"kick": "Remover participante",
|
||||
"laugh": "Risada",
|
||||
"leaveConference": "Sair da reunião",
|
||||
"like": "Gostei",
|
||||
"linkToSalesforce": "Link com o Salesforce",
|
||||
"lobbyButton": "Habilitar/desabilitar sala de espera",
|
||||
"localRecording": "Alternar controles de gravação local",
|
||||
"lockRoom": "Ativar/desativar senha de reunião",
|
||||
"lowerHand": "Abaixar a mão",
|
||||
"moreActions": "Alternar mais menu de ações",
|
||||
"moreActionsMenu": "Menu de mais ações",
|
||||
"moreOptions": "Mostrar mais opções",
|
||||
"mute": "Alternar mudo do áudio",
|
||||
"muteEveryone": "Silenciar todos",
|
||||
"muteEveryoneElse": "Silenciar todos os demais",
|
||||
"muteEveryoneElsesVideo": "Desativar a câmera de todos os demais",
|
||||
"muteEveryonesVideo": "Desativar a câmera de todos",
|
||||
"muteEveryoneElsesVideoStream": "Parar o vídeo de todos os outros",
|
||||
"muteEveryonesVideoStream": "Parar o vídeo de todos",
|
||||
"muteGUMPending": "Conectando seu microfone",
|
||||
"noiseSuppression": "Supressão de ruído",
|
||||
"openChat": "Abrir chat",
|
||||
"participants": "Participantes",
|
||||
"pip": "Alternar modo Picture-in-Picture",
|
||||
"privateMessage": "Enviar mensagem privada",
|
||||
"profile": "Editar seu perfil",
|
||||
"raiseHand": "Alternar levantar a mão",
|
||||
"reactions": "Reações",
|
||||
"reactionsMenu": "Abrir / fechar menu de reações",
|
||||
"recording": "Alternar gravação",
|
||||
"remoteMute": "Silenciar participante",
|
||||
"remoteVideoMute": "Desativar a câmera do participante",
|
||||
"security": "Opções de segurança",
|
||||
"selectBackground": "Selecionar Fundo",
|
||||
"selfView": "Alternar auto-visualização",
|
||||
"shareRoom": "Convidar alguém",
|
||||
"shareYourScreen": "Alternar compartilhamento de tela",
|
||||
"shareaudio": "Compartilhar áudio",
|
||||
"sharedvideo": "Alternar compartilhamento de vídeo",
|
||||
"shortcuts": "Alternar atalhos",
|
||||
"show": "Mostrar no palco",
|
||||
"showWhiteboard": "Exibir quadro branco",
|
||||
"silence": "Silenciar",
|
||||
"speakerStats": "Alternar estatísticas do apresentador",
|
||||
"stopScreenSharing": "Parar de compartilhar sua tela",
|
||||
"stopSharedVideo": "Parar vídeo",
|
||||
"surprised": "Surpresa",
|
||||
"tileView": "Alternar visualização em blocos",
|
||||
"toggleCamera": "Alternar câmera",
|
||||
"toggleFilmstrip": "Alterar tira de filme",
|
||||
"unmute": "Ativar som",
|
||||
"videoblur": "Alternar desfoque de vídeo",
|
||||
"videomute": "Alternar mudo do vídeo"
|
||||
"videomute": "Alternar mudo do vídeo",
|
||||
"videomuteGUMPending": "Conectando sua câmera",
|
||||
"videounmute": "Ativar câmera"
|
||||
},
|
||||
"addPeople": "Adicionar pessoas à sua chamada",
|
||||
"audioOnlyOff": "Desabilitar modo de largura de banda baixa",
|
||||
@@ -963,23 +1199,33 @@
|
||||
"chat": "Abrir ou fechar o bate-papo",
|
||||
"clap": "Aplauso",
|
||||
"closeChat": "Fechar chat",
|
||||
"closeParticipantsPane": "Fechar painel de participantes",
|
||||
"closeReactionsMenu": "Fechar menu de reações",
|
||||
"disableNoiseSuppression": "Desabilitar supressão de ruído",
|
||||
"disableReactionSounds": "Você pode desabilitar os sons de reação para esta reunião",
|
||||
"documentClose": "Fechar documento compartilhado",
|
||||
"documentOpen": "Abrir documento compartilhado",
|
||||
"download": "Baixe nossos aplicativos",
|
||||
"e2ee": "Encriptação ponto a ponto",
|
||||
"embedMeeting": "Reunião em formato compacto",
|
||||
"enableNoiseSuppression": "Habilitar supressão de ruído",
|
||||
"endConference": "Terminar para todos",
|
||||
"enterFullScreen": "Ver em tela cheia",
|
||||
"enterTileView": "Entrar em exibição de bloco",
|
||||
"exitFullScreen": "Sair da tela cheia",
|
||||
"exitTileView": "Sair de exibição de bloco",
|
||||
"feedback": "Deixar feedback",
|
||||
"giphy": "Alternar menu do GIPHY",
|
||||
"hangup": "Sair",
|
||||
"help": "Ajuda",
|
||||
"hideWhiteboard": "Ocultar quadro branco",
|
||||
"invite": "Convidar pessoas",
|
||||
"joinBreakoutRoom": "Ingressar na sala de apoio",
|
||||
"laugh": "Risada",
|
||||
"leaveBreakoutRoom": "Sair da sala de apoio",
|
||||
"leaveConference": "Sair da reunião",
|
||||
"like": "Gostei",
|
||||
"linkToSalesforce": "Link com o Salesforce",
|
||||
"lobbyButtonDisable": "Desabilitar sala de espera",
|
||||
"lobbyButtonEnable": "Habilitar sala de espera",
|
||||
"login": "Iniciar sessão",
|
||||
@@ -990,11 +1236,13 @@
|
||||
"mute": "Mudo / Não mudo",
|
||||
"muteEveryone": "Silenciar todos",
|
||||
"muteEveryonesVideo": "Desativar a câmera de todos",
|
||||
"muteGUMPending": "Conectando seu microfone",
|
||||
"noAudioSignalDesc": "Se você não o desativou propositalmente das configurações do sistema ou do hardware, considere trocar o dispositivo.",
|
||||
"noAudioSignalDescSuggestion": "Se você não o desativou propositalmente das configurações do sistema ou do hardware, considere trocar para o dispositivo sugerido.",
|
||||
"noAudioSignalDialInDesc": "Você também pode discar usando:",
|
||||
"noAudioSignalDialInLinkDesc": "Discar números",
|
||||
"noAudioSignalTitle": "Não há entrada de áudio vindo do seu microfone!",
|
||||
"noiseSuppression": "Supressão de ruído",
|
||||
"noisyAudioInputDesc": "Parece que o microfone está fazendo barulho, considere silenciar ou alterar o dispositivo.",
|
||||
"noisyAudioInputTitle": "O seu microfone parece estar barulhento!",
|
||||
"openChat": "Abrir chat",
|
||||
@@ -1011,12 +1259,14 @@
|
||||
"reactionLike": "Enviar reação de gostei",
|
||||
"reactionSilence": "Enviar reação de silêncio",
|
||||
"reactionSurprised": "Enviar reação de surpresa",
|
||||
"reactions": "Reações",
|
||||
"security": "Opções de segurança",
|
||||
"selectBackground": "Selecionar fundo",
|
||||
"shareRoom": "Convidar alguém",
|
||||
"shareaudio": "Compartilhar áudio",
|
||||
"sharedvideo": "Compartilhar um vídeo",
|
||||
"shortcuts": "Ver atalhos",
|
||||
"showWhiteboard": "Exibir quadro branco",
|
||||
"silence": "Silêncio",
|
||||
"speakerStats": "Estatísticas do Apresentador",
|
||||
"startScreenSharing": "Iniciar compart. de tela",
|
||||
@@ -1029,8 +1279,11 @@
|
||||
"talkWhileMutedPopup": "Tentando falar? Você está em mudo.",
|
||||
"tileViewToggle": "Alternar visualização em blocos",
|
||||
"toggleCamera": "Alternar câmera",
|
||||
"unmute": "Ativar som",
|
||||
"videoSettings": "Configurações de vídeo",
|
||||
"videomute": "Iniciar ou parar a câmera"
|
||||
"videomute": "Iniciar ou parar a câmera",
|
||||
"videomuteGUMPending": "Conectando sua câmera",
|
||||
"videounmute": "Ativar câmera"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Iniciar/parar legendas",
|
||||
@@ -1040,10 +1293,15 @@
|
||||
"labelToolTip": "A reunião esta sendo transcrita",
|
||||
"off": "Transcrição parada",
|
||||
"pending": "Preparando a transcrição da reunião...",
|
||||
"sourceLanguageDesc": "No momento o idioma da reunião está definido para <b>{{sourceLanguage}}</b>. <br/> Você pode alterar-lo ",
|
||||
"sourceLanguageHere": "aqui",
|
||||
"start": "Exibir legendas",
|
||||
"stop": "Não exibir legendas",
|
||||
"subtitles": "Legendas",
|
||||
"subtitlesOff": "Desativadas",
|
||||
"tr": "TR"
|
||||
},
|
||||
"unpinParticipant": "{{participantName}} - Desafixar",
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "Selecione <b><i>Permitir</i></b> quando seu navegador perguntar pelas permissões.",
|
||||
"chromeGrantPermissions": "Selecione <b><i>Permitir</i></b> quando seu navegador perguntar pelas permissões.",
|
||||
@@ -1067,20 +1325,26 @@
|
||||
"pending": "{{displayName}} foi convidado"
|
||||
},
|
||||
"videoStatus": {
|
||||
"adjustFor": "Ajustar para:",
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "Você está em modo de banda baixa. Neste modo, se recebe somente áudio e compartilhamento de tela.",
|
||||
"bestPerformance": "Melhor performance",
|
||||
"callQuality": "Qualidade de vídeo",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Ver vídeo em alta definição",
|
||||
"highDefinition": "Alta definição (HD)",
|
||||
"highestQuality": "Alta qualidade",
|
||||
"labelTooiltipNoVideo": "Sem vídeo",
|
||||
"labelTooltipAudioOnly": "Modo de largura de banda baixa habilitada",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Ver vídeo em baixa definição",
|
||||
"lowDefinition": "Baixa definição (LD)",
|
||||
"performanceSettings": "Configurações de performance",
|
||||
"recording": "Gravação em andamento",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Ver vídeo em definição padrão",
|
||||
"standardDefinition": "Definição padrão"
|
||||
"standardDefinition": "Definição padrão",
|
||||
"streaming": "Streaming em andamento"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Informações da Conexão",
|
||||
@@ -1090,12 +1354,19 @@
|
||||
"domuteVideoOfOthers": "Desativar a câmera de todos os demais",
|
||||
"flip": "Inverter",
|
||||
"grantModerator": "Atribuir Moderador",
|
||||
"hideSelfView": "Ocultar auto-visualização",
|
||||
"kick": "Remover",
|
||||
"mirrorVideo": "Espelhar meu vídeo",
|
||||
"moderator": "Moderador",
|
||||
"mute": "Participante está mudo",
|
||||
"muted": "Mudo",
|
||||
"pinToStage": "Fixar no palco",
|
||||
"remoteControl": "Controle remoto",
|
||||
"screenSharing": "O participante está compartilhando sua tela",
|
||||
"show": "Mostrar no palco",
|
||||
"showSelfView": "Exibir auto-visualização",
|
||||
"unpinFromStage": "Desafixar",
|
||||
"verify": "Verificar participante",
|
||||
"videoMuted": "Câmera desativada",
|
||||
"videomute": "O participante parou a câmera"
|
||||
},
|
||||
@@ -1120,7 +1391,16 @@
|
||||
"slightBlur": "Desfoque suave",
|
||||
"title": "Fundos virtuais",
|
||||
"uploadedImage": "Imagem enviada {{index}}",
|
||||
"webAssemblyWarning": "Não há suporte para WebAssembly"
|
||||
"webAssemblyWarning": "Não há suporte para WebAssembly",
|
||||
"webAssemblyWarningDescription": "WebAssembly desativado ou não suportado neste navegador"
|
||||
},
|
||||
"visitors": {
|
||||
"chatIndicator": "(visitante)",
|
||||
"labelTooltip": "Número de visitantes: {{count}}",
|
||||
"notification": {
|
||||
"description": "Para participar levante a mão",
|
||||
"title": "Você é um visitante na reunião"
|
||||
}
|
||||
},
|
||||
"volumeSlider": "Controle de volume",
|
||||
"welcomepage": {
|
||||
@@ -1154,6 +1434,7 @@
|
||||
"microsoftLogo": "Logo da Microsoft",
|
||||
"policyLogo": "Logo da Política de Privacidade"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "Reuniões",
|
||||
"mobileDownLoadLinkAndroid": "Baixar aplicativo móvel para Android",
|
||||
"mobileDownLoadLinkFDroid": "Baixar aplicativo móvel para F-Droid",
|
||||
"mobileDownLoadLinkIos": "Baixar aplicativo móvel para iOS",
|
||||
@@ -1162,13 +1443,21 @@
|
||||
"recentList": "Recente",
|
||||
"recentListDelete": "Remover",
|
||||
"recentListEmpty": "Sua lista recente está vazia. As reuniões que você realizar serão exibidas aqui.",
|
||||
"recentMeetings": "Suas reuniões recentes",
|
||||
"reducedUIText": "Bem-vindo ao {{app}}!",
|
||||
"roomNameAllowedChars": "Nome da reunião não deve conter qualquer um destes caracteres: ?. &, :, ', \", %, #.",
|
||||
"roomname": "Digite o nome da sala",
|
||||
"roomnameHint": "Digite o nome ou a URL da sala que você deseja entrar. Você pode digitar um nome, e apenas deixe para as pessoas que você quer se reunir digitem o mesmo nome.",
|
||||
"sendFeedback": "Enviar comentários",
|
||||
"settings": "Configurações",
|
||||
"startMeeting": "Iniciar reunião",
|
||||
"terms": "Termos",
|
||||
"title": "Videoconferências mais seguras, flexíveis e totalmente gratuitas"
|
||||
"title": "Videoconferências mais seguras, flexíveis e totalmente gratuitas",
|
||||
"upcomingMeetings": "Suas próximas reuniões"
|
||||
},
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Quadro branco"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
"leaveBreakoutRoom": "Leave breakout room",
|
||||
"more": "More",
|
||||
"remove": "Remove",
|
||||
"rename": "Rename",
|
||||
"renameBreakoutRoom": "Rename breakout room",
|
||||
"sendToBreakoutRoom": "Send participant to:"
|
||||
},
|
||||
"defaultName": "Breakout room #{{index}}",
|
||||
@@ -386,6 +388,8 @@
|
||||
"removePassword": "Remove $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": "Are you sure you would like to remove your shared video?",
|
||||
"removeSharedVideoTitle": "Remove shared video",
|
||||
"renameBreakoutRoomLabel": "Room name",
|
||||
"renameBreakoutRoomTitle": "Rename breakout room",
|
||||
"reservationError": "Reservation system error",
|
||||
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
|
||||
"retry": "Retry",
|
||||
@@ -743,7 +747,6 @@
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
"noiseSuppressionDesktopAudioDescription": "Noise suppression can't be enabled while sharing desktop audio, please disable it and try again.",
|
||||
"noiseSuppressionFailedTitle": "Failed to start noise suppression",
|
||||
"noiseSuppressionNoTrackDescription": "Please unmute your microphone first.",
|
||||
"noiseSuppressionStereoDescription": "Stereo audio noise suppression is not currently supported.",
|
||||
"oldElectronClientDescription1": "You appear to be using an old version of the Jitsi Meet client which has known security vulnerabilities. Please make sure you update to our ",
|
||||
"oldElectronClientDescription2": "latest build",
|
||||
|
||||
@@ -2002,14 +2002,16 @@ class API {
|
||||
* Notify external application ( if API is enabled) that a participant menu button was clicked.
|
||||
*
|
||||
* @param {string} key - The key of the participant menu button.
|
||||
* @param {string} participantId - The ID of the participant for with the participant menu button was clicked.
|
||||
* @param {string} participantId - The ID of the participant for whom the participant menu button was clicked.
|
||||
* @param {boolean} preventExecution - Whether execution of the button click was prevented or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyParticipantMenuButtonClicked(key, participantId) {
|
||||
notifyParticipantMenuButtonClicked(key, participantId, preventExecution) {
|
||||
this._sendEvent({
|
||||
name: 'participant-menu-button-clicked',
|
||||
key,
|
||||
participantId
|
||||
participantId,
|
||||
preventExecution
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,15 @@ import { parseURLParams } from '../../react/features/base/util/parseURLParams';
|
||||
|
||||
/**
|
||||
* JitsiMeetExternalAPI id - unique for a webpage.
|
||||
* TODO: This shouldn't be computed here.
|
||||
*/
|
||||
export const API_ID = parseURLParams(window.location).jitsi_meet_external_api_id;
|
||||
let _apiID;
|
||||
|
||||
try {
|
||||
_apiID = parseURLParams(window.location).jitsi_meet_external_api_id;
|
||||
} catch (_) { /* Ignore. */ }
|
||||
|
||||
export const API_ID = _apiID;
|
||||
|
||||
/**
|
||||
* The payload name for the datachannel/endpoint text message event.
|
||||
|
||||
82
package-lock.json
generated
82
package-lock.json
generated
@@ -18,7 +18,7 @@
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/js-utils": "2.0.6",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
@@ -60,7 +60,7 @@
|
||||
"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/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -83,7 +83,7 @@
|
||||
"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-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
@@ -3110,9 +3110,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jitsi/js-utils": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.5.tgz",
|
||||
"integrity": "sha512-Aa7lt/sGsDymWnKJtM1RePmR2b2J5TwY3QLv5iOmzMDYR+5RE0NyYc/vKW51JeatDVSkj+LT7kpUDvtJua0rmQ==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
|
||||
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
|
||||
"dependencies": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
@@ -5526,6 +5526,11 @@
|
||||
"yarn": ">= 1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@testrtc/watchrtc-sdk": {
|
||||
"version": "1.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
|
||||
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -7109,6 +7114,11 @@
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
},
|
||||
"node_modules/async-es": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async-es/-/async-es-3.2.4.tgz",
|
||||
"integrity": "sha512-GFHAAfmW7GzEiHFR6DvE5WMm6+js9pb+RLm+m1UpsyMX+I4j/R4QVw2Te664q+fvDVOz7Y0bORPDNvQS7BJ3Hw=="
|
||||
},
|
||||
"node_modules/async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
@@ -12760,8 +12770,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTHkhQiuLn28wrDhzcgDwJTJ106bgWMgARxgVZpFM1FyRjgJt2e9pn6mZtjEKT5FZeUcUtpy9cDuU6+yLusZEw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -12769,7 +12779,8 @@
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "3.2.3",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
"async-es": "3.2.4",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jquery": "3.6.1",
|
||||
@@ -15807,12 +15818,12 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz",
|
||||
"integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g=="
|
||||
},
|
||||
"node_modules/react-native-immersive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz",
|
||||
"integrity": "sha512-9TL05nTHN/x9sN1wbUlBoGyzH4NCuZ/7WEEUp5CvOoKuUABvdYosov0O0SAMbm/5J913RRoy98VB6tGNi9lRSw==",
|
||||
"node_modules/react-native-immersive-mode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
|
||||
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw==",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.47.0"
|
||||
"react-native": ">=0.60.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-keep-awake": {
|
||||
@@ -19717,9 +19728,9 @@
|
||||
"integrity": "sha512-rCPf3AakAAgvapnbYVvG2bQyI3g6EDbPpjDJ72fdAu+XTzB1qvX4ZC6OnZ0I2+thaspjTb+8KwdyhdBl8Lt/QA=="
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -22020,9 +22031,9 @@
|
||||
"integrity": "sha512-iK7p7i6qJFOkjTVZhWDvurDW1u+eMoOhAVgpab9CZEqCTX+W4Ih4AOPrUpf+mjaAHK5XqmQZSc5nVEpMg+xIGQ=="
|
||||
},
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.5.tgz",
|
||||
"integrity": "sha512-Aa7lt/sGsDymWnKJtM1RePmR2b2J5TwY3QLv5iOmzMDYR+5RE0NyYc/vKW51JeatDVSkj+LT7kpUDvtJua0rmQ==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
|
||||
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
@@ -23720,6 +23731,11 @@
|
||||
"seedrandom": "2.4.3"
|
||||
}
|
||||
},
|
||||
"@testrtc/watchrtc-sdk": {
|
||||
"version": "1.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
|
||||
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
|
||||
},
|
||||
"@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -24989,6 +25005,11 @@
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
},
|
||||
"async-es": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async-es/-/async-es-3.2.4.tgz",
|
||||
"integrity": "sha512-GFHAAfmW7GzEiHFR6DvE5WMm6+js9pb+RLm+m1UpsyMX+I4j/R4QVw2Te664q+fvDVOz7Y0bORPDNvQS7BJ3Hw=="
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
@@ -29268,14 +29289,15 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTHkhQiuLn28wrDhzcgDwJTJ106bgWMgARxgVZpFM1FyRjgJt2e9pn6mZtjEKT5FZeUcUtpy9cDuU6+yLusZEw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "3.2.3",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
"async-es": "3.2.4",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jquery": "3.6.1",
|
||||
@@ -31577,10 +31599,10 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz",
|
||||
"integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g=="
|
||||
},
|
||||
"react-native-immersive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz",
|
||||
"integrity": "sha512-9TL05nTHN/x9sN1wbUlBoGyzH4NCuZ/7WEEUp5CvOoKuUABvdYosov0O0SAMbm/5J913RRoy98VB6tGNi9lRSw=="
|
||||
"react-native-immersive-mode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
|
||||
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw=="
|
||||
},
|
||||
"react-native-keep-awake": {
|
||||
"version": "4.0.0",
|
||||
@@ -34415,9 +34437,9 @@
|
||||
"integrity": "sha512-rCPf3AakAAgvapnbYVvG2bQyI3g6EDbPpjDJ72fdAu+XTzB1qvX4ZC6OnZ0I2+thaspjTb+8KwdyhdBl8Lt/QA=="
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "jitsi-meet",
|
||||
"version": "0.0.0",
|
||||
"description": "A sample app for the Jitsi Videobridge",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jitsi/jitsi-meet"
|
||||
@@ -23,7 +24,7 @@
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/js-utils": "2.0.6",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
@@ -65,7 +66,7 @@
|
||||
"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/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -88,7 +89,7 @@
|
||||
"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-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
diff --git a/node_modules/react-native-immersive/index.js b/node_modules/react-native-immersive/index.js
|
||||
index 55dab57..110260b 100644
|
||||
--- a/node_modules/react-native-immersive/index.js
|
||||
+++ b/node_modules/react-native-immersive/index.js
|
||||
@@ -18,7 +18,13 @@ const Immersive = Platform.OS === 'android' ? {
|
||||
isListenerEnabled = true
|
||||
RNImmersive.addImmersiveListener()
|
||||
},
|
||||
- removeImmersiveListener: (listener) => DeviceEventEmitter.removeListener('@@IMMERSIVE_STATE_CHANGED', listener)
|
||||
+ removeImmersiveListener: (listener) => {
|
||||
+ const immersiveListener = DeviceEventEmitter.addListener('@@IMMERSIVE_STATE_CHANGED', listener);
|
||||
+
|
||||
+ return () => {
|
||||
+ immersiveListener.remove();
|
||||
+ }
|
||||
+ }
|
||||
} : {
|
||||
on: unSupportedError,
|
||||
off: unSupportedError,
|
||||
@@ -2,20 +2,23 @@
|
||||
|
||||
|
||||
## Installation
|
||||
Inside your project, run `npm i @jitsi/react-native-sdk`.<br/><br/>Additionally, if not already installed, some dependencies will need to be added.
|
||||
Inside your project, run;
|
||||
```console
|
||||
npm i @jitsi/react-native-sdk
|
||||
```
|
||||
<br/><br/>Additionally, if not already installed, some dependencies will need to be added.
|
||||
|
||||
This can be done by running the following script:
|
||||
```
|
||||
```console
|
||||
node node_modules/@jitsi/react-native-sdk/update_dependencies.js
|
||||
```
|
||||
This will check and update all your dependencies.<br/><br/>
|
||||
|
||||
|
||||
[comment]: # (These deps definitely need to be added manually, more could be neccesary)
|
||||
|
||||
Because of SVG use in react native, you need to update metro.config your project's file:
|
||||
|
||||
```
|
||||
```javascript
|
||||
const { getDefaultConfig } = require('metro-config');
|
||||
|
||||
module.exports = (async () => {
|
||||
@@ -56,22 +59,31 @@ module.exports = (async () => {
|
||||
- Audio
|
||||
- Voice over IP
|
||||
- Background fetch
|
||||
- Add Copy Sounds step:
|
||||
1. Open XCode, go to Build Phases and add this step and the script below.
|
||||
|
||||
Run;
|
||||
```console
|
||||
cd ios && pod install && cd ..
|
||||
```
|
||||
SOUNDS_DIR="${PROJECT_DIR}/../node_modules/@jitsi/react-native-sdk/sounds"
|
||||
cp $SOUNDS_DIR/* ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
|
||||
```
|
||||
|
||||
Run `cd ios && pod install && cd ..`
|
||||
|
||||
### Android
|
||||
|
||||
- In your build.gradle have at least `minSdkVersion = 24`
|
||||
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
|
||||
```
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
```
|
||||
|
||||
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
|
||||
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
|
||||
|
||||
|
||||
### Using JWT tokens
|
||||
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
|
||||
- For example:
|
||||
```javascript
|
||||
<JitsiMeeting
|
||||
room={'ThisIsNotATestRoomName'}
|
||||
serverURL={'https://meet.jit.si/'}
|
||||
token={'dkhalhfajhflahlfaahalhfahfsl'} />
|
||||
```
|
||||
|
||||
@@ -138,3 +138,19 @@ if (isNewArchitectureEnabled()) {
|
||||
codegenJavaPackageName = "org.jitsi.meet.sdk"
|
||||
}
|
||||
}
|
||||
|
||||
// Copy sounds to assets directory
|
||||
android.libraryVariants.all { def variant ->
|
||||
def mergeAssetsTask = variant.mergeAssetsProvider.get()
|
||||
def mergeResourcesTask = variant.mergeResourcesProvider.get()
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir.get()
|
||||
def soundsDir = "${projectDir}/../sounds"
|
||||
copy {
|
||||
from("${soundsDir}")
|
||||
include("*.wav")
|
||||
include("*.mp3")
|
||||
into("${assetsDir}/sounds")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,67 @@
|
||||
/* eslint-disable lines-around-comment, no-undef, no-unused-vars */
|
||||
|
||||
import 'react-native-gesture-handler';
|
||||
// Apply all necessary polyfills as early as possible
|
||||
// to make sure anything imported henceforth sees them.
|
||||
import 'react-native-get-random-values';
|
||||
import './react/features/mobile/polyfills';
|
||||
// NB: This import must always come first.
|
||||
import './react/bootstrap.native';
|
||||
|
||||
// @ts-ignore
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
|
||||
import { appNavigate } from './react/features/app/actions.native';
|
||||
import { App } from './react/features/app/components/App.native';
|
||||
import { setAudioMuted, setVideoMuted } from './react/features/base/media/actions';
|
||||
// @ts-ignore
|
||||
import JitsiThemePaperProvider from './react/features/base/ui/components/JitsiThemeProvider.native';
|
||||
|
||||
|
||||
interface IEventListeners {
|
||||
onConferenceBlurred?: Function;
|
||||
onConferenceFocused?: Function;
|
||||
onConferenceJoined?: Function;
|
||||
onConferenceLeft?: Function;
|
||||
onConferenceWillJoin?: Function;
|
||||
onEnterPictureInPicture?: Function;
|
||||
onParticipantJoined?: Function;
|
||||
onReadyToClose?: Function;
|
||||
}
|
||||
|
||||
interface IUserInfo {
|
||||
avatarURL: string;
|
||||
displayName: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface IAppProps {
|
||||
flags: [];
|
||||
meetingOptions: {
|
||||
domain: string;
|
||||
roomName: string;
|
||||
onReadyToClose?: Function;
|
||||
onConferenceJoined?: Function;
|
||||
onConferenceWillJoin?: Function;
|
||||
onConferenceLeft?: Function;
|
||||
onParticipantJoined?: Function;
|
||||
settings?: {
|
||||
startWithAudioMuted?: boolean;
|
||||
startAudioOnly?: boolean;
|
||||
startWithVideoMuted?: boolean;
|
||||
}
|
||||
};
|
||||
config: object;
|
||||
eventListeners?: IEventListeners;
|
||||
flags?: object;
|
||||
room: string;
|
||||
serverURL?: string;
|
||||
style?: Object;
|
||||
token?: string;
|
||||
userInfo?: IUserInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
|
||||
*/
|
||||
export const JitsiMeeting = forwardRef(({ flags, meetingOptions, style }: IAppProps, ref) => {
|
||||
export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
|
||||
const [ appProps, setAppProps ] = useState({});
|
||||
const app = useRef(null);
|
||||
const {
|
||||
config,
|
||||
eventListeners,
|
||||
flags,
|
||||
room,
|
||||
serverURL,
|
||||
style,
|
||||
token,
|
||||
userInfo
|
||||
} = props;
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -64,33 +84,63 @@ export const JitsiMeeting = forwardRef(({ flags, meetingOptions, style }: IAppPr
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const url = `${meetingOptions.domain}/${meetingOptions.roomName}`;
|
||||
const urlObj = {
|
||||
config,
|
||||
jwt: token
|
||||
};
|
||||
|
||||
let urlProps;
|
||||
|
||||
if (room.includes('://')) {
|
||||
urlProps = {
|
||||
...urlObj,
|
||||
url: room
|
||||
};
|
||||
} else {
|
||||
urlProps = {
|
||||
...urlObj,
|
||||
room,
|
||||
serverURL
|
||||
};
|
||||
}
|
||||
|
||||
setAppProps({
|
||||
'url': {
|
||||
url,
|
||||
config: meetingOptions.settings
|
||||
},
|
||||
'flags': flags,
|
||||
'rnSdkHandlers': {
|
||||
onReadyToClose: meetingOptions.onReadyToClose,
|
||||
onConferenceJoined: meetingOptions.onConferenceJoined,
|
||||
onConferenceWillJoin: meetingOptions.onConferenceWillJoin,
|
||||
onConferenceLeft: meetingOptions.onConferenceLeft,
|
||||
onParticipantJoined: meetingOptions.onParticipantJoined
|
||||
onConferenceBlurred: eventListeners?.onConferenceBlurred,
|
||||
onConferenceFocused: eventListeners?.onConferenceFocused,
|
||||
onConferenceJoined: eventListeners?.onConferenceJoined,
|
||||
onConferenceWillJoin: eventListeners?.onConferenceWillJoin,
|
||||
onConferenceLeft: eventListeners?.onConferenceLeft,
|
||||
onEnterPictureInPicture: eventListeners?.onEnterPictureInPicture,
|
||||
onParticipantJoined: eventListeners?.onParticipantJoined,
|
||||
onReadyToClose: eventListeners?.onReadyToClose
|
||||
},
|
||||
'flags': { ...flags }
|
||||
'url': urlProps,
|
||||
'userInfo': userInfo
|
||||
});
|
||||
}, []
|
||||
);
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
useLayoutEffect(() => {
|
||||
/**
|
||||
* When you close the component you need to reset it.
|
||||
* In some cases it needs to be added as the parent component may have been destroyed.
|
||||
* Without this change the call remains active without having the jitsi screen.
|
||||
*/
|
||||
return () => {
|
||||
const dispatch = app.current?.state?.store?.dispatch;
|
||||
|
||||
dispatch && dispatch(appNavigate(undefined));
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style = { style }>
|
||||
<JitsiThemePaperProvider>
|
||||
{/* @ts-ignore */}
|
||||
<App
|
||||
{ ...appProps }
|
||||
ref = { app } />
|
||||
</JitsiThemePaperProvider>
|
||||
<View style = { style as ViewStyle }>
|
||||
<App
|
||||
{ ...appProps }
|
||||
ref = { app } />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -22,4 +22,14 @@ Pod::Spec.new do |s|
|
||||
s.dependency 'react-native-webrtc'
|
||||
|
||||
s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
|
||||
s.script_phase = {
|
||||
:name => 'Copy Sound Files',
|
||||
:script => '
|
||||
SOURCE_PATH="${PODS_TARGET_SRCROOT}/sounds"
|
||||
TARGET_PATH="$(dirname ${CONFIGURATION_BUILD_DIR})"
|
||||
PROJECT_NAME=$(basename $(dirname $(dirname ${PROJECT_DIR}))).app
|
||||
cp -R "${PODS_TARGET_SRCROOT}/sounds/" "${TARGET_PATH}/${PROJECT_NAME}"
|
||||
',
|
||||
}
|
||||
end
|
||||
|
||||
48
react-native-sdk/package-lock.json
generated
48
react-native-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@jitsi/react-native-sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@jitsi/react-native-sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"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/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -68,7 +68,7 @@
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "https://git@github.com/wonday/react-native-orientation-locker#f483520ea6b64b97002374a9e9f053a5299a062a",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
@@ -823,6 +823,11 @@
|
||||
"@svgr/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@testrtc/watchrtc-sdk": {
|
||||
"version": "1.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
|
||||
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -960,10 +965,10 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
"node_modules/async-es": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async-es/-/async-es-3.2.4.tgz",
|
||||
"integrity": "sha512-GFHAAfmW7GzEiHFR6DvE5WMm6+js9pb+RLm+m1UpsyMX+I4j/R4QVw2Te664q+fvDVOz7Y0bORPDNvQS7BJ3Hw=="
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@@ -2443,8 +2448,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTHkhQiuLn28wrDhzcgDwJTJ106bgWMgARxgVZpFM1FyRjgJt2e9pn6mZtjEKT5FZeUcUtpy9cDuU6+yLusZEw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -2452,7 +2457,8 @@
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "3.2.3",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
"async-es": "3.2.4",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jquery": "3.6.1",
|
||||
@@ -4200,6 +4206,11 @@
|
||||
"svgo": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"@testrtc/watchrtc-sdk": {
|
||||
"version": "1.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
|
||||
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
|
||||
},
|
||||
"@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -4318,10 +4329,10 @@
|
||||
"is-string": "^1.0.7"
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
"async-es": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async-es/-/async-es-3.2.4.tgz",
|
||||
"integrity": "sha512-GFHAAfmW7GzEiHFR6DvE5WMm6+js9pb+RLm+m1UpsyMX+I4j/R4QVw2Te664q+fvDVOz7Y0bORPDNvQS7BJ3Hw=="
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@@ -5349,14 +5360,15 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTHkhQiuLn28wrDhzcgDwJTJ106bgWMgARxgVZpFM1FyRjgJt2e9pn6mZtjEKT5FZeUcUtpy9cDuU6+yLusZEw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "3.2.3",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
"async-es": "3.2.4",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jquery": "3.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jitsi/react-native-sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.0",
|
||||
"description": "React Native SDK for Jitsi Meet.",
|
||||
"main": "index.tsx",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
"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/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -69,7 +69,7 @@
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "4.11.1",
|
||||
@@ -98,4 +98,4 @@
|
||||
"keywords": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
7
react/bootstrap.native.js
vendored
Normal file
7
react/bootstrap.native.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// https://github.com/software-mansion/react-native-gesture-handler/issues/320#issuecomment-443815828
|
||||
import 'react-native-gesture-handler';
|
||||
|
||||
// Apply all necessary polyfills as early as possible to make sure anything imported henceforth
|
||||
// sees them.
|
||||
import 'react-native-get-random-values';
|
||||
import './features/mobile/polyfills';
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { NativeModules, Platform, StyleSheet, View } from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import SplashScreen from 'react-native-splash-screen';
|
||||
|
||||
@@ -11,6 +12,7 @@ import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { clientResized, setSafeAreaInsets } from '../../base/responsive-ui/actions';
|
||||
import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDetector.native';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native';
|
||||
import { _getRouteToRender } from '../getRouteToRender.native';
|
||||
import logger from '../logger';
|
||||
|
||||
@@ -36,7 +38,7 @@ interface IProps extends AbstractAppProps {
|
||||
/**
|
||||
* An object with the feature flags.
|
||||
*/
|
||||
flags: Object;
|
||||
flags: any;
|
||||
|
||||
/**
|
||||
* An object with user information (display name, email, avatar URL).
|
||||
@@ -88,6 +90,20 @@ export class App extends AbstractApp<IProps> {
|
||||
logger.info(`Loaded SDK ${AppInfo.sdkVersion}${liteTxt}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<JitsiThemePaperProvider>
|
||||
{ super.render() }
|
||||
</JitsiThemePaperProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes feature flags and updates settings.
|
||||
*
|
||||
@@ -95,7 +111,13 @@ export class App extends AbstractApp<IProps> {
|
||||
*/
|
||||
async _extraInit() {
|
||||
const { dispatch, getState } = this.state.store ?? {};
|
||||
const { flags } = this.props;
|
||||
const { flags = {} } = this.props;
|
||||
|
||||
// CallKit does not work on the simulator, make sure we disable it.
|
||||
if (Platform.OS === 'ios' && DeviceInfo.isEmulatorSync()) {
|
||||
flags['call-integration.enabled'] = false;
|
||||
logger.info('Disabling CallKit because this is a simulator');
|
||||
}
|
||||
|
||||
// We set these early enough so then we avoid any unnecessary re-renders.
|
||||
dispatch?.(updateFlags(flags));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-expect-error
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
|
||||
import { getTokenAuthUrl } from '../authentication/functions';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { isSupportedBrowser } from '../base/environment/environment';
|
||||
@@ -36,11 +37,24 @@ export function _getRouteToRender(stateful: IStateful) {
|
||||
* @returns {Promise|undefined}
|
||||
*/
|
||||
function _getWebConferenceRoute(state: IReduxState) {
|
||||
if (!isRoomValid(state['features/base/conference'].room)) {
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (!isRoomValid(room)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const route = _getEmptyRoute();
|
||||
const config = state['features/base/config'];
|
||||
|
||||
// if we have auto redirect enabled, and we have previously logged in successfully
|
||||
// let's redirect to the auth url to get the token and login again
|
||||
if (config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt && room) {
|
||||
route.href = getTokenAuthUrl(config, room);
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
// Update the location if it doesn't match. This happens when a room is
|
||||
// joined from the welcome page. The reason for doing this instead of using
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AnyAction } from 'redux';
|
||||
|
||||
import { createConnectionEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { appWillNavigate } from '../base/app/actions';
|
||||
import { SET_ROOM } from '../base/conference/actionTypes';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
|
||||
import { getURLWithoutParams } from '../base/connection/utils';
|
||||
@@ -146,11 +147,15 @@ function _isMaybeSplitBrainError(getState: IStore['getState'], action: AnyAction
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _navigate({ getState }: IStore) {
|
||||
function _navigate({ dispatch, getState }: IStore) {
|
||||
const state = getState();
|
||||
const { app } = state['features/base/app'];
|
||||
|
||||
_getRouteToRender(state).then(route => app._navigate(route));
|
||||
_getRouteToRender(state).then((route: Object) => {
|
||||
dispatch(appWillNavigate(app, route));
|
||||
|
||||
return app._navigate(route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,16 @@ export const LOGIN = 'LOGIN';
|
||||
*/
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that we have authenticated successful when
|
||||
* tokenAuthUrl is set.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOKEN_AUTH_URL_SUCCESS
|
||||
* }
|
||||
*/
|
||||
export const SET_TOKEN_AUTH_URL_SUCCESS = 'SET_TOKEN_AUTH_URL_SUCCESS';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the cyclic operation of waiting
|
||||
* for conference owner has been aborted.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
UPGRADE_ROLE_STARTED, WAIT_FOR_OWNER
|
||||
@@ -185,3 +186,16 @@ export function waitForOwner() {
|
||||
export function openLoginDialog() {
|
||||
return openDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the config with new options.
|
||||
*
|
||||
* @param {boolean} value - The new value.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setTokenAuthUrlSuccess(value: boolean) {
|
||||
return {
|
||||
type: SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,9 +55,13 @@ export function cancelWaitForOwner() {
|
||||
// clients/consumers need an event.
|
||||
const { authRequired } = getState()['features/base/conference'];
|
||||
|
||||
authRequired && dispatch(conferenceLeft(authRequired));
|
||||
if (authRequired) {
|
||||
dispatch(conferenceLeft(authRequired));
|
||||
|
||||
dispatch(appNavigate(undefined));
|
||||
// in case we are showing lobby and on top of it wait for owner
|
||||
// we do not want to navigate away from the conference
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,17 @@ export function cancelLogin() {
|
||||
|
||||
/**
|
||||
* Cancels authentication, closes {@link WaitForOwnerDialog}
|
||||
* and navigates back to the welcome page.
|
||||
* and navigates back to the welcome page only in the case of authentication required error.
|
||||
* We can be showing the dialog while lobby is enabled and participant is still waiting there and hiding this dialog
|
||||
* should do nothing.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancelWaitForOwner() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { authRequired } = getState()['features/base/conference'];
|
||||
|
||||
authRequired && dispatch(maybeRedirectToWelcomePage());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import { connect as reduxConnect } from 'react-redux';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import { IConfig } from '../../../base/config/configType';
|
||||
import { connect } from '../../../base/connection/actions.web';
|
||||
import { toJid } from '../../../base/connection/functions';
|
||||
import { translate, translateToHTML } from '../../../base/i18n/functions';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
import { joinConference } from '../../../prejoin/actions.web';
|
||||
import {
|
||||
authenticateAndUpgradeRole,
|
||||
cancelLogin
|
||||
@@ -134,7 +134,9 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
if (conference) {
|
||||
dispatch(authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
dispatch(connect(jid, password));
|
||||
// dispatch(connect(jid, password));
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
dispatch(joinConference(undefined, false, jid, password));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { IConfig } from '../base/config/configType';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the token for authentication is available.
|
||||
@@ -12,13 +10,30 @@ export const isTokenAuthEnabled = (config: IConfig) =>
|
||||
typeof config.tokenAuthUrl === 'string'
|
||||
&& config.tokenAuthUrl.length;
|
||||
|
||||
|
||||
/**
|
||||
* Token url.
|
||||
* Creates the URL pointing to JWT token authentication service. It is
|
||||
* formatted from the 'urlPattern' argument which can contain the following
|
||||
* constants:
|
||||
* '{room}' - name of the conference room passed as <tt>roomName</tt>
|
||||
* argument to this method.
|
||||
* '{roleUpgrade}' - will contain 'true' if the URL will be used for
|
||||
* the role upgrade scenario, where user connects from anonymous domain and
|
||||
* then gets upgraded to the moderator by logging-in from the popup window.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {string}
|
||||
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
|
||||
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
|
||||
*
|
||||
* @returns {string|undefined} - The URL pointing to JWT login service or
|
||||
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
|
||||
* constructed.
|
||||
*/
|
||||
export const getTokenAuthUrl = (config: IConfig) =>
|
||||
JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null,
|
||||
config.tokenAuthUrl);
|
||||
export const getTokenAuthUrl = (config: IConfig, roomName: string) => {
|
||||
|
||||
const url = config.tokenAuthUrl;
|
||||
|
||||
if (typeof url !== 'string' || !roomName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return url.replace('{room}', roomName);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_NAVIGATE } from '../base/app/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
|
||||
import { hangup } from '../base/connection/actions';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
@@ -31,8 +33,10 @@ import {
|
||||
openLoginDialog,
|
||||
openWaitForOwnerDialog,
|
||||
redirectToDefaultLocation,
|
||||
setTokenAuthUrlSuccess,
|
||||
stopWaitForOwner,
|
||||
waitForOwner } from './actions';
|
||||
waitForOwner
|
||||
} from './actions';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
|
||||
|
||||
@@ -89,8 +93,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// CONFERENCE_FAILED caused by
|
||||
// JitsiConferenceErrors.AUTHENTICATION_REQUIRED.
|
||||
let recoverable;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [ _lobbyJid, lobbyWaitingForHost ] = error.params;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
|
||||
if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED
|
||||
|| (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR && lobbyWaitingForHost)) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
@@ -104,12 +111,25 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
case CONFERENCE_JOINED: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// auto redirect is turned on and we have succesfully logged in
|
||||
// let's mark that
|
||||
dispatch(setTokenAuthUrlSuccess(true));
|
||||
}
|
||||
|
||||
if (_isWaitingForOwner(store)) {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
store.dispatch(stopWaitForOwner());
|
||||
@@ -143,15 +163,53 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case LOGOUT: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
if (!conference) {
|
||||
break;
|
||||
}
|
||||
|
||||
store.dispatch(openLogoutDialog(() =>
|
||||
conference.room.moderator.logout(() => store.dispatch(hangup(true)))
|
||||
));
|
||||
dispatch(openLogoutDialog(() => {
|
||||
const logoutUrl = config.tokenLogoutUrl;
|
||||
|
||||
if (isTokenAuthEnabled(config)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// user is logging out remove auto redirect indication
|
||||
dispatch(setTokenAuthUrlSuccess(false));
|
||||
}
|
||||
|
||||
if (logoutUrl) {
|
||||
window.location.href = logoutUrl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.room.moderator.logout(() => dispatch(hangup(true)));
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_NAVIGATE: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (isRoomValid(room)
|
||||
&& config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt) {
|
||||
// if we have auto redirect enabled, and we have previously logged in successfully
|
||||
// we will redirect to the auth url to get the token and login again
|
||||
// we want to mark token auth success to false as if login is unsuccessful
|
||||
// the participant can join anonymously and not go in login loop
|
||||
dispatch(setTokenAuthUrlSuccess(false));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -236,9 +294,18 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were originally passed.
|
||||
// redirectToTokenAuthService
|
||||
window.location.href = getTokenAuthUrl(config)(room, false);
|
||||
const tokenAuthServiceUrl = getTokenAuthUrl(config, room);
|
||||
|
||||
if (tokenAuthServiceUrl) {
|
||||
// we have already shown the prejoin screen, so no need to show it again after obtaining the token
|
||||
window.location.href = `${tokenAuthServiceUrl}${
|
||||
tokenAuthServiceUrl.includes('#') ? '&' : '#'}skipPrejoin=true`;
|
||||
}
|
||||
} else {
|
||||
dispatch(openLoginDialog());
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { assign } from '../base/redux/functions';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
UPGRADE_ROLE_STARTED,
|
||||
@@ -15,9 +17,17 @@ export interface IAuthenticationState {
|
||||
thenableWithCancel?: {
|
||||
cancel: Function;
|
||||
};
|
||||
tokenAuthUrlSuccessful?: boolean;
|
||||
waitForOwnerTimeoutID?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the persistence of the feature {@code authentication}.
|
||||
*/
|
||||
PersistenceRegistry.register('features/authentication', {
|
||||
tokenAuthUrlSuccessful: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Listens for actions which change the state of the authentication feature.
|
||||
*
|
||||
@@ -35,6 +45,10 @@ ReducerRegistry.register<IAuthenticationState>('features/authentication',
|
||||
progress: undefined,
|
||||
thenableWithCancel: undefined
|
||||
});
|
||||
case SET_TOKEN_AUTH_URL_SUCCESS:
|
||||
return assign(state, {
|
||||
tokenAuthUrlSuccessful: action.value
|
||||
});
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
return assign(state, {
|
||||
|
||||
@@ -19,3 +19,15 @@ export const APP_WILL_MOUNT = 'APP_WILL_MOUNT';
|
||||
* }
|
||||
*/
|
||||
export const APP_WILL_UNMOUNT = 'APP_WILL_UNMOUNT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a specific App will navigate using a route (in
|
||||
* React terms).
|
||||
*
|
||||
* {
|
||||
* type: APP_WILL_NAVIGATE,
|
||||
* app: App,
|
||||
* route: Route
|
||||
* }
|
||||
*/
|
||||
export const APP_WILL_NAVIGATE = 'APP_WILL_NAVIGATE';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { IStore } from '../../app/types';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||
import {
|
||||
APP_WILL_MOUNT,
|
||||
APP_WILL_NAVIGATE,
|
||||
APP_WILL_UNMOUNT
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Signals that a specific App will mount (in the terms of React).
|
||||
@@ -44,3 +48,22 @@ export function appWillUnmount(app: Object) {
|
||||
app
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a specific App will navigate (in the terms of React).
|
||||
*
|
||||
* @param {App} app - The App which will navigate.
|
||||
* @param {Object} route - The route which will be used.
|
||||
* @returns {{
|
||||
* type: APP_WILL_NAVIGATE,
|
||||
* app: App,
|
||||
* route: Object
|
||||
* }}
|
||||
*/
|
||||
export function appWillNavigate(app: Object, route: Object) {
|
||||
return {
|
||||
type: APP_WILL_NAVIGATE,
|
||||
app,
|
||||
route
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,6 +53,25 @@ export const CONFERENCE_JOIN_IN_PROGRESS = 'CONFERENCE_JOIN_IN_PROGRESS';
|
||||
*/
|
||||
export const CONFERENCE_LEFT = 'CONFERENCE_LEFT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the conference is out of focus.
|
||||
* For example, if the user navigates to the Chat screen.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_BLURRED,
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_BLURRED = 'CONFERENCE_BLURRED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the conference is in focus.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_FOCUSED,
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_FOCUSED = 'CONFERENCE_FOCUSED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action, which indicates conference local subject changes.
|
||||
*
|
||||
|
||||
@@ -95,9 +95,14 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
|
||||
// Dispatches into features/base/conference follow:
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled: boolean, authLogin: string) => dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
// we want to ignore this event in case of tokenAuthUrl config
|
||||
// we are deprecating this and at some point will get rid of it
|
||||
if (!state['features/base/config'].tokenAuthUrl) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled: boolean, authLogin: string) => dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
}
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_FAILED,
|
||||
(err: string, ...args: any[]) => dispatch(conferenceFailed(conference, err, ...args)));
|
||||
|
||||
@@ -304,10 +304,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
// and the visitor will be redirected back to a vnode from jicofo
|
||||
if (config.oldConfig && username) {
|
||||
return {
|
||||
hosts: {
|
||||
domain: config.oldConfig.hosts.domain,
|
||||
muc: config.oldConfig.hosts.muc
|
||||
},
|
||||
hosts: config.oldConfig.hosts,
|
||||
focusUserJid: focusJid,
|
||||
disableLocalStats: false,
|
||||
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
|
||||
@@ -323,8 +320,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
|
||||
const oldConfig = {
|
||||
hosts: {
|
||||
domain: config.hosts.domain,
|
||||
muc: config.hosts.muc
|
||||
domain: ''
|
||||
},
|
||||
focusUserJid: config.focusUserJid,
|
||||
bosh: config.bosh,
|
||||
@@ -332,6 +328,9 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
websocket: config.websocket
|
||||
};
|
||||
|
||||
// copy original hosts, to make sure we do not use a modified one later
|
||||
Object.assign(oldConfig.hosts, config.hosts);
|
||||
|
||||
const domain = `${vnode}.meet.jitsi`;
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { overwriteConfig } from '../config/actions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
|
||||
import { connect, connectionDisconnected, disconnect } from '../connection/actions';
|
||||
import { validateJwt } from '../jwt/functions';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import { JitsiConferenceErrors, JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
import { PARTICIPANT_UPDATED, PIN_PARTICIPANT } from '../participants/actionTypes';
|
||||
import { PARTICIPANT_ROLE } from '../participants/constants';
|
||||
import {
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
import { destroyLocalTracks } from '../tracks/actions.any';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
import {
|
||||
authStatusChanged,
|
||||
conferenceFailed,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
@@ -204,10 +206,19 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
|
||||
if (newConfig) {
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(dispatch(conferenceWillLeave(conference)))
|
||||
.then(conference.leave())
|
||||
.then(dispatch(disconnect()))
|
||||
.then(dispatch(connect()));
|
||||
.then(() => dispatch(conferenceWillLeave(conference)))
|
||||
.then(() => conference.leave())
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
const localTracks = getLocalTracks(getState()['features/base/tracks']);
|
||||
const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
|
||||
|
||||
APP.conference.startConference(jitsiTracks).catch(logger.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -341,9 +352,23 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
async function _connectionEstablished({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
async function _connectionEstablished({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
const { tokenAuthUrl = false } = getState()['features/base/config'];
|
||||
|
||||
// if there is token auth URL defined and local participant is using jwt
|
||||
// this means it is logged in when connection is established, so we can change the state
|
||||
if (tokenAuthUrl) {
|
||||
let email;
|
||||
|
||||
if (getState()['features/base/jwt'].jwt) {
|
||||
email = getLocalParticipant(getState())?.email;
|
||||
}
|
||||
|
||||
dispatch(authStatusChanged(true, email || ''));
|
||||
}
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
// conference is handled by /conference.js.
|
||||
if (typeof APP === 'undefined') {
|
||||
@@ -392,18 +417,22 @@ function _logJwtErrors(message: string, state: IReduxState) {
|
||||
function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
_logJwtErrors(action.error.message, getState());
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.tokenAuthFailed',
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
const { connection, error } = action;
|
||||
|
||||
// do not show the notification when we will prompt the user
|
||||
// for username and password
|
||||
if (error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& getState()['features/base/jwt'].jwt) {
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.tokenAuthFailed',
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
|
||||
const result = next(action);
|
||||
|
||||
_removeUnloadHandler(getState);
|
||||
|
||||
const { connection } = action;
|
||||
const { error } = action;
|
||||
|
||||
forEachConference(getState, conference => {
|
||||
// TODO: revisit this
|
||||
// It feels that it would make things easier if JitsiConference
|
||||
|
||||
@@ -68,6 +68,33 @@ type ButtonsWithNotifyClick = 'camera' |
|
||||
'add-passcode' |
|
||||
'__end';
|
||||
|
||||
type ParticipantMenuButtonsWithNotifyClick = 'allow-video' |
|
||||
'ask-unmute' |
|
||||
'conn-status' |
|
||||
'flip-local-video' |
|
||||
'grant-moderator' |
|
||||
'hide-self-view' |
|
||||
'kick' |
|
||||
'mute' |
|
||||
'mute-others' |
|
||||
'mute-others-video' |
|
||||
'mute-video' |
|
||||
'pinToStage' |
|
||||
'privateMessage' |
|
||||
'remote-control' |
|
||||
'send-participant-to-room' |
|
||||
'verify';
|
||||
|
||||
type NotifyClickButtonKey = string |
|
||||
ButtonsWithNotifyClick |
|
||||
ParticipantMenuButtonsWithNotifyClick;
|
||||
|
||||
export type NotifyClickButton = NotifyClickButtonKey |
|
||||
{
|
||||
key: NotifyClickButtonKey;
|
||||
preventExecution: boolean;
|
||||
};
|
||||
|
||||
export type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
|
||||
'E2EE_OFF_SOUND' |
|
||||
'E2EE_ON_SOUND' |
|
||||
@@ -126,6 +153,25 @@ export interface INoiseSuppressionConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWatchRTCConfiguration {
|
||||
allowBrowserLogCollection?: boolean;
|
||||
collectionInterval?: number;
|
||||
console?: {
|
||||
level: string;
|
||||
override: boolean;
|
||||
};
|
||||
debug?: boolean;
|
||||
keys?: any;
|
||||
logGetStats?: boolean;
|
||||
proxyUrl?: string;
|
||||
rtcApiKey: string;
|
||||
rtcPeerId?: string;
|
||||
rtcRoomId?: string;
|
||||
rtcTags?: string[];
|
||||
rtcToken?: string;
|
||||
wsUrl?: string;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
_desktopSharingSourceDevice?: string;
|
||||
_immediateReloadThreshold?: string;
|
||||
@@ -145,6 +191,7 @@ export interface IConfig {
|
||||
rtcstatsStoreLogs?: boolean;
|
||||
rtcstatsUseLegacy?: boolean;
|
||||
scriptURLs?: Array<string>;
|
||||
watchRTCEnabled?: boolean;
|
||||
whiteListedEvents?: string[];
|
||||
};
|
||||
apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>;
|
||||
@@ -283,6 +330,7 @@ export interface IConfig {
|
||||
disableThirdPartyRequests?: boolean;
|
||||
disableTileEnlargement?: boolean;
|
||||
disableTileView?: boolean;
|
||||
disableVirtualBackground?: boolean;
|
||||
disabledNotifications?: Array<string>;
|
||||
disabledSounds?: Array<Sounds>;
|
||||
doNotFlipLocalVideo?: boolean;
|
||||
@@ -447,6 +495,10 @@ export interface IConfig {
|
||||
mobileCodecPreferenceOrder?: Array<string>;
|
||||
stunServers?: Array<{ urls: string; }>;
|
||||
};
|
||||
participantMenuButtonsWithNotifyClick?: Array<string | ParticipantMenuButtonsWithNotifyClick | {
|
||||
key: string | ParticipantMenuButtonsWithNotifyClick;
|
||||
preventExecution: boolean;
|
||||
}>;
|
||||
participantsPane?: {
|
||||
hideModeratorSettingsTab?: boolean;
|
||||
hideMoreActionsButton?: boolean;
|
||||
@@ -524,6 +576,8 @@ export interface IConfig {
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenLogoutUrl?: string;
|
||||
toolbarButtons?: Array<ToolbarButtons>;
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
@@ -559,6 +613,7 @@ export interface IConfig {
|
||||
mobileCodecPreferenceOrder?: Array<string>;
|
||||
persist?: boolean;
|
||||
};
|
||||
watchRTCConfigParams?: IWatchRTCConfiguration;
|
||||
webhookProxyUrl?: string;
|
||||
webrtcIceTcpDisable?: boolean;
|
||||
webrtcIceUdpDisable?: boolean;
|
||||
|
||||
@@ -15,6 +15,7 @@ export default [
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'analytics.disabled',
|
||||
'analytics.rtcstatsEnabled',
|
||||
'analytics.watchRTCEnabled',
|
||||
'audioLevelsInterval',
|
||||
'audioQuality',
|
||||
'autoKnockLobby',
|
||||
@@ -193,6 +194,7 @@ export default [
|
||||
'openSharedDocumentOnJoin',
|
||||
'opusMaxAverageBitrate',
|
||||
'p2p',
|
||||
'participantMenuButtonsWithNotifyClick',
|
||||
'participantsPane',
|
||||
'pcStatsInterval',
|
||||
'prejoinConfig',
|
||||
@@ -227,6 +229,7 @@ export default [
|
||||
'useHostPageLocalStorage',
|
||||
'useTurnUdp',
|
||||
'videoQuality',
|
||||
'watchRTCConfigParams',
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable',
|
||||
'whiteboard.enabled'
|
||||
|
||||
@@ -27,8 +27,10 @@ export function _cleanupConfig(config: IConfig) {
|
||||
delete config.analytics?.rtcstatsSendSdp;
|
||||
delete config.analytics?.rtcstatsUseLegacy;
|
||||
delete config.analytics?.obfuscateRoomName;
|
||||
delete config.analytics?.watchRTCEnabled;
|
||||
delete config.callStatsID;
|
||||
delete config.callStatsSecret;
|
||||
delete config.watchRTCConfigParams;
|
||||
config.giphy = { enabled: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import JitsiMeetJS from '../../base/lib-jitsi-meet';
|
||||
import { NOTIFY_CLICK_MODE } from '../../toolbox/constants';
|
||||
|
||||
import { IConfig, IDeeplinkingConfig, IDeeplinkingMobileConfig, IDeeplinkingPlatformConfig } from './configType';
|
||||
import {
|
||||
IConfig,
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingMobileConfig,
|
||||
IDeeplinkingPlatformConfig,
|
||||
NotifyClickButton
|
||||
} from './configType';
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
|
||||
export * from './functions.any';
|
||||
@@ -120,14 +127,21 @@ export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of buttons that have that notify the api when clicked.
|
||||
* Common logic to gather buttons that have to notify the api when clicked.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array} - The list of buttons.
|
||||
* @param {Array} buttonsWithNotifyClick - The array of systme buttons that need to notify the api.
|
||||
* @param {Array} customButtons - The custom buttons.
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getButtonsWithNotifyClick(state: IReduxState): Array<{ key: string; preventExecution: boolean; }> {
|
||||
const { buttonsWithNotifyClick, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => {
|
||||
const buildButtonsArray = (
|
||||
buttonsWithNotifyClick?: NotifyClickButton[],
|
||||
customButtons?: {
|
||||
icon: string;
|
||||
id: string;
|
||||
text: string;
|
||||
}[]
|
||||
): NotifyClickButton[] => {
|
||||
const customButtonsWithNotifyClick = customButtons?.map(({ id }) => {
|
||||
return {
|
||||
key: id,
|
||||
preventExecution: false
|
||||
@@ -135,13 +149,69 @@ export function getButtonsWithNotifyClick(state: IReduxState): Array<{ key: stri
|
||||
});
|
||||
|
||||
const buttons = Array.isArray(buttonsWithNotifyClick)
|
||||
? buttonsWithNotifyClick as Array<{ key: string; preventExecution: boolean; }>
|
||||
? buttonsWithNotifyClick as NotifyClickButton[]
|
||||
: [];
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
if (customButtonsWithNotifyClick) {
|
||||
buttons.push(...customButtonsWithNotifyClick);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the list of toolbar buttons that have to notify the api when clicked.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array} - The list of buttons.
|
||||
*/
|
||||
export function getButtonsWithNotifyClick(
|
||||
state: IReduxState
|
||||
): NotifyClickButton[] {
|
||||
const { buttonsWithNotifyClick, customToolbarButtons } = state['features/base/config'];
|
||||
|
||||
return buildButtonsArray(
|
||||
buttonsWithNotifyClick,
|
||||
customToolbarButtons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of participant menu buttons that have that notify the api when clicked.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array} - The list of participant menu buttons.
|
||||
*/
|
||||
export function getParticipantMenuButtonsWithNotifyClick(
|
||||
state: IReduxState
|
||||
): NotifyClickButton[] {
|
||||
const { participantMenuButtonsWithNotifyClick, customParticipantMenuButtons } = state['features/base/config'];
|
||||
|
||||
return buildButtonsArray(
|
||||
participantMenuButtonsWithNotifyClick,
|
||||
customParticipantMenuButtons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notify mode for the specified button.
|
||||
*
|
||||
* @param {string} buttonKey - The button key.
|
||||
* @param {Array} buttonsWithNotifyClick - The buttons with notify click.
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export const getButtonNotifyMode = (
|
||||
buttonKey: string,
|
||||
buttonsWithNotifyClick?: NotifyClickButton[]
|
||||
): string | undefined => {
|
||||
const notify = buttonsWithNotifyClick?.find(
|
||||
(btn: NotifyClickButton) =>
|
||||
(typeof btn === 'string' && btn === buttonKey) || (typeof btn === 'object' && btn.key === buttonKey)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
return typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { setPrejoinDisplayNameRequired } from '../../prejoin/actions.any';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
@@ -232,15 +231,6 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
|
||||
/**
|
||||
* Marks the display name for the prejoin screen as required.
|
||||
* This can happen if a user tries to join a room with lobby enabled.
|
||||
*/
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
|
||||
() => dispatch(setPrejoinDisplayNameRequired())
|
||||
);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
* {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
|
||||
|
||||
@@ -35,7 +35,7 @@ export function connect(id?: string, password?: string) {
|
||||
}
|
||||
})
|
||||
.then(j => j && dispatch(setJWT(j)))
|
||||
.then(() => _connectInternal(id, password));
|
||||
.then(() => dispatch(_connectInternal(id, password)));
|
||||
}
|
||||
|
||||
// used by jibri
|
||||
|
||||
3
react/features/base/icons/svg/edit.svg
Normal file
3
react/features/base/icons/svg/edit.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
|
||||
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
@@ -25,6 +25,7 @@ export { default as IconDeviceHeadphone } from './headset.svg';
|
||||
export { default as IconDotsHorizontal } from './dots-horizontal.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconE2EE } from './e2ee.svg';
|
||||
export { default as IconEdit } from './edit.svg';
|
||||
export { default as IconEnlarge } from './enlarge.svg';
|
||||
export { default as IconEnterFullscreen } from './enter-fullscreen.svg';
|
||||
export { default as IconEnvelope } from './envelope.svg';
|
||||
|
||||
@@ -72,6 +72,7 @@ function setupJitsiLocalStorage() {
|
||||
logger.error('Can\'t parse localStorageContent.', error);
|
||||
}
|
||||
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
jitsiLocalStorage.on('changed', onFakeLocalStorageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,13 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
|
||||
_overwriteLocalParticipant(
|
||||
store, { ...newUser,
|
||||
features: context.features });
|
||||
} else if (jwtPayload.name || jwtPayload.picture || jwtPayload.email) {
|
||||
// there are some tokens (firebase) having picture and name on the main level.
|
||||
_overwriteLocalParticipant(store, {
|
||||
avatarURL: jwtPayload.picture,
|
||||
name: jwtPayload.name,
|
||||
email: jwtPayload.email
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (typeof APP === 'undefined') {
|
||||
|
||||
@@ -337,12 +337,12 @@ export default class AbstractButton<P extends IProps, S=any> extends Component<P
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e?: React.MouseEvent<HTMLElement> | GestureResponderEvent) {
|
||||
const { afterClick, handleClick, notifyMode, buttonKey } = this.props;
|
||||
_onClick(e?: React.MouseEvent | GestureResponderEvent) {
|
||||
const { afterClick, buttonKey, handleClick, notifyMode } = this.props;
|
||||
|
||||
if (typeof APP !== 'undefined' && notifyMode) {
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { NoiseSuppressionEffect } from '../../stream-effects/noise-suppression/NoiseSuppressionEffect';
|
||||
import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background';
|
||||
|
||||
import logger from './logger';
|
||||
@@ -12,6 +13,9 @@ import logger from './logger';
|
||||
export default function loadEffects(store: IStore): Promise<any> {
|
||||
const state = store.getState();
|
||||
const virtualBackground = state['features/virtual-background'];
|
||||
const noiseSuppression = state['features/noise-suppression'];
|
||||
const { noiseSuppression: nsOptions } = state['features/base/config'];
|
||||
|
||||
|
||||
const backgroundPromise = virtualBackground.backgroundEffectEnabled
|
||||
? createVirtualBackgroundEffect(virtualBackground)
|
||||
@@ -22,5 +26,9 @@ export default function loadEffects(store: IStore): Promise<any> {
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
return Promise.all([ backgroundPromise ]);
|
||||
const noiseSuppressionPromise = noiseSuppression?.enabled
|
||||
? Promise.resolve(new NoiseSuppressionEffect(nsOptions))
|
||||
: Promise.resolve();
|
||||
|
||||
return Promise.all([ backgroundPromise, noiseSuppressionPromise ]);
|
||||
}
|
||||
|
||||
@@ -170,13 +170,13 @@ const BaseDialog = ({
|
||||
if (e.key === 'Enter' && !disableEnter) {
|
||||
submit?.();
|
||||
}
|
||||
}, []);
|
||||
}, [ disableEnter, onClose, submit ]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
}, [ handleKeyDown ]);
|
||||
|
||||
return (
|
||||
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function getUnsafeRoomText(state: IReduxState, t: Function, conte
|
||||
const link = React.createElement(Link, {
|
||||
url: securityUrl,
|
||||
children: 'here',
|
||||
key: 'support-link',
|
||||
style: { color: BaseTheme.palette.action01 } });
|
||||
|
||||
const options = {
|
||||
|
||||
@@ -77,6 +77,25 @@ export function closeBreakoutRoom(roomId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to rename a breakout room.
|
||||
*
|
||||
* @param {string} breakoutRoomJid - The jid of the breakout room to rename.
|
||||
* @param {string} name - New name / subject for the breakout room.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function renameBreakoutRoom(breakoutRoomJid: string, name = '') {
|
||||
return (_dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const trimmedName = name.trim();
|
||||
|
||||
if (trimmedName.length !== 0) {
|
||||
sendAnalytics(createBreakoutRoomsEvent('rename'));
|
||||
getCurrentConference(getState)?.getBreakoutRooms()
|
||||
?.renameBreakoutRoom(breakoutRoomJid, trimmedName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to remove a breakout room.
|
||||
*
|
||||
|
||||
@@ -2,3 +2,8 @@
|
||||
* Key for this feature.
|
||||
*/
|
||||
export const FEATURE_KEY = 'features/breakout-rooms';
|
||||
|
||||
/**
|
||||
* Feature to rename breakout rooms.
|
||||
*/
|
||||
export const BREAKOUT_ROOMS_RENAME_FEATURE = 'rename';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
BackHandler,
|
||||
NativeModules,
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../../app/actions';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
|
||||
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { getParticipantCount } from '../../../base/participants/functions';
|
||||
@@ -60,7 +61,6 @@ import TitleBar from './TitleBar';
|
||||
import { EXPANDED_LABEL_TIMEOUT } from './constants';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Conference}.
|
||||
*/
|
||||
@@ -443,7 +443,7 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
)
|
||||
}
|
||||
|
||||
<LonelyMeetingExperience />
|
||||
{ !_shouldDisplayTileView && <LonelyMeetingExperience /> }
|
||||
|
||||
{
|
||||
_shouldDisplayTileView
|
||||
@@ -608,18 +608,17 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
}
|
||||
|
||||
export default withSafeAreaInsets(connect(_mapStateToProps)(props => {
|
||||
const isFocused = useIsFocused();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
setPictureInPictureEnabled(true);
|
||||
} else {
|
||||
useFocusEffect(useCallback(() => {
|
||||
dispatch({ type: CONFERENCE_FOCUSED });
|
||||
setPictureInPictureEnabled(true);
|
||||
|
||||
return () => {
|
||||
dispatch({ type: CONFERENCE_BLURRED });
|
||||
setPictureInPictureEnabled(false);
|
||||
}
|
||||
|
||||
// We also need to disable PiP when we are back on the WelcomePage
|
||||
return () => setPictureInPictureEnabled(false);
|
||||
}, [ isFocused ]);
|
||||
};
|
||||
}, []));
|
||||
|
||||
return ( // @ts-ignore
|
||||
<Conference { ...props } />
|
||||
|
||||
@@ -5,12 +5,10 @@ export default {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
paddingVertical: BaseTheme.spacing[1]
|
||||
padding: 6
|
||||
},
|
||||
|
||||
displayNamePadding: {
|
||||
padding: BaseTheme.spacing[1],
|
||||
paddingRight: 6
|
||||
},
|
||||
|
||||
|
||||
@@ -36,4 +36,4 @@ export const SET_PASSWORD_JOIN_FAILED = 'SET_PASSWORD_JOIN_FAILED';
|
||||
/**
|
||||
* Action type to remove chattingWithModerator field
|
||||
*/
|
||||
export const REMOVE_LOBBY_CHAT_WITH_MODERATOR = 'REMOVE_LOBBY_CHAT_WITH_MODERATOR';
|
||||
export const REMOVE_LOBBY_CHAT_WITH_MODERATOR = 'REMOVE_LOBBY_CHAT_WITH_MODERATOR';
|
||||
|
||||
@@ -8,6 +8,7 @@ import { LOBBY_CHAT_MESSAGE } from '../chat/constants';
|
||||
import { handleLobbyMessageReceived } from '../chat/middleware';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import { LOBBY_NOTIFICATION_ID } from '../notifications/constants';
|
||||
import { joinConference } from '../prejoin/actions';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
@@ -205,6 +206,18 @@ export function startKnocking() {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
|
||||
if (!membersOnly) {
|
||||
|
||||
// no membersOnly, this means we got lobby screen shown as someone
|
||||
// tried to join a conference that has lobby enabled without setting display name
|
||||
// join conference should trigger the lobby/member_only path after setting the display name
|
||||
// this is possible only for web, where we can join without a prejoin screen
|
||||
dispatch(joinConference());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch(conferenceWillJoin(membersOnly));
|
||||
@@ -406,3 +419,4 @@ export function setLobbyMessageListener() {
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ export interface IProps {
|
||||
*/
|
||||
_deviceStatusVisible: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the message that display name is required is shown.
|
||||
*/
|
||||
_isDisplayNameRequiredActive: boolean;
|
||||
|
||||
/**
|
||||
* True if moderator initiated a chat session with the participant.
|
||||
*/
|
||||
@@ -435,7 +440,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
const participantId = localParticipant?.id;
|
||||
const inviteEnabledFlag = getFeatureFlag(state, INVITE_ENABLED, true);
|
||||
const { disableInviteFunctions } = state['features/base/config'];
|
||||
const { knocking, passwordJoinFailed } = state['features/lobby'];
|
||||
const { isDisplayNameRequiredError, knocking, passwordJoinFailed } = state['features/lobby'];
|
||||
const { iAmSipGateway } = state['features/base/config'];
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
|
||||
@@ -445,6 +450,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
|
||||
return {
|
||||
_deviceStatusVisible: deviceStatusVisible,
|
||||
_isDisplayNameRequiredActive: Boolean(isDisplayNameRequiredError),
|
||||
_knocking: knocking,
|
||||
_lobbyChatMessages: messages,
|
||||
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
|
||||
|
||||
@@ -153,16 +153,25 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
*/
|
||||
_renderParticipantInfo() {
|
||||
const { displayName } = this.state;
|
||||
const { t } = this.props;
|
||||
const { _isDisplayNameRequiredActive, t } = this.props;
|
||||
const showError = _isDisplayNameRequiredActive && !displayName;
|
||||
|
||||
return (
|
||||
<Input
|
||||
className = 'lobby-prejoin-input'
|
||||
id = 'lobby-name-field'
|
||||
onChange = { this._onChangeDisplayName }
|
||||
placeholder = { t('lobby.nameField') }
|
||||
testId = 'lobby.nameField'
|
||||
value = { displayName } />
|
||||
<>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'lobby-prejoin-input'
|
||||
error = { showError }
|
||||
id = 'lobby-name-field'
|
||||
onChange = { this._onChangeDisplayName }
|
||||
placeholder = { t('lobby.nameField') }
|
||||
testId = 'lobby.nameField'
|
||||
value = { displayName } />
|
||||
|
||||
{ showError && <div
|
||||
className = 'lobby-prejoin-error'
|
||||
data-testid = 'lobby.errorMessage'>{t('prejoin.errorMissingName')}</div>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
import { INotificationProps } from '../notifications/types';
|
||||
import { open as openParticipantsPane } from '../participants-pane/actions';
|
||||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
@@ -267,17 +267,23 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const nonFirstFailure = Boolean(membersOnly);
|
||||
const { isDisplayNameRequiredError } = state['features/lobby'];
|
||||
const { prejoinConfig } = state['features/base/config'];
|
||||
|
||||
if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [ _lobbyJid, lobbyWaitingForHost ] = error.params;
|
||||
|
||||
const result = next(action);
|
||||
|
||||
dispatch(openLobbyScreen());
|
||||
|
||||
if (shouldAutoKnock(state)) {
|
||||
// if there was an error about display name and pre-join is not enabled
|
||||
if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled) || lobbyWaitingForHost) {
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
|
||||
@@ -288,6 +294,18 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
|
||||
dispatch(setPasswordJoinFailed(nonFirstFailure));
|
||||
|
||||
return result;
|
||||
} else if (error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) {
|
||||
const [ isLobbyEnabled ] = error.params;
|
||||
|
||||
const result = next(action);
|
||||
|
||||
// if the error is due to required display name because lobby is enabled for the room
|
||||
// if not showing the prejoin page then show lobby UI
|
||||
if (isLobbyEnabled && !isPrejoinPageVisible(state)) {
|
||||
dispatch(openLobbyScreen());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { CONFERENCE_JOINED, CONFERENCE_LEFT, SET_PASSWORD } from '../base/conference/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
SET_PASSWORD
|
||||
} from '../base/conference/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
@@ -14,6 +20,7 @@ import {
|
||||
import { IKnockingParticipant } from './types';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
isDisplayNameRequiredError: false,
|
||||
knocking: false,
|
||||
knockingParticipants: [],
|
||||
lobbyEnabled: false,
|
||||
@@ -22,6 +29,12 @@ const DEFAULT_STATE = {
|
||||
};
|
||||
|
||||
export interface ILobbyState {
|
||||
|
||||
/**
|
||||
* A conference error when we tried to join into a room with no display name
|
||||
* when lobby is enabled in the room.
|
||||
*/
|
||||
isDisplayNameRequiredError: boolean;
|
||||
knocking: boolean;
|
||||
knockingParticipants: IKnockingParticipant[];
|
||||
lobbyEnabled: boolean;
|
||||
@@ -39,6 +52,16 @@ export interface ILobbyState {
|
||||
*/
|
||||
ReducerRegistry.register<ILobbyState>('features/lobby', (state = DEFAULT_STATE, action): ILobbyState => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED: {
|
||||
if (action.error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) {
|
||||
return {
|
||||
...state,
|
||||
isDisplayNameRequiredError: true
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
case CONFERENCE_JOINED:
|
||||
case CONFERENCE_LEFT:
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AppState } from 'react-native';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
@@ -23,12 +22,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case APP_WILL_MOUNT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
_setAppStateListener(store, next, action, _onAppStateChange.bind(undefined, dispatch));
|
||||
_setAppStateListener(store, _onAppStateChange.bind(undefined, dispatch));
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
_setAppStateListener(store, next, action, undefined);
|
||||
_setAppStateListener(store, undefined);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -55,20 +54,14 @@ function _onAppStateChange(dispatch: IStore['dispatch'], appState: string) {
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action {@code _SET_IMMERSIVE_LISTENER}
|
||||
* which is being dispatched in the specified store.
|
||||
* @param {any} listener - Listener for app state status.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setAppStateListener({ dispatch, getState }: IStore, next: Function, action: AnyAction, listener: any) {
|
||||
function _setAppStateListener({ dispatch, getState }: IStore, listener: any) {
|
||||
const { subscription } = getState()['features/background'];
|
||||
const result = next(action);
|
||||
|
||||
subscription?.remove();
|
||||
listener && dispatch(_setAppStateSubscription(AppState.addEventListener('change', listener)));
|
||||
|
||||
return result;
|
||||
dispatch(_setAppStateSubscription(listener ? AppState.addEventListener('change', listener) : undefined));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import {
|
||||
CONFERENCE_BLURRED,
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_FOCUSED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
@@ -32,12 +34,14 @@ import { getURLWithoutParams } from '../../base/connection/utils';
|
||||
import {
|
||||
JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { toggleCameraFacingMode } from '../../base/media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../base/media/constants';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/actionTypes';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getRemoteParticipants
|
||||
getRemoteParticipants,
|
||||
isScreenShareParticipantById
|
||||
} from '../../base/participants/functions';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
@@ -150,6 +154,14 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
|
||||
_registerForEndpointTextMessages(store);
|
||||
break;
|
||||
|
||||
case CONFERENCE_BLURRED:
|
||||
sendEvent(store, CONFERENCE_BLURRED, {});
|
||||
break;
|
||||
|
||||
case CONFERENCE_FOCUSED:
|
||||
sendEvent(store, CONFERENCE_FOCUSED, {});
|
||||
break;
|
||||
|
||||
case CONNECTION_DISCONNECTED: {
|
||||
// FIXME: This is a hack. See the description in the JITSI_CONNECTION_CONFERENCE_KEY constant definition.
|
||||
// Check if this connection was attached to any conference.
|
||||
@@ -198,7 +210,9 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
const { participant } = action;
|
||||
|
||||
if (participant?.isVirtualScreenshareParticipant) {
|
||||
const isVirtualScreenshareParticipant = isScreenShareParticipantById(store.getState(), participant.id);
|
||||
|
||||
if (isVirtualScreenshareParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -253,7 +267,7 @@ externalAPIEnabled && StateListenerRegistry.register(
|
||||
/* listener */ debounce((tracks: ITrack[], store: IStore) => {
|
||||
const oldScreenShares = store.getState()['features/mobile/external-api'].screenShares || [];
|
||||
const newScreenShares = tracks
|
||||
.filter(track => track.mediaType === 'video' && track.videoType === 'desktop')
|
||||
.filter(track => track.mediaType === MEDIA_TYPE.SCREENSHARE || track.videoType === VIDEO_TYPE.DESKTOP)
|
||||
.map(track => track.participantId);
|
||||
|
||||
oldScreenShares.forEach(participantId => {
|
||||
@@ -368,6 +382,10 @@ function _registerForNativeEvents(store: IStore) {
|
||||
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }: any) => {
|
||||
dispatch(setRequestingSubtitles(enabled));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.TOGGLE_CAMERA, () => {
|
||||
dispatch(toggleCameraFacingMode());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,6 +405,7 @@ function _unregisterForNativeEvents() {
|
||||
eventEmitter.removeAllListeners(ExternalAPI.CLOSE_CHAT);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SEND_CHAT_MESSAGE);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.TOGGLE_CAMERA);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* The type of (redux) action to set the react-native-immersive's change event
|
||||
* listener.
|
||||
* subscription.
|
||||
*
|
||||
* {
|
||||
* type: _SET_IMMERSIVE_LISTENER,
|
||||
* listener: Function
|
||||
* type: _SET_IMMERSIVE_SUBSCRIPTION,
|
||||
* subscription: Function
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_IMMERSIVE_LISTENER = '_SET_IMMERSIVE_LISTENER';
|
||||
export const _SET_IMMERSIVE_SUBSCRIPTION = '_SET_IMMERSIVE_SUBSCRIPTION';
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import { _SET_IMMERSIVE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the change event listener to be used with react-native-immersive's API.
|
||||
*
|
||||
* @param {Function} [listener] - The function to be used with
|
||||
* @param {Function} subscription - The function to be used with
|
||||
* react-native-immersive's API as the change event listener.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* type: _SET_IMMERSIVE_LISTENER,
|
||||
* listener: ?Function
|
||||
* type: _SET_IMMERSIVE_SUBSCRIPTION,
|
||||
* subscription: ?NativeEventSubscription
|
||||
* }}
|
||||
*/
|
||||
export function _setImmersiveListener(listener?: Function) {
|
||||
export function _setImmersiveSubscription(subscription?: NativeEventSubscription) {
|
||||
return {
|
||||
type: _SET_IMMERSIVE_LISTENER,
|
||||
listener
|
||||
type: _SET_IMMERSIVE_SUBSCRIPTION,
|
||||
subscription
|
||||
};
|
||||
}
|
||||
|
||||
3
react/features/mobile/full-screen/logger.ts
Normal file
3
react/features/mobile/full-screen/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../../base/logging/functions';
|
||||
|
||||
export default getLogger('features/full-screen');
|
||||
@@ -1,6 +1,4 @@
|
||||
// @ts-expect-error
|
||||
import { Immersive } from 'react-native-immersive';
|
||||
import { AnyAction } from 'redux';
|
||||
import ImmersiveMode from 'react-native-immersive-mode';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
@@ -8,12 +6,18 @@ import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import Platform from '../../base/react/Platform.native';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
|
||||
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
|
||||
import { _setImmersiveListener as _setImmersiveListenerA } from './actions';
|
||||
import { _setImmersiveSubscription } from './actions';
|
||||
import logger from './logger';
|
||||
|
||||
type BarVisibilityType = {
|
||||
navigationBottomBar: boolean;
|
||||
statusBar: boolean;
|
||||
};
|
||||
|
||||
type ImmersiveListener = (visibility: BarVisibilityType) => void;
|
||||
|
||||
/**
|
||||
* Middleware that captures conference actions and activates or deactivates the
|
||||
@@ -28,20 +32,14 @@ import { _setImmersiveListener as _setImmersiveListenerA } from './actions';
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case _SET_IMMERSIVE_LISTENER:
|
||||
return _setImmersiveListenerF(store, next, action);
|
||||
|
||||
case APP_WILL_MOUNT: {
|
||||
const result = next(action);
|
||||
_setImmersiveListener(store, _onImmersiveChange.bind(undefined, store));
|
||||
|
||||
store.dispatch(
|
||||
_setImmersiveListenerA(_onImmersiveChange.bind(undefined, store)));
|
||||
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch(_setImmersiveListenerA(undefined));
|
||||
_setImmersiveListener(store, undefined);
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -95,11 +93,9 @@ function _onImmersiveChange({ getState }: IStore) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setFullScreen(fullScreen: boolean) {
|
||||
// XXX The React Native module Immersive is only implemented on Android and
|
||||
// throws on other platforms.
|
||||
if (Platform.OS === 'android') {
|
||||
fullScreen ? Immersive.on() : Immersive.off();
|
||||
}
|
||||
logger.info(`Setting full-screen mode: ${fullScreen}`);
|
||||
ImmersiveMode.fullLayout(fullScreen);
|
||||
ImmersiveMode.setBarMode(fullScreen ? 'Full' : 'Normal');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,29 +105,14 @@ function _setFullScreen(fullScreen: boolean) {
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action {@code _SET_IMMERSIVE_LISTENER}
|
||||
* which is being dispatched in the specified store.
|
||||
* @param {Function} listener - Listener for immersive state.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setImmersiveListenerF({ getState }: IStore, next: Function, action: AnyAction) {
|
||||
// XXX The React Native module Immersive is only implemented on Android and
|
||||
// throws on other platforms.
|
||||
if (Platform.OS === 'android') {
|
||||
// Remove the old Immersive listener and add the new one.
|
||||
const { listener: oldListener } = getState()['features/full-screen'];
|
||||
const result = next(action);
|
||||
const { listener: newListener } = getState()['features/full-screen'];
|
||||
function _setImmersiveListener({ dispatch, getState }: IStore, listener?: ImmersiveListener) {
|
||||
const { subscription } = getState()['features/full-screen'];
|
||||
|
||||
if (oldListener !== newListener) {
|
||||
oldListener && Immersive.removeImmersiveListener(oldListener);
|
||||
newListener && Immersive.addImmersiveListener(newListener);
|
||||
}
|
||||
subscription?.remove();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
dispatch(_setImmersiveSubscription(listener ? ImmersiveMode.addEventListener(listener) : undefined));
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import ReducerRegistry from '../../base/redux/ReducerRegistry';
|
||||
|
||||
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
|
||||
import { _SET_IMMERSIVE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
export interface IFullScreenState {
|
||||
listener?: Function;
|
||||
subscription?: NativeEventSubscription;
|
||||
}
|
||||
|
||||
ReducerRegistry.register<IFullScreenState>('features/full-screen', (state = {}, action): IFullScreenState => {
|
||||
switch (action.type) {
|
||||
case _SET_IMMERSIVE_LISTENER:
|
||||
case _SET_IMMERSIVE_SUBSCRIPTION:
|
||||
return {
|
||||
...state,
|
||||
listener: action.listener
|
||||
subscription: action.subscription
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,11 @@ export function enterPictureInPicture() {
|
||||
new Error('Picture-in-Picture not supported'))
|
||||
: Promise.resolve();
|
||||
|
||||
p.then(
|
||||
() => dispatch({ type: ENTER_PICTURE_IN_PICTURE }),
|
||||
(e: string) => logger.warn(`Error entering PiP mode: ${e}`));
|
||||
p.catch((e: string) => logger.warn(`Error entering PiP mode: ${e}`));
|
||||
|
||||
// We should still dispatch ENTER_PICTURE_IN_PICTURE for cases where
|
||||
// the external app needs to handle the event (ie. react-native-sdk)
|
||||
p.finally(() => dispatch({ type: ENTER_PICTURE_IN_PICTURE }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getAppProp } from '../../base/app/functions';
|
||||
import {
|
||||
CONFERENCE_BLURRED,
|
||||
CONFERENCE_FOCUSED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN
|
||||
@@ -8,6 +10,7 @@ import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import { READY_TO_CLOSE } from '../external-api/actionTypes';
|
||||
import { participantToParticipantInfo } from '../external-api/functions';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
|
||||
|
||||
import { isExternalAPIAvailable } from './functions';
|
||||
|
||||
@@ -22,17 +25,23 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
|
||||
|
||||
switch (type) {
|
||||
case READY_TO_CLOSE:
|
||||
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
|
||||
case CONFERENCE_BLURRED:
|
||||
rnSdkHandlers?.onConferenceBlurred && rnSdkHandlers?.onConferenceBlurred();
|
||||
break;
|
||||
case CONFERENCE_FOCUSED:
|
||||
rnSdkHandlers?.onConferenceFocused && rnSdkHandlers?.onConferenceFocused();
|
||||
break;
|
||||
case CONFERENCE_JOINED:
|
||||
rnSdkHandlers?.onConferenceJoined && rnSdkHandlers?.onConferenceJoined();
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
// Props are torn down at this point, perhaps need to leave this one out
|
||||
break;
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
rnSdkHandlers?.onConferenceWillJoin && rnSdkHandlers?.onConferenceWillJoin();
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
// Props are torn down at this point, perhaps need to leave this one out
|
||||
case ENTER_PICTURE_IN_PICTURE:
|
||||
rnSdkHandlers?.onEnterPictureInPicture && rnSdkHandlers?.onEnterPictureInPicture();
|
||||
break;
|
||||
case PARTICIPANT_JOINED: {
|
||||
const { participant } = action;
|
||||
@@ -41,10 +50,11 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
rnSdkHandlers?.onParticipantJoined && rnSdkHandlers?.onParticipantJoined(participantInfo);
|
||||
break;
|
||||
}
|
||||
case READY_TO_CLOSE:
|
||||
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -56,8 +56,22 @@ export function setNoiseSuppressionEnabled(enabled: boolean): any {
|
||||
|
||||
logger.info(`Attempting to set noise suppression enabled state: ${enabled}`);
|
||||
|
||||
if (enabled === noiseSuppressionEnabled) {
|
||||
logger.warn(`Noise suppression enabled state already: ${enabled}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no local audio, simply set the enabled state. Once an audio track is created
|
||||
// the effects list will be applied.
|
||||
if (!localAudio) {
|
||||
dispatch(setNoiseSuppressionEnabledState(enabled));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled && !noiseSuppressionEnabled) {
|
||||
if (enabled) {
|
||||
if (!canEnableNoiseSuppression(state, dispatch, localAudio)) {
|
||||
return;
|
||||
}
|
||||
@@ -66,12 +80,10 @@ export function setNoiseSuppressionEnabled(enabled: boolean): any {
|
||||
dispatch(setNoiseSuppressionEnabledState(true));
|
||||
logger.info('Noise suppression enabled.');
|
||||
|
||||
} else if (!enabled && noiseSuppressionEnabled) {
|
||||
} else {
|
||||
await localAudio.setEffect(undefined);
|
||||
dispatch(setNoiseSuppressionEnabledState(false));
|
||||
logger.info('Noise suppression disabled.');
|
||||
} else {
|
||||
logger.warn(`Noise suppression enabled state already: ${enabled}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
|
||||
@@ -22,15 +22,6 @@ export function isNoiseSuppressionEnabled(state: IReduxState): boolean {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function canEnableNoiseSuppression(state: IReduxState, dispatch: IStore['dispatch'], localAudio: any): boolean {
|
||||
if (!localAudio) {
|
||||
dispatch(showWarningNotification({
|
||||
titleKey: 'notify.noiseSuppressionFailedTitle',
|
||||
descriptionKey: 'notify.noiseSuppressionNoTrackDescription'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const { channelCount } = localAudio.track.getSettings();
|
||||
|
||||
// Sharing screen audio implies an effect being applied to the local track, because currently we don't support
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
@@ -8,14 +9,18 @@ export interface INoiseSuppressionState {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const STORE_NAME = 'features/noise-suppression';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
enabled: false
|
||||
};
|
||||
|
||||
PersistenceRegistry.register(STORE_NAME);
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature features/noise-suppression.
|
||||
*/
|
||||
ReducerRegistry.register<INoiseSuppressionState>('features/noise-suppression',
|
||||
ReducerRegistry.register<INoiseSuppressionState>(STORE_NAME,
|
||||
(state = DEFAULT_STATE, action): INoiseSuppressionState => {
|
||||
const { enabled } = action;
|
||||
|
||||
|
||||
@@ -16,6 +16,19 @@ const NotificationsTransition = ({ children }: { children: ReactElement[]; }) =>
|
||||
const toMount = children?.filter(child =>
|
||||
childrenToRender.findIndex(c => c.props.uid === child.props.uid) === -1) ?? [];
|
||||
|
||||
/**
|
||||
* Update current notifications.
|
||||
* In some cases the UID is the same but the other props change.
|
||||
* This way we make sure the notification displays the latest info.
|
||||
*/
|
||||
children.forEach(child => {
|
||||
const index = childrenToRender.findIndex(c => c.props.uid === child.props.uid);
|
||||
|
||||
if (index !== -1) {
|
||||
childrenToRender[index] = child;
|
||||
}
|
||||
});
|
||||
|
||||
if (toUnmount.length > 0) {
|
||||
const ids = new Map(timeoutIds);
|
||||
|
||||
|
||||
@@ -6,28 +6,43 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../../../analytics/functions';
|
||||
import { hideSheet } from '../../../../../base/dialog/actions';
|
||||
import { hideSheet, openDialog } from '../../../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../../../base/dialog/components/native/BottomSheet';
|
||||
import Icon from '../../../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge, IconRingGroup } from '../../../../../base/icons/svg';
|
||||
import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../../../base/icons/svg';
|
||||
import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
|
||||
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
import { getBreakoutRoomsConfig } from '../../../../../breakout-rooms/functions';
|
||||
import { IRoom } from '../../../../../breakout-rooms/types';
|
||||
import { isBreakoutRoomRenameAllowed } from '../../../../functions';
|
||||
import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../../types';
|
||||
import styles from '../../../native/styles';
|
||||
|
||||
import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';
|
||||
|
||||
/**
|
||||
* An array with all possible breakout rooms actions.
|
||||
*/
|
||||
const ALL_ACTIONS = [ ACTIONS.JOIN, ACTIONS.REMOVE, ACTIONS.RENAME ];
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The actions that will be displayed.
|
||||
*/
|
||||
actions: Array<ACTIONS>;
|
||||
|
||||
/**
|
||||
* The room for which the menu is open.
|
||||
*/
|
||||
room: IRoom;
|
||||
}
|
||||
|
||||
const BreakoutRoomContextMenu = ({ room }: IProps) => {
|
||||
const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
|
||||
const _isBreakoutRoomRenameAllowed = useSelector(isBreakoutRoomRenameAllowed);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onJoinRoom = useCallback(() => {
|
||||
@@ -41,6 +56,14 @@ const BreakoutRoomContextMenu = ({ room }: IProps) => {
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room.jid,
|
||||
initialRoomName: room.name
|
||||
}));
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onCloseBreakoutRoom = useCallback(() => {
|
||||
dispatch(closeBreakoutRoom(room.id));
|
||||
dispatch(hideSheet());
|
||||
@@ -51,7 +74,7 @@ const BreakoutRoomContextMenu = ({ room }: IProps) => {
|
||||
addScrollViewPadding = { false }
|
||||
showSlidingView = { true }>
|
||||
{
|
||||
!hideJoinRoomButton && (
|
||||
!hideJoinRoomButton && actions.includes(ACTIONS.JOIN) && (
|
||||
<TouchableOpacity
|
||||
onPress = { onJoinRoom }
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
@@ -62,7 +85,17 @@ const BreakoutRoomContextMenu = ({ room }: IProps) => {
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
{!room?.isMainRoom && isLocalModerator
|
||||
{!room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
|
||||
&& <TouchableOpacity
|
||||
onPress = { onRenameBreakoutRoom }
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconEdit } />
|
||||
<Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.rename')}</Text>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
{!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
|
||||
&& (room?.participants && Object.keys(room.participants).length > 0
|
||||
? <TouchableOpacity
|
||||
onPress = { onCloseBreakoutRoom }
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import InputDialog from '../../../../../base/dialog/components/native/InputDialog';
|
||||
import { renameBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
import { IBreakoutRoomNamePromptProps as IProps } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Implements a component to render a breakout room name prompt.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomName }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const onSubmit = useCallback((roomName: string) => {
|
||||
const formattedRoomName = roomName?.trim();
|
||||
|
||||
if (formattedRoomName) {
|
||||
dispatch(renameBreakoutRoom(formattedRoomName, roomName));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [ breakoutRoomJid, dispatch ]);
|
||||
|
||||
return (
|
||||
<InputDialog
|
||||
descriptionKey = 'dialog.renameBreakoutRoomTitle'
|
||||
initialValue = { initialRoomName?.trim() }
|
||||
onSubmit = { onSubmit } />
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Dialog from '../../../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../../../base/ui/components/web/Input';
|
||||
import { renameBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
import { IBreakoutRoomNamePromptProps as IProps } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} for displaying a dialog with an field
|
||||
* for setting a breakout room's name.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomName }: IProps) {
|
||||
const [ roomName, setRoomName ] = useState(initialRoomName?.trim());
|
||||
const { t } = useTranslation();
|
||||
const okDisabled = !roomName;
|
||||
const dispatch = useDispatch();
|
||||
const onBreakoutRoomNameChange = useCallback((newRoomName: string) => {
|
||||
setRoomName(newRoomName?.trim());
|
||||
}, [ setRoomName ]);
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName));
|
||||
}, [ breakoutRoomJid, dispatch, roomName ]);
|
||||
|
||||
return (<Dialog
|
||||
ok = {{
|
||||
disabled: okDisabled,
|
||||
translationKey: 'dialog.Ok'
|
||||
}}
|
||||
onSubmit = { onSubmit }
|
||||
titleKey = 'dialog.renameBreakoutRoomTitle'>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'dialog-bottom-margin'
|
||||
id = 'breakout-rooms-name-input'
|
||||
label = { t('dialog.renameBreakoutRoomLabel') }
|
||||
name = 'breakoutRoomName'
|
||||
onChange = { onBreakoutRoomNameChange }
|
||||
type = 'text'
|
||||
value = { roomName } />
|
||||
</Dialog>);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { openDialog } from '../../../../../base/dialog/actions';
|
||||
import { IconEdit } from '../../../../../base/icons/svg';
|
||||
|
||||
import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';
|
||||
|
||||
const useStyles = makeStyles()(_theme => {
|
||||
return {
|
||||
container: {
|
||||
position: 'absolute',
|
||||
cursor: 'pointer',
|
||||
marginTop: 2,
|
||||
marginLeft: 5
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
interface IProps {
|
||||
breakoutRoomJid: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the rename button component which is displayed only for renaming a breakout room which is joined by the
|
||||
* user.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function RenameButton({ breakoutRoomJid, name }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { classes, cx } = useStyles();
|
||||
const onRename = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid,
|
||||
initialRoomName: name
|
||||
}));
|
||||
}, [ dispatch, breakoutRoomJid, name ]);
|
||||
|
||||
return (
|
||||
<span
|
||||
className = { cx('jitsi-icon jitsi-icon-default', classes.container) }
|
||||
onClick = { onRename }>
|
||||
<IconEdit
|
||||
height = { 16 }
|
||||
key = { 1 }
|
||||
width = { 16 } />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -4,13 +4,18 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../../../analytics/functions';
|
||||
import { IconCloseLarge, IconRingGroup } from '../../../../../base/icons/svg';
|
||||
import { openDialog } from '../../../../../base/dialog/actions';
|
||||
import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../../../base/icons/svg';
|
||||
import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
|
||||
import ContextMenu from '../../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
import { IRoom } from '../../../../../breakout-rooms/types';
|
||||
import { showOverflowDrawer } from '../../../../../toolbox/functions.web';
|
||||
import { isBreakoutRoomRenameAllowed } from '../../../../functions';
|
||||
|
||||
import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';
|
||||
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -50,6 +55,7 @@ export const RoomContextMenu = ({
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const _isBreakoutRoomRenameAllowed = useSelector(isBreakoutRoomRenameAllowed);
|
||||
const _overflowDrawer = useSelector(showOverflowDrawer);
|
||||
|
||||
const onJoinRoom = useCallback(() => {
|
||||
@@ -61,6 +67,13 @@ export const RoomContextMenu = ({
|
||||
dispatch(removeBreakoutRoom(room?.jid ?? ''));
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room?.jid,
|
||||
initialRoomName: room?.name
|
||||
}));
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onCloseBreakoutRoom = useCallback(() => {
|
||||
dispatch(closeBreakoutRoom(room?.id ?? ''));
|
||||
}, [ dispatch, room ]);
|
||||
@@ -74,6 +87,13 @@ export const RoomContextMenu = ({
|
||||
onClick: onJoinRoom,
|
||||
text: t('breakoutRooms.actions.join')
|
||||
} : null,
|
||||
!room?.isMainRoom && _isBreakoutRoomRenameAllowed ? {
|
||||
accessibilityLabel: t('breakoutRooms.actions.rename'),
|
||||
icon: IconEdit,
|
||||
id: `rename-room-${room?.id}`,
|
||||
onClick: onRenameBreakoutRoom,
|
||||
text: t('breakoutRooms.actions.rename')
|
||||
} : null,
|
||||
!room?.isMainRoom && isLocalModerator ? {
|
||||
accessibilityLabel: isRoomEmpty ? t('breakoutRooms.actions.remove') : t('breakoutRooms.actions.close'),
|
||||
icon: IconCloseLarge,
|
||||
|
||||
@@ -4,12 +4,18 @@ import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Avatar from '../../../../../base/avatar/components/Avatar';
|
||||
import {
|
||||
getButtonNotifyMode,
|
||||
getParticipantMenuButtonsWithNotifyClick
|
||||
} from '../../../../../base/config/functions.web';
|
||||
import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
|
||||
import ContextMenu from '../../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { getBreakoutRooms } from '../../../../../breakout-rooms/functions';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../../../toolbox/constants';
|
||||
import { showOverflowDrawer } from '../../../../../toolbox/functions.web';
|
||||
import SendToRoomButton from '../../../../../video-menu/components/web/SendToRoomButton';
|
||||
import { PARTICIPANT_MENU_BUTTONS as BUTTONS } from '../../../../../video-menu/constants';
|
||||
import { AVATAR_SIZE } from '../../../../constants';
|
||||
|
||||
|
||||
@@ -72,11 +78,30 @@ export const RoomParticipantContextMenu = ({
|
||||
const lowerMenu = useCallback(() => onSelect(true), [ onSelect ]);
|
||||
const rooms: Object = useSelector(getBreakoutRooms);
|
||||
const overflowDrawer = useSelector(showOverflowDrawer);
|
||||
const buttonsWithNotifyClick = useSelector(getParticipantMenuButtonsWithNotifyClick);
|
||||
|
||||
const notifyClick = useCallback(
|
||||
(buttonKey: string, participantId?: string) => {
|
||||
const notifyMode = getButtonNotifyMode(buttonKey, buttonsWithNotifyClick);
|
||||
|
||||
if (!notifyMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.API.notifyParticipantMenuButtonClicked(
|
||||
buttonKey,
|
||||
participantId,
|
||||
notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}, [ buttonsWithNotifyClick, getButtonNotifyMode ]);
|
||||
|
||||
const breakoutRoomsButtons = useMemo(() => Object.values(rooms || {}).map((room: any) => {
|
||||
if (room.id !== entity?.room?.id) {
|
||||
return (<SendToRoomButton
|
||||
key = { room.id }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
notifyClick = { () => notifyClick(BUTTONS.SEND_PARTICIPANT_TO_ROOM, entity?.jid) }
|
||||
notifyMode = { getButtonNotifyMode(BUTTONS.SEND_PARTICIPANT_TO_ROOM, buttonsWithNotifyClick) }
|
||||
onClick = { lowerMenu }
|
||||
participantID = { entity?.jid ?? '' }
|
||||
room = { room } />);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FlatList, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { openSheet } from '../../../base/dialog/actions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconAddUser } from '../../../base/icons/svg';
|
||||
@@ -24,9 +25,12 @@ import {
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
import { getInviteOthersControl } from '../../../share-room/functions';
|
||||
import {
|
||||
isCurrentRoomRenamable,
|
||||
participantMatchesSearch,
|
||||
shouldRenderInviteButton
|
||||
} from '../../functions';
|
||||
import { BREAKOUT_CONTEXT_MENU_ACTIONS } from '../../types';
|
||||
import BreakoutRoomContextMenu from '../breakout-rooms/components/native/BreakoutRoomContextMenu';
|
||||
|
||||
import CollapsibleList from './CollapsibleList';
|
||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||
@@ -50,6 +54,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_isAddPeopleFeatureEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the room that is currently joined can be renamed.
|
||||
*/
|
||||
_isCurrentRoomRenamable: boolean;
|
||||
|
||||
/**
|
||||
* The local participant.
|
||||
*/
|
||||
@@ -126,6 +135,7 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._onInvite = this._onInvite.bind(this);
|
||||
this._openContextMenu = this._openContextMenu.bind(this);
|
||||
this._renderParticipant = this._renderParticipant.bind(this);
|
||||
this._onSearchStringChange = this._onSearchStringChange.bind(this);
|
||||
}
|
||||
@@ -185,6 +195,18 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
this.props.setSearchString(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the context menu to rename the current breakout room.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_openContextMenu() {
|
||||
this.props.dispatch(openSheet(BreakoutRoomContextMenu, {
|
||||
room: this.props._currentRoom,
|
||||
actions: [ BREAKOUT_CONTEXT_MENU_ACTIONS.RENAME ]
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -194,6 +216,7 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
render() {
|
||||
const {
|
||||
_currentRoom,
|
||||
_isCurrentRoomRenamable,
|
||||
_inviteOthersControl,
|
||||
_localParticipant,
|
||||
_participantsCount,
|
||||
@@ -229,6 +252,7 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
const _visitorsLabelText = _visitorsCount > 0
|
||||
? t('participantsPane.headings.visitors', { count: _visitorsCount })
|
||||
: undefined;
|
||||
const onLongPress = _isCurrentRoomRenamable ? this._openContextMenu : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -236,7 +260,8 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
}
|
||||
<CollapsibleList
|
||||
containerStyle = { finalContainerStyle }
|
||||
title = { title } >
|
||||
onLongPress = { onLongPress }
|
||||
title = { title }>
|
||||
{
|
||||
_showInviteButton
|
||||
&& <Button
|
||||
@@ -298,6 +323,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_currentRoom,
|
||||
_isAddPeopleFeatureEnabled,
|
||||
_isCurrentRoomRenamable: isCurrentRoomRenamable(state),
|
||||
_inviteOthersControl,
|
||||
_participantsCount,
|
||||
_remoteParticipants,
|
||||
|
||||
@@ -16,8 +16,9 @@ import { normalizeAccents } from '../../../base/util/strings.web';
|
||||
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
import { muteRemote } from '../../../video-menu/actions.web';
|
||||
import { getSortedParticipantIds, shouldRenderInviteButton } from '../../functions';
|
||||
import { getSortedParticipantIds, isCurrentRoomRenamable, shouldRenderInviteButton } from '../../functions';
|
||||
import { useParticipantDrawer } from '../../hooks';
|
||||
import RenameButton from '../breakout-rooms/components/web/RenameButton';
|
||||
|
||||
import { InviteButton } from './InviteButton';
|
||||
import MeetingParticipantContextMenu from './MeetingParticipantContextMenu';
|
||||
@@ -50,7 +51,10 @@ const useStyles = makeStyles()(theme => {
|
||||
});
|
||||
|
||||
interface IProps {
|
||||
currentRoom?: { name: string; };
|
||||
currentRoom?: {
|
||||
jid: string;
|
||||
name: string;
|
||||
};
|
||||
overflowDrawer?: boolean;
|
||||
participantsCount?: number;
|
||||
searchString: string;
|
||||
@@ -102,6 +106,7 @@ function MeetingParticipants({
|
||||
const youText = t('chat.you');
|
||||
const isBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
|
||||
const _isCurrentRoomRenamable = useSelector(isCurrentRoomRenamable);
|
||||
|
||||
const { classes: styles, cx } = useStyles();
|
||||
|
||||
@@ -122,6 +127,10 @@ function MeetingParticipants({
|
||||
{currentRoom?.name
|
||||
? `${currentRoom.name} (${participantsCount})`
|
||||
: t('participantsPane.headings.participantsList', { count: participantsCount })}
|
||||
{ currentRoom?.name && _isCurrentRoomRenamable
|
||||
&& <RenameButton
|
||||
breakoutRoomJid = { currentRoom?.jid }
|
||||
name = { currentRoom?.name } /> }
|
||||
</div>
|
||||
{showInviteButton && <InviteButton />}
|
||||
<Input
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isSupported
|
||||
} from '../av-moderation/functions';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { INVITE_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { normalizeAccents } from '../base/util/strings';
|
||||
import { BREAKOUT_ROOMS_RENAME_FEATURE } from '../breakout-rooms/constants';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import { MEDIA_STATE, QUICK_ACTION_BUTTON, REDUCER_KEY } from './constants';
|
||||
@@ -282,3 +284,28 @@ export const isMuteAllVisible = (state: IReduxState) => {
|
||||
|
||||
return inBreakoutRoom ? false : !hideMuteAllButton && isLocalModerator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if reanming the currently joined breakout room is allowed and false otherwise.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - True if reanming the currently joined breakout room is allowed and false otherwise.
|
||||
*/
|
||||
export function isCurrentRoomRenamable(state: IReduxState) {
|
||||
return isInBreakoutRoom(state) && isBreakoutRoomRenameAllowed(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if renaming a breakout room is allowed and false otherwise.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - True if renaming a breakout room is allowed and false otherwise.
|
||||
*/
|
||||
export function isBreakoutRoomRenameAllowed(state: IReduxState) {
|
||||
const isLocalModerator = isLocalParticipantModerator(state);
|
||||
const conference = getCurrentConference(state);
|
||||
const isRenameBreakoutRoomsSupported
|
||||
= conference?.getBreakoutRooms().isFeatureSupported(BREAKOUT_ROOMS_RENAME_FEATURE);
|
||||
|
||||
return isLocalModerator && isRenameBreakoutRoomsSupported;
|
||||
}
|
||||
|
||||
36
react/features/participants-pane/types.ts
Normal file
36
react/features/participants-pane/types.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link BreakoutRoomNamePrompt}.
|
||||
*/
|
||||
export interface IBreakoutRoomNamePromptProps {
|
||||
|
||||
/**
|
||||
* The jid of the breakout room to rename.
|
||||
*/
|
||||
breakoutRoomJid: string;
|
||||
|
||||
/**
|
||||
* The initial breakout room name.
|
||||
*/
|
||||
initialRoomName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The available actions for breakout rooms context menu.
|
||||
*/
|
||||
export enum BREAKOUT_CONTEXT_MENU_ACTIONS {
|
||||
|
||||
/**
|
||||
* Join breakout room.
|
||||
*/
|
||||
JOIN = 1,
|
||||
|
||||
/**
|
||||
* Rename breakout room.
|
||||
*/
|
||||
RENAME = 2,
|
||||
|
||||
/**
|
||||
* Remove breakout room.
|
||||
*/
|
||||
REMOVE = 3
|
||||
}
|
||||
@@ -19,11 +19,6 @@ export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
|
||||
*/
|
||||
export const SET_SKIP_PREJOIN_RELOAD = 'SET_SKIP_PREJOIN_RELOAD';
|
||||
|
||||
/**
|
||||
* Action type used to set the mandatory stance of the prejoin display name.
|
||||
*/
|
||||
export const SET_PREJOIN_DISPLAY_NAME_REQUIRED = 'SET_PREJOIN_DISPLAY_NAME_REQUIRED';
|
||||
|
||||
/**
|
||||
* Action type to set the country to dial out to.
|
||||
*/
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import {
|
||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Action used to set the stance of the display name.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinDisplayNameRequired() {
|
||||
return {
|
||||
type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user