Compare commits

...

88 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
9f4c036174 fix(blur) fix model paths 2021-02-18 15:36:37 +01:00
Дамян Минков
8fcaea9e3d feat(load-test): Load test startmuted (#8629)
* feat(load-test): Senders unmute themselves if muted by policy.

* feat(load-test): Adds option to skip creating local audio track.

We currently create local audio track even when starting audio muted. Adding the option to control that can load test that for clients or signalling.
2021-02-17 10:28:01 -06:00
Pawel Domas
e0aab11f98 fix: TypeError: Cannot read property 'isAudioTrack' of undefined
When there's no jitsiTrack set on the base/tracks entry it means
a track is being created (get user media is in progress).
2021-02-17 09:25:09 -06:00
Tudor D. Pop
946339a52e feat(blur) replace BodyPix with TFLite
Use the Google Meet model and SIMD optimized WASM.
2021-02-17 16:03:33 +01:00
tmoldovan8x8
f71e8a9982 feat(mobile) adds actions and events for the chat 2021-02-17 16:26:40 +02:00
chipechop
af6080f173 fix(lang) update Italian translation 2021-02-17 13:25:23 +01:00
Jaya Allamsetty
b1080340ec chore(deps) lib-jitsi-meet@latest
* ref(QualityController): Split send and receive video constraints handling.
* fix: Save guards _features to be always empty and nver undefined. (#1493)

d1f0ab4d5a...c534f74884
2021-02-16 11:16:08 -05:00
damencho
684d121159 fix(load-test): Fixes keepalive url when using load-test. 2021-02-12 13:03:29 -06:00
Saúl Ibarra Corretgé
6740b0861e fix(rn,lobby) make sure the enable dialog follows the theme color 2021-02-12 16:40:56 +01:00
tmoldovan8x8
65c56669c4 feat refactors the chat flow so it has open and close functions 2021-02-12 13:18:16 +02:00
Дамян Минков
2cd43ba2e4 fix(load-test): Always create local audio track. (#8612)
* fix(load-test): Always create local audio track.

When audio mutes will mute the track. Also fixes previous change where we do not add any of the tracks to the room.

* squash: Fix lint errors.
2021-02-11 17:25:46 -06:00
Hristo Terezov
fec2641730 fix(popups): covered by labels. 2021-02-11 16:18:56 -06:00
damencho
04ee423257 fix(load-test): Create local tracks and then join.
Behaves now as the main client, and skips few unnecessary presences.
2021-02-11 15:14:31 -06:00
Hristo Terezov
460e137ee4 chore(deps) lib-jitsi-meet@latest
* fix(GUM-permissions): cache permissions on init.
* feat: Reuse billingId from localstorage as jitsiMeetId.
* fix(example) simplify
* feat(docs) mvoe API documentatrion to the handbook

84357ce1a8...d1f0ab4d5a
2021-02-11 13:56:46 -06:00
hmuresan
1a789130a3 feat (external_api) add command for kick participant 2021-02-11 07:33:26 -06:00
Nando Thomassen
16b00dc2af feat(flags) add feature flag for help button
Introduces a new feature flag ('help.enabled') and uses that to
determine the visibility of the 'Help' button in a call.
2021-02-10 22:34:13 +01:00
Saúl Ibarra Corretgé
c3a41b8cf3 fix(avatar) refactor preloading to avoid CORS issues
Fixes: https://github.com/jitsi/jitsi-meet/issues/8510

This basically reverts
a3fb996ff0
while retaining the same properties that prompted it's original intent, namely
avoiding sending the Referrer header.
2021-02-10 14:32:56 +01:00
damencho
f4d0ec1bb4 fix(load-test): Uses websocket if available and adds room param to the connection url. 2021-02-09 15:48:06 -06:00
tmoldovan8x8
ef6b641802 bugfix(ios) changes the participantInfo completion handler reference to strong.
When the method was called from Swift they were collected before calling them.
2021-02-09 18:03:25 +02:00
Saúl Ibarra Corretgé
579acbc570 feat(embed-meeting) add autoplay permission to iframe sample 2021-02-09 08:01:51 -06:00
niteshletxsoft
bca9a12df1 feat(external_ap) add api call to get live stream url 2021-02-09 12:43:38 +01:00
Shoolpani Dubey
a57b967f2e fix(external_api) add autoplay capabilities to created iframe
Should fix https://github.com/jitsi/jitsi-meet/issues/7037
2021-02-09 11:22:42 +01:00
Hristo Terezov
299927fcad docs: Add comment for initial GUM timeout values. 2021-02-08 15:53:38 -06:00
Hristo Terezov
7dc899ace1 ref(DeviceSelectionPopup): remove. 2021-02-08 15:53:38 -06:00
Hristo Terezov
a6c6cd6c56 fix: Add GUM timeout & improve device permissions 2021-02-08 15:53:38 -06:00
Hristo Terezov
7dc45c28a2 fix(AudioSlider): removed when volume is 0 2021-02-08 15:25:17 -06:00
Hristo Terezov
a215f9706a chore(deps) lib-jitsi-meet@latest
* fix(GUM): improve permissions logic.
* feat(GUM): timeout.
* ref: Remove pinEndpoint. (#1440)

30c8795770...84357ce1a8
2021-02-08 14:52:25 -06:00
Jonathan Lennox
4beca0d5dd Set receiver video constraint in load-test script, as-if tile view. (#8567) 2021-02-08 13:35:03 -05:00
Vlad Piersec
bfc4b2ac6f feat(vpaas): Send billing id to prosody 2021-02-08 12:49:55 +02:00
kazan417
53bdaa7928 fix(lang) update Russian translation 2021-02-07 12:44:20 +01:00
Tobias Kneidl
ba18b12024 add module proxy_wstunnel 2021-02-06 22:49:12 -06:00
Tobias Kneidl
a1438f1f21 change websocket url from http:// to ws:// 2021-02-06 22:49:04 -06:00
Julian1203
c856c20513 Update main-de.json
Small improvements (again)
2021-02-06 07:27:55 -06:00
Julian1203
cf92c964b4 Update main-de.json
Small improvements (again)
2021-02-05 22:15:51 -06:00
Julian1203
e69529867e Update main-de.json
Small improvements for the gender-neutral version and other small improvements.
2021-02-05 16:15:39 -06:00
Jaya Allamsetty
fd313c1af7 fix(tests): Add more checks so that test don't error out. 2021-02-05 16:30:58 -05:00
Jonathan Lennox
73c3feb8fa Improve load-test script. (#8563)
* In load-test, merge URL params into config.

* Honor config.testing.noAutoPlayVideo in load-test.
2021-02-05 16:00:28 -05:00
Дамян Минков
67a01364d3 Move load-test to resources (#8560)
* fix: Move load-test to resources.

* squash: Updates load-test dependencies.

* squash: Fix load-test build.
2021-02-05 09:12:45 -06:00
Saúl Ibarra Corretgé
7a64bf006e misc(tools) add script for updating mobile apps versions 2021-02-05 11:56:44 +01:00
Saúl Ibarra Corretgé
e5ea96fd4c feat(rn) update SDK version to 3.1.0 2021-02-05 11:56:44 +01:00
Saúl Ibarra Corretgé
c8ad04d0ff misc(tools) add script for updating SDK version 2021-02-05 11:56:44 +01:00
Saúl Ibarra Corretgé
c56afde00c feat(dev) bind to 0.0.0.0 on the dev server by default 2021-02-05 11:15:20 +01:00
tmoldovan8x8
9ed1969f7e feat(android) adds ability to disable the requestFocus on Android 2021-02-05 09:05:55 +02:00
Jonathan Lennox
12680c35ca Add lightweight load-test webpage, disabled by default (#8514)
Co-authored-by: Hristo Terezov <hristo@jitsi.org>
Co-authored-by: damencho <damencho@jitsi.org>
2021-02-04 18:04:36 -05:00
Jaya Allamsetty
0138f23755 feat(conference): Enable forced reload of client on bridge failure.
* feat(conference): Enable forced reload of client on bridge failure.
Force the client to reload when the bridge that is handling the media goes down.
This mitigates issues seen on the bridge because of a client re-joining the call with the same endpointId, BWE issues, etc.
This behavior is configurable through 'enableForcedReload' setting in config.js.
The client skips the pre-join page when the page reloads.

* squash: refactor the restart logic.

* squash: fix description

* squash: dispatch conferenceWillLeave action before reload.
2021-02-04 12:33:18 -05:00
Marc Seitz
16d88a288f feat: add ipados to list of Platform.OS (#8205)
* feat: add ipados list of Platform.OS
2021-02-04 10:34:44 -05:00
Jaya Allamsetty
210c4857fd deps: Update latest@lib-jitsi-meet.
Add the ability to configure different max bitrates for VP8 and VP9.
Set max bitrate for presenter to 2500 Kbps irrespective of the configured max bitrates for video.
479dd98...77978f0.
2021-02-04 09:50:32 -05:00
tmoldovan8x8
dca96f25f3 feat(mobile) adds feature flags for audioMute, videoMute and overflow… (#8537) 2021-02-04 15:32:09 +02:00
Mihai-Andrei Uscat
b69e93a900 fix(Safari): Fix mobile double tapping for toolbar and overflow.
* Create generic tooltip wrapper for mobile usability.
* Change overflow menu icon/font/padding sizes.
* Change overflow drawer expand icon.
2021-02-04 15:24:25 +02:00
tmoldovan8x8
d2568b874b feat(mobile) adds ability to retrieve participantsInfo array 2021-02-04 14:26:35 +02:00
Juan Searle
20c6115c38 Fix suspicious URL on Persian localization file 2021-02-03 20:39:08 -06:00
OctopusET
68b8ee5961 fix(lang) update korean translation 2021-02-03 15:52:59 +01:00
Andrei Gavrilescu
9895a04609 feat(rtcstats): send meeting uuid to rtcstats (#8526)
* send meeting uuid to rtcstats

* change ret description

* fix flow error

* update lib-jitsi-meet version
2021-02-03 12:28:39 +02:00
Andrzej Moskal
87f688dc8f fix(android) add ability to localize notification actions strings 2021-02-03 10:58:05 +01:00
Jaya Allamsetty
c58657c759 fix(test): Make sure test doesn't error out. 2021-02-01 11:49:45 -05:00
Ali Kazemkhanloo
687106818a Add Persian to languages-fa.json 2021-02-01 07:49:58 -06:00
Ali Kazemkhanloo
f228b4ecc1 Add Persian language to the list of languages 2021-02-01 07:49:58 -06:00
kichinosukey
8582b25d28 typo fix 2021-02-01 07:46:32 -06:00
Vlad Piersec
9418dbc2b1 fix(recents-list): Order recents by last used 2021-02-01 13:30:34 +02:00
Jonas Rittershofer
19bf027b8b Include xmpp and colibri proxy for apache
Signed-off-by: Jonas Rittershofer <jotoeri@users.noreply.github.com>
2021-01-30 10:06:22 -06:00
Mihai-Andrei Uscat
c370c05701 fix(Filmstrip): Prevent Toolbox from being shown indefinitely when hovering filmstrip 2021-01-29 15:34:37 +02:00
Emil Ivov
f034f179ff Merge pull request #8507 from saghul/readme-jaas
fix(docs) add JaaS link to README
2021-01-29 06:57:51 -06:00
Saúl Ibarra Corretgé
b94b18770c fix(docs) add JaaS link to README 2021-01-29 13:54:27 +01:00
Mihai-Andrei Uscat
3f93726c41 fix(Safari): Fix zoomed in mobile interface and cropped tile 2021-01-29 12:55:24 +02:00
Titus-Andrei Moldovan
af8072d9d2 chore(mobile) changes the name for screenShares to remoteScreenShares to better reflect it's content 2021-01-29 12:45:53 +02:00
Titus-Andrei Moldovan
45f4643469 feat(mobile) adds ToggleScreenShare event and action. 2021-01-29 12:45:53 +02:00
Avram Tudor
745879c447 Merge pull request #8503 from jitsi/tavram/dropbox
feat(external_api) allow dropbox option to be overwritten
2021-01-29 12:30:03 +02:00
Mihai-Andrei Uscat
4aab5e2054 feat(Dialog): Make dialog close button more mobile friendly.
* Remove pointless custom headers, fall back to ModalHeader instead.
2021-01-29 12:22:43 +02:00
Tudor-Ovidiu Avram
69971a0e90 feat(external_api) allow dropbox option to be overwritten 2021-01-29 11:44:16 +02:00
Pawel Domas
7c90f75ec9 fix(conference.js): crash on undefined
While on the prejoin screen, the local tracks are managed by the redux store and not conference.js, so localAudio is undefined.
2021-01-28 21:59:44 -05:00
damencho
bf714c1c8b feat: Allow star for room in moderated tenants. 2021-01-28 16:28:39 -06:00
Avram Tudor
8414e9d99f Merge pull request #8495 from jitsi/tavram/chat-updated
feat(external_api) add event for chat updates (unread counter, open s…
2021-01-28 16:42:06 +02:00
Tudor-Ovidiu Avram
dcaad41e69 feat(external_api) add event for chat updates (unread counter, open state) 2021-01-28 11:41:27 +02:00
Jonathan Lennox
63f0166f75 Add mime type mapping for wasm to default Debian nginx config. 2021-01-27 13:42:37 -06:00
Дамян Минков
79f3756d33 feat: Adds option to set ws keepalive url through config. (#8487)
* feat: Adds option to set ws keepalive url through config.

* chore(deps) lib-jitsi-meet@latest

* feat: Adds option to set ws keepalive url through config.
* fix VADAudioAnalyser: catch error

be18ff34be...9fdde46694
2021-01-27 11:13:32 -06:00
damencho
5ac30262a5 fix(debian): Improves handling db_get. 2021-01-26 09:49:03 -06:00
damencho
09315fa653 fix: Adds luasec as dependency.
This is in Recommended but we need it.
2021-01-26 09:49:03 -06:00
damencho
5f0dd903f6 fix(debian): Fixes enforce_apache option. 2021-01-26 09:49:03 -06:00
Steffen Kolmer
ef7d425859 feat(ui) update AtlasKit components 2021-01-26 16:47:55 +01:00
bgrozev
a9bb8e5e81 fix: Use internal_hashed everywhere. (#8485) 2021-01-26 09:33:41 -06:00
Mihai-Andrei Uscat
8cf4e15b23 Add config flag for tile responsiveness 2021-01-26 13:42:57 +02:00
Mihai-Andrei Uscat
db84889143 feat(tiles): Add responsive behaviour.
* Enforce fixed column number at various width breakpoints.
* Bring back the filmstrip at small sizes but hide it.
* Change default maximum columns to 7.
2021-01-26 13:42:57 +02:00
Ali Karpuzoglu
6ca3c6e43a fix(misc) typo 2021-01-26 12:38:49 +01:00
DeBuXer
6fd280a960 fix(lang) update Dutch translation 2021-01-26 11:27:42 +01:00
Jonas Rittershofer
6f9e65d348 Allow to enforce Apache via debconf
Signed-off-by: Jonas Rittershofer <jotoeri@users.noreply.github.com>
2021-01-25 16:27:01 -06:00
damencho
f1e06bff7b fix: Lobby display name set when preJoin is disabled. Fixes #8415. 2021-01-25 16:03:54 -06:00
Jaya Allamsetty
1cf7a361e9 feat: Implement aggressive layer suspension on RN.
RN doesn't support RTCRtpSender yet. Therefore, media is suspended on RN by changing the media direction in the SDP whenever the client receives an ideal height of 0 for sender constraints on the bridge channel.
LJM update - 3570339360...be18ff34be.
2021-01-25 15:21:16 -05:00
okyanusoz
e7990baa7d Fixed Turkish translation issues 2021-01-25 13:36:32 -06:00
225 changed files with 11700 additions and 3525 deletions

View File

@@ -6,6 +6,8 @@ build/*
flow-typed/*
libs/*
react/features/stream-effects/blur/vendor/*
# ESLint will by default ignore its own configuration file. However, there does
# not seem to be a reason why we will want to risk being inconsistent with our
# remaining JavaScript source code.

View File

@@ -5,6 +5,8 @@ LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
OLM_DIR = node_modules/olm
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
TFLITE_WASM = react/features/stream-effects/blur/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/blur/vendor/models/
NODE_SASS = ./node_modules/.bin/sass
NPM = npm
OUTPUT_DIR = .
@@ -16,14 +18,17 @@ WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack-dev-server
all: compile deploy clean
compile:
compile: compile-load-test
$(WEBPACK) -p
compile-load-test:
${NPM} install --prefix resources/load-test && ${NPM} run build --prefix resources/load-test
clean:
rm -fr $(BUILD_DIR)
.NOTPARALLEL:
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -39,8 +44,6 @@ deploy-appbundle:
$(BUILD_DIR)/external_api.min.map \
$(BUILD_DIR)/flacEncodeWorker.min.js \
$(BUILD_DIR)/flacEncodeWorker.min.map \
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
$(BUILD_DIR)/dial_in_info_bundle.min.map \
$(BUILD_DIR)/alwaysontop.min.js \
@@ -81,6 +84,16 @@ deploy-rnnoise-binary:
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
$(DEPLOY_DIR)
deploy-tflite:
cp \
$(TFLITE_WASM)/*.wasm \
$(DEPLOY_DIR)
deploy-meet-models:
cp \
$(MEET_MODELS_DIR)/*.tflite \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
@@ -90,7 +103,7 @@ deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
.NOTPARALLEL:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm
$(WEBPACK_DEV_SERVER) --detect-circular-deps
source-package:

View File

@@ -6,6 +6,8 @@ The Jitsi Meet client runs in your browser, without installing anything else on
Jitsi Meet allows very efficient collaboration. Users can stream their desktop or only some windows. It also supports shared document editing with Etherpad.
**NOTE:** If you are looking for Jitsi as a Service (JaaS) please start [here](https://jaas.8x8.vc).
## Installation
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing a Jitsi Meet suite on your server and hosting your own conferencing service.

View File

@@ -26,4 +26,4 @@ android.useAndroidX=true
android.enableJetifier=true
appVersion=21.0.0
sdkVersion=3.0.0
sdkVersion=3.1.0

View File

@@ -47,6 +47,7 @@ dependencies {
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
if (rootProject.ext.libreBuild) {
implementation(project(':react-native-device-info')) {

View File

@@ -61,7 +61,12 @@ public class BroadcastAction {
enum Type {
SET_AUDIO_MUTED("org.jitsi.meet.SET_AUDIO_MUTED"),
HANG_UP("org.jitsi.meet.HANG_UP"),
SEND_ENDPOINT_TEXT_MESSAGE("org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE");
SEND_ENDPOINT_TEXT_MESSAGE("org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE"),
TOGGLE_SCREEN_SHARE("org.jitsi.meet.TOGGLE_SCREEN_SHARE"),
RETRIEVE_PARTICIPANTS_INFO("org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO"),
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE");
private final String action;

View File

@@ -81,7 +81,11 @@ public class BroadcastEvent {
AUDIO_MUTED_CHANGED("org.jitsi.meet.AUDIO_MUTED_CHANGED"),
PARTICIPANT_JOINED("org.jitsi.meet.PARTICIPANT_JOINED"),
PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT"),
ENDPOINT_TEXT_MESSAGE_RECEIVED("org.jitsi.meet.ENDPOINT_TEXT_MESSAGE_RECEIVED");
ENDPOINT_TEXT_MESSAGE_RECEIVED("org.jitsi.meet.ENDPOINT_TEXT_MESSAGE_RECEIVED"),
SCREEN_SHARE_TOGGLED("org.jitsi.meet.SCREEN_SHARE_TOGGLED"),
PARTICIPANTS_INFO_RETRIEVED("org.jitsi.meet.PARTICIPANTS_INFO_RETRIEVED"),
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED");
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
@@ -90,6 +94,10 @@ public class BroadcastEvent {
private static final String PARTICIPANT_JOINED_NAME = "PARTICIPANT_JOINED";
private static final String PARTICIPANT_LEFT_NAME = "PARTICIPANT_LEFT";
private static final String ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME = "ENDPOINT_TEXT_MESSAGE_RECEIVED";
private static final String SCREEN_SHARE_TOGGLED_NAME = "SCREEN_SHARE_TOGGLED";
private static final String PARTICIPANTS_INFO_RETRIEVED_NAME = "PARTICIPANTS_INFO_RETRIEVED";
private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED";
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
private final String action;
@@ -126,6 +134,14 @@ public class BroadcastEvent {
return PARTICIPANT_LEFT;
case ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME:
return ENDPOINT_TEXT_MESSAGE_RECEIVED;
case SCREEN_SHARE_TOGGLED_NAME:
return SCREEN_SHARE_TOGGLED;
case PARTICIPANTS_INFO_RETRIEVED_NAME:
return PARTICIPANTS_INFO_RETRIEVED;
case CHAT_MESSAGE_RECEIVED_NAME:
return CHAT_MESSAGE_RECEIVED;
case CHAT_TOGGLED_NAME:
return CHAT_TOGGLED;
}
return null;

View File

@@ -19,4 +19,25 @@ public class BroadcastIntentHelper {
intent.putExtra("message", message);
return intent;
}
public static Intent buildToggleScreenShareIntent() {
return new Intent(BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
}
public static Intent buildOpenChatIntent(String participantId) {
Intent intent = new Intent(BroadcastAction.Type.OPEN_CHAT.getAction());
intent.putExtra("to", participantId);
return intent;
}
public static Intent buildCloseChatIntent() {
return new Intent(BroadcastAction.Type.CLOSE_CHAT.getAction());
}
public static Intent buildSendChatMessageIntent(String participantId, String message) {
Intent intent = new Intent(BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
intent.putExtra("to", participantId);
intent.putExtra("message", message);
return intent;
}
}

View File

@@ -16,9 +16,10 @@ public class BroadcastReceiver extends android.content.BroadcastReceiver {
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
intentFilter.addAction(BroadcastAction.Type.HANG_UP.getAction());
intentFilter.addAction(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
for (BroadcastAction.Type type : BroadcastAction.Type.values()) {
intentFilter.addAction(type.getAction());
}
localBroadcastManager.registerReceiver(this, intentFilter);
}

View File

@@ -53,6 +53,8 @@ class ExternalAPIModule
broadcastEmitter = new BroadcastEmitter(reactContext);
broadcastReceiver = new BroadcastReceiver(reactContext);
ParticipantsService.init(reactContext);
}
/**
@@ -78,6 +80,11 @@ class ExternalAPIModule
constants.put("SET_AUDIO_MUTED", BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
constants.put("HANG_UP", BroadcastAction.Type.HANG_UP.getAction());
constants.put("SEND_ENDPOINT_TEXT_MESSAGE", BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
constants.put("TOGGLE_SCREEN_SHARE", BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
constants.put("RETRIEVE_PARTICIPANTS_INFO", BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
constants.put("OPEN_CHAT", BroadcastAction.Type.OPEN_CHAT.getAction());
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
return constants;
}

View File

@@ -266,12 +266,10 @@ public class JitsiMeetActivity extends FragmentActivity
private void registerForBroadcastMessages() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_JOINED.getAction());
intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_WILL_JOIN.getAction());
intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_TERMINATED.getAction());
intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_JOINED.getAction());
intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_LEFT.getAction());
intentFilter.addAction(BroadcastEvent.Type.ENDPOINT_TEXT_MESSAGE_RECEIVED.getAction());
for (BroadcastEvent.Type type : BroadcastEvent.Type.values()) {
intentFilter.addAction(type.getAction());
}
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter);
}

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -96,11 +97,11 @@ class OngoingNotification {
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, "Hang up");
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted
? JitsiMeetOngoingConferenceService.Action.UNMUTE : JitsiMeetOngoingConferenceService.Action.MUTE;
String toggleAudioTitle = isMuted ? "Unmute" : "Mute";
int toggleAudioTitle = isMuted ? R.string.ongoing_notification_action_unmute : R.string.ongoing_notification_action_mute;
NotificationCompat.Action audioAction = createAction(context, toggleAudioAction, toggleAudioTitle);
builder.addAction(hangupAction);
@@ -109,11 +110,12 @@ class OngoingNotification {
return builder.build();
}
private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, String title) {
private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, @StringRes int titleId) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
intent.setAction(action.getName());
PendingIntent pendingIntent
= PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = context.getString(titleId);
return new NotificationCompat.Action(0, title, pendingIntent);
}
}

View File

@@ -0,0 +1,27 @@
package org.jitsi.meet.sdk;
import com.google.gson.annotations.SerializedName;
public class ParticipantInfo {
@SerializedName("participantId")
public String id;
@SerializedName("displayName")
public String displayName;
@SerializedName("avatarUrl")
public String avatarUrl;
@SerializedName("email")
public String email;
@SerializedName("name")
public String name;
@SerializedName("isLocal")
public boolean isLocal;
@SerializedName("role")
public String role;
}

View File

@@ -0,0 +1,90 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
public class ParticipantsService extends android.content.BroadcastReceiver {
private static final String TAG = ParticipantsService.class.getSimpleName();
private static final String REQUEST_ID = "requestId";
private final Map<String, WeakReference<ParticipantsInfoCallback>> participantsInfoCallbackMap = new HashMap<>();
private static ParticipantsService instance;
@Nullable
public static ParticipantsService getInstance() {
return instance;
}
private ParticipantsService(Context context) {
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BroadcastEvent.Type.PARTICIPANTS_INFO_RETRIEVED.getAction());
localBroadcastManager.registerReceiver(this, intentFilter);
}
static void init(Context context) {
instance = new ParticipantsService(context);
}
public void retrieveParticipantsInfo(ParticipantsInfoCallback participantsInfoCallback) {
String callbackKey = UUID.randomUUID().toString();
this.participantsInfoCallbackMap.put(callbackKey, new WeakReference<>(participantsInfoCallback));
String actionName = BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction();
WritableMap data = Arguments.createMap();
data.putString(REQUEST_ID, callbackKey);
ReactInstanceManagerHolder.emitEvent(actionName, data);
}
@Override
public void onReceive(Context context, Intent intent) {
BroadcastEvent event = new BroadcastEvent(intent);
switch (event.getType()) {
case PARTICIPANTS_INFO_RETRIEVED:
try {
List<ParticipantInfo> participantInfoList = new Gson().fromJson(
event.getData().get("participantsInfo").toString(),
new TypeToken<ArrayList<ParticipantInfo>>() {
}.getType());
ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();
if (participantsInfoCallback != null) {
participantsInfoCallback.onReceived(participantInfoList);
this.participantsInfoCallbackMap.remove(participantsInfoCallback);
}
} catch (Exception e) {
JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
}
break;
}
}
public interface ParticipantsInfoCallback {
void onReceived(List<ParticipantInfo> participantInfoList);
}
}

View File

@@ -3,4 +3,7 @@
<string name="dropbox_app_key"></string>
<string name="ongoing_notification_title">Ongoing meeting</string>
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
<string name="ongoing_notification_action_hang_up">Hang up</string>
<string name="ongoing_notification_action_mute">Mute</string>
<string name="ongoing_notification_action_unmute">Unmute</string>
</resources>

View File

@@ -33,6 +33,7 @@ import {
conferenceLeft,
conferenceSubjectChanged,
conferenceTimestampChanged,
conferenceUniqueIdSet,
conferenceWillJoin,
conferenceWillLeave,
dataChannelOpened,
@@ -503,6 +504,11 @@ export default {
let tryCreateLocalTracks;
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
// spend much time displaying the overlay screen. If GUM is not resolved withing 15 seconds it will
// probably never resolve.
const timeout = browser.isElectron() ? 15000 : 60000;
// FIXME is there any simpler way to rewrite this spaghetti below ?
if (options.startScreenSharing) {
tryCreateLocalTracks = this._createDesktopTrack()
@@ -511,7 +517,10 @@ export default {
return [ desktopStream ];
}
return createLocalTracksF({ devices: [ 'audio' ] }, true)
return createLocalTracksF({
devices: [ 'audio' ],
timeout
}, true)
.then(([ audioStream ]) =>
[ desktopStream, audioStream ])
.catch(error => {
@@ -525,7 +534,10 @@ export default {
errors.screenSharingError = error;
return requestedAudio
? createLocalTracksF({ devices: [ 'audio' ] }, true)
? createLocalTracksF({
devices: [ 'audio' ],
timeout
}, true)
: [];
})
.catch(error => {
@@ -537,15 +549,33 @@ export default {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout
}, true)
.catch(err => {
if (requestedAudio && requestedVideo) {
// Try audio only...
errors.audioAndVideoError = err;
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
// In this case we expect that the permission prompt is still visible. There is no point of
// executing GUM with different source. Also at the time of writting the following
// inconsistency have been noticed in some browsers - if the permissions prompt is visible
// and another GUM is executed the prompt does not change its content but if the user
// clicks allow the user action isassociated with the latest GUM call.
errors.audioOnlyError = err;
errors.videoOnlyError = err;
return [];
}
return (
createLocalTracksF({ devices: [ 'audio' ] }, true));
createLocalTracksF({
devices: [ 'audio' ],
timeout
}, true));
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
@@ -566,7 +596,10 @@ export default {
// Try video only...
return requestedVideo
? createLocalTracksF({ devices: [ 'video' ] }, true)
? createLocalTracksF({
devices: [ 'video' ],
timeout
}, true)
: [];
})
.catch(err => {
@@ -1865,6 +1898,10 @@ export default {
APP.store.dispatch(conferenceLeft(room, ...args));
});
room.on(
JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET,
(...args) => APP.store.dispatch(conferenceUniqueIdSet(room, ...args)));
room.on(
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
(authEnabled, authLogin) =>
@@ -2248,7 +2285,7 @@ export default {
return this.useAudioStream(stream);
})
.then(() => {
if (hasDefaultMicChanged) {
if (this.localAudio && hasDefaultMicChanged) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
@@ -2588,7 +2625,7 @@ export default {
// Use the new stream or null if we failed to obtain it.
return useStream(tracks.find(track => track.getType() === mediaType) || null)
.then(() => {
if (hasDefaultMicChanged) {
if (this.localAudio && hasDefaultMicChanged) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of
// the above mentioned chrome bug.

View File

@@ -261,9 +261,16 @@ var config = {
// // the available bandwidth calculated by the browser, but it will be capped by the values specified here.
// // This is currently not implemented on app based clients on mobile.
// maxBitratesVideo: {
// low: 200000,
// standard: 500000,
// high: 1500000
// VP8 : {
// low: 200000,
// standard: 500000,
// high: 1500000
// },
// VP9: {
// low: 100000,
// standard: 300000,
// high: 1200000
// }
// },
//
// // The options can be used to override default thresholds of video thumbnail heights corresponding to
@@ -318,6 +325,11 @@ var config = {
// TCC sequence numbers starting from 0.
// enableIceRestart: false,
// Enables forced reload of the client when the call is migrated as a result of
// the bridge going down. Currently enabled by default as call migration through
// session-terminate is causing siganling issues when Octo is enabled.
// enableForcedReload: true,
// 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)
@@ -326,6 +338,9 @@ var config = {
// UI
//
// Disables responsive tiles.
// disableResponsiveTiles: false,
// Hides lobby button
// hideLobbyButton: false,
@@ -696,6 +711,8 @@ var config = {
forceTurnRelay
hiddenDomain
ignoreStartMuted
websocketKeepAlive
websocketKeepAliveUrl
*/
/**
@@ -720,6 +737,7 @@ var config = {
// 'dialog.reservationError',
// 'dialog.serviceUnavailable', // shown when server is not reachable
// 'dialog.sessTerminated', // shown when there is a failed conference session
// 'dialog.sessionRestarted', // show when a client reload is initiated because of bridge migration
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
// 'dialog.transcribing', // transcribing notifications (pending, off)
// 'dialOut.statusMessage', // shown when dial out status is updated.

View File

@@ -94,6 +94,10 @@ function connect(id, password, roomName) {
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
if (connectionConfig.websocketKeepAliveUrl) {
connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
if (config.iAmRecorder) {

View File

@@ -2,65 +2,58 @@
* Move the @atlaskit/flag container up a little bit so it does not cover the
* toolbar with the first notification.
*/
.jIMojv{
.atlaskit-portal > #notifications-container {
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
}
/**
* Disable the slide-in animation for @atlaskit/flag due to the animation
* repeating for each queued flag once it becomes the top flag.
*/
.mIBKA:first-child {
animation: cbfRuT 0s !important;
-webkit-animation: cbfRuT 0s !important;
}
.modal-dialog-form {
/**
* Update the @atlaskit/dropdown-menu trigger wrapper to make sure it looks
* click-able.
*/
.cjJUnw {
cursor: pointer;
}
/**
* Override @atlaskit/dropdown-menu styling when in a modal because the
* dropdown backgrounds clash with the modal backgrounds.
*/
.cksvax[data-role=droplistContent] {
border: 1px solid #455166;
.dropdown-menu div[style*="transform"] {
outline: 1px solid #455166;
}
}
/**
* Override @atlaskit/modal-dialog header styling
*/
.atlaskit-portal [role="dialog"] header {
.jitsi-icon {
cursor: pointer;
}
.jitsi-icon svg {
fill: #B8C7E0;
}
}
/**
* Make header close button more easily tappable on mobile.
*/
.mobile-browser .atlaskit-portal [role="dialog"] header .jitsi-icon {
display: grid;
place-items: center;
height: 48px;
width: 48px;
background: #2a3a4b;
border-radius: 3px;
}
/**
* Override @atlaskit/theme styling for the top toolbar so it displays over
* the video thumbnail while obscuring as little as possible.
*/
.videocontainer .tOoji {
.videocontainer__toptoolbar > div > div {
background: none;
}
/**
* Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
* with the correct height.
*/
.toolbox-button-wth-dialog .eYJELv {
max-height: initial;
}
/**
* Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
* a scrollable list of elements at small screen widths.
*/
.sc-eNQAEJ {
overflow-y: auto;
}
/**
* Keep overflow menu within screen vertical bounds and make it scrollable.
*/
.toolbox-button-wth-dialog .sc-ckVGcZ.fdAqDG > :first-child {
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 16px);
.toolbox-button-wth-dialog > div:nth-child(2) {
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
overflow-y: auto;
}
}

View File

@@ -143,8 +143,8 @@
top: 18px;
}
// Override @atlaskit/InlineDialog container which is made with styled components
& > div > div:nth-child(2) > div > div {
// Override @atlaskit/InlineDialog container which is made with styled components
& > div:nth-child(2) {
outline: none;
padding: 0;
}

View File

@@ -45,20 +45,12 @@ body {
* pad the modal container in order for the modals to be centered
* while also taking the chat size into consideration.
*/
@media (min-width: 480px + $sidebarWidth) {
.shift-right [class^="Modal__FillScreen"] {
@media (min-width: 581px) {
.shift-right .atlaskit-portal > div:not(.Tooltip) {
padding-left: $sidebarWidth;
}
}
/**
* Similarly, we offset the notifications when the chat is open by
* padding the container.
*/
.shift-right [class^="styledFlagGroup-"] {
padding-left: $sidebarWidth;
}
.jitsi-icon svg {
fill: white;
}

View File

@@ -103,25 +103,22 @@
position: relative;
width: 100%;
z-index: 1;
display: flex;
justify-content: space-between;
padding: 16px;
align-items: center;
box-sizing: border-box;
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
.chat-close {
align-items: center;
bottom: 8px;
color: white;
.jitsi-icon {
cursor: pointer;
display: flex;
font-size: 18px;
height: 40px;
justify-content: center;
line-height: 15px;
padding: 4px;
position: absolute;
right: 5px;
width: 40px;
}
&:hover {
color: rgba(255, 255, 255, 0.8);
}
.jitsi-icon > svg {
fill: #A4B8D1;
}
}
@@ -184,6 +181,10 @@
text-overflow: ellipsis;
overflow: hidden;
}
@media (max-width: 580px) {
display: none !important;
}
}
.chatmessage {
@@ -389,6 +390,7 @@
&-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 16px 16px 24px;
width: calc(100% - 32px);
box-sizing: border-box;
@@ -397,8 +399,11 @@
font-size: 24px;
line-height: 32px;
.jitsi-icon > svg {
.jitsi-icon {
cursor: pointer;
}
.jitsi-icon > svg {
fill: #A4B8D1;
}
}
@@ -407,3 +412,15 @@
width: 100%;
}
}
/**
* Make header close button more easily tappable on mobile.
*/
.mobile-browser .chat-dialog-header .jitsi-icon {
display: grid;
place-items: center;
height: 48px;
width: 48px;
background: #2a3a4b;
border-radius: 3px;
}

View File

@@ -69,7 +69,7 @@
}
// Override @Atlaskit/inline-dialog styles
.cpick-container > div > div:nth-child(2) > div > div {
.cpick-container > div:nth-child(2) {
outline: none;
padding: 8px 0 0 0;
}

View File

@@ -7,7 +7,7 @@
}
.drawer-menu {
padding: 12px 16px;
padding: 0 16px;
max-height: 50vh;
background: #242528;
border-radius: 16px 16px 0 0;
@@ -24,12 +24,14 @@
height: 44px;
cursor: pointer;
&:hover {
background-color: $overflowMenuItemHoverBG;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: $overflowMenuItemHoverBG;
}
}
svg, path {
fill: #b8c7e0;
svg {
fill: none;
}
}
@@ -57,7 +59,7 @@
color: $overflowMenuItemColor;
cursor: pointer;
display: flex;
font-size: 14px;
font-size: 16px;
div {
display: flex;
@@ -65,16 +67,20 @@
align-items: center;
}
&:hover {
background-color: $overflowMenuItemHoverBG;
color: $overflowMenuItemHoverColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: $overflowMenuItemHoverBG;
color: $overflowMenuItemHoverColor;
}
}
&.unclickable {
cursor: default;
}
&.unclickable:hover {
background: inherit;
@media (hover: hover) and (pointer: fine) {
&.unclickable:hover {
background: inherit;
}
}
&.disabled {
cursor: initial;
@@ -93,15 +99,17 @@
}
.overflow-menu-item-icon {
margin-right: 10px;
margin-right: 16px;
i {
display: inline;
font-size: 24px;
}
i:hover {
background-color: initial;
@media (hover: hover) and (pointer: fine) {
i:hover {
background-color: initial;
}
}
img {
@@ -111,11 +119,13 @@
svg {
fill: #B8C7E0 !important;
height: 20px;
width: 20px;
}
}
.profile-text {
max-width: 150px;
max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;

View File

@@ -4,7 +4,7 @@
top: 30px;
right: 30px;
transition: right 0.5s;
z-index: $filmstripVideosZ + 1;
z-index: $labelsZ;
.circular-label {
align-items: center;

View File

@@ -47,4 +47,5 @@
border-radius: 3px;
margin: -16px -24px;
padding: 16px 24px;
z-index: $popoverZ;
}

View File

@@ -129,7 +129,7 @@
}
&-dropdown-container {
& > div > div:nth-child(2) > div > div {
& > div:nth-child(2) {
background: #fff;
padding: 0;
}

View File

@@ -143,16 +143,6 @@
@media only screen and (max-width: $verySmallScreen) {
@include very-small-button-size();
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
&.shift-right {
@@ -177,23 +167,23 @@
}
@media (min-width: 480px) and (max-width: 580px) {
.shift-right [class^="Modal__PositionerAbsolute"] {
.shift-right .focus-lock > div > div {
@include full-size-modal-positioner();
}
.shift-right [class^="Modal__Dialog-"] {
.shift-right .focus-lock [role="dialog"] {
@include full-size-modal-dialog();
}
}
@media (min-width: 580px) and (max-width: 680px) {
.mobile-browser {
&.shift-right [class^="Modal__PositionerAbsolute"] {
&.shift-right .focus-lock > div > div {
@include full-size-modal-positioner();
}
&.shift-right [class^="Modal__Dialog-"] {
&.shift-right .focus-lock [role="dialog"] {
@include full-size-modal-dialog();
}
}
}
}

View File

@@ -42,9 +42,11 @@
display: none;
}
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
@media (min-width: 581px) {
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
.toolbox-background {
@@ -88,31 +90,35 @@
margin: 0px 4px;
width: 38px;
height: 38px;
&:hover {
background-color: #daebfa;
border: 1px solid #daebfa;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: #daebfa;
border: 1px solid #daebfa;
}
}
&.toggled {
background: #2a3a4b;
border: 1px solid #5e6d7a;
svg {
fill: #fff;
}
&:hover {
background-color: #5e6d7a;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: #5e6d7a;
}
}
}
&.disabled, .disabled & {
cursor: initial;
color: #fff;
background-color: #a4b8d1;
}
svg {
fill: #5e6d7a;
}
@@ -124,9 +130,11 @@
border: 1px solid $hangupColor;
width: 40px;
height: 40px;
&:hover {
background-color: $hangupColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: $hangupColor;
}
}
svg {
@@ -157,8 +165,9 @@
cursor: pointer;
display: flex;
font-size: 14px;
height: 22px;
height: 40px;
padding: 5px 12px;
box-sizing: border-box;
div {
display: flex;
@@ -166,16 +175,20 @@
align-items: center;
}
&:hover {
background-color: $overflowMenuItemHoverBG;
color: $overflowMenuItemHoverColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: $overflowMenuItemHoverBG;
color: $overflowMenuItemHoverColor;
}
}
&.unclickable {
cursor: default;
}
&.unclickable:hover {
background: inherit;
@media (hover: hover) and (pointer: fine) {
&.unclickable:hover {
background: inherit;
}
}
&.disabled {
cursor: initial;
@@ -194,15 +207,17 @@
}
.overflow-menu-item-icon {
margin-right: 10px;
margin-right: 16px;
i {
display: inline;
font-size: 24px;
}
i:hover {
background-color: initial;
@media (hover: hover) and (pointer: fine) {
i:hover {
background-color: initial;
}
}
img {
@@ -212,6 +227,8 @@
svg {
fill: #B8C7E0 !important;
height: 20px;
width: 20px;
}
}
@@ -259,10 +276,16 @@
justify-content: center;
width: $newToolbarSize;
&:hover, &.toggled {
@media (hover: hover) and (pointer: fine) {
&:hover {
background: $newToolbarButtonHoverColor;
}
}
&.toggled {
background: $newToolbarButtonHoverColor;
}
&.disabled {
cursor: initial !important;
background-color: #a4b8d1 !important;

View File

@@ -12,7 +12,7 @@
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
transform: translateX(-50%);
z-index: $filmstripVideosZ + 1;
z-index: $subtitlesZ;
span {
background: black;

View File

@@ -114,7 +114,10 @@ $zindex1: 1;
$zindex2: 2;
$zindex3: 3;
$toolbarBackgroundZ: 4;
$filmstripVideosZ: 5;
$labelsZ: 5;
$filmstripVideosZ: 6;
$subtitlesZ: 7;
$popoverZ: 8;
$zindex10: 10;
$reloadZ: 20;
$poweredByZ: 100;
@@ -126,7 +129,6 @@ $tooltipsZ: 401;
$dropdownMaskZ: 900;
$dropdownZ: 901;
$centeredVideoLabelZ: 1010;
$popoverZ: 1015;
$overlayZ: 1016;

View File

@@ -63,7 +63,7 @@
}
// Override @atlaskit/InlineDialog container which is made with styled components
& > div > div:nth-child(2) > div > div {
& > div:nth-child(2) {
outline: none;
padding: 0;
}

View File

@@ -182,10 +182,12 @@
z-index: $zindex2;
}
&.shift-right {
&#largeVideoContainer {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
@media (min-width: 581px) {
&.shift-right {
&#largeVideoContainer {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
}
}
@@ -478,14 +480,6 @@
z-index: $reloadZ; /*The reload button should appear on top of the header!*/
}
.audiolevel {
display: inline-block;
position: absolute;
z-index: $zindex0;
border-radius:1px;
pointer-events: none;
}
#dominantSpeaker {
visibility: hidden;
width: 300px;

View File

@@ -6,7 +6,6 @@
}
.horizontal-filmstrip .filmstrip {
position: absolute;
bottom: 0;
right: 0;
padding: 10px 5px;

View File

@@ -15,7 +15,7 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
height: 100%;
width: 100vw;
}
@@ -42,17 +42,18 @@
height: 100%;
justify-content: center;
left: 0;
position: fixed;
position: absolute;
top: 0;
width: 100%;
z-index: $filmstripVideosZ;
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
@media (min-width: 581px) {
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
#filmstripRemoteVideos {
width: calc(100vw - #{$sidebarWidth});
#filmstripRemoteVideos {
width: calc(100vw - #{$sidebarWidth});
}
}
}
}
@@ -86,6 +87,7 @@
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
flex-shrink: 0;
margin-top: auto;
margin-bottom: auto;
justify-content: center;
@@ -100,6 +102,15 @@
video {
object-fit: contain;
}
/**
* Max-width corresponding to the ASPECT_RATIO_BREAKPOINT from features/filmstrip/constants.
*/
@media only screen and (max-width: 500px) {
video {
object-fit: cover;
}
}
}
.has-overflow#filmstripRemoteVideosContainer {

View File

@@ -22,11 +22,6 @@
display: none;
}
#remoteConnectionMessage,
.watermark {
z-index: $filmstripVideosZ + 1;
}
/**
* The follow styling uses !important to override inline styles set with
* javascript.

View File

@@ -39,7 +39,7 @@
.device-selector-trigger-text {
overflow: hidden;
margin-left: 8px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;

View File

@@ -2,22 +2,6 @@
&-dialog {
display: flex;
flex-direction: column;
&-header {
display: flex;
justify-content: space-between;
margin: 16px 16px 24px;
width: calc(100% - 32px);
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
& > div > svg {
cursor: pointer;
fill: #A4B8D1;
}
}
}
&-copy {

View File

@@ -7,10 +7,6 @@
text-align: center;
z-index: $zindex2;
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
&.elevated {
z-index: $filmstripVideosZ + 1;
}
}
&-header {
@@ -32,8 +28,10 @@
line-height: 24px;
cursor: pointer;
&:hover {
background: #278ADF;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: #278ADF;
}
}
&-text {
@@ -47,22 +45,6 @@
font-size: 15px;
line-height: 24px;
&.header {
display: flex;
justify-content: space-between;
margin: 16px 16px 24px;
width: calc(100% - 32px);
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
& > div > svg {
cursor: pointer;
fill: #A4B8D1;
}
}
&.separator {
margin: 24px 0 24px -20px;
padding: 0 20px;
@@ -112,11 +94,11 @@
border-radius: 4px;
cursor: pointer;
}
&:hover > div:hover {
background-color: rgba(255, 255, 255, 0.2);
}
& > :not(:last-child) {
margin-right: 16px;
}

View File

@@ -23,6 +23,10 @@
padding: 20px 0px 4px 0px;
}
input[type="checkbox"] + svg + span {
color: #9FB0CC;
}
.calendar-tab,
.more-tab,
.profile-edit {

View File

@@ -111,7 +111,3 @@
display: none;
}
}
#videoResolutionLabel {
z-index: $zindex3 + 1;
}

2
debian/control vendored
View File

@@ -33,7 +33,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody | prosody-trunk | prosody-0.11
Depends: openssl, prosody | prosody-trunk | prosody-0.11, lua-sec
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi

View File

@@ -45,8 +45,9 @@ case "$1" in
JVB_SERVE="false"
# this detect only old installations
RET=""
db_get jitsi-meet/jvb-serve || true
if [ -n "$RET" ] && [ "$RET" = "true" ] ; then
if [ "$RET" = "true" ] ; then
JVB_SERVE="true"
fi
@@ -68,14 +69,22 @@ case "$1" in
if [ "$APACHE_INSTALL_CHECK" = "installed" ] || [ "$APACHE_INSTALL_CHECK" = "unpacked" ] ; then
FORCE_APACHE="true"
fi
# In case user enforces apache and if apache is available, unset nginx.
RET=""
db_get jitsi-meet/enforce_apache || RET="false"
if [ "$RET" = "true" ] && [ "$FORCE_APACHE" = "true" ]; then
FORCE_NGINX="false"
fi
UPLOADED_CERT_CHOICE="I want to use my own certificate"
# if first time config ask for certs, or if we are reconfiguring
if [ -z "$JVB_HOSTNAME_OLD" ] || [ "$RECONFIGURING" = "true" ] ; then
RET=""
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
RET=""
db_get jitsi-meet/cert-path-key
if [ -z "$RET" ] ; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
@@ -84,6 +93,7 @@ case "$1" in
db_get jitsi-meet/cert-path-key
fi
CERT_KEY="$RET"
RET=""
db_get jitsi-meet/cert-path-crt
if [ -z "$RET" ] ; then
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
@@ -146,12 +156,15 @@ case "$1" in
# Removing this value will force nginx or apache to be locally configured
JVB_HOSTNAME_OLD=""
RET=""
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
# Fix certs on upgrade from jetty
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
RET=""
db_get jitsi-meet/cert-path-key
CERT_KEY="$RET"
RET=""
db_get jitsi-meet/cert-path-crt
CERT_CRT="$RET"
else
@@ -205,7 +218,7 @@ case "$1" in
# apache2 config
if [ ! -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf ] ; then
# when creating new config, make sure all needed modules are enabled
a2enmod rewrite ssl headers proxy_http include
a2enmod rewrite ssl headers proxy_http proxy_wstunnel include
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
a2ensite $JVB_HOSTNAME.conf
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/apache2/sites-available/$JVB_HOSTNAME.conf

View File

@@ -15,3 +15,5 @@ resources/robots.txt /usr/share/jitsi-meet/
resources/*.sh /usr/share/jitsi-meet/scripts/
pwa-worker.js /usr/share/jitsi-meet/
manifest.json /usr/share/jitsi-meet/
resources/load-test/*.html /usr/share/jitsi-meet/load-test/
resources/load-test/libs /usr/share/jitsi-meet/load-test/

View File

@@ -74,7 +74,7 @@ Component "internal.auth.jitmeet.example.com" "muc"
muc_room_default_public_jids = true
VirtualHost "auth.jitmeet.example.com"
authentication = "internal_plain"
authentication = "internal_hashed"
-- Proxy to jicofo's user JID, so that it doesn't have to register as a component.
Component "focus.jitmeet.example.com" "client_proxy"

View File

@@ -1,5 +1,9 @@
server_names_hash_bucket_size 64;
types {
# nginx's default mime.types doesn't include a mapping for wasm
application/wasm wasm;
}
server {
listen 80;
listen [::]:80;
@@ -96,6 +100,15 @@ server {
tcp_nodelay on;
}
# load test minimal client, uncomment when used
#location ~ ^/_load-test/([^/?&:'"]+)$ {
# rewrite ^/_load-test/(.*)$ /load-test/index.html break;
#}
#location ~ ^/_load-test/libs/(.*)$ {
# add_header 'Access-Control-Allow-Origin' '*';
# alias /usr/share/jitsi-meet/load-test/libs/$1;
#}
location ~ ^/([^/?&:'"]+)$ {
try_files $uri @root_path;
}

View File

@@ -44,6 +44,9 @@
ProxyPreserveHost on
ProxyPass /http-bind http://localhost:5280/http-bind/
ProxyPassReverse /http-bind http://localhost:5280/http-bind/
ProxyPass /xmpp-websocket ws://localhost:5280/xmpp-websocket
ProxyPassReverse /xmpp-websocket ws://localhost:5280/xmpp-websocket
ProxyPassMatch ^/colibri-ws/default-id ws://localhost:9090
RewriteEngine on
RewriteRule ^/([a-zA-Z0-9]+)$ /index.html

View File

@@ -142,7 +142,6 @@ pidfile = "/var/run/prosody/prosody.pid"
-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
-- for information about using the hashed backend.
-- authentication = "internal_plain"
authentication = "internal_hashed"
-- Select the storage backend to use. By default Prosody uses flat files
@@ -190,7 +189,7 @@ VirtualHost "auth.jitsi.example.com"
key = "/var/lib/prosody/auth.jitsi.example.com.key";
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
}
authentication = "internal_plain"
authentication = "internal_hashed"
------ Components ------
-- You can specify components to add hosts that provide special services,

View File

@@ -139,7 +139,6 @@ pidfile = "/var/run/prosody/prosody.pid"
-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
-- for information about using the hashed backend.
-- authentication = "internal_plain"
authentication = "internal_hashed"
-- Select the storage backend to use. By default Prosody uses flat files
@@ -187,7 +186,7 @@ VirtualHost "auth.jitsi.example.com"
key = "/var/lib/prosody/auth.jitsi.example.com.key";
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
}
authentication = "internal_plain"
authentication = "internal_hashed"
------ Components ------
-- You can specify components to add hosts that provide special services,

View File

@@ -67,7 +67,7 @@ VirtualHost "auth.meet.example.com"
key = "/etc/prosody/certs/auth.meet.example.com.key";
certificate = "/etc/prosody/certs/auth.meet.example.com.crt";
}
authentication = "internal_plain"
authentication = "internal_hashed"
Component "focus.meet.example.com"
component_secret = "jicofo_secret_test"
@@ -83,5 +83,5 @@ VirtualHost "recorder.meet.example.com"
modules_enabled = {
"ping";
}
authentication = "internal_plain"
authentication = "internal_hashed"
c2s_require_encryption = false

View File

@@ -3,7 +3,7 @@
<!--#include virtual="head.html" -->
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="theme-color" content="#2A3A4B">
<!--#include virtual="base.html" -->

View File

@@ -115,10 +115,22 @@
NSLog(@"%@%@", @"Audio muted changed: ", data[@"muted"]);
}
- (void)endpointTextMessageReceived:(NSDictionary *)data; {
- (void)endpointTextMessageReceived:(NSDictionary *)data {
NSLog(@"%@%@", @"Endpoint text message received: ", data);
}
- (void)screenShareToggled:(NSDictionary *)data {
NSLog(@"%@%@", @"Screen share toggled: ", data);
}
- (void)chatMessageReceived:(NSDictionary *)data {
NSLog(@"%@%@", @"Chat message received: ", data);
}
- (void)chatToggled:(NSDictionary *)data {
NSLog(@"%@%@", @"Chat toggled: ", data);
}
#pragma mark - Helpers
- (void)terminate {

View File

@@ -19,7 +19,12 @@
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
- (void)sendHangUp;
- (void)sendSetAudioMuted: (BOOL)muted;
- (void)sendSetAudioMuted:(BOOL)muted;
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
- (void)toggleScreenShare;
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
- (void)openChat:(NSString*)to;
- (void)closeChat;
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
@end

View File

@@ -21,16 +21,33 @@
static NSString * const hangUpAction = @"org.jitsi.meet.HANG_UP";
static NSString * const setAudioMutedAction = @"org.jitsi.meet.SET_AUDIO_MUTED";
static NSString * const sendEndpointTextMessageAction = @"org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE";
static NSString * const toggleScreenShareAction = @"org.jitsi.meet.TOGGLE_SCREEN_SHARE";
static NSString * const retrieveParticipantsInfoAction = @"org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO";
static NSString * const openChatAction = @"org.jitsi.meet.OPEN_CHAT";
static NSString * const closeChatAction = @"org.jitsi.meet.CLOSE_CHAT";
static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSAGE";
@implementation ExternalAPI
static NSMapTable<NSString*, void (^)(NSArray* participantsInfo)> *participantInfoCompletionHandlers;
__attribute__((constructor))
static void initializeViewsMap() {
participantInfoCompletionHandlers = [NSMapTable strongToStrongObjectsMapTable];
}
RCT_EXPORT_MODULE();
- (NSDictionary *)constantsToExport {
return @{
@"HANG_UP": hangUpAction,
@"SET_AUDIO_MUTED" : setAudioMutedAction,
@"SEND_ENDPOINT_TEXT_MESSAGE": sendEndpointTextMessageAction
@"SEND_ENDPOINT_TEXT_MESSAGE": sendEndpointTextMessageAction,
@"TOGGLE_SCREEN_SHARE": toggleScreenShareAction,
@"RETRIEVE_PARTICIPANTS_INFO": retrieveParticipantsInfoAction,
@"OPEN_CHAT": openChatAction,
@"CLOSE_CHAT": closeChatAction,
@"SEND_CHAT_MESSAGE": sendChatMessageAction
};
};
@@ -46,7 +63,15 @@ RCT_EXPORT_MODULE();
}
- (NSArray<NSString *> *)supportedEvents {
return @[ hangUpAction, setAudioMutedAction, sendEndpointTextMessageAction ];
return @[ hangUpAction,
setAudioMutedAction,
sendEndpointTextMessageAction,
toggleScreenShareAction,
retrieveParticipantsInfoAction,
openChatAction,
closeChatAction,
sendChatMessageAction
];
}
/**
@@ -74,6 +99,11 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
if (!delegate) {
return;
}
if ([name isEqual: @"PARTICIPANTS_INFO_RETRIEVED"]) {
[self onParticipantsInfoRetrieved: data];
return;
}
SEL sel = NSSelectorFromString([self methodNameFromEventName:name]);
@@ -82,6 +112,15 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
}
}
- (void) onParticipantsInfoRetrieved:(NSDictionary *)data {
NSArray *participantsInfoArray = [data objectForKey:@"participantsInfo"];
NSString *completionHandlerId = [data objectForKey:@"requestId"];
void (^completionHandler)(NSArray*) = [participantInfoCompletionHandlers objectForKey:completionHandlerId];
completionHandler(participantsInfoArray);
[participantInfoCompletionHandlers removeObjectForKey:completionHandlerId];
}
/**
* Converts a specific event name i.e. redux action type description to a
* method name.
@@ -115,12 +154,43 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
}
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
NSDictionary *data = @{
@"to": to,
@"message": message
};
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
data[@"to"] = to;
data[@"message"] = message;
[self sendEventWithName:sendEndpointTextMessageAction body:data];
}
- (void)toggleScreenShare {
[self sendEventWithName:toggleScreenShareAction body:nil];
}
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler {
NSString *completionHandlerId = [[NSUUID UUID] UUIDString];
NSDictionary *data = @{ @"requestId": completionHandlerId};
[participantInfoCompletionHandlers setObject:[completionHandler copy] forKey:completionHandlerId];
[self sendEventWithName:retrieveParticipantsInfoAction body:data];
}
- (void)openChat:(NSString*)to {
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
data[@"to"] = to;
[self sendEventWithName:openChatAction body:data];
}
- (void)closeChat {
[self sendEventWithName:closeChatAction body:nil];
}
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
data[@"to"] = to;
data[@"message"] = message;
[self sendEventWithName:sendChatMessageAction body:data];
}
@end

View File

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

View File

@@ -36,11 +36,13 @@
* Leaves the currently active conference.
*/
- (void)leave;
- (void)hangUp;
- (void)setAudioMuted:(BOOL)muted;
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
- (void)toggleScreenShare;
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler;
- (void)openChat:(NSString*)to;
- (void)closeChat;
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
@end

View File

@@ -130,6 +130,31 @@ static void initializeViewsMap() {
[externalAPI sendEndpointTextMessage:to :message];
}
- (void)toggleScreenShare {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI toggleScreenShare];
}
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI retrieveParticipantsInfo:completionHandler];
}
- (void)openChat:(NSString*)to {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI openChat:to];
}
- (void)closeChat {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI closeChat];
}
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendChatMessage:to :message];
}
#pragma mark Private methods
/**

View File

@@ -83,4 +83,25 @@
*/
- (void)endpointTextMessageReceived:(NSDictionary *)data;
/**
* Called when a participant toggled shared screen.
*
* The `data` dictionary contains a `participantId` key with the id of the participant and a 'sharing' key with boolean value.
*/
- (void)screenShareToggled:(NSDictionary *)data;
/**
* Called when a chat message is received.
*
* The `data` dictionary contains `message`, `senderId` and `isPrivate` keys.
*/
- (void)chatMessaageReceived:(NSDictionary *)data;
/**
* Called when the chat dialog is displayed/hidden.
*
* The `data` dictionary contains a `isOpen` key.
*/
- (void)chatToggled:(NSDictionary *)data;
@end

View File

@@ -32,6 +32,7 @@
"lv": "لتونیایی",
"nl": "هلندی",
"oc": "اکسیتان(قدیمی)",
"fa": "فارسی",
"pl": "لهستانی",
"ptBR": "پرتغالی (برزیل)",
"ru": "روسی",
@@ -47,4 +48,4 @@
"vi": "ویتنامی",
"zhCN": "چینی",
"zhTW": "چینی (تایوان)"
}
}

View File

@@ -32,6 +32,7 @@
"lv": "Latvian",
"nl": "Dutch",
"oc": "Occitan",
"fa": "Persian",
"pl": "Polish",
"ptBR": "Portuguese (Brazil)",
"ru": "Russian",

View File

@@ -105,7 +105,7 @@
"bridgeCount": "Serverzahl: ",
"codecs": "Codecs (A/V): ",
"connectedTo": "Verbunden mit:",
"e2e_rtt": "E2E RTT:",
"e2e_rtt": "Ende-zu-Ende-Paketumlaufzeit:",
"framerate": "Bildwiederholrate:",
"less": "Weniger anzeigen",
"localaddress": "Lokale Adresse:",
@@ -146,7 +146,7 @@
"downloadApp": "App herunterladen",
"ifDoNotHaveApp": "Wenn Sie die App noch nicht haben:",
"ifHaveApp": "Wenn Sie die App bereits haben:",
"joinInApp": "An dem Meeting teilnehmen mit der App",
"joinInApp": "Mit der App am Meeting teilnehmen",
"launchWebButton": "Im Web öffnen",
"title": "Die Konferenz wird in {{app}} geöffnet …",
"tryAgainButton": "Erneut mit der nativen Applikation versuchen"
@@ -206,9 +206,9 @@
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
"error": "Fehler",
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
"grantModeratorDialog": "Möchten Sie diesen Person wirklich zum Moderator:in machen?",
"grantModeratorDialog": "Möchten Sie wirklich Moderationsrechte an diese Person vergeben?",
"grantModeratorTitle": "Moderationsrechte vergeben",
"IamHost": "Ich organisiere das Meeting",
"IamHost": "Ich leite das Meeting",
"incorrectRoomLockPassword": "Falsches Passwort",
"incorrectPassword": "Name oder Passwort ungültig",
"internalError": "Oh! Es hat etwas nicht funktioniert. Der folgende Fehler ist aufgetreten: {{error}}",
@@ -227,7 +227,7 @@
"lockTitle": "Sperren fehlgeschlagen",
"logoutQuestion": "Sind Sie sicher, dass Sie sich abmelden und die Konferenz verlassen möchten?",
"logoutTitle": "Abmelden",
"maxUsersLimitReached": "Das Limit für die maximale Personenzahl ist erreicht. Die Konferenz ist voll. Bitte wenden Sie sich an die verwaltende Person des Meetings oder versuchen Sie es später noch einmal!",
"maxUsersLimitReached": "Das Limit für die maximale Personenzahl ist erreicht. Die Konferenz ist voll. Bitte wenden Sie sich an die Konferenzleitung oder versuchen Sie es später noch einmal!",
"maxUsersLimitReachedTitle": "Maximale Personenzahl erreicht",
"micConstraintFailedError": "Ihr Mikrofon erfüllt die notwendigen Anforderungen nicht.",
"micNotFoundError": "Mikrofon nicht gefunden.",
@@ -305,8 +305,8 @@
"unlockRoom": "Konferenz$t(lockRoomPassword) entfernen",
"user": "Anmeldename",
"userPassword": "Passwort",
"WaitForHostMsg": "Die Konferenz <b>{{room}}</b> wurde noch nicht gestartet. Falls sie die Konferenz organisieren, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostMsgWOk": "Die Konferenz <b>{{room}}</b> wurde noch nicht gestartet. Falls sie die Konferenz organisieren, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostMsg": "Die Konferenz <b>{{room}}</b> wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostMsgWOk": "Die Konferenz <b>{{room}}</b> wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitingForHost": "Warten auf den Beginn der Konferenz …",
"Yes": "Ja",
"yourEntireScreen": "Ganzer Bildschirm"
@@ -391,7 +391,7 @@
"localRecording": "Lokale Aufzeichnungssteuerelemente ein- oder ausblenden",
"mute": "Stummschaltung aktivieren oder deaktivieren",
"pushToTalk": "Push-to-Talk (Sprechtaste)",
"raiseHand": "Hand erheben",
"raiseHand": "Hand heben",
"showSpeakerStats": "Sprechstatistik anzeigen",
"toggleChat": "Chat öffnen oder schließen",
"toggleFilmstrip": "Video-Miniaturansichten ein- oder ausblenden",
@@ -401,8 +401,8 @@
"videoQuality": "Anrufqualität verwalten"
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihr Stream auf {{limit}} min. begrenzt. Für unlimitiertes Streaming nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Ihr Stream ist begrenzt auf {{limit}} min. Für unlimitiertes Streaming, nutzen Sie bitte {{app}}.",
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihr Stream auf {{limit}} Min. begrenzt. Für unlimitiertes Streaming nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Ihr Stream ist begrenzt auf {{limit}} Min. Für unlimitiertes Streaming, nutzen Sie bitte {{app}}.",
"busy": "Es werden Ressourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
"changeSignIn": "Konten wechseln.",
@@ -454,7 +454,7 @@
"finishedModerator": "Aufzeichnung der Sitzung {{token}} ist beendet. Die Aufzeichnung des lokalen Verlaufs wurde gespeichert. Bitten Sie die anderen Personen, ihre Aufzeichnungen zu übermitteln.",
"notModerator": "Sie moderieren nicht. Sie können die lokale Aufzeichnung nicht starten oder stoppen."
},
"moderator": "Moderator:in",
"moderator": "Moderation",
"no": "Nein",
"participant": "Person",
"participantStats": "Personenstatistik",
@@ -471,8 +471,8 @@
"connectedThreePlusMembers": "{{name}} und {{count}} andere Personen nehmen am Meeting teil",
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
"disconnected": "getrennt",
"focus": "Konferenz-Organisator:in",
"focusFail": "{{component}} ist im Moment nicht verfügbar - wiederholen in {{ms}} Sekunden",
"focus": "Konferenzleitung",
"focusFail": "{{component}} ist im Moment nicht verfügbar wiederholen in {{ms}} Sekunden",
"grantedTo": "Moderationsrechte an {{to}} vergeben!",
"invitedOneMember": "{{name}} wurde eingeladen",
"invitedThreePlusMembers": "{{name}} und {{count}} andere wurden eingeladen",
@@ -526,7 +526,7 @@
"goodQuality": "Großartig! Ihre Bild- und Tonqualität sollte super sein.",
"noMediaConnectivity": "Es konnte für diesen Test keine Medienverbindung hergestellt werden. Das wird gewöhnlich durch eine Firewall oder ein NAT ausgelöst.",
"noVideo": "Ihr Bild wird wahrscheinlich eine schlechte Qualität haben.",
"undetectable": "Wenn Sie mit Ihrem Browser weiterhin Probleme in Konferenzen haben, sollten Sie die Verbindung und Funktion Ihrer Lautsprecher, Ihres Mikrofons und Ihrer Kamera überprüfen. Stellen Sie außerdem sicher, dass Ihr Browser die erforderlichen Rechte hat, auf das Mikrofon und die Kamera zuzugreifen, und dass Sie die neuste Browserversion installiert haben. Sollten Sie immer noch Probleme haben, kontaktieren Sie bitte die Entwickler:innen der Webanwendung.",
"undetectable": "Wenn Sie mit Ihrem Browser weiterhin Probleme in Konferenzen haben, sollten Sie die Verbindung und Funktion Ihrer Lautsprecher, Ihres Mikrofons und Ihrer Kamera überprüfen. Stellen Sie außerdem sicher, dass Ihr Browser die erforderlichen Rechte hat, auf das Mikrofon und die Kamera zuzugreifen, und dass Sie die neuste Browserversion installiert haben. Sollten Sie immer noch Probleme haben, kontaktieren Sie bitte den Support der Webanwendung.",
"veryPoorConnection": "Ihre Konferenzqualität wird wahrscheinlich sehr schlecht sein.",
"videoFreezing": "Ihr Bild wird wahrscheinlich einfrieren, schwarz werden und eine geringe Auflösung haben.",
"videoHighQuality": "Ihr Bild sollte sehr gut aussehen.",
@@ -583,8 +583,8 @@
},
"raisedHand": "Ich möchte sprechen",
"recording": {
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} min begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
"authDropboxText": "In Dropbox hochladen",
"availableSpace": "Verfügbarer Speicherplatz: {{spaceLeft}} MB (ca. {{duration}} Minuten Aufzeichnung)",
"beta": "BETA",
@@ -633,7 +633,7 @@
"language": "Sprache",
"loggedIn": "Als {{name}} angemeldet",
"microphones": "Mikrofon",
"moderator": "Moderator:in",
"moderator": "Moderation",
"more": "Mehr",
"name": "Name",
"noDevice": "Kein",
@@ -671,12 +671,12 @@
"dialInfoText": "\n\n=====\n\nWollen Sie sich nur auf Ihrem Telefon einwählen?\n\n{{defaultDialInNumber}}Klicken Sie auf diesen Link, um die eingewählten Telefonnummern für dieses Meeting zu sehen\n{{dialInfoPageUrl}}",
"mainText": "Klicken Sie auf den folgenden Link, um dem Meeting beizutreten:\n{{roomUrl}}"
},
"speaker": "Sprecher:in",
"speaker": "Sprecher/-in",
"speakerStats": {
"hours": "{{count}}h",
"minutes": "{{count}}m",
"hours": "{{count}} Std.",
"minutes": "{{count}} Min.",
"name": "Name",
"seconds": "{{count}}s",
"seconds": "{{count}} Sek.",
"speakerStats": "Sprechstatistik",
"speakerTime": "Sprechzeit"
},
@@ -853,7 +853,7 @@
"flip": "Spiegeln",
"grantModerator": "Moderationsrechte vergeben",
"kick": "Hinauswerfen",
"moderator": "Moderator:in",
"moderator": "Moderation",
"mute": "Person ist stumm geschaltet",
"muted": "Stummgeschaltet",
"remoteControl": "Fernsteuerung",
@@ -897,7 +897,7 @@
},
"lonelyMeetingExperience": {
"button": "Andere einladen",
"youAreAlone": "Sie sind alleine in dieser Konferenz"
"youAreAlone": "Nur Sie sind in dieser Konferenz"
},
"helpView": {
"header": "Hilfecenter"
@@ -907,7 +907,7 @@
"allow": "Annehmen",
"backToKnockModeButton": "Kein Passwort, stattdessen Beitritt anfragen",
"dialogTitle": "Lobbymodus",
"disableDialogContent": "Lobbymodus derzeit aktiviert. Diese Funktion stellt sicher, dass unerwünschte Personen Ihrer Konferenz nicht beitreten können. Funktion deaktivieren?",
"disableDialogContent": "Der Lobbymodus ist derzeit aktiviert. Diese Funktion stellt sicher, dass unerwünschte Personen Ihrer Konferenz nicht beitreten können. Funktion deaktivieren?",
"disableDialogSubmit": "Deaktivieren",
"emailField": "E-Mail-Adresse eingeben",
"enableDialogPasswordField": "Passwort setzen (optional)",

View File

@@ -200,7 +200,7 @@
"dismiss": "رد کردن",
"displayNameRequired": "نام خود را وارد نمایید",
"done": "تایید",
"e2eeDescription": "<p>رمزگذاری دو طرفه در حال حاضر به صورت <strong>آزمایشی</strong> استفاده می‌شود؛ برای اطلاعات بیشتر می‌توانید <a href='https://blog.bebbin.ir/e2ee/' target='_blank'>این مقاله</a> را ببینید.</p><br/><p>در نظر داشته باشید با فعال کردن رمز گذاری دو طرفه قابلیت‌های سمت سرو غیرفعال خواهند شد، قابلیت‌هایی از قبیل:ضبط، پخش زنده و مشارکت تلفنی در جلسات؛ همچنین افرادی می‌توانند به این جلسه بپیوندند که مرورگر آن‌ها از قابلیت پخش درون مرورگری پشتیبانی کند،</p>",
"e2eeDescription": "<p>رمزگذاری دو طرفه در حال حاضر به صورت <strong>آزمایشی</strong> استفاده می‌شود؛ برای اطلاعات بیشتر می‌توانید <a href='https://jitsi.org/blog/e2ee/' target='_blank'>این مقاله</a> را ببینید.</p><br/><p>در نظر داشته باشید با فعال کردن رمز گذاری دو طرفه قابلیت‌های سمت سرو غیرفعال خواهند شد، قابلیت‌هایی از قبیل:ضبط، پخش زنده و مشارکت تلفنی در جلسات؛ همچنین افرادی می‌توانند به این جلسه بپیوندند که مرورگر آن‌ها از قابلیت پخش درون مرورگری پشتیبانی کند،</p>",
"e2eeLabel": "کلید E2EE",
"e2eeWarning": "<br /><p><strong>هشدار:</strong> همه‌ی مشارکت کنندگان رمزنگاری دو طرفه را پشتیبانی نمی‌کنند؛ اگر این قابلیت را فعال کنید آن‌ها صدای جلسه را نمی‌شنوند و تصویر را مشاهده نخواهند کرد</p>",
"enterDisplayName": "لطفا نام نمایشی خود را وارد نمایید",

View File

@@ -180,6 +180,7 @@
"cameraNotSendingData": "Non possiamo accedere alla tua videocamera. Controlla che non sia già usata da un'altra applicazione, seleziona un altro dispositivo dalle impostazioni o prova a ricaricare l'applicazione.",
"cameraNotSendingDataTitle": "Impossibile accedere alla videocamera",
"cameraPermissionDeniedError": "Non hai concesso il permesso di usare la videocamera. Potrai partecipare comunque alla riunione ma gli altri non potranno vederti. Usa il pulsante a forma di videocamera nella barra degli indirizzi per risolvere il problema.",
"cameraTimeoutError": "Impossibile avviare la sorgente video. Tempo di attesa scaduto.",
"cameraUnknownError": "Impossibile usare la videocamera per un motivo sconosciuto.",
"cameraUnsupportedResolutionError": "La tua videocamera non supporta la risoluzione richiesta.",
"Cancel": "Annulla",
@@ -233,6 +234,7 @@
"micNotSendingData": "Apri le impostazioni del computer per togliere il «muto» al microfono e imposta il volume.",
"micNotSendingDataTitle": "Il microfono è muto per impostazione di sistema",
"micPermissionDeniedError": "Non hai concesso il permesso di usare il microfono. Puoi comunque partecipare alla riunione ma gli altri non potranno sentirti. Usa il bottone a forma di telecamera nella barra degli indirizzi per cambiare impostazioni.",
"micTimeoutError": "Impossibile avviare la fonte audio. Tempo di attesa scaduto.",
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
"muteEveryoneElseDialog": "Una volta zittiti, non potrai riattivargli i microfoni, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
@@ -280,6 +282,7 @@
"sendPrivateMessageTitle": "Invio privatamente?",
"serviceUnavailable": "Servizio non disponibile",
"sessTerminated": "Chiamata terminata",
"sessionRestarted": "Chiamata riavviata automaticamente",
"Share": "Condividi",
"shareVideoLinkError": "Fornire un link youtube corretto.",
"shareVideoTitle": "Condividi un video",
@@ -734,7 +737,7 @@
},
"addPeople": "Aggiungi persone alla chiamata",
"audioOnlyOff": "Disabilita modalità per banda limitata",
"audioOnlyOn": "Abilita modalità per banda limitatao",
"audioOnlyOn": "Abilita modalità per banda limitata",
"audioRoute": "Scegli l'uscita audio",
"authenticate": "Autenticazione",
"callQuality": "Imposta qualità video",

View File

@@ -489,7 +489,7 @@
"suboptimalBrowserWarning": "あなたのミーティングの体験が、ここではあまり良好ではないのではと心配しています。これを改善する方法を模索していますが、それまでは<a href='{{recommendedBrowserPageLink}}' target='_blank'> フルサポートしているブラウザー</a>のいずれかを利用してください。",
"suboptimalExperienceTitle": "ブラウザーの警告",
"unmute": "ミュート解除",
"newDeviceCameraTitle": "新しいカメラを出しました",
"newDeviceCameraTitle": "新しいカメラを出しました",
"newDeviceAudioTitle": "新しいオーディオデバイスを検出しました",
"newDeviceAction": "使用する",
"OldElectronAPPTitle": "セキュリティ上の脆弱性があります!",

View File

@@ -233,9 +233,9 @@
"passwordRequired": "비밀번호 필수",
"popupError": "브라우저가이 사이트의 팝업 창을 차단하고 있습니다. 브라우저의 보안 설정에서 팝업을 활성화하고 다시 시도하십시오.",
"popupErrorTitle": "팝업 차단됨",
"recording": "레코딩",
"recordingDisabledForGuestTooltip": "게스트는 녹음을 시작할 수 없습니다.",
"recordingDisabledTooltip": "녹화 비활성화 되었습니다.",
"recording": "녹화",
"recordingDisabledForGuestTooltip": "게스트는 녹화를 시작할 수 없습니다.",
"recordingDisabledTooltip": "녹화 비활성화 되었습니다.",
"rejoinNow": "지금 재가입",
"remoteControlAllowedMessage": "{{user}}이(가) 원격 제어 요청을 수락했습니다",
"remoteControlDeniedMessage": "{{user}}이(가) 원격 제어 요청을 거부했습니다",
@@ -270,11 +270,11 @@
"shareYourScreenDisabled": "화면 공유가 비활성화 되었습니다.",
"shareYourScreenDisabledForGuest": "게스트는 화면을 공유 할 수 없습니다.",
"startLiveStreaming": "라이브 스트리밍 시작",
"startRecording": "레코딩 시작",
"startRecording": "녹화 시작",
"startRemoteControlErrorMessage": "원격 제어 세션을 시작하는 동안 오류가 발생했습니다",
"stopLiveStreaming": "라이브 스트리밍 종료",
"stopRecording": "레코딩 종료",
"stopRecordingWarning": "레코딩을 중단하고 싶으십니까?",
"stopRecording": "녹화 종료",
"stopRecordingWarning": "녹화를 중단하고 싶으십니까?",
"stopStreamingWarning": "라이브 스트리밍을 중단하고 싶으십니까?",
"streamKey": "라이브 스트리밍 키",
"Submit": "제출",
@@ -362,7 +362,7 @@
"focusRemote": "다른 발신자의 동영상에 포커스",
"fullScreen": "전체화면 표시 또는 종료",
"keyboardShortcuts": "키보드 단축키",
"localRecording": "로컬 녹 컨트롤 표시 또는 숨기기",
"localRecording": "로컬 녹 컨트롤 표시 또는 숨기기",
"mute": "마이크 음소거 또는 음소거 해제",
"pushToTalk": "대화 요청",
"raiseHand": "말하기 요청/해제",
@@ -429,8 +429,8 @@
"participant": "",
"participantStats": "",
"sessionToken": "",
"start": "레코딩 시작",
"stop": "레코딩 종료",
"start": "녹화 시작",
"stop": "녹화 종료",
"yes": "예"
},
"lockRoomPassword": "비밀번호",
@@ -494,26 +494,26 @@
"authDropboxText": "Dropbox에 업로드",
"availableSpace": "사용 가능한 공간 : {{spaceLeft}}MB (약 {{duration}}분 녹화)",
"beta": "베타",
"busy": "레코딩 자원을 확보하고 있습니다. 몇 분 후에 다시 시도하십시오.",
"busyTitle": "모든 레코더가 현재 사용중입니다",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedOff": "레코딩이 중지됨",
"expandedOn": "회의가 현재 녹화중입니다.",
"busy": "녹화 자원을 확보하고 있습니다. 몇 분 후에 다시 시도하십시오.",
"busyTitle": "모든 레코더가 현재 사용 중입니다",
"error": "녹화가 실패했습니다. 다시 시도하십시오.",
"expandedOff": "녹화가 중지됨",
"expandedOn": "회의가 현재 녹화 중입니다.",
"expandedPending": "녹화가 시작됩니다 ...",
"failedToStart": "레코딩을 시작하지 못했습니다",
"fileSharingdescription": "회의 참가자와 녹 공유",
"failedToStart": "녹화를 시작하지 못했습니다",
"fileSharingdescription": "회의 참가자와 녹 공유",
"live": "라이브",
"loggedIn": "{{userName}}으로 로그인했습니다.",
"off": "레코딩이 중지됨",
"on": "레코딩",
"off": "녹화가 중지됨",
"on": "녹화",
"pending": "참석할 멤버를 기다리는 중입니다…",
"rec": "녹",
"serviceDescription": "녹음은 서비스에 의해 저장됩니다.",
"serviceName": "레코딩 서비스",
"rec": "녹",
"serviceDescription": "녹화는 서비스에 의해 저장됩니다.",
"serviceName": "녹화 서비스",
"signIn": "로그인",
"signOut": "로그아웃",
"unavailable": "죄송합니다. {{serviceName}}은 현재 사용할 수 없습니다. 저희는 문제를 해결하기 위해 노력하고 있습니다. 나중에 다시 시도 해주십시오.",
"unavailableTitle": "레코딩을 사용할 수 없습니다"
"unavailableTitle": "녹화를 사용할 수 없습니다"
},
"sectionList": {
"pullToRefresh": "당겨서 새로고침"
@@ -665,8 +665,8 @@
},
"transcribing": {
"ccButtonTooltip": "자막 시작/종료",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedLabel": "현재 스크립트 작성중",
"error": "녹화가 실패했습니다. 다시 시도하십시오.",
"expandedLabel": "현재 스크립트 작성 중",
"failedToStart": "스크립트 작성을 시작하지 못했습니다.",
"labelToolTip": "회의가 기록되고 있습니다.",
"off": "스크립트 작성이 중지되었습니다.",

View File

@@ -203,6 +203,8 @@
"enterDisplayName": "Voer hier uw naam in",
"error": "Fout",
"gracefulShutdown": "Onze service is momenteel niet beschikbaar vanwege onderhoud. Probeer het later opnieuw.",
"grantModeratorDialog": "Weet u zeker dat u de moderatorrechten wilt verlenen aan deze deelnemer?",
"grantModeratorTitle": "Moderatorrechten verlenen",
"IamHost": "Ik ben de host",
"incorrectRoomLockPassword": "Onjuist wachtwoord",
"incorrectPassword": "Onjuiste gebruikersnaam of wachtwoord",
@@ -669,6 +671,7 @@
"e2ee": "Eind-tot-eind-versleuteling",
"feedback": "Feedback achterlaten",
"fullScreen": "Volledig scherm in- of uitschakelen",
"grantModerator": "Moderatorrechten verlenen",
"hangup": "Het gesprek verlaten",
"help": "Hulp",
"invite": "Personen uitnodigen",
@@ -817,6 +820,7 @@
"domute": "Dempen",
"domuteOthers": "Alle anderen dempen",
"flip": "Omdraaien",
"grantModerator": "Moderatorrechten verlenen",
"kick": "Verwijderen",
"moderator": "Moderator",
"mute": "Deelnemer is gedempt",

View File

@@ -908,6 +908,8 @@
"getHelp": "Справка",
"go": "ОК",
"goSmall": "ОК",
"headerTitle":"Сервер видеоконференцсвязи Jitsi Meet",
"headerSubtitle":"Защищенная высококачественная видеосвязь",
"info": "Инфо",
"join": "СОЗДАТЬ / ПРИСОЕДИНИТЬСЯ",
"moderatedMessage": "Или заранее <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">зарезервируйте URL-адрес встречи</a>, где вы будете единственным модератором.",
@@ -920,6 +922,7 @@
"roomname": "Укажите название комнаты",
"roomnameHint": "Укажите название комнаты или ее адрес. Можете сами создать название и передать его будущим участникам встречи, чтобы они использовали именно его.",
"sendFeedback": "Обратная связь",
"startMeeting": "Создать конференцию",
"terms": "Условия",
"title": "Защищенная, полнофункциональная и совершенно бесплатная система видеоконференций"
}

View File

@@ -60,7 +60,7 @@
"today": "Bugün"
},
"chat": {
"error": "Hata: mesajınız gönderilmedi. Gerekçe: {{error}}",
"error": "Hata: Mesajınız gönderilmedi. Neden: {{error}}",
"fieldPlaceHolder": "Mesajınızı buraya yazın",
"messagebox": "Bir mesaj yazın",
"messageTo": "{{recipient}} adlı kişiye özel mesaj",
@@ -730,7 +730,7 @@
"noisyAudioInputDesc": "Mikrofonunuz gürültü yapıyor gibi görünüyor, lütfen cihazı kapatmayı veya değiştirmeyi düşünün.",
"openChat": "Mesajlaşmayı aç",
"pip": "Resim içinde Resim moduna gir",
"privateMessage": "Özel mesajgönder",
"privateMessage": "Özel mesaj gönder",
"profile": "Profilinizi düzenleyin",
"raiseHand": "Elinizi kaldırın/indirin",
"raiseYourHand": "Elinizi kaldırın",
@@ -806,7 +806,7 @@
"domute": "Sustur",
"domuteOthers": "Diğer herkesi sustur",
"flip": "Döndür",
"kick": ıkarıldı",
"kick": ıkar",
"moderator": "Yönetici",
"mute": "Katılımcı sessiz",
"muted": "Sessiz",

View File

@@ -180,6 +180,7 @@
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
"cameraNotSendingDataTitle": "Unable to access camera",
"cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
"cameraTimeoutError": "Could not start video source. Timeout occured!",
"cameraUnknownError": "Cannot use camera for an unknown reason.",
"cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
"Cancel": "Cancel",
@@ -233,6 +234,7 @@
"micNotSendingData": "Go to your computer's settings to unmute your mic and adjust its level",
"micNotSendingDataTitle": "Your mic is muted by your system settings",
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
"micTimeoutError": "Could not start audio source. Timeout occured!",
"micUnknownError": "Cannot use microphone for an unknown reason.",
"muteEveryoneElseDialog": "Once muted, you won't be able to unmute them, but they can unmute themselves at any time.",
"muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
@@ -280,6 +282,7 @@
"sendPrivateMessageTitle": "Send privately?",
"serviceUnavailable": "Service unavailable",
"sessTerminated": "Call terminated",
"sessionRestarted": "Call restarted by the bridge",
"Share": "Share",
"shareVideoLinkError": "Please provide a correct youtube link.",
"shareVideoTitle": "Share a video",
@@ -809,7 +812,7 @@
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"edgeGrantPermissions": "Select <b><i>Yes</i></b> when your browser asks for permissions.",
"electronGrantPermissions": "Please grant permissions to use your camera and microphone",
"electronGrantPermissions": "Trying to access your camera and microphone",
"firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
"iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone",

View File

@@ -14,8 +14,9 @@ import {
} from '../../react/features/base/conference';
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { pinParticipant, getParticipantById } from '../../react/features/base/participants';
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
import { openChat } from '../../react/features/chat/actions.web';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
@@ -342,13 +343,16 @@ function initCommands() {
if (!isChatOpen) {
APP.UI.toggleChat();
}
APP.store.dispatch(setPrivateMessageRecipient(participant));
APP.store.dispatch(openChat(participant));
} else {
logger.error('No participant found for the given participantId');
}
},
'cancel-private-chat': () => {
APP.store.dispatch(setPrivateMessageRecipient());
},
'kick-participant': participantId => {
APP.store.dispatch(kickParticipant(participantId));
}
};
transport.on('event', ({ data, name }) => {
@@ -441,6 +445,23 @@ function initCommands() {
});
break;
}
case 'get-livestream-url': {
const state = APP.store.getState();
const conference = getCurrentConference(state);
let livestreamUrl;
if (conference) {
const activeSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
livestreamUrl = activeSession?.liveStreamViewURL;
} else {
logger.error('Conference is not defined');
}
callback({
livestreamUrl
});
break;
}
default:
return false;
}
@@ -556,6 +577,21 @@ class API {
}
}
/**
* Notify external application (if API is enabled) that the chat state has been updated.
*
* @param {number} unreadCount - The unread messages counter.
* @param {boolean} isOpen - True if the chat panel is open.
* @returns {void}
*/
notifyChatUpdated(unreadCount: number, isOpen: boolean) {
this._sendEvent({
name: 'chat-updated',
unreadCount,
isOpen
});
}
/**
* Notify external application (if API is enabled) that message was sent.
*

View File

@@ -35,6 +35,7 @@ const commands = {
toggleLobby: 'toggle-lobby',
hangup: 'video-hangup',
intiatePrivateChat: 'initiate-private-chat',
kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone',
password: 'password',
pinParticipant: 'pin-participant',
@@ -64,6 +65,7 @@ const events = {
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'camera-error': 'cameraError',
'chat-updated': 'chatUpdated',
'content-sharing-participants-changed': 'contentSharingParticipantsChanged',
'device-list-changed': 'deviceListChanged',
'display-name-change': 'displayNameChange',
@@ -318,7 +320,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
this._frame.allow = 'camera; microphone; display-capture';
this._frame.allow = 'camera; microphone; display-capture; autoplay;';
this._frame.src = this._url;
this._frame.name = frameName;
this._frame.id = frameName;
@@ -572,6 +574,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* logLevel: the message log level
* arguments: an array of strings that compose the actual log message
* }}
* {@code chatUpdated} - receives event notifications about chat state being
* updated. The listener will receive object with the following structure:
* {{
* 'unreadCount': unreadCounter, // the unread message(s) counter,
* 'isOpen': isOpen, // whether the chat panel is open or not
* }}
* {@code incomingMessage} - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
@@ -746,6 +754,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return getCurrentDevices(this._transport);
}
/**
* Returns the current livestream url.
*
* @returns {Promise} - Resolves with the current livestream URL if exists, with
* undefined if not and rejects on failure.
*/
getLivestreamUrl() {
return this._transport.sendRequest({
name: 'get-livestream-url'
});
}
/**
* Returns the conference participants information.
*

View File

@@ -372,7 +372,7 @@ export default class LargeVideoManager {
let widthToUse = this.preferredWidth || window.innerWidth;
const { isOpen } = APP.store.getState()['features/chat'];
if (isOpen) {
if (isOpen && window.innerWidth > 580) {
/**
* If chat state is open, we re-compute the container width
* by subtracting the default width of the chat.

3432
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,23 +15,23 @@
"author": "",
"readmeFilename": "README.md",
"dependencies": {
"@atlaskit/button": "10.1.1",
"@atlaskit/checkbox": "5.0.10",
"@atlaskit/dropdown-menu": "6.1.25",
"@atlaskit/field-text": "7.0.19",
"@atlaskit/field-text-area": "4.0.15",
"@atlaskit/flag": "13.0.0",
"@atlaskit/icon": "15.0.3",
"@atlaskit/inline-dialog": "5.3.0",
"@atlaskit/inline-message": "7.0.10",
"@atlaskit/lozenge": "6.2.4",
"@atlaskit/modal-dialog": "8.0.1",
"@atlaskit/multi-select": "11.0.13",
"@atlaskit/spinner": "9.0.13",
"@atlaskit/tabs": "8.0.11",
"@atlaskit/theme": "7.0.2",
"@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13",
"@atlaskit/button": "15.1.4",
"@atlaskit/checkbox": "12.0.0",
"@atlaskit/dropdown-menu": "10.1.2",
"@atlaskit/field-text": "11.0.4",
"@atlaskit/field-text-area": "8.0.4",
"@atlaskit/flag": "14.1.0",
"@atlaskit/icon": "21.2.0",
"@atlaskit/inline-dialog": "13.0.9",
"@atlaskit/inline-message": "11.0.8",
"@atlaskit/lozenge": "10.1.1",
"@atlaskit/modal-dialog": "11.2.4",
"@atlaskit/multi-select": "15.0.5",
"@atlaskit/spinner": "15.0.6",
"@atlaskit/tabs": "12.1.2",
"@atlaskit/theme": "11.0.2",
"@atlaskit/toggle": "12.0.3",
"@atlaskit/tooltip": "17.1.2",
"@jitsi/js-utils": "1.0.5",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.13.2",
@@ -56,7 +56,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#3570339360763fa0911d92c5ae7c270860b6934b",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
@@ -64,8 +64,8 @@
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"pixelmatch": "5.1.0",
"punycode": "2.1.1",
"react": "16.9",
"react-dom": "16.9",
"react": "16.12",
"react-dom": "16.12",
"react-emoji-render": "1.2.4",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
@@ -95,10 +95,10 @@
"redux-thunk": "2.2.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
"stackblur-canvas": "2.3.0",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "3.1.0",
"wasm-check": "2.0.1",
"windows-iana": "^3.1.0",
"xmldom": "0.1.27",
"zxcvbn": "4.4.2"

View File

@@ -29,7 +29,6 @@ import '../blur/reducer';
import '../calendar-sync/reducer';
import '../chat/reducer';
import '../deep-linking/reducer';
import '../device-selection/reducer';
import '../dropbox/reducer';
import '../dynamic-branding/reducer';
import '../etherpad/reducer';

View File

@@ -4,6 +4,7 @@ import '../authentication/reducer';
import '../mobile/audio-mode/reducer';
import '../mobile/background/reducer';
import '../mobile/call-integration/reducer';
import '../mobile/external-api/reducer';
import '../mobile/full-screen/reducer';
import '../mobile/incoming-call/reducer';
import '../mobile/watchos/reducer';

View File

@@ -62,6 +62,16 @@ export const CONFERENCE_SUBJECT_CHANGED = 'CONFERENCE_SUBJECT_CHANGED';
*/
export const CONFERENCE_TIMESTAMP_CHANGED = 'CONFERENCE_TIMESTAMP_CHANGED';
/**
* The type of (redux) action which signals that an uuid for a conference has been set.
*
* {
* type: CONFERENCE_UNIQUE_ID_SET,
* conference: JitsiConference
* }
*/
export const CONFERENCE_UNIQUE_ID_SET = 'CONFERENCE_UNIQUE_ID_SET';
/**
* The type of (redux) action which signals that a specific conference will be
* joined.

View File

@@ -36,6 +36,7 @@ import {
CONFERENCE_LEFT,
CONFERENCE_SUBJECT_CHANGED,
CONFERENCE_TIMESTAMP_CHANGED,
CONFERENCE_UNIQUE_ID_SET,
CONFERENCE_WILL_JOIN,
CONFERENCE_WILL_LEAVE,
DATA_CHANNEL_OPENED,
@@ -327,6 +328,22 @@ export function conferenceTimestampChanged(conferenceTimestamp: number) {
};
}
/**
* Signals that the unique identifier for conference has been set.
*
* @param {JitsiConference} conference - The JitsiConference instance, where the uuid has been set.
* @returns {{
* type: CONFERENCE_UNIQUE_ID_SET,
* conference: JitsiConference,
* }}
*/
export function conferenceUniqueIdSet(conference: Object) {
return {
type: CONFERENCE_UNIQUE_ID_SET,
conference
};
}
/**
* Adds any existing local tracks to a specific conference before the conference
* is joined. Then signals the intention of the application to have the local

View File

@@ -7,6 +7,7 @@ import {
createPinnedEvent,
sendAnalytics
} from '../../analytics';
import { reloadNow } from '../../app/actions';
import { openDisplayNamePrompt } from '../../display-name';
import { showErrorNotification } from '../../notifications';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
@@ -117,6 +118,7 @@ MiddlewareRegistry.register(store => next => action => {
function _conferenceFailed({ dispatch, getState }, next, action) {
const result = next(action);
const { conference, error } = action;
const { enableForcedReload } = getState()['features/base/config'];
// Handle specific failure reasons.
switch (error.name) {
@@ -130,6 +132,16 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
break;
}
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
if (enableForcedReload) {
dispatch(showErrorNotification({
description: 'Restart initiated because of a bridge failure',
titleKey: 'dialog.sessionRestarted'
}));
}
break;
}
case JitsiConferenceErrors.CONNECTION_ERROR: {
const [ msg ] = error.params;
@@ -147,26 +159,26 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
break;
}
// FIXME: Workaround for the web version. Currently, the creation of the
// conference is handled by /conference.js and appropriate failure handlers
// are set there.
if (typeof APP !== 'undefined') {
if (typeof beforeUnloadHandler !== 'undefined') {
window.removeEventListener('beforeunload', beforeUnloadHandler);
beforeUnloadHandler = undefined;
}
return result;
}
// XXX After next(action), it is clear whether the error is recoverable.
!error.recoverable
if (typeof APP === 'undefined') {
!error.recoverable
&& conference
&& conference.leave().catch(reason => {
// Even though we don't care too much about the failure, it may be
// good to know that it happen, so log it (on the info level).
logger.info('JitsiConference.leave() rejected with:', reason);
});
} else if (typeof beforeUnloadHandler !== 'undefined') {
// FIXME: Workaround for the web version. Currently, the creation of the
// conference is handled by /conference.js and appropriate failure handlers
// are set there.
window.removeEventListener('beforeunload', beforeUnloadHandler);
beforeUnloadHandler = undefined;
}
if (enableForcedReload && error?.name === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
dispatch(conferenceWillLeave(conference));
dispatch(reloadNow());
}
return result;
}

View File

@@ -1,15 +1,35 @@
// @flow
import UIEvents from '../../../../service/UI/UIEvents';
import { setPrejoinPageVisibility, setSkipPrejoinOnReload } from '../../prejoin';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes';
import './middleware.any';
declare var APP: Object;
MiddlewareRegistry.register((/* store */) => next => action => {
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { enableForcedReload } = getState()['features/base/config'];
switch (action.type) {
case CONFERENCE_JOINED: {
if (enableForcedReload) {
dispatch(setPrejoinPageVisibility(false));
dispatch(setSkipPrejoinOnReload(false));
}
break;
}
case CONFERENCE_FAILED: {
enableForcedReload
&& action.error?.name === JitsiConferenceErrors.CONFERENCE_RESTARTED
&& dispatch(setSkipPrejoinOnReload(true));
break;
}
case TOGGLE_SCREENSHARING: {
if (typeof APP === 'object') {
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);

View File

@@ -89,6 +89,7 @@ export default [
'disableProfile',
'disableRemoteControl',
'disableRemoteMute',
'disableResponsiveTiles',
'disableRtx',
'disableShortcuts',
'disableSimulcast',
@@ -96,6 +97,7 @@ export default [
'disableTileView',
'displayJids',
'doNotStoreRoom',
'dropbox',
'e2eping',
'enableDisplayNameInStats',
'enableEmailInStats',

View File

@@ -84,3 +84,13 @@ export const REMOVE_PENDING_DEVICE_REQUESTS = 'REMOVE_PENDING_DEVICE_REQUESTS';
* }
*/
export const CHECK_AND_NOTIFY_FOR_NEW_DEVICE = 'CHECK_AND_NOTIFY_FOR_NEW_DEVICE';
/**
* The type of Redux action which signals that the device permissions have changed.
*
* {
* type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE
* permissions: Object
* }
*/
export const DEVICE_PERMISSIONS_CHANGED = 'DEVICE_PERMISSIONS_CHANGED';

View File

@@ -7,6 +7,7 @@ import {
import {
ADD_PENDING_DEVICE_REQUEST,
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
DEVICE_PERMISSIONS_CHANGED,
NOTIFY_CAMERA_ERROR,
NOTIFY_MIC_ERROR,
REMOVE_PENDING_DEVICE_REQUESTS,
@@ -320,3 +321,19 @@ export function checkAndNotifyForNewDevice(newDevices, oldDevices) {
oldDevices
};
}
/**
* Signals that the device permissions have changed.
*
* @param {Object} permissions - Object with the permissions.
* @returns {{
* type: DEVICE_PERMISSIONS_CHANGED,
* permissions: Object
* }}
*/
export function devicePermissionsChanged(permissions) {
return {
type: DEVICE_PERMISSIONS_CHANGED,
permissions
};
}

View File

@@ -5,7 +5,8 @@ import { processExternalDeviceRequest } from '../../device-selection';
import { showNotification, showWarningNotification } from '../../notifications';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { JitsiTrackErrors } from '../lib-jitsi-meet';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { updateSettings } from '../settings';
@@ -18,6 +19,7 @@ import {
UPDATE_DEVICE_LIST
} from './actionTypes';
import {
devicePermissionsChanged,
removePendingDeviceRequests,
setAudioInputDevice,
setVideoInputDevice
@@ -35,17 +37,25 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
[JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
[JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError'
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError',
[JitsiTrackErrors.TIMEOUT]: 'dialog.micTimeoutError'
},
camera: {
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
[JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
[JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError'
[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError',
[JitsiTrackErrors.TIMEOUT]: 'dialog.cameraTimeoutError'
}
};
/**
* A listener for device permissions changed reported from lib-jitsi-meet.
*/
let permissionsListener;
/**
* Logs the current device list.
*
@@ -73,6 +83,36 @@ function logDeviceList(deviceList) {
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT: {
const _permissionsListener = permissions => {
store.dispatch(devicePermissionsChanged(permissions));
};
const { mediaDevices } = JitsiMeetJS;
permissionsListener = _permissionsListener;
mediaDevices.addEventListener(JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, permissionsListener);
Promise.all([
mediaDevices.isDevicePermissionGranted('audio'),
mediaDevices.isDevicePermissionGranted('video')
])
.then(results => {
_permissionsListener({
audio: results[0],
video: results[1]
});
})
.catch(() => {
// Ignore errors.
});
break;
}
case APP_WILL_UNMOUNT:
if (typeof permissionsListener === 'function') {
JitsiMeetJS.mediaDevices.removeEventListener(
JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, permissionsListener);
permissionsListener = undefined;
}
break;
case NOTIFY_CAMERA_ERROR: {
if (typeof APP !== 'object' || !action.error) {
break;

View File

@@ -2,6 +2,7 @@ import { ReducerRegistry } from '../redux';
import {
ADD_PENDING_DEVICE_REQUEST,
DEVICE_PERMISSIONS_CHANGED,
REMOVE_PENDING_DEVICE_REQUESTS,
SET_AUDIO_INPUT_DEVICE,
SET_VIDEO_INPUT_DEVICE,
@@ -16,7 +17,11 @@ const DEFAULT_STATE = {
audioOutput: [],
videoInput: []
},
pendingRequests: []
pendingRequests: [],
permissions: {
audio: false,
video: false
}
};
/**
@@ -68,6 +73,12 @@ ReducerRegistry.register(
return state;
}
case DEVICE_PERMISSIONS_CHANGED: {
return {
...state,
permissions: action.permissions
};
}
default:
return state;
}

View File

@@ -0,0 +1,90 @@
// @flow
/* eslint-disable react/no-multi-comp */
import ErrorIcon from '@atlaskit/icon/glyph/error';
import WarningIcon from '@atlaskit/icon/glyph/warning';
import {
Header,
Title,
titleIconWrapperStyles,
TitleText
} from '@atlaskit/modal-dialog/dist/es2019/styled/Content';
import React from 'react';
import { Icon, IconClose } from '../../../icons';
const TitleIcon = ({ appearance }: { appearance?: 'danger' | 'warning' }) => {
if (!appearance) {
return null;
}
const IconSymbol = appearance === 'danger' ? ErrorIcon : WarningIcon;
return (
<span css = { titleIconWrapperStyles(appearance) }>
<IconSymbol label = { `${appearance} icon` } />
</span>
);
};
type Props = {
id: string,
appearance?: 'danger' | 'warning',
heading: string,
onClose: Function,
showKeyline: boolean,
isHeadingMultiline: boolean,
testId: string,
t: Function
}
/**
* A default header for modal-dialog components
*
* @export
* @class ModalHeader
* @extends {React.Component<Props>}
*/
export default class ModalHeader extends React.Component<Props> {
static defaultProps = {
isHeadingMultiline: true
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
id,
appearance,
heading,
onClose,
showKeyline,
isHeadingMultiline,
testId
} = this.props;
if (!heading) {
return null;
}
return (
<Header showKeyline = { showKeyline }>
<Title>
<TitleIcon appearance = { appearance } />
<TitleText
data-testid = { testId && `${testId}-heading` }
id = { id }
isHeadingMultiline = { isHeadingMultiline }>
{heading}
</TitleText>
</Title>
<Icon
onClick = { onClose }
src = { IconClose } />
</Header>
);
}
}

View File

@@ -1,6 +1,7 @@
// @flow
import Button, { ButtonGroup } from '@atlaskit/button';
import ButtonGroup from '@atlaskit/button/button-group';
import Button from '@atlaskit/button/standard-button';
import Modal, { ModalFooter } from '@atlaskit/modal-dialog';
import _ from 'lodash';
import React, { Component } from 'react';
@@ -8,6 +9,8 @@ import React, { Component } from 'react';
import { translate } from '../../../i18n/functions';
import type { DialogProps } from '../../constants';
import ModalHeader from './ModalHeader';
/**
* The ID to be used for the cancel button if enabled.
* @type {string}
@@ -127,10 +130,13 @@ class StatelessDialog extends Component<Props> {
<Modal
autoFocus = { true }
components = {{
Header: customHeader
Header: customHeader ? customHeader : props => (
<ModalHeader
{ ...props }
heading = { titleString || t(titleKey) } />
)
}}
footer = { this._renderFooter }
heading = { customHeader ? undefined : titleString || t(titleKey) }
i18n = { this.props.i18n }
onClose = { this._onDialogDismissed }
onDialogDismissed = { this._onDialogDismissed }

View File

@@ -0,0 +1,25 @@
import {
Dialog,
FillScreen,
dialogWidth,
dialogHeight,
PositionerAbsolute,
PositionerRelative
} from '@atlaskit/modal-dialog/dist/es2019/styled/Modal.js';
import { N0, DN50 } from '@atlaskit/theme/colors';
import { themed } from '@atlaskit/theme/components';
import React from 'react';
const ThemedDialog = props => {
const style = { backgroundColor: props.isChromeless ? 'transparent' : themed({ light: N0,
dark: DN50 })({ theme: { mode: 'dark' } }) };
return (<Dialog
{ ...props }
aria-modal = { true }
style = { style }
theme = {{ mode: 'dark' }} />);
};
export { ThemedDialog as Dialog, FillScreen, dialogWidth, dialogHeight, PositionerAbsolute, PositionerRelative };

View File

@@ -6,6 +6,19 @@
*/
export const ADD_PEOPLE_ENABLED = 'add-people.enabled';
/**
* Flag indicating if the SDK should not require the audio focus.
* Used by apps that do not use Jitsi audio.
* Default: disabled (false)
*/
export const ANDROID_AUDIO_FOCUS_DISABLED = 'android.audio-focus.disabled';
/**
* Flag indicating if the audio mute button should be displayed.
* Default: enabled (true).
*/
export const AUDIO_MUTE_BUTTON_ENABLED = 'audio-mute.enabled';
/**
* Flag indicating if calendar integration should be enabled.
* Default: enabled (true) on Android, auto-detected on iOS.
@@ -43,6 +56,12 @@ export const CHAT_ENABLED = 'chat.enabled';
*/
export const FILMSTRIP_ENABLED = 'filmstrip.enabled';
/**
* Flag indicating if the Help button should be enabled.
* Default: enabled (true).
*/
export const HELP_BUTTON_ENABLED = 'help.enabled';
/**
* Flag indicating if invite functionality should be enabled.
* Default: enabled (true).
@@ -81,13 +100,18 @@ export const MEETING_NAME_ENABLED = 'meeting-name.enabled';
*/
export const MEETING_PASSWORD_ENABLED = 'meeting-password.enabled';
/**
* Flag indicating if the notifications should be enabled.
* Default: enabled (true).
*/
export const NOTIFICATIONS_ENABLED = 'notifications.enabled';
/**
* Flag indicating if the audio overflow menu button should be displayed.
* Default: enabled (true).
*/
export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
/**
* Flag indicating if Picture-in-Picture should be enabled.
* Default: auto-detected.
@@ -137,6 +161,12 @@ export const TOOLBOX_ALWAYS_VISIBLE = 'toolbox.alwaysVisible';
*/
export const TOOLBOX_ENABLED = 'toolbox.enabled';
/**
* Flag indicating if the video mute button should be displayed.
* Default: enabled (true).
*/
export const VIDEO_MUTE_BUTTON_ENABLED = 'video-mute.enabled';
/**
* Flag indicating if the video share button should be enabled
* Default: enabled (true).

View File

@@ -0,0 +1,3 @@
<svg width="29" height="12" viewBox="0 0 29 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 2L14.2616 10L26.5 2" stroke="#929292" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -0,0 +1,3 @@
<svg width="29" height="12" viewBox="0 0 29 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 10L14.2616 2L26.5 10" stroke="#929292" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

View File

@@ -6,8 +6,10 @@ export { default as IconArrowBack } from './arrow_back.svg';
export { default as IconArrowDown } from './arrow_down.svg';
export { default as IconArrowDownLarge } from './arrow_down_large.svg';
export { default as IconArrowDownSmall } from './arrow-down-small.svg';
export { default as IconArrowDownWide } from './icon-arrow-down-wide.svg';
export { default as IconArrowUp } from './arrow_up.svg';
export { default as IconArrowUpLarge } from './arrow_up_large.svg';
export { default as IconArrowUpWide } from './icon-arrow-up-wide.svg';
export { default as IconArrowLeft } from './arrow-left.svg';
export { default as IconAudioOnly } from './visibility.svg';
export { default as IconAudioOnlyOff } from './visibility-off.svg';

View File

@@ -3,7 +3,7 @@
import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
import { SCREEN_SHARE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-layout/actionTypes';
import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-layout/actionTypes';
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
import { CONFERENCE_JOINED } from '../conference/actionTypes';
import {
@@ -33,7 +33,7 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_JOINED:
case PARTICIPANT_KICKED:
case PARTICIPANT_LEFT:
case SCREEN_SHARE_PARTICIPANTS_UPDATED:
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case SELECT_LARGE_VIDEO_PARTICIPANT:
case SET_AUDIO_ONLY:
case SET_FILMSTRIP_ENABLED:
@@ -80,7 +80,7 @@ function _updateLastN({ getState }) {
if (typeof appState !== 'undefined' && appState !== 'active') {
lastN = isLocalVideoTrackDesktop(state) ? 1 : 0;
} else if (audioOnly) {
const { screenShares, tileViewEnabled } = state['features/video-layout'];
const { remoteScreenShares, tileViewEnabled } = state['features/video-layout'];
const largeVideoParticipantId = state['features/large-video'].participantId;
const largeVideoParticipant
= largeVideoParticipantId ? getParticipantById(state, largeVideoParticipantId) : undefined;
@@ -89,7 +89,7 @@ function _updateLastN({ getState }) {
// view since we make an exception only for screenshare when in audio-only mode. If the user unpins
// the screenshare, lastN will be set to 0 here. It will be set to 1 if screenshare has been auto pinned.
if (!tileViewEnabled && largeVideoParticipant && !largeVideoParticipant.local) {
lastN = (screenShares || []).includes(largeVideoParticipantId) ? 1 : 0;
lastN = (remoteScreenShares || []).includes(largeVideoParticipantId) ? 1 : 0;
} else {
lastN = 0;
}

View File

@@ -13,9 +13,10 @@ const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
* @param {string} type - The media type of track being created. Expected values
* are "video" or "audio".
* @param {string} deviceId - The id of the target media source.
* @param {number} [timeout] - A timeout for the JitsiMeetJS.createLocalTracks function call.
* @returns {Promise<JitsiLocalTrack>}
*/
export function createLocalTrack(type: string, deviceId: string) {
export function createLocalTrack(type: string, deviceId: string, timeout: ?number) {
return (
JitsiMeetJS.createLocalTracks({
cameraDeviceId: deviceId,
@@ -24,7 +25,8 @@ export function createLocalTrack(type: string, deviceId: string) {
// eslint-disable-next-line camelcase
firefox_fake_device:
window.config && window.config.firefox_fake_device,
micDeviceId: deviceId
micDeviceId: deviceId,
timeout
})
.then(([ jitsiLocalTrack ]) => jitsiLocalTrack));
}

View File

@@ -79,10 +79,10 @@ export function getStartWithAudioMuted(stateful: Object | Function) {
}
/**
* Computes the startWithAudioMuted by retrieving its values from config, URL and settings.
* Computes the startWithVideoMuted by retrieving its values from config, URL and settings.
*
* @param {Object|Function} stateful - The redux state object or {@code getState} function.
* @returns {boolean} - The computed startWithAudioMuted value that will be used.
* @returns {boolean} - The computed startWithVideoMuted value that will be used.
*/
export function getStartWithVideoMuted(stateful: Object | Function) {
return Boolean(getPropertyValue(stateful, 'startWithVideoMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES));

View File

@@ -360,10 +360,10 @@ export function shouldRenderParticipantVideo(stateful: Object | Function, id: st
}
/* Last, check if the participant is sharing their screen and they are on stage. */
const screenShares = state['features/video-layout'].screenShares || [];
const remoteScreenShares = state['features/video-layout'].remoteScreenShares || [];
const largeVideoParticipantId = state['features/large-video'].participantId;
const participantIsInLargeVideoWithScreen
= participant.id === largeVideoParticipantId && screenShares.includes(participant.id);
= participant.id === largeVideoParticipantId && remoteScreenShares.includes(participant.id);
return participantIsInLargeVideoWithScreen;
}

View File

@@ -15,16 +15,13 @@ export function preloadImage(src: string | Object): Promise<string> {
}
return new Promise((resolve, reject) => {
fetch(src, { referrer: '' })
.then(response => {
if (response.ok) {
resolve(src);
} else {
reject();
}
})
.catch(e => {
reject(e);
});
const image = document.createElement('img');
image.onload = () => resolve(src);
image.onerror = reject;
// $FlowExpectedError
image.referrerPolicy = 'no-referrer';
image.src = src;
});
}

View File

@@ -28,7 +28,7 @@ const DIALOG_TO_PADDING_POSITION = {
* @returns {string}
*/
function _mapPositionToPaddingClass(position = 'left') {
return DIALOG_TO_PADDING_POSITION[position.split(' ')[0]];
return DIALOG_TO_PADDING_POSITION[position.split('-')[0]];
}
/**
@@ -212,7 +212,7 @@ class Popover extends Component<Props, State> {
<InlineDialog
content = { this._renderContent() }
isOpen = { this.state.showDialog }
position = { position }>
placement = { position }>
{ children }
</InlineDialog>
</div>

View File

@@ -1,11 +1,11 @@
/* @flow */
const { userAgent } = navigator;
const { userAgent, maxTouchPoints, platform } = navigator;
let OS;
if (userAgent.match(/Android/i)) {
OS = 'android';
} else if (userAgent.match(/iP(ad|hone|od)/i)) {
} else if (userAgent.match(/iP(ad|hone|od)/i) || (maxTouchPoints && maxTouchPoints > 2 && /MacIntel/.test(platform))) {
OS = 'ios';
} else if (userAgent.match(/Mac(intosh| OS X)/i)) {
OS = 'macos';

View File

@@ -1,10 +1,10 @@
/* @flow */
import Tooltip from '@atlaskit/tooltip';
import React, { Component } from 'react';
import { translate } from '../../../i18n';
import { Icon } from '../../../icons';
import { Tooltip } from '../../../tooltip';
/**
* The type of the React {@code Component} props of {@link BaseIndicator}.

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