mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-25 08:00:18 +00:00
Compare commits
45 Commits
saghul-pat
...
dockerisms
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
505ed15323 | ||
|
|
8758c222c6 | ||
|
|
29dc63fbcb | ||
|
|
475a2ae596 | ||
|
|
338c960215 | ||
|
|
e6093e0706 | ||
|
|
d1d968997e | ||
|
|
45570bc0e7 | ||
|
|
f4bcad02d8 | ||
|
|
26f7951894 | ||
|
|
35dabb1a27 | ||
|
|
c3b79802b2 | ||
|
|
e6dbe65193 | ||
|
|
ff23f81dfe | ||
|
|
bc66c9063a | ||
|
|
974ef4a382 | ||
|
|
3bf82b573c | ||
|
|
b4b4339a1a | ||
|
|
6773aed67f | ||
|
|
d740752522 | ||
|
|
d93b219c7f | ||
|
|
10cd150a07 | ||
|
|
a31f3c0c76 | ||
|
|
af39186a5f | ||
|
|
d4d1d0aa70 | ||
|
|
3a88f4939c | ||
|
|
fe221fe4be | ||
|
|
1caaa47f5e | ||
|
|
a2c4d17e4d | ||
|
|
ce1de9e1e7 | ||
|
|
3e7abf3da0 | ||
|
|
8b4f1789a6 | ||
|
|
444e2b90df | ||
|
|
7de88995a5 | ||
|
|
f0c6e934ce | ||
|
|
78b01d2c97 | ||
|
|
bf60be1654 | ||
|
|
5202a7e5b8 | ||
|
|
2af0c0ba17 | ||
|
|
fbb6486b5f | ||
|
|
a113151563 | ||
|
|
470fda3467 | ||
|
|
edea6316ab | ||
|
|
adac9ee5f8 | ||
|
|
af8bd876e6 |
@@ -21,4 +21,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=20.3.0
|
||||
sdkVersion=2.8.2
|
||||
sdkVersion=2.9.0
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
@@ -167,13 +166,7 @@ public class JitsiMeetActivityDelegate {
|
||||
}
|
||||
|
||||
public static void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
final int requestCode, final String[] permissions, final int[] grantResults) {
|
||||
permissionsCallback = new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
getDefaultDeviceId,
|
||||
notifyCameraError,
|
||||
notifyMicError,
|
||||
setAudioOutputDeviceId,
|
||||
@@ -101,7 +102,10 @@ import {
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
} from './react/features/base/tracks';
|
||||
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
||||
import {
|
||||
getBackendSafePath,
|
||||
getJitsiMeetGlobalNS
|
||||
} from './react/features/base/util';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import { setE2EEKey } from './react/features/e2ee';
|
||||
@@ -292,12 +296,6 @@ class ConferenceConnector {
|
||||
logger.error('CONFERENCE FAILED:', err, ...params);
|
||||
|
||||
switch (err) {
|
||||
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
||||
const [ msg ] = params;
|
||||
|
||||
APP.UI.notifyConnectionFailed(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
|
||||
// let's show some auth not allowed page
|
||||
@@ -332,14 +330,6 @@ class ConferenceConnector {
|
||||
APP.UI.notifyGracefulShutdown();
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = params;
|
||||
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyConferenceDestroyed(reason);
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME FOCUS_DISCONNECTED is a confusing event name.
|
||||
// What really happens there is that the library is not ready yet,
|
||||
// because Jicofo is not available, but it is going to give it another
|
||||
@@ -1258,7 +1248,7 @@ export default {
|
||||
items[key] = param[1];
|
||||
}
|
||||
|
||||
if (typeof items.e2eekey !== undefined) {
|
||||
if (typeof items.e2eekey !== 'undefined') {
|
||||
APP.store.dispatch(setE2EEKey(items.e2eekey));
|
||||
|
||||
// Clean URL in browser history.
|
||||
@@ -1364,7 +1354,13 @@ export default {
|
||||
const options = config;
|
||||
const { email, name: nick } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
const { locationURL } = APP.store.getState()['features/base/connection'];
|
||||
const state = APP.store.getState();
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { tenant } = state['features/base/jwt'];
|
||||
|
||||
if (tenant) {
|
||||
options.siteID = tenant;
|
||||
}
|
||||
|
||||
if (options.enableDisplayNameInStats && nick) {
|
||||
options.statisticsDisplayName = nick;
|
||||
@@ -1376,7 +1372,7 @@ export default {
|
||||
|
||||
options.applicationName = interfaceConfig.APP_NAME;
|
||||
options.getWiFiStatsMethod = this._getWiFiStatsMethod;
|
||||
options.confID = `${locationURL.host}${locationURL.pathname}`;
|
||||
options.confID = `${locationURL.host}${getBackendSafePath(locationURL.pathname)}`;
|
||||
options.createVADProcessor = createRnnoiseProcessorPromise;
|
||||
|
||||
// Disable CallStats, if requessted.
|
||||
@@ -2425,11 +2421,20 @@ export default {
|
||||
micDeviceId => {
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
|
||||
// When the 'default' mic needs to be selected, we need to
|
||||
// pass the real device id to gUM instead of 'default' in order
|
||||
// to get the correct MediaStreamTrack from chrome because of the
|
||||
// following bug.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
|
||||
const hasDefaultMicChanged = micDeviceId === 'default';
|
||||
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
cameraDeviceId: null,
|
||||
micDeviceId
|
||||
micDeviceId: hasDefaultMicChanged
|
||||
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
|
||||
: micDeviceId
|
||||
})
|
||||
.then(([ stream ]) => {
|
||||
// if audio was muted before changing the device, mute
|
||||
@@ -2453,6 +2458,12 @@ export default {
|
||||
return this.useAudioStream(stream);
|
||||
})
|
||||
.then(() => {
|
||||
if (hasDefaultMicChanged) {
|
||||
// workaround for the default device to be shown as selected in the
|
||||
// settings even when the real device id was passed to gUM because of the
|
||||
// above mentioned chrome bug.
|
||||
this.localAudio._realDeviceId = this.localAudio.deviceId = 'default';
|
||||
}
|
||||
logger.log(`switched local audio device: ${this.localAudio?.getDeviceId()}`);
|
||||
|
||||
this._updateAudioDeviceId();
|
||||
@@ -2754,11 +2765,20 @@ export default {
|
||||
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
|
||||
}
|
||||
|
||||
// When the 'default' mic needs to be selected, we need to
|
||||
// pass the real device id to gUM instead of 'default' in order
|
||||
// to get the correct MediaStreamTrack from chrome because of the
|
||||
// following bug.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
|
||||
const hasDefaultMicChanged = newDevices.audioinput === 'default';
|
||||
|
||||
promises.push(
|
||||
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
|
||||
createLocalTracksF,
|
||||
newDevices.videoinput,
|
||||
newDevices.audioinput)
|
||||
hasDefaultMicChanged
|
||||
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
|
||||
: newDevices.audioinput)
|
||||
.then(tracks => {
|
||||
// If audio or video muted before, or we unplugged current
|
||||
// device and selected new one, then mute new track.
|
||||
@@ -2783,6 +2803,12 @@ export default {
|
||||
// Use the new stream or null if we failed to obtain it.
|
||||
return useStream(tracks.find(track => track.getType() === mediaType) || null)
|
||||
.then(() => {
|
||||
if (hasDefaultMicChanged) {
|
||||
// workaround for the default device to be shown as selected in the
|
||||
// settings even when the real device id was passed to gUM because of
|
||||
// the above mentioned chrome bug.
|
||||
this.localAudio._realDeviceId = this.localAudio.deviceId = 'default';
|
||||
}
|
||||
mediaType === 'audio'
|
||||
? this._updateAudioDeviceId()
|
||||
: this._updateVideoDeviceId();
|
||||
|
||||
43
config.js
43
config.js
@@ -54,6 +54,13 @@ var config = {
|
||||
// Disables the auto-play behavior of *all* newly created video element.
|
||||
// This is useful when the client runs on a host with limited resources.
|
||||
// noAutoPlayVideo: false
|
||||
|
||||
// Enable / disable 500 Kbps bitrate cap on desktop tracks. When enabled,
|
||||
// simulcast is turned off for the desktop share. If presenter is turned
|
||||
// on while screensharing is in progress, the max bitrate is automatically
|
||||
// adjusted to 2.5 Mbps. This takes a value between 0 and 1 which determines
|
||||
// the probability for this to be enabled.
|
||||
// capScreenshareBitrate: 1 // 0 to disable
|
||||
},
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
@@ -210,6 +217,21 @@ var config = {
|
||||
// Default value for the channel "last N" attribute. -1 for unlimited.
|
||||
channelLastN: -1,
|
||||
|
||||
// // Options for the recording limit notification.
|
||||
// recordingLimit: {
|
||||
//
|
||||
// // The recording limit in minutes. Note: This number appears in the notification text
|
||||
// // but doesn't enforce the actual recording time limit. This should be configured in
|
||||
// // jibri!
|
||||
// limit: 60,
|
||||
//
|
||||
// // The name of the app with unlimited recordings.
|
||||
// appName: 'Unlimited recordings APP',
|
||||
//
|
||||
// // The URL of the app with unlimited recordings.
|
||||
// appURL: 'https://unlimited.recordings.app.com/'
|
||||
// },
|
||||
|
||||
// Disables or enables RTX (RFC 4588) (defaults to false).
|
||||
// disableRtx: false,
|
||||
|
||||
@@ -352,7 +374,7 @@ var config = {
|
||||
|
||||
// { urls: 'stun:jitsi-meet.example.com:4446' },
|
||||
{ urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' }
|
||||
],
|
||||
]
|
||||
|
||||
// Sets the ICE transport policy for the p2p connection. At the time
|
||||
// of this writing the list of possible values are 'all' and 'relay',
|
||||
@@ -364,7 +386,7 @@ var config = {
|
||||
|
||||
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
|
||||
// is supported).
|
||||
preferH264: true
|
||||
// preferH264: true
|
||||
|
||||
// If set to true, disable H.264 video codec by stripping it out of the
|
||||
// SDP.
|
||||
@@ -490,6 +512,23 @@ var config = {
|
||||
// If set to true all muting operations of remote participants will be disabled.
|
||||
// disableRemoteMute: true,
|
||||
|
||||
/**
|
||||
External API url used to receive branding specific information.
|
||||
If there is no url set or there are missing fields, the defaults are applied.
|
||||
None of the fieds are mandatory and the response must have the shape:
|
||||
{
|
||||
// The hex value for the colour used as background
|
||||
backgroundColor: '#fff',
|
||||
// The url for the image used as background
|
||||
backgroundImageUrl: 'https://example.com/background-img.png',
|
||||
// The anchor url used when clicking the logo image
|
||||
logoClickUrl: 'https://example-company.org',
|
||||
// The url used for the image used as logo
|
||||
logoImageUrl: 'https://example.com/logo-img.png'
|
||||
}
|
||||
*/
|
||||
// brandingDataUrl: '',
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.audio-preview {
|
||||
&-content {
|
||||
background: #2A3A4B;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
max-height: 456px;
|
||||
@@ -32,7 +33,7 @@
|
||||
margin-left: 48px;
|
||||
|
||||
&--selected {
|
||||
background: rgba(28,32,37,0.5);
|
||||
background: #1C2025;
|
||||
cursor: initial;
|
||||
margin-left: 0;
|
||||
padding-left: 21px;
|
||||
@@ -55,7 +56,7 @@
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: rgba(255,255,255, 0.2);
|
||||
background: #3F4E5E;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
@@ -80,8 +81,23 @@
|
||||
|
||||
&-microphone {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #3F4E5E;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
padding-left: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 196px;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -115,8 +115,9 @@ form {
|
||||
.leftwatermark {
|
||||
left: 32px;
|
||||
top: 32px;
|
||||
background-image: url($defaultWatermarkLink);
|
||||
background-position: center left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
|
||||
140
css/_lobby.scss
Normal file
140
css/_lobby.scss
Normal file
@@ -0,0 +1,140 @@
|
||||
#lobby-screen {
|
||||
.content {
|
||||
|
||||
.container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.spinner {
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.joining-message {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.participant-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#lobby-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#knocking-participant-list {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 1px solid rgba(255, 255, 255, .4);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0;
|
||||
margin: 20px;
|
||||
position: fixed;
|
||||
top: 20;
|
||||
transition: top 1s ease;
|
||||
z-index: 100;
|
||||
|
||||
&.toolbox-visible {
|
||||
// Same as toolbox subject position
|
||||
top: 120px;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: rgba(0, 0, 0, .2);
|
||||
font-size: 1.2em;
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0 15px 15px 15px;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 8px 0;
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
margin: 0 30px 0 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
align-self: unset;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
border: 1px solid #B8C7E0;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
padding: 12px 8px;
|
||||
|
||||
&:focus {
|
||||
border-color: rgb(3, 118, 218);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
align-self: stretch;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
transition: .2s transform ease;
|
||||
|
||||
&:disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
|
||||
&:disabled {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.borderLess {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: rgb(3, 118, 218);
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,4 @@
|
||||
.prejoin {
|
||||
&-full-page {
|
||||
background: #1C2025;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: $toolbarZ + 1;
|
||||
}
|
||||
|
||||
&-input-area-container {
|
||||
position: absolute;
|
||||
bottom: 48px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&-input-area {
|
||||
margin: 0 auto;
|
||||
@@ -27,65 +13,6 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&--primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background: #2A3A4B;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&--text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.prejoin-btn-icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.prejoin-btn-options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-btn-options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&-text-btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -179,25 +106,6 @@
|
||||
margin: 200px auto 0 auto;
|
||||
}
|
||||
|
||||
&-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
right: -8px;
|
||||
|
||||
&--hovered {
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-overlay {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
@@ -217,22 +125,20 @@
|
||||
|
||||
&-status {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
align-self: stretch;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
min-height: 24px;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&--warning {
|
||||
background: rgba(241, 173, 51, 0.5)
|
||||
background: rgba(241, 173, 51, 0.7)
|
||||
}
|
||||
&--ok {
|
||||
background: rgba(49, 183, 106, 0.5);
|
||||
background: rgba(49, 183, 106, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,63 +197,3 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.prejoin-copy {
|
||||
&-meeting {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-url {
|
||||
max-width: 278px;
|
||||
padding: 8px 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-badge {
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
line-height: 38px;
|
||||
position: absolute;
|
||||
padding-left: 10px;
|
||||
text-align: left;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
&--hover {
|
||||
background: #1C2025;
|
||||
}
|
||||
|
||||
&--done {
|
||||
background: #31B76A;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
|
||||
&--white {
|
||||
&> svg > path {
|
||||
fill: #fff
|
||||
}
|
||||
}
|
||||
|
||||
&--light {
|
||||
&> svg > path {
|
||||
fill: #D1DBE8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-textarea {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
}
|
||||
|
||||
194
css/_premeeting-screens.scss
Normal file
194
css/_premeeting-screens.scss
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Shared style for full screen local track based dialogs/modals.
|
||||
*/
|
||||
.premeeting-screen {
|
||||
align-items: stretch;
|
||||
background: #1C2025;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.3em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: $toolbarZ + 1;
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
background-image: linear-gradient(transparent, black);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
z-index: $toolbarZ + 2;
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.copy-meeting {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
justify-content: center;
|
||||
line-height: 24px;
|
||||
|
||||
.url {
|
||||
display: flex;
|
||||
padding: 8px 10px;
|
||||
|
||||
&:hover {
|
||||
background: #1C2025;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: #31B76A;
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border-width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input.field {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
outline-width: 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
&.focused {
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
&.error::placeholder {
|
||||
color: $defaultWarningColor;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin: 10px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&.primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&.text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 32px 0;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
right: -8px;
|
||||
|
||||
&--hovered {
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#preview {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
&.no-video {
|
||||
background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
background: #A4B8D1;
|
||||
margin: 200px auto 0 auto;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,6 @@ $sidebarWidth: 375px;
|
||||
* Misc.
|
||||
*/
|
||||
$borderRadius: 4px;
|
||||
$defaultWatermarkLink: '../images/watermark.png';
|
||||
$popoverMenuPadding: 13px;
|
||||
$happySoftwareBackground: transparent;
|
||||
$desktopAppDragBarHeight: 25px;
|
||||
@@ -270,4 +269,3 @@ $chromeExtensionBannerTop: 80px;
|
||||
$chromeExtensionBannerRight: 16px;
|
||||
$chromeExtensionBannerTopInMeeting: 10px;
|
||||
$chromeExtensionBannerRightInMeeeting: 10px;
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
#remotePresenceMessage {
|
||||
display: none !important;
|
||||
}
|
||||
#largeVideoContainer {
|
||||
background-color: $defaultBackground !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thumbnail popover menus can overlap other thumbnails. Setting an auto
|
||||
|
||||
@@ -76,6 +76,7 @@ $flagsImagePath: "../images/";
|
||||
@import 'filmstrip/vertical_filmstrip';
|
||||
@import 'filmstrip/vertical_filmstrip_overrides';
|
||||
@import 'labels';
|
||||
@import 'lobby';
|
||||
@import 'unsupported-browser/main';
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'deep-linking/main';
|
||||
@@ -96,5 +97,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'country-picker';
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'premeeting-screens';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -3,25 +3,47 @@
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
|
||||
&.password {
|
||||
|
||||
&.password-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&-actions {
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
color: #6FB1EA;
|
||||
}
|
||||
.password {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
& > :first-child:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
&-actions {
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
color: #6FB1EA;
|
||||
}
|
||||
|
||||
&>a+a {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&> :first-child:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
margin: 24px 0 24px -20px;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #5E6D7A;
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +56,4 @@
|
||||
background: rgba(241, 173, 51, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,12 @@ VirtualHost "jitmeet.example.com"
|
||||
"speakerstats";
|
||||
"turncredentials";
|
||||
"conference_duration";
|
||||
"muc_lobby_rooms";
|
||||
}
|
||||
c2s_require_encryption = false
|
||||
lobby_muc = "lobby.jitmeet.example.com"
|
||||
main_muc = "conference.jitmeet.example.com"
|
||||
-- muc_lobby_whitelist = { "recorder.jitmeet.example.com" } -- Here we can whitelist jibri to enter lobby enabled rooms
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
storage = "memory"
|
||||
@@ -81,3 +85,9 @@ Component "speakerstats.jitmeet.example.com" "speakerstats_component"
|
||||
|
||||
Component "conferenceduration.jitmeet.example.com" "conference_duration_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
Component "lobby.jitmeet.example.com" "muc"
|
||||
storage = "memory"
|
||||
restrict_room_creation = true
|
||||
muc_room_locking = false
|
||||
muc_room_default_public_jids = true
|
||||
|
||||
46
docker/Dockerfile
Normal file
46
docker/Dockerfile
Normal file
@@ -0,0 +1,46 @@
|
||||
ARG JITSI_REPO=jitsi
|
||||
ARG JITSI_GIT_REPO=https://github.com/jitsi/jitsi-meet.git
|
||||
ARG JITSI_GIT_REF=HEAD
|
||||
|
||||
FROM node:12 as builder
|
||||
|
||||
ARG JITSI_GIT_REPO
|
||||
ARG JITSI_GIT_REF
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN \
|
||||
git clone $JITSI_GIT_REPO && \
|
||||
cd jitsi-meet && \
|
||||
git reset --hard $JITSI_GIT_REF && \
|
||||
npm install && \
|
||||
make
|
||||
|
||||
FROM ${JITSI_REPO}/base
|
||||
|
||||
RUN \
|
||||
apt-dpkg-wrap apt-get update && \
|
||||
apt-dpkg-wrap apt-get install -y nginx-extras && \
|
||||
apt-cleanup && \
|
||||
rm -f /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY rootfs/ /
|
||||
COPY --from=builder /src/jitsi-meet/libs /usr/share/jitsi-meet/libs
|
||||
COPY --from=builder /src/jitsi-meet/static /usr/share/jitsi-meet/static
|
||||
COPY --from=builder /src/jitsi-meet/sounds /usr/share/jitsi-meet/sounds
|
||||
COPY --from=builder /src/jitsi-meet/fonts /usr/share/jitsi-meet/fonts
|
||||
COPY --from=builder /src/jitsi-meet/images /usr/share/jitsi-meet/images
|
||||
COPY --from=builder /src/jitsi-meet/lang /usr/share/jitsi-meet/lang
|
||||
COPY --from=builder /src/jitsi-meet/connection_optimization /usr/share/jitsi-meet/connection_optimization
|
||||
COPY --from=builder /src/jitsi-meet/css/all.css /usr/share/jitsi-meet/css/
|
||||
COPY --from=builder /src/jitsi-meet/resources/*.sh /usr/share/jitsi-meet/scripts/
|
||||
COPY --from=builder /src/jitsi-meet/*.html /usr/share/jitsi-meet/
|
||||
COPY --from=builder /src/jitsi-meet/*.ico /usr/share/jitsi-meet/
|
||||
COPY --from=builder /src/jitsi-meet/resources/robots.txt /usr/share/jitsi-meet/
|
||||
|
||||
ENV XMPP_BOSH_URL_BASE=https://meet.jit.si
|
||||
ENV XMPP_DOMAIN=meet.jit.si
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
VOLUME ["/config"]
|
||||
46
docker/rootfs/defaults/meet.conf
Normal file
46
docker/rootfs/defaults/meet.conf
Normal file
@@ -0,0 +1,46 @@
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
root /usr/share/jitsi-meet;
|
||||
|
||||
# ssi on with javascript for multidomain variables in config.js
|
||||
ssi on;
|
||||
ssi_types application/x-javascript application/javascript;
|
||||
|
||||
index index.html index.htm;
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
location = /config.js {
|
||||
alias /config/config.js;
|
||||
}
|
||||
|
||||
location = /interface_config.js {
|
||||
alias /config/interface_config.js;
|
||||
}
|
||||
|
||||
location = /external_api.js {
|
||||
alias /usr/share/jitsi-meet/libs/external_api.min.js;
|
||||
}
|
||||
|
||||
# ensure all static content can always be found first
|
||||
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
}
|
||||
|
||||
# BOSH
|
||||
location = /http-bind {
|
||||
proxy_pass {{ .Env.XMPP_BOSH_URL_BASE }}/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host {{ .Env.XMPP_DOMAIN }};
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
location @root_path {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
60
docker/rootfs/defaults/nginx.conf
Normal file
60
docker/rootfs/defaults/nginx.conf
Normal file
@@ -0,0 +1,60 @@
|
||||
user www-data;
|
||||
worker_processes 4;
|
||||
pid /run/nginx.pid;
|
||||
daemon off;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
# multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
##
|
||||
# Basic Settings
|
||||
##
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
server_tokens off;
|
||||
|
||||
# server_names_hash_bucket_size 64;
|
||||
# server_name_in_redirect off;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
types {
|
||||
# add support for wasm MIME type, that is required by specification and it is not part of default mime.types file
|
||||
application/wasm wasm;
|
||||
}
|
||||
default_type application/octet-stream;
|
||||
|
||||
##
|
||||
# Logging Settings
|
||||
##
|
||||
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stderr;
|
||||
|
||||
##
|
||||
# Gzip Settings
|
||||
##
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json;
|
||||
gzip_vary on;
|
||||
gzip_min_length 860;
|
||||
|
||||
##
|
||||
## The Sever
|
||||
##
|
||||
server {
|
||||
listen 8000 default_server;
|
||||
include /config/nginx/meet.conf;
|
||||
}
|
||||
}
|
||||
12
docker/rootfs/etc/cont-init.d/10-config
Normal file
12
docker/rootfs/etc/cont-init.d/10-config
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# make our folders
|
||||
mkdir -p \
|
||||
/config/nginx \
|
||||
/run \
|
||||
/var/lib/nginx/tmp/client_body \
|
||||
/var/tmp/nginx
|
||||
|
||||
# copy config files
|
||||
cp /defaults/nginx.conf /config/nginx/nginx.conf
|
||||
tpl /defaults/meet.conf > /config/nginx/meet.conf
|
||||
3
docker/rootfs/etc/services.d/nginx/run
Normal file
3
docker/rootfs/etc/services.d/nginx/run
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
exec nginx -c /config/nginx/nginx.conf
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable no-unused-vars, no-var, max-len */
|
||||
|
||||
var interfaceConfig = {
|
||||
// TO FIX: this needs to be handled from SASS variables. There are some
|
||||
// methods allowing to use variables both in css and js.
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
DEFAULT_LOGO_URL: '../images/watermark.png',
|
||||
|
||||
/**
|
||||
* Whether or not the blurred video background for large video should be
|
||||
|
||||
@@ -287,7 +287,7 @@ PODS:
|
||||
- React-jsinspector (0.61.5-jitsi.1)
|
||||
- react-native-background-timer (2.1.1):
|
||||
- React
|
||||
- react-native-calendar-events (1.7.3):
|
||||
- react-native-calendar-events (2.0.0):
|
||||
- React
|
||||
- react-native-keep-awake (4.0.0):
|
||||
- React
|
||||
@@ -365,7 +365,7 @@ PODS:
|
||||
- React
|
||||
- RNSVG (9.7.1):
|
||||
- React
|
||||
- RNWatch (0.2.0):
|
||||
- RNWatch (0.4.3):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
|
||||
@@ -566,7 +566,7 @@ SPEC CHECKSUMS:
|
||||
React-jsiexecutor: de1c37cf59ae9adcbf2be82eea0e090dc3f3205e
|
||||
React-jsinspector: b76c4e84a7833bb4c90549d59ed53ec299ff912b
|
||||
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
|
||||
react-native-calendar-events: 2fe35a9294af05de0ed819d3a1b5dac048d2c010
|
||||
react-native-calendar-events: 1442fad71a00388f933cfa25512588fec300fcf8
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-webrtc: 86d841823e66d68cc1f86712db1c2956056bf0c2
|
||||
@@ -586,7 +586,7 @@ SPEC CHECKSUMS:
|
||||
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
|
||||
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
|
||||
RNSVG: aac12785382e8fd4f28d072fe640612e34914631
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
|
||||
Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f
|
||||
|
||||
PODFILE CHECKSUM: 082858daebbe170e7a490de433e7f2a99e0c3701
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.8.1</string>
|
||||
<string>2.9.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
38
lang/languages-mr.json
Normal file
38
lang/languages-mr.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"en": "English",
|
||||
"af": "Afrikaans",
|
||||
"az": "",
|
||||
"bg": "Bulgarian",
|
||||
"cs": "Czech",
|
||||
"de": "German",
|
||||
"el": "Greek",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"hy": "Armenian",
|
||||
"it": "Italian",
|
||||
"ja": "Japanese",
|
||||
"ko": "Korean",
|
||||
"nb": "",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polish",
|
||||
"ptBR": "Portuguese (Brazil)",
|
||||
"ru": "Russian",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "Swedish",
|
||||
"tr": "Turkish",
|
||||
"vi": "Vietnamese",
|
||||
"zhCN": "Chinese (China)",
|
||||
"zhTW": "Chinese (Taiwan)",
|
||||
"nl": "Dutch",
|
||||
"hu": "Hungarian",
|
||||
"hr": "Croatian",
|
||||
"frCA": "French (Canadian)",
|
||||
"fi": "Finnish",
|
||||
"et": "Estonian",
|
||||
"esUS": "Spanish (Latin America)",
|
||||
"enGB": "English (United Kingdom)",
|
||||
"da": "Danish",
|
||||
"ca": "Catalan"
|
||||
}
|
||||
@@ -1,38 +1,45 @@
|
||||
{
|
||||
"en": "Engleză",
|
||||
"af": "Afrikaans",
|
||||
"az": "",
|
||||
"ar": "Arabă",
|
||||
"bg": "Bulgară",
|
||||
"ca": "Catalană",
|
||||
"cs": "Cehă",
|
||||
"da": "Daneză",
|
||||
"de": "Germană",
|
||||
"el": "Greacă",
|
||||
"enGB": "Engleză (Regatul Unit)",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spaniolă",
|
||||
"esUS": "Spaniolă (America Latină)",
|
||||
"et": "Estonă",
|
||||
"eu": "Bască",
|
||||
"fi": "Finlandeză",
|
||||
"fr": "Franceză",
|
||||
"hy": "Armeniana",
|
||||
"frCA": "Franceză (Canada)",
|
||||
"he": "Ebraică",
|
||||
"hr": "Croată",
|
||||
"hu": "Maghiară",
|
||||
"hy": "Armeană",
|
||||
"id": "Indoneziană",
|
||||
"it": "Italiană",
|
||||
"ja": "Japoneză",
|
||||
"ko": "Koreană",
|
||||
"nb": "",
|
||||
"oc": "Occitan",
|
||||
"lt": "Lituaniană",
|
||||
"nl": "Olandeză",
|
||||
"oc": "Occitană",
|
||||
"pl": "Poloneză",
|
||||
"ptBR": "Portugheză (Brazilia)",
|
||||
"ru": "Rusă",
|
||||
"ro": "Română",
|
||||
"sc": "Sardă",
|
||||
"sk": "Slovacă",
|
||||
"sl": "Slovenă",
|
||||
"sv": "Suedeză",
|
||||
"th": "Thailandeză",
|
||||
"tr": "Turcă",
|
||||
"uk": "Ucraineană",
|
||||
"vi": "Vietnameză",
|
||||
"zhCN": "Chineză (China)",
|
||||
"zhTW": "Chineză (Taiwan)",
|
||||
"nl": "Olandeză",
|
||||
"hu": "Maghiară",
|
||||
"hr": "Croată",
|
||||
"frCA": "Franceză (Canadia)",
|
||||
"fi": "Finlandeză",
|
||||
"et": "Estoniană",
|
||||
"esUS": "Spaniolă (America Latină)",
|
||||
"enGB": "Engleză (Regatul Unit)",
|
||||
"da": "Daneză",
|
||||
"că": "Catalană"
|
||||
"zhTW": "Chineză (Taiwan)"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"fr": "French",
|
||||
"frCA": "French (Canadian)",
|
||||
"he": "Hebrew",
|
||||
"mr":"Marathi",
|
||||
"hr": "Croatian",
|
||||
"hu": "Hungarian",
|
||||
"hy": "Armenian",
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "Einladen",
|
||||
"addContacts": "Laden Sie Ihre Kontakte ein",
|
||||
"copyInvite": "Sitzungseinladung kopieren",
|
||||
"copyLink": "Meeting-Link kopieren",
|
||||
"copyStream": "Live-Streaming-Link kopieren",
|
||||
"countryNotSupported": "Wir unterstützen dieses Land noch nicht.",
|
||||
"countryReminder": "Telefonnummer nicht in den USA? Bitte sicherstellen, dass die Telefonnummer mit dem Ländercode beginnt.",
|
||||
"defaultEmail": "Ihre Standard-E-Mail",
|
||||
"disabled": "Sie können keine Teilnehmer einladen.",
|
||||
"failedToAdd": "Fehler beim Hinzufügen von Teilnehmern",
|
||||
"footerText": "Abgehender Ruf ist deaktiviert.",
|
||||
"inviteMoreHeader": "Sie sind alleine in der Sitzung",
|
||||
"inviteMoreMailSubject": "An {{appName}} Meeting teilnehmen",
|
||||
"inviteMorePrompt": "Mehr Leute einladen",
|
||||
"linkCopied": "Link in die Zwischenablage kopiert",
|
||||
"loading": "Suche nach Teilnehmern und Telefonnummern",
|
||||
"loadingNumber": "Telefonnummer wird überprüft",
|
||||
"loadingPeople": "Suche nach einzuladenden Teilnehmern",
|
||||
@@ -14,6 +23,9 @@
|
||||
"searchNumbers": "Telefonnummern hinzufügen",
|
||||
"searchPeople": "Nach Teilnehmern suchen",
|
||||
"searchPeopleAndNumbers": "Nach Teilnehmen suchen oder deren Telefonnummern hinzufügen",
|
||||
"shareInvite": "Einladung zur Versammlung teilen",
|
||||
"shareLink": "Teilen Sie den Meeting-Link, um andere einzuladen",
|
||||
"shareStream": "Den Live-Streaming-Link freigeben",
|
||||
"telephone": "Telefon: {{number}}",
|
||||
"title": "Teilnehmer zu dieser Konferenz einladen"
|
||||
},
|
||||
@@ -518,6 +530,11 @@
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Ziehen, um zu aktualisieren"
|
||||
},
|
||||
"security": {
|
||||
"about": "Sie können einen Passwort zu Ihrer Sitzung hinzufügen. Die Teilnehmer müssen dieses ebenfalls eingeben, bevor sie an der Sitzung teilnehmen dürfen",
|
||||
"insecureRoomNameWarning": "Der Raumname ist unsicher. Unerwünschte Teilnehmer könnten Ihrer Konferenz beitreten",
|
||||
"securityOptions": "Sicherheitsoptionen"
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "Die Kalenderintegration von {{appName}} wird verwendet, um ein sicheres Zugreifen auf Ihren Kalender und Auslesen der bevorstehenden Termine zu ermöglichen.",
|
||||
@@ -612,6 +629,7 @@
|
||||
"raiseHand": "„Melden“ ein-/ausschalten",
|
||||
"recording": "Aufzeichnung ein-/ausschalten",
|
||||
"remoteMute": "Teilnehmer stummschalten",
|
||||
"security": "Sicherheitsoptionen",
|
||||
"Settings": "Einstellungen ein-/ausschalten",
|
||||
"sharedvideo": "YouTube-Videofreigabe ein-/ausschalten",
|
||||
"shareRoom": "Person einladen",
|
||||
@@ -663,6 +681,7 @@
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben",
|
||||
"raiseYourHand": "Melden",
|
||||
"security": "Sicherheitsoptionen",
|
||||
"Settings": "Einstellungen",
|
||||
"sharedvideo": "YouTube-Video teilen",
|
||||
"shareRoom": "Person einladen",
|
||||
|
||||
@@ -679,7 +679,6 @@
|
||||
"videomute": "Démarrer / Arrêter la caméra",
|
||||
"startvideoblur": "Flouter mon arrière plan",
|
||||
"stopvideoblur": "Désactiver le flou d'arrière-plan",
|
||||
"muteEveryone": "Mettre tout le monde en sourdine",
|
||||
"noAudioSignalDialInDesc": "Vous pouvez également composer un numéro en utilisant :",
|
||||
"noAudioSignalDialInLinkDesc": "Numéros d'appel"
|
||||
},
|
||||
|
||||
817
lang/main-mr.json
Normal file
817
lang/main-mr.json
Normal file
@@ -0,0 +1,817 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "आमंत्रित करा",
|
||||
"countryNotSupported": "आम्ही अद्याप या गंतव्याचे समर्थन करत नाही.",
|
||||
"countryReminder": "यूएस बाहेर कॉल करीत आहे? कृपया आपण देशाच्या कोडसह प्रारंभ केल्याचे सुनिश्चित करा!",
|
||||
"disabled": "आपण लोकांना आमंत्रित करू शकत नाही.",
|
||||
"failedToAdd": "सहभागी जोडण्यात अयशस्वी",
|
||||
"footerText": "डायल आउट करणे अक्षम केले आहे.",
|
||||
"loading": "लोक आणि फोन नंबर शोधत आहे",
|
||||
"loadingNumber": "फोन नंबर सत्यापित करीत आहे",
|
||||
"loadingPeople": "लोकांना आमंत्रित करण्यासाठी शोधत आहे",
|
||||
"noResults": "कोणतेही जुळणारे शोध परिणाम नाहीत",
|
||||
"noValidNumbers": "कृपया एक फोन नंबर प्रविष्ट करा",
|
||||
"searchNumbers": "फोन नंबर जोडा",
|
||||
"searchPeople": "लोकांचा शोध घ्या",
|
||||
"searchPeopleAndNumbers": "लोक शोधा किंवा त्यांचा फोन नंबर जोडा",
|
||||
"telephone": "दूरध्वनी: {{number}}",
|
||||
"title": "या संमेलनात लोकांना आमंत्रित करा"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "ब्लूटुथ",
|
||||
"headphones": "हेडफोन",
|
||||
"phone": "फोन",
|
||||
"speaker": "स्पीकर",
|
||||
"none": "कोणतेही ऑडिओ डिव्हाइस उपलब्ध नाहीत"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "कमी बँडविड्थ"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "मीटिंगचा दुवा जोडा",
|
||||
"confirmAddLink": "आपण या कार्यक्रमास एक Jitsi दुवा जोडू इच्छिता?",
|
||||
"error": {
|
||||
"appConfiguration": "कॅलेंडर समाकलन योग्यरित्या कॉन्फिगर केलेले नाही.",
|
||||
"generic": "त्रुटी आढळली आहे. कृपया आपल्या कॅलेंडर सेटिंग्ज तपासा किंवा कॅलेंडर रीफ्रेश करा.",
|
||||
"notSignedIn": "कॅलेंडर इव्हेंट पाहण्यासाठी प्रमाणीकरण करताना त्रुटी आली. कृपया आपल्या कॅलेंडर सेटिंग्ज तपासा आणि पुन्हा लॉग इन करण्याचा प्रयत्न करा."
|
||||
},
|
||||
"join": "सामील व्हा",
|
||||
"joinTooltip": "बैठकीत सामील व्हा",
|
||||
"nextMeeting": "पुढील बैठक",
|
||||
"noEvents": "कोणतेही आगामी कार्यक्रम शेड्यूल केलेले नाहीत.",
|
||||
"ongoingMeeting": "चालू बैठक",
|
||||
"permissionButton": "सेटिंग्ज उघडा",
|
||||
"permissionMessage": "अॅपमधील आपली संमेलने पहाण्यासाठी कॅलेंडर परवानगी आवश्यक आहे.",
|
||||
"refresh": "रीफ्रेश कॅलेंडर",
|
||||
"today": "आज"
|
||||
},
|
||||
"chat": {
|
||||
"error": "Error: तुमचा संदेश पाठविला गेला नाही. कारणः {{error}}",
|
||||
"fieldPlaceHolder": "आपला संदेश येथे टाइप करा",
|
||||
"messagebox": "एक संदेश टाइप करा",
|
||||
"messageTo": "यांना खाजगी संदेश {{recipient}}",
|
||||
"noMessagesMessage": "अद्याप मीटिंगमध्ये कोणतेही संदेश नाहीत. येथे संभाषण सुरू करा!",
|
||||
"nickname": {
|
||||
"popover": "टोपणनाव निवडा",
|
||||
"title": "चॅट वापरण्यासाठी टोपणनाव प्रविष्ट करा"
|
||||
},
|
||||
"privateNotice": "यांना खाजगी संदेश{{recipient}}",
|
||||
"title": "गप्पा",
|
||||
"you": "आपण"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Google कॅलेंडर आणि ऑफिस 365 एकत्रिकरणासाठी विस्तार स्थापित करा",
|
||||
"buttonText": "Chrome विस्तार स्थापित करा",
|
||||
"dontShowAgain": "मला हे पुन्हा दर्शवू नका"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "आपल्याला आपल्या संमेलनात कनेक्ट करीत आहे ..."
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "जोडले",
|
||||
"AUTHENTICATING": "प्रमाणीकरण करीत आहे",
|
||||
"AUTHFAIL": "प्रमाणीकरण अयशस्वी",
|
||||
"CONNECTED": "जोडलेले",
|
||||
"CONNECTING": "कनेक्ट करीत आहे",
|
||||
"CONNFAIL": "संपर्क खंडित",
|
||||
"DISCONNECTED": "डिस्कनेक्ट केलेले",
|
||||
"DISCONNECTING": "डिस्कनेक्ट करत आहे",
|
||||
"ERROR": "त्रुटी",
|
||||
"FETCH_SESSION_ID": "सत्र आयडी प्राप्त करीत आहे ...",
|
||||
"GET_SESSION_ID_ERROR": "सत्र-आयडी त्रुटी मिळवा:{{code}}",
|
||||
"GOT_SESSION_ID": "सत्र-आयडी मिळवित आहे ... पूर्ण झाले",
|
||||
"LOW_BANDWIDTH": "बँडविड्थ जतन करण्यासाठी {{displayName}}चा व्हिडिओ बंद केला गेला आहे"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "पत्ता:",
|
||||
"bandwidth": "अंदाजे बँडविड्थ:",
|
||||
"bitrate": "बिटरेट:",
|
||||
"bridgeCount": "सर्व्हर संख्या:",
|
||||
"connectedTo": "यांना जोडलेले:",
|
||||
"e2e_rtt": "E2E आरटीटी:",
|
||||
"framerate": "फ्रेम दर:",
|
||||
"less": "कमी दाखवा",
|
||||
"localaddress": "स्थानिक पत्ता:",
|
||||
"localaddress_plural": "स्थानिक पत्ते:",
|
||||
"localport": "स्थानिक बंदर:",
|
||||
"localport_plural": "स्थानिक बंदरे:",
|
||||
"more": "अजून दाखवा",
|
||||
"packetloss": "पॅकेट तोटा",
|
||||
"quality": {
|
||||
"good": "चांगले",
|
||||
"inactive": "निष्क्रिय",
|
||||
"lost": "हरवले",
|
||||
"nonoptimal": "नॉनओप्टिमल",
|
||||
"poor": "गरीब"
|
||||
},
|
||||
"remoteaddress": "दूरस्थ पत्ता:",
|
||||
"remoteaddress_plural": "दूरस्थ पत्ते:",
|
||||
"remoteport": "रिमोट पोर्ट:",
|
||||
"remoteport_plural": "दूरस्थ बंदरे:",
|
||||
"resolution": "ठराव:",
|
||||
"status": "कनेक्शन:",
|
||||
"transport": "वाहतूक:",
|
||||
"transport_plural": "परिवहन:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "यापूर्वी",
|
||||
"today": "आज",
|
||||
"yesterday": "काल"
|
||||
},
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "आपल्या फोनवर या संमेलनात सामील होण्यासाठी आपल्यास {{app}}मोबाइल अॅप आवश्यक आहे.",
|
||||
"description": "काहीच घडलं नाही? आम्ही आपली बैठक {{app}} डेस्कटॉप अॅपमध्ये लाँच करण्याचा प्रयत्न केला. पुन्हा प्रयत्न करा किंवा {{app}} वेब अॅपमध्ये लाँच करा",
|
||||
"descriptionWithoutWeb": "काहीच घडलं नाही? आम्ही आपली बैठक {{app}} डेस्कटॉप अॅपमध्ये लाँच करण्याचा प्रयत्न केला.",
|
||||
"downloadApp": "अॅप डाउनलोड करा",
|
||||
"launchWebButton": "वेबमध्ये लाँच करा",
|
||||
"openApp": "अॅप वर सुरू ठेवा",
|
||||
"title": " {{app}}मध्ये आपली बैठक सुरू करत आहे",
|
||||
"tryAgainButton": "डेस्कटॉपवर पुन्हा प्रयत्न करा"
|
||||
},
|
||||
"defaultLink": "उदा. {{url}}",
|
||||
"defaultNickname": " उदा. जेन गुलाबी",
|
||||
"deviceError": {
|
||||
"cameraError": "आपल्या कॅमेर्यावर प्रवेश करण्यात अयशस्वी",
|
||||
"cameraPermission": "कॅमेर्याची परवानगी घेण्यात त्रुटी",
|
||||
"microphoneError": "आपल्या मायक्रोफोनवर प्रवेश करण्यात अयशस्वी",
|
||||
"microphonePermission": "मायक्रोफोन परवानगी प्राप्त करताना त्रुटी"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"noPermission": "परवानगी दिली नाही",
|
||||
"previewUnavailable": "पूर्वावलोकन अनुपलब्ध",
|
||||
"selectADevice": "एक डिव्हाइस निवडा",
|
||||
"testAudio": "चाचणी आवाज प्ले करा"
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "थेट प्रसारण"
|
||||
},
|
||||
"allow": "परवानगी द्या",
|
||||
"alreadySharedVideoMsg": "दुसरा सहभागी आधीपासूनच व्हिडिओ सामायिक करीत आहे. ही परिषद एका वेळी फक्त एकच सामायिक व्हिडिओ परवानगी देते.",
|
||||
"alreadySharedVideoTitle": "एकावेळी फक्त सामायिक केलेला व्हिडिओ अनुमत आहे",
|
||||
"applicationWindow": "अनुप्रयोग विंडो",
|
||||
"Back": "Back",
|
||||
"cameraConstraintFailedError": "आपला कॅमेरा काही आवश्यक मर्यादा पूर्ण करीत नाही.",
|
||||
"cameraNotFoundError": "कॅमेरा आढळला नाही.",
|
||||
"cameraNotSendingData": "आम्ही आपल्या कॅमेर्यावर प्रवेश करण्यात अक्षम आहोत. कृपया एखादा दुसरा अनुप्रयोग हे डिव्हाइस वापरत आहे की नाही ते तपासा, सेटिंग्ज मेनूमधून दुसरे डिव्हाइस निवडा किंवा अनुप्रयोग रीलोड करण्याचा प्रयत्न करा.",
|
||||
"cameraNotSendingDataTitle": "कॅमेर्यावर प्रवेश करण्यात अक्षम",
|
||||
"cameraPermissionDeniedError": "आपल्याला आपला कॅमेरा वापरण्याची परवानगी नाही. आपण अद्याप परिषदेत सामील होऊ शकता परंतु इतर आपल्याला पाहणार नाहीत. हे निश्चित करण्यासाठी अॅड्रेस बारमधील कॅमेरा बटण वापरा.",
|
||||
"cameraUnknownError": "अज्ञात कारणासाठी कॅमेरा वापरू शकत नाही.",
|
||||
"cameraUnsupportedResolutionError": "आपला कॅमेरा आवश्यक व्हिडिओ रिझोल्यूशनला समर्थन देत नाही.",
|
||||
"Cancel": "रद्द करा",
|
||||
"close": "बंद",
|
||||
"conferenceDisconnectMsg": "आपण आपले नेटवर्क कनेक्शन तपासू शकता. सेकंदात पुन्हा कनेक्ट करत आहे {{seconds}}..",
|
||||
"conferenceDisconnectTitle": "आपण डिस्कनेक्ट झाला आहात.",
|
||||
"conferenceReloadMsg": "आम्ही हे निश्चित करण्याचा प्रयत्न करीत आहोत. पुन्हा कनेक्ट करत आहे. {{seconds}} sec...",
|
||||
"conferenceReloadTitle": "दुर्दैवाने, काहीतरी चूक झाली",
|
||||
"confirm": "पुष्टी",
|
||||
"confirmNo": "नाही",
|
||||
"confirmYes": "होय",
|
||||
"connectError": "अरेरे! काहीतरी चूक झाली आणि आम्ही परिषदेत कनेक्ट होऊ शकलो नाही.",
|
||||
"connectErrorWithMsg": "अरेरे! काहीतरी चूक झाली आणि आम्ही परिषदेत कनेक्ट होऊ शकलो नाही:{{msg}}",
|
||||
"connecting": "कनेक्ट करीत आहे",
|
||||
"contactSupport": "समर्थन संपर्क",
|
||||
"copy": "Copy",
|
||||
"dismiss": "Dismiss",
|
||||
"displayNameRequired": "हाय! तुझे नाव काय आहे?",
|
||||
"done": "पूर्ण झाले",
|
||||
"e2eeDescription": "<p>एंड-टू-एंड एनक्रिप्शन सध्या आहे <strong>प्रायोगिक</strong>. कृपया पहा <a href='https://jitsi.org/blog/e2ee/' target='_blank'>this post</a>तपशीलांसाठी.</p><br/><p>कृपया लक्षात ठेवा की एंड-टू-एंड एन्क्रिप्शन चालू केल्याने सर्व्हर-साइड प्रदान सेवा प्रभावीपणे अक्षम होईल: रेकॉर्डिंग, थेट प्रवाह आणि फोन सहभाग. हे देखील लक्षात ठेवा की मीटिंग केवळ समाविष्ट करण्यायोग्य प्रवाहांसाठी समर्थन असलेल्या ब्राउझरमधून सामील झालेल्या लोकांसाठीच कार्य करेल.</p>",
|
||||
"e2eeLabel": "Key",
|
||||
"e2eeTitle": "एंड-टू-एंड एनक्रिप्शन",
|
||||
"e2eeWarning": "<br /><p><strong>चेतावणी:</strong>या बैठकीतील सर्व सहभागींना एंड-टू-एंड एनक्रिप्शनसाठी समर्थन असल्याचे दिसत नाही. आपण सक्षम केल्यास ते आपल्याला पाहण्यास किंवा ऐकण्यास सक्षम राहणार नाहीत.</p>",
|
||||
"enterDisplayName": "कृपया आपले नाव येथे प्रविष्ट करा",
|
||||
"error": "त्रुटी",
|
||||
"externalInstallationMsg": "आपल्याला आमचा डेस्कटॉप सामायिकरण विस्तार स्थापित करणे आवश्यक आहे.",
|
||||
"externalInstallationTitle": "विस्तार आवश्यक",
|
||||
"goToStore": "वेब स्टोअरवर जा",
|
||||
"gracefulShutdown": "आमची सेवा सध्या देखभालीसाठी बंद आहे. कृपया पुन्हा प्रयत्न करा.",
|
||||
"IamHost": "मी यजमान आहे",
|
||||
"incorrectRoomLockPassword": "चुकीचा संकेतशब्द",
|
||||
"incorrectPassword": "वापरकर्त्याचे नाव अथवा पासवर्ड चुकीचा",
|
||||
"inlineInstallationMsg": "आपल्याला आमचा डेस्कटॉप सामायिकरण विस्तार स्थापित करणे आवश्यक आहे.",
|
||||
"inlineInstallExtension": "स्थापित करा",
|
||||
"internalError": "अरेरे! काहीतरी चूक झाली. पुढील त्रुटी आली:{{error}}",
|
||||
"internalErrorTitle": "अंतर्गत त्रुटी",
|
||||
"kickMessage": "अधिक तपशीलांसाठी आपण {{participantDisplayName}} वर संपर्क साधू शकता.",
|
||||
"kickParticipantButton": "लाथ मारा",
|
||||
"kickParticipantDialog": "आपणास खात्री आहे की आपण या सहभागीस लाथ मारायची आहे?",
|
||||
"kickParticipantTitle": "या सहभागीला बाहेर करा?",
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"liveStreaming": "थेट प्रवाह",
|
||||
"liveStreamingDisabledForGuestTooltip": "अतिथी थेट प्रवाह सुरू करू शकत नाहीत.",
|
||||
"liveStreamingDisabledTooltip": "थेट प्रवाह अक्षम करा.",
|
||||
"lockMessage": "परिषद लॉक करण्यात अयशस्वी.",
|
||||
"lockRoom": "मीटिंग जोडा $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "लॉक अयशस्वी",
|
||||
"logoutQuestion": "आपणास खात्री आहे की आपण लॉगआउट आणि परिषद थांबवू इच्छिता?",
|
||||
"logoutTitle": "बाहेर पडणे",
|
||||
"maxUsersLimitReached": "जास्तीत जास्त सहभागी होण्याची मर्यादा गाठली आहे. परिषद भरली आहे. कृपया मीटिंग मालकाशी संपर्क साधा किंवा नंतर पुन्हा प्रयत्न करा!",
|
||||
"maxUsersLimitReachedTitle": "जास्तीत जास्त सहभागींची मर्यादा गाठली",
|
||||
"micConstraintFailedError": "आपला मायक्रोफोन आवश्यक असलेल्या काही मर्यादा पूर्ण करीत नाही.",
|
||||
"micNotFoundError": "मायक्रोफोन सापडला नाही.",
|
||||
"micNotSendingData": "आपला माईक सशब्द करण्यासाठी आणि त्याचा स्तर समायोजित करण्यासाठी आपल्या संगणकाच्या सेटिंग्जवर जा",
|
||||
"micNotSendingDataTitle": "आपले माइक आपल्या सिस्टम सेटिंग्जद्वारे निःशब्द केले आहे",
|
||||
"micPermissionDeniedError": "आपल्याला आपला मायक्रोफोन वापरण्याची परवानगी नाही. आपण अद्याप परिषदेत सामील होऊ शकता परंतु इतर आपले ऐकणार नाहीत. हे निश्चित करण्यासाठी अॅड्रेस बारमधील कॅमेरा बटण वापरा.",
|
||||
"micUnknownError": "Cannot use microphone for an unknown reason.",
|
||||
"muteEveryoneElseDialog": "एकदा नि: शब्द झाल्यास आपण त्यांना ध्वनीमुद्रित करण्यास सक्षम राहणार नाही परंतु ते कधीही स्वत: ला सशब्द करू शकतात.",
|
||||
"muteEveryoneElseTitle": "सोडून सर्वांना नि: शब्द करा{{whom}}?",
|
||||
"muteEveryoneDialog": "आपली खात्री आहे की आपण प्रत्येकाला निःशब्द करू इच्छिता? आपण त्यांना सशब्द करण्यास सक्षम राहणार नाही परंतु ते कधीही स्वत: ला सशब्द करू शकतात.",
|
||||
"muteEveryoneTitle": "सर्वांना नि: शब्द करा?",
|
||||
"muteEveryoneSelf": "तू स्वतः",
|
||||
"muteEveryoneStartMuted": "आतापासून प्रत्येकजण निःशब्द होऊ लागतो",
|
||||
"muteParticipantBody": "आपण त्यांना सशब्द करण्यास सक्षम राहणार नाही परंतु ते कधीही स्वत: ला सशब्द करू शकतात.",
|
||||
"muteParticipantButton": "नि: शब्द करा",
|
||||
"muteParticipantDialog": "आपली खात्री आहे की आपण या सहभागीस नि: शब्द करू इच्छिता? आपण त्यांना सशब्द करण्यास सक्षम राहणार नाही परंतु ते कधीही स्वत: ला सशब्द करू शकतात.",
|
||||
"muteParticipantTitle": "हा सहभागी नि: शब्द करायचा?",
|
||||
"Ok": "Ok",
|
||||
"passwordLabel": "संमेलनास एका सहभागीने लॉक केले आहे. कृपया सामील होण्यासाठी $t(lockRoomPassword) प्रविष्ट करा.",
|
||||
"passwordNotSupported": "मीटिंग सेट करणे $t(lockRoomPassword) समर्थित नाही..",
|
||||
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) समर्थित नाही",
|
||||
"passwordRequired": "$t(lockRoomPasswordUppercase)आवश्यक",
|
||||
"popupError": "आपला ब्राउझर या साइटवरील पॉप-अप विंडोज अवरोधित करत आहे. कृपया आपल्या ब्राउझरच्या सुरक्षा सेटिंग्जमध्ये पॉप-अप सक्षम करा आणि पुन्हा प्रयत्न करा.",
|
||||
"popupErrorTitle": "पॉप-अप अवरोधित",
|
||||
"recording": "मुद्रित करणे",
|
||||
"recordingDisabledForGuestTooltip": "अतिथी रेकॉर्डिंग प्रारंभ करू शकत नाहीत.",
|
||||
"recordingDisabledTooltip": "रेकॉर्डिंग प्रारंभ अक्षम.",
|
||||
"rejoinNow": "आता पुन्हा सामील व्हा",
|
||||
"remoteControlAllowedMessage": "{{user}} आपली रिमोट कंट्रोल विनंती मान्य केली!",
|
||||
"remoteControlDeniedMessage": "{{user}} आपली रिमोट कंट्रोल विनंती नाकारली!",
|
||||
"remoteControlErrorMessage": "वरून रिमोट कंट्रोल परवानग्यांची विनंती करण्याचा प्रयत्न करताना एक त्रुटी आली {{user}}!",
|
||||
"remoteControlRequestMessage": "आपण{{user}} ला डेस्कटॉप दूरस्थपणे नियंत्रित करण्यास अनुमती द्याल?",
|
||||
"remoteControlShareScreenWarning": "लक्षात ठेवा की आपण \"Allow\" दाबल्यास आपण आपली स्क्रीन सामायिक कराल!",
|
||||
"remoteControlStopMessage": "रिमोट कंट्रोल सत्र संपले!",
|
||||
"remoteControlTitle": "दूरस्थ डेस्कटॉप नियंत्रण",
|
||||
"Remove": "काढा",
|
||||
"removePassword": "काढा $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": " आपली खात्री आहे की आपण आपला सामायिक केलेला व्हिडिओ काढू इच्छिता?",
|
||||
"removeSharedVideoTitle": "सामायिक केलेला व्हिडिओ काढा",
|
||||
"reservationError": "आरक्षण प्रणाली त्रुटी",
|
||||
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
|
||||
"retry": "पुन्हा प्रयत्न करा",
|
||||
"screenSharingAudio": "ऑडिओ सामायिक करा",
|
||||
"screenSharingFailedToInstall": "अरेरे! आपला स्क्रीन सामायिकरण विस्तार स्थापित करण्यात अयशस्वी.",
|
||||
"screenSharingFailedToInstallTitle": "स्क्रीन सामायिकरण विस्तार स्थापित करण्यात अयशस्वी",
|
||||
"screenSharingFirefoxPermissionDeniedError": "आम्ही आपली स्क्रीन सामायिक करण्याचा प्रयत्न करीत असताना काहीतरी चुकीचे झाले. कृपया याची खात्री करा की आपण आम्हाला तसे करण्यास परवानगी दिली आहे.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "अरेरे! आम्ही स्क्रीन सामायिकरण प्रारंभ करण्यास सक्षम नाही!",
|
||||
"screenSharingPermissionDeniedError": "अरेरे! आपल्या स्क्रीन सामायिकरण विस्तार परवानग्यांसह काहीतरी चूक झाली. कृपया रीलोड करा आणि पुन्हा प्रयत्न करा.",
|
||||
"sendPrivateMessage": "आपल्याला अलीकडे एक खाजगी संदेश प्राप्त झाला आहे. त्यास खाजगीरित्या उत्तर देण्याचा आपला हेतू होता की आपण आपला संदेश गटाला पाठवू इच्छिता?",
|
||||
"sendPrivateMessageCancel": "गटाला पाठवा",
|
||||
"sendPrivateMessageOk": "खाजगी पाठवा",
|
||||
"sendPrivateMessageTitle": "खाजगी पाठवायचे?",
|
||||
"serviceUnavailable": "सेवा अनुपलब्ध",
|
||||
"sessTerminated": "कॉल संपुष्टात आला",
|
||||
"Share": "सामायिक करा",
|
||||
"shareVideoLinkError": "कृपया योग्य YouTube दुवा प्रदान करा.",
|
||||
"shareVideoTitle": "एक व्हिडिओ सामायिक करा",
|
||||
"shareYourScreen": "आपली स्क्रीन सामायिक करा",
|
||||
"shareYourScreenDisabled": "स्क्रीन सामायिकरण अक्षम केले.",
|
||||
"shareYourScreenDisabledForGuest": "अतिथी स्क्रीन सामायिकरण करू शकत नाहीत.",
|
||||
"startLiveStreaming": "थेट प्रवाह सुरू करा",
|
||||
"startRecording": "रेकॉर्डिंग प्रारंभ करा",
|
||||
"startRemoteControlErrorMessage": "रिमोट कंट्रोल सत्र सुरू करण्याचा प्रयत्न करताना एक त्रुटी आली!",
|
||||
"stopLiveStreaming": "थेट प्रवाह थांबवा",
|
||||
"stopRecording": "रेकॉर्डिंग थांबवा",
|
||||
"stopRecordingWarning": "आपली खात्री आहे की आपण रेकॉर्डिंग थांबवू इच्छिता?",
|
||||
"stopStreamingWarning": "आपणास खात्री आहे की आपण थेट प्रवाह थांबवू इच्छिता?",
|
||||
"streamKey": "थेट प्रवाह की",
|
||||
"Submit": "प्रस्तुत करणे",
|
||||
"thankYou": "वापरल्याबद्दल धन्यवाद {{appName}}!",
|
||||
"token": "टोकन",
|
||||
"tokenAuthFailed": "क्षमस्व, आपणास या कॉलमध्ये सामील होण्याची परवानगी नाही.",
|
||||
"tokenAuthFailedTitle": "प्रमाणीकरण अयशस्वी",
|
||||
"transcribing": "लिप्यंतरण",
|
||||
"unlockRoom": "मीटिंग काढा $t(lockRoomPassword)",
|
||||
"userPassword": "user password",
|
||||
"WaitForHostMsg": "परिषद <b>{{room}}</b>अद्याप सुरू झाले नाही. आपण होस्ट असल्यास कृपया अधिकृत करा. अन्यथा, कृपया होस्ट येण्याची प्रतीक्षा करा.",
|
||||
"WaitForHostMsgWOk": "परिषद <b>{{room}}</b> अद्याप सुरू झाले नाही. आपण होस्ट असल्यास कृपया प्रमाणीकरणासाठी ओके दाबा. अन्यथा, कृपया होस्ट येण्याची प्रतीक्षा करा.",
|
||||
"WaitingForHost": " होस्टची प्रतीक्षा करीत आहे ...",
|
||||
"Yes": "होय",
|
||||
"yourEntireScreen": "आपली संपूर्ण स्क्रीन"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "आता आहे {{status}}"
|
||||
},
|
||||
"documentSharing": {
|
||||
"title": "सामायिक दस्तऐवज"
|
||||
},
|
||||
"e2ee": {
|
||||
"labelToolTip": "या बैठकीतील सर्व सहभागींनी एंड-टू-एंड एनक्रिप्शन सक्षम केले आहे"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "सरासरी",
|
||||
"bad": "वाईट",
|
||||
"detailsLabel": " त्याबद्दल आम्हाला सांगा.",
|
||||
"good": "चांगले",
|
||||
"rateExperience": "आपल्या भेटीचा अनुभव रेट करा",
|
||||
"veryBad": "फार वाईट",
|
||||
"veryGood": "खुप छान"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "उत्तर",
|
||||
"audioCallTitle": " कॉल येत आहे",
|
||||
"decline": "काढून टाकणे",
|
||||
"productLabel": "Jitsi Meet पासून",
|
||||
"videoCallTitle": "येणारा व्हिडिओ कॉल"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "माहिती दर्शवा",
|
||||
"addPassword": "जोडा $t(lockRoomPassword)",
|
||||
"cancelPassword": " रद्द करा $t(lockRoomPassword)",
|
||||
"conferenceURL": "दुवा:",
|
||||
"country": "देश",
|
||||
"dialANumber": "आपल्या संमेलनात सामील होण्यासाठी, यापैकी एक क्रमांक डायल करा आणि नंतर पिन प्रविष्ट करा.",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "क्षमस्व, सध्या डायल करणे समर्थित नाही.",
|
||||
"dialInNumber": "डायल-इन:",
|
||||
"dialInSummaryError": "आता डायल-इन माहिती आणताना त्रुटी. कृपया पुन्हा प्रयत्न करा.",
|
||||
"dialInTollFree": "कर मुक्त",
|
||||
"genericError": "अरेरे, काहीतरी चूक झाली.",
|
||||
"inviteLiveStream": "या सभेचा थेट प्रवाह पाहण्यासाठी, या दुव्यावर क्लिक करा: {{url}}",
|
||||
"invitePhone": "त्याऐवजी फोनद्वारे सामील होण्यासाठी, हे टॅप करा:{{number}},,{{conferenceID}}#\n",
|
||||
"invitePhoneAlternatives": "वेगळा डायल-इन नंबर शोधत आहात? \nमीटिंग डायल-इन नंबर पहा: {{url}}\n\n\nIf also dialing-in through a room phone, join without connecting to audio: {{silentUrl}}",
|
||||
"inviteURLFirstPartGeneral": " आपल्याला बैठकीत सामील होण्यासाठी आमंत्रित केले आहे.",
|
||||
"inviteURLFirstPartPersonal": "{{name}} आपल्याला मीटिंगसाठी आमंत्रित करीत आहे.\n",
|
||||
"inviteURLSecondPart": "\nसभेमध्ये सामील व्हा:\n{{url}}\n",
|
||||
"liveStreamURL": "थेट प्रसारण:",
|
||||
"moreNumbers": "अधिक संख्या",
|
||||
"noNumbers": "डायल-इन नंबर नाहीत.",
|
||||
"noPassword": "काहीही नाही",
|
||||
"noRoom": "डायल-इन करण्यासाठी कोणतीही खोली निर्दिष्ट केलेली नाही.",
|
||||
"numbers": "डायल-इन क्रमांक",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"title": "सामायिक करा",
|
||||
"tooltip": "या संमेलनासाठी दुवा आणि डायल-इन माहिती सामायिक करा",
|
||||
"label": "संमेलनाची माहिती"
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertText": "काही सहभागींना आमंत्रित करण्यात अयशस्वी.",
|
||||
"header": "आमंत्रित करा",
|
||||
"searchCallOnlyPlaceholder": " फोन नंबर प्रविष्ट करा",
|
||||
"searchPeopleOnlyPlaceholder": "सहभागींचा शोध घ्या",
|
||||
"searchPlaceholder": "सहभागी किंवा फोन नंबर",
|
||||
"send": "पाठवा"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "आम्ही जरा अडखळलो.",
|
||||
"retry": "पुन्हा प्रयत्न करा",
|
||||
"support": "आधार",
|
||||
"supportMsg": "हे असेच होत राहिल्यास संपर्क साधा"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "आपल्या व्हिडिओवर लक्ष द्या",
|
||||
"focusRemote": "दुसर्या व्यक्तीच्या व्हिडिओवर लक्ष द्या",
|
||||
"fullScreen": "पूर्ण स्क्रीन पहा किंवा बाहेर पडा",
|
||||
"keyboardShortcuts": "कीबोर्ड शॉर्टकट",
|
||||
"localRecording": "स्थानिक रेकॉर्डिंग नियंत्रणे दर्शवा किंवा लपवा",
|
||||
"mute": "आपला मायक्रोफोन नि: शब्द करा किंवा सशब्द करा",
|
||||
"pushToTalk": "बोलण्यासाठी दाबा",
|
||||
"raiseHand": "आपला हात वर करा किंवा कमी करा",
|
||||
"showSpeakerStats": "स्पीकरची आकडेवारी दर्शवा",
|
||||
"toggleChat": "गप्पा उघडा किंवा बंद करा",
|
||||
"toggleFilmstrip": "व्हिडिओ लघुप्रतिमा दर्शवा किंवा लपवा",
|
||||
"toggleScreensharing": "कॅमेरा आणि स्क्रीन सामायिकरण दरम्यान स्विच करा",
|
||||
"toggleShortcuts": "कीबोर्ड शॉर्टकट दर्शवा किंवा लपवा",
|
||||
"videoMute": "आपला कॅमेरा प्रारंभ करा किंवा थांबवा",
|
||||
"videoQuality": "कॉल गुणवत्ता व्यवस्थापित करा"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": " आम्ही प्रवाह स्त्रोत मुक्त करण्याचे कार्य करीत आहोत. कृपया काही मिनिटांत पुन्हा प्रयत्न करा.",
|
||||
"busyTitle": "सर्व स्ट्रीमर सध्या व्यस्त आहेत",
|
||||
"changeSignIn": "खाती स्विच करा.",
|
||||
"choose": "थेट प्रवाह निवडा",
|
||||
"chooseCTA": "प्रवाह पर्याय निवडा. आपण सध्या म्हणून लॉग इन आहात {{email}}.",
|
||||
"enterStreamKey": " येथे आपली YouTube थेट प्रवाह की प्रविष्ट करा.",
|
||||
"error": "थेट प्रवाह अयशस्वी. कृपया पुन्हा प्रयत्न करा.",
|
||||
"errorAPI": "आपल्या YouTube प्रसारणामध्ये प्रवेश करताना त्रुटी आली. कृपया पुन्हा लॉग इन करून पहा.",
|
||||
"errorLiveStreamNotEnabled": "{{email}}. वर थेट प्रवाह सक्षम केलेले नाही. कृपया थेट प्रवाह सक्षम करा किंवा थेट प्रवाह सक्षम केलेल्या खात्यात लॉग इन करा",
|
||||
"expandedOff": "थेट प्रवाह थांबला आहे",
|
||||
"expandedOn": "या संमेलनाचे सध्या युट्यूबवर प्रसारण केले जात आहे.",
|
||||
"expandedPending": "थेट प्रवाह सुरू केला जात आहे ",
|
||||
"failedToStart": "थेट प्रवाह सुरू करण्यात अयशस्वी",
|
||||
"getStreamKeyManually": "आम्ही कोणतेही थेट प्रवाह आणण्यात सक्षम नाही. YouTube वरून आपली थेट प्रवाह की मिळवण्याचा प्रयत्न करा.",
|
||||
"invalidStreamKey": "थेट प्रवाह की चुकीची असू शकते.",
|
||||
"off": "थेट प्रवाह थांबला",
|
||||
"offBy": "{{name}} थेट प्रवाह थांबविला",
|
||||
"on": "थेट प्रवाह",
|
||||
"onBy": "{{name}} थेट प्रवाह सुरू केला",
|
||||
"pending": "थेट प्रवाह सुरू करत आहे ...",
|
||||
"serviceName": "थेट प्रवाह सेवा",
|
||||
"signedInAs": "आपण सध्या म्हणून साइन इन केले आहे:",
|
||||
"signIn": "Google सह साइन इन करा",
|
||||
"signInCTA": "YouTube वरून साइन इन करा किंवा आपली थेट प्रवाह की प्रविष्ट करा.",
|
||||
"signOut": "साइन आउट करा",
|
||||
"start": "थेट प्रवाह सुरू करा",
|
||||
"streamIdHelp": "हे काय आहे?",
|
||||
"unavailableTitle": "थेट प्रवाह अनुपलब्ध",
|
||||
"youtubeTerms": "YouTube सेवा अटी",
|
||||
"googlePrivacyPolicy": "Google गोपनीयता धोरण"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "बंद",
|
||||
"on": "चालू",
|
||||
"unknown": "अज्ञात"
|
||||
},
|
||||
"dialogTitle": "स्थानिक रेकॉर्डिंग नियंत्रणे",
|
||||
"duration": "कालावधी",
|
||||
"durationNA": "N/A",
|
||||
"encoding": "एन्कोडिंग",
|
||||
"label": "LOR",
|
||||
"labelToolTip": "स्थानिक रेकॉर्डिंग गुंतलेली आहे",
|
||||
"localRecording": "स्थानिक रेकॉर्डिंग",
|
||||
"me": "Me",
|
||||
"messages": {
|
||||
"engaged": "स्थानिक रेकॉर्डिंग व्यस्त",
|
||||
"finished": " रेकॉर्डिंग सत्र {{token}} . समाप्त. कृपया रेकॉर्ड केलेली फाईल नियंत्रकावर पाठवा.",
|
||||
"finishedModerator": "रेकॉर्डिंग सत्र {{token}}. समाप्त. लोकल ट्रॅकचे रेकॉर्डिंग सेव्ह केले गेले आहे. कृपया इतर सहभागींना त्यांचे रेकॉर्डिंग सबमिट करण्यास सांगा.",
|
||||
"notModerator": "आपण नियंत्रक नाही. आपण स्थानिक रेकॉर्डिंग प्रारंभ करू किंवा थांबवू शकत नाही."
|
||||
},
|
||||
"moderator": "नियंत्रक",
|
||||
"no": "No",
|
||||
"participant": "नियंत्रक",
|
||||
"participantStats": "सहभागी आकडेवारी",
|
||||
"sessionToken": "सत्र टोकन",
|
||||
"start": "रेकॉर्डिंग प्रारंभ करा",
|
||||
"stop": "रेकॉर्डिंग थांबवा",
|
||||
"yes": "होय"
|
||||
},
|
||||
"lockRoomPassword": "संकेतशब्द",
|
||||
"lockRoomPasswordUppercase": "संकेतशब्द",
|
||||
"me": "मी",
|
||||
"notify": {
|
||||
"connectedOneMember": "{{name}} बैठकीत सामील झाले",
|
||||
"connectedThreePlusMembers": "{{name}} आणि {{count}} इतर बैठकीत सामील झाले",
|
||||
"connectedTwoMembers": "{{first}} आणि {{second}} बैठकीत सामील झाले",
|
||||
"disconnected": "डिस्कनेक्ट झाले",
|
||||
"focus": "परिषद लक्ष",
|
||||
"focusFail": "{{component}} उपलब्ध नाही - पुन्हा प्रयत्न करा {{ms}} सेकंद",
|
||||
"grantedTo": "नियंत्रक अधिकार यांना दिले {{to}}!",
|
||||
"invitedOneMember": "{{name}}आमंत्रित केले गेले आहे",
|
||||
"invitedThreePlusMembers": "{{name}} आणि {{count}} इतरांना आमंत्रित केले गेले आहे",
|
||||
"invitedTwoMembers": "{{first}} आणि {{second}} आमंत्रित केले गेले आहे",
|
||||
"kickParticipant": "{{kicked}} was kicked by {{kicker}}",
|
||||
"me": "Me",
|
||||
"moderator": "नियंत्रक अधिकार मंजूर!",
|
||||
"muted": "आपण संभाषण निःशब्द केले आहे.",
|
||||
"mutedTitle": "आपण निःशब्द आहात!",
|
||||
"mutedRemotelyTitle": "आपण द्वारे निःशब्द केले गेले आहे {{participantDisplayName}}!",
|
||||
"mutedRemotelyDescription": "आपण बोलण्यास तयार असता तेव्हा आपण नेहमी सशब्द करू शकता. आपण संमेलनापासून आवाज दूर ठेवण्यासाठी पूर्ण झाल्यावर परत नि: शब्द करा.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) दुसर्या सहभागीने काढले",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) दुसर्या सहभागीने सेट केलेले",
|
||||
"raisedHand": "{{name}} बोलायला आवडेल.",
|
||||
"somebody": "कुणीतरी",
|
||||
"startSilentTitle": "आपण ऑडिओ आउटपुटसह सामील झालात!",
|
||||
"startSilentDescription": "ऑडिओ सक्षम करण्यासाठी संमेलनात पुन्हा सामील व्हा",
|
||||
"suboptimalBrowserWarning": " आम्हाला भीती वाटते की येथे आपल्या भेटीचा अनुभव इतका उत्कृष्ट होणार नाही. आम्ही यामध्ये सुधारणा करण्याचे मार्ग शोधत आहोत, परंतु तोपर्यंत कृपया त्यापैकी एक वापरून पहा <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
|
||||
"suboptimalExperienceTitle": "ब्राउझर चेतावणी",
|
||||
"unmute": "सशब्द करा",
|
||||
"newDeviceCameraTitle": "नवीन कॅमेरा आढळला",
|
||||
"newDeviceAudioTitle": "नवीन ऑडिओ डिव्हाइस आढळले",
|
||||
"newDeviceAction": "वापरा",
|
||||
"OldElectronAPPTitle": "सुरक्षा असुरक्षा!",
|
||||
"oldElectronClientDescription1": "आपण सुरक्षा असुरक्षा ज्ञात असलेल्या जितसी मीट क्लायंटची जुनी आवृत्ती वापरत असल्याचे दिसून येत आहे. कृपया आमच्याकडे आपण अद्यतनित असल्याची खात्री करा",
|
||||
"oldElectronClientDescription2": "नवीनतम बिल्ड",
|
||||
"oldElectronClientDescription3": " आता!"
|
||||
},
|
||||
"passwordSetRemotely": "दुसर्या सहभागीने सेट केलेले",
|
||||
"passwordDigitsOnly": " पर्यंत {{number}} अंक",
|
||||
"poweredby": "द्वारा समर्थित",
|
||||
"prejoin": {
|
||||
|
||||
"audioAndVideoError": "ऑडिओ आणि व्हिडिओ त्रुटी:",
|
||||
"audioOnlyError": "ऑडिओ त्रुटी:",
|
||||
"audioTrackError": "ऑडिओ ट्रॅक तयार करू शकलो नाही.",
|
||||
"callMe": "मला कॉल करा",
|
||||
"callMeAtNumber": "मला या नंबरवर कॉल करा:",
|
||||
"configuringDevices": "डिव्हाइस कॉन्फिगर करीत आहे ...",
|
||||
"connectedWithAudioQ": "आपण ऑडिओशी कनेक्ट आहात?",
|
||||
"copyAndShare": "मीटिंगचा दुवा कॉपी आणि सामायिक करा",
|
||||
"dialInMeeting": "बैठकीत डायल करा",
|
||||
"dialInPin": "संमेलनात डायल करा आणि पिन कोड प्रविष्ट करा:",
|
||||
"dialing": "डायल करत आहे",
|
||||
"iWantToDialIn": "मला डायल करायचे आहे",
|
||||
"joinAudioByPhone": "फोन ऑडिओसह सामील व्हा",
|
||||
"joinMeeting": "बैठकीत सामील व्हा",
|
||||
"joinWithoutAudio": "ऑडिओशिवाय सामील व्हा",
|
||||
"initiated": "कॉल सुरू झाला",
|
||||
"linkCopied": "क्लिपबोर्डवर दुवा कॉपी केला",
|
||||
"lookGood": "स्पीकर आणि मायक्रोफोन चांगले दिसतात",
|
||||
"or": "किंवा",
|
||||
"calling": "कॉल करीत आहे",
|
||||
"startWithPhone": "फोन ऑडिओसह प्रारंभ करा",
|
||||
"screenSharingError": "स्क्रीन सामायिकरण त्रुटी:",
|
||||
"videoOnlyError": "व्हिडिओ त्रुटी:",
|
||||
"videoTrackError": "व्हिडिओ ट्रॅक तयार करू शकलो नाही.",
|
||||
"viewAllNumbers": "सर्व संख्या पहा"
|
||||
},
|
||||
"presenceStatus": {
|
||||
"busy": "व्यस्त",
|
||||
"calling": "कॉल करीत आहे ...",
|
||||
"connected": "जोडलेले",
|
||||
"connecting": "कनेक्ट करीत आहे ...",
|
||||
"connecting2": "कनेक्ट करीत आहे ...",
|
||||
"disconnected": "डिस्कनेक्ट केलेले",
|
||||
"expired": "कालबाह्य",
|
||||
"ignored": "दुर्लक्षित",
|
||||
"initializingCall": "कॉल प्रारंभ करीत आहे ...",
|
||||
"invited": "आमंत्रित केले",
|
||||
"rejected": "नाकारले",
|
||||
"ringing": "रिंग होत आहे ..."
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": " आपले प्रदर्शन नाव सेट करा",
|
||||
"setEmailInput": "ई-मेल प्रविष्ट करा",
|
||||
"setEmailLabel": "आपला गुरुतर ईमेल सेट करा",
|
||||
"title": "प्रोफाइल"
|
||||
},
|
||||
"raisedHand": "बोलायला आवडेल",
|
||||
"recording": {
|
||||
"authDropboxText": " ड्रॉपबॉक्सवर अपलोड करा",
|
||||
"availableSpace": "उपलब्ध जागा: {{spaceLeft}} MB (approximately {{duration}} रेकॉर्डिंग मिनिटे)",
|
||||
"beta": "BETA",
|
||||
"busy": "आम्ही रेकॉर्डिंग संसाधने मुक्त करण्यावर कार्य करीत आहोत. कृपया काही मिनिटांत पुन्हा प्रयत्न करा.",
|
||||
"busyTitle": "सर्व रेकॉर्डर सध्या व्यस्त आहेत",
|
||||
"error": "रेकॉर्डिंग अयशस्वी. कृपया पुन्हा प्रयत्न करा.",
|
||||
"expandedOff": "रेकॉर्डिंग थांबले आहे",
|
||||
"expandedOn": "सभेची नोंद सध्या घेतली जात आहे.",
|
||||
"expandedPending": "रेकॉर्डिंग सुरू केले जात आहे ...",
|
||||
"failedToStart": "रेकॉर्डिंग सुरू करण्यात अयशस्वी",
|
||||
"fileSharingdescription": "मीटिंगमधील सहभागींसह रेकॉर्डिंग सामायिक करा",
|
||||
"live": "LIVE",
|
||||
"loggedIn": "म्हणून लॉग इन केले {{userName}}",
|
||||
"off": "रेकॉर्डिंग थांबले",
|
||||
"offBy": "{{name}} रेकॉर्डिंग थांबविले",
|
||||
"on": "Recording",
|
||||
"onBy": "{{name}} रेकॉर्डिंग सुरू केले",
|
||||
"pending": "मीटिंग रेकॉर्ड करण्याची तयारी करत आहे ...",
|
||||
"rec": "REC",
|
||||
"serviceDescription": "आपले रेकॉर्डिंग रेकॉर्डिंग सेवेद्वारे जतन केले जाईल",
|
||||
"serviceName": "रेकॉर्डिंग सेवा",
|
||||
"signIn": "साइन इन करा",
|
||||
"signOut": "साइन आउट करा",
|
||||
"unavailable": " अरेरे! {{serviceName}} currently सध्या अनुपलब्ध आहे. आम्ही या समस्येचे निराकरण करण्याचे काम करीत आहोत. कृपया पुन्हा प्रयत्न करा.",
|
||||
"unavailableTitle": "रेकॉर्डिंग अनुपलब्ध"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "रीफ्रेश करण्यासाठी खेचा"
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": " {{appName}} कॅलेंडर समाकलन सुरक्षितपणे आपल्या कॅलेंडरमध्ये प्रवेश करण्यासाठी वापरले जाते जेणेकरून ते आगामी कार्यक्रम वाचू शकतील.",
|
||||
"disconnect": "डिस्कनेक्ट करा",
|
||||
"microsoftSignIn": "मायक्रोसॉफ्ट सह साइन इन करा",
|
||||
"signedIn": "सध्या {{email}} साठी कॅलेंडर इव्हेंटमध्ये प्रवेश करत आहे. कॅलेंडर इव्हेंटमध्ये प्रवेश करणे थांबविण्यासाठी खालील डिस्कनेक्ट बटणावर क्लिक करा",
|
||||
"title": "कॅलेंडर"
|
||||
},
|
||||
"devices": "उपकरणे",
|
||||
"followMe": "प्रत्येकजण माझ्या मागे येतो",
|
||||
"language": "भाषा",
|
||||
"loggedIn": "{{name}} म्हणून लॉग इन केले",
|
||||
"microphones": "मायक्रोफोन",
|
||||
"moderator": "नियंत्रक",
|
||||
"more": "अधिक",
|
||||
"name": "नाव",
|
||||
"noDevice": "काहीही नाही",
|
||||
"selectAudioOutput": "ऑडिओ आउटपुट",
|
||||
"selectCamera": "कॅमेरा",
|
||||
"selectMic": "मायक्रोफोन",
|
||||
"speakers": "स्पीकर्स",
|
||||
"startAudioMuted": " प्रत्येकजण निःशब्द होतो",
|
||||
"startVideoMuted": "प्रत्येकजण दडलेला सुरू होतो",
|
||||
"title": "सेटिंग्ज"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "प्रगत",
|
||||
"alertOk": "ठीक आहे",
|
||||
"alertTitle": "चेतावणी",
|
||||
"alertURLText": "प्रविष्ट केलेली सर्व्हर URL अवैध आहे",
|
||||
"buildInfoSection": "बिल्ड माहिती",
|
||||
"conferenceSection": "परिषद",
|
||||
"disableCallIntegration": "नेटिव्ह कॉल एकत्रीकरण अक्षम करा",
|
||||
"disableP2P": " पीअर-टू-पीअर मोड अक्षम करा",
|
||||
"displayName": " नाव प्रदर्शन",
|
||||
"email": "ईमेल",
|
||||
"header": "सेटिंग्ज",
|
||||
"profileSection": "प्रोफाइल",
|
||||
"serverURL": "सर्व्हर URL",
|
||||
"showAdvanced":"प्रगत सेटिंग्ज दर्शवा",
|
||||
"startWithAudioMuted": "ऑडिओ नि: शब्द सह प्रारंभ करा",
|
||||
"startWithVideoMuted": "निःशब्द व्हिडिओसह प्रारंभ करा",
|
||||
"version": "आवृत्ती"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nफक्त आपल्या फोनवर डायल करू इच्छिता?\n\n{{defaultDialInNumber}} या संमेलनासाठी फोन नंबर डायल पाहण्यासाठी या दुव्यावर क्लिक करा\n{{dialInfoPageUrl}}",
|
||||
"mainText": "संमेलनात सामील होण्यासाठी खालील दुव्यावर क्लिक करा:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "स्पीकर",
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "नाव",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "स्पीकर आकडेवारी",
|
||||
"speakerTime": "स्पीकर वेळ"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "{{app}} आपला मायक्रोफोन आणि कॅमेरा वापरण्याची आवश्यकता आहे."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "पुन्हा जॉइन करा",
|
||||
"text": " पुन्हा कनेक्ट करण्यासाठी <i>Rejoin</i> बटण दाबा.",
|
||||
"title": "आपला व्हिडिओ कॉल व्यत्यय आला कारण हा संगणक झोपायला गेला."
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
|
||||
},
|
||||
"addPeople": "आपल्या कॉलमध्ये लोकांना जोडा",
|
||||
"audioOnlyOff": "कमी बँडविड्थ मोड अक्षम करा",
|
||||
"audioOnlyOn": "कमी बँडविड्थ मोड सक्षम करा",
|
||||
"audioRoute": "ध्वनी यंत्र निवडा",
|
||||
"authenticate": "प्रमाणित करा",
|
||||
"callQuality": "व्हिडिओ गुणवत्ता व्यवस्थापित करा",
|
||||
"chat": "गप्पा / बंद करा उघडा",
|
||||
"closeChat": "गप्पा बंद करा",
|
||||
"documentClose": "सामायिक दस्तऐवज बंद करा",
|
||||
"documentOpen": "सामायिक दस्तऐवज उघडा",
|
||||
"download": "आमचे अॅप्स डाउनलोड करा",
|
||||
"e2ee": "एंड-टू-एंड एनक्रिप्शन",
|
||||
"enterFullScreen": "पूर्ण स्क्रीन पहा",
|
||||
"enterTileView": "टाइल दृश्य प्रविष्ट करा",
|
||||
"exitFullScreen": "पूर्ण स्क्रीनमधून बाहेर पडा",
|
||||
"exitTileView": "बाहेर पडा टाइल दृश्य",
|
||||
"feedback": "अभिप्राय द्या",
|
||||
"hangup": "सोडा",
|
||||
"help": "मदत करा",
|
||||
"invite": "लोकांना आमंत्रित करा",
|
||||
"login": "लॉगिन",
|
||||
"logout": "बाहेर पडणे",
|
||||
"lowerYourHand": "बाहेर पडणे",
|
||||
"moreActions": "अधिक क्रिया",
|
||||
"moreOptions": "अधिक पर्याय",
|
||||
"mute": "नि: शब्द / सशब्द करा",
|
||||
"muteEveryone": "सर्वांना नि: शब्द करा",
|
||||
"noAudioSignalTitle": "आपल्या माइकवरून कोणतेही इनपुट येत नाही!",
|
||||
"noAudioSignalDesc": "आपण सिस्टम सेटिंग्ज किंवा हार्डवेअरवरून हेतुपुरस्सर नि: शब्द न केल्यास, डिव्हाइस बदलण्याचा विचार करा.",
|
||||
"noAudioSignalDescSuggestion": "आपण सिस्टम सेटिंग्ज किंवा हार्डवेअरवरून हेतुपुरस्सर नि: शब्द न केल्यास, सूचित डिव्हाइसवर स्विच करण्याचा विचार करा.",
|
||||
"noAudioSignalDialInDesc": "आपण हे वापरून डायल-इन देखील करू शकता:",
|
||||
"noAudioSignalDialInLinkDesc": "डायल-इन क्रमांक",
|
||||
"noisyAudioInputTitle": "आपला मायक्रोफोन गोंगाट करणारा दिसत आहे!",
|
||||
"noisyAudioInputDesc": "आपला मायक्रोफोन आवाज देत असल्यासारखे दिसत आहे, कृपया डिव्हाइस नि: शब्द करणे किंवा बदलणे याचा विचार करा.",
|
||||
"openChat": "खुली गप्पा",
|
||||
"pip": "पिक्चर-इन-पिक्चर मोड प्रविष्ट करा",
|
||||
"privateMessage": "खाजगी संदेश पाठवा",
|
||||
"profile": "आपले प्रोफाइल संपादित करा",
|
||||
"raiseHand": "हात वर करा / कमी करा",
|
||||
"raiseYourHand": "तुझा हात वर कर",
|
||||
"Settings": "सेटिंग्ज",
|
||||
"sharedvideo": "एक YouTube व्हिडिओ सामायिक करा",
|
||||
"shareRoom": "एखाद्यास आमंत्रित करा",
|
||||
"shortcuts": "शॉर्टकट पहा",
|
||||
"speakerStats": "स्पीकर आकडेवारी",
|
||||
"startScreenSharing": "स्क्रीन सामायिकरण प्रारंभ करा",
|
||||
"startSubtitles": "उपशीर्षके प्रारंभ करा",
|
||||
"stopScreenSharing": "स्क्रीन सामायिकरण थांबवा",
|
||||
"stopSubtitles": "उपशीर्षके थांबवा",
|
||||
"stopSharedVideo": "YouTube व्हिडिओ थांबवा",
|
||||
"talkWhileMutedPopup": "बोलण्याचा प्रयत्न करीत आहात? आपण निःशब्द आहात.",
|
||||
"tileViewToggle": "टाइल दृश्य टॉगल करा",
|
||||
"toggleCamera": "टॉगल कॅमेरा",
|
||||
"videomute": "कॅमेरा प्रारंभ / थांबवा",
|
||||
"startvideoblur": "माझी पार्श्वभूमी अस्पष्ट करा",
|
||||
"stopvideoblur": "पार्श्वभूमी डाग अक्षम करा"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "उपशीर्षके प्रारंभ / थांबवा",
|
||||
"error": "लिप्यंतरण अयशस्वी. कृपया पुन्हा प्रयत्न करा.",
|
||||
"expandedLabel": "लिप्यंतरण चालू आहे",
|
||||
"failedToStart": "लिप्यंतरण सुरू करण्यात अयशस्वी",
|
||||
"labelToolTip": "सभेचे प्रतिलेखन केले जात आहे",
|
||||
"off": "लिप्यंतरण थांबविले",
|
||||
"pending": "संमेलनाची नक्कल करण्याची तयारी करत आहे ...",
|
||||
"start": "उपशीर्षके दर्शविणे प्रारंभ करा",
|
||||
"stop": "उपशीर्षके दर्शविणे थांबवा",
|
||||
"tr": "टीआर"
|
||||
},
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "निवडा <b><i>परवानगी द्या</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"chromeGrantPermissions": "निवडा <b><i>परवानगी द्या</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"edgeGrantPermissions": "निवडा <b><i> होय</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"electronGrantPermissions": "कृपया आपला कॅमेरा आणि मायक्रोफोन वापरण्यास परवानगी द्या",
|
||||
"firefoxGrantPermissions": "Select <b><i>निवडलेले डिव्हाइस सामायिक करा</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"iexplorerGrantPermissions": "निवडा <b><i>ठीक आहे</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"nwjsGrantPermissions": "कृपया आपला कॅमेरा आणि मायक्रोफोन वापरण्यास परवानगी द्या",
|
||||
"operaGrantPermissions": "निवडा <b><i>परवानगी द्या</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"react-nativeGrantPermissions": "निवडा <b><i>परवानगी द्या</i></b>जेव्हा आपला ब्राउझर परवानग्या विचारतो.",
|
||||
"safariGrantPermissions": "निवडा <b><i>ठीक आहे</i></b> जेव्हा आपला ब्राउझर परवानग्या विचारतो."
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "आम्ही स्त्रोत मुक्त करण्याचे काम करत आहोत. कृपया काही मिनिटांत पुन्हा प्रयत्न करा.",
|
||||
"busyTitle": " परिषद खोलीत सेवा सध्या व्यस्त आहे",
|
||||
"errorAlreadyInvited": " {{displayName}} आधीच आमंत्रित आहे",
|
||||
"errorInvite": "परिषद अद्याप स्थापन केलेली नाही. कृपया पुन्हा प्रयत्न करा.",
|
||||
"errorInviteFailed": "आम्ही या समस्येचे निराकरण करण्याचे काम करीत आहोत. कृपया पुन्हा प्रयत्न करा.",
|
||||
"errorInviteFailedTitle": "{{displayName}} आमंत्रित करणे अयशस्वी",
|
||||
"errorInviteTitle": " परिषद खोलीत आमंत्रित करताना त्रुटी",
|
||||
"pending": "{{displayName}} आमंत्रित केले गेले आहे"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "एडीडी",
|
||||
"audioOnlyExpanded": "आपण कमी बँडविड्थ मोडमध्ये आहात. या मोडमध्ये आपल्याला केवळ ऑडिओ आणि स्क्रीन सामायिकरण प्राप्त होईल.",
|
||||
"callQuality": "व्हिडिओ गुणवत्ता",
|
||||
"hd": "एचडी",
|
||||
"hdTooltip": "उच्च परिभाषा व्हिडिओ पहात आहे",
|
||||
"highDefinition": "उंच - व्याख्या",
|
||||
"labelTooiltipNoVideo": "व्हिडिओ नाही",
|
||||
"labelTooltipAudioOnly": "कमी बँडविड्थ मोड सक्षम",
|
||||
"ld": "एलडी",
|
||||
"ldTooltip": "निम्न परिभाषा व्हिडिओ पहात आहे",
|
||||
"lowDefinition": "कमी व्याख्या",
|
||||
"onlyAudioAvailable": "केवळ ऑडिओ उपलब्ध आहे",
|
||||
"onlyAudioSupported": "आम्ही या ब्राउझरमध्ये केवळ ऑडिओचे समर्थन करतो.",
|
||||
"p2pEnabled": "सरदार ते सरदार सक्षम",
|
||||
"p2pVideoQualityDescription": "पीअर टू पीअर मोडमध्ये, प्राप्त व्हिडिओ गुणवत्ता केवळ उच्च आणि ऑडिओ दरम्यानच टॉगल केली जाऊ शकते. पीअर टू पीअर बाहेर येईपर्यंत इतर सेटिंग्जचा आदर केला जाणार नाही.",
|
||||
"recHighDefinitionOnly": "उच्च परिभाषा पसंत करेल.",
|
||||
"sd": "एसडी",
|
||||
"sdTooltip": "मानक परिभाषा व्हिडिओ पहात आहे",
|
||||
"standardDefinition": "मानक व्याख्या"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "नि: शब्द करा",
|
||||
"domuteOthers": "इतर सर्वांना नि: शब्द करा",
|
||||
"flip": "फ्लिप",
|
||||
"kick": "लाथा मारून बाहेर काढ",
|
||||
"moderator": "नियंत्रक",
|
||||
"mute": "सहभागी निःशब्द आहे",
|
||||
"muted": "नि: शब्द केलेले",
|
||||
"remoteControl": "रिमोट कंट्रोल प्रारंभ / थांबवा.",
|
||||
"show": "रंगमंचावर दाखवा",
|
||||
"videomute": "सहभागीने कॅमेरा थांबविला आहे"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "सामील होण्यासाठी टॅप करा",
|
||||
"roomname": "खोलीचे नाव प्रविष्ट करा"
|
||||
},
|
||||
"appDescription": " पुढे जा, संपूर्ण टीमसह व्हिडिओ चॅट करा. खरं तर, आपल्या ओळखीच्या प्रत्येकास आमंत्रित करा. {{app}} एक संपूर्ण एनक्रिप्टेड, 100% मुक्त स्रोत व्हिडिओ कॉन्फरन्सिंग समाधान आहे जे आपण दिवसभर, दररोज विनामूल्य वापरु शकता - कोणतेही खाते आवश्यक नाही.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "आवाज",
|
||||
"video": "व्हिडिओ"
|
||||
},
|
||||
"calendar": "कॅलेंडर",
|
||||
"connectCalendarButton": "आपले कॅलेंडर कनेक्ट करा",
|
||||
"connectCalendarText": "आपली सर्व सभा {{app}} in मध्ये पाहण्यासाठी कॅलेंडर कनेक्ट करा. तसेच, आपल्या कॅलेंडरमध्ये {{provider}} संमेलने जोडा आणि एका क्लिकने त्या प्रारंभ करा.",
|
||||
"enterRoomTitle": "नवीन बैठक सुरू करा",
|
||||
"getHelp": "Get help",
|
||||
"roomNameAllowedChars": "संमेलनाच्या नावात यापैकी कोणतेही वर्ण नसावेत: ?, &, :, ', \", %, #.",
|
||||
"go": "GO",
|
||||
"goSmall": "GO",
|
||||
"join": "तयार करा / सामील व्हा",
|
||||
"info": "माहिती",
|
||||
"privacy": "गोपनीयता",
|
||||
"recentList": "अलीकडील",
|
||||
"recentListDelete": "हटवा",
|
||||
"recentListEmpty": "आपली अलीकडील यादी सध्या रिक्त आहे. आपल्या कार्यसंघाशी गप्पा मारा आणि आपल्याला आपल्या सर्व अलीकडील संमेलने येथे आढळतील.",
|
||||
"reducedUIText": "{{App}} वर आपले स्वागत आहे!",
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "आपण सामील होऊ इच्छित असलेल्या खोलीचे नाव किंवा URL प्रविष्ट करा. आपण नाव लिहू शकता, आपण ज्यांना भेटत आहात त्या लोकांना हे कळू द्या जेणेकरुन ते समान नाव प्रविष्ट करा.",
|
||||
"sendFeedback": "अभिप्राय पाठवा",
|
||||
"terms": "अटी",
|
||||
"title": "सुरक्षित, पूर्णपणे वैशिष्ट्यीकृत आणि पूर्णपणे विनामूल्य व्हिडिओ कॉन्फरन्सिंग"
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "इतरांना आमंत्रित करा",
|
||||
"youAreAlone": "आपण सभेत एकटाच आहात"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "मदत केंद्र"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Căști",
|
||||
"phone": "Telefon",
|
||||
"speaker": "Difuzor"
|
||||
"speaker": "Difuzor",
|
||||
"none": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Doar audio"
|
||||
@@ -51,7 +52,12 @@
|
||||
"popover": "Alegeți un pseudonim",
|
||||
"title": "Introduceți un pseudonim pentru a conversa"
|
||||
},
|
||||
"title": "Apel video"
|
||||
"title": "Apel video",
|
||||
"you": "",
|
||||
"privateNotice": "",
|
||||
"noMessagesMessage": "",
|
||||
"messageTo": "",
|
||||
"fieldPlaceHolder": ""
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Sunteți conectat la conversația dumneavoastră ..."
|
||||
@@ -66,7 +72,11 @@
|
||||
"DISCONNECTED": "Deconectat",
|
||||
"DISCONNECTING": "Se deconectează",
|
||||
"ERROR": "Eroare",
|
||||
"RECONNECTING": "A apărut o eroare de rețea. Reconectare..."
|
||||
"RECONNECTING": "A apărut o eroare de rețea. Reconectare...",
|
||||
"LOW_BANDWIDTH": "",
|
||||
"GOT_SESSION_ID": "",
|
||||
"GET_SESSION_ID_ERROR": "",
|
||||
"FETCH_SESSION_ID": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "Adresă:",
|
||||
@@ -97,7 +107,8 @@
|
||||
"status": "Conexiune:",
|
||||
"transport": "Mod Transport:",
|
||||
"transport_plural": "Moduri Transport:",
|
||||
"turn": " (turn)"
|
||||
"turn": " (turn)",
|
||||
"e2e_rtt": ""
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Mai devreme",
|
||||
@@ -109,6 +120,8 @@
|
||||
"description": "Nu s-a întâmplat nimic? Am încercat să vă deschidem conversația în {{app}} aplicația pentru desktop. Încercați din nou sau deschideți {{app}} aplicația web.",
|
||||
"descriptionWithoutWeb": "",
|
||||
"downloadApp": "Descărcați aplicația",
|
||||
"ifDoNotHaveApp": "Dacă nu aveti încă aplicația atunci:",
|
||||
"ifHaveApp": "Dacă aveti deja aplicația:",
|
||||
"launchWebButton": "Deschideți în browser",
|
||||
"openApp": "Continuați spre aplicație",
|
||||
"title": "Deschidere apel video în {{app}}...",
|
||||
@@ -256,13 +269,24 @@
|
||||
"WaitForHostMsgWOk": "Conferința {{room}} nu a început. Daca sunteți moderatorul, apăsați butonul OK pentru autentificare. Dacă nu, așteptați ca moderatorul să înceapă conferința.",
|
||||
"WaitingForHost": "Așteptare moderator conferință ...",
|
||||
"Yes": "Da",
|
||||
"yourEntireScreen": "Întregul ecran"
|
||||
"yourEntireScreen": "Întregul ecran",
|
||||
"sendPrivateMessageTitle": "",
|
||||
"sendPrivateMessageOk": "",
|
||||
"sendPrivateMessageCancel": "",
|
||||
"sendPrivateMessage": "",
|
||||
"screenSharingAudio": "",
|
||||
"muteEveryoneStartMuted": "",
|
||||
"muteEveryoneSelf": "",
|
||||
"muteEveryoneTitle": "",
|
||||
"muteEveryoneDialog": "",
|
||||
"muteEveryoneElseTitle": "",
|
||||
"muteEveryoneElseDialog": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "Este {{status}}"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Mediu",
|
||||
"average": "Medie",
|
||||
"bad": "Rău",
|
||||
"detailsLabel": "Spuneți-ne mai multe despre experiența dumneavoastră.",
|
||||
"good": "Bine",
|
||||
@@ -335,7 +359,8 @@
|
||||
"toggleFilmstrip": "Afișați sau ascundeți imagini video",
|
||||
"toggleScreensharing": "Comutați între cameră și partajare ecran",
|
||||
"toggleShortcuts": "Arătați sau ascundeți comenzi rapide tastatură",
|
||||
"videoMute": "Porniți sau opriți camera"
|
||||
"videoMute": "Porniți sau opriți camera",
|
||||
"videoQuality": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Lucrăm la eliberarea resurselor de transmitere. Vă rugam să încercați din nou în câteva minute.",
|
||||
@@ -363,7 +388,11 @@
|
||||
"signOut": "Deconectare",
|
||||
"start": "Începeți o transmitere live",
|
||||
"streamIdHelp": "Ce înseamnă acest lucru?",
|
||||
"unavailableTitle": "Transmitere live indisponibilă"
|
||||
"unavailableTitle": "Transmitere live indisponibilă",
|
||||
"onBy": "",
|
||||
"offBy": "",
|
||||
"googlePrivacyPolicy": "Politica de confidențialitate Google",
|
||||
"youtubeTerms": "Termeni și condiții Youtube"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -426,9 +455,10 @@
|
||||
"unmute": "",
|
||||
"newDeviceCameraTitle": "A fost detectată o cameră noua",
|
||||
"newDeviceAudioTitle": "A fost detectat un dispozitiv audio nou",
|
||||
"newDeviceAction": "Utilizați"
|
||||
"newDeviceAction": "Utilizați",
|
||||
"suboptimalBrowserWarning": "Folosind acest browser nu veți beneficia de cea mai bună experiență pentru aceste apeluri video. În timp ce lucrăm la asta, vă recomandăm să folosiți unul din <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser-ele suportate</a>."
|
||||
},
|
||||
"passwordSetRemotely": "Setat de un alt membru",
|
||||
"passwordSetRemotely": "Setată de un alt membru",
|
||||
"passwordDigitsOnly": "Până la {{number}} cifre",
|
||||
"poweredby": "cu sprijinul",
|
||||
"presenceStatus": {
|
||||
@@ -474,7 +504,9 @@
|
||||
"signIn": "Conectare",
|
||||
"signOut": "Deconectare",
|
||||
"unavailable": "Oops! Serviciul {{serviceName}} este indisponibil momentan. Se lucrează la remedierea acestei probleme. Vă rugam să încercați mai tărziu.",
|
||||
"unavailableTitle": "Înregistrare indisponibilă"
|
||||
"unavailableTitle": "Înregistrare indisponibilă",
|
||||
"onBy": "{{name}} a pornit înregistrarea",
|
||||
"offBy": "{{name}} a oprit înregistrarea"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Trageți pentru a reîmprospătă"
|
||||
@@ -499,8 +531,10 @@
|
||||
"selectCamera": "Cameră",
|
||||
"selectMic": "Microfon",
|
||||
"startAudioMuted": "Toată lumea începe cu sunetul dezactivat",
|
||||
"startVideoMuted": "Toată lumea începe cu sunetul dezactivat",
|
||||
"title": "Setări"
|
||||
"startVideoMuted": "Toată lumea începe cu fară video",
|
||||
"title": "Setări",
|
||||
"speakers": "Difuzoare",
|
||||
"microphones": "Microfoane"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
@@ -515,7 +549,11 @@
|
||||
"serverURL": "Server URL",
|
||||
"startWithAudioMuted": "Începeți cu sunetul dezactivat",
|
||||
"startWithVideoMuted": "Începeți cu video dezactivat",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"showAdvanced": "Arată setările avansate",
|
||||
"disableP2P": "Dezactivează modul Peer-To-Peer",
|
||||
"disableCallIntegration": "Dezactivează integrarea cu apelurile native",
|
||||
"advanced": "Avansat"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nVreti doar să apelați telefonic?\n\n{{defaultDialInNumber}}Faceți click pe acest link pentru a vizualiza numerele pentru apelare în acest apel video\n{{dialInfoPageUrl}}",
|
||||
@@ -525,10 +563,10 @@
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Name",
|
||||
"name": "Nume",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Parametrii difuzorului",
|
||||
"speakerTime": "Durată participant"
|
||||
"speakerStats": "Statistici participanți",
|
||||
"speakerTime": "Durată vorbire participant"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -572,9 +610,14 @@
|
||||
"tileView": "Afișați/ascundeți miniatura video",
|
||||
"toggleCamera": "Afișați / ascundeți camera",
|
||||
"videomute": "Activați / dezactivați înregistrarea",
|
||||
"videoblur": ""
|
||||
"videoblur": "",
|
||||
"privateMessage": "Trimite un mesaj privat",
|
||||
"muteEveryone": "Oprește microfonul tuturor",
|
||||
"moreOptions": "Arată mai multe opțiuni",
|
||||
"help": "Ajutor",
|
||||
"download": "Descarcă aplicațiile noastre"
|
||||
},
|
||||
"addPeople": "Adaugați persoane în apel",
|
||||
"addPeople": "Adăugați persoane în apel",
|
||||
"audioOnlyOff": "Dezactivați modul 'doar audio'",
|
||||
"audioOnlyOn": "Activați modul 'doar audio'",
|
||||
"audioRoute": "Selectați dispozitivul pentru sunet",
|
||||
@@ -664,9 +707,6 @@
|
||||
"lowDefinition": "Calitate redusă",
|
||||
"onlyAudioAvailable": "Doar audio este disponibil",
|
||||
"onlyAudioSupported": "În acest navigator este suportat doar 'mod audio'.",
|
||||
"p2pEnabled": "Peer-to-Peer activat",
|
||||
"p2pVideoQualityDescription": "În modul peer-to-peer pentru calitatea apelurilor primite puteți alege doar între calitate superioară sau 'mod audio'. Alte setări nu pot fi activate până când modul peer-to-peer nu este dezactivat.",
|
||||
"recHighDefinitionOnly": "Este de preferat o calitate superioară.",
|
||||
"sd": "SD",
|
||||
"standardDefinition": "Calitate standard"
|
||||
},
|
||||
@@ -698,7 +738,7 @@
|
||||
"go": "ÎNCEPEȚI",
|
||||
"join": "ACCESARE",
|
||||
"info": "Informații",
|
||||
"privacy": "Securitate",
|
||||
"privacy": "Confidențialitate",
|
||||
"recentList": "Recent",
|
||||
"recentListDelete": "Ștergeți",
|
||||
"recentListEmpty": "Lista dumneavoastră recentă este momentan goală. Discutați cu echipa dumneavoastră și veți găsi toate conversațiile aici.",
|
||||
@@ -707,13 +747,16 @@
|
||||
"roomnameHint": "Introduceți numele sau adresa web a ședinței la care doriți să vă conectați. Puteți asocia un nume, dar transmiteți și celorlalți participanți acest nume.",
|
||||
"sendFeedback": "Lăsați-ne feedback",
|
||||
"terms": "Termeni",
|
||||
"title": "Video-conferință securizata, cu opțiuni multiple și complet gratuită "
|
||||
"title": "Video-conferință securizată, cu multiple funcționalități și complet gratuită"
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "Invită alte persoane",
|
||||
"youAreAlone": "Ești singura persoană din acest apel"
|
||||
"documentSharing": {
|
||||
"title": ""
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Centru de ajutor"
|
||||
}
|
||||
"defaultNickname": "",
|
||||
"chromeExtensionBanner": {
|
||||
"dontShowAgain": "",
|
||||
"buttonText": "",
|
||||
"installExtensionText": ""
|
||||
},
|
||||
"raisedHand": "Ar dori să vorbească"
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
"remoteControlStopMessage": "Uzaktan kontrol oturumu sona erdi!",
|
||||
"remoteControlTitle": "Uzak masaüstü kontrolü",
|
||||
"Remove": "Kaldır",
|
||||
"removePassword": "Şifreyi kaldır",
|
||||
"removePassword": "Parolayı kaldır",
|
||||
"removeSharedVideoMsg": "Paylaşılan videonuzu kaldırmak istediğinizden emin misiniz?",
|
||||
"removeSharedVideoTitle": "Paylaşılan videoyu kaldır",
|
||||
"reservationError": "Rezervasyon sistemi hatası",
|
||||
@@ -289,8 +289,8 @@
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "Bilgiyi göster",
|
||||
"addPassword": "Şifre ekle",
|
||||
"cancelPassword": "Şifreyi iptal et",
|
||||
"addPassword": "Parola ekle",
|
||||
"cancelPassword": "Parolayı iptal et",
|
||||
"conferenceURL": "Bağlantı:",
|
||||
"country": "Ülke",
|
||||
"dialANumber": "Toplantınıza katılmak için bu numaralardan birini çevirin ve ardından kodu girin.",
|
||||
@@ -312,7 +312,7 @@
|
||||
"noPassword": "Yok",
|
||||
"noRoom": "Aranacak oda belirtilmedi.",
|
||||
"numbers": "Arama Numaraları",
|
||||
"password": "Şifre:",
|
||||
"password": "Parola:",
|
||||
"title": "Paylaş",
|
||||
"tooltip": "Bu toplantı için bağlantıyı ve arama bilgilerini paylaşın",
|
||||
"label": "Toplantı bilgileri"
|
||||
@@ -424,16 +424,16 @@
|
||||
"muted": "Görüşmeye sesiniz kapalı olarak başladınız.",
|
||||
"mutedTitle": "Sesiniz kapalı!",
|
||||
"mutedRemotelyTitle": "{{participantDisplayName}} tarafından sessize alındınız!",
|
||||
"mutedRemotelyDescription": "",
|
||||
"passwordRemovedRemotely": "",
|
||||
"passwordSetRemotely": "",
|
||||
"mutedRemotelyDescription": "Konuşmaya hazır olduğun zaman, Kendi mikrofonunu açabilirsin. Görüşmeden gürültüyü uzak tutmak için kendini tekrar sessize almalısın.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) başka bir katılımcı tarafından kaldırıldı",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) başka bir katılımcı tarafından ayarlandı",
|
||||
"raisedHand": "{{name}} konuşmak istiyor.",
|
||||
"somebody": "Birisi",
|
||||
"startSilentTitle": "",
|
||||
"startSilentDescription": "",
|
||||
"suboptimalExperienceDescription": "Mmm... {{appName}} ile olan deneyiminizin burada çok iyi olmayacağından korkuyoruz. Bunu iyileştirmenin yollarını arıyoruz, ancak o zamana kadar lütfen şunlardan birini deneyin: <a href='{{recommendedBrowserPageLink}}' target='_blank'>fully supported browsers</a>.",
|
||||
"startSilentTitle": "Ses çıkışı olmadan bağlandınız",
|
||||
"startSilentDescription": "Ses çıkışını açtıktan sonra tekrar bağlanın",
|
||||
"suboptimalExperienceDescription": "Mmm... {{appName}} ile olan deneyiminizin burada çok iyi olmayacağından korkuyoruz. Bunu iyileştirmenin yollarını arıyoruz, ancak o zamana kadar lütfen şunlardan birini deneyin: <a href='{{recommendedBrowserPageLink}}' target='_blank'>desteklenen tarayıcılar</a>.",
|
||||
"suboptimalExperienceTitle": "Tarayıcı Uyarısı",
|
||||
"unmute": "",
|
||||
"unmute": "Sessizden çıkar",
|
||||
"newDeviceCameraTitle": "Yeni kamera algılandı",
|
||||
"newDeviceAudioTitle": "Yeni ses aygıtı algılandı",
|
||||
"newDeviceAction": "Kullan"
|
||||
@@ -577,7 +577,7 @@
|
||||
"shareRoom": "Birini davet et",
|
||||
"shareYourScreen": "Ekran paylaşımını aç/kapat",
|
||||
"shortcuts": "Kısayolları aç/kapat",
|
||||
"show": "",
|
||||
"show": "Sahnede göster",
|
||||
"speakerStats": "Konuşmacı istatistiklerini aç/kapat",
|
||||
"tileView": "Döşeme görünümünü aç/kapat",
|
||||
"toggleCamera": "Kamerayı aç/kapat",
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
"kickParticipantTitle": "Kick this participant?",
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
"lockMessage": "Failed to lock the conference.",
|
||||
@@ -249,6 +250,7 @@
|
||||
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
|
||||
"popupErrorTitle": "Pop-up blocked",
|
||||
"recording": "Recording",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
|
||||
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
|
||||
"recordingDisabledTooltip": "Start recording disabled.",
|
||||
"rejoinNow": "Rejoin now",
|
||||
@@ -393,6 +395,8 @@
|
||||
"videoQuality": "Manage call quality"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Your streaming will be limited to {{limit}} min. For unlimited streaming try {{app}}.",
|
||||
"busy": "We're working on freeing streaming resources. Please try again in a few minutes.",
|
||||
"busyTitle": "All streamers are currently busy",
|
||||
"changeSignIn": "Switch accounts.",
|
||||
@@ -550,6 +554,8 @@
|
||||
},
|
||||
"raisedHand": "Would like to speak",
|
||||
"recording": {
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
|
||||
"authDropboxText": "Upload to Dropbox",
|
||||
"availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)",
|
||||
"beta": "BETA",
|
||||
@@ -669,6 +675,7 @@
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"kick": "Kick participant",
|
||||
"lobbyButton": "Enable/disable lobby mode",
|
||||
"localRecording": "Toggle local recording controls",
|
||||
"lockRoom": "Toggle meeting password",
|
||||
"moreActions": "Toggle more actions menu",
|
||||
@@ -716,6 +723,8 @@
|
||||
"hangup": "Leave",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"lobbyButtonDisable": "Disable lobby mode",
|
||||
"lobbyButtonEnable": "Enable lobby mode",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"lowerYourHand": "Lower your hand",
|
||||
@@ -855,5 +864,32 @@
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Help center"
|
||||
},
|
||||
"lobby": {
|
||||
"allow": "Allow",
|
||||
"backToKnockModeButton": "No password, ask to join instead",
|
||||
"dialogTitle": "Lobby mode",
|
||||
"disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
|
||||
"disableDialogSubmit": "Disable",
|
||||
"emailField": "Enter your email address",
|
||||
"enableDialogPasswordField": "Set password (optional)",
|
||||
"enableDialogSubmit": "Enable",
|
||||
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
|
||||
"enterPasswordButton": "Enter meeting password",
|
||||
"enterPasswordTitle": "Enter password to join meeting",
|
||||
"invalidPassword": "Invalid password",
|
||||
"joiningMessage": "You'll join the meeting as soon as someone accepts your request",
|
||||
"joinWithPasswordMessage": "Trying to join with password, please wait...",
|
||||
"joinRejectedMessage": "Your join request was rejected by a moderator.",
|
||||
"joinTitle": "Join Meeting",
|
||||
"joiningTitle": "Asking to join meeting...",
|
||||
"joiningWithPasswordTitle": "Joining with password...",
|
||||
"knockButton": "Ask to Join",
|
||||
"knockTitle": "Someone wants to join the meeting",
|
||||
"nameField": "Enter your name",
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
"toggleLabel": "Enable lobby"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,21 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import {
|
||||
getCurrentConference,
|
||||
sendTones,
|
||||
setPassword,
|
||||
setSubject
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
|
||||
import { setE2EEKey } from '../../react/features/e2ee';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession } from '../../react/features/recording/functions';
|
||||
import { muteAllParticipants } from '../../react/features/remote-video-menu/actions';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { setVideoQuality } from '../../react/features/video-quality';
|
||||
@@ -190,6 +195,114 @@ function initCommands() {
|
||||
logger.debug('Set video quality command received');
|
||||
sendAnalytics(createApiEvent('set.video.quality'));
|
||||
APP.store.dispatch(setVideoQuality(frameHeight));
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a file recording or streaming depending on the passed on params.
|
||||
* For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
|
||||
* For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
|
||||
* For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
|
||||
* No other params should be passed.
|
||||
*
|
||||
* @param { string } arg.mode - Recording mode, either `file` or `stream`.
|
||||
* @param { string } arg.dropboxToken - Dropbox oauth2 token.
|
||||
* @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
|
||||
* Only applies to certain jitsi meet deploys.
|
||||
* @param { string } arg.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
|
||||
* @returns {void}
|
||||
*/
|
||||
'start-recording': ({ mode, dropboxToken, shouldShare, youtubeStreamKey, youtubeBroadcastID }) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken && !isDropboxEnabled(state)) {
|
||||
logger.error('Failed starting recording: dropbox is not enabled on this deployment');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !youtubeStreamKey) {
|
||||
logger.error('Failed starting recording: missing youtube stream key');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let recordingConfig;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
if (dropboxToken) {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
'upload_credentials': {
|
||||
'service_name': RECORDING_TYPES.DROPBOX,
|
||||
'token': dropboxToken
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
} else {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
'share': shouldShare
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey
|
||||
};
|
||||
} else {
|
||||
logger.error('Invalid recording mode provided');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.startRecording(recordingConfig);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops a recording or streaming in progress.
|
||||
*
|
||||
* @param {string} mode - `file` or `stream`.
|
||||
* @returns {void}
|
||||
*/
|
||||
'stop-recording': mode => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
|
||||
logger.error('Invalid recording mode provided!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (activeSession && activeSession.id) {
|
||||
conference.stopRecording(activeSession.id);
|
||||
} else {
|
||||
logger.error('No recording or streaming session found');
|
||||
}
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
@@ -514,7 +627,8 @@ class API {
|
||||
notifyDeviceListChanged(devices: Object) {
|
||||
this._sendEvent({
|
||||
name: 'device-list-changed',
|
||||
devices });
|
||||
devices
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -37,6 +37,8 @@ const commands = {
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendTones: 'send-tones',
|
||||
setVideoQuality: 'set-video-quality',
|
||||
startRecording: 'start-recording',
|
||||
stopRecording: 'stop-recording',
|
||||
subject: 'subject',
|
||||
submitFeedback: 'submit-feedback',
|
||||
toggleAudio: 'toggle-audio',
|
||||
|
||||
@@ -98,19 +98,6 @@ UI.notifyReservationError = function(code, msg) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that conference was destroyed.
|
||||
* @param reason {string} the reason text
|
||||
*/
|
||||
UI.notifyConferenceDestroyed = function(reason) {
|
||||
// FIXME: use Session Terminated from translation, but
|
||||
// 'reason' text comes from XMPP packet and is not translated
|
||||
messageHandler.showError({
|
||||
description: reason,
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change nickname for the user.
|
||||
* @param {string} id user id
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SHARED_VIDEO_CONTAINER_TYPE = 'sharedvideo';
|
||||
* Example shared video link.
|
||||
* @type {string}
|
||||
*/
|
||||
const defaultSharedVideoLink = 'https://www.youtube.com/watch?v=xNXN7CZk8X0';
|
||||
const defaultSharedVideoLink = 'https://youtu.be/TB7LlM4erx8';
|
||||
const updateInterval = 5000; // milliseconds
|
||||
|
||||
/**
|
||||
|
||||
@@ -498,9 +498,6 @@ export class VideoContainer extends LargeContainer {
|
||||
});
|
||||
|
||||
this._updateBackground();
|
||||
|
||||
// Reset the large video background depending on the stream.
|
||||
this.setLargeVideoBackground(this.avatarDisplayed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,14 +530,6 @@ export class VideoContainer extends LargeContainer {
|
||||
* @param {boolean} show
|
||||
*/
|
||||
showAvatar(show) {
|
||||
// TO FIX: Video background need to be black, so that we don't have a
|
||||
// flickering effect when scrolling between videos and have the screen
|
||||
// move to grey before going back to video. Avatars though can have the
|
||||
// default background set.
|
||||
// In order to fix this code we need to introduce video background or
|
||||
// find a workaround for the video flickering.
|
||||
this.setLargeVideoBackground(show);
|
||||
|
||||
this.$avatar.css('visibility', show ? 'visible' : 'hidden');
|
||||
this.avatarDisplayed = show;
|
||||
|
||||
@@ -596,21 +585,6 @@ export class VideoContainer extends LargeContainer {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the large video container background depending on the container
|
||||
* type and the parameter indicating if an avatar is currently shown on
|
||||
* large.
|
||||
*
|
||||
* @param {boolean} isAvatar - Indicates if the avatar is currently shown
|
||||
* on the large video.
|
||||
* @returns {void}
|
||||
*/
|
||||
setLargeVideoBackground(isAvatar) {
|
||||
$('#largeVideoContainer').css('background',
|
||||
this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar
|
||||
? '#000' : interfaceConfig.DEFAULT_BACKGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when the video element changes dimensions.
|
||||
*
|
||||
|
||||
3156
package-lock.json
generated
3156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -50,13 +50,13 @@
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
|
||||
"jquery": "3.4.0",
|
||||
"jquery": "3.5.1",
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"js-utils": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c94f6a570f69ebfe18de6c1549cc76370c791468",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#6af8eee57d1ebdc0881c8c2875d4346e02d01549",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -69,7 +69,7 @@
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
|
||||
"react-native-background-timer": "2.1.1",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
|
||||
"react-native-callstats": "3.61.0",
|
||||
"react-native-collapsible": "1.5.1",
|
||||
"react-native-default-preference": "1.4.2",
|
||||
@@ -80,7 +80,7 @@
|
||||
"react-native-svg": "9.7.1",
|
||||
"react-native-svg-transformer": "0.13.0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.75.3",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
@@ -110,6 +110,7 @@
|
||||
"@babel/runtime": "7.5.5",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-loader": "8.0.4",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css": "3.4.25",
|
||||
"css-loader": "0.28.7",
|
||||
"eslint": "5.6.1",
|
||||
@@ -124,14 +125,14 @@
|
||||
"imports-loader": "0.7.1",
|
||||
"jetifier": "1.6.4",
|
||||
"metro-react-native-babel-preset": "0.56.0",
|
||||
"node-sass": "4.13.1",
|
||||
"node-sass": "4.14.1",
|
||||
"string-replace-loader": "2.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"unorm": "1.6.0",
|
||||
"webpack": "4.27.1",
|
||||
"webpack": "4.43.0",
|
||||
"webpack-bundle-analyzer": "3.4.1",
|
||||
"webpack-cli": "3.1.2",
|
||||
"webpack-dev-server": "3.8.2"
|
||||
"webpack-cli": "3.3.11",
|
||||
"webpack-dev-server": "3.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
parseURIString,
|
||||
toURLString
|
||||
} from '../base/util';
|
||||
import { showNotification } from '../notifications';
|
||||
import { clearNotifications, showNotification } from '../notifications';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
import {
|
||||
@@ -79,6 +79,10 @@ export function appNavigate(uri: ?string) {
|
||||
dispatch(disconnect());
|
||||
}
|
||||
|
||||
// There are notifications now that gets displayed after we technically left
|
||||
// the conference, but we're still on the conference screen.
|
||||
dispatch(clearNotifications());
|
||||
|
||||
dispatch(configWillLoad(locationURL, room));
|
||||
|
||||
let protocol = location.protocol.toLowerCase();
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../base/lastn'; // Register lastN middleware
|
||||
import { toURLString } from '../../base/util';
|
||||
import '../../follow-me';
|
||||
import { OverlayContainer } from '../../overlay';
|
||||
import '../../lobby'; // Import lobby function
|
||||
import '../../rejoin'; // Enable rejoin analytics
|
||||
import { appNavigate } from '../actions';
|
||||
import { getDefaultURL } from '../functions';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { DialogContainer } from '../../base/dialog';
|
||||
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import '../../base/responsive-ui';
|
||||
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import '../../google-api';
|
||||
import '../../mobile/audio-mode';
|
||||
@@ -78,6 +78,9 @@ export class App extends AbstractApp {
|
||||
// This will effectively kill the app. In accord with the Web, do not
|
||||
// kill the app.
|
||||
this._maybeDisableExceptionsManager();
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +110,21 @@ export class App extends AbstractApp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link DimensionsDetector} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
{ super._createMainElement(component, props) }
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
@@ -144,6 +162,22 @@ export class App extends AbstractApp {
|
||||
}
|
||||
}
|
||||
|
||||
_onDimensionsChanged: (width: number, height: number) => void;
|
||||
|
||||
/**
|
||||
* Updates the known available size for the app to occupy.
|
||||
*
|
||||
* @param {number} width - The component's current width.
|
||||
* @param {number} height - The component's current height.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
const { dispatch } = this.state.store;
|
||||
|
||||
dispatch(clientResized(width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
|
||||
initialsText: (size: number = DEFAULT_SIZE) => {
|
||||
return {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
color: 'white',
|
||||
fontSize: size * 0.45,
|
||||
fontWeight: '100'
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from '../participants';
|
||||
import { getLocalTracks, trackAdded, trackRemoved } from '../tracks';
|
||||
import {
|
||||
getBackendSafePath,
|
||||
getBackendSafeRoomName,
|
||||
getJitsiMeetGlobalNS
|
||||
} from '../util';
|
||||
@@ -248,6 +249,7 @@ export function authStatusChanged(authEnabled: boolean, authLogin: string) {
|
||||
* @param {JitsiConference} conference - The JitsiConference that has failed.
|
||||
* @param {string} error - The error describing/detailing the cause of the
|
||||
* failure.
|
||||
* @param {any} params - Rest of the params that we receive together with the event.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_FAILED,
|
||||
* conference: JitsiConference,
|
||||
@@ -255,7 +257,7 @@ export function authStatusChanged(authEnabled: boolean, authLogin: string) {
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
export function conferenceFailed(conference: Object, error: string) {
|
||||
export function conferenceFailed(conference: Object, error: string, ...params: any) {
|
||||
return {
|
||||
type: CONFERENCE_FAILED,
|
||||
conference,
|
||||
@@ -264,6 +266,7 @@ export function conferenceFailed(conference: Object, error: string) {
|
||||
// jitsi-meet needs it).
|
||||
error: {
|
||||
name: error,
|
||||
params,
|
||||
recoverable: undefined
|
||||
}
|
||||
};
|
||||
@@ -417,7 +420,9 @@ export function createConference() {
|
||||
}
|
||||
|
||||
const config = state['features/base/config'];
|
||||
const { tenant } = state['features/base/jwt'];
|
||||
const { email, name: nick } = getLocalParticipant(state);
|
||||
|
||||
const conference
|
||||
= connection.initJitsiConference(
|
||||
|
||||
@@ -425,7 +430,8 @@ export function createConference() {
|
||||
...config,
|
||||
applicationName: getName(),
|
||||
getWiFiStatsMethod: getJitsiMeetGlobalNS().getWiFiStats,
|
||||
confID: `${locationURL.host}${locationURL.pathname}`,
|
||||
confID: `${locationURL.host}${getBackendSafePath(locationURL.pathname)}`,
|
||||
siteID: tenant,
|
||||
statisticsDisplayName: config.enableDisplayNameInStats ? nick : undefined,
|
||||
statisticsId: config.enableEmailInStats ? email : undefined
|
||||
});
|
||||
@@ -646,28 +652,23 @@ export function setPassword(
|
||||
case conference.join: {
|
||||
let state = getState()['features/base/conference'];
|
||||
|
||||
// Make sure that the action will set a password for a conference
|
||||
// that the application wants joined.
|
||||
if (state.passwordRequired === conference) {
|
||||
dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
});
|
||||
dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
});
|
||||
|
||||
// Join the conference with the newly-set password.
|
||||
// Join the conference with the newly-set password.
|
||||
|
||||
// Make sure that the action did set the password.
|
||||
state = getState()['features/base/conference'];
|
||||
if (state.password === password
|
||||
&& !state.passwordRequired
|
||||
// Make sure that the action did set the password.
|
||||
state = getState()['features/base/conference'];
|
||||
if (state.password === password
|
||||
|
||||
// Make sure that the application still wants the
|
||||
// conference joined.
|
||||
&& !state.conference) {
|
||||
method.call(conference, password);
|
||||
}
|
||||
// Make sure that the application still wants the
|
||||
// conference joined.
|
||||
&& !state.conference) {
|
||||
method.call(conference, password);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ export function getConferenceTimestamp(stateful: Function | Object): number {
|
||||
* @returns {JitsiConference|undefined}
|
||||
*/
|
||||
export function getCurrentConference(stateful: Function | Object) {
|
||||
const { conference, joining, leaving, passwordRequired }
|
||||
const { conference, joining, leaving, membersOnly, passwordRequired }
|
||||
= toState(stateful)['features/base/conference'];
|
||||
|
||||
// There is a precendence
|
||||
@@ -211,7 +211,7 @@ export function getCurrentConference(stateful: Function | Object) {
|
||||
return conference === leaving ? undefined : conference;
|
||||
}
|
||||
|
||||
return joining || passwordRequired;
|
||||
return joining || passwordRequired || membersOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { openDisplayNamePrompt } from '../../display-name';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection';
|
||||
import { showErrorNotification } from '../../notifications';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
import {
|
||||
@@ -140,13 +141,40 @@ StateListenerRegistry.register(
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceFailed(store, next, action) {
|
||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const { conference, error } = action;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.OFFER_ANSWER_FAILED) {
|
||||
// Handle specific failure reasons.
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = error.params;
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
description: reason,
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
}));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.UI.hideStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
||||
const [ msg ] = error.params;
|
||||
|
||||
dispatch(connectionDisconnected(getState()['features/base/connection'].connection));
|
||||
dispatch(showErrorNotification({
|
||||
descriptionArguments: { msg },
|
||||
descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
|
||||
titleKey: 'connection.CONNFAIL'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
|
||||
@@ -36,6 +36,7 @@ const DEFAULT_STATE = {
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.HIGH,
|
||||
membersOnly: undefined,
|
||||
password: undefined,
|
||||
passwordRequired: undefined,
|
||||
preferredVideoQuality: VIDEO_QUALITY_LEVELS.HIGH
|
||||
@@ -161,6 +162,7 @@ function _conferenceFailed(state, { conference, error }) {
|
||||
}
|
||||
|
||||
let authRequired;
|
||||
let membersOnly;
|
||||
let passwordRequired;
|
||||
|
||||
switch (error.name) {
|
||||
@@ -168,6 +170,11 @@ function _conferenceFailed(state, { conference, error }) {
|
||||
authRequired = conference;
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED:
|
||||
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR:
|
||||
membersOnly = conference;
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.PASSWORD_REQUIRED:
|
||||
passwordRequired = conference;
|
||||
break;
|
||||
@@ -189,6 +196,7 @@ function _conferenceFailed(state, { conference, error }) {
|
||||
* @type {string}
|
||||
*/
|
||||
locked: passwordRequired ? LOCKED_REMOTELY : undefined,
|
||||
membersOnly,
|
||||
password: undefined,
|
||||
|
||||
/**
|
||||
@@ -232,6 +240,7 @@ function _conferenceJoined(state, { conference }) {
|
||||
e2eeSupported: conference.isE2EESupported(),
|
||||
|
||||
joining: undefined,
|
||||
membersOnly: undefined,
|
||||
leaving: undefined,
|
||||
|
||||
/**
|
||||
@@ -378,34 +387,30 @@ function _setDesktopSharingEnabled(state, action) {
|
||||
function _setPassword(state, { conference, method, password }) {
|
||||
switch (method) {
|
||||
case conference.join:
|
||||
if (state.passwordRequired === conference) {
|
||||
return assign(state, {
|
||||
// XXX 1. The JitsiConference which transitions away from
|
||||
// passwordRequired MUST remain in the redux state
|
||||
// features/base/conference until it transitions into
|
||||
// conference; otherwise, there is a span of time during which
|
||||
// the redux state does not even know that there is a
|
||||
// JitsiConference whatsoever.
|
||||
//
|
||||
// 2. The redux action setPassword will attempt to join the
|
||||
// JitsiConference so joining is an appropriate transitional
|
||||
// redux state.
|
||||
//
|
||||
// 3. The redux action setPassword will perform the same check
|
||||
// before it proceeds with the re-join.
|
||||
joining: state.conference ? state.joining : conference,
|
||||
locked: LOCKED_REMOTELY,
|
||||
return assign(state, {
|
||||
// 1. The JitsiConference which transitions away from
|
||||
// passwordRequired MUST remain in the redux state
|
||||
// features/base/conference until it transitions into
|
||||
// conference; otherwise, there is a span of time during which
|
||||
// the redux state does not even know that there is a
|
||||
// JitsiConference whatsoever.
|
||||
//
|
||||
// 2. The redux action setPassword will attempt to join the
|
||||
// JitsiConference so joining is an appropriate transitional
|
||||
// redux state.
|
||||
//
|
||||
// 3. The redux action setPassword will perform the same check
|
||||
// before it proceeds with the re-join.
|
||||
joining: state.conference ? state.joining : conference,
|
||||
locked: LOCKED_REMOTELY,
|
||||
|
||||
/**
|
||||
* The password with which the conference is to be joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
password,
|
||||
passwordRequired: undefined
|
||||
});
|
||||
}
|
||||
break;
|
||||
/**
|
||||
* The password with which the conference is to be joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
password
|
||||
});
|
||||
|
||||
case conference.lock:
|
||||
return assign(state, {
|
||||
|
||||
@@ -18,6 +18,7 @@ export default [
|
||||
'CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT',
|
||||
'CONNECTION_INDICATOR_DISABLED',
|
||||
'DEFAULT_BACKGROUND',
|
||||
'DEFAULT_LOGO_URL',
|
||||
'DISABLE_PRESENCE_STATUS',
|
||||
'DISABLE_JOIN_LEAVE_NOTIFICATIONS',
|
||||
'DEFAULT_LOCAL_DISPLAY_NAME',
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
*
|
||||
* {
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* connection: JitsiConnection
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_DISCONNECTED = 'CONNECTION_DISCONNECTED';
|
||||
|
||||
@@ -113,13 +113,12 @@ export function connect(id: ?string, password: ?string) {
|
||||
* Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @param {string} message - Disconnect reason.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionDisconnected(message: string) {
|
||||
function _onConnectionDisconnected() {
|
||||
unsubscribe();
|
||||
dispatch(_connectionDisconnected(connection, message));
|
||||
dispatch(connectionDisconnected(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,19 +186,16 @@ export function connect(id: ?string, password: ?string) {
|
||||
*
|
||||
* @param {JitsiConnection} connection - The {@code JitsiConnection} which
|
||||
* disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
function _connectionDisconnected(connection: Object, message: string) {
|
||||
export function connectionDisconnected(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { configureInitialDevices } from '../devices';
|
||||
import { getBackendSafeRoomName } from '../util';
|
||||
|
||||
export {
|
||||
connectionDisconnected,
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
setLocationURL
|
||||
|
||||
@@ -243,8 +243,10 @@ export function setAudioInputDeviceAndUpdateSettings(deviceId) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setAudioOutputDevice(deviceId) {
|
||||
return function(dispatch) {
|
||||
return setAudioOutputDeviceId(deviceId, dispatch);
|
||||
return function(dispatch, getState) {
|
||||
const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioOutput');
|
||||
|
||||
return setAudioOutputDeviceId(deviceId, dispatch, true, deviceLabel);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ import logger from './logger';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
const webrtcKindToJitsiKindTranslator = {
|
||||
audioinput: 'audioInput',
|
||||
audiooutput: 'audioOutput',
|
||||
videoinput: 'videoInput'
|
||||
};
|
||||
|
||||
/**
|
||||
* Detects the use case when the labels are not available if the A/V permissions
|
||||
* are not yet granted.
|
||||
@@ -41,6 +47,29 @@ export function getAudioOutputDeviceId() {
|
||||
return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the real device id of the default device of the given type.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {*} kind - The type of the device. One of "audioInput",
|
||||
* "audioOutput", and "videoInput". Also supported is all lowercase versions
|
||||
* of the preceding types.
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getDefaultDeviceId(state: Object, kind: string) {
|
||||
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
|
||||
const defaultDevice = (state['features/base/devices'].availableDevices[kindToSearch] || [])
|
||||
.find(d => d.deviceId === 'default');
|
||||
|
||||
// Find the device with a matching group id.
|
||||
const matchingDevice = (state['features/base/devices'].availableDevices[kindToSearch] || [])
|
||||
.find(d => d.deviceId !== 'default' && d.groupId === defaultDevice.groupId);
|
||||
|
||||
if (matchingDevice) {
|
||||
return matchingDevice.deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a device with a label that matches the passed label and returns its id.
|
||||
*
|
||||
@@ -52,12 +81,6 @@ export function getAudioOutputDeviceId() {
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
|
||||
const webrtcKindToJitsiKindTranslator = {
|
||||
audioinput: 'audioInput',
|
||||
audiooutput: 'audioOutput',
|
||||
videoinput: 'videoInput'
|
||||
};
|
||||
|
||||
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
|
||||
|
||||
const device
|
||||
@@ -80,12 +103,6 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getDeviceLabelById(state: Object, id: string, kind: string) {
|
||||
const webrtcKindToJitsiKindTranslator = {
|
||||
audioinput: 'audioInput',
|
||||
audiooutput: 'audioOutput',
|
||||
videoinput: 'videoInput'
|
||||
};
|
||||
|
||||
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
|
||||
|
||||
const device
|
||||
|
||||
@@ -30,14 +30,17 @@ export function hideDialog(component: ?Object) {
|
||||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
* @param {boolean} rawDialog - True if the dialog is a raw dialog.
|
||||
* (Doesn't inherit behavior from other common frameworks).
|
||||
* @returns {{
|
||||
* type: OPEN_DIALOG,
|
||||
* component: React.Component,
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openDialog(component: Object, componentProps: ?Object) {
|
||||
export function openDialog(component: Object, componentProps: ?Object, rawDialog?: boolean) {
|
||||
return {
|
||||
rawDialog,
|
||||
type: OPEN_DIALOG,
|
||||
component,
|
||||
componentProps
|
||||
|
||||
@@ -17,6 +17,11 @@ type Props = {
|
||||
*/
|
||||
_componentProps: Object,
|
||||
|
||||
/**
|
||||
* True if the dialog is a raw dialog (doesn't inherit behavior from other common frameworks, such as atlaskit).
|
||||
*/
|
||||
_rawDialog: boolean,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
@@ -52,19 +57,16 @@ export default class AbstractDialogContainer extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _component: React.Component,
|
||||
* _componentProps: Object,
|
||||
* _reducedUI: boolean
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: Object) {
|
||||
export function abstractMapStateToProps(state: Object): $Shape<Props> {
|
||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_component: stateFeaturesBaseDialog.component,
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||
_rawDialog: stateFeaturesBaseDialog.rawDialog,
|
||||
_reducedUI: reducedUI
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,14 +57,13 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
<KeyboardAvoidingView
|
||||
behavior = 'height'
|
||||
style = { [
|
||||
styles.overlay,
|
||||
style
|
||||
styles.overlay
|
||||
] }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
_dialogStyles.dialog,
|
||||
this.props.style
|
||||
style
|
||||
] }>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onCancel }
|
||||
|
||||
@@ -34,7 +34,7 @@ class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
|
||||
* @returns {string}
|
||||
*/
|
||||
_getSubmitButtonKey() {
|
||||
return 'dialog.Ok';
|
||||
return this.props.okKey || 'dialog.Ok';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,11 @@ import StatelessDialog from './StatelessDialog';
|
||||
*/
|
||||
type Props = AbstractDialogProps & {
|
||||
|
||||
/**
|
||||
* True if listening for the Enter key should be disabled.
|
||||
*/
|
||||
disableEnter: boolean,
|
||||
|
||||
/**
|
||||
* Whether the dialog is modal. This means clicking on the blanket will
|
||||
* leave the dialog open. No cancel button.
|
||||
|
||||
@@ -20,6 +20,10 @@ class DialogContainer extends AbstractDialogContainer {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (this.props._rawDialog) {
|
||||
return this._renderDialogContent();
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalTransition>
|
||||
{ this._renderDialogContent() }
|
||||
|
||||
@@ -33,6 +33,11 @@ type Props = {
|
||||
*/
|
||||
customHeader?: React$Element<any> | Function,
|
||||
|
||||
/*
|
||||
* True if listening for the Enter key should be disabled.
|
||||
*/
|
||||
disableEnter: boolean,
|
||||
|
||||
/**
|
||||
* Disables dismissing the dialog when the blanket is clicked. Enabled
|
||||
* by default.
|
||||
@@ -313,7 +318,7 @@ class StatelessDialog extends Component<Props> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
if (event.key === 'Enter' && !this.props.disableEnter) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
|
||||
if (typeof component === 'undefined' || state.component === component) {
|
||||
return assign(state, {
|
||||
component: undefined,
|
||||
componentProps: undefined
|
||||
componentProps: undefined,
|
||||
rawDialog: false
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -30,7 +31,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
|
||||
case OPEN_DIALOG:
|
||||
return assign(state, {
|
||||
component: action.component,
|
||||
componentProps: action.componentProps
|
||||
componentProps: action.componentProps,
|
||||
rawDialog: action.rawDialog
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const CALENDAR_ENABLED = 'calendar.enabled';
|
||||
export const CALL_INTEGRATION_ENABLED = 'call-integration.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if chat should be enabled.
|
||||
* Flag indicating if close captions should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const CLOSE_CAPTIONS_ENABLED = 'close-captions.enabled';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z" fill="#5E6D7A"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 374 B |
1
react/features/base/icons/svg/edit.svg
Normal file
1
react/features/base/icons/svg/edit.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||
|
After Width: | Height: | Size: 287 B |
@@ -31,6 +31,7 @@ export { default as IconDominantSpeaker } from './dominant-speaker.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconDragHandle } from './drag-handle.svg';
|
||||
export { default as IconE2EE } from './e2ee.svg';
|
||||
export { default as IconEdit } from './edit.svg';
|
||||
export { default as IconEmail } from './envelope.svg';
|
||||
export { default as IconEventNote } from './event_note.svg';
|
||||
export { default as IconExclamation } from './exclamation.svg';
|
||||
@@ -46,6 +47,8 @@ export { default as IconInviteMore } from './user-plus.svg';
|
||||
export { default as IconKick } from './kick.svg';
|
||||
export { default as IconLiveStreaming } from './public.svg';
|
||||
export { default as IconLockPassword } from './lock.svg';
|
||||
export { default as IconMeetingLocked } from './meeting-locked.svg';
|
||||
export { default as IconMeetingUnlocked } from './meeting-unlocked.svg';
|
||||
export { default as IconMenu } from './menu.svg';
|
||||
export { default as IconMenuDown } from './menu-down.svg';
|
||||
export { default as IconMenuThumb } from './thumb-menu.svg';
|
||||
|
||||
1
react/features/base/icons/svg/meeting-locked.svg
Normal file
1
react/features/base/icons/svg/meeting-locked.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M11 11h-1v2h2v-1l9.73 9.73L20.46 23 14 16.54V21H3v-2h2V7.54l-4-4 1.27-1.27L11 11zm3 .49L5.51 3H14v1h5v12.49l-2-2V6h-3v5.49z"/></svg>
|
||||
|
After Width: | Height: | Size: 224 B |
1
react/features/base/icons/svg/meeting-unlocked.svg
Normal file
1
react/features/base/icons/svg/meeting-unlocked.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M14 6v15H3v-2h2V3h9v1h5v15h2v2h-4V6h-3zm-4 5v2h2v-2h-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
@@ -141,11 +141,12 @@ function _setJWT(store, next, action) {
|
||||
action.jwt = jwt;
|
||||
action.issuer = iss;
|
||||
if (context) {
|
||||
const user = _user2participant(context.user);
|
||||
const user = _user2participant(context.user || {});
|
||||
|
||||
action.callee = context.callee;
|
||||
action.group = context.group;
|
||||
action.server = context.server;
|
||||
action.tenant = context.tenant;
|
||||
action.user = user;
|
||||
|
||||
user && _overwriteLocalParticipant(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference';
|
||||
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference/actionTypes';
|
||||
import { ReducerRegistry } from '../redux';
|
||||
import { TRACK_REMOVED } from '../tracks';
|
||||
import { TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
|
||||
import {
|
||||
SET_AUDIO_AVAILABLE,
|
||||
|
||||
3
react/features/base/premeeting/components/index.web.js
Normal file
3
react/features/base/premeeting/components/index.web.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -0,0 +1,77 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, IconArrowDown } from '../../../icons';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Text of the button.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Text css class of the button.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* If the button is disabled or not.
|
||||
*/
|
||||
disabled?: boolean,
|
||||
|
||||
/**
|
||||
* If the button has options.
|
||||
*/
|
||||
hasOptions?: boolean,
|
||||
|
||||
/**
|
||||
* The type of th button: primary, secondary, text.
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* OnClick button handler.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* Click handler for options.
|
||||
*/
|
||||
onOptionsClick?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Button used for pre meeting actions.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ActionButton({
|
||||
children,
|
||||
className = '',
|
||||
disabled,
|
||||
hasOptions,
|
||||
type = 'primary',
|
||||
onClick,
|
||||
onOptionsClick
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className = { `action-btn ${className} ${type} ${disabled ? 'disabled' : ''}` }
|
||||
onClick = { disabled ? undefined : onClick }>
|
||||
{children}
|
||||
{hasOptions && <div
|
||||
className = 'options'
|
||||
onClick = { disabled ? undefined : onOptionsClick }>
|
||||
<Icon
|
||||
className = 'icon'
|
||||
size = { 14 }
|
||||
src = { IconArrowDown } />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionButton;
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { getCurrentConferenceUrl } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getCurrentConferenceUrl } from '../../../connection';
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../icons';
|
||||
import { connect } from '../../../redux';
|
||||
import logger from '../../logger';
|
||||
|
||||
type Props = {
|
||||
@@ -108,7 +108,8 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
*/
|
||||
_hideCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: false
|
||||
showCopyLink: false,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,7 +123,8 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
*/
|
||||
_showCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: true
|
||||
showCopyLink: true,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,35 +154,30 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
const { url, t } = this.props;
|
||||
const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
|
||||
const src = showLinkCopied ? IconCheck : IconCopy;
|
||||
const iconCls = showCopyLink || showCopyLink ? 'prejoin-copy-icon--white' : 'prejoin-copy-icon--light';
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'prejoin-copy-meeting'
|
||||
className = 'copy-meeting'
|
||||
onMouseEnter = { _showCopyLink }
|
||||
onMouseLeave = { _hideCopyLink }>
|
||||
<div className = 'prejoin-copy-url'>{url}</div>
|
||||
{showCopyLink && <div
|
||||
className = 'prejoin-copy-badge prejoin-copy-badge--hover'
|
||||
onClick = { _copyUrl }>
|
||||
{t('prejoin.copyAndShare')}
|
||||
</div>}
|
||||
{showLinkCopied && <div
|
||||
className = 'prejoin-copy-badge prejoin-copy-badge--done'>
|
||||
{t('prejoin.linkCopied')}
|
||||
</div>}
|
||||
<Icon
|
||||
className = { `prejoin-copy-icon ${iconCls}` }
|
||||
onClick = { _copyUrl }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
<div
|
||||
className = { `url ${showLinkCopied ? 'done' : ''}` }
|
||||
onClick = { _copyUrl } >
|
||||
{ !showCopyLink && !showLinkCopied && url }
|
||||
{ showCopyLink && t('prejoin.copyAndShare') }
|
||||
{ showLinkCopied && t('prejoin.linkCopied') }
|
||||
<Icon
|
||||
onClick = { _copyUrl }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
</div>
|
||||
<textarea
|
||||
className = 'prejoin-copy-textarea'
|
||||
readOnly = { true }
|
||||
ref = { this.textarea }
|
||||
tabIndex = '-1'
|
||||
value = { url } />
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
175
react/features/base/premeeting/components/web/InputField.js
Normal file
175
react/features/base/premeeting/components/web/InputField.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { getFieldValue } from '../../../react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Class name to be appended to the default class list.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*/
|
||||
onChange: Function,
|
||||
|
||||
/**
|
||||
* Callback to be used when the user hits Enter in the field.
|
||||
*/
|
||||
onSubmit?: Function,
|
||||
|
||||
/**
|
||||
* Placeholder text for the field.
|
||||
*/
|
||||
placeHolder: string,
|
||||
|
||||
/**
|
||||
* The field type (e.g. text, password...etc).
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* Externally provided value.
|
||||
*/
|
||||
value?: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* True if the field is focused, false otherwise.
|
||||
*/
|
||||
focused: boolean,
|
||||
|
||||
/**
|
||||
* The current value of the field.
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a pre-styled input field to be used on pre-meeting screens.
|
||||
*/
|
||||
export default class InputField extends PureComponent<Props, State> {
|
||||
static defaultProps: {
|
||||
className: '',
|
||||
type: 'text'
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
focused: false,
|
||||
value: props.value || ''
|
||||
};
|
||||
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent.getDerivedStateFromProps}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
const { value } = props;
|
||||
|
||||
if (state.value !== value) {
|
||||
return {
|
||||
...state,
|
||||
value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
|
||||
onBlur = { this._onBlur }
|
||||
onChange = { this._onChange }
|
||||
onFocus = { this._onFocus }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
placeholder = { this.props.placeHolder }
|
||||
type = { this.props.type }
|
||||
value = { this.state.value } />
|
||||
);
|
||||
}
|
||||
|
||||
_onBlur: () => void;
|
||||
|
||||
/**
|
||||
* Callback for the onBlur event of the field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onBlur() {
|
||||
this.setState({
|
||||
focused: false
|
||||
});
|
||||
}
|
||||
|
||||
_onChange: Object => void;
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*
|
||||
* @param {Object} evt - The static event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChange(evt) {
|
||||
const value = getFieldValue(evt);
|
||||
|
||||
this.setState({
|
||||
value
|
||||
});
|
||||
|
||||
const { onChange } = this.props;
|
||||
|
||||
onChange && onChange(value);
|
||||
}
|
||||
|
||||
_onFocus: () => void;
|
||||
|
||||
/**
|
||||
* Callback for the onFocus event of the field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFocus() {
|
||||
this.setState({
|
||||
focused: true
|
||||
});
|
||||
}
|
||||
|
||||
_onKeyDown: Object => void;
|
||||
|
||||
/**
|
||||
* Joins the conference on 'Enter'.
|
||||
*
|
||||
* @param {Event} event - Key down event object.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
const { onSubmit } = this.props;
|
||||
|
||||
onSubmit && event.key === 'Enter' && onSubmit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox';
|
||||
|
||||
import CopyMeetingUrl from './CopyMeetingUrl';
|
||||
import Preview from './Preview';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children component(s) to be rendered on the screen.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Footer to be rendered for the page (if any).
|
||||
*/
|
||||
footer?: React$Node,
|
||||
|
||||
/**
|
||||
* Title of the screen.
|
||||
*/
|
||||
title: string,
|
||||
|
||||
/**
|
||||
* True if the preview overlay should be muted, false otherwise.
|
||||
*/
|
||||
videoMuted?: boolean,
|
||||
|
||||
/**
|
||||
* The video track to render as preview (if omitted, the default local track will be rendered).
|
||||
*/
|
||||
videoTrack?: Object
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
|
||||
* on the prejoin screen (pre-connection) or lobby (post-connection).
|
||||
*/
|
||||
export default class PreMeetingScreen extends PureComponent<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { title, videoMuted, videoTrack } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'premeeting-screen'
|
||||
id = 'lobby-screen'>
|
||||
<Preview
|
||||
videoMuted = { videoMuted }
|
||||
videoTrack = { videoTrack } />
|
||||
<div className = 'content'>
|
||||
<div className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
{ this.props.children }
|
||||
<div className = 'media-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
<VideoSettingsButton visible = { true } />
|
||||
</div>
|
||||
{ this.props.footer }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
73
react/features/base/premeeting/components/web/Preview.js
Normal file
73
react/features/base/premeeting/components/web/Preview.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../avatar';
|
||||
import { Video } from '../../../media';
|
||||
import { connect } from '../../../redux';
|
||||
import { getLocalVideoTrack } from '../../../tracks';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The name of the user that is about to join.
|
||||
*/
|
||||
name: string,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
videoMuted: boolean,
|
||||
|
||||
/**
|
||||
* The JitsiLocalTrack to display.
|
||||
*/
|
||||
videoTrack: ?Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component showing the video preview and device status.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function Preview(props: Props) {
|
||||
const { name, videoMuted, videoTrack } = props;
|
||||
|
||||
if (!videoMuted && videoTrack) {
|
||||
return (
|
||||
<div id = 'preview'>
|
||||
<Video
|
||||
className = 'flipVideoX'
|
||||
videoTrack = {{ jitsiTrack: videoTrack }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'no-video'
|
||||
id = 'preview'>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
|
||||
videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Preview);
|
||||
5
react/features/base/premeeting/components/web/index.js
Normal file
5
react/features/base/premeeting/components/web/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
export { default as ActionButton } from './ActionButton';
|
||||
export { default as InputField } from './InputField';
|
||||
export { default as PreMeetingScreen } from './PreMeetingScreen';
|
||||
3
react/features/base/premeeting/index.js
Normal file
3
react/features/base/premeeting/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './components';
|
||||
5
react/features/base/premeeting/logger.js
Normal file
5
react/features/base/premeeting/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/premeeting');
|
||||
@@ -21,11 +21,27 @@ const _RIGHT_WATERMARK_STYLE = {
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The user selected url used to navigate to on logo click.
|
||||
*/
|
||||
_customLogoLink: string,
|
||||
|
||||
/**
|
||||
* The url of the user selected logo.
|
||||
*/
|
||||
_customLogoUrl: string,
|
||||
|
||||
/**
|
||||
* Whether or not the current user is logged in through a JWT.
|
||||
*/
|
||||
_isGuest: boolean,
|
||||
|
||||
/**
|
||||
* Flag used to signal that the logo can be displayed.
|
||||
* It becomes true after the user customization options are fetched.
|
||||
*/
|
||||
_readyToDisplayJitsiWatermark: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -133,6 +149,26 @@ class Watermarks extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the watermark is ready to be displayed.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_canDisplayJitsiWatermark() {
|
||||
const {
|
||||
showJitsiWatermark,
|
||||
showJitsiWatermarkForGuests
|
||||
} = this.state;
|
||||
const {
|
||||
_isGuest,
|
||||
_readyToDisplayJitsiWatermark
|
||||
} = this.props;
|
||||
|
||||
return _readyToDisplayJitsiWatermark
|
||||
&& (showJitsiWatermark || (_isGuest && showJitsiWatermarkForGuests));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a brand watermark if it is enabled.
|
||||
*
|
||||
@@ -173,18 +209,27 @@ class Watermarks extends Component<Props, State> {
|
||||
*/
|
||||
_renderJitsiWatermark() {
|
||||
let reactElement = null;
|
||||
const {
|
||||
_customLogoUrl,
|
||||
_customLogoLink
|
||||
} = this.props;
|
||||
|
||||
if (this.state.showJitsiWatermark
|
||||
|| (this.props._isGuest
|
||||
&& this.state.showJitsiWatermarkForGuests)) {
|
||||
reactElement = <div className = 'watermark leftwatermark' />;
|
||||
if (this._canDisplayJitsiWatermark()) {
|
||||
const link = _customLogoLink || this.state.jitsiWatermarkLink;
|
||||
const style = {
|
||||
backgroundImage: `url(${_customLogoUrl || interfaceConfig.DEFAULT_LOGO_URL})`,
|
||||
maxWidth: 140,
|
||||
maxHeight: 70
|
||||
};
|
||||
|
||||
const { jitsiWatermarkLink } = this.state;
|
||||
reactElement = (<div
|
||||
className = 'watermark leftwatermark'
|
||||
style = { style } />);
|
||||
|
||||
if (jitsiWatermarkLink) {
|
||||
if (link) {
|
||||
reactElement = (
|
||||
<a
|
||||
href = { jitsiWatermarkLink }
|
||||
href = { link }
|
||||
target = '_new'>
|
||||
{ reactElement }
|
||||
</a>
|
||||
@@ -223,12 +268,11 @@ class Watermarks extends Component<Props, State> {
|
||||
* Maps parts of Redux store to component prop types.
|
||||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _isGuest: boolean
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const { customizationReady, logoClickUrl, logoImageUrl } = state['features/dynamic-branding'];
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -238,7 +282,10 @@ function _mapStateToProps(state) {
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_isGuest: isGuest
|
||||
_customLogoLink: logoClickUrl,
|
||||
_customLogoUrl: logoImageUrl,
|
||||
_isGuest: isGuest,
|
||||
_readyToDisplayJitsiWatermark: customizationReady
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
11
react/features/base/react/functions.js
Normal file
11
react/features/base/react/functions.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Returns the field value in a platform generic way.
|
||||
*
|
||||
* @param {Object | string} fieldParameter - The parameter passed through the change event function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getFieldValue(fieldParameter: Object | string) {
|
||||
return typeof fieldParameter === 'string' ? fieldParameter : fieldParameter?.target?.value;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
export { default as Platform } from './Platform';
|
||||
export * from './Types';
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { connect } from '../../redux';
|
||||
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from '../constants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AspectRatioAware}.
|
||||
*/
|
||||
type Props = {
|
||||
aspectRatio: ASPECT_RATIO_NARROW | ASPECT_RATIO_WIDE
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a specific React {@code Component} decorated into an
|
||||
* {@link AspectRatioAware} has {@link ASPECT_RATIO_NARROW} as the value of its
|
||||
* {@code aspectRatio} React prop.
|
||||
*
|
||||
* @param {AspectRatioAware} component - An {@link AspectRatioAware} which may
|
||||
* have an {@code aspectRatio} React prop.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNarrowAspectRatio(component: React$Component<*>) {
|
||||
return component.props.aspectRatio === ASPECT_RATIO_NARROW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a specific React {@code Component} class into an
|
||||
* {@link AspectRatioAware} which provides the React prop {@code aspectRatio}
|
||||
* updated on each redux state change.
|
||||
*
|
||||
* @param {Class<React$Component>} WrappedComponent - A React {@code Component}
|
||||
* class to be wrapped.
|
||||
* @returns {AspectRatioAwareWrapper}
|
||||
*/
|
||||
export function makeAspectRatioAware(
|
||||
WrappedComponent: Class<React$Component<*>>
|
||||
): Class<React$Component<*>> {
|
||||
/**
|
||||
* Renders {@code WrappedComponent} with the React prop {@code aspectRatio}.
|
||||
*/
|
||||
class AspectRatioAware extends Component<Props> {
|
||||
/**
|
||||
* Implement's React render method to wrap the nested component.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
render(): React$Element<*> {
|
||||
return <WrappedComponent { ...this.props } />;
|
||||
}
|
||||
}
|
||||
|
||||
return connect(_mapStateToProps)(AspectRatioAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link AspectRatioAware} props.
|
||||
*
|
||||
* @param {Object} state - The whole redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
aspectRatio: state['features/base/responsive-ui'].aspectRatio
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* AspectRatioDetector component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
@@ -64,7 +60,7 @@ export default class DimensionsDetector extends PureComponent<Props> {
|
||||
return (
|
||||
<View
|
||||
onLayout = { this._onLayout }
|
||||
style = { styles.dimensionsDetector } >
|
||||
style = { StyleSheet.absoluteFillObject } >
|
||||
{ this.props.children }
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './AspectRatioAware';
|
||||
export { default as DimensionsDetector } from './DimensionsDetector';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The styles of the feature base/responsive-ui.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* The style of {@link DimensionsDetector} used on react-native.
|
||||
*/
|
||||
dimensionsDetector: {
|
||||
alignSelf: 'stretch',
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
@@ -1,16 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import { CLIENT_RESIZED } from './actionTypes';
|
||||
import { setAspectRatio, setReducedUI } from './actions';
|
||||
|
||||
/**
|
||||
* Dimensions change handler.
|
||||
*/
|
||||
let handler;
|
||||
|
||||
/**
|
||||
* Middleware that handles widnow dimension changes and updates the aspect ratio and
|
||||
@@ -19,65 +13,19 @@ let handler;
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
MiddlewareRegistry.register(({ dispatch }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_UNMOUNT: {
|
||||
_appWillUnmount();
|
||||
case CLIENT_RESIZED: {
|
||||
const { clientWidth: width, clientHeight: height } = action;
|
||||
|
||||
dispatch(setAspectRatio(width, height));
|
||||
dispatch(setReducedUI(width, height));
|
||||
break;
|
||||
}
|
||||
case APP_WILL_MOUNT:
|
||||
_appWillMount(store);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillMount(store) {
|
||||
handler = dim => {
|
||||
_onDimensionsChange(dim, store);
|
||||
};
|
||||
|
||||
Dimensions.addEventListener('change', handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillUnmount() {
|
||||
Dimensions.removeEventListener('change', handler);
|
||||
|
||||
handler = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles window dimension changes.
|
||||
*
|
||||
* @param {Object} dimensions - The new dimensions.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onDimensionsChange(dimensions, store) {
|
||||
const { width, height } = dimensions.window;
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(setAspectRatio(width, height));
|
||||
dispatch(setReducedUI(width, height));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ const DEFAULT_STATE = {
|
||||
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CLIENT_RESIZED: {
|
||||
|
||||
return {
|
||||
...state,
|
||||
clientWidth: action.clientWidth,
|
||||
|
||||
@@ -99,6 +99,24 @@ function _fixURIStringScheme(uri: string) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a path to a backend-safe format, by splitting the path '/' processing each part.
|
||||
* Properly lowercased and url encoded.
|
||||
*
|
||||
* @param {string?} path - The path to convert.
|
||||
* @returns {string?}
|
||||
*/
|
||||
export function getBackendSafePath(path: ?string): ?string {
|
||||
if (!path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return path
|
||||
.split('/')
|
||||
.map(getBackendSafeRoomName)
|
||||
.join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a room name to a backend-safe format. Properly lowercased and url encoded.
|
||||
*
|
||||
|
||||
@@ -50,8 +50,9 @@ export default class AbstractInsecureRoomNameLabel extends PureComponent<Props>
|
||||
*/
|
||||
export function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const { room } = state['features/base/conference'];
|
||||
const { enableInsecureRoomNameWarning = false } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_visible: room && isInsecureRoomName(room)
|
||||
_visible: enableInsecureRoomNameWarning && room && isInsecureRoomName(room)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ import { appNavigate } from '../../../app';
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
@@ -25,6 +22,7 @@ import {
|
||||
} from '../../../filmstrip';
|
||||
import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList } from '../../../lobby';
|
||||
import { BackButtonRegistry } from '../../../mobile/back-button';
|
||||
import { Captions } from '../../../subtitles';
|
||||
import { isToolboxVisible, setToolboxVisible, Toolbox } from '../../../toolbox';
|
||||
@@ -45,10 +43,13 @@ import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
|
||||
@@ -57,15 +58,11 @@ type Props = AbstractProps & {
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
* joining the room. If truthy, then an activity/loading indicator will be
|
||||
* rendered.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* Set to {@code true} when the filmstrip is currently visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_filmstripVisible: boolean,
|
||||
|
||||
@@ -76,34 +73,17 @@ type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_pictureInPictureEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to accommodate
|
||||
* smaller display areas).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reducedUI: boolean,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action {@link setToolboxVisible}
|
||||
* to show/hide the {@link Toolbox}.
|
||||
*
|
||||
* @param {boolean} visible - {@code true} to show the {@code Toolbox} or
|
||||
* {@code false} to hide it.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setToolboxVisible: Function,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
|
||||
@@ -249,6 +229,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
_renderContent() {
|
||||
const {
|
||||
_aspectRatio,
|
||||
_connecting,
|
||||
_filmstripVisible,
|
||||
_largeVideoParticipantId,
|
||||
@@ -257,7 +238,8 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_toolboxVisible
|
||||
} = this.props;
|
||||
const showGradient = _toolboxVisible;
|
||||
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
|
||||
const applyGradientStretching
|
||||
= _filmstripVisible && _aspectRatio === ASPECT_RATIO_NARROW && !_shouldDisplayTileView;
|
||||
|
||||
if (_reducedUI) {
|
||||
return this._renderContentForReducedUi();
|
||||
@@ -339,6 +321,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
style = { styles.navBarSafeView }>
|
||||
<NavigationBar />
|
||||
{ this._renderNotificationsContainer() }
|
||||
<KnockingParticipantList />
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
@@ -393,7 +376,9 @@ class Conference extends AbstractConference<Props, *> {
|
||||
// flex layout. The only option that seemed to limit the notification's
|
||||
// size was explicit 'width' value which is not better than the margin
|
||||
// added here.
|
||||
if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
|
||||
const { _aspectRatio, _filmstripVisible } = this.props;
|
||||
|
||||
if (_filmstripVisible && _aspectRatio !== ASPECT_RATIO_NARROW) {
|
||||
notificationsStyle.marginRight = FILMSTRIP_SIZE;
|
||||
}
|
||||
|
||||
@@ -431,9 +416,10 @@ function _mapStateToProps(state) {
|
||||
const {
|
||||
conference,
|
||||
joining,
|
||||
membersOnly,
|
||||
leaving
|
||||
} = state['features/base/conference'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
// XMPP connection and the subsequent commencement of joining the MUC during
|
||||
@@ -445,65 +431,19 @@ function _mapStateToProps(state) {
|
||||
// - the XMPP connection is connected and we have no conference yet, nor we
|
||||
// are leaving one.
|
||||
const connecting_
|
||||
= connecting || (connection && (joining || (!conference && !leaving)));
|
||||
= connecting || (connection && (!membersOnly && (joining || (!conference && !leaving))));
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_aspectRatio: aspectRatio,
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines that we are still connecting to the
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
* joining the room. If truthy, then an activity/loading indicator will
|
||||
* be rendered.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_connecting: Boolean(connecting_),
|
||||
|
||||
/**
|
||||
* Is {@code true} when the filmstrip is currently visible.
|
||||
*/
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
|
||||
/**
|
||||
* The ID of the participant currently on stage.
|
||||
*/
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to
|
||||
* accommodate smaller display areas).
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_reducedUI: reducedUI,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_toolboxVisible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Conference));
|
||||
export default connect(_mapStateToProps)(Conference);
|
||||
|
||||
@@ -5,10 +5,7 @@ import { TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
|
||||
import {
|
||||
RecordingExpandedLabel
|
||||
} from '../../../recording';
|
||||
@@ -29,14 +26,19 @@ import styles from './styles';
|
||||
type Props = AbstractLabelsProps & {
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
t: Function,
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* True if the labels should be visible, false otherwise.
|
||||
*/
|
||||
_visible: boolean
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -149,12 +151,13 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._visible) {
|
||||
const { _aspectRatio, _filmstripVisible, _visible } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wide = !isNarrowAspectRatio(this);
|
||||
const { _filmstripVisible } = this.props;
|
||||
const wide = _aspectRatio === ASPECT_RATIO_WIDE;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -354,8 +357,9 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_visible: !shouldDisplayNotifications(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));
|
||||
export default connect(_mapStateToProps)(Labels);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Chat } from '../../../chat';
|
||||
import { Filmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList } from '../../../lobby';
|
||||
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
|
||||
import {
|
||||
Toolbox,
|
||||
@@ -176,8 +177,6 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
VIDEO_QUALITY_LABEL_DISABLED,
|
||||
|
||||
// XXX The character casing of the name filmStripOnly utilized by
|
||||
// interfaceConfig is obsolete but legacy support is required.
|
||||
filmStripOnly: filmstripOnly
|
||||
@@ -187,10 +186,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_layoutClassName,
|
||||
_showPrejoin
|
||||
} = this.props;
|
||||
const hideVideoQualityLabel
|
||||
= filmstripOnly
|
||||
|| VIDEO_QUALITY_LABEL_DISABLED
|
||||
|| _iAmRecorder;
|
||||
const hideLabels = filmstripOnly || _iAmRecorder;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -203,8 +199,8 @@ class Conference extends AbstractConference<Props, *> {
|
||||
<InviteMore />
|
||||
<div id = 'videospace'>
|
||||
<LargeVideo />
|
||||
{ hideVideoQualityLabel
|
||||
|| <Labels /> }
|
||||
<KnockingParticipantList />
|
||||
{ hideLabels || <Labels /> }
|
||||
<Filmstrip filmstripOnly = { filmstripOnly } />
|
||||
</div>
|
||||
|
||||
@@ -213,9 +209,9 @@ class Conference extends AbstractConference<Props, *> {
|
||||
|
||||
{ this.renderNotificationsContainer() }
|
||||
|
||||
{ !filmstripOnly && _showPrejoin && <Prejoin />}
|
||||
|
||||
<CalleeInfoContainer />
|
||||
|
||||
{ !filmstripOnly && _showPrejoin && <Prejoin />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import AbstractLabels, {
|
||||
type Props
|
||||
} from '../AbstractLabels';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link Labels}.
|
||||
*/
|
||||
@@ -39,8 +41,7 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props, prevState: State) {
|
||||
return {
|
||||
filmstripBecomingVisible: !prevState.filmstripBecomingVisible
|
||||
&& props._filmstripVisible
|
||||
filmstripBecomingVisible: !prevState.filmstripBecomingVisible && props._filmstripVisible
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,6 +68,7 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
render() {
|
||||
const { _filmstripVisible } = this.props;
|
||||
const { filmstripBecomingVisible } = this.state;
|
||||
const { VIDEO_QUALITY_LABEL_DISABLED } = interfaceConfig;
|
||||
const className = `large-video-labels ${
|
||||
filmstripBecomingVisible ? 'opening' : ''} ${
|
||||
_filmstripVisible ? 'with-filmstrip' : 'without-filmstrip'}`;
|
||||
@@ -91,7 +93,7 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
this._renderTranscribingLabel()
|
||||
}
|
||||
{
|
||||
this.props._showVideoQualityLabel
|
||||
this.props._showVideoQualityLabel && !VIDEO_QUALITY_LABEL_DISABLED
|
||||
&& this._renderVideoQualityLabel()
|
||||
}
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, prevConference) => {
|
||||
const { authRequired, passwordRequired }
|
||||
const { authRequired, membersOnly, passwordRequired }
|
||||
= getState()['features/base/conference'];
|
||||
|
||||
if (conference !== prevConference) {
|
||||
@@ -80,6 +80,7 @@ StateListenerRegistry.register(
|
||||
// and explicitly check.
|
||||
if (typeof authRequired === 'undefined'
|
||||
&& typeof passwordRequired === 'undefined'
|
||||
&& typeof membersOnly === 'undefined'
|
||||
&& !isDialogOpen(getState(), FeedbackDialog)) {
|
||||
// Conference changed, left or failed... and there is no
|
||||
// pending authentication, nor feedback request, so close any
|
||||
|
||||
9
react/features/dynamic-branding/actionTypes.js
Normal file
9
react/features/dynamic-branding/actionTypes.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Action used to set custom user properties.
|
||||
*/
|
||||
export const SET_DYNAMIC_BRANDING_DATA = 'SET_DYNAMIC_BRANDING_DATA';
|
||||
|
||||
/**
|
||||
* Action used to signal the branding elements are ready to be displayed
|
||||
*/
|
||||
export const SET_DYNAMIC_BRANDING_READY = 'SET_DYNAMIC_BRANDING_READY';
|
||||
66
react/features/dynamic-branding/actions.js
Normal file
66
react/features/dynamic-branding/actions.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from 'jitsi-meet-logger';
|
||||
|
||||
import { doGetJSON } from '../base/util';
|
||||
|
||||
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
|
||||
import { extractFqnFromPath } from './functions';
|
||||
|
||||
const logger = getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Fetches custom branding data.
|
||||
* If there is no data or the request fails, sets the `customizationReady` flag
|
||||
* so the defaults can be displayed.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function fetchCustomBrandingData() {
|
||||
return async function(dispatch: Function, getState: Function) {
|
||||
const state = getState();
|
||||
const baseUrl = state['features/base/config'].brandingDataUrl;
|
||||
const { customizationReady } = state['features/dynamic-branding'];
|
||||
|
||||
if (!customizationReady) {
|
||||
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
|
||||
if (baseUrl && fqn) {
|
||||
try {
|
||||
const res = await doGetJSON(`${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`);
|
||||
|
||||
return dispatch(setDynamicBrandingData(res));
|
||||
} catch (err) {
|
||||
logger.error('Error fetching branding data', err);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setDynamicBrandingReady());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the user customizations.
|
||||
*
|
||||
* @param {Object} value - The custom data to be set.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function setDynamicBrandingData(value) {
|
||||
return {
|
||||
type: SET_DYNAMIC_BRANDING_DATA,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to signal the branding elements are ready to be displayed.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function setDynamicBrandingReady() {
|
||||
return {
|
||||
type: SET_DYNAMIC_BRANDING_READY
|
||||
};
|
||||
}
|
||||
15
react/features/dynamic-branding/functions.js
Normal file
15
react/features/dynamic-branding/functions.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Extracts the fqn part from a path, where fqn represents
|
||||
* tenant/roomName.
|
||||
*
|
||||
* @param {string} path - The URL path.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function extractFqnFromPath(path: string) {
|
||||
const parts = path.split('/');
|
||||
const len = parts.length;
|
||||
|
||||
return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
|
||||
}
|
||||
4
react/features/dynamic-branding/index.js
Normal file
4
react/features/dynamic-branding/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
46
react/features/dynamic-branding/reducer.js
Normal file
46
react/features/dynamic-branding/reducer.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The name of the redux store/state property which is the root of the redux
|
||||
* state of the feature {@code dynamic-branding}.
|
||||
*/
|
||||
const STORE_NAME = 'features/dynamic-branding';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
backgroundColor: '',
|
||||
backgroundImageUrl: '',
|
||||
customizationReady: false,
|
||||
logoClickUrl: '',
|
||||
logoImageUrl: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces redux actions for the purposes of the feature {@code dynamic-branding}.
|
||||
*/
|
||||
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_DYNAMIC_BRANDING_DATA: {
|
||||
const { backgroundColor, backgroundImageUrl, logoClickUrl, logoImageUrl } = action.value;
|
||||
|
||||
return {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
logoClickUrl,
|
||||
logoImageUrl,
|
||||
customizationReady: true
|
||||
};
|
||||
}
|
||||
case SET_DYNAMIC_BRANDING_READY:
|
||||
return {
|
||||
...state,
|
||||
customizationReady: true
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
@@ -5,10 +5,7 @@ import { ScrollView } from 'react-native';
|
||||
|
||||
import { Container, Platform } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { isFilmstripVisible } from '../../functions';
|
||||
|
||||
import LocalThumbnail from './LocalThumbnail';
|
||||
@@ -20,24 +17,23 @@ import styles from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_enabled: boolean,
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_participants: Array<any>,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_visible: boolean
|
||||
};
|
||||
@@ -90,40 +86,36 @@ class Filmstrip extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._enabled) {
|
||||
const { _aspectRatio, _enabled, _participants, _visible } = this.props;
|
||||
|
||||
if (!_enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isNarrowAspectRatio_ = isNarrowAspectRatio(this);
|
||||
const filmstripStyle
|
||||
= isNarrowAspectRatio_
|
||||
? styles.filmstripNarrow
|
||||
: styles.filmstripWide;
|
||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
|
||||
|
||||
return (
|
||||
<Container
|
||||
style = { filmstripStyle }
|
||||
visible = { this.props._visible }>
|
||||
visible = { _visible }>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio_
|
||||
&& !isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
<ScrollView
|
||||
horizontal = { isNarrowAspectRatio_ }
|
||||
horizontal = { isNarrowAspectRatio }
|
||||
showsHorizontalScrollIndicator = { false }
|
||||
showsVerticalScrollIndicator = { false }
|
||||
style = { styles.scrollView } >
|
||||
{
|
||||
!this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio_
|
||||
!this._separateLocalThumbnail && !isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
{
|
||||
|
||||
this._sort(
|
||||
this.props._participants,
|
||||
isNarrowAspectRatio_)
|
||||
this._sort(_participants, isNarrowAspectRatio)
|
||||
.map(p => (
|
||||
<Thumbnail
|
||||
key = { p.id }
|
||||
@@ -131,14 +123,12 @@ class Filmstrip extends Component<Props> {
|
||||
|
||||
}
|
||||
{
|
||||
!this._separateLocalThumbnail
|
||||
&& isNarrowAspectRatio_
|
||||
!this._separateLocalThumbnail && isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
</ScrollView>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& isNarrowAspectRatio_
|
||||
this._separateLocalThumbnail && isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
</Container>
|
||||
@@ -150,13 +140,13 @@ class Filmstrip extends Component<Props> {
|
||||
*
|
||||
* @param {Participant[]} participants - The array of {@code Participant}s
|
||||
* to sort in display order.
|
||||
* @param {boolean} isNarrowAspectRatio_ - Indicates if the aspect ratio is
|
||||
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
|
||||
* wide or narrow.
|
||||
* @private
|
||||
* @returns {Participant[]} A new array containing the elements of the
|
||||
* specified {@code participants} array sorted in display order.
|
||||
*/
|
||||
_sort(participants, isNarrowAspectRatio_) {
|
||||
_sort(participants, isNarrowAspectRatio) {
|
||||
// XXX Array.prototype.sort() is not appropriate because (1) it operates
|
||||
// in place and (2) it is not necessarily stable.
|
||||
|
||||
@@ -164,7 +154,7 @@ class Filmstrip extends Component<Props> {
|
||||
...participants
|
||||
];
|
||||
|
||||
if (isNarrowAspectRatio_) {
|
||||
if (isNarrowAspectRatio) {
|
||||
// When the narrow aspect ratio is used, we want to have the remote
|
||||
// participants from right to left with the newest added/joined to
|
||||
// the leftmost side. The local participant is the leftmost item.
|
||||
@@ -180,42 +170,18 @@ class Filmstrip extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _participants: Participant[],
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participants = state['features/base/participants'];
|
||||
const { enabled } = state['features/filmstrip'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is enabled.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_enabled: enabled,
|
||||
|
||||
/**
|
||||
* The remote participants in the conference.
|
||||
*
|
||||
* @private
|
||||
* @type {Participant[]}
|
||||
*/
|
||||
_participants: participants.filter(p => !p.local),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible. The
|
||||
* mobile/react-native Filmstrip is visible when there are at least 2
|
||||
* participants in the conference (including the local one).
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: isFilmstripVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Filmstrip));
|
||||
export default connect(_mapStateToProps)(Filmstrip);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user