Compare commits

..

55 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
01345d6d9d chore(ios) remove references to no longer used files 2020-12-16 10:28:30 +01:00
Saúl Ibarra Corretgé
28cd74077b fix(ios) fix joining a meeting when the app was closed
Weird timing issue in appDidFinishLaunching, make sure we store the
launchOptions before we touch any view code.
2020-12-16 10:28:30 +01:00
Hristo Terezov
af6c794fda ref(remote-control): Use React/Redux. 2020-12-15 18:09:58 -06:00
Pawel Domas
f88061db06 fix(conference.js): 2 tracks of the same media type
...caused by bad state as a result of timing issue around the prejoin flow.

If get user media call is delayed for a while and if user joins
the conference, when it hasn't completed then confrence.js will not assign
'localAudio' and 'localVideo' variables and will create additional media
tracks on unmute operation and add them to JitsiConference via replaceTracks
operation.
2020-12-15 14:11:00 -06:00
damencho
ec6abc1ce9 fix: Skip sending unnecessary signalling for raise hand. 2020-12-15 11:21:56 -06:00
sorinant
e261bb5616 feat(ios) added ability to use an external CXProvider and CXCallController 2020-12-15 12:21:53 +01:00
Saúl Ibarra Corretgé
b1a4b58f7a chore(deps) update react-native-webview 2020-12-15 12:05:59 +01:00
dependabot[bot]
42dabd4cdb chore(deps): bump react-native-webview from 10.9.0 to 11.0.0
Bumps [react-native-webview](https://github.com/react-native-community/react-native-webview) from 10.9.0 to 11.0.0.
- [Release notes](https://github.com/react-native-community/react-native-webview/releases)
- [Changelog](https://github.com/react-native-webview/react-native-webview/blob/master/.releaserc)
- [Commits](https://github.com/react-native-community/react-native-webview/compare/v10.9.0...v11.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-15 11:09:28 +01:00
qwertiko GmbH
89ebb4d918 fix(LoginDialog) added missing double quotes 2020-12-15 11:06:44 +01:00
damencho
fc54fc80d1 fix: Start p2p only when we have received all presences. 2020-12-14 18:21:46 -06:00
Hristo Terezov
87b1155180 fix(video):Always show avatar if video is inactive 2020-12-14 16:44:08 -06:00
Saúl Ibarra Corretgé
4ca02c1ebf feat(tile-view) optimize for less margins
- Lower the inter-tile margin to 2px
- Remove the 100px top/bottom margin when the toolbar is hidden
2020-12-14 21:27:13 +01:00
BasmaAwatef
70fcabd136 fix: the user placeholder translation issue #8219 (#8233)
* ✏️ user placeholder translation in authentication added

* Create node.js.yml

* Create npm-publish.yml

* Create deno.yml

* Create ci.yml

* Create stale.yml

* Delete deno.yml

* Delete npm-publish.yml

* Delete ci.yml

* Delete node.js.yml

* Delete ci.yml

* Create main.yml

* Delete stale.yml

* Delete main.yml

* Create ci.yml

* Create simpleCI.yml

* Update simpleCI.yml

* Revert "Delete stale.yml"

This reverts commit 27d88166b9.

* Revert "Delete main.yml"

This reverts commit 037cd96e71.

* Revert "Revert "Delete stale.yml""

This reverts commit 834d96e41c.

* Revert "Revert "Delete main.yml""

This reverts commit f69bc066f9.

* Revert "Revert "Revert "Delete stale.yml"""

This reverts commit d60372da56.

* Revert "Revert "Revert "Delete main.yml"""

This reverts commit 521ca60df3.

* Revert "Revert "Revert "Revert "Delete stale.yml""""

This reverts commit 1f2a9518e1.

* Revert "Revert "Revert "Revert "Delete main.yml""""

This reverts commit adf6c58f8e.

* Revert "Revert "Revert "Revert "Revert "Delete stale.yml"""""

This reverts commit 81c7ecc42f.

* Revert "Revert "Revert "Revert "Revert "Delete main.yml"""""

This reverts commit d1e1c03983.

* Revert "Revert "Revert "Revert "Revert "Revert "Delete stale.yml""""""

This reverts commit b5f16b3e4d.

* Revert "Revert "Revert "Revert "Revert "Revert "Delete main.yml""""""

This reverts commit f9ef5ab193.

* Revert "Revert "Revert "Revert "Revert "Revert "Revert "Delete stale.yml"""""""

This reverts commit b66bbf8698.

* Revert "Revert "Revert "Revert "Revert "Revert "Revert "Delete main.yml"""""""

This reverts commit 01a44daf95.

* Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Delete main.yml""""""""

This reverts commit eb896849a7.

* 🔥 modifications dropped for all languages except for English

* Delete simpleCI.yml

* Delete stale.yml
2020-12-11 07:35:20 -06:00
Marek Suchánek
25a238f4e4 feat: Update the Czech translation (#8133)
* Update the Czech translation of `addPeople`

* Update the Czech translation of `calendarSync`

* Update the Czech translation of `chat`

* Add the Czech translation of `chromeExtensionBanner`

* Update the Czech translation of `connectingOverlay` and `connection`

* Update the Czech translation of `connectionindicator`

* Update the Czech translation of `deepLinking`

* Update various strings in the Czech translation

* Update various strings in the Czech translation

* Fix a trailing comma that broke JSON

* Sort keys in main-cs.json to deduplicate the translation and make diffs from main.json more readable

* Add several missing strings in the Czech translation

* Add several missing strings in the Czech translation, mainly lobby mode

* Add several missing strings in the Czech translation, mainly `prejoin`

* Add the missing Czech translation of `recording` and `security`

* Update various strings in the Czech translation

* Add the missing Czech translation of `accessibilityLabel`

* Add the missing Czech translation of `toolbar`

* Update various strings in the Czech translation

* Update various strings in the Czech translation

* Various edits of the Czech translation and a spell check

* Add missing language names in the Czech translation
2020-12-11 06:43:37 -06:00
Jaya Allamsetty
26dbc9a78b fix: Fixes VP9 support on Chrome.
chore(deps) lib-jitsi-meet@latest
2020-12-10 15:01:25 -05:00
Hristo Terezov
79e517ed65 feat(analytics): Add tenant. 2020-12-10 13:57:13 -06:00
Avram Tudor
148234ea50 Merge pull request #8230 from jitsi/tavram/revert-fixroom
Revert "fix(external_api) replace special chars in roomName before co…
2020-12-10 14:59:52 +02:00
Tudor-Ovidiu Avram
b23f4b02ea Revert "fix(external_api) replace special chars in roomName before constructing URL"
This reverts commit 6f90458ff1.
2020-12-10 14:51:51 +02:00
damencho
e9200bab09 fix: Fixes detecting websocket disconnect by using xmpp pings. 2020-12-09 15:19:39 -06:00
Дамян Минков
97f47998ba feat: Exposes a method for checking is remote track received and played/testing. (#8186)
* feat: Exposes a method for checking is remote track received and played.

Used for some tests in torture.

* squash: Drop not matching string.

Duplicate translation key with not matching content.

* squash: Moves torture specific functions to features/base/testing.

Listens for media events from the video tag of the large video and stores them in redux.

* squash: Fix comments.

* feat: Listens for media events from the video tag of the remote videos and stores them in redux.

* squash: Fix undefined videoTrack if between switches.
2020-12-08 08:01:16 -06:00
Jaya Allamsetty
0019284b10 feat: Add option to force pc to use turn relay candidates.
Helps with testing turn relay cases.
2020-12-07 20:17:26 -05:00
Hristo Terezov
5cae5985c0 feat(ConferenceTimer): Add config option to hide. 2020-12-03 17:00:33 -06:00
bgrozev
d77c5ccb7d doc: Add docs for enableLipSync. (#8195)
* doc: Add docs for enableLipSync.
2020-12-03 14:10:30 -06:00
bgrozev
96af156465 Remove conference-wide RTX/REMB/TCC/opus-red options. (#8194)
* doc: Updates the docs for REMB, TCC, opus-red.
2020-12-03 14:10:17 -06:00
רטו
09aa486ff1 fix(lang) fix a few typos in Italian translation 2020-12-03 16:13:55 +01:00
bgrozev
0f2be8c642 fix(config) remove openBridgeChannel 2020-12-03 16:12:47 +01:00
Jaya Allamsetty
65562d1ef4 fix(tracks): Do not add a second audio track. 2020-12-02 14:02:04 -05:00
bgrozev
9535f84775 cleanup: Remove the startBitrate option. (#8193) 2020-12-01 14:19:17 -06:00
Boris Grozev
6c477cad9b cleanup: Remove obsolete config option. 2020-12-01 12:12:45 -06:00
Boris Grozev
2b6c7a51a3 cleanup: Remove unused code. 2020-12-01 08:32:26 -06:00
Boris Grozev
42d1389338 feat: Remove the min-participants config. 2020-12-01 08:32:01 -06:00
רטו
d74f93209c fix(lang) typo 2020-12-01 13:56:46 +01:00
Boris Grozev
6f61077a65 ref: Remove unused option from whitelist. 2020-12-01 06:09:20 -06:00
Saúl Ibarra Corretgé
997c3f75b5 chore(rn) raise SDK and app versions 2020-11-27 15:24:26 +01:00
Saúl Ibarra Corretgé
baa39896f1 fix(android) set stream type hardware buttons should control
Ref:
https://developer.android.com/reference/android/app/Activity#setVolumeControlStream(int)
2020-11-26 16:30:01 +01:00
Saúl Ibarra Corretgé
3725f698e4 fix(android) reset audio route after audio focus was lost
Looks like audio devices must be re-set after focus was lost and regained.
Otherwise some devices (tested on a Samsung Galaxy S9) are in a weird state
where the second microphone is not used when speakerphone is on.
2020-11-26 15:33:36 +01:00
Saúl Ibarra Corretgé
67002c903a fix(android) use modern API for requesting audio focus 2020-11-26 15:33:36 +01:00
chipechop
1b15820f01 fix(lang) update Italian translation 2020-11-26 13:39:44 +01:00
Titus-Andrei Moldovan
4cced3af07 fix(android) disables the RNWebViewFileProvider 2020-11-26 11:24:43 +01:00
Mihai-Andrei Uscat
a8db3c1b28 chore(popups): Move popup configs to electron-utils 2020-11-26 12:13:50 +02:00
Jaya Allamsetty
39cf8854af fix(TPC): Do not scale down desktop track in p2p/non-simulcast cases.
chore(deps) lib-jitsi-meet@latest
2020-11-25 17:13:13 -05:00
Saúl Ibarra Corretgé
57f3e8a3e8 fix(deps) update logger 2020-11-25 16:48:58 +01:00
Saúl Ibarra Corretgé
f6fa903f8f feat(rn) switch to XCFramework and WebRTC M87 2020-11-25 16:39:26 +01:00
Hristo Terezov
3796db20ea fix(connection-status): action 2020-11-24 16:03:18 -05:00
Saúl Ibarra Corretgé
911df4b18a fix(avatar) revert back to defaulting to Gravatar
While the base URL remains configurable, this patch reverts back to using
Gravatar.

We noticed high latency with libravatar and contacted them. They are in the
process of migrarting to a better infrastructure (it's a single personal server
at the moment) so we'll re-evaluate once that has happened.

As for why not leave the default and change it on the meet.jit.si installation,
we don't want to kill their server :-)
2020-11-24 11:43:26 +01:00
George Politis
1041cd8055 feat: Makes it possible to hide the "Save Logs" link. (#8143)
As per @fremzy, the "Save Logs" feature generates a json
file with a bevy of technical information about the
meeting. This log contains the server name, server IP
address, participant's IP addresses (only in p2p sessions)
e.t.c. While this may be a useful feature for the
admin-like 'moderator', it creates unnecessary exposure
when made readily available to all users in the meeting.

This commit fixes #8036 by a config.js option to enable
the link (disabled by default), thus giving the owner of
the deployment the choice of enabling it or not.
2020-11-24 10:49:10 +01:00
Andrei Bora
898eca86d5 Make jwt accept boolean values for features 2020-11-23 11:34:34 -06:00
Oskars G
e0d41a30ef feat: Include "Latvian" in the languages list (#8129) 2020-11-21 09:57:56 -06:00
Jaya Allamsetty
d6ab0a72a1 fix(lastN): select screenshare endpoint always when auto pinning.
When trying to auto pin screenshare, always select the endpoint even though it happens to be the large video participant in redux. The auto pin screenshare logic kicks in after the track is added.  If the screenshare endpoint is not among the forwarded endpoints from the bridge, it needs to be selected again.
2020-11-20 10:29:12 -05:00
Jaya Allamsetty
fc694641dc fix(lastN): Do not override channelLastN value.
If limitLastN values are specified and channelLastN < limitLastN, configure channelLastN on the conference.
2020-11-20 10:29:12 -05:00
damencho
1ee7e81918 fix: Fixes 404 page link when base is used. 2020-11-19 10:49:03 -06:00
Saúl Ibarra Corretgé
a7de8be0aa feat(avatar) add ability to customize Gravatar base URL
Also, default to libravatar.

Closes: https://github.com/jitsi/jitsi-meet/issues/4927
2020-11-18 00:05:49 +01:00
Jaya Allamsetty
696ec36c8c fix(UI): Add method for returning the video type of remote participants.
This is needed for the torture clients to determine the video type for the remote participants when testing desktop share.
2020-11-17 12:49:36 -06:00
Avram Tudor
76c9d96361 Merge pull request #8110 from jitsi/tavram/fix-double-slash
fix(jaas) replace only the first slash in a pathname
2020-11-16 11:26:09 +02:00
Tudor-Ovidiu Avram
b889bd5664 fix(jaas) replace only the first slash in a pathname 2020-11-16 11:01:31 +02:00
71 changed files with 1513 additions and 1009 deletions

1
.gitignore vendored
View File

@@ -69,6 +69,7 @@ buck-out/
*.framework
android/app/debug
android/app/release
ios/sdk/out
# precommit-hook
.jshintignore

View File

@@ -25,5 +25,5 @@ android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.5.0
sdkVersion=2.11.0
appVersion=20.6.0
sdkVersion=2.12.0

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.jitsi.meet.sdk">
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -34,7 +35,7 @@
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize"></activity>
android:windowSoftInputMode="adjustResize"/>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service
@@ -48,6 +49,13 @@
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
<provider
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
android:authorities="${applicationId}.fileprovider"
android:enabled="false"
tools:replace="android:authorities">
</provider>
</application>
</manifest>

View File

@@ -16,8 +16,11 @@
package org.jitsi.meet.sdk;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import java.util.HashSet;
import java.util.Set;
@@ -60,7 +63,7 @@ class AudioDeviceHandlerGeneric implements
private AudioManager audioManager;
/**
* {@link Runnable} for running audio device detection the main thread.
* {@link Runnable} for running audio device detection in the audio thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@@ -142,7 +145,7 @@ class AudioDeviceHandlerGeneric implements
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.updateAudioRoute();
module.resetAudioRoute();
}
audioFocusLost = false;
break;
@@ -216,8 +219,24 @@ class AudioDeviceHandlerGeneric implements
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
int gotFocus;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
} else {
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
}
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}

View File

@@ -16,6 +16,7 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
@@ -256,7 +257,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
if (mode != -1) {
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
userSelectedDevice = device;
updateAudioRoute(mode);
updateAudioRoute(mode, false);
}
}
});
@@ -276,13 +277,22 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return;
}
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
if (mode == DEFAULT) {
currentActivity.setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
} else {
currentActivity.setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
}
runInAudioThread(new Runnable() {
@Override
public void run() {
boolean success;
try {
success = updateAudioRoute(mode);
success = updateAudioRoute(mode, false);
} catch (Throwable e) {
success = false;
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
@@ -321,7 +331,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* @return {@code true} if the audio route was updated successfully;
* {@code false}, otherwise.
*/
private boolean updateAudioRoute(int mode) {
private boolean updateAudioRoute(int mode, boolean force) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
if (!audioDeviceHandler.setMode(mode)) {
@@ -356,7 +366,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// If the previously selected device and the current default one
// match, do nothing.
if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
if (!force && selectedDevice != null && selectedDevice.equals(audioDevice)) {
return true;
}
@@ -421,7 +431,16 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
void updateAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode);
updateAudioRoute(mode, false);
}
}
/**
* Re-sets the current audio route. Needed when focus is lost and regained.
*/
void resetAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode, true);
}
}

View File

@@ -594,6 +594,42 @@ export default {
};
},
/**
* Displays error notifications according to the state carried by {@code errors} object returned
* by {@link createInitialLocalTracks}.
* @param {Object} errors - the errors (if any) returned by {@link createInitialLocalTracks}.
*
* @returns {void}
* @private
*/
_displayErrorsForCreateInitialLocalTracks(errors) {
const {
audioAndVideoError,
audioOnlyError,
screenSharingError,
videoOnlyError
} = errors;
// FIXME If there will be microphone error it will cover any screensharing dialog, but it's still better than in
// the reverse order where the screensharing dialog will sometimes be closing the microphone alert
// ($.prompt.close(); is called). Need to figure out dialogs chaining to fix that.
if (screenSharingError) {
this._handleScreenSharingError(screenSharingError);
}
if (audioAndVideoError || audioOnlyError) {
if (audioOnlyError || videoOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only failed, we assume that there are some
// problems with user's microphone and show corresponding dialog.
APP.store.dispatch(notifyMicError(audioOnlyError));
APP.store.dispatch(notifyCameraError(videoOnlyError));
} else {
// If request for 'audio' + 'video' failed, but request for 'audio' only was OK, we assume that we had
// problems with camera and show corresponding dialog.
APP.store.dispatch(notifyCameraError(audioAndVideoError));
}
}
},
/**
* Creates local media tracks and connects to a room. Will show error
* dialogs in case accessing the local microphone and/or camera failed. Will
@@ -614,38 +650,11 @@ export default {
*/
createInitialLocalTracksAndConnect(roomName, options = {}) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
const {
audioAndVideoError,
audioOnlyError,
screenSharingError,
videoOnlyError
} = errors;
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
.then(([ tracks, con ]) => {
// FIXME If there will be microphone error it will cover any
// screensharing dialog, but it's still better than in
// the reverse order where the screensharing dialog will
// sometimes be closing the microphone alert ($.prompt.close();
// is called). Need to figure out dialogs chaining to fix that.
if (screenSharingError) {
this._handleScreenSharingError(screenSharingError);
}
if (audioAndVideoError || audioOnlyError) {
if (audioOnlyError || videoOnlyError) {
// If both requests for 'audio' + 'video' and 'audio'
// only failed, we assume that there are some problems
// with user's microphone and show corresponding dialog.
APP.store.dispatch(notifyMicError(audioOnlyError));
APP.store.dispatch(notifyCameraError(videoOnlyError));
} else {
// If request for 'audio' + 'video' failed, but request
// for 'audio' only was OK, we assume that we had
// problems with camera and show corresponding dialog.
APP.store.dispatch(
notifyCameraError(audioAndVideoError));
}
}
this._displayErrorsForCreateInitialLocalTracks(errors);
return [ tracks, con ];
});
@@ -753,7 +762,15 @@ export default {
// they may remain as empty strings.
this._initDeviceList(true);
return APP.store.dispatch(initPrejoin(tracks, errors));
if (isPrejoinPageVisible(APP.store.getState())) {
return APP.store.dispatch(initPrejoin(tracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
this._displayErrorsForCreateInitialLocalTracks(errors);
return this._setLocalAudioVideoStreams(tracks);
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(

View File

@@ -14,9 +14,6 @@ var config = {
// Domain for authenticated users. Defaults to <domain>.
// authdomain: 'jitsi-meet.example.com',
// Call control component (Jigasi).
// call_control: 'callcontrol.jitsi-meet.example.com',
// Focus component domain. Defaults to focus.<domain>.
// focus: 'focus.jitsi-meet.example.com',
@@ -94,6 +91,11 @@ var config = {
// input and will suggest another valid device if one is present.
enableNoAudioDetection: true,
// Enabling this will show a "Save Logs" link in the GSM popover that can be
// used to collect debug information (XMPP IQs, SDP offer/answer cycles)
// about the call.
// enableSaveLogs: false,
// Enabling this will run the lib-jitsi-meet noise detection module which will
// notify the user if there is noise, other than voice, coming from the current
// selected microphone. The purpose it to let the user know that the input could
@@ -120,7 +122,7 @@ var config = {
// Valid values are in the range 6000 to 510000
// opusMaxAverageBitrate: 20000,
// Enables redundancy for Opus
// Enables support for opus-red (redundancy for Opus).
// enableOpusRed: false
// Video
@@ -302,18 +304,11 @@ var config = {
// Disables or enables RTX (RFC 4588) (defaults to false).
// disableRtx: false,
// Disables or enables TCC (the default is in Jicofo and set to true)
// (draft-holmer-rmcat-transport-wide-cc-extensions-01). This setting
// affects congestion control, it practically enables send-side bandwidth
// estimations.
// Disables or enables TCC support in this client (default: enabled).
// enableTcc: true,
// Disables or enables REMB (the default is in Jicofo and set to false)
// (draft-alvestrand-rmcat-remb-03). This setting affects congestion
// control, it practically enables recv-side bandwidth estimations. When
// both TCC and REMB are enabled, TCC takes precedence. When both are
// disabled, then bandwidth estimations are disabled.
// enableRemb: false,
// Disables or enables REMB support in this client (default: enabled).
// enableRemb: true,
// Enables ICE restart logic in LJM and displays the page reload overlay on
// ICE failure. Current disabled by default because it's causing issues with
@@ -323,23 +318,11 @@ var config = {
// TCC sequence numbers starting from 0.
// enableIceRestart: false,
// Defines the minimum number of participants to start a call (the default
// is set in Jicofo and set to 2).
// minParticipants: 2,
// Use TURN/UDP servers for the jitsi-videobridge connection (by default
// we filter out TURN/UDP because it is usually not needed since the
// bridge itself is reachable via UDP)
// useTurnUdp: false
// Enables / disables a data communication channel with the Videobridge.
// Values can be 'datachannel', 'websocket', true (treat it as
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't
// open any channel).
// openBridgeChannel: true,
openBridgeChannel: 'websocket',
// UI
//
@@ -393,6 +376,9 @@ var config = {
// Document should be focused for this option to work
// enableAutomaticUrlCopy: false,
// Base URL for a Gravatar-compatible service. Defaults to libravatar.
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/';
// Stats
//
@@ -605,6 +591,9 @@ var config = {
// If set to true all muting operations of remote participants will be disabled.
// disableRemoteMute: true,
// Enables support for lip-sync for this client (if the browser supports it).
// enableLipSync: false
/**
External API url used to receive branding specific information.
If there is no url set or there are missing fields, the defaults are applied.
@@ -627,6 +616,12 @@ var config = {
// otherwise the app doesn't render it.
// moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com',
// Hides the conference timer.
// hideConferenceTimer: true,
// Sets the conference subject
// subject: 'Conference Subject',
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -673,12 +668,11 @@ var config = {
disableAP
disableHPF
disableNS
enableLipSync
enableTalkWhileMuted
forceJVB121Ratio
forceTurnRelay
hiddenDomain
ignoreStartMuted
startBitrate
*/

View File

@@ -15,9 +15,8 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
height: calc(100vh - 200px);
height: 100vh;
width: 100vw;
margin: 100px 0px;
}
.filmstrip__videos .videocontainer {
@@ -95,7 +94,7 @@
border: 0;
box-sizing: border-box;
display: block;
margin: 5px;
margin: 2px;
}
video {

View File

@@ -1,6 +1,7 @@
platform :ios, '11.0'
workspace 'jitsi-meet'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
install! 'cocoapods', :deterministic_uuids => false
target 'jitsi-meet' do
project 'app/app.xcodeproj'

View File

@@ -293,10 +293,10 @@ PODS:
- React
- react-native-splash-screen (3.2.0):
- React
- react-native-webrtc (1.84.1):
- react-native-webrtc (1.87.1):
- React-Core
- react-native-webview (11.0.2):
- React-Core
- react-native-webview (10.9.0):
- React
- React-RCTActionSheet (0.61.5-jitsi.2):
- React-Core/RCTActionSheetHeaders (= 0.61.5-jitsi.2)
- React-RCTAnimation (0.61.5-jitsi.2):
@@ -562,8 +562,8 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
react-native-webrtc: 40eca4cac200fda34fb843da07e3402211bbbd10
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
React-RCTBlob: 70d88f7b68b5c44953cdb286ac2e36a7a509a97e
@@ -582,6 +582,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: f2400f8e5a52c4d91697cbacba6956569efc5ab8
PODFILE CHECKSUM: f6626cd705333112182cedbe175ae2f9006e8874
COCOAPODS: 1.9.3
COCOAPODS: 1.10.0

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
@@ -13,8 +13,6 @@
0B412F211EDEE95300B1A0A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0B412F201EDEE95300B1A0A6 /* Main.storyboard */; };
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */; };
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B70016F1F7C51CC005944F4 /* InCallController.swift */; };
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; };
0BD6B4381EF82A6B00D1F4CD /* WebRTC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0BEA5C291F7B8F73000D0AB4 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */; };
0BEA5C2B1F7B8F73000D0AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C2A1F7B8F73000D0AB4 /* Assets.xcassets */; };
0BEA5C321F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -28,6 +26,8 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */; };
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; };
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */; };
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
@@ -57,8 +57,8 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
0B26BE6F1EC5BC3C00EEFB41 /* JitsiMeet.framework in Embed Frameworks */,
0BD6B4381EF82A6B00D1F4CD /* WebRTC.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -117,8 +117,10 @@
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.debug.xcconfig"; sourceTree = "<group>"; };
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-jitsi-meet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
DE050388256E904600DEE3A5 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/apple/WebRTC.xcframework"; sourceTree = "<group>"; };
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUtilities.m; sourceTree = "<group>"; };
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRUtilities.h; sourceTree = "<group>"; };
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -136,8 +138,8 @@
buildActionMask = 2147483647;
files = (
0B26BE6E1EC5BC3C00EEFB41 /* JitsiMeet.framework in Frameworks */,
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */,
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */,
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -154,7 +156,9 @@
0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
isa = PBXGroup;
children = (
DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */,
);
@@ -290,8 +294,6 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */,
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEF4813D224925A2002AD03A /* Copy Google Plist file */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
@@ -420,20 +422,6 @@
shellPath = /bin/sh;
shellScript = "../scripts/run-packager.sh\n";
};
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Adjust embedded framework architectures";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-frameworks.sh\n";
};
B6607F42A5CF0C76E98929E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -474,24 +462,6 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nGOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
};
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Copy WebRTC dSYM";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -x\n\nif [[ \"${CONFIGURATION}\" != \"Debug\" ]]; then\n cp -r ../../node_modules/react-native-webrtc/ios/WebRTC.dSYM ${DWARF_DSYM_FOLDER_PATH}/\nfi\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -652,7 +622,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 4.0;
@@ -679,7 +650,11 @@
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = watchos/extension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
@@ -713,12 +688,17 @@
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = watchos/extension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 4.0;
@@ -729,7 +709,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -738,16 +717,11 @@
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"../../node_modules/react-native-webrtc/ios",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -764,7 +738,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -772,16 +745,11 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"../../node_modules/react-native-webrtc/ios",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",

View File

@@ -45,6 +45,8 @@
#endif
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
// Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided.
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enabling Firebase");
@@ -55,8 +57,6 @@
ViewController *rootController = (ViewController *)self.window.rootViewController;
[jitsiMeet showSplashScreen:rootController.view];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}

View File

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

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
#!/bin/bash
# This script will download a bitcode build of the WebRTC framework, if needed.
if [[ ! "$CONFIGURATION" = "Debug" ]]; then
RN_WEBRTC="$SRCROOT/../../node_modules/react-native-webrtc"
if otool -arch arm64 -l $RN_WEBRTC/ios/WebRTC.framework/WebRTC | grep -q LLVM; then
echo "WebRTC framework has bitcode"
else
echo "WebRTC framework has NO bitcode"
$RN_WEBRTC/tools/downloadBitcode.sh
fi
fi

View File

@@ -1,39 +0,0 @@
#!/bin/bash
# This script gets executed from Xcode to fixup the embedded frameworks and
# bundle the necessary architectures.
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
if lipo -info "$FRAMEWORK_EXECUTABLE_PATH" | grep -q -v "^Non-fat"
then
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
fi
done
if [ -n "$EXTRACTED_ARCHS" ]
then
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
fi
done

View File

@@ -24,8 +24,36 @@ popd
# Build the SDK
pushd ${PROJECT_REPO}
rm -rf ios/sdk/JitsiMeet.framework
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release ENABLE_BITCODE=NO clean archive
rm -rf ios/sdk/out
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-configuration Release \
-sdk iphonesimulator \
-destination='generic/platform=iOS Simulator' \
-archivePath ios/sdk/out/ios-simulator \
VALID_ARCHS=x86_64 \
ENABLE_BITCODE=NO \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-configuration Release \
-sdk iphoneos \
-destination='generic/platform=iOS' \
-archivePath ios/sdk/out/ios-device \
VALID_ARCHS=arm64 \
ENABLE_BITCODE=NO \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-output ios/sdk/out/JitsiMeet.xcframework
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-${SDK_VERSION}
fi
@@ -34,12 +62,8 @@ popd
pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -r ${PROJECT_REPO}/ios/sdk/JitsiMeet.framework Frameworks/
cp -r ${PROJECT_REPO}/node_modules/react-native-webrtc/ios/WebRTC.framework Frameworks/
# Strip bitcode
xcrun bitcode_strip -r Frameworks/JitsiMeet.framework/JitsiMeet -o Frameworks/JitsiMeet.framework/JitsiMeet
xcrun bitcode_strip -r Frameworks/WebRTC.framework/WebRTC -o Frameworks/WebRTC.framework/WebRTC
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeet.xcframework Frameworks/
cp -a ${PROJECT_REPO}/node_modules/react-native-webrtc/apple/WebRTC.xcframework Frameworks/
# Add all files to git
if [[ $DO_GIT_TAG == 1 ]]; then

View File

@@ -81,8 +81,6 @@
0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = "<group>"; };
0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = "<group>"; };
0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = "<group>"; };
0BCA49631EC4B76D00B793EE /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = jitsi.ttf; path = ../../fonts/jitsi.ttf; sourceTree = "<group>"; };
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -153,7 +151,6 @@
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */,
75635B0820751D6D00F29C9F /* joined.wav */,
75635B0920751D6D00F29C9F /* left.wav */,
C30F88D1CB0F4F5593216D24 /* liveStreamingOff.mp3 */,
@@ -240,7 +237,6 @@
0B93EF7A1EC608550030D24D /* CoreText.framework */,
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -321,7 +317,6 @@
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
buildPhases = (
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */,
DE3D81D6228B50FB00A6C149 /* Bitcode */,
0BD906E01EC0C00300C8C18E /* Sources */,
0BD906E11EC0C00300C8C18E /* Frameworks */,
0BD906E21EC0C00300C8C18E /* Headers */,
@@ -454,24 +449,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
showEnvVarsInLog = 0;
};
DE3D81D6228B50FB00A6C149 /* Bitcode */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = Bitcode;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/bitcode.sh\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -635,7 +612,6 @@
baseConfigurationReference = 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -646,7 +622,6 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -655,7 +630,6 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -665,7 +639,6 @@
baseConfigurationReference = 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -676,7 +649,6 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -684,7 +656,6 @@
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Release;

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -72,23 +72,5 @@
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "exec &gt; /tmp/${PROJECT_NAME}_archive.log 2&gt;&amp;1&#10;&#10;UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal&#10;&#10;if [ &quot;true&quot; == ${ALREADYINVOKED:-false} ]&#10;then&#10;echo &quot;RECURSION: Detected, stopping&quot;&#10;else&#10;export ALREADYINVOKED=&quot;true&quot;&#10;&#10;# make sure the output directory exists&#10;mkdir -p &quot;${UNIVERSAL_OUTPUTFOLDER}&quot;&#10;&#10;echo &quot;Building for iPhoneSimulator&quot;&#10;xcodebuild -workspace &quot;${WORKSPACE_PATH}&quot; -scheme &quot;${TARGET_NAME}&quot; -configuration ${CONFIGURATION} -sdk iphonesimulator -destination &apos;platform=iOS Simulator,name=iPhone 8&apos; ONLY_ACTIVE_ARCH=NO ARCHS=&apos;x86_64&apos; BUILD_DIR=&quot;${BUILD_DIR}&quot; BUILD_ROOT=&quot;${BUILD_ROOT}&quot; ENABLE_BITCODE=YES OTHER_CFLAGS=&quot;-fembed-bitcode&quot; BITCODE_GENERATION_MODE=bitcode build&#10;&#10;# Step 1. Copy the framework structure (from iphoneos build) to the universal folder&#10;echo &quot;Copying to output folder&quot;&#10;cp -R &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FULL_PRODUCT_NAME}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/&quot;&#10;&#10;# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory&#10;SIMULATOR_SWIFT_MODULES_DIR=&quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/.&quot;&#10;if [ -d &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; ]; then&#10;cp -R &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule&quot;&#10;fi&#10;&#10;# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory&#10;echo &quot;Combining executables&quot;&#10;lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${EXECUTABLE_PATH}&quot;&#10;&#10;fi&#10;&#10;# Step 4. Convenience step to copy the framework to the project&amp;apos;s directory&#10;echo &quot;Copying to project dir&amp;quot&quot;&#10;yes | cp -Rf ${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME} ${PROJECT_DIR}&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
</ArchiveAction>
</Scheme>

View File

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

View File

@@ -18,6 +18,29 @@
import CallKit
import Foundation
public protocol CXProviderProtocol: class {
var configuration: CXProviderConfiguration { get set }
func setDelegate(_ delegate: CXProviderDelegate?, queue: DispatchQueue?)
func reportNewIncomingCall(with UUID: UUID, update: CXCallUpdate, completion: @escaping (Error?) -> Void)
func reportCall(with UUID: UUID, updated update: CXCallUpdate)
func reportCall(with UUID: UUID, endedAt dateEnded: Date?, reason endedReason: CXCallEndedReason)
func reportOutgoingCall(with UUID: UUID, startedConnectingAt dateStartedConnecting: Date?)
func reportOutgoingCall(with UUID: UUID, connectedAt dateConnected: Date?)
func invalidate()
}
public protocol CXCallControllerProtocol: class {
var calls: [CXCall] { get }
func request(_ transaction: CXTransaction, completion: @escaping (Error?) -> Swift.Void)
}
extension CXProvider: CXProviderProtocol {}
extension CXCallController: CXCallControllerProtocol {
public var calls: [CXCall] {
return callObserver.calls
}
}
/// JitsiMeet CallKit proxy
// NOTE: The methods this class exposes are meant to be called in the UI thread.
// All delegate methods called by JMCallKitEmitter will be called in the UI thread.
@@ -26,11 +49,17 @@ import Foundation
private override init() {}
// MARK: - CallKit proxy
public static var callKitProvider: CXProviderProtocol?
public static var callKitCallController: CXCallControllerProtocol?
private static var provider: CXProvider = {
let configuration = CXProviderConfiguration(localizedName: "")
return CXProvider(configuration: configuration)
}()
private static var provider: CXProviderProtocol {
callKitProvider ?? defaultProvider
}
private static var callController: CXCallControllerProtocol {
callKitCallController ?? defaultCallController
}
private static var providerConfiguration: CXProviderConfiguration? {
didSet {
@@ -39,10 +68,15 @@ import Foundation
provider.setDelegate(emitter, queue: nil)
}
}
private static let callController: CXCallController = {
private static let defaultCallController: CXCallController = {
return CXCallController()
}()
private static var defaultProvider: CXProvider = {
let configuration = CXProviderConfiguration(localizedName: "")
return CXProvider(configuration: configuration)
}()
private static let emitter: JMCallKitEmitter = {
return JMCallKitEmitter()
@@ -52,10 +86,16 @@ import Foundation
/// Defaults to enabled, set to false when you don't want to use CallKit.
@objc public static var enabled: Bool = true {
didSet {
provider.invalidate()
if callKitProvider == nil {
provider.invalidate()
}
if enabled {
guard isProviderConfigured() else { return; }
provider = CXProvider(configuration: providerConfiguration!)
guard isProviderConfigured() else { return }
if callKitProvider == nil {
defaultProvider = CXProvider(configuration: providerConfiguration!)
}
provider.setDelegate(emitter, queue: nil)
} else {
provider.setDelegate(nil, queue: nil)
@@ -92,19 +132,18 @@ import Foundation
}
@objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool {
let activeCallForUUID = callController.callObserver.calls.first {
let activeCallForUUID = callController.calls.first {
$0.uuid == UUID(uuidString: callUUID)
}
guard activeCallForUUID != nil else { return false }
return true
}
@objc public static func reportNewIncomingCall(
UUID: UUID,
handle: String?,
displayName: String?,
hasVideo: Bool,
completion: @escaping (Error?) -> Void) {
@objc public static func reportNewIncomingCall(UUID: UUID,
handle: String?,
displayName: String?,
hasVideo: Bool,
completion: @escaping (Error?) -> Void) {
guard enabled else { return }
let callUpdate = makeCXUpdate(handle: handle,
@@ -132,7 +171,6 @@ import Foundation
endedAt dateEnded: Date?,
reason endedReason: CXCallEndedReason) {
guard enabled else { return }
provider.reportCall(with: UUID,
endedAt: dateEnded,
reason: endedReason)
@@ -142,7 +180,6 @@ import Foundation
with UUID: UUID,
startedConnectingAt dateStartedConnecting: Date?) {
guard enabled else { return }
provider.reportOutgoingCall(with: UUID,
startedConnectingAt: dateStartedConnecting)
}

View File

@@ -1,27 +1,50 @@
{
"en": "Angličtina",
"af": "",
"az": "",
"af": "Afrikánština",
"ar": "Arabština",
"az": "Ázerbájdžánština",
"bg": "Bulharština",
"cs": "",
"ca": "Katalánština",
"cs": "Čeština",
"da": "Dánština",
"de": "Němčina",
"el": "",
"el": "Řečtina",
"enGB": "Angličtina (Spojené království)",
"eo": "Esperanto",
"es": "Španělština",
"esUS": "Španělština (Latinská Amerika)",
"et": "Estonština",
"eu": "Baskičtina",
"fi": "Finština",
"fr": "Francouština",
"frCA": "Francouzština (Kanada)",
"he": "Hebrejština",
"mr":"Maráthština",
"hr": "Chorvatština",
"hu": "Maďarština",
"hy": "Arménština",
"id": "Indonéština",
"it": "Italština",
"ja": "",
"ko": "",
"ja": "Japonština",
"kab": "Kabylština",
"ko": "Korejština",
"lt": "Litevština",
"nl": "Nizozemština",
"nb": "Norština Bokmal",
"oc": "Okcitánština",
"pl": "Polština",
"ptBR": "Portugalština (Brazilská)",
"ptBR": "Portugalština (Brazílie)",
"ru": "Ruština",
"ro": "Rumunština",
"sc": "Sardinština",
"sk": "Slovenština",
"sl": "Slovinština",
"sr": "Srbština",
"sv": "Švédština",
"th": "Thajština",
"tr": "Turečtina",
"vi": "",
"zhCN": "Čínština (Čína)"
}
"uk": "Ukrajinština",
"vi": "Vietnamština",
"zhCN": "Čínština (Čína)",
"zhTW": "Čínština (Taiwan)"
}

View File

@@ -28,6 +28,7 @@
"kab": "Kabyle",
"ko": "Korean",
"lt": "Lithuanian",
"lv": "Latvian",
"nl": "Dutch",
"oc": "Occitan",
"pl": "Polish",

File diff suppressed because it is too large Load Diff

View File

@@ -707,7 +707,7 @@
"kick": "Teilnehmer entfernen",
"lobbyButton": "Lobbymodus ein-/ausschalten",
"localRecording": "Lokale Aufzeichnungssteuerelemente ein-/ausschalten",
"lockRoom": "Konferenzpasswort ein-/auschalten",
"lockRoom": "Konferenzpasswort ein-/ausschalten",
"moreActions": "Menü „Weitere Aktionen“ ein-/ausschalten",
"moreActionsMenu": "Menü „Weitere Aktionen“",
"moreOptions": "Menü „Weitere Optionen“",

View File

@@ -1,28 +1,43 @@
{
"addPeople": {
"add": "Invita",
"addContacts": "Invita tuoi contatti",
"copyInvite": "Copia invito della riunione",
"copyLink": "Copia collegamento della riunione",
"copyStream": "Copia collegamento della diretta",
"countryNotSupported": "Non supportiamo ancora questa destinazione.",
"countryReminder": "Stai chiamando fuori dagli Stati Uniti? Assicurati d'inserire il prefisso internazionale!",
"defaultEmail": "Tua Email di default",
"disabled": "Non puoi invitare persone.",
"failedToAdd": "L'aggiunta di nuove persone è fallita",
"footerText": "La chiamata all'esterno è disabilitata.",
"googleEmail": "Email Google",
"inviteMoreHeader": "Sei l'unico presente alla riunione",
"inviteMoreMailSubject": "Unisciti alla riunione {{appName}}",
"inviteMorePrompt": "Invita altra gente",
"linkCopied": "Collegamento copiato negli appunti",
"loading": "Sto cercando persone e numeri di telefono",
"loadingNumber": "Sto validando il numero di telefono",
"loadingPeople": "Sto cercando le persone da invitare",
"noResults": "Nessun risultato corrispondente",
"noValidNumbers": "Per favore inserire un numero di telefono",
"outlookEmail": "Email Outlook",
"searchNumbers": "Aggiungi numeri di telefono",
"searchPeople": "Cerca persone",
"searchPeopleAndNumbers": "Cerca persone o aggiungi i loro numeri di telefono",
"shareInvite": "Condividi invito alla riunione",
"shareLink": "Condividi il collegamento alla riunione per invitare altri",
"shareStream": "Condividi il collegamento alla diretta",
"telephone": "Telefono: {{number}}",
"title": "Invita persone a questo meeting"
"title": "Invita persone a questa riunione",
"yahooEmail": "Email Yahoo"
},
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Cuffie",
"phone": "Telefono",
"speaker": "Vivavoce",
"none": ""
"none": "Nessun disposistivo audio esistente"
},
"audioOnly": {
"audioOnly": "Solo audio"
@@ -47,20 +62,25 @@
},
"chat": {
"error": "Errore: il tuo messaggio “{{originalText}}” non e stato inviato. Motivo: {{error}}",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"messagebox": "Digitare un messaggio",
"messageTo": "Messaggio privato per {{recipient}}",
"noMessagesMessage": "Non ci sono ancora messaggi nella riunione. Comincia una conversazione, qui!",
"nickname": {
"popover": "Scegli un nickname",
"title": "Inserire un nickname per utilizzare la chat"
},
"privateNotice": "Messaggio privato per {{recipient}}",
"title": "Chat",
"you": "",
"privateNotice": "",
"noMessagesMessage": "",
"messageTo": "",
"fieldPlaceHolder": ""
"you": "tu"
},
"chromeExtensionBanner": {
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365",
"buttonText": "Installa l'estensione Chrome",
"dontShowAgain": "Non mostrare più questo messaggio"
},
"connectingOverlay": {
"joiningRoom": "Collegamento al tuo meeting in corso…"
"joiningRoom": "Collegamento alla riunione in corso…"
},
"connection": {
"ATTACHED": "Collegato",
@@ -72,24 +92,27 @@
"DISCONNECTED": "Disconnesso",
"DISCONNECTING": "Disconnessione in corso",
"ERROR": "Errore",
"RECONNECTING": "Si è verificato un problema di rete. Riconnessione...",
"LOW_BANDWIDTH": "",
"GOT_SESSION_ID": "",
"GET_SESSION_ID_ERROR": "",
"FETCH_SESSION_ID": ""
"FETCH_SESSION_ID": "Sto ottenendo ID di sessione...",
"GET_SESSION_ID_ERROR": "Id dell'errore di sessione: {{code}}",
"GOT_SESSION_ID": "Ricevuto ID di sessione",
"LOW_BANDWIDTH": "Il video per {{displayName}} è stato interrotto per risparmiare banda"
},
"connectionindicator": {
"address": "Indirizzo:",
"audio_ssrc": "Audio SSRC:",
"bandwidth": "Banda stimata:",
"bitrate": "Bitrate:",
"bridgeCount": "Contatore server:",
"codecs": "Codec (A/V): ",
"connectedTo": "Connesso a:",
"e2e_rtt": "E2E RTT:",
"framerate": "Fotogrammi al secondo:",
"less": "Mostra meno",
"localaddress": "Indirizzo locale:",
"localaddress_plural": "Indirizzi locali:",
"localport": "Porta locale:",
"localport_plural": "Porte locali:",
"maxEnabledResolution": "manda max",
"more": "Mostra di più",
"packetloss": "Perdita pacchetti:",
"quality": {
@@ -104,11 +127,12 @@
"remoteport": "Porta remota:",
"remoteport_plural": "Porte remote:",
"resolution": "Risoluzione:",
"savelogs": "Salva log",
"participant_id": "Id participante:",
"status": "Connessione:",
"transport": "Trasporto:",
"transport_plural": "Trasporti:",
"turn": "(ruota)",
"e2e_rtt": ""
"video_ssrc": "Video SSRC:"
},
"dateUtils": {
"earlier": "Prima",
@@ -118,7 +142,7 @@
"deepLinking": {
"appNotInstalled": "Per partecipare a questo meeting sul tuo telefono ti serve l'app mobile di {{app}}",
"description": "Non è successo nulla? Abbiamo provato ad avviare la tua videoconferenza sull'app desktop di {{app}}. Prova di nuovo o avviala nell'app web di {{app}}.",
"descriptionWithoutWeb": "",
"descriptionWithoutWeb": "Non è successo niente? Abbiamo provato ad avviare la riunione nell'app per desktop {{app}}",
"downloadApp": "Scarica l'app",
"ifDoNotHaveApp": "Se non hai ancora l'app:",
"ifHaveApp": "Se hai già l'app:",
@@ -128,6 +152,7 @@
"tryAgainButton": "Prova di nuovo sul desktop"
},
"defaultLink": "es. {{url}}",
"defaultNickname": "es. Anna Rossi",
"deviceError": {
"cameraError": "Impossibile accedere alla videocamera",
"cameraPermission": "Errore nell'ottenere i permessi per la videocamera",
@@ -142,8 +167,9 @@
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Diretta"
"liveStreaming": "Diretta streaming"
},
"add": "Aggiungi",
"allow": "Consenti",
"alreadySharedVideoMsg": "Un altro utente sta condividendo un video. Questa conferenza permette di condividere un solo video alla volta.",
"alreadySharedVideoTitle": "È permesso un solo video alla volta",
@@ -169,25 +195,26 @@
"connectErrorWithMsg": "Oops! Qualcosa è andato storto e non ti puoi collegare alla conferenza: {{msg}}",
"connecting": "Connessione",
"contactSupport": "Contatta il supporto",
"copied": "Copiato",
"copy": "Copia",
"dismiss": "Scarta",
"displayNameRequired": "Tutti devono avere un nome",
"done": "Fatto",
"enterDisplayName": "Inserisci il nome da visualizzare",
"e2eeDescription": "La crittografia punto-a-punto al momento è SPERIMENTALE. Tieni presente che attivandola disabiliterai i servizi svolti dal server, come: la registrazione su Dropobox, le dirette streaming e la partecipazione usando solo telefoni. Tieni anche presente che la riunione funzionerà solo per chi si collega usando browser che supportano flussi inseribili (insertable streams).",
"e2eeLabel": "Attiva la crittografia punto-a-punto",
"e2eeWarning": "ATTENZIONE: non tutti i partecipanti a questa riunione sembrano supportare le funzionalità di crittografia punto-a-punto. Se la attivi, non potranno sentirti, o vederti.",
"enterDisplayName": "Inserisci qui il tuo nome",
"error": "Errore",
"externalInstallationMsg": "Devi installare la nostra estensione per la condivisione desktop.",
"externalInstallationTitle": "Richiesta estensione",
"goToStore": "Vai al negozio on-line",
"gracefulShutdown": "Il nostro servizio è al momento spento per manutenzione. Si prega di riprovare più tardi.",
"grantModeratorDialog": "Sei sicuro di voler rendere moderatore questo partecipante?",
"grantModeratorTitle": "Autorizza moderatore",
"IamHost": "Sono l'organizzatore",
"incorrectRoomLockPassword": "",
"incorrectRoomLockPassword": "Password errata",
"incorrectPassword": "Nome utente o password errati",
"inlineInstallationMsg": "Devi installare la nostra estensione per la condivisione desktop.",
"inlineInstallExtension": "Installa adesso",
"internalError": "Ops! Qualcosa è andato storto. Questo è l'errore: {{error}}",
"internalErrorTitle": "Errore interno",
"kickMessage": "Acc! Sei stato espulso dal meeting!",
"kickParticipantButton": "Espelli",
"kickParticipantButton": "Butta fuori",
"kickParticipantDialog": "Sei sicuro di voler espellere questo partecipante?",
"kickParticipantTitle": "Espellere questi partecipante?",
"kickTitle": "Espulso dal meeting",
@@ -203,21 +230,28 @@
"maxUsersLimitReachedTitle": "Raggiunto limite partecipanti",
"micConstraintFailedError": "Il tuo microfono non soddisfa alcuni dei requisiti richiesti.",
"micNotFoundError": "Microfono non trovato.",
"micNotSendingData": "Non riusciamo a ricevere suoni dal microfono scelto. Prova a selezionare nelle impostazioni un microfono diverso, o a riavvare l'applicazione.",
"micNotSendingData": "Non riusciamo a ricevere suoni dal microfono scelto. Prova a selezionare nelle impostazioni un microfono diverso, o a riavviare l'applicazione.",
"micNotSendingDataTitle": "Impossibile accedere al microfono",
"micPermissionDeniedError": "Non hai concesso il permesso di usare il microfono. Puoi comunque partecipare alla conferenza ma gli altri non potranno sentirti. Usa il bottone a forma di telecamera nella barra degli indirizzi per cambiare impostazioni.",
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
"muteParticipantBody": "Tu non sarai in grado di riattivare il loro audio, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Silenzia partecipante",
"muteParticipantDialog": "Sei sicuro di voler disattivare l'audio di questo partecipante? Saranno loro a doversi riattivare l'audio, per parlare.",
"muteParticipantTitle": "Silenzio questo partecipante?",
"Ok": "Ok",
"passwordLabel": "",
"passwordNotSupported": "Le password per le videoconferenze non sono supportate.",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"popupError": "Il tuo browser sta bloccando i pop-up da questo sito. Per favore abilità i pop-up dalle impostazioni di sicurezza del browser e riprova.",
"muteEveryoneElseDialog": "Una volta zittiti, non potrai riattivargli i microfoni, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
"muteEveryoneDialog": "Sei sicuro di voler zittire tutti? Non potrai riattivar loro il microfono, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneTitle": "Zittisco tutti?",
"muteEveryoneSelf": "te stesso",
"muteEveryoneStartMuted": "Tutti cominciano a microfono spento, d'adessp in avanti",
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Zittisci",
"muteParticipantDialog": "Sei sicuro di voler zittire questo partecipante? Saranno lui a doversi riattivare l'audio, per parlare.",
"muteParticipantTitle": "Zittisco questo partecipante?",
"Ok": "OK",
"passwordLabel": "La riunione è stata bloccata da un partecipante. Immetti la $t(lockRoomPassword) per collegarti, per favore.",
"passwordNotSupported": "Le password per le riunioni non sono supportate.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) non supportato",
"passwordRequired": "E' richiesto $t(lockRoomPasswordUppercase)",
"popupError": "Il tuo browser sta bloccando i pop-up da questo sito. Per favore abilita i pop-up dalle impostazioni di sicurezza del browser e riprova.",
"popupErrorTitle": "Pop-up bloccato",
"readMore": "altro",
"recording": "Registrazione",
"recordingDisabledForGuestTooltip": "Gli ospiti non possono avviare una registrazione.",
"recordingDisabledTooltip": "Registrazione disabilitata.",
@@ -236,11 +270,14 @@
"reservationError": "Errore di sistema in prenotazione",
"reservationErrorMsg": "Codice di errore: {{code}}, messaggio: {{msg}}",
"retry": "Riprova",
"screenSharingFailedToInstall": "Oh! Non è stato possibile installare l'estensione per la condivisione schermo.",
"screenSharingFailedToInstallTitle": "Impossibile installare l'estensione per la condivisione schermo",
"screenSharingFirefoxPermissionDeniedError": "Qualcosa è andato storto mentre cercavamo di condividere il tuo schermo. Assicurati di averci dato il premesso di condivisione.",
"screenSharingFirefoxPermissionDeniedTitle": "Ops! Non siamo stati in grado di avviare la condivisione schermo!",
"screenSharingPermissionDeniedError": "Oops! Qualcosa è andato storto con le impostazioni dell'estensione per la condivisione dello schermo. Ricarica la pagina e prova di nuovo.",
"screenSharingAudio": "Condividi audio",
"screenSharingFailed": "Ops! Non è stato possibile avviare la condivisione dello schermo!",
"screenSharingFailedTitle": "Condivisione dello schermo fallita!",
"screenSharingPermissionDeniedError": "Qualcosa non funziona nei permessi di condivisione dello schermo. Ricarica e prova ancora, autorizzando la condivisione.",
"sendPrivateMessage": "Hai ricevuto un messaggio privato, poco fa. Vorresti rispondergli privatamente, o vuoi mandare la risposta al gruppo?",
"sendPrivateMessageCancel": "Invia al gruppo",
"sendPrivateMessageOk": "Invia privatamente",
"sendPrivateMessageTitle": "Mando privatamente?",
"serviceUnavailable": "Servizio non disponibile",
"sessTerminated": "Chiamata terminata",
"Share": "Condividi",
@@ -248,7 +285,6 @@
"shareVideoTitle": "Condividi un video",
"shareYourScreen": "Condividi schermo",
"shareYourScreenDisabled": "Condivisione schermo disabilitata.",
"shareYourScreenDisabledForGuest": "Gli ospiti non possono condividere lo schermo.",
"startLiveStreaming": "Inizia una diretta",
"startRecording": "Inizia a registrare",
"startRemoteControlErrorMessage": "Si è verificato un errore cercando di avviare la sessione di controllo remoto!",
@@ -269,21 +305,19 @@
"WaitForHostMsgWOk": "La conferenza <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, allora premi OK per autenticarti. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitingForHost": "In attesa dell'organizzatore ...",
"Yes": "Sì",
"yourEntireScreen": "Schermo intero",
"sendPrivateMessageTitle": "",
"sendPrivateMessageOk": "",
"sendPrivateMessageCancel": "",
"sendPrivateMessage": "",
"screenSharingAudio": "",
"muteEveryoneStartMuted": "",
"muteEveryoneSelf": "",
"muteEveryoneTitle": "",
"muteEveryoneDialog": "",
"muteEveryoneElseTitle": "",
"muteEveryoneElseDialog": ""
"yourEntireScreen": "Schermo intero"
},
"dialOut": {
"statusMessage": ora {{status}}"
"statusMessage": "è {{status}}"
},
"documentSharing": {
"title": "Documento condiviso"
},
"e2ee": {
"labelToolTip": "Le comunicazioni audio e video di questa chiamata, sono crittografate dall'origine alla destinazione"
},
"embedMeeting": {
"title": "Incorpora questa riunione altrove"
},
"feedback": {
"average": "Media",
@@ -309,14 +343,14 @@
"country": "Paese",
"dialANumber": "Per collegarti telefonicamente al meeting, chiama uno di questi numeri e metti il pin.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Spiacenti, la partecipazionne solo telefonica non è supportata attualmente",
"dialInNotSupported": "Spiacenti, la partecipazione solo telefonica non è supportata attualmente",
"dialInNumber": "Componi:",
"dialInSummaryError": "Errore nella ricerca dei numeri telefonici. Riprova più tardi.",
"dialInTollFree": "Numero verde",
"genericError": "Ops, qualcosa è andato storto.",
"inviteLiveStream": "Per vedere la diretta di questo meeting, clicca su questo link: {{url}}",
"invitePhone": "Per seguire solo telefonicamente, clicca: {{number}},,{{conferenceID}}#",
"invitePhoneAlternatives": "",
"invitePhone": "ATTENZIONE E' UNA CHIAMATA INTERNAZIONALE A PAGAMENTO! NON E' GRATUITA. Per seguire solo telefonicamente, clicca: {{number}},,{{conferenceID}}#",
"invitePhoneAlternatives": "Cerchi un numero diverso da chiamare?\nEcco dei numeri telefonici per collegarsi alle riunioni: {{url}}\n\n\nSe entri in riunione anche col computer, entra senza attivare l'audio: {{silentUrl}}",
"inviteURLFirstPartGeneral": "Invito a connettersi ad una conferenza.",
"inviteURLFirstPartPersonal": "{{name}} ti sta invitando ad un meeting.\n",
"inviteURLSecondPart": "\nPartecipa al meeting:\n{{url}}\n",
@@ -332,12 +366,12 @@
"label": "Informazioni meeting"
},
"inviteDialog": {
"alertText": "",
"alertText": "Errore nell'invitare alcuni partecipanti.",
"header": "Invita",
"searchCallOnlyPlaceholder": "Inserisci numero di telefono",
"searchPeopleOnlyPlaceholder": "",
"searchPlaceholder": "",
"send": ""
"searchPeopleOnlyPlaceholder": "Cerca partecipanti",
"searchPlaceholder": "Partecipante o numero di telefono",
"send": "Invia"
},
"inlineDialogFailure": {
"msg": "Un piccolo inconveniente.",
@@ -360,9 +394,11 @@
"toggleScreensharing": "Cambia modalità tra videocamera e condivisione schermo",
"toggleShortcuts": "Mostra o nascondi le scorciatoie",
"videoMute": "Accendo o spegni la videocamera",
"videoQuality": ""
"videoQuality": "Imposta qualità della telefonata"
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "La tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova {{app}}.",
"busy": "Stiamo cercando di liberare risorse per la diretta. Riprova tra qualche minuto.",
"busyTitle": "Tutti gli streamer sono impegnati al momento",
"changeSignIn": "Cambia account",
@@ -379,7 +415,9 @@
"getStreamKeyManually": "Non siamo stati in grado di trovare nessuna trasmissione dal vivo. Prova ad ottenere una chiave stream da Youtube",
"invalidStreamKey": "La chiave stream potrebbe non essere corretta.",
"off": "La diretta si è interrotta",
"offBy": "{{name}} ha fermato la diretta",
"on": "Trasmissione in diretta",
"onBy": "{{name}} ha iniziato la diretta",
"pending": "Avvio diretta...",
"serviceName": "Servizio live streaming",
"signedInAs": "Sei attualmente collegato come:",
@@ -389,10 +427,8 @@
"start": "Inizia una diretta",
"streamIdHelp": "Cos'è questo?",
"unavailableTitle": "La diretta non è disponibile",
"onBy": "",
"offBy": "",
"googlePrivacyPolicy": "Google Privacy Policy",
"youtubeTerms": "YouTube terms of services"
"youtubeTerms": "YouTube terms of services",
"googlePrivacyPolicy": "Google Privacy Policy"
},
"localRecording": {
"clientState": {
@@ -443,24 +479,84 @@
"muted": "Hai iniziato la conversazione con l'audio disattivato.",
"mutedTitle": "Hai l'audio disattivato!",
"mutedRemotelyTitle": "Ti è stato disattivato l'audio da {{participantDisplayName}}!",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono, quando vuoi parlare. Spegni il microfono quando hai finito, per non introdurre rumori di fondo nella riunione.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata tolta da un altro partecipante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) è stata messa da un altro partecipante",
"raisedHand": "{{name}} vorrebbe intervenire.",
"somebody": "Qualcuno",
"startSilentTitle": "",
"startSilentDescription": "",
"startSilentTitle": "Sei entrato in riunione senza aver scelto un dispositivo audio per sentire!",
"startSilentDescription": "Entra di nuovo in riunione, per attivare l'audio",
"suboptimalExperienceDescription": "Ehm... temiamo che la tua esperienza con {{appName}} non sarà granché su questo browser. Stiamo cercando di migliorare la situazione ma, per il momento, prova ad utilizzare uno di questi <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser supportati</a>.",
"suboptimalExperienceTitle": "Problemi con il browser",
"unmute": "",
"unmute": "Accendi microfono",
"newDeviceCameraTitle": "Trovata nuova videocamera",
"newDeviceAudioTitle": "Trovata nuova origine audio",
"newDeviceAction": "Usala",
"suboptimalBrowserWarning": "Ci spiace che la tua videoconferenza non sarà ottimale, qui. Stiamo cercando modi per migliorare, ma fino ad allora per favore prova ad usare <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser supportati completamente/a>."
"newDeviceAction": "OK, usala",
"OldElectronAPPTitle": "Falla di sicurezza!",
"oldElectronClientDescription1": "Sembri stare usando una versione obsoleta di Jitsi Meet che ha delle falle di sicurezza note. Assicurati di aggiornarla presso il nostro ",
"oldElectronClientDescription2": "ultima build",
"oldElectronClientDescription3": " ora!"
},
"passwordSetRemotely": "definita da altro utente",
"passwordDigitsOnly": "Fino a {{number}} cifre",
"poweredby": "offerto da",
"prejoin": {
"audioAndVideoError": "Errore audio e video:",
"audioDeviceProblem": "C'è un problema con il tuo microfono",
"audioOnlyError": "Errore audio:",
"audioTrackError": "Impossibile creare traccia audio.",
"calling": "Chiamando",
"callMe": "Chiamami",
"callMeAtNumber": "Chiamami a questo numero:",
"configuringDevices": "Configurazione dispositivi...",
"connectedWithAudioQ": "Sei connesso con l'audio?",
"connection": {
"good": "La tua connessione Internet sembra buona!",
"nonOptimal": "La tua connessione Internet non è ottimale",
"poor": "La tua connessione Internet è scarsa"
},
"connectionDetails": {
"audioClipping": "Ci aspettiamo che il tuo audio sarà a sighiozzo.",
"audioHighQuality": "Ci aspettiamo che il tuo audio sarà di eccellente qualità.",
"audioLowNoVideo": "Ci aspettiamo una bassa qualità audio e che il video sia assente.",
"goodQuality": "Ottimo! La tue funzioni multimediali andranno alla grande.",
"noMediaConnectivity": "Non siamo riusciti a stabilire una connessione multimediale per fare questo test. Questo è tipicamente causato da un firewall o dal NAT.",
"noVideo": "Ci aspettiamo una pessima qualità video.",
"undetectable": "Se non riesci ancora a fare chiamate nel browser, ti consigliamo di verificare che il microfono e la tua videocamera siano configurate correttamente, che tu abbia dato al browser i permessi di usare microfono e videocamera, e che il tuo browser sia aggiornato all'ultima versione. Se hai ancora problemi, dovresti contattare lo sviluppatore dell'applicazione web.",
"veryPoorConnection": "Ci aspettiamo una qualità della chiamata davvero terribile.",
"videoFreezing": "Ci aspettiamo che il video si blocchi, sparisca, o sia molto pixelato.",
"videoHighQuality": "Ci aspettiamo che il video sia di buona qualità.",
"videoLowQuality": "Ci aspettiamo che il video abbia pochi fotogrammi al secondo e sia a bassa risoluzione.",
"videoTearing": "Ci aspettiamo che il video sia pixelato e abbia deformazioni visive."
},
"copyAndShare": "Copia e condividi il collegamento della riunione",
"dialInMeeting": "Chiama e collegati alla riunione",
"dialInPin": "Chiama e inserisci il PIN per entrare nella riunione:",
"dialing": "Chiamata",
"doNotShow": "Non mostrare più questa finestra",
"errorDialOut": "Impossibile fare la chiamata",
"errorDialOutDisconnected": "Impossibile fare la chiamata. Occupato",
"errorDialOutFailed": "Impossibile fare la chiamata. Chiamata fallita",
"errorDialOutStatus": "Errore nel ricevere lo stato della rete",
"errorMissingName": "Inserire il proprio nome, per accedere alla riunione",
"errorStatusCode": "Errore nella chiamata, codice: {{status}}",
"errorValidation": "Numero inesistente",
"iWantToDialIn": "Voglio chiamare il numero",
"joinAudioByPhone": "Collegati usando un telefono, per parlare",
"joinMeeting": "Collegati alla riunione",
"joinWithoutAudio": "Collegati senza poter parlare",
"initiated": "Chiamata avviata",
"linkCopied": "Collegamento copiato negli appunti",
"lookGood": "Sembra che il tuo microfono funzioni correttamente",
"or": "o",
"premeeting": "Attesa riunione",
"showScreen": "Avvia la schermata d'attesa della riunione",
"startWithPhone": "Avvia usando il telefono, per parlare",
"screenSharingError": "Errore di condivisione dello schermo:",
"videoOnlyError": "Errore video:",
"videoTrackError": "Impossibile creare la traccia video.",
"viewAllNumbers": "vedi tutti i numeri"
},
"presenceStatus": {
"busy": "Occupato",
"calling": "Chiamata…",
@@ -481,7 +577,10 @@
"setEmailLabel": "Imposta la mail gravatar",
"title": "Profilo"
},
"raisedHand": "Vorrebbe parlare",
"recording": {
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "La tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <3>{{app}}</3>.",
"authDropboxText": "Carica su Dropbox",
"availableSpace": "Spazio disponibile: {{spaceLeft}} MB (rimangono approssimativamente {{duration}} minuti di registrazione)",
"beta": "BETA",
@@ -492,25 +591,31 @@
"expandedOn": "La registrazione della conferenza è attiva.",
"expandedPending": "La registrazione è in fase di avvio…",
"failedToStart": "Non è stato possibile avviare la registrazione",
"fileSharingdescription": "",
"fileSharingdescription": "Condividi la registrazione con i partecipanti alla riunione",
"live": "DIRETTA",
"loggedIn": "Accesso effettuato come {{userName}}",
"off": "Registrazione interrotta",
"offBy": "{{name}} registrazione fermata",
"on": "Registrazione",
"onBy": "{{name}} registrazione iniziata",
"pending": "In preparazione alla registrazione della conferenza…",
"rec": "REC",
"serviceDescription": "",
"serviceDescription": "La tua registrazione verrà salvata dal servizio di registrazione che hai scelto",
"serviceName": "Servizio di registrazione",
"signIn": "Entra",
"signOut": "Esci",
"unavailable": "Ops! Il {{serviceName}} non è al momento disponibile. Stiamo lavorando per risolvere il problema. Riprova più tardi.",
"unavailableTitle": "Registrazione non disponibile",
"onBy": "{{name}} registrazione iniziata",
"offBy": "{{name}} registrazione fermata"
"unavailableTitle": "Registrazione non disponibile"
},
"sectionList": {
"pullToRefresh": "Trascina per aggiornare"
},
"security": {
"about": "Puoi aggiungere alla riunione una $t(lockRoomPassword). I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"aboutReadOnly": "I moderatori della riunione possono aggiungere $t(lockRoomPassword). I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"insecureRoomNameWarning": "La riunione non è protetta. Dei partecipanti indesiderati potrebbero unirsi alla riunione. Puoi proteggere l'accesso alla riunione col bottone sicurezza.",
"securityOptions": "Impostazioni sicurezza"
},
"settings": {
"calendar": {
"about": "Lintegrazione del calendario con {{appName}} e consigliata per accedere in sicurezza al proprio calendario per poter leggere i prossimi appuntamenti ",
@@ -537,23 +642,26 @@
"microphones": "Microfoni"
},
"settingsView": {
"advanced": "Avanzate",
"alertOk": "OK",
"alertCancel": "Annulla",
"alertTitle": "Attenzione",
"alertURLText": "L'URL del server inserito non è valido",
"buildInfoSection": "Versione",
"conferenceSection": "Conferenza",
"disableCallIntegration": "Disattiva l'integrazione delle chiamate native",
"disableP2P": "Disattiva la modalità punto-punto",
"disableCrashReporting": "Disattiva la diagnostica dei crash",
"disableCrashReportingWarning": "Sei sicuro di voler disattivare la diagnostica dei crash? Quest'impostazione verrà eseguita al prossimo avvio dell'app.",
"displayName": "Nome visualizzato",
"email": "Email",
"header": "Impostazioni",
"profileSection": "Profilo",
"serverURL": "URL del server",
"showAdvanced": "Impostazioni avanzate",
"startWithAudioMuted": "Inizia con l'audio disattivato",
"startWithVideoMuted": "Avvia con il video disattivato",
"version": "Versione",
"showAdvanced": "Mostra impostazioni avanzate",
"disableP2P": "Disabilita modalità uno-a-uno",
"disableCallIntegration": "Disabilita integrazione nativa delle chiamate",
"advanced": "Avanzate"
"version": "Versione"
},
"share": {
"dialInfoText": "\n\n=====\n\nVuoi solo ascoltare la conferenza da un telefono?\n\n{{defaultDialInNumber}}Clicca questo link per vedere i numeri telefonici di questo meeting\n{{dialInfoPageUrl}}",
@@ -565,90 +673,113 @@
"minutes": "{{count}}m",
"name": "Nome",
"seconds": "{{count}}s",
"speakerStats": "Statistiche del relatore",
"speakerTime": "Tempo del relatore"
"speakerStats": "Statistiche",
"speakerTime": "Tempo"
},
"startupoverlay": {
"policyText": " ",
"title": "{{app}} chiede di usare il tuo microfono e la tua videocamera."
"genericTitle": "Per la riunione devono essere usati il tuo microfono e la tua videocamera.",
"title": "{{app}} ha bisogno di usare il tuo microfono e la tua videocamera."
},
"suspendedoverlay": {
"rejoinKeyTitle": "Ricollegati",
"text": "Premi il pulsante <i>Ricollegati</i> per ricollegarti.",
"title": "La video chiamata si è interrotta perchè il computer è stato sospeso."
"title": "La video chiamata si è interrotta perché il computer è stato sospeso."
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "Attiva/disattiva solo audio",
"audioRoute": "Scegli l'uscita audio",
"callQuality": "Gestisci qualità della chiamata",
"callQuality": "Imposta qualità della chiamata",
"cc": "Attiva/disattiva sottotitoli",
"chat": "Attiva/disattiva la chat",
"document": "Attiva/disattiva documento condiviso",
"download": "Scarica le nostre app",
"embedMeeting": "Incorpora riunione altrove",
"feedback": "Lascia un feedback",
"fullScreen": "Attiva/disattiva schermo intero",
"grantModerator": "Autorizza Moderator",
"hangup": "Lascia la conferenza",
"help": "Aiuto",
"invite": "Invita persone",
"kick": "Espelli partecipante",
"lobbyButton": "Attiva/disattiva sala d'attesa",
"localRecording": "Abilita controlli di registrazione locale",
"lockRoom": "Attiva o disattiva password",
"moreActions": "Attiva o disattiva menu avanzato",
"moreActionsMenu": "Menu avanzato",
"moreOptions": "Più opzioni",
"mute": "Attiva/disattiva audio",
"muteEveryone": "Zittisci tutti",
"pip": "Attiva/disattiva immagine nellimmagine",
"privateMessage": "Invia messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Attiva/disattiva alzata di mano",
"recording": "Attiva/disattiva registrazione",
"remoteMute": "Disattiva audio partecipante",
"remoteMute": "Zittisci partecipante",
"security": "Impostazioni sicurezza",
"Settings": "Attiva/disattiva impostazioni",
"sharedvideo": "Attiva/disattiva condivisione YouTube",
"shareRoom": "Invita qualcuno",
"shareYourScreen": "Attiva/disattiva condivisione schermo",
"shortcuts": "Attiva/disattiva scorciatoie",
"show": "",
"show": "Mostra in primo piano",
"speakerStats": "Attiva/disattiva statistiche relatore",
"tileView": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"toggleFilmstrip": "Attiva/disattiva pellicola",
"videomute": "Attiva/disattiva videocamera",
"videoblur": "Attiva/disattiva offuscamento video",
"privateMessage": "Invia messaggio privato",
"muteEveryone": "Zittisci tutti",
"moreOptions": "Mostra più opzioni",
"help": "Aiuto",
"download": "Scarica altre app"
"videoblur": "Attiva/disattiva offuscamento video"
},
"addPeople": "Aggiungi persone alla chiamata",
"audioOnlyOff": "Anche video",
"audioOnlyOn": "Solo audio",
"audioRoute": "Scegli l'uscita audio",
"authenticate": "Autenticazione",
"callQuality": "Gestisci qualità della chiamata",
"callQuality": "Imposta qualità della chiamata",
"chat": "Apri / Chiudi chat",
"closeChat": "Chiudi chat",
"documentClose": "Chiudi documento condiviso",
"documentOpen": "Apri documento condiviso",
"download": "Scarica le nostre app",
"e2ee": "Crittografia punto-punto",
"embedMeeting": "Incorpora riunione altrove",
"enterFullScreen": "Visualizza a schermo intero",
"enterTileView": "Vedi tutti i partecipanti",
"exitFullScreen": "Esci da schermo intero",
"exitTileView": "Vedi una persona sola",
"feedback": "Lascia un feedback",
"hangup": "Esci",
"hangup": "Butta giù",
"help": "Aiuto",
"invite": "Invita persone",
"lobbyButtonDisable": "Disabilita sala d'attesa",
"lobbyButtonEnable": "Abilita sala d'attesa",
"login": "Login",
"logout": "Logout",
"lowerYourHand": "Abbassa la mano",
"moreActions": "Più azioni",
"mute": "Microfono Attiva / Disattiva",
"moreOptions": "Più opzioni",
"mute": "Attiva / Disattiva microfono",
"muteEveryone": "Zittisci tutti",
"noAudioSignalTitle": "Non arrivano suoni dal tuo microfono!",
"noAudioSignalDesc": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a cambiare dispositivo di input.",
"noAudioSignalDescSuggestion": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a scegliere il dispositivo consigliato.",
"noAudioSignalDialInDesc": "Puoi anche chiamare usando:",
"noAudioSignalDialInLinkDesc": "Numberi di telefono",
"noisyAudioInputTitle": "Il tuo microfono sembra rumoroso!",
"noisyAudioInputDesc": "Sembra che il tuo microfono faccia dei rumori, prova a spegnerlo o cambiarlo per favore.",
"openChat": "Apri una chat",
"pip": "Abilita visualizzazione immagine nellimmagine",
"privateMessage": "invia un messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza / Abbassa la mano",
"raiseYourHand": "Alza la mano",
"security": "Impostazioni sicurezza",
"Settings": "Impostazioni",
"sharedvideo": "Condividi un video Youtube",
"shareRoom": "Invita partecipante",
"shortcuts": "Visualizza scorciatoie",
"speakerStats": "Statistiche dell'interlocutore",
"speakerStats": "Statistiche",
"startScreenSharing": "Inizia la condivisione dello schermo",
"startSubtitles": "Avvia sottotitoli",
"stopScreenSharing": "Ferma la condivisione dello schermo",
@@ -697,34 +828,37 @@
},
"videoStatus": {
"audioOnly": "AUD",
"audioOnlyExpanded": "Hai attivato la modalità solo audio. Questa modalità permette di rispamiare banda, ma non vedrai gli altri partecipanti.",
"audioOnlyExpanded": "Hai attivato la modalità solo audio. Questa modalità permette di risparmiare banda, ma non vedrai gli altri partecipanti.",
"callQuality": "Qualità video",
"hd": "HD",
"hdTooltip": "Stai vedendo in alta definizione",
"highDefinition": "Alta definizione",
"labelTooiltipNoVideo": "Nessun video",
"labelTooltipAudioOnly": "Hai attivato la modalità solo audio",
"ld": "LD",
"ldTooltip": "Stai vedendo a bassa definizione",
"lowDefinition": "Bassa definizione",
"onlyAudioAvailable": "È disponibile solo l'audio",
"onlyAudioSupported": "Per questo browser è supportato solo l'audio.",
"sd": "SD",
"sdTooltip": "Stai vedendo a definizione standard",
"standardDefinition": "Definizione standard"
},
"videothumbnail": {
"domute": "Disattiva audio",
"domuteOthers": "Zittisci tutti gli altri",
"flip": "Rifletti",
"kick": "Espelli",
"grantModerator": "Autorizza moderatore",
"kick": "Butta fuori",
"moderator": "Moderatore",
"mute": "Il partecipante è in muto",
"mute": "Il partecipante ha il microfono spento",
"muted": "Audio disattivato",
"remoteControl": "Controllo remoto",
"show": "",
"videomute": "Silenzia il video"
"show": "Mostra in primo piano",
"videomute": "Il partecipante ha la videocamera spenta"
},
"welcomepage": {
"accessibilityLabel": {
"join": "Tap per accedere",
"roomname": "Inserisci Nome Stanza"
"roomname": "Inserisci nome stanza"
},
"appDescription": "Avvia una videochiamata con tutto il team. Invita tutti quelli che conosci. {{app}} è una soluzione per effettuare videoconferenze totalmente crittografata, 100% open source, che puoi usare sempre, ogni giorno, gratuitamente senza bisogno di un account.",
"audioVideoSwitch": {
@@ -733,34 +867,68 @@
},
"calendar": "Calendario",
"connectCalendarButton": "Collega calendario",
"connectCalendarText": "",
"connectCalendarText": "Connetti il tuo calendario per vedere tutte le riunione dentro {{app}}. Poi, aggiungi {{provider}} di riunioni al tuo calendario, per avviarle con un clic.",
"enterRoomTitle": "Avvia una nuova conferenza",
"getHelp": "Trova aiuto",
"go": "VAI",
"goSmall": "VAI",
"headerTitle": "Jitsi Meet",
"headerSubtitle": "Secure and high quality meetings",
"info": "Informazioni chiamata",
"join": "UNISCITI",
"info": "Informazioni",
"jitsiOnMobile": "Jitsi su mobile scarica le nostre app e dai inizio ad una riunione dovunque tu sia",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara una URL</a> in anticipo, per le riunioni di cui sei il moderatore.",
"privacy": "Privacy",
"recentList": "Recente",
"recentListDelete": "Cancella",
"recentListEmpty": "La tua lista è vuota. Chatta con qualcuno del tuo team e lo vedrai apparire nella lista di meeting recenti.",
"reducedUIText": "",
"roomname": "Inserisci Nome Stanza",
"reducedUIText": "Benvenuto in {{app}}!",
"roomNameAllowedChars": "Il nome della riunione non deve contenere questi caratteri: ?, &, :, ', \", %, #.",
"roomname": "Inserisci nome stanza",
"roomnameHint": "Inserisci il nome o l'URL della stanza alla quale vuoi accedere. Puoi anche inventarti un nome, assicurati solo che le persone che vuoi contattare lo sappiano, così che possano inserire lo stesso nome.",
"sendFeedback": "Invia feedback",
"startMeeting": "Inizia riunione",
"terms": "Termini di utilizzo",
"title": "Il sistema di conferenza sicuro, funzionale e completamente gratuito."
"title": "Il sistema di videoconferenza sicuro, funzionale e completamente gratuito."
},
"documentSharing": {
"title": ""
},
"defaultNickname": "",
"chromeExtensionBanner": {
"dontShowAgain": "",
"buttonText": "",
"installExtensionText": ""
},
"raisedHand": "Vorrebbe parlare",
"lonelyMeetingExperience": {
"button": "Invita altri",
"button": "invita altri",
"youAreAlone": "Sei l'unico in riunione"
},
"helpView": {
"header": "Aiuto"
},
"lobby": {
"knockingParticipantList": "Lista dei partecipanti in attesa",
"allow": "Autorizza",
"backToKnockModeButton": "No password, ask to join instead",
"dialogTitle": "Sala d'attesa",
"disableDialogContent": "Sala d'attesa attiva. Questa funzione ti permette di non dare accesso alla riunione a partecipanti indesiderati. Vuoi disattivarla?",
"disableDialogSubmit": "Disattiva",
"emailField": "Inserisci il tuo indirizzo Email",
"enableDialogPasswordField": "Imposta password (opzionale)",
"enableDialogSubmit": "Attiva",
"enableDialogText": "La sala d'attesa ti permette di proteggere la tua riunione concedendo l'ingresso solo alle persone autorizzate da un moderatore.",
"enterPasswordButton": "Inserisci password riunione",
"enterPasswordTitle": "Inserisci la password per entrare nella riunione",
"invalidPassword": "Password errata",
"joiningMessage": "Entrerai nella riunione, non appena qualcuno approva la tua richiesta",
"joinWithPasswordMessage": "Ho inviato la password per entrare, attendi...",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
"joinTitle": "Entra in riunione",
"joiningTitle": "Richiesta inviata...",
"joiningWithPasswordTitle": "Entrando con la password...",
"knockButton": "Chiedi d'entrare",
"knockTitle": "Qualcuno vuole entrare in riunione",
"nameField": "Scrivi il tuo nome",
"notificationLobbyAccessDenied": "{{targetParticipantName}} è stato respinto da {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} è stato autorizzato ad entrare da {{originParticipantName}}",
"notificationLobbyDisabled": "La sala d'attesa è stata disattivata da {{originParticipantName}}",
"notificationLobbyEnabled": "La sala d'attesa è stata attivata da {{originParticipantName}}",
"notificationTitle": "Sala d'attesa",
"passwordField": "Inserisci la password della riunione",
"passwordJoinButton": "Entra",
"reject": "Respingi",
"toggleLabel": "Attiva sala d'attesa"
}
}

View File

@@ -104,7 +104,7 @@
},
"deepLinking": {
"downloadApp": "Last ned programmet",
"openApp": "Fortsett til programmet",
"openApp": "Fortsett til programmet"
},
"defaultLink": "f.eks.",
"deviceError": {

View File

@@ -300,6 +300,7 @@
"tokenAuthFailedTitle": "Authentication failed",
"transcribing": "Transcribing",
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
"user": "user",
"userPassword": "user password",
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
@@ -393,8 +394,7 @@
"toggleFilmstrip": "Show or hide video thumbnails",
"toggleScreensharing": "Switch between camera and screen sharing",
"toggleShortcuts": "Show or hide keyboard shortcuts",
"videoMute": "Start or stop your camera",
"videoQuality": "Manage call quality"
"videoMute": "Start or stop your camera"
},
"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>.",

View File

@@ -1,14 +0,0 @@
{
"google-auth": {
"matchPatterns": {
"url": "accounts.google.com"
},
"target": "electron"
},
"dropbox-auth": {
"matchPatterns": {
"url": "dropbox.com/oauth2/authorize"
},
"target": "electron"
}
}

View File

@@ -7,7 +7,6 @@ import {
Transport
} from '../../transport';
import electronPopupsConfig from './electronPopupsConfig.json';
import {
getAvailableDevices,
getCurrentDevices,
@@ -1070,16 +1069,4 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
stopRecording(mode) {
this.executeCommand('startRecording', mode);
}
/**
* Returns the configuration for electron for the windows that are open
* from Jitsi Meet.
*
* @returns {Promise<Object>}
*
* NOTE: For internal use only.
*/
_getElectronPopupsConfig() {
return Promise.resolve(electronPopupsConfig);
}
}

View File

@@ -17,6 +17,7 @@ function getPasswordInputHtml() {
return `
<input name="username" type="text"
class="input-control"
data-i18n="[placeholder]dialog.user"
placeholder=${placeholder} autofocus>
<input name="password" type="password"
class="input-control"

View File

@@ -218,21 +218,11 @@ export default class LargeVideoManager {
// change the avatar url on large
this.updateAvatar();
// If the user's connection is disrupted then the avatar will be
// displayed in case we have no video image cached. That is if
// there was a user switch (image is lost on stream detach) or if
// the video was not rendered, before the connection has failed.
const wasUsersImageCached
= !isUserSwitch && container.wasVideoRendered;
const isVideoMuted = !stream || stream.isMuted();
const participant = getParticipantById(APP.store.getState(), id);
const connectionStatus = participant?.connectionStatus;
const isVideoRenderable
= !isVideoMuted
&& (APP.conference.isLocalId(id)
|| connectionStatus
=== JitsiParticipantConnectionStatus.ACTIVE
|| wasUsersImageCached);
const isVideoRenderable = !isVideoMuted
&& (APP.conference.isLocalId(id) || connectionStatus === JitsiParticipantConnectionStatus.ACTIVE);
const showAvatar
= isVideoContainer

View File

@@ -13,6 +13,8 @@ import {
JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet';
import { getParticipantById } from '../../../react/features/base/participants';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
import { PresenceLabel } from '../../../react/features/presence-status';
import { stopController, requestRemoteControl } from '../../../react/features/remote-control';
import { RemoteVideoMenuTriggerButton } from '../../../react/features/remote-video-menu';
@@ -23,6 +25,15 @@ import SmallVideo from './SmallVideo';
const logger = Logger.getLogger(__filename);
/**
* List of container events that we are going to process, will be added as listener to the
* container for every event in the list. The latest event will be stored in redux.
*/
const containerEvents = [
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
];
/**
*
* @param {*} spanId
@@ -206,23 +217,20 @@ export default class RemoteVideo extends SmallVideo {
}
/**
* The remote video is considered "playable" once the can play event has been received. It will be allowed to
* display video also in {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video has received the canplay
* event and was not muted while not in ACTIVE state. This basically means that there is stalled video image cached
* that could be displayed. It's used to show "grey video image" in user's thumbnail when there are connectivity
* issues.
* The remote video is considered "playable" once the can play event has been received.
*
* @inheritdoc
* @override
*/
isVideoPlayable() {
const participant = getParticipantById(APP.store.getState(), this.id);
const { connectionStatus, mutedWhileDisconnected } = participant || {};
const { connectionStatus } = participant || {};
return super.isVideoPlayable()
&& this._canPlayEventReceived
&& (connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
|| (connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED && !mutedWhileDisconnected));
return (
super.isVideoPlayable()
&& this._canPlayEventReceived
&& connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
);
}
/**
@@ -248,6 +256,8 @@ export default class RemoteVideo extends SmallVideo {
* @param {*} stream
*/
waitForPlayback(streamElement, stream) {
$(streamElement).hide();
const webRtcStream = stream.getOriginalStream();
const isVideo = stream.isVideoTrack();
@@ -257,7 +267,12 @@ export default class RemoteVideo extends SmallVideo {
const listener = () => {
this._canPlayEventReceived = true;
this.VideoLayout.remoteVideoActive(streamElement, this.id);
logger.info(`${this.id} video is now active`, streamElement);
if (streamElement) {
$(streamElement).show();
}
streamElement.removeEventListener('canplay', listener);
// Refresh to show the video
@@ -297,8 +312,6 @@ export default class RemoteVideo extends SmallVideo {
// Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement);
$(streamElement).hide();
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
@@ -309,6 +322,13 @@ export default class RemoteVideo extends SmallVideo {
// attached we need to update the menu in order to show the volume
// slider.
this.updateRemoteVideoMenu();
} else if (isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
containerEvents.forEach(event => {
streamElement.addEventListener(event, cb.bind(this, event));
});
}
}

View File

@@ -433,7 +433,7 @@ export default class SmallVideo {
*/
computeDisplayModeInput() {
let isScreenSharing = false;
let connectionStatus, mutedWhileDisconnected;
let connectionStatus;
const state = APP.store.getState();
const participant = getParticipantById(state, this.id);
@@ -443,7 +443,6 @@ export default class SmallVideo {
isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop';
connectionStatus = participant.connectionStatus;
mutedWhileDisconnected = participant.mutedWhileDisconnected;
}
return {
@@ -454,7 +453,6 @@ export default class SmallVideo {
isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus,
mutedWhileDisconnected,
canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(this.videoStream),
isScreenSharing,

View File

@@ -5,7 +5,8 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
@@ -19,6 +20,15 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
const FADE_DURATION_MS = 300;
/**
* List of container events that we are going to process, will be added as listener to the
* container for every event in the list. The latest event will be stored in redux.
*/
const containerEvents = [
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
];
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
@@ -223,14 +233,6 @@ export class VideoContainer extends LargeContainer {
this.$remotePresenceMessage = $('#remotePresenceMessage');
/**
* Indicates whether or not the video stream attached to the video
* element has started(which means that there is any image rendered
* even if the video is stalled).
* @type {boolean}
*/
this.wasVideoRendered = false;
this.$wrapper = $('#largeVideoWrapper');
/**
@@ -239,17 +241,12 @@ export class VideoContainer extends LargeContainer {
* video anyway.
*/
this.$wrapperParent = this.$wrapper.parent();
this.avatarHeight = $('#dominantSpeakerAvatarContainer').height();
const onPlayingCallback = function(event) {
this.$video[0].onplaying = function(event) {
if (typeof resizeContainer === 'function') {
resizeContainer(event);
}
this.wasVideoRendered = true;
}.bind(this);
this.$video[0].onplaying = onPlayingCallback;
};
/**
* A Set of functions to invoke when the video element resizes.
@@ -259,6 +256,14 @@ export class VideoContainer extends LargeContainer {
this._resizeListeners = new Set();
this.$video[0].onresize = this._onResize.bind(this);
if (isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastLargeVideoMediaEvent(name));
containerEvents.forEach(event => {
this.$video[0].addEventListener(event, cb.bind(this, event));
});
}
}
/**
@@ -473,10 +478,6 @@ export class VideoContainer extends LargeContainer {
return;
}
// The stream has changed, so the image will be lost on detach
this.wasVideoRendered = false;
// detach old stream
if (this.stream) {
this.stream.detach(this.$video[0]);

View File

@@ -1,4 +1,4 @@
/* global APP, $ */
/* global APP */
import Logger from 'jitsi-meet-logger';
@@ -313,15 +313,6 @@ const VideoLayout = {
remoteVideo.updateView();
},
// FIXME: what does this do???
remoteVideoActive(videoElement, resourceJid) {
logger.info(`${resourceJid} video is now active`, videoElement);
if (videoElement) {
$(videoElement).show();
}
this._updateLargeVideoIfDisplayed(resourceJid, true);
},
/**
* On video muted event.
*/

52
package-lock.json generated
View File

@@ -3302,9 +3302,9 @@
}
},
"@jitsi/js-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.3.tgz",
"integrity": "sha512-m6mZz7R716mHP21lTKQffyM0nNFu3Fe/EHCaOVLFY/vdPsaUl9DhypJqtPIYzRUfPnmnugdaxcxrUeSZQXQzVA==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -6989,6 +6989,11 @@
"gud": "^1.0.0"
}
},
"cross-os": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/cross-os/-/cross-os-1.3.0.tgz",
"integrity": "sha512-9kViqCcAwlPLTeSDPlyC2FdMQ5UVPtGZUnGV8vYDcBA3olJ/hDR7H6IfrNJft2DlKONleHf8CMhD+7Uv2tBnEw=="
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -10588,8 +10593,8 @@
"integrity": "sha512-+f/4OLeqY8RAmXnonI1ffeY1DR8kMNJPhv5WMFehchf7U71cjMQVKkOz1n6asz6kfVoAqKNWJz1A/18i18AcXA=="
},
"jitsi-meet-logger": {
"version": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"from": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b"
"version": "github:jitsi/jitsi-meet-logger#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"from": "github:jitsi/jitsi-meet-logger#v1.0.0"
},
"jquery": {
"version": "3.5.1",
@@ -10771,8 +10776,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"from": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"version": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"from": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -10780,7 +10785,7 @@
"async": "0.9.0",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"lodash.isequal": "4.5.0",
@@ -10792,6 +10797,24 @@
"webrtc-adapter": "7.5.0"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
},
"jitsi-meet-logger": {
"version": "github:jitsi/jitsi-meet-logger#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"from": "github:jitsi/jitsi-meet-logger#v1.0.0"
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -14265,11 +14288,12 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
"version": "1.84.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
"integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
"version": "1.87.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.1.tgz",
"integrity": "sha512-XIztid40ohLUoOIDqpavskyAPzopWIjNOoC/y3AtTymt+o+W/rIHZ9Qw8JZCaIjWh2AIrcO2wtb/f1aMWSz2Zw==",
"requires": {
"base64-js": "^1.1.2",
"cross-os": "^1.3.0",
"event-target-shim": "^1.0.5",
"prop-types": "^15.5.10",
"uuid": "^3.3.2"
@@ -14283,9 +14307,9 @@
}
},
"react-native-webview": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-10.9.0.tgz",
"integrity": "sha512-zYZfmdJca/xRbwvvOfPhzL59SQC4L0W9rPWVF4zMi7BMDdCVHXVp0wKZ9KzmqxZNwadZNTxl5s0pvd6p3S34Fg==",
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.0.2.tgz",
"integrity": "sha512-GDyIBRbCZ2wbMUGCxA7LufSEbSoWKOzkFB8YljmAffA15tzN6ccvGEquB/hkk5KhvoYy300kwJyEmyBeG6d/AA==",
"requires": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"

View File

@@ -32,7 +32,7 @@
"@atlaskit/theme": "7.0.2",
"@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13",
"@jitsi/js-utils": "1.0.2",
"@jitsi/js-utils": "1.0.3",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4",
"@react-native-community/google-signin": "3.0.1",
@@ -50,13 +50,13 @@
"i18next-browser-languagedetector": "3.0.1",
"i18next-xhr-backend": "3.0.0",
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"jquery": "3.5.1",
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
@@ -84,8 +84,8 @@
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "1.84.1",
"react-native-webview": "10.9.0",
"react-native-webrtc": "1.87.1",
"react-native-webview": "11.0.2",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",
"react-textarea-autosize": "7.1.0",

View File

@@ -11,7 +11,7 @@ import JitsiMeetJS, {
browser,
isAnalyticsEnabled
} from '../base/lib-jitsi-meet';
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
import { getJitsiMeetGlobalNS, loadScript, parseURIString } from '../base/util';
import { AmplitudeHandler, MatomoHandler } from './handlers';
import logger from './logger';
@@ -166,6 +166,8 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
} = config;
const { group, server } = state['features/base/jwt'];
const roomName = state['features/base/conference'].room;
const { locationURL = {} } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const permanentProperties = {};
if (server) {
@@ -187,6 +189,9 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
// Report if we are loaded in iframe
permanentProperties.inIframe = _inIframe();
// Report the tenant from the URL.
permanentProperties.tenant = tenant || '/';
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
if (deploymentInfo) {

View File

@@ -196,17 +196,6 @@ export const SET_PENDING_SUBJECT_CHANGE = 'SET_PENDING_SUBJECT_CHANGE';
*/
export const SET_ROOM = 'SET_ROOM';
/**
* The type of (redux) action, which indicates if a SIP gateway is enabled on
* the server.
*
* {
* type: SET_SIP_GATEWAY_ENABLED
* isSIPGatewayEnabled: boolean
* }
*/
export const SET_SIP_GATEWAY_ENABLED = 'SET_SIP_GATEWAY_ENABLED';
/**
* The type of (redux) action which updates the current known status of the
* moderator features for starting participants as audio or video muted.

View File

@@ -20,7 +20,6 @@ import {
SET_PASSWORD,
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_SIP_GATEWAY_ENABLED,
SET_START_MUTED_POLICY
} from './actionTypes';
import { isRoomValid } from './functions';
@@ -90,9 +89,6 @@ ReducerRegistry.register(
case SET_ROOM:
return _setRoom(state, action);
case SET_SIP_GATEWAY_ENABLED:
return _setSIPGatewayEnabled(state, action);
case SET_START_MUTED_POLICY:
return {
...state,
@@ -416,16 +412,3 @@ function _setRoom(state, action) {
});
}
/**
* Reduces a specific Redux action SET_SIP_GATEWAY_ENABLED of the feature
* base/conference.
*
* @param {Object} state - The Redux state of the feature base/conference.
* @param {Action} action - The Redux action SET_SIP_GATEWAY_ENABLED to reduce.
* @private
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setSIPGatewayEnabled(state, action) {
return set(state, 'isSIPGatewayEnabled', action.isSIPGatewayEnabled);
}

View File

@@ -90,7 +90,6 @@ export default [
'disableRemoteMute',
'disableRtx',
'disableSimulcast',
'disableSuspendVideo',
'disableThirdPartyRequests',
'displayJids',
'doNotStoreRoom',
@@ -115,8 +114,10 @@ export default [
'fileRecordingsEnabled',
'firefox_fake_device',
'forceJVB121Ratio',
'forceTurnRelay',
'gatherStats',
'googleApiApplicationClientID',
'hideConferenceTimer',
'hiddenDomain',
'hideLobbyButton',
'hosts',
@@ -127,7 +128,6 @@ export default [
'liveStreamingEnabled',
'localRecording',
'maxFullResolutionParticipants',
'minParticipants',
'openBridgeChannel',
'opusMaxAverageBitrate',
'p2p',
@@ -140,7 +140,6 @@ export default [
'resolution',
'startAudioMuted',
'startAudioOnly',
'startBitrate',
'startScreenSharing',
'startSilent',
'startVideoMuted',

View File

@@ -59,7 +59,7 @@ export function getInviteURL(stateOrGetState: Function | Object): string {
if (inviteDomain) {
const meetingId
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname.replace('/', '');
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname.replace(/\//, '');
return `${inviteDomain}/${meetingId}`;
}

View File

@@ -68,14 +68,13 @@ function _updateLastN({ getState }) {
return;
}
const defaultLastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
let lastN = defaultLastN;
let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
// Apply last N limit based on the # of participants
// Apply last N limit based on the # of participants and channelLastN settings.
const limitedLastN = limitLastN(participantCount, lastNLimits);
if (limitedLastN !== undefined) {
lastN = limitedLastN;
lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
}
if (typeof appState !== 'undefined' && appState !== 'active') {

View File

@@ -21,7 +21,6 @@ import {
getLocalParticipant,
getNormalizedDisplayName,
getParticipantDisplayName,
figureOutMutedWhileDisconnectedStatus,
getParticipantById
} from './functions';
import logger from './logger';
@@ -220,15 +219,12 @@ export function muteRemoteParticipant(id) {
* }}
*/
export function participantConnectionStatusChanged(id, connectionStatus) {
return (dispatch, getState) => {
return {
type: PARTICIPANT_UPDATED,
participant: {
connectionStatus,
id,
mutedWhileDisconnected: figureOutMutedWhileDisconnectedStatus(getState(), id, connectionStatus)
}
};
return {
type: PARTICIPANT_UPDATED,
participant: {
connectionStatus,
id
}
};
}

View File

@@ -1,11 +1,12 @@
// @flow
import { getGravatarURL } from '@jitsi/js-utils/avatar';
import type { Store } from 'redux';
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
import { toState } from '../redux';
import { getTrackByMediaTypeAndParticipant, isRemoteTrackMuted } from '../tracks';
import { getTrackByMediaTypeAndParticipant } from '../tracks';
import { createDeferred } from '../util';
import {
@@ -23,30 +24,39 @@ declare var interfaceConfig: Object;
*/
const AVATAR_QUEUE = [];
const AVATAR_CHECKED_URLS = new Map();
/* eslint-disable arrow-body-style */
/* eslint-disable arrow-body-style, no-unused-vars */
const AVATAR_CHECKER_FUNCTIONS = [
participant => {
(participant, _) => {
return participant && participant.isJigasi ? JIGASI_PARTICIPANT_ICON : null;
},
participant => {
(participant, _) => {
return participant && participant.avatarURL ? participant.avatarURL : null;
},
participant => {
return participant && participant.email ? getGravatarURL(participant.email) : null;
(participant, store) => {
if (participant && participant.email) {
// TODO: remove once libravatar has deployed their new scaled up infra. -saghul
const gravatarBaseURL
= store.getState()['features/base/config'].gravatarBaseURL ?? 'https://www.gravatar.com/avatar/';
return getGravatarURL(participant.email, gravatarBaseURL);
}
return null;
}
];
/* eslint-enable arrow-body-style */
/* eslint-enable arrow-body-style, no-unused-vars */
/**
* Resolves the first loadable avatar URL for a participant.
*
* @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {Promise}
*/
export function getFirstLoadableAvatarUrl(participant: Object) {
export function getFirstLoadableAvatarUrl(participant: Object, store: Store<any, any>) {
const deferred = createDeferred();
const fullPromise = deferred.promise
.then(() => _getFirstLoadableAvatarUrl(participant))
.then(() => _getFirstLoadableAvatarUrl(participant, store))
.then(src => {
if (AVATAR_QUEUE.length) {
@@ -359,54 +369,16 @@ export function shouldRenderParticipantVideo(stateful: Object | Function, id: st
return participantIsInLargeVideoWithScreen;
}
/**
* Figures out the value of mutedWhileDisconnected status by taking into
* account remote participant's network connectivity and video muted status.
* The flag is set to <tt>true</tt> if remote participant's video gets muted
* during his media connection disruption. This is to prevent black video
* being render on the thumbnail, because even though once the video has
* been played the image usually remains on the video element it seems that
* after longer period of the video element being hidden this image can be
* lost.
*
* @param {Object|Function} stateful - Object or function that can be resolved
* to the Redux state.
* @param {string} participantID - The ID of the participant.
* @param {string} [connectionStatus] - A connection status to be used.
* @returns {boolean} - The mutedWhileDisconnected value.
*/
export function figureOutMutedWhileDisconnectedStatus(
stateful: Function | Object, participantID: string, connectionStatus: ?string) {
const state = toState(stateful);
const participant = getParticipantById(state, participantID);
if (!participant || participant.local) {
return undefined;
}
const isActive = (connectionStatus || participant.connectionStatus) === JitsiParticipantConnectionStatus.ACTIVE;
const isVideoMuted = isRemoteTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantID);
let mutedWhileDisconnected = participant.mutedWhileDisconnected || false;
if (!isActive && isVideoMuted) {
mutedWhileDisconnected = true;
} else if (isActive && !isVideoMuted) {
mutedWhileDisconnected = false;
}
return mutedWhileDisconnected;
}
/**
* Resolves the first loadable avatar URL for a participant.
*
* @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {?string}
*/
async function _getFirstLoadableAvatarUrl(participant) {
async function _getFirstLoadableAvatarUrl(participant, store) {
for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
const url = AVATAR_CHECKER_FUNCTIONS[i](participant);
const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
if (url) {
if (AVATAR_CHECKED_URLS.has(url)) {

View File

@@ -12,7 +12,6 @@ import {
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';
import { getTrackByJitsiTrack, TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../tracks';
import {
DOMINANT_SPEAKER_CHANGED,
@@ -42,8 +41,7 @@ import {
getLocalParticipant,
getParticipantById,
getParticipantCount,
getParticipantDisplayName,
figureOutMutedWhileDisconnectedStatus
getParticipantDisplayName
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
@@ -73,16 +71,24 @@ MiddlewareRegistry.register(store => next => action => {
break;
case DOMINANT_SPEAKER_CHANGED: {
// Ensure the raised hand state is cleared for the dominant speaker.
// Ensure the raised hand state is cleared for the dominant speaker
// and only if it was set when this is the local participant
const { conference, id } = action.participant;
const participant = getLocalParticipant(store.getState());
const isLocal = participant && participant.id === id;
if (isLocal && participant.raisedHand === undefined) {
// if local was undefined, let's leave it like that
// avoids sending unnecessary presence updates
break;
}
participant
&& store.dispatch(participantUpdated({
conference,
id,
local: participant.id === id,
local: isLocal,
raisedHand: false
}));
@@ -137,10 +143,6 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_UPDATED:
return _participantJoinedOrUpdated(store, next, action);
case TRACK_ADDED:
case TRACK_REMOVED:
case TRACK_UPDATED:
return _trackChanged(store, next, action);
}
return next(action);
@@ -372,7 +374,8 @@ function _maybePlaySounds({ getState, dispatch }, action) {
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store;
const { participant: { avatarURL, e2eeEnabled, email, id, local, name, raisedHand } } = action;
// Send an external update of the local participant's raised hand state
@@ -381,10 +384,10 @@ function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
if (local) {
const { conference } = getState()['features/base/conference'];
conference
&& conference.setLocalParticipantProperty(
'raisedHand',
raisedHand);
// Send raisedHand signalling only if there is a change
if (conference && raisedHand !== getLocalParticipant(getState()).raisedHand) {
conference.setLocalParticipantProperty('raisedHand', raisedHand);
}
}
}
@@ -408,7 +411,7 @@ function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
const participantId = !id && local ? getLocalParticipant(getState()).id : id;
const updatedParticipant = getParticipantById(getState(), participantId);
getFirstLoadableAvatarUrl(updatedParticipant)
getFirstLoadableAvatarUrl(updatedParticipant, store)
.then(url => {
dispatch(setLoadableAvatarUrl(participantId, url));
});
@@ -466,55 +469,6 @@ function _registerSounds({ dispatch }) {
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}
/**
* Notifies the feature base/participants that the action there has been a change in the tracks of the participants.
*
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the specified {@code action} in the
* specified {@code store}.
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} which is being
* dispatched in the specified {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _trackChanged({ dispatch, getState }, next, action) {
const { jitsiTrack } = action.track;
let track;
if (action.type === TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
const result = next(action);
if (action.type !== TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
if (typeof track === 'undefined' || track.local) {
return result;
}
const { participantId } = track;
const state = getState();
const participant = getParticipantById(state, participantId);
if (!participant) {
return result;
}
const mutedWhileDisconnected = figureOutMutedWhileDisconnectedStatus(state, participantId);
if (participant.mutedWhileDisconnected !== mutedWhileDisconnected) {
dispatch(participantUpdated({
id: participantId,
mutedWhileDisconnected
}));
}
return result;
}
/**
* Unregisters sounds related with the participants feature.
*

View File

@@ -221,7 +221,6 @@ function _participantJoined({ participant }) {
isJigasi,
loadableAvatarUrl,
local: local || false,
mutedWhileDisconnected: local ? undefined : false,
name,
pinned: pinned || false,
presence,

View File

@@ -1,5 +1,8 @@
// @flow
import { MEDIA_TYPE } from '../media';
import { getTrackByMediaTypeAndParticipant } from '../tracks';
/**
* Indicates whether the test mode is enabled. When it's enabled
* {@link TestHint} and other components from the testing package will be
@@ -13,3 +16,43 @@ export function isTestModeEnabled(state: Object): boolean {
return Boolean(testingConfig && testingConfig.testMode);
}
/**
* Returns the video type of the remote participant's video.
*
* @param {Store} store - The redux store.
* @param {string} id - The participant ID for the remote video.
* @returns {MEDIA_TYPE}
*/
export function getRemoteVideoType({ getState }: Object, id: String): boolean {
return getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id)?.videoType;
}
/**
* Returns whether the last media event received for large video indicates that the video is playing, if not muted.
*
* @param {Store} store - The redux store.
* @returns {boolean}
*/
export function isLargeVideoReceived({ getState }: Object): boolean {
const largeVideoParticipantId = getState()['features/large-video'].participantId;
const videoTrack = getTrackByMediaTypeAndParticipant(
getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, largeVideoParticipantId);
const lastMediaEvent = getState()['features/large-video'].lastMediaEvent;
return videoTrack && !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
}
/**
* Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
*
* @param {Store} store - The redux store.
* @param {string} id - The participant ID for the remote video.
* @returns {boolean}
*/
export function isRemoteVideoReceived({ getState }: Object, id: String): boolean {
const videoTrack = getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
const lastMediaEvent = videoTrack.lastMediaEvent;
return !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
}

View File

@@ -1,10 +1,18 @@
// @flow
import { CONFERENCE_WILL_JOIN } from '../conference';
import { SET_CONFIG } from '../config';
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { getJitsiMeetGlobalNS } from '../util';
import { setConnectionState } from './actions';
import {
getRemoteVideoType,
isLargeVideoReceived,
isRemoteVideoReceived,
isTestModeEnabled
} from './functions';
import logger from './logger';
/**
@@ -19,6 +27,13 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_WILL_JOIN:
_bindConferenceConnectionListener(action.conference, store);
break;
case SET_CONFIG: {
const result = next(action);
_bindTortureHelpers(store);
return result;
}
}
return next(action);
@@ -52,6 +67,29 @@ function _bindConferenceConnectionListener(conference, { dispatch }) {
null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
}
/**
* Binds all the helper functions needed by torture.
*
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _bindTortureHelpers(store) {
const { getState } = store;
// We bind helpers only if testing mode is enabled
if (!isTestModeEnabled(getState())) {
return;
}
// All torture helper methods go in here
getJitsiMeetGlobalNS().testing = {
getRemoteVideoType: getRemoteVideoType.bind(null, store),
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
};
}
/**
* The handler function for conference connection events which wil store the
* latest even name in the Redux store of feature testing.

View File

@@ -104,3 +104,14 @@ export const TRACK_UPDATED = 'TRACK_UPDATED';
* }
*/
export const TRACK_WILL_CREATE = 'TRACK_WILL_CREATE';
/**
* Action to update the redux store with the current media event name of the video track.
*
* @returns {{
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
* track: Track,
* name: string
* }}
*/
export const TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT = 'TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT';

View File

@@ -23,7 +23,8 @@ import {
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_UPDATED,
TRACK_WILL_CREATE
TRACK_WILL_CREATE,
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT
} from './actionTypes';
import {
createLocalTracksF,
@@ -704,3 +705,22 @@ export function setNoSrcDataNotificationUid(uid) {
uid
};
}
/**
* Updates the last media event received for a video track.
*
* @param {JitsiRemoteTrack} track - JitsiTrack instance.
* @param {string} name - The current media event name for the video.
* @returns {{
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
* track: Track,
* name: string
* }}
*/
export function updateLastTrackVideoMediaEvent(track, name) {
return {
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
track,
name
};
}

View File

@@ -8,6 +8,7 @@ import {
TRACK_CREATE_ERROR,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
TRACK_UPDATED,
TRACK_WILL_CREATE
} from './actionTypes';
@@ -40,6 +41,7 @@ import {
* @param {Track|undefined} state - Track to be modified.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @param {string} action.name - Name of last media event.
* @param {string} action.newValue - New participant ID value (in this
* particular case).
* @param {string} action.oldValue - Old participant ID value (in this
@@ -77,6 +79,20 @@ function track(state, action) {
}
break;
}
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT: {
const t = action.track;
if (state.jitsiTrack === t) {
if (state.lastMediaEvent !== action.name) {
return {
...state,
lastMediaEvent: action.name
};
}
}
break;
}
case TRACK_NO_DATA_FROM_SOURCE: {
const t = action.track;
@@ -104,6 +120,7 @@ ReducerRegistry.register('features/base/tracks', (state = [], action) => {
switch (action.type) {
case PARTICIPANT_ID_CHANGED:
case TRACK_NO_DATA_FROM_SOURCE:
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
case TRACK_UPDATED:
return state.map(t => track(t, action));

View File

@@ -363,6 +363,11 @@ export function parseURIString(uri: ?string) {
}
obj.room = room;
if (contextRootEndIndex > 1) {
// The part of the pathname from the beginning to the room name is the tenant.
obj.tenant = pathname.substring(1, contextRootEndIndex);
}
return obj;
}
@@ -514,7 +519,7 @@ export function urlObjectToString(o: Object): ?string {
// pathname
// Web's ExternalAPI roomName
const room = _fixRoom(o.roomName || o.room);
const room = o.roomName || o.room;
if (room
&& (url.pathname.endsWith('/')

View File

@@ -96,7 +96,8 @@ class NavigationBar extends Component<Props> {
*/
function _mapStateToProps(state) {
return {
_conferenceTimerEnabled: getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true),
_conferenceTimerEnabled:
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !state['features/base/config'].hideConferenceTimer,
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_visible: isToolboxVisible(state)

View File

@@ -16,7 +16,12 @@ import ParticipantsCount from './ParticipantsCount';
type Props = {
/**
* Whether then participant count should be shown or not.
* Whether the conference timer should be shown or not.
*/
_hideConferenceTimer: Boolean,
/**
* Whether the participant count should be shown or not.
*/
_showParticipantCount: boolean,
@@ -46,13 +51,13 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _showParticipantCount, _subject, _visible } = this.props;
const { _hideConferenceTimer, _showParticipantCount, _subject, _visible } = this.props;
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
{ _showParticipantCount && <ParticipantsCount /> }
<ConferenceTimer />
{ !_hideConferenceTimer && <ConferenceTimer /> }
</div>
);
}
@@ -65,6 +70,8 @@ class Subject extends Component<Props> {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _hideConferenceTimer: boolean,
* _showParticipantCount: boolean,
* _subject: string,
* _visible: boolean
* }}
@@ -73,6 +80,7 @@ function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
return {
_hideConferenceTimer: Boolean(state['features/base/config'].hideConferenceTimer),
_showParticipantCount: participantCount > 2,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state) && participantCount > 1

View File

@@ -84,6 +84,12 @@ type Props = AbstractProps & {
*/
dispatch: Dispatch<any>,
/**
* Whether or not should display the "Save Logs" link in the local video
* stats table.
*/
enableSaveLogs: boolean,
/**
* Whether or not clicking the indicator should display a popover for more
* details.
@@ -386,6 +392,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
codec = { codec }
connectionSummary = { this._getConnectionStatusTip() }
e2eRtt = { e2eRtt }
enableSaveLogs = { this.props.enableSaveLogs }
framerate = { framerate }
isLocalVideo = { this.props.isLocalVideo }
maxEnabledResolution = { maxEnabledResolution }
@@ -440,7 +447,8 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
const participant
= typeof participantId === 'undefined' ? getLocalParticipant(state) : getParticipantById(state, participantId);
const props = {
_connectionStatus: participant?.connectionStatus
_connectionStatus: participant?.connectionStatus,
enableSaveLogs: state['features/base/config'].enableSaveLogs
};
if (conference) {

View File

@@ -54,6 +54,11 @@ type Props = {
*/
e2eRtt: number,
/**
* Whether or not should display the "Save Logs" link.
*/
enableSaveLogs: boolean,
/**
* The endpoint id of this client.
*/
@@ -153,13 +158,13 @@ class ConnectionStatsTable extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { isLocalVideo } = this.props;
const { isLocalVideo, enableSaveLogs } = this.props;
return (
<div className = 'connection-info'>
{ this._renderStatistics() }
<div className = 'connection-actions'>
{ isLocalVideo ? this._renderSaveLogs() : null}
{ isLocalVideo && enableSaveLogs ? this._renderSaveLogs() : null}
{ this._renderShowMoreLink() }
</div>
{ this.props.shouldShowMore ? this._renderAdditionalStats() : null }

View File

@@ -17,23 +17,32 @@ const TILE_VIEW_SIDE_MARGINS = 10 * 2;
* @param {Object} windowSize - The size of the window.
* @param {boolean} isChatOpen - Whether the chat panel is displayed, in
* order to properly compute the tile view size.
* @param {boolean} isToolboxVisible - Whether the toolbox is visible, in order
* to adjust the available size.
* @returns {{
* type: SET_TILE_VIEW_DIMENSIONS,
* dimensions: Object
* }}
*/
export function setTileViewDimensions(dimensions: Object, windowSize: Object, isChatOpen: boolean) {
export function setTileViewDimensions(
dimensions: Object, windowSize: Object, isChatOpen: boolean, isToolboxVisible: boolean) {
const { clientWidth, clientHeight } = windowSize;
let heightToUse = clientHeight;
let widthToUse = clientWidth;
if (isChatOpen) {
widthToUse -= CHAT_SIZE;
}
if (isToolboxVisible) {
// The distance from the top and bottom of the screen, to avoid overlapping UI elements.
heightToUse -= 150;
}
const thumbnailSize = calculateThumbnailSizeForTileView({
...dimensions,
clientWidth: widthToUse,
clientHeight
clientHeight: heightToUse
});
const filmstripWidth = dimensions.columns * (TILE_VIEW_SIDE_MARGINS + thumbnailSize.width);

View File

@@ -94,17 +94,13 @@ export function calculateThumbnailSizeForTileView({
clientWidth,
clientHeight
}: Object) {
// The distance from the top and bottom of the screen, as set by CSS, to
// avoid overlapping UI elements.
const topBottomPadding = 200;
// Minimum space to keep between the sides of the tiles and the sides
// of the window.
const sideMargins = 30 * 2;
const verticalMargins = visibleRows * 10;
const viewWidth = clientWidth - sideMargins;
const viewHeight = clientHeight - topBottomPadding - verticalMargins;
const viewHeight = clientHeight - verticalMargins;
const initialWidth = viewWidth / columns;
const aspectRatioHeight = initialWidth / TILE_ASPECT_RATIO;
const height = Math.floor(Math.min(aspectRatioHeight, viewHeight / visibleRows));

View File

@@ -30,6 +30,7 @@ MiddlewareRegistry.register(store => next => action => {
const { gridDimensions } = state['features/filmstrip'].tileViewDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -38,7 +39,8 @@ MiddlewareRegistry.register(store => next => action => {
clientHeight,
clientWidth
},
isOpen
isOpen,
visible
)
);
break;

View File

@@ -18,10 +18,12 @@ StateListenerRegistry.register(
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
if (!equals(gridDimensions, oldGridDimensions)) {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
@@ -29,7 +31,8 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isOpen
isOpen,
visible
)
);
}
@@ -48,6 +51,7 @@ StateListenerRegistry.register(
case LAYOUTS.TILE_VIEW: {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -56,7 +60,8 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isOpen
isOpen,
visible
)
);
break;
@@ -109,6 +114,7 @@ StateListenerRegistry.register(
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -117,7 +123,35 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isChatOpen
isChatOpen,
visible
)
);
}
});
/**
* Listens for changes in the chat state to calculate the dimensions of the tile view grid and the tiles.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/toolbox'].visible,
/* listener */ (visible, store) => {
const state = store.getState();
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
{
clientHeight,
clientWidth
},
isOpen,
visible
)
);
}

View File

@@ -19,3 +19,14 @@ export const SELECT_LARGE_VIDEO_PARTICIPANT
*/
export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
= 'UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION';
/**
* Action to update the redux store with the current media event name of large video.
*
* @returns {{
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
* name: string
* }}
*/
export const UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
= 'UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT';

View File

@@ -61,8 +61,20 @@ export function selectParticipantInLargeVideo(participant: ?string) {
const state = getState();
const participantId = participant ?? _electParticipantInLargeVideo(state);
const largeVideo = state['features/large-video'];
const screenShares = state['features/video-layout'].screenShares;
let latestScreenshareParticipantId;
if (participantId !== largeVideo.participantId) {
if (screenShares && screenShares.length) {
latestScreenshareParticipantId = screenShares[screenShares.length - 1];
}
// When trying to auto pin screenshare, always select the endpoint even though it happens to be
// the large video participant in redux (for the reasons listed above in the large video selection
// logic above). The auto pin screenshare logic kicks in after the track is added
// (which updates the large video participant and selects all endpoints because of the auto tile
// view mode). If the screenshare endpoint is not among the forwarded endpoints from the bridge,
// it needs to be selected again at this point.
if (participantId !== largeVideo.participantId || participantId === latestScreenshareParticipantId) {
dispatch({
type: SELECT_LARGE_VIDEO_PARTICIPANT,
participantId

View File

@@ -6,6 +6,8 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { MEDIA_TYPE } from '../base/media';
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes';
export * from './actions.any';
/**
@@ -83,3 +85,19 @@ export function resizeLargeVideo(width: number, height: number) {
}
};
}
/**
* Updates the last media event received for the large video.
*
* @param {string} name - The current media event name for the video.
* @returns {{
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
* name: string
* }}
*/
export function updateLastLargeVideoMediaEvent(name: String) {
return {
type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
name
};
}

View File

@@ -5,7 +5,7 @@ import { ReducerRegistry } from '../base/redux';
import {
SELECT_LARGE_VIDEO_PARTICIPANT,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
} from './actionTypes';
ReducerRegistry.register('features/large-video', (state = {}, action) => {
@@ -36,6 +36,13 @@ ReducerRegistry.register('features/large-video', (state = {}, action) => {
...state,
resolution: action.resolution
};
case UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT:
return {
...state,
lastMediaEvent: action.name
};
}
return state;

View File

@@ -144,12 +144,13 @@ function _conferenceJoined({ dispatch }, next, action) {
* @param {Object} participant - The knocking participant.
* @returns {void}
*/
function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
function _findLoadableAvatarForKnockingParticipant(store, { id }) {
const { dispatch, getState } = store;
const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
const { disableThirdPartyRequests } = getState()['features/base/config'];
if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
getFirstLoadableAvatarUrl(updatedParticipant, store).then(loadableAvatarUrl => {
if (loadableAvatarUrl) {
dispatch(participantIsKnockingOrUpdated({
loadableAvatarUrl,

View File

@@ -275,7 +275,7 @@ class Toolbox extends Component<Props, State> {
this._shouldShowButton('videoquality') && {
character: 'A',
exec: this._onShortcutToggleVideoQuality,
helpDescription: 'keyboardShortcuts.videoQuality'
helpDescription: 'toolbar.callQuality'
},
this._shouldShowButton('chat') && {
character: 'C',

View File

@@ -227,7 +227,7 @@ end
-- everything.
function is_feature_allowed(session, feature)
if (session.jitsi_meet_context_features == nil
or session.jitsi_meet_context_features[feature] == "true") then
or session.jitsi_meet_context_features[feature] == "true" or session.jitsi_meet_context_features[feature] == true) then
return true;
else
return false;

View File

@@ -7,7 +7,7 @@
<body>
<div class="error_page">
<h2>404 Not Found</h2>
<p class="error_page__message">You can create new conversation <a class="link" href="/">here</a></p>
<p class="error_page__message">You can create a new conversation <a class="link" onclick="window.location = window.location.protocol + '//' + window.location.hostname">here</a></p>
</div>
</body>
</html>