Compare commits
28 Commits
7808
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f43020f650 | ||
|
|
ba95ea3ac8 | ||
|
|
7dcd9c21bc | ||
|
|
990d21038e | ||
|
|
a4d53f271f | ||
|
|
2a1f472873 | ||
|
|
8679119677 | ||
|
|
b02c072ba7 | ||
|
|
e8317fccfe | ||
|
|
bbc9c64978 | ||
|
|
8d2b8bc772 | ||
|
|
52c2911350 | ||
|
|
a1ebcd559b | ||
|
|
5bc47ec16a | ||
|
|
e1ac7d1609 | ||
|
|
8a596f1ba2 | ||
|
|
38e7c65836 | ||
|
|
9602a939d8 | ||
|
|
07b01b1371 | ||
|
|
a1549086aa | ||
|
|
7e0b00ba5f | ||
|
|
3016853d81 | ||
|
|
ead27ace30 | ||
|
|
067bb653e6 | ||
|
|
ba20fc71a8 | ||
|
|
a7b2726ebe | ||
|
|
a98eef7eb3 | ||
|
|
895afbab65 |
@@ -19,9 +19,9 @@ buildscript {
|
||||
ext {
|
||||
kotlinVersion = "1.7.0"
|
||||
buildToolsVersion = "33.0.2"
|
||||
compileSdkVersion = 34
|
||||
compileSdkVersion = 33
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 34
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -23,8 +24,9 @@ class JitsiMeetMediaProjectionModule
|
||||
@ReactMethod
|
||||
public void launch() {
|
||||
Context context = getReactApplicationContext();
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
|
||||
JitsiMeetMediaProjectionService.launch(context);
|
||||
JitsiMeetMediaProjectionService.launch(context, currentActivity);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
@@ -28,6 +29,7 @@ import android.os.IBinder;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class implements an Android {@link Service}, a foreground one specifically, and it's
|
||||
@@ -39,8 +41,11 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
public class JitsiMeetMediaProjectionService extends Service {
|
||||
private static final String TAG = JitsiMeetMediaProjectionService.class.getSimpleName();
|
||||
|
||||
public static void launch(Context context) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
|
||||
public static void launch(Context context, Activity currentActivity) {
|
||||
|
||||
NotificationUtils.createNotificationChannel(currentActivity);
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
|
||||
|
||||
@@ -55,12 +60,12 @@ public class JitsiMeetMediaProjectionService extends Service {
|
||||
} catch (RuntimeException e) {
|
||||
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
|
||||
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
|
||||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
|
||||
JitsiMeetLogger.w(TAG + "Media projection service not started", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (componentName == null) {
|
||||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
|
||||
JitsiMeetLogger.w(TAG + "Media projection service not started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,24 +74,6 @@ public class JitsiMeetMediaProjectionService extends Service {
|
||||
context.stopService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(null);
|
||||
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
@@ -95,6 +82,21 @@ public class JitsiMeetMediaProjectionService extends Service {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
Notification notification = MediaProjectionNotification.buildMediaProjectionNotification(this);
|
||||
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
@@ -33,6 +34,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class implements an Android {@link Service}, a foreground one specifically, and it's
|
||||
@@ -52,8 +54,12 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
|
||||
private boolean isAudioMuted;
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
|
||||
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
|
||||
NotificationUtils.createNotificationChannel((Activity) context);
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
|
||||
@@ -90,15 +96,15 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,13 +136,13 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
if (isAudioMuted != null) {
|
||||
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,13 +222,13 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
|
||||
JitsiMeetLogger.i(TAG + " audio muted changed");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import static org.jitsi.meet.sdk.NotificationUtils.ONGOING_CONFERENCE_CHANNEL_ID;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Helper class for creating the media projection notification which is used with
|
||||
* {@link JitsiMeetMediaProjectionService}.
|
||||
*/
|
||||
class MediaProjectionNotification {
|
||||
private static final String TAG = MediaProjectionNotification.class.getSimpleName();
|
||||
|
||||
static Notification buildMediaProjectionNotification(Context context) {
|
||||
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.d(TAG, " Cannot create notification: no current context");
|
||||
return null;
|
||||
}
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
builder
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setContentTitle(context.getString(R.string.media_projection_notification_title))
|
||||
.setContentText(context.getString(R.string.media_projection_notification_text))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(false)
|
||||
.setUsesChronometer(false)
|
||||
.setAutoCancel(true)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NotificationChannels {
|
||||
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
|
||||
|
||||
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class NotificationUtils {
|
||||
|
||||
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
|
||||
|
||||
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
|
||||
|
||||
private static final String TAG = NotificationUtils.class.getSimpleName();
|
||||
|
||||
static void createNotificationChannel(Activity context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager
|
||||
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel
|
||||
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
if (channel != null) {
|
||||
// The channel was already created, no need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setShowBadge(false);
|
||||
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
@@ -16,23 +16,18 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
|
||||
import static org.jitsi.meet.sdk.NotificationUtils.ONGOING_CONFERENCE_CHANNEL_ID;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Helper class for creating the ongoing notification which is used with
|
||||
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
|
||||
@@ -41,40 +36,10 @@ import java.util.Random;
|
||||
class OngoingNotification {
|
||||
private static final String TAG = OngoingNotification.class.getSimpleName();
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
private static long startingTime = 0;
|
||||
|
||||
static void createOngoingConferenceNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context) {
|
||||
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager
|
||||
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel
|
||||
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
if (channel != null) {
|
||||
// The channel was already created, no need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setShowBadge(false);
|
||||
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
static Notification buildOngoingConferenceNotification(Boolean isMuted) {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
|
||||
return null;
|
||||
@@ -92,7 +57,7 @@ class OngoingNotification {
|
||||
builder
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setContentTitle(context.getString(R.string.ongoing_notification_title))
|
||||
.setContentText(isMuted != null ? context.getString(R.string.ongoing_notification_text) : context.getString(R.string.ongoing_notification_action_screenshare))
|
||||
.setContentText(context.getString(R.string.ongoing_notification_text))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
@@ -103,10 +68,6 @@ class OngoingNotification {
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
|
||||
|
||||
if (isMuted == null) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
|
||||
|
||||
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<resources>
|
||||
<string name="app_name">Jitsi Meet SDK</string>
|
||||
<string name="dropbox_app_key"></string>
|
||||
<string name="media_projection_notification_title">Media projection</string>
|
||||
<string name="media_projection_notification_text">You are currently sharing your screen.</string>
|
||||
<string name="ongoing_notification_title">Ongoing meeting</string>
|
||||
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
|
||||
<string name="ongoing_notification_action_hang_up">Hang up</string>
|
||||
<string name="ongoing_notification_action_mute">Mute</string>
|
||||
<string name="ongoing_notification_action_screenshare">You are currently screen-sharing. Tap to return to the meeting.</string>
|
||||
<string name="ongoing_notification_action_unmute">Unmute</string>
|
||||
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
|
||||
</resources>
|
||||
|
||||
@@ -91,7 +91,7 @@ import {
|
||||
setVideoMuted,
|
||||
setVideoUnmutePermissions
|
||||
} from './react/features/base/media/actions';
|
||||
import { MEDIA_TYPE } from './react/features/base/media/constants';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from './react/features/base/media/constants';
|
||||
import {
|
||||
getStartWithAudioMuted,
|
||||
getStartWithVideoMuted,
|
||||
@@ -405,7 +405,13 @@ function disconnect() {
|
||||
* @returns {void}
|
||||
*/
|
||||
function setGUMPendingStateOnFailedTracks(tracks) {
|
||||
const tracksTypes = tracks.map(track => track.getType());
|
||||
const tracksTypes = tracks.map(track => {
|
||||
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
return MEDIA_TYPE.SCREENSHARE;
|
||||
}
|
||||
|
||||
return track.getType();
|
||||
});
|
||||
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
|
||||
|
||||
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
|
||||
@@ -679,6 +685,8 @@ export default {
|
||||
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
|
||||
};
|
||||
|
||||
logger.debug(`Executed conference.init with roomName: ${roomName}`);
|
||||
|
||||
this.roomName = roomName;
|
||||
|
||||
try {
|
||||
@@ -1253,7 +1261,24 @@ export default {
|
||||
room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
|
||||
|
||||
// Filter out the tracks that are muted (except on Safari).
|
||||
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
|
||||
let tracks = localTracks;
|
||||
|
||||
if (!browser.isWebKitBased()) {
|
||||
const mutedTrackTypes = [];
|
||||
|
||||
tracks = localTracks.filter(track => {
|
||||
if (!track.isMuted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (track.getVideoType() !== VIDEO_TYPE.DESKTOP) {
|
||||
mutedTrackTypes.push(track.getType());
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
APP.store.dispatch(gumPending(mutedTrackTypes, IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
this._setLocalAudioVideoStreams(tracks);
|
||||
this._room = room; // FIXME do not use this
|
||||
|
||||
@@ -723,7 +723,7 @@ var config = {
|
||||
// Configs for prejoin page.
|
||||
// prejoinConfig: {
|
||||
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
|
||||
// // This replaces `prejoinPageEnabled`.
|
||||
// // This replaces `prejoinPageEnabled`. Defaults to true.
|
||||
// enabled: true,
|
||||
// // Hides the participant name editing field in the prejoin screen.
|
||||
// // If requireDisplayName is also set as true, a name should still be provided through
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 48px;
|
||||
max-width: calc(100% - 24px);
|
||||
}
|
||||
|
||||
@keyframes hideSubject {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"accessibilityLabel": {
|
||||
"meetingLink": "Lidhje takimi: {{url}}"
|
||||
},
|
||||
"add": "Ftoni",
|
||||
"addContacts": "Ftoni kontaktet tuaja",
|
||||
"contacts": "kontakte",
|
||||
@@ -39,6 +42,18 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Gjerësi e ulët bande"
|
||||
},
|
||||
"bandwidthSettings": {
|
||||
"assumedBandwidthBps": "p.sh. 10000000 për 10 Mbps",
|
||||
"assumedBandwidthBpsWarning": "Vlera më të mëdha mund të shkaktojnë probleme rrjeti.",
|
||||
"customValue": "vlerë vetjake",
|
||||
"customValueEffect": "për të ujdisur vlerën e tanishme bps",
|
||||
"leaveEmpty": "lëreni të zbrazët",
|
||||
"leaveEmptyEffect": "për të lejuar kryerje vlerësimesh",
|
||||
"possibleValues": "Vlera të mundshme",
|
||||
"setAssumedBandwidthBps": "Gjerësi bande e supozuar (bps)",
|
||||
"title": "Rregullime gjerësi bande",
|
||||
"zeroEffect": "për të çaktivizuar videon"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "Shtoni aneks konsultimesh",
|
||||
@@ -48,15 +63,22 @@
|
||||
"leaveBreakoutRoom": "Dilni nga aneks konsultimesh",
|
||||
"more": "Më tepër",
|
||||
"remove": "Hiqe",
|
||||
"rename": "Riemërtojeni",
|
||||
"renameBreakoutRoom": "Riemërtoni aneks konsultimesh",
|
||||
"sendToBreakoutRoom": "Dërgoje pjesëmarrësin te:"
|
||||
},
|
||||
"breakoutList": "Listë aneksesh",
|
||||
"buttonLabel": "Dhoma aneks konsultimesh",
|
||||
"defaultName": "Dhomë aneks konsultimesh #{{index}}",
|
||||
"hideParticipantList": "Fshihe listën e pjesëmarrësve",
|
||||
"mainRoom": "Dhoma kryesore",
|
||||
"notifications": {
|
||||
"joined": "Po hyhet te dhomë aneks konsultimesh \"{{name}}\"",
|
||||
"joinedMainRoom": "Po hyhet te dhoma kryesore",
|
||||
"joinedTitle": "Dhoma Aneks Konsultimesh"
|
||||
}
|
||||
},
|
||||
"showParticipantList": "Shfaq listë pjesëmarrësish",
|
||||
"title": "Dhoma Aneks Konsultimesh"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Shtoni një lidhje takimi",
|
||||
@@ -68,9 +90,9 @@
|
||||
},
|
||||
"join": "Merrni pjesë",
|
||||
"joinTooltip": "Merrni pjesë në takim",
|
||||
"nextMeeting": "takimi pasues",
|
||||
"nextMeeting": "Takimi pasues",
|
||||
"noEvents": "S’ka veprimtari të ardhshme të vëna në plan.",
|
||||
"ongoingMeeting": "takim në zhvillim e sipër",
|
||||
"ongoingMeeting": "Takim në zhvillim e sipër",
|
||||
"permissionButton": "Hapni rregullimet",
|
||||
"permissionMessage": "Që të shihni në aplikacion takimet tuaja, janë të domosdoshme lejet mbi Kalendarin.",
|
||||
"refresh": "Rifresko kalendarin",
|
||||
@@ -156,6 +178,7 @@
|
||||
"localport_plural": "Porta vendore:",
|
||||
"maxEnabledResolution": "maksimum dërgimi",
|
||||
"more": "Shfaq më tepër",
|
||||
"no": "jo",
|
||||
"packetloss": "Humbje paketesh:",
|
||||
"participant_id": "ID pjesëmarrësi:",
|
||||
"quality": {
|
||||
@@ -174,7 +197,8 @@
|
||||
"status": "Lidhje:",
|
||||
"transport": "Transport:",
|
||||
"transport_plural": "Transporte:",
|
||||
"video_ssrc": "SSRC Videoje:"
|
||||
"video_ssrc": "Video SSRC:",
|
||||
"yes": "po"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Më herët",
|
||||
@@ -195,7 +219,9 @@
|
||||
"joinInBrowser": "Hyni që nga shfletues",
|
||||
"launchMeetingLabel": "Si doni të hyhet në këtë takim?",
|
||||
"launchWebButton": "Nise në web",
|
||||
"noDesktopApp": "S’e keni aplikacionin?",
|
||||
"noMobileApp": "S’e keni aplikacionin?",
|
||||
"or": "OR",
|
||||
"termsAndConditions": "Duke vazhduar, pajtoheni me <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a> tona",
|
||||
"title": "Po niset takimi juaj në {{app}}…",
|
||||
"titleNew": "Po niset takimi juaj…",
|
||||
@@ -220,7 +246,7 @@
|
||||
"noPermission": "S’u akordua leje",
|
||||
"previewUnavailable": "S’bëhet dot paraparje",
|
||||
"selectADevice": "Përzgjidhni një pajisje",
|
||||
"testAudio": "Luaj një tingull, për provë"
|
||||
"testAudio": "Testojeni"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "Përmbledhje rënieje numrash"
|
||||
@@ -231,21 +257,28 @@
|
||||
"dialog": {
|
||||
"Back": "Mbrapsht",
|
||||
"Cancel": "Anuloje",
|
||||
"IamHost": "Jam organizatori",
|
||||
"IamHost": "Hyni",
|
||||
"Ok": "OK",
|
||||
"Remove": "Hiqe",
|
||||
"Share": "Ndaje",
|
||||
"Submit": "Parashtroje",
|
||||
"WaitForHostMsg": "Konferenca s’ka nisur ende. Nëse jeni organizatori, ju lutemi, bëni mirëfilltësimin. Përndryshe, ju lutemi, pritni që të mbërrijë organizatori.",
|
||||
"WaitingForHostTitle": "Po pritet për organizatorin…",
|
||||
"WaitForHostMsg": "Konferenca s’ka nisur, ngaqë s’ka mbërritur ende ndonjë moderator. Nëse dëshironi të bëheni një moderator, ju lutemi, bëni hyrjen. Përndryshe, ju lutemi, pritni.",
|
||||
"WaitingForHostButton": "Prit për moderator",
|
||||
"WaitingForHostTitle": "Po pritet për një moderator…",
|
||||
"Yes": "Po",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Transmetim i Drejtpërdrejtë"
|
||||
"Cancel": "Anuloje (dilni nga dialogu)",
|
||||
"Ok": "OK (ruajeni dhe dilni nga dialogu)",
|
||||
"close": "Mbylle dialogun",
|
||||
"liveStreaming": "Transmetim i Drejtpërdrejtë",
|
||||
"sharingTabs": "Mundësi ndarjeje me të tjerë"
|
||||
},
|
||||
"add": "Shtoni",
|
||||
"addMeetingNote": "Shtoni një shënim rreth këtij takimi",
|
||||
"addOptionalNote": "Shtoni një shënim (në daçi):",
|
||||
"allow": "Lejoje",
|
||||
"allowToggleCameraDialog": "A e lejoni {{initiatorName}} të ndryshojë anën nga sheh kamera juaj?",
|
||||
"allowToggleCameraTitle": "Të lejohet ndryshimi i kamerës?",
|
||||
"alreadySharedVideoMsg": "Një tjetër pjesëmarrës po ndan me të tjerët një video. Kjo konferencë lejon vetëm një ndarje videoje në herë.",
|
||||
"alreadySharedVideoTitle": "Lejohet vetëm një ndarje videoje me të tjerët në herë",
|
||||
"applicationWindow": "Dritare aplikacioni",
|
||||
@@ -306,7 +339,8 @@
|
||||
"lockRoom": "Shtoni takim $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Kyçja dështoi",
|
||||
"login": "Hyrje",
|
||||
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të ndalet konferenca?",
|
||||
"loginQuestion": "Jeni i sigurt se doni të hyhet dhe të braktiset konferenca?",
|
||||
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të braktiset konferenca?",
|
||||
"logoutTitle": "Dalje",
|
||||
"maxUsersLimitReached": "U mbërrit në kufi numri maksimum pjesëmarrësish. Konferenca është plot. Ju lutemi, lidhuni me të zotin e takimit, ose provoni më vonë!",
|
||||
"maxUsersLimitReachedTitle": "U mbërrit në kufi numri maksimum pjesëmarrësish",
|
||||
@@ -334,7 +368,7 @@
|
||||
"muteEveryonesVideoTitle": "Të ndalet videoja e gjithkujt?",
|
||||
"muteParticipantBody": "S’do jeni në gjendje të hiqni heshtimin për ta, por ata munden kurdo ta heqin për veten.",
|
||||
"muteParticipantButton": "Heshtoje",
|
||||
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktvizojnë për veten.",
|
||||
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktivizojnë për veten.",
|
||||
"muteParticipantsVideoBodyModerationOn": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre dhe as ata s’do të munden.",
|
||||
"muteParticipantsVideoButton": "Ndale videon",
|
||||
"muteParticipantsVideoDialog": "Jeni i sigurt se doni të çaktivizoni kamerën e këtij pjesëmarrësi? S’do të jeni në gjendje të riaktivizoni kamerën, por ai mund ta riaktivizojë kurdo.",
|
||||
@@ -349,8 +383,6 @@
|
||||
"permissionCameraRequiredError": "Leja mbi kamerën është e domosdoshme për të marrë pjesë në konferenca me video. Ju lutemi, akordojeni që nga Rregullimet",
|
||||
"permissionErrorTitle": "Leje e domosdoshme",
|
||||
"permissionMicRequiredError": "Leja mbi mikrofonin është e domosdoshme për të marrë pjesë në konferenca me audio. Ju lutemi, akordojeni që nga Rregullimet",
|
||||
"popupError": "Shfletuesi juaj i bllokon dritaret flluskë prej këtij sajti. Ju lutemi, aktivizoni flluskat te rregullimet e sigurisë të shfletuesit tuaj dhe riprovoni.",
|
||||
"popupErrorTitle": "Flluska u bllokua",
|
||||
"readMore": "më tepër",
|
||||
"recentlyUsedObjects": "Së fundi përdorët objekte",
|
||||
"recording": "Regjistrim",
|
||||
@@ -367,6 +399,8 @@
|
||||
"removePassword": "Hiqe $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": "Jeni i sigurt se doni të hiqni videon që keni ndarë me të tjerë?",
|
||||
"removeSharedVideoTitle": "Hiqe videon e ndarë me të tjerë",
|
||||
"renameBreakoutRoomLabel": "Emër dhome",
|
||||
"renameBreakoutRoomTitle": "Riemërtoni aneks konsultimesh",
|
||||
"reservationError": "Gabim sistemi rezervimi",
|
||||
"reservationErrorMsg": "Kod gabimi: {{code}}, mesazh: {{msg}}",
|
||||
"retry": "Riprovoni",
|
||||
@@ -386,8 +420,10 @@
|
||||
"sendPrivateMessageTitle": "Të dërgohet privatisht?",
|
||||
"serviceUnavailable": "Shërbim jashtë funksionimi",
|
||||
"sessTerminated": "Thirrja përfundoi",
|
||||
"sessTerminatedReason": "Takimi u përfundua",
|
||||
"sessionRestarted": "Thirrja rinisi për shkak të një problemi lidhjeje.",
|
||||
"shareAudio": "Vazhdo",
|
||||
"shareAudio": "Vazhdoni",
|
||||
"shareAudioAltText": "që të ndani me të tjerë lëndën e dëshiruar, kaloni te \"Skedë Shfletuesi\", përzgjidhni lëndën, aktivizoni shenjën për \"ndani audio me të tjerë\" dhe mandej klikoni butonin \"ndaje me të tjerë\"",
|
||||
"shareAudioTitle": "Si të ndahet audio me të tjerë",
|
||||
"shareAudioWarningD1": "lypset të ndalni tregim ekrani, para se të ndani audion tuaj me të tjerë.",
|
||||
"shareAudioWarningD2": "lypset të rinisni tregimin e ekranit tuaj dhe t’i vini shenjë mundësisë “ndani audio me të tjerë”.",
|
||||
@@ -417,7 +453,25 @@
|
||||
"thankYou": "Faleminderit që përdorni {{appName}}!",
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Na ndjeni, s’keni leje të merrni pjesë në këtë thirrje.",
|
||||
"tokenAuthFailedReason": {
|
||||
"audInvalid": "Vlerë `aud` e pavlefshme. Duhet të jetë `jitsi`.",
|
||||
"contextNotFound": "Prej ngarkesës mungon objekti `context`.",
|
||||
"expInvalid": "Vlerë `exp` e pavlefshme.",
|
||||
"featureInvalid": "Veçori e pavlefshme: {{feature}}, gjasat janë të mos jetë sendërtuar ende.",
|
||||
"featureValueInvalid": "Vlerë e pavlefshme për veçorinë: {{feature}}.",
|
||||
"featuresNotFound": "Prej ngarkesës mungon objekti `features`.",
|
||||
"headerNotFound": "Mungon krye.",
|
||||
"issInvalid": "Vlerë `iss` e pavlefshme. Duhet të jetë `chat`.",
|
||||
"kidMismatch": "ID kyçi (kid) nuk përputhet me sub.",
|
||||
"kidNotFound": "Mungon ID Kyçi (kid).",
|
||||
"nbfFuture": "Vlera `nbf` gjendet në të ardhmen.",
|
||||
"nbfInvalid": "Vlerë `nbf` e pavlefshme.",
|
||||
"payloadNotFound": "Mungon ngarkesë.",
|
||||
"tokenExpired": "Token-i ka skaduar."
|
||||
},
|
||||
"tokenAuthFailedTitle": "Mirëfilltësimi dështoi",
|
||||
"tokenAuthFailedWithReasons": "Na ndjeni, nuk keni leje të merrni pjesë në këtë thirrje. Arsye e mundshme: {{reason}}",
|
||||
"tokenAuthUnsupported": "Nuk mbulohet URL token-i.",
|
||||
"transcribing": "Transkriptim",
|
||||
"unlockRoom": "Hiq $t(lockRoomPassword) takimi",
|
||||
"user": "Përdorues",
|
||||
@@ -429,8 +483,12 @@
|
||||
"verifyParticipantTitle": "Verifikim përdoruesi",
|
||||
"videoLink": "Lidhje videoje",
|
||||
"viewUpgradeOptions": "Shihni mundësi përmirësimi",
|
||||
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transcriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
|
||||
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transkriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
|
||||
"viewUpgradeOptionsTitle": "Zbuluat një veçori me pagesë!",
|
||||
"whiteboardLimitContent": "Na ndjeni, është mbërritur te kufiri i tabelave të njëkohshme.",
|
||||
"whiteboardLimitReference": "Për më tepër hollësi, ju lutemi, vizitoni",
|
||||
"whiteboardLimitReferenceUrl": "sajtin tonë",
|
||||
"whiteboardLimitTitle": "Kufizim përdorimi tabele",
|
||||
"yourEntireScreen": "Krejt ekranin tuaj"
|
||||
},
|
||||
"documentSharing": {
|
||||
@@ -443,6 +501,9 @@
|
||||
"title": "Trupëzojeni këtë takim"
|
||||
},
|
||||
"feedback": {
|
||||
"accessibilityLabel": {
|
||||
"yourChoice": "Zgjedhja juaj: {{rating}}"
|
||||
},
|
||||
"average": "Çka",
|
||||
"bad": "I dobët",
|
||||
"detailsLabel": "Na thoni më tepër rreth tij.",
|
||||
@@ -499,13 +560,16 @@
|
||||
"noNumbers": "S’ka numra për përdorim.",
|
||||
"noPassword": "Asnjë",
|
||||
"noRoom": "S’u dha dhomë për të cilën të formësohet numri.",
|
||||
"noWhiteboard": "S’u ngarkua dot tabela.",
|
||||
"numbers": "Numra Për T’u Përdorur",
|
||||
"password": "$t(lockRoomPasswordUppercase): ",
|
||||
"reachedLimit": "Keni mbërritur në kufijtë e planit tuaj.",
|
||||
"sip": "Adresë SIP",
|
||||
"sipAudioOnly": "Adresë SIP vetëm për audio",
|
||||
"title": "Ndani me të tjerë",
|
||||
"tooltip": "Ndani me të tjerë lidhje dhe hollësi numrash për këtë takim",
|
||||
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te"
|
||||
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te",
|
||||
"whiteboardError": "Gabim në ngarkimin e tabelës. Ju lutemi, riprovoni më vonë."
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Ngecëm pak.",
|
||||
@@ -607,13 +671,13 @@
|
||||
"knockingParticipantList": "Listë pjesëmarrësish që duan të hyjnë",
|
||||
"lobbyChatStartedNotification": "{{moderator}} filloi një fjalosje në holl me {{attendee}}",
|
||||
"lobbyChatStartedTitle": "{{moderator}} ka filluar një fjalosje në holl me ju.",
|
||||
"lobbyClosed": "Dhoma holl u mbyll.",
|
||||
"nameField": "Jepni emrin tuaj",
|
||||
"notificationLobbyAccessDenied": "Hyrja e {{targetParticipantName}} është hedhur poshtë nga {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} është lejuar të hyjë nga {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Holli është çaktivizuar nga {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Holli është aktivizuar nga {{originParticipantName}}",
|
||||
"notificationTitle": "Holl",
|
||||
"passwordField": "Jepni fjalëkalim takimi",
|
||||
"passwordJoinButton": "Hyni",
|
||||
"title": "Holl",
|
||||
"toggleLabel": "Aktivizoni hollin"
|
||||
@@ -646,6 +710,8 @@
|
||||
"sessionToken": "Token Sesioni",
|
||||
"start": "Nis Regjistrim",
|
||||
"stop": "Ndale Regjistrimin",
|
||||
"stopping": "Po ndalet Regjistrimi",
|
||||
"wait": "Ju lutemi, prisni, teksa ruajmë regjistrimin tuaj",
|
||||
"yes": "Po"
|
||||
},
|
||||
"lockRoomPassword": "fjalëkalim",
|
||||
@@ -667,8 +733,11 @@
|
||||
"connectedTwoMembers": "{{first}} dhe {{second}} tjetër hynë në takim",
|
||||
"dataChannelClosed": "Rënie cilësie videoje",
|
||||
"dataChannelClosedDescription": "Kanali urë u shkëput, kështu që cilësia e videos është kufizuar te vlera më e ulët.",
|
||||
"disabledIframe": "Trupëzimi është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash.",
|
||||
"disabledIframeSecondary": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash. Ju lutemi, për trupëzime të njëmendta përdorni <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a>!",
|
||||
"disconnected": "u shkëput",
|
||||
"displayNotifications": "Shfaq njoftime për",
|
||||
"dontRemindMe": "Mos ma kujto",
|
||||
"focus": "Fokusi te konferenca",
|
||||
"focusFail": "{{component}} jo i passhëm - riprovoni pas {{ms}} sekondash",
|
||||
"gifsMenu": "GIPHY",
|
||||
@@ -677,6 +746,7 @@
|
||||
"invitedOneMember": "{{name}} u ftua",
|
||||
"invitedThreePlusMembers": "{{name}} dhe {{count}} të tjerë u ftuan",
|
||||
"invitedTwoMembers": "{{first}} dhe {{second}} u ftuan",
|
||||
"joinMeeting": "Hyni",
|
||||
"kickParticipant": "{{kicked}} u përzu nga {{kicker}}",
|
||||
"leftOneMember": "{{name}} doli nga takimi",
|
||||
"leftThreePlusMembers": "{{name}} dhe mjaft të tjerë dolën nga takimi",
|
||||
@@ -711,7 +781,6 @@
|
||||
"newDeviceCameraTitle": "U pikas kamerë e re",
|
||||
"noiseSuppressionDesktopAudioDescription": "Mbytja e zhurmave s’mund të aktivizohet teksa ndahet me të tjerët audioja e desktopit, ju lutemi, çaktivizojeni dhe riprovoni.",
|
||||
"noiseSuppressionFailedTitle": "S’u arrit të nisej mbytja e zhurmave",
|
||||
"noiseSuppressionNoTrackDescription": "Ju lutemi, së pari, çheshtoni mikrofonin tuaj.",
|
||||
"noiseSuppressionStereoDescription": "Aktualisht nuk mbulohet mbytje zhurmash audioje stereo.",
|
||||
"oldElectronClientDescription1": "Duket se përdorni një version të vjetër të klientit Jitsi Meet, i cili ka cenueshmëri të ditura sigurie. Ju lutemi, siguroni përditësimin me ",
|
||||
"oldElectronClientDescription2": "montimin tonë më të ri",
|
||||
@@ -735,13 +804,18 @@
|
||||
"startSilentTitle": "Hytë pa zë në dalje!",
|
||||
"suboptimalBrowserWarning": "Kemi frikë se funksionimi i takimit për ju s’do të jetë kushedi këtu. Po kërkojmë rrugë për ta përmirësuar këtë punë, por deri atëherë, ju lutemi, provoni të përdorni një nga <a href='{{recommendedBrowserPageLink}}' target='_blank'>shfletuesit e mbuluar plotësisht</a>.",
|
||||
"suboptimalExperienceTitle": "Sinjalizim Mbi Shfletuesin",
|
||||
"suggestRecordingAction": "Niseni",
|
||||
"suggestRecordingDescription": "Do të donit të nisej një regjistrim?",
|
||||
"suggestRecordingTitle": "Regjistroje këtë takim",
|
||||
"unmute": "Çheshtoje",
|
||||
"videoMutedRemotelyDescription": "Mundeni përherë ta rihapni.",
|
||||
"videoMutedRemotelyTitle": "Videoja juaj u mbyll nga {{moderator}}",
|
||||
"videoUnmuteBlockedDescription": "Heqja e heshtimit të kamerës dhe veprimi i tregimit të desktopit janë bllokuar përkohësisht për shkak kufizimesh të sistemit.",
|
||||
"videoUnmuteBlockedTitle": "Heqja e heshtimit të kamerës dhe tregimi i desktopit janë bllokuar!",
|
||||
"viewLobby": "Shihni hollin",
|
||||
"waitingParticipants": "{{waitingParticipants}} vetë"
|
||||
"waitingParticipants": "{{waitingParticipants}} vetë",
|
||||
"whiteboardLimitDescription": "Ju lutemi, ruani ç’keni bërë, ngaqë së shpejti do të mbërrihet në kufi përdoruesi dhe tabela do të mbyllet.",
|
||||
"whiteboardLimitTitle": "Përdorim tabele"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
@@ -752,6 +826,7 @@
|
||||
"askUnmute": "Kërkoni heqje heshtimi",
|
||||
"audioModeration": "Heqin heshtim të vetes",
|
||||
"blockEveryoneMicCamera": "Bllokoni mikrofonin dhe kamerën e gjithkujt",
|
||||
"breakoutRooms": "Dhoma anekse konsultimesh",
|
||||
"invite": "Ftoni Dikë",
|
||||
"moreModerationActions": "Më tepër mundësi moderimi",
|
||||
"moreModerationControls": "Më tepër kontrolle moderimi",
|
||||
@@ -769,7 +844,8 @@
|
||||
"headings": {
|
||||
"lobby": "Holli ({{count}})",
|
||||
"participantsList": "Pjesëmarrës në takim ({{count}})",
|
||||
"visitors": "Vizitorë ({{count}})",
|
||||
"visitorRequests": " (requests {{count}})",
|
||||
"visitors": "Vizitorë {{count}}",
|
||||
"waitingLobby": "Duke pritur në holl ({{count}})"
|
||||
},
|
||||
"search": "Kërkoni te pjesëmarrësit",
|
||||
@@ -859,12 +935,15 @@
|
||||
"joinWithoutAudio": "Merrni pjesë pa audio",
|
||||
"keyboardShortcuts": "Aktivizo shkurtore tastiere",
|
||||
"linkCopied": "Lidhja u kopjua në të papastër",
|
||||
"lookGood": "Mikrofoni juaj po punon si duhet",
|
||||
"lookGood": "Gjithçka po punon si duhet",
|
||||
"or": "ose",
|
||||
"premeeting": "Para takimit",
|
||||
"proceedAnyway": "Vazhdo, sido qoftë",
|
||||
"recordingWarning": "Këtë thirrje pjesëmarrës të tjerë mund ta regjistrojnë",
|
||||
"screenSharingError": "Gabim ndarjeje ekrani me të tjerë:",
|
||||
"showScreen": "Aktivizoni skenë para takimit",
|
||||
"startWithPhone": "Nise me audio telefoni",
|
||||
"unsafeRoomConsent": "I kuptoj rreziqet, dëshiroj të marr pjesë te takimi",
|
||||
"videoOnlyError": "Gabim video:",
|
||||
"videoTrackError": "S’u krijua dot pistë video.",
|
||||
"viewAllNumbers": "shihni krejt numrat"
|
||||
@@ -885,9 +964,9 @@
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "avatar",
|
||||
"setDisplayNameLabel": "Caktoni emrin tuaj për në ekran",
|
||||
"setDisplayNameLabel": "Emër",
|
||||
"setEmailInput": "Jepni email",
|
||||
"setEmailLabel": "Caktoni email të gravatarit tuaj",
|
||||
"setEmailLabel": "Jepni email të gravatarit tuaj",
|
||||
"title": "Profil"
|
||||
},
|
||||
"raisedHand": "Do të donte të fliste",
|
||||
@@ -919,17 +998,16 @@
|
||||
"failedToStart": "S’u arrit të niset regjistrimi",
|
||||
"fileSharingdescription": "Ndajeni regjistrimin me pjesëmarrësit në takim",
|
||||
"highlight": "Nxjerrje në pah",
|
||||
"highlightMoment": "Nxirni në pah një çast",
|
||||
"highlightMoment": "Nxirrni në pah një çast",
|
||||
"highlightMomentDisabled": "Mund të nxirrni në pah çaste kur fillon regjistrimi",
|
||||
"highlightMomentSuccess": "Çasti u nxorr në pah",
|
||||
"highlightMomentSuccess": "Çasti u nxor në pah",
|
||||
"highlightMomentSucessDescription": "Çasti i nxjerrë në pah nga ju do të shtohet te përmbledhja e takimit.",
|
||||
"inProgress": "Regjistrim ose transmetim drejtpërsëdrejti në ecuri e sipër",
|
||||
"limitNotificationDescriptionNative": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "Kemi prodhuar një lidhje për te regjistrimi juaj.",
|
||||
"live": "DREJTPËRSËDREJTI",
|
||||
"localRecordingNoNotificationWarning": "Regjistrimi s’do t’u njoftohet pjesëmarrësve të tjerë. Do t’ju duhet t’i vini në dijeni se takimi po regjistrohet.",
|
||||
"localRecordingNoVideo": "Videoja nuk po regjistrohet",
|
||||
"localRecordingNoVideo": "Videoja s’po regjistrohet",
|
||||
"localRecordingStartWarning": "Ju lutemi, sigurohuni se e ndalni regjistrimin para se dilni nga takimi, që të mund ta ruani.",
|
||||
"localRecordingStartWarningTitle": "Ndaleni regjistrimin që ta ruani",
|
||||
"localRecordingVideoStop": "Ndalja e videos tuaj do të ndalë gjithashtu edhe regjistrimin vendor. Jeni i sigurt se doni të vazhdohet?",
|
||||
@@ -944,13 +1022,15 @@
|
||||
"onBy": "{{name}} nisi regjistrimin",
|
||||
"onlyRecordSelf": "Regjistro vetëm rrjedhat e mia audio dhe video",
|
||||
"pending": "Po përgatitet të regjistrohet takimi…",
|
||||
"rec": "REC",
|
||||
"recordAudioAndVideo": "Regjistro audio dhe video",
|
||||
"recordTranscription": "Regjistro transkriptimin",
|
||||
"saveLocalRecording": "Ruajeni lokalisht kartelën e regjistrimit (Beta)",
|
||||
"serviceDescription": "Regjistrimi juaj do të ruhet nga shërbimi i regjistrimit",
|
||||
"serviceDescriptionCloud": "Regjistrim në re",
|
||||
"serviceDescriptionCloudInfo": "Takimet e regjistruara spastrohen automatikisht 24h pas kohës së regjistrimit të tyre.",
|
||||
"serviceName": "Shërbim regjistrimi",
|
||||
"sessionAlreadyActive": "Ky sesion po regjistrohet ose transmetohet drejtpërsëdrejti tashmë.",
|
||||
"showAdvancedOptions": "Mundësi të mëtejshme",
|
||||
"signIn": "Hyni",
|
||||
"signOut": "Dilni",
|
||||
"surfaceError": "Ju lutemi, përdorni skedën e tanishme.",
|
||||
@@ -966,10 +1046,17 @@
|
||||
"security": {
|
||||
"about": "Takimit tuaj mund t’i shtoni një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
|
||||
"aboutReadOnly": "Pjesëmarrësit moderatorë mund t’i shtojnë takimit një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
|
||||
"insecureRoomNameWarning": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të marrin pjesë në konferencën tuaj. Shihni mundësinë e bërjes të sigurt të takimit tuaj, duke përdorur butonin e sigurisë.",
|
||||
"title": "Mundësi Sigurie"
|
||||
"insecureRoomNameWarningNative": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj ",
|
||||
"insecureRoomNameWarningWeb": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">këtu</a>.",
|
||||
"title": "Mundësi Sigurie",
|
||||
"unsafeRoomActions": {
|
||||
"meeting": "Shihni mundësinë e sigurimit të takimit tuaj, duke përdorur butonin e sigurisë.",
|
||||
"prejoin": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë.",
|
||||
"welcome": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë, ose zgjidhni një nga sugjerimet."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"audio": "Audio",
|
||||
"buttonLabel": "Rregullime",
|
||||
"calendar": {
|
||||
"about": "Integrimi me kalendar {{appName}} përdoret për të hyrë me sukses te kalendari juaj, që kështu të mund të lexohen veprimtari të ardhshme.",
|
||||
@@ -990,9 +1077,11 @@
|
||||
"maxStageParticipants": "Numër maksimum pjesëmarrësish që mund të fiksohen te skena kryesore (EKSPERIMENTALe)",
|
||||
"microphones": "Mikrofona",
|
||||
"moderator": "Moderator",
|
||||
"more": "Më tepër",
|
||||
"moderatorOptions": "Mundësi moderatori",
|
||||
"more": "Të përgjitshme",
|
||||
"name": "Emër",
|
||||
"noDevice": "Asnjë",
|
||||
"notifications": "Njoftime",
|
||||
"participantJoined": "Hyri Pjesëmarrës",
|
||||
"participantKnocking": "Pjesëmarrës hyri në holl",
|
||||
"participantLeft": "Doli Pjesëmarrës",
|
||||
@@ -1003,13 +1092,14 @@
|
||||
"selectCamera": "Kamerë",
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Parje e vetes",
|
||||
"sounds": "Tinguj",
|
||||
"shortcuts": "Shkurtore",
|
||||
"speakers": "Altoparlantë",
|
||||
"startAudioMuted": "Gjithkush fillon i heshtuar",
|
||||
"startReactionsMuted": "Heshto tinguj reagimesh për këdo",
|
||||
"startVideoMuted": "Gjithkush fillon i fshehur",
|
||||
"talkWhileMuted": "Flisni, teksa jeni i heshtuar",
|
||||
"title": "Rregullime"
|
||||
"title": "Rregullime",
|
||||
"video": "Video"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Të mëtejshme",
|
||||
@@ -1017,6 +1107,7 @@
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Kujdes",
|
||||
"alertURLText": "URL-ja e dhënë për shërbyesin është e pavlefshme",
|
||||
"apply": "Aplikoji",
|
||||
"buildInfoSection": "Hollësi Montimi",
|
||||
"conferenceSection": "Konferencë",
|
||||
"disableCallIntegration": "Çaktivizo integrim thirrje me sistemin",
|
||||
@@ -1027,19 +1118,21 @@
|
||||
"displayNamePlaceholderText": "P.sh.: Zamir Gjoli",
|
||||
"email": "Email",
|
||||
"emailPlaceholderText": "email@example.com",
|
||||
"gavatarMessage": "Nëse emal-i juaj është i përshoqëruar me një llogari Gravatar, do ta përdorim për të shfaqur foton e profilit tuaj.",
|
||||
"goTo": "Kalo te",
|
||||
"header": "Rregullime",
|
||||
"help": "Ndihmë",
|
||||
"links": "Lidhje",
|
||||
"privacy": "Privatësi",
|
||||
"profileSection": "Profil",
|
||||
"sdkVersion": "Version SDK",
|
||||
"serverURL": "URL Shërbyesi",
|
||||
"showAdvanced": "Shfaq rregullime të mëtejshme",
|
||||
"startCarModeInLowBandwidthMode": "Nën mënyrën “gjerësi e ulët bande” nis mënyrën automjet",
|
||||
"startWithAudioMuted": "Fillo me audio të mbyllur",
|
||||
"startWithVideoMuted": "Fillo me video të mbyllur",
|
||||
"terms": "Terma",
|
||||
"version": "Version"
|
||||
"version": "Version aplikacioni"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nThjesht doni t’i bini numrit në telefonin tuaj?\n\n{{defaultDialInNumber}}Klikoni mbi këtë lidhje, që të shihni numra telefoni për këtë takim\n{{dialInfoPageUrl}}",
|
||||
@@ -1050,7 +1143,7 @@
|
||||
"angry": "I zemëruar",
|
||||
"disgusted": "I pështirosur",
|
||||
"displayEmotions": "Shfaq emocione",
|
||||
"fearful": "I firkësuar",
|
||||
"fearful": "I frikësuar",
|
||||
"happy": "I gëzuar",
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
@@ -1058,7 +1151,7 @@
|
||||
"neutral": "Asnjanës",
|
||||
"sad": "I trishtuar",
|
||||
"search": "Kërko",
|
||||
"searchHint": "Kërkoni pjesëmarrësit",
|
||||
"searchHint": "Kërkoni për pjesëmarrës",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Statistika Folësi",
|
||||
"speakerTime": "Kohë Folësi",
|
||||
@@ -1081,7 +1174,7 @@
|
||||
"toolbar": {
|
||||
"Settings": "Rregullime",
|
||||
"accessibilityLabel": {
|
||||
"Settings": "Shfaq/Fshih rregullimet",
|
||||
"Settings": "Hap rregullimet",
|
||||
"audioOnly": "Hap/Mbyll vetëm audion",
|
||||
"audioRoute": "Përzgjidhni pajisje zëri",
|
||||
"boo": "Ya",
|
||||
@@ -1091,11 +1184,20 @@
|
||||
"cc": "Shfaq/Fshih titra",
|
||||
"chat": "Hapni / Mbyllni fjalosje",
|
||||
"clap": "Duartrokitje",
|
||||
"closeChat": "Mbylle fjalosjen",
|
||||
"closeMoreActions": "Mbyll menunë Më Tepër Veprime",
|
||||
"closeParticipantsPane": "Mbyll kuadratin pjesëmarrës",
|
||||
"collapse": "Tkurre",
|
||||
"document": "Shfaq/Fshih dokument të ndarë",
|
||||
"documentClose": "Mbyll dokument të ndarë",
|
||||
"documentOpen": "Hap dokument të ndarë",
|
||||
"download": "Shkarkoni aplikacionet tona",
|
||||
"embedMeeting": "Trupëzoni takimin",
|
||||
"endConference": "Përfundoje takimin për të tërë",
|
||||
"enterFullScreen": "Shiheni sa krejt ekrani",
|
||||
"enterTileView": "Kaloni nën mënyrën me kuadrate",
|
||||
"exitFullScreen": "Dil nga mënyra “Sa krejt ekrani”",
|
||||
"exitTileView": "Dil nga mënyra me kuadrate",
|
||||
"expand": "Zgjeroje",
|
||||
"feedback": "Lini përshtypje",
|
||||
"fullScreen": "Kalo nën/Dil nga mënyra “Sa krejt ekrani”",
|
||||
@@ -1104,6 +1206,7 @@
|
||||
"hangup": "Braktiseni takimin",
|
||||
"heading": "Panel",
|
||||
"help": "Ndihmë",
|
||||
"hideWhiteboard": "Fshihe tabelën",
|
||||
"invite": "Ftoni njerëz",
|
||||
"kick": "Përzëre pjesëmarrësin",
|
||||
"laugh": "E qeshur",
|
||||
@@ -1113,21 +1216,25 @@
|
||||
"lobbyButton": "Aktivizo/Çaktivizoni mënyrën holl",
|
||||
"localRecording": "Shfaq/Fshih kontrolle regjistrimi vendor",
|
||||
"lockRoom": "Aktivizo/Çaktivizo fjalëkalim takimi",
|
||||
"lowerHand": "Ulni dorën",
|
||||
"moreActions": "Më tepër veprime",
|
||||
"moreActionsMenu": "Menu “Më tepër veprime”",
|
||||
"moreOptions": "Shfaq më tepër mundësi",
|
||||
"mute": "Heshtoje / Çheshtoje",
|
||||
"mute": "Heshto mikrofonin",
|
||||
"muteEveryone": "Heshto gjithkënd",
|
||||
"muteEveryoneElse": "Heshto gjithkënd tjetër",
|
||||
"muteEveryoneElsesVideoStream": "Ndal videon e gjithkujt tjetër",
|
||||
"muteEveryonesVideoStream": "Ndal videon e gjithkujt",
|
||||
"muteGUMPending": "Po lidhet mikrofoni juaj",
|
||||
"noiseSuppression": "Mbytje zhurmash",
|
||||
"participants": "Pjesëmarrës",
|
||||
"openChat": "Hapni fjalosje",
|
||||
"participants": "Hapni kuadrat pjesëmarrësish",
|
||||
"pip": "Aktivizo/Çaktivizo mënyrën “Picture-in-Picture”",
|
||||
"privateMessage": "Dërgoni mesazh privat",
|
||||
"profile": "Përpunoni profilin tuaj",
|
||||
"raiseHand": "Ngrini / Ulni dorën tuaj",
|
||||
"reactionsMenu": "Hap / Mbyll menu reagimesh",
|
||||
"raiseHand": "Ngrini dorën",
|
||||
"reactions": "Reagime",
|
||||
"reactionsMenu": "Menu reagimesh",
|
||||
"recording": "Nis/Ndal regjistrimin",
|
||||
"remoteMute": "Heshto pjesëmarrësin",
|
||||
"remoteVideoMute": "Çaktivizo kamerën e pjesëmarrësit",
|
||||
@@ -1135,20 +1242,25 @@
|
||||
"selectBackground": "Përzgjidhni Sfond",
|
||||
"selfView": "Shfaq/Fshih pamje të vetes",
|
||||
"shareRoom": "Ftoni dikë",
|
||||
"shareYourScreen": "Nisni / Ndalni tregimin e ekranit tuaj",
|
||||
"shareYourScreen": "Nisni tregimin e ekranit tuaj",
|
||||
"shareaudio": "Ndani audio me të tjerë",
|
||||
"sharedvideo": "Aktivizo/Çaktivizo ndarje videoje me të tjerë",
|
||||
"sharedvideo": "Ndani video me të tjerë",
|
||||
"shortcuts": "Shfaq/Fshih shkurtore",
|
||||
"show": "Shfaqe në skenë",
|
||||
"showWhiteboard": "Shfaq tabelë",
|
||||
"silence": "Heshtje",
|
||||
"speakerStats": "Shfaq/Fshih statistika folësi",
|
||||
"stopScreenSharing": "Ndalni tregimin e ekranit tuaj",
|
||||
"stopSharedVideo": "Ndalni videon",
|
||||
"surprised": "I befasuar",
|
||||
"tileView": "Kalo në/Dil nga mënyra mozaik",
|
||||
"toggleCamera": "Hap/Mbyll kamerën",
|
||||
"toggleFilmstrip": "Shfaq/Fshih shirit filmi",
|
||||
"unmute": "Hiqni heshtim mikrofoni",
|
||||
"videoblur": "Aktivizo/Çaktivizo turbullim videoje",
|
||||
"videomute": "Nis / Ndal kamerën",
|
||||
"whiteboard": "Shfaq / Fshih tabelën"
|
||||
"videomute": "Ndal kamerën",
|
||||
"videomuteGUMPending": "Po lidhet kamera juaj",
|
||||
"videounmute": "Nis kamerën"
|
||||
},
|
||||
"addPeople": "Shtoni persona te thirrja juaj",
|
||||
"audioOnlyOff": "Çaktivizo mënyrën “Sasi e ulët të dhënash trafiku”",
|
||||
@@ -1161,6 +1273,7 @@
|
||||
"chat": "Hap / Mbyll fjalosje",
|
||||
"clap": "Duartrokitje",
|
||||
"closeChat": "Mbyll fjalosjen",
|
||||
"closeParticipantsPane": "Mbylle kuadratin e pjesëmarrësve",
|
||||
"closeReactionsMenu": "Mbyll menu reagimesh",
|
||||
"disableNoiseSuppression": "Çaktivizo mbytje zhurmash",
|
||||
"disableReactionSounds": "Mund të çaktivizoni tinguj reagimesh për këtë takim",
|
||||
@@ -1169,6 +1282,7 @@
|
||||
"download": "Shkarkoni aplikacione tonat",
|
||||
"e2ee": "Fshehtëzim Skaj-Më-Skaj",
|
||||
"embedMeeting": "Trupëzoni takim",
|
||||
"enableNoiseSuppression": "Aktivizoni mbytje zhurmash",
|
||||
"endConference": "Përfundoje takimin për të tërë",
|
||||
"enterFullScreen": "Shiheni sa krejt ekrani",
|
||||
"enterTileView": "Kalo te pamja me kuadrate",
|
||||
@@ -1193,9 +1307,10 @@
|
||||
"lowerYourHand": "Ulni dorën",
|
||||
"moreActions": "Më tepër veprime",
|
||||
"moreOptions": "Më tepër veprime",
|
||||
"mute": "Heshtoje / Hiqi heshtimin",
|
||||
"mute": "Heshto mikrofonin",
|
||||
"muteEveryone": "Heshto gjithkënd",
|
||||
"muteEveryonesVideo": "Çaktivizo videon e gjithkujt",
|
||||
"muteGUMPending": "Po lidhet mikrofoni juaj",
|
||||
"noAudioSignalDesc": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e ndërrimit të pajisjes.",
|
||||
"noAudioSignalDescSuggestion": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e kalimit te pajisja e sugjeruar.",
|
||||
"noAudioSignalDialInDesc": "Mund t’i bini numrit edhe duke përdorur:",
|
||||
@@ -1210,7 +1325,7 @@
|
||||
"pip": "Kalo nën mënyrën “Picture-in-Picture”",
|
||||
"privateMessage": "Dërgo mesazh privat",
|
||||
"profile": "Përpunoni profilin tuaj",
|
||||
"raiseHand": "Ngrini / Ulni dorën",
|
||||
"raiseHand": "Ngrini dorën",
|
||||
"raiseYourHand": "Ngrini dorën",
|
||||
"reactionBoo": "Dërgoni reagim me ya",
|
||||
"reactionClap": "Dërgoni reagim me duartrokitje",
|
||||
@@ -1218,6 +1333,7 @@
|
||||
"reactionLike": "Dërgoni reagim me “thumbs up”",
|
||||
"reactionSilence": "Dërgoni reagim me heshtje",
|
||||
"reactionSurprised": "Dërgoni reagim të befasuari",
|
||||
"reactions": "Reagime",
|
||||
"security": "Mundësi sigurie",
|
||||
"selectBackground": "Përzgjidhni sfond",
|
||||
"shareRoom": "Ftoni dikë",
|
||||
@@ -1237,17 +1353,17 @@
|
||||
"talkWhileMutedPopup": "Po provoni të flisni? Jeni i heshtuar.",
|
||||
"tileViewToggle": "Kalo në/Dil nga mënyra mozaik",
|
||||
"toggleCamera": "Hapni/Mbyllni kamerën",
|
||||
"videoSetting": "Rregullime videoje",
|
||||
"videomute": "Nisni / Ndalni kamerën"
|
||||
"unmute": "Hiqni heshtimin e mikrofonit",
|
||||
"videoSettings": "Rregullime videoje",
|
||||
"videomute": "Ndalni kamerën",
|
||||
"videomuteGUMPending": "Po lidhet kamera juaj",
|
||||
"videounmute": "Nisni kamerën"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Ndali / Nisi titrat",
|
||||
"error": "Transkriptimi dështoi. Ju lutemi, riprovoni.",
|
||||
"expandedLabel": "Transkriptimi aktualisht është aktiv",
|
||||
"failedToStart": "S’u arrit të nisej transkriptim",
|
||||
"labelToolTip": "Takimit po i bëhet transkriptim",
|
||||
"off": "Transkriptimi u ndal",
|
||||
"pending": "Po përgatitet të transkriptohet takimi…",
|
||||
"sourceLanguageDesc": "Aktualisht si gjuhë takimi është caktuar <b>{{sourceLanguage}}</b>. <br/> Mund ta ndryshoni që nga ",
|
||||
"sourceLanguageHere": "këtu",
|
||||
"start": "Fillo shfaqje titrash",
|
||||
@@ -1284,7 +1400,7 @@
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "Gjendeni nën mënyrën “gjerësi e ulët bande”. Nën këtë mënyrë, do të merrni vetëm audio dhe tregim ekrani.",
|
||||
"bestPerformance": "Punimi më i mirë",
|
||||
"callQuality": "Cilësi videoje",
|
||||
"callQuality": "Cilësi Videoje (0 për punimin më të mirë, 3 për cilësinë më të lartë)",
|
||||
"hd": "CL",
|
||||
"hdTooltip": "Po shihet video në cilësi të lartë",
|
||||
"highDefinition": "Cilësi e Lartë",
|
||||
@@ -1326,6 +1442,10 @@
|
||||
"videomute": "Pjesëmarrësi ka ndalur kamerën"
|
||||
},
|
||||
"virtualBackground": {
|
||||
"accessibilityLabel": {
|
||||
"currentBackground": "Sfondi i tanishëm: {{background}}",
|
||||
"selectBackground": "Përzgjidhni një sfond"
|
||||
},
|
||||
"addBackground": "Shtoni sfond",
|
||||
"apply": "Zbatoje",
|
||||
"backgroundEffectError": "S’u arrit të zbatohej efekt sfondi.",
|
||||
@@ -1343,13 +1463,20 @@
|
||||
"none": "Asnjë",
|
||||
"pleaseWait": "Ju lutemi, pritni…",
|
||||
"removeBackground": "Hiqe sfondin",
|
||||
"slightBlur": "Turbullim Paksa",
|
||||
"slightBlur": "Gjysmë-Turbullim",
|
||||
"title": "Sfonde virtualë",
|
||||
"uploadedImage": "Ngarkoi figurën {{index}}",
|
||||
"webAssemblyWarning": "Nuk mbulohet WebAssembly",
|
||||
"webAssemblyWarningDescription": "WebAssembly e çaktivizuar ose e pambuluar nga ky shfletues"
|
||||
},
|
||||
"visitorsLabel": "Numër vizitorësh: {{count}}",
|
||||
"visitors": {
|
||||
"chatIndicator": "(vizitor)",
|
||||
"labelTooltip": "Numër vizitorësh: {{count}}",
|
||||
"notification": {
|
||||
"description": "Që të merrni pjesë, ngrini dorën",
|
||||
"title": "Jeni vizitor në takim"
|
||||
}
|
||||
},
|
||||
"volumeSlider": "Rrëshqitës volumi",
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -1382,6 +1509,7 @@
|
||||
"microsoftLogo": "Stemë Microsoft-i",
|
||||
"policyLogo": "Stemë Rregullash"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "Takime",
|
||||
"mobileDownLoadLinkAndroid": "Shkarkoni aplikacionin për celular me Android",
|
||||
"mobileDownLoadLinkFDroid": "Shkarkoni aplikacionin për celular me F-Droid",
|
||||
"mobileDownLoadLinkIos": "Shkarkoni aplikacionin për celular me iOS",
|
||||
@@ -1405,6 +1533,7 @@
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Tabelë shënimesh"
|
||||
}
|
||||
},
|
||||
"screenTitle": "Tabelë shënimesh"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +939,7 @@
|
||||
"or": "or",
|
||||
"premeeting": "Pre meeting",
|
||||
"proceedAnyway": "Proceed anyway",
|
||||
"recordingWarning": "This call might be recorded",
|
||||
"recordingWarning": "Other participants may be recording this call",
|
||||
"screenSharingError": "Screen sharing error:",
|
||||
"showScreen": "Enable pre meeting screen",
|
||||
"startWithPhone": "Start with phone audio",
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -61,7 +61,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/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -12943,8 +12943,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-i1ygVANxENUVAuWpo/KPcgk1LW0w6/LVnib9+xfcvZs7f1o/58eolG0k/rCJEUEpUWivHgdY7tKTbhHj8yPVDQ==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-qs18nB89iQgiNR/UWTg/GCuEqUAIBdJRyFv9r0EaDThkQS8vHLNzYO9UQId8xZxiOGOR2kunJO84SbcivOTqbw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -29527,8 +29527,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-i1ygVANxENUVAuWpo/KPcgk1LW0w6/LVnib9+xfcvZs7f1o/58eolG0k/rCJEUEpUWivHgdY7tKTbhHj8yPVDQ==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-qs18nB89iQgiNR/UWTg/GCuEqUAIBdJRyFv9r0EaDThkQS8vHLNzYO9UQId8xZxiOGOR2kunJO84SbcivOTqbw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
|
||||
@@ -67,7 +67,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/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
||||
@@ -206,9 +206,9 @@ const PreMeetingScreen = ({
|
||||
{children}
|
||||
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
|
||||
{skipPrejoinButton}
|
||||
{showRecordingWarning && <RecordingWarning />}
|
||||
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
|
||||
{showDeviceStatus && <DeviceStatus />}
|
||||
{showRecordingWarning && <RecordingWarning />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,53 +2,37 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconRecord } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.palette.warning01,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
color: theme.palette.text04,
|
||||
warning: {
|
||||
bottom: 0,
|
||||
color: theme.palette.text03,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
marginBottom: theme.spacing(3),
|
||||
marginTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2),
|
||||
width: '100%'
|
||||
},
|
||||
warning: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
paddingLeft: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3)
|
||||
},
|
||||
text: {
|
||||
fontWeight: 600,
|
||||
paddingLeft: theme.spacing(2)
|
||||
paddingRight: theme.spacing(3),
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
|
||||
'@media (max-width: 720px)': {
|
||||
position: 'relative'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const RecordingWarning = () => {
|
||||
const { t } = useTranslation();
|
||||
const { classes, theme } = useStyles();
|
||||
const color = theme.palette.icon04;
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<div className = { classes.container }>
|
||||
<div className = { classes.warning }>
|
||||
<Icon
|
||||
color = { color }
|
||||
src = { IconRecord } />
|
||||
<span className = { classes.text }>
|
||||
{t('prejoin.recordingWarning')}
|
||||
</span>
|
||||
</div>
|
||||
<div className = { classes.warning }>
|
||||
{t('prejoin.recordingWarning')}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,12 +33,12 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
|
||||
if (enabled) {
|
||||
const isSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
if (!isSharing) {
|
||||
_startScreenSharing(dispatch, state);
|
||||
if (isSharing) {
|
||||
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
|
||||
} else {
|
||||
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.launch();
|
||||
_startScreenSharing(dispatch, state);
|
||||
}
|
||||
|
||||
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
|
||||
} else {
|
||||
dispatch(setScreenshareMuted(true));
|
||||
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { openHighlightDialog } from '../../../recording/actions.native';
|
||||
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
|
||||
import RecordingLabel from '../../../recording/components/native/RecordingLabel';
|
||||
import { getActiveSession } from '../../../recording/functions';
|
||||
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
|
||||
|
||||
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
|
||||
@@ -28,7 +30,10 @@ interface IProps {
|
||||
|
||||
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
const isStreaming = useSelector((state: IReduxState) =>
|
||||
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)));
|
||||
const openHighlightDialogCallback = useCallback(() =>
|
||||
dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
|
||||
return (<>
|
||||
<TouchableOpacity
|
||||
@@ -36,11 +41,14 @@ const AlwaysOnLabels = ({ createOnPress }: IProps) => {
|
||||
onPress = { createOnPress(LABEL_ID_RECORDING) } >
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
hitSlop = { LabelHitSlop }
|
||||
onPress = { createOnPress(LABEL_ID_STREAMING) } >
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</TouchableOpacity>
|
||||
{
|
||||
isStreaming
|
||||
&& <TouchableOpacity
|
||||
hitSlop = { LabelHitSlop }
|
||||
onPress = { createOnPress(LABEL_ID_STREAMING) } >
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</TouchableOpacity>
|
||||
}
|
||||
<TouchableOpacity
|
||||
hitSlop = { LabelHitSlop }
|
||||
onPress = { openHighlightDialogCallback }>
|
||||
|
||||
@@ -43,7 +43,11 @@ import {
|
||||
import { INotificationProps } from '../notifications/types';
|
||||
import { open as openParticipantsPane } from '../participants-pane/actions';
|
||||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions';
|
||||
import {
|
||||
isPrejoinEnabledInConfig,
|
||||
isPrejoinPageVisible,
|
||||
shouldAutoKnock
|
||||
} from '../prejoin/functions';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
@@ -269,7 +273,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
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') {
|
||||
@@ -284,7 +287,9 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
dispatch(openLobbyScreen());
|
||||
|
||||
// if there was an error about display name and pre-join is not enabled
|
||||
if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled) || lobbyWaitingForHost) {
|
||||
if (shouldAutoKnock(state)
|
||||
|| (isDisplayNameRequiredError && !isPrejoinEnabledInConfig(state))
|
||||
|| lobbyWaitingForHost) {
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { IconCloseLarge } from '../../base/icons/svg';
|
||||
import { toState } from '../../base/redux/functions';
|
||||
import { cancelKnocking } from '../../lobby/actions.native';
|
||||
import { isPrejoinEnabledInConfig } from '../../prejoin/functions';
|
||||
|
||||
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
||||
|
||||
@@ -50,10 +51,8 @@ export function screenHeaderCloseButton(goBack: (e?: GestureResponderEvent | Rea
|
||||
*/
|
||||
export function isPrejoinPageEnabled(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const { prejoinConfig } = state['features/base/config'];
|
||||
const isPrejoinEnabledInConfig = prejoinConfig?.enabled;
|
||||
|
||||
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig ?? true);
|
||||
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig(state));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,7 +72,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
}
|
||||
|
||||
case NON_PARTICIPANT_MESSAGE_RECEIVED: {
|
||||
const { id, data } = action;
|
||||
const { id, json: data } = action;
|
||||
const isNewPoll = data.type === COMMAND_NEW_POLL;
|
||||
|
||||
_handleReceivePollsMessage({
|
||||
|
||||
@@ -42,7 +42,6 @@ import { isDisplayNameRequired, isRoomNameEnabled } from '../../functions';
|
||||
import { IPrejoinProps } from '../../types';
|
||||
import { hasDisplayName } from '../../utils';
|
||||
|
||||
import RecordingWarning from './RecordingWarning';
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
@@ -60,8 +59,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const isDisplayNameReadonly = useSelector(isNameReadOnly);
|
||||
const roomName = useSelector((state: IReduxState) => getConferenceName(state));
|
||||
const roomNameEnabled = useSelector((state: IReduxState) => isRoomNameEnabled(state));
|
||||
const showRecordingWarning
|
||||
= useSelector((state: IReduxState) => state['features/base/config'].recordings?.showPrejoinWarning);
|
||||
const participantName = localParticipant?.name;
|
||||
const [ displayName, setDisplayName ]
|
||||
= useState(participantName || '');
|
||||
@@ -182,7 +179,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{showRecordingWarning && <RecordingWarning />}
|
||||
</View>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
|
||||
@@ -8,30 +8,19 @@ import {
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconRecord } from '../../../base/icons/svg';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
const RecordingWarning = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const color = BaseTheme.palette.icon04;
|
||||
|
||||
return (
|
||||
<View style = { styles.recordingWarningContainer as StyleProp<ViewStyle> }>
|
||||
<View style = { styles.recordingWarning as StyleProp<ViewStyle> }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 20 }
|
||||
src = { IconRecord } />
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.recordingWarningText as StyleProp<TextStyle> }>
|
||||
{ t('prejoin.recordingWarning') }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.recordingWarning as StyleProp<ViewStyle> }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.recordingWarningText as StyleProp<TextStyle> }>
|
||||
{ t('prejoin.recordingWarning') }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -122,30 +122,16 @@ export const preJoinStyles = {
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
paddingVertical: BaseTheme.spacing[1]
|
||||
},
|
||||
recordingWarningContainer: {
|
||||
backgroundColor: BaseTheme.palette.warning01,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
color: BaseTheme.palette.text04,
|
||||
recordingWarning: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
lineHeight: 22,
|
||||
marginBottom: BaseTheme.spacing[2],
|
||||
marginTop: BaseTheme.spacing[1],
|
||||
paddingBottom: BaseTheme.spacing[1],
|
||||
paddingTop: BaseTheme.spacing[1],
|
||||
width: 'auto'
|
||||
},
|
||||
recordingWarning: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: BaseTheme.spacing[2],
|
||||
paddingRight: BaseTheme.spacing[2]
|
||||
},
|
||||
recordingWarningText: {
|
||||
fontWeight: '600',
|
||||
paddingLeft: BaseTheme.spacing[1]
|
||||
color: BaseTheme.palette.text03
|
||||
},
|
||||
unsafeRoomWarningContainer: {
|
||||
height: '100%',
|
||||
|
||||
@@ -42,6 +42,16 @@ export function isDisplayNameRequired(state: IReduxState): boolean {
|
||||
|| state['features/base/config']?.requireDisplayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the prejoin page is enabled in config. Defaults to `true`.
|
||||
*
|
||||
* @param {IReduxState} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinEnabledInConfig(state: IReduxState): boolean {
|
||||
return state['features/base/config'].prejoinConfig?.enabled ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the prejoin display name field is visible.
|
||||
*
|
||||
@@ -154,7 +164,7 @@ export function isJoinByPhoneDialogVisible(state: IReduxState): boolean {
|
||||
*/
|
||||
export function isPrejoinPageVisible(state: IReduxState): boolean {
|
||||
return Boolean(navigator.product !== 'ReactNative'
|
||||
&& state['features/base/config'].prejoinConfig?.enabled
|
||||
&& isPrejoinEnabledInConfig(state)
|
||||
&& state['features/prejoin']?.showPrejoin
|
||||
&& !(state['features/base/config'].enableForcedReload && state['features/prejoin'].skipPrejoinOnReload));
|
||||
}
|
||||
@@ -166,12 +176,11 @@ export function isPrejoinPageVisible(state: IReduxState): boolean {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldAutoKnock(state: IReduxState): boolean {
|
||||
const { iAmRecorder, iAmSipGateway, prejoinConfig } = state['features/base/config'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
const { userSelectedSkipPrejoin } = state['features/base/settings'];
|
||||
const { autoKnock } = getLobbyConfig(state);
|
||||
const isPrejoinEnabled = prejoinConfig?.enabled;
|
||||
|
||||
return Boolean(((isPrejoinEnabled && !userSelectedSkipPrejoin)
|
||||
return Boolean(((isPrejoinEnabledInConfig(state) && !userSelectedSkipPrejoin)
|
||||
|| autoKnock || (iAmRecorder && iAmSipGateway))
|
||||
&& !state['features/lobby'].knocking);
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ export function stopLocalVideoRecording() {
|
||||
*/
|
||||
export function showStartRecordingNotificationWithCallback(openRecordingDialog: Function) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
let state = getState();
|
||||
const { recordings } = state['features/base/config'];
|
||||
const { suggestRecording } = recordings || {};
|
||||
const recordButtonProps = getRecordButtonProps(state);
|
||||
@@ -427,9 +427,9 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
|
||||
customActionType: [ BUTTON_TYPES.PRIMARY ],
|
||||
customActionNameKey: [ 'notify.suggestRecordingAction' ],
|
||||
customActionHandler: [ () => {
|
||||
state = getState();
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const { recordingService } = state['features/base/config'];
|
||||
|
||||
const canBypassDialog = isModerator
|
||||
&& recordingService?.enabled
|
||||
&& isJwtFeatureEnabled(state, 'recording', true);
|
||||
|
||||
@@ -397,7 +397,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.shouldRecordTranscription) {
|
||||
if (this.state.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE
|
||||
&& this.state.shouldRecordTranscription) {
|
||||
dispatch(setRequestingSubtitles(true, false, null));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ interface ILocalRecordingManager {
|
||||
|
||||
const getMimeType = (): string => {
|
||||
const possibleTypes = [
|
||||
'video/mp4;codecs=h264',
|
||||
'video/webm;codecs=h264',
|
||||
'video/webm;codecs=vp9',
|
||||
'video/webm;codecs=vp8'
|
||||
];
|
||||
|
||||
@@ -164,7 +161,11 @@ const LocalRecordingManager: ILocalRecordingManager = {
|
||||
this.audioContext = undefined;
|
||||
this.audioDestination = undefined;
|
||||
this.totalSize = MAX_SIZE;
|
||||
setTimeout(() => this.saveRecording(this.recordingData, this.getFilename()), 1000);
|
||||
setTimeout(() => {
|
||||
if (this.recordingData.length > 0) {
|
||||
this.saveRecording(this.recordingData, this.getFilename());
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderAdvancedOptions() {
|
||||
if (!this._canStartTranscribing()) {
|
||||
const { selectedRecordingService } = this.props;
|
||||
|
||||
if (selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE || !this._canStartTranscribing()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderAdvancedOptions() {
|
||||
if (!this._canStartTranscribing()) {
|
||||
const { selectedRecordingService } = this.props;
|
||||
|
||||
if (selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE || !this._canStartTranscribing()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class RecordingExpandedLabel extends ExpandedLabel<IProps> {
|
||||
*/
|
||||
_getLabel() {
|
||||
const { _status, mode, t } = this.props;
|
||||
let postfix = 'recording', prefix = 'expandedOn'; // Default values.
|
||||
let postfix = 'expandedOn', prefix = 'recording'; // Default values.
|
||||
|
||||
switch (mode) {
|
||||
case JitsiRecordingConstants.mode.STREAM:
|
||||
|
||||
@@ -11,6 +11,7 @@ import { updateSettings } from '../../../base/settings/actions';
|
||||
import Switch from '../../../base/ui/components/native/Switch';
|
||||
import { navigate } from '../../../mobile/navigation/components/settings/SettingsNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { isPrejoinEnabledInConfig } from '../../../prejoin/functions';
|
||||
|
||||
import FormRow from './FormRow';
|
||||
import FormSection from './FormSection';
|
||||
@@ -27,8 +28,7 @@ const GeneralSection = () => {
|
||||
|
||||
const showPrejoinPage = !userSelectedSkipPrejoin;
|
||||
|
||||
let showPrejoinSettings = useSelector(
|
||||
(state: IReduxState) => state['features/base/config'].prejoinConfig?.enabled);
|
||||
let showPrejoinSettings = useSelector(isPrejoinEnabledInConfig);
|
||||
|
||||
const { language = DEFAULT_LANGUAGE } = i18next;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getHideSelfView } from '../base/settings/functions.any';
|
||||
import { parseStandardURIString } from '../base/util/uri';
|
||||
import { isStageFilmstripEnabled } from '../filmstrip/functions';
|
||||
import { isFollowMeActive } from '../follow-me/functions';
|
||||
import { isPrejoinEnabledInConfig } from '../prejoin/functions';
|
||||
import { isReactionsEnabled } from '../reactions/functions.any';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
@@ -114,7 +115,7 @@ export function getMoreTabProps(stateful: IStateful) {
|
||||
maxStageParticipants: state['features/base/settings'].maxStageParticipants,
|
||||
showLanguageSettings: configuredTabs.includes('language'),
|
||||
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
|
||||
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled,
|
||||
showPrejoinSettings: isPrejoinEnabledInConfig(state),
|
||||
stageFilmstripEnabled
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ local saslprep = require "util.encodings".stringprep.saslprep;
|
||||
local secure_equals = require "util.hashes".equals;
|
||||
|
||||
local shared_secret = module:get_option_string('shared_secret');
|
||||
local shared_secret_prev = module:get_option_string('shared_secret_prev');
|
||||
if shared_secret == nil then
|
||||
module:log('error', 'No shared_secret specified. No secret to operate on!');
|
||||
return;
|
||||
@@ -24,6 +25,9 @@ function provider.test_password(username, password)
|
||||
|
||||
if secure_equals(password, saslprep(shared_secret)) then
|
||||
return true;
|
||||
elseif (shared_secret_prev ~= nil and secure_equals(password, saslprep(shared_secret_prev))) then
|
||||
module:log("info", "Accepting login using previous shared secret, username=%s", username);
|
||||
return true;
|
||||
else
|
||||
return nil, "Auth failed. Invalid username or password.";
|
||||
end
|
||||
|
||||
@@ -15,6 +15,12 @@ end
|
||||
|
||||
module:depends("jitsi_session");
|
||||
|
||||
local measure_pre_fetch_fail = module:measure('pre_fetch_fail', 'counter');
|
||||
local measure_verify_fail = module:measure('verify_fail', 'counter');
|
||||
local measure_success = module:measure('success', 'counter');
|
||||
local measure_ban = module:measure('ban', 'counter');
|
||||
local measure_post_auth_fail = module:measure('post_auth_fail', 'counter');
|
||||
|
||||
-- define auth provider
|
||||
local provider = {};
|
||||
|
||||
@@ -74,6 +80,7 @@ function provider.get_sasl_handler(session)
|
||||
module:log("warn",
|
||||
"Error verifying token on pre authentication stage:%s, reason:%s", pre_event_result.error, pre_event_result.reason);
|
||||
session.auth_token = nil;
|
||||
measure_pre_fetch_fail(1);
|
||||
return pre_event_result.res, pre_event_result.error, pre_event_result.reason;
|
||||
end
|
||||
|
||||
@@ -82,21 +89,21 @@ function provider.get_sasl_handler(session)
|
||||
module:log("warn",
|
||||
"Error verifying token err:%s, reason:%s", error, reason);
|
||||
session.auth_token = nil;
|
||||
measure_verify_fail(1);
|
||||
return res, error, reason;
|
||||
end
|
||||
|
||||
local shouldAllow = prosody.events.fire_event("jitsi-access-ban-check", session);
|
||||
if shouldAllow == false then
|
||||
module:log("warn", "user is banned")
|
||||
measure_ban(1);
|
||||
return false, "not-allowed", "user is banned";
|
||||
end
|
||||
|
||||
local customUsername
|
||||
= prosody.events.fire_event("pre-jitsi-authentication", session);
|
||||
|
||||
if (customUsername) then
|
||||
local customUsername = prosody.events.fire_event("pre-jitsi-authentication", session);
|
||||
if customUsername then
|
||||
self.username = customUsername;
|
||||
elseif (session.previd ~= nil) then
|
||||
elseif session.previd ~= nil then
|
||||
for _, session1 in pairs(sessions) do
|
||||
if (session1.resumption_token == session.previd) then
|
||||
self.username = session1.username;
|
||||
@@ -112,9 +119,11 @@ function provider.get_sasl_handler(session)
|
||||
module:log("warn",
|
||||
"Error verifying token on post authentication stage :%s, reason:%s", post_event_result.error, post_event_result.reason);
|
||||
session.auth_token = nil;
|
||||
measure_post_auth_fail(1);
|
||||
return post_event_result.res, post_event_result.error, post_event_result.reason;
|
||||
end
|
||||
|
||||
measure_success(1);
|
||||
return res;
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
local st = require "util.stanza";
|
||||
local is_feature_allowed = module:require "util".is_feature_allowed;
|
||||
local token_util = module:require "token/util".new(module);
|
||||
|
||||
local accepted_rayo_iq_token_issuers = module:get_option_array("accepted_rayo_iq_token_issuers");
|
||||
|
||||
-- filters jibri iq in case of requested from jwt authenticated session that
|
||||
-- has features in the user context, but without feature for recording
|
||||
@@ -15,27 +12,6 @@ module:hook("pre-iq/full", function(event)
|
||||
local token = session.auth_token;
|
||||
|
||||
if jibri.attr.action == 'start' then
|
||||
local errorReason;
|
||||
if accepted_rayo_iq_token_issuers then
|
||||
local iq_token = jibri.attr.token;
|
||||
if iq_token then
|
||||
local session = {};
|
||||
session.auth_token = iq_token;
|
||||
local verified, reason = token_util:process_and_verify_token(
|
||||
session, accepted_rayo_iq_token_issuers);
|
||||
if verified then
|
||||
return nil; -- this will proceed with dispatching the stanza
|
||||
end
|
||||
errorReason = reason;
|
||||
else
|
||||
errorReason = 'No recording token provided';
|
||||
end
|
||||
|
||||
module:log("warn", "not a valid token %s", tostring(errorReason));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
if token == nil
|
||||
or not is_feature_allowed(session.jitsi_meet_context_features,
|
||||
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')
|
||||
|
||||
@@ -12,6 +12,8 @@ local jid_bare = require "util.jid".bare;
|
||||
|
||||
local sessions = prosody.full_sessions;
|
||||
|
||||
local measure_drop = module:measure('drop', 'counter');
|
||||
|
||||
local main_muc_component_host = module:get_option_string('main_muc');
|
||||
if main_muc_component_host == nil then
|
||||
module:log('error', 'main_muc not configured. Cannot proceed.');
|
||||
@@ -108,6 +110,7 @@ module:hook("pre-iq/full", function(event)
|
||||
then
|
||||
module:log("warn",
|
||||
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
|
||||
measure_drop(1);
|
||||
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
|
||||
return true;
|
||||
end
|
||||
|
||||
@@ -27,6 +27,9 @@ module:depends("jitsi_session");
|
||||
|
||||
local jid_split = require 'util.jid'.split;
|
||||
local jid_bare = require 'util.jid'.bare;
|
||||
local jid_prep = require "util.jid".prep;
|
||||
local jid_resource = require "util.jid".resource;
|
||||
local resourceprep = require "util.encodings".stringprep.resourceprep;
|
||||
local json = require 'util.json';
|
||||
local filters = require 'util.filters';
|
||||
local st = require 'util.stanza';
|
||||
@@ -235,6 +238,77 @@ function destroy_lobby_room(room, newjid, message)
|
||||
end
|
||||
end
|
||||
|
||||
-- handle multiple items at once
|
||||
function handle_admin_query_set_command(self, origin, stanza)
|
||||
for i=1,#stanza.tags[1] do
|
||||
if handle_admin_query_set_command_item(self, origin, stanza, stanza.tags[1].tags[i]) then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
-- This is a copy of the function(handle_admin_query_set_command) from prosody 12 (d7857ef7843a)
|
||||
function handle_admin_query_set_command_item(self, origin, stanza, item)
|
||||
if not item then
|
||||
origin.send(st.error_reply(stanza, "cancel", "bad-request"));
|
||||
return true;
|
||||
end
|
||||
if item.attr.jid then -- Validate provided JID
|
||||
item.attr.jid = jid_prep(item.attr.jid);
|
||||
if not item.attr.jid then
|
||||
origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
|
||||
return true;
|
||||
elseif jid_resource(item.attr.jid) then
|
||||
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "Bare JID expected, got full JID"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
if item.attr.nick then -- Validate provided nick
|
||||
item.attr.nick = resourceprep(item.attr.nick);
|
||||
if not item.attr.nick then
|
||||
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "invalid nickname"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
if not item.attr.jid and item.attr.nick then
|
||||
-- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
|
||||
local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick);
|
||||
if occupant then item.attr.jid = occupant.bare_jid; end
|
||||
elseif item.attr.role and not item.attr.nick and item.attr.jid then
|
||||
-- Role changes should use nick, but we have a JID so pull the nick from that
|
||||
local nick = self:get_occupant_jid(item.attr.jid);
|
||||
if nick then item.attr.nick = jid_resource(nick); end
|
||||
end
|
||||
local actor = stanza.attr.from;
|
||||
local reason = item:get_child_text("reason");
|
||||
local success, errtype, err
|
||||
if item.attr.affiliation and item.attr.jid and not item.attr.role then
|
||||
local registration_data;
|
||||
if item.attr.nick then
|
||||
local room_nick = self.jid.."/"..item.attr.nick;
|
||||
local existing_occupant = self:get_occupant_by_nick(room_nick);
|
||||
if existing_occupant and existing_occupant.bare_jid ~= item.attr.jid then
|
||||
module:log("debug", "Existing occupant for %s: %s does not match %s", room_nick, existing_occupant.bare_jid, item.attr.jid);
|
||||
self:set_role(true, room_nick, nil, "This nickname is reserved");
|
||||
end
|
||||
module:log("debug", "Reserving %s for %s (%s)", item.attr.nick, item.attr.jid, item.attr.affiliation);
|
||||
registration_data = { reserved_nickname = item.attr.nick };
|
||||
end
|
||||
success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason, registration_data);
|
||||
elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
|
||||
success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason);
|
||||
else
|
||||
success, errtype, err = nil, "cancel", "bad-request";
|
||||
end
|
||||
self:save(true);
|
||||
if not success then
|
||||
origin.send(st.error_reply(stanza, errtype, err));
|
||||
else
|
||||
origin.send(st.reply(stanza));
|
||||
end
|
||||
end
|
||||
|
||||
-- operates on already loaded lobby muc module
|
||||
function process_lobby_muc_loaded(lobby_muc, host_module)
|
||||
module:log('debug', 'Lobby muc loaded');
|
||||
@@ -441,6 +515,24 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
end
|
||||
end
|
||||
end);
|
||||
|
||||
-- listen for admin set
|
||||
for event_name, method in pairs {
|
||||
-- Normal room interactions
|
||||
["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
|
||||
-- Host room
|
||||
["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
|
||||
} do
|
||||
host_module:hook(event_name, function (event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local room_jid = jid_bare(stanza.attr.to);
|
||||
local room = get_room_from_jid(room_jid);
|
||||
|
||||
if room then
|
||||
return handle_admin_query_set_command(room, origin, stanza);
|
||||
end
|
||||
end, 1) -- make sure we handle it before prosody that uses priority -2 for this
|
||||
end
|
||||
end);
|
||||
|
||||
function handle_create_lobby(event)
|
||||
|
||||
@@ -37,16 +37,16 @@ local function load_config()
|
||||
-- Max allowed message rate in events per second.
|
||||
config.message_rate = module:get_option_number("rate_limit_message_rate", 3);
|
||||
|
||||
-- A list of jids for which sessions we ignore rate limiting
|
||||
config.whitelist_jids = module:get_option_set("rate_limit_whitelist_jids", {});
|
||||
-- A list of hosts for which sessions we ignore rate limiting
|
||||
config.whitelist_hosts = module:get_option_set("rate_limit_whitelist_hosts", {});
|
||||
|
||||
local wl = "";
|
||||
for ip in config.whitelist do wl = wl .. ip .. "," end
|
||||
local wl_jids = "";
|
||||
for j in config.whitelist_jids do wl_jids = wl_jids .. j .. "," end
|
||||
local wl_hosts = "";
|
||||
for j in config.whitelist_hosts do wl_hosts = wl_hosts .. j .. "," end
|
||||
module:log("info", "Loaded configuration: ");
|
||||
module:log("info", "- session_rate=%s bytes/sec, timeout=%s sec, cache size=%s, whitelist=%s, whitelist_jids=%s",
|
||||
config.session_rate, config.timeout, config.cache_size, wl, wl_jids);
|
||||
module:log("info", "- session_rate=%s bytes/sec, timeout=%s sec, cache size=%s, whitelist=%s, whitelist_hosts=%s",
|
||||
config.session_rate, config.timeout, config.cache_size, wl, wl_hosts);
|
||||
module:log("info", "- login_rate=%s/sec, presence_rate=%s/sec, iq_rate=%s/sec, message_rate=%s/sec",
|
||||
config.login_rate, config.presence_rate, config.iq_rate, config.message_rate);
|
||||
end
|
||||
@@ -70,8 +70,8 @@ local function is_whitelisted(ip)
|
||||
return false;
|
||||
end
|
||||
|
||||
local function is_whitelisted_jid(jid)
|
||||
return config.whitelist_jids:contains(jid);
|
||||
local function is_whitelisted_host(h)
|
||||
return config.whitelist_hosts:contains(h);
|
||||
end
|
||||
|
||||
-- Discover real remote IP of a session
|
||||
@@ -125,10 +125,10 @@ local function throttle_session(session)
|
||||
if not session.jitsi_throttle then
|
||||
if (session.conn and session.conn.setlimit) then
|
||||
-- TODO: we don't have a mechanism to unthrottle a session in this case.
|
||||
module:log("info", "Enabling throttle (%s bytes/s) via setlimit, session=%s, ip=%s.", config.session_rate, session, session.ip);
|
||||
module:log("info", "Enabling throttle (%s bytes/s) via setlimit, session=%s, ip=%s.", config.session_rate, session.id, session.ip);
|
||||
session.conn:setlimit(config.session_rate);
|
||||
else
|
||||
module:log("info", "Enabling throttle (%s bytes/s) via filter, session=%s, ip=%s.", config.session_rate, session, session.ip);
|
||||
module:log("info", "Enabling throttle (%s bytes/s) via filter, session=%s, ip=%s.", config.session_rate, session.id, session.ip);
|
||||
session.jitsi_throttle = new_throttle(config.session_rate, 2);
|
||||
filters.add_filter(session, "bytes/in", limit_bytes_in, 1000);
|
||||
-- throttle.start used for stop throttling after the timeout
|
||||
@@ -179,7 +179,7 @@ local function filter_hook(session)
|
||||
local request = get_request_from_conn(session.conn);
|
||||
local ip = request and request.ip or session.ip;
|
||||
module:log("debug", "New session from %s", ip);
|
||||
if is_whitelisted(ip) or (session.username and is_whitelisted_jid(session.username..'@'..session.host)) then
|
||||
if is_whitelisted(ip) or is_whitelisted_host(session.host) then
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ local jid_bare = require 'util.jid'.bare;
|
||||
|
||||
local DEBUG = false;
|
||||
|
||||
local measure_success = module:measure('success', 'counter');
|
||||
local measure_fail = module:measure('fail', 'counter');
|
||||
|
||||
local function is_admin(jid)
|
||||
return um_is_admin(jid, host);
|
||||
end
|
||||
@@ -36,9 +39,9 @@ if token_util == nil then
|
||||
end
|
||||
|
||||
module:log("debug",
|
||||
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
|
||||
tostring(host), tostring(token_util.appId), tostring(token_util.appSecret),
|
||||
tostring(token_util.allowEmptyToken));
|
||||
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
|
||||
tostring(host), tostring(token_util.appId), tostring(token_util.appSecret),
|
||||
tostring(token_util.allowEmptyToken));
|
||||
|
||||
-- option to disable room modification (sending muc config form) for guest that do not provide token
|
||||
local require_token_for_moderation;
|
||||
@@ -91,16 +94,20 @@ module:hook("muc-room-pre-create", function(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
if DEBUG then module:log("debug", "pre create: %s %s", tostring(origin), tostring(stanza)); end
|
||||
if not verify_user(origin, stanza) then
|
||||
measure_fail(1);
|
||||
return true; -- Returning any value other than nil will halt processing of the event
|
||||
end
|
||||
measure_success(1);
|
||||
end, 99);
|
||||
|
||||
module:hook("muc-occupant-pre-join", function(event)
|
||||
local origin, room, stanza = event.origin, event.room, event.stanza;
|
||||
if DEBUG then module:log("debug", "pre join: %s %s", tostring(room), tostring(stanza)); end
|
||||
if not verify_user(origin, stanza) then
|
||||
measure_fail(1);
|
||||
return true; -- Returning any value other than nil will halt processing of the event
|
||||
end
|
||||
measure_success(1);
|
||||
end, 99);
|
||||
|
||||
for event_name, method in pairs {
|
||||
|
||||
@@ -14,6 +14,7 @@ local ends_with = main_util.ends_with;
|
||||
local http_get_with_retry = main_util.http_get_with_retry;
|
||||
local extract_subdomain = main_util.extract_subdomain;
|
||||
local starts_with = main_util.starts_with;
|
||||
local table_shallow_copy = main_util.table_shallow_copy;
|
||||
local cjson_safe = require 'cjson.safe'
|
||||
local timer = require "util.timer";
|
||||
local async = require "util.async";
|
||||
@@ -119,17 +120,26 @@ function Util.new(module)
|
||||
end
|
||||
|
||||
if self.cacheKeysUrl then
|
||||
self.cachedKeys = {};
|
||||
local update_keys_cache;
|
||||
update_keys_cache = async.runner(function (name)
|
||||
content, code, cache_for = http_get_with_retry(self.cacheKeysUrl, nr_retries);
|
||||
if content ~= nil then
|
||||
self.cachedKeys = {};
|
||||
local keys_to_delete = table_shallow_copy(self.cachedKeys);
|
||||
-- Let's convert any certificate to public key
|
||||
for k, v in pairs(cjson_safe.decode(content)) do
|
||||
if starts_with(v, '-----BEGIN CERTIFICATE-----') then
|
||||
self.cachedKeys[k] = ssl.loadcertificate(v):pubkey();
|
||||
-- do not clean this key if it already exists
|
||||
keys_to_delete[k] = nil;
|
||||
end
|
||||
end
|
||||
-- let's schedule the clean in an hour and a half, current tokens will be valid for an hour
|
||||
timer.add_task(90*60, function ()
|
||||
for k, _ in pairs(keys_to_delete) do
|
||||
self.cachedKeys[k] = nil;
|
||||
end
|
||||
end);
|
||||
|
||||
if cache_for then
|
||||
cache_for = tonumber(cache_for);
|
||||
@@ -140,8 +150,18 @@ function Util.new(module)
|
||||
timer.add_task(cache_for, function ()
|
||||
update_keys_cache:run("update_keys_cache");
|
||||
end);
|
||||
else
|
||||
-- no cache header let's consider updating in 6hours
|
||||
timer.add_task(6*60*60, function ()
|
||||
update_keys_cache:run("update_keys_cache");
|
||||
end);
|
||||
end
|
||||
|
||||
else
|
||||
module:log('warn', 'Failed to retrieve cached public keys code:%s', code);
|
||||
-- failed let's retry in 30 seconds
|
||||
timer.add_task(30, function ()
|
||||
update_keys_cache:run("update_keys_cache");
|
||||
end);
|
||||
end
|
||||
end);
|
||||
update_keys_cache:run("update_keys_cache");
|
||||
@@ -175,19 +195,25 @@ end
|
||||
-- @return the public key (the content of requested resource) or nil
|
||||
function Util:get_public_key(keyId)
|
||||
local content = self.cache:get(keyId);
|
||||
local code;
|
||||
if content == nil then
|
||||
-- If the key is not found in the cache.
|
||||
module:log("debug", "Cache miss for key: %s", keyId);
|
||||
-- module:log("debug", "Cache miss for key: %s", keyId);
|
||||
local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
|
||||
module:log("debug", "Fetching public key from: %s", keyurl);
|
||||
content = http_get_with_retry(keyurl, nr_retries);
|
||||
-- module:log("debug", "Fetching public key from: %s", keyurl);
|
||||
content, code = http_get_with_retry(keyurl, nr_retries);
|
||||
if content ~= nil then
|
||||
self.cache:set(keyId, content);
|
||||
else
|
||||
if code == nil then
|
||||
-- this is timout after nr_retries retries
|
||||
module:log('warn', 'Timeout retrieving %s from %s', keyId, keyurl);
|
||||
end
|
||||
end
|
||||
return content;
|
||||
else
|
||||
-- If the key is in the cache, use it.
|
||||
module:log("debug", "Cache hit for key: %s", keyId);
|
||||
-- module:log("debug", "Cache hit for key: %s", keyId);
|
||||
return content;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -509,7 +509,13 @@ function process_host_module(name, callback)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function table_shallow_copy(t)
|
||||
local t2 = {}
|
||||
for k, v in pairs(t) do
|
||||
t2[k] = v
|
||||
end
|
||||
return t2
|
||||
end
|
||||
|
||||
return {
|
||||
OUTBOUND_SIP_JIBRI_PREFIX = OUTBOUND_SIP_JIBRI_PREFIX;
|
||||
@@ -534,4 +540,5 @@ return {
|
||||
http_get_with_retry = http_get_with_retry;
|
||||
ends_with = ends_with;
|
||||
starts_with = starts_with;
|
||||
table_shallow_copy = table_shallow_copy;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user