Compare commits

...

118 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
8eed42c273 fix(virtual-backgrounds) add segmentation model license information
Fixes: https://github.com/jitsi/jitsi-meet/issues/8792
2021-04-07 17:15:17 +02:00
tmoldovan8x8
e803e8cfd9 feat(ios): adds ios screensharing enabled flag 2021-04-07 16:28:26 +03:00
tudordan7
e5277deed5 chore(deps) lib-jitsi-meet@latest
* fix(rtc) Fix setting effects while not in a conference.

3cd9d31b97...cd53f249c5
2021-04-07 13:12:36 +02:00
Tudor D. Pop
8b315846b9 feat(premeeting-screen) add virtual background functionality 2021-04-07 11:29:54 +02:00
Jaya Allamsetty
c687f41a89 chore(deps) lib-jitsi-meet@latest
* feat(RTC): Signal video type and availability to bridge.

dddbab99f1...3cd9d31b97
2021-04-06 17:46:04 -04:00
Jonathan Lennox
31c0ba4481 Load-test: emulate jitsi-meet stage view behavior, if selected. (#8957) 2021-04-06 16:31:26 -04:00
Calinteodor
fc3a743372 fix(ios) keyboard no longer covers message board and input 2021-04-06 12:07:24 +02:00
damencho
8b038716a5 chore(deps) lib-jitsi-meet@latest
* fix: Fixes error for undefined error, on happening on p2p kick.

2e598a4bda...dddbab99f1
2021-04-05 16:49:58 -05:00
Jonathan Lennox
9662b2ae67 Load test: send video constraints only after ICE is connected. (#8952) 2021-04-05 17:17:25 -04:00
Jonathan Lennox
6275439a91 Load-test: Fix getId call. (#8941) 2021-04-05 12:03:54 -04:00
Vlad Piersec
d9693117f2 fix(Toolbar, rn): Button overflow in landscape orientation 2021-04-05 13:54:44 +03:00
Jaya Allamsetty
21382ea6d5 chore(deps) lib-jitsi-meet@latest
* Get rid of stats debug message, fix typo with codec type.
* fix(receiveVideoController): Do a deep copy of constraints for comparsion.
* fix(codec-selection): Fix codec selection for unified plan browsers.

93af5ada95...2e598a4bda
2021-04-02 16:18:44 -04:00
JohnProv
6df67694d1 Update main-nl.json (#8938)
Remove keys in main-nl but not in main.
2021-04-02 12:01:32 -05:00
JohnProv
08756bc6d0 Update main-nl.json (#8937)
* Update main-nl.json

Add some translated keys.

* Update main-nl.json

Fix

* Update main-nl.json

Fix typo

* Update main-nl.json

Fix
2021-04-02 11:25:23 -05:00
Mihai-Andrei Uscat
1b1d650b75 fix(MoreTab): Fix languages not being scrollable on mobile 2021-04-02 13:38:02 +03:00
Jaya Allamsetty
b1eff72394 chore(deps) lib-jitsi-meet@latest
* fix(receiveVideoController): Do not send redundant video constraints to the bridge.
* feat(stats): Add a new bridge message "EndpointStats" for stats. Use the new Colibri message "EndpointStats" for broadcasting the local stats. The bridge then will be able to filter the endpoint stats and send them only to the interested parties instead of broadcasting it to all the endpoints in the call.
* Test RTCRtpReceiver.getCapabilities before using

2b94da12e8...93af5ada95
2021-04-01 10:44:22 -04:00
Jonathan Lennox
357bbd1158 Load test: emulate Jitsi-Meet's lastN and selectParticipant behavior. (#8926) 2021-04-01 10:30:23 -04:00
Arnaud (Martient) Leherpeur
0ca47e9ffb fix (lang): update french and canadian french i18n
change "cryptage" to "chiffrement"
2021-04-01 08:29:12 -05:00
Дамян Минков
1123b4f2fe fix: Adds Portuguese to listed languages 2021-04-01 08:27:49 -05:00
tmoldovan8x8
1224597ede feat(e2ee): auto turns on e2ee when one participant enabled it 2021-04-01 12:34:01 +03:00
Avram Tudor
58b7663a97 Merge pull request #8866 from jitsi/tavram/sip-invite
feat(sipcall) implement sip invite
2021-04-01 11:39:31 +03:00
Christoph Settgast
cf8ab5e13b fix(lang) Differentiate prejoin and lobby better in German translation
Signed-off-by: Christoph Settgast <csett86@web.de>
2021-03-31 15:46:05 -05:00
Tudor-Ovidiu Avram
f99c919416 code review changes 2021-03-31 15:51:53 +03:00
Tudor-Ovidiu Avram
ae21a09bd6 feat(sipcall) implement sip invite 2021-03-31 09:53:55 +03:00
Tudor D. Pop
39011d8fd3 feat(virtual-background) persist settings 2021-03-30 23:27:44 +02:00
Johnny998
77f1a24344 Update main-sk.json
Translated a few missing strings.
2021-03-30 08:41:32 -05:00
tudordan7
3453e49182 fix(virtual-background): Hide scrollbar on loading action. 2021-03-30 13:43:57 +02:00
tmoldovan8x8
b1d7debfb9 feat(e2ee): adds sounds for e2ee enabling/disabling 2021-03-30 12:59:32 +03:00
Дамян Минков
b826fc1d5a fix: Correct some missing comas in config.js. 2021-03-30 08:51:43 +02:00
Jaya Allamsetty
c5626e99e9 chore(deps) lib-jitsi-meet@latest
* feat(stats): Get audio levels for the top 5 speakers only.

1249681a0e...43c589f409
2021-03-29 17:21:46 -04:00
Christoph Wiechert
ae28fcc12f Fix: used deprecated onmousewheel event
https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event
2021-03-29 10:53:13 -05:00
chipechop
987760abbd Update main-it.json (#8795)
* Update main-it.json

Added roughly 20 missinig lines

* Update main-it.json

Fixed two typos, left behind...
2021-03-29 10:53:02 -05:00
KyungheeKo
a3b364f8d7 lang: Update korean translation (#8879)
* Update main-ko.json

update korean translation

* Update main-ko.json

fix comma error

* Update languages-ko.json

add korean translation
2021-03-29 10:52:50 -05:00
JohnProv
2ea317d721 Update main-nl.json (#8891)
* Update main-nl.json

Add missing keys for virtualBackground

* Update main-nl.json
2021-03-29 10:52:22 -05:00
Vlad Piersec
eb41a306a6 fix(lobby): Knocking participants list for small widths 2021-03-29 09:47:11 -05:00
Vlad Piersec
3426290bf2 fix(captions): Lift captions upper when invite box is shown & fix icon 2021-03-29 09:09:21 -05:00
Tudor D. Pop
dfd33521bf fix(virtual-background): Fixes upload virtual background on Firefox
Fixes: #8892
2021-03-29 14:28:22 +02:00
Hristo Terezov
be3bc75403 chore(deps) lib-jitsi-meet@latest
* fix(caps): features update event is not emitted.

0e180efdfa...1249681a0e
2021-03-26 17:43:54 -05:00
Jaya Allamsetty
4621fad832 fix(large-video): Always pin screenshare to large-video if it exists.
Set higher preference for screenshare over dominant speaker when trying to elect a participant for large-video. This prevents the dominant speaker from taking over the stage when a user toggles tile view on and off while a screenshare is in progress.
2021-03-26 09:14:03 -04:00
tmoldovan8x8
e4b34e1c89 feat(rn): makes InputDialog textInput autoFocus 2021-03-26 10:51:47 +02:00
damencho
0067f6b077 fix: Fixes lobby when allowners is enabled. 2021-03-25 15:20:49 -06:00
JohnProv
989044b3a9 fix(lang) update Dutch translation 2021-03-25 17:43:52 +01:00
Mihai-Andrei Uscat
a78ca5fcad feat(external_api): Add command for toggling localFlipX 2021-03-25 14:57:41 +02:00
Mihai-Andrei Uscat
1ad40de487 feat(external_api): Add command for toggling camera on mobile web 2021-03-25 13:48:49 +02:00
ggalperi
2c9078985f fix(lang) fix typo in Russian translation
Fixed typo
2021-03-24 15:59:45 -06:00
Tudor D. Pop
77ee4b13e1 feat(virtual-backgrounds) add ability to upload custom images 2021-03-24 17:32:45 +01:00
Jaya Allamsetty
a3a2ce3875 feat(rn,polyfill): Add a polyfill for Promise.allSettled.
Promise.allSettled is supported from RN 0.63 onwards and is not supported on the current version, use a polyfill for that shims Promise.allSettled if its unavailable or noncompliant.

Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2021-03-24 11:59:52 -04:00
Saúl Ibarra Corretgé
e0c77dcd95 feat(tile-view) allow to toggle tile view while alone 2021-03-24 16:43:50 +01:00
Calinteodor
e035d33fa9 feat(authentication) refactor auth dialogs to use React 2021-03-24 15:09:40 +01:00
Kylian Kropf
11202595bd fix(lang) update Dutch translation 2021-03-24 11:16:32 +01:00
Jaya Allamsetty
415670e24b chore(deps) lib-jitsi-meet@latest
* fix(TPC): get ssrc info per ssrc and not per mline.
* feat: Consider absence of A/V muted from presence as muted.
* Feature: Moderator can revoke moderator role to others and himself (#1532)

4191198233...0e180efdfa
2021-03-23 18:11:23 -04:00
Izak Glasenčnik
05f3b4390d feat(iFrame): Emit event when recording status changes, including errors (#7973)
* feat(iFrame): Emit event when recording status changes, including errors

* Fix APP access on mobile
2021-03-23 11:35:46 -05:00
Saúl Ibarra Corretgé
cff0a619f5 fix(interfaceConfig) mark as deprecated 2021-03-23 16:59:46 +01:00
hmuresan
f7c0d4f1fe feat(background alpha) Set background transparency 2021-03-23 16:16:56 +02:00
TigiBoom
8fccb05519 fix(lang) fix typo in Russian translation 2021-03-23 14:47:25 +01:00
Vlad Piersec
b4155ab6d2 fix(toolbox): Add missing lang key for video settings 2021-03-23 15:38:15 +02:00
tmoldovan8x8
a1d3870634 feat(external_api): add videoMuted event and action (#8862) 2021-03-23 15:30:17 +02:00
hmuresan
07f16a7a51 feat (external-api) Add command for setting tile view mode 2021-03-23 15:21:57 +02:00
Vlad Piersec
0e7bde2ff0 fix(overflow-menu): Don't change state on hover for disabled items 2021-03-23 14:30:52 +02:00
Vlad Piersec
e9d00acad8 fix(menu): Pop menu icons & background 2021-03-23 14:18:22 +02:00
Mihai-Andrei Uscat
911aaed052 fix: Refactor client width computation.
* Unify chat open/close size changes and move them to redux.
* Fix responsive columns not accounting for chat.
2021-03-23 14:06:43 +02:00
Vlad Piersec
5fd9dc74e4 fix(welcome): Align meeting list at the top when no footer 2021-03-23 14:03:54 +02:00
Vlad Piersec
eb68467e15 fix(rn, toolbox): Change button appearing order 2021-03-23 10:14:54 +02:00
Jaya Allamsetty
6a5d6afc94 chore(deps) lib-jitsi-meet@latest
* fix(JingleSession): Avoid renegotiation when user with no sources leaves the call.
* feat: participant kick reason add
* ref(RTC): remove legacy pc constraints. Stop using the legacy pc constraints that are no longer wired up to WebRTC.
* fix(deps) update webrtc-adapter to v7.7.1

087a8e19eb...4191198233
2021-03-22 19:25:02 -04:00
Дамян Минков
2a9b6a7d28 fix(load-test): Fixes unmuting loadtest client. (#8849)
* fix(load-test): Fixes unmuting loadtest client.

Fixes the case where audio track was not added due to jicofo muting clients.

* squash(load-test): Drop noAutoLocalAudio and change add track logic.

Trying to mimic jitsi-meet.

* squash(load-test): Fix adding video.
2021-03-22 14:39:57 -05:00
adam j hartz
67beafc9af add option for disabling join/leave sounds (#8596)
* add option for disabling join/leave sounds

* document disableJoinLeaveSounds and add it to whitelist
2021-03-22 11:28:34 -05:00
BenjaminVega
6175a5cad5 Be able to toggle the raise-hand via external-api (#8838)
* Be able to toggle the raise-hand via external_api

* Code Review inputs
2021-03-22 11:22:45 -05:00
Vlad Piersec
678f3e232b fix(toolbar): Re-add "mute everyone's video" button 2021-03-22 15:25:30 +02:00
Jake Breen
f3c1b8ac08 fix(android) apply flags when launching activity from non-activity context
Check whether context is that of an Activity before launching the Jitsi Conference Activity. If context is not an activity context, apply flag FLAG_ACTIVITY_NEW_TASK to the Jitsi Activity Intent to ensure activity can launch without error.

This scenario would manifest when a user attempts to launch the Jitsi Actvity from a Widget... for example.

https://developer.android.com/about/versions/pie/android-9.0-changes-all#fant-required
2021-03-22 12:59:43 +01:00
John Wu
f225ce886f fix(chore) fix typo 2021-03-22 11:21:48 +01:00
dependabot[bot]
6a4417c6cc chore(deps): bump ini from 1.3.5 to 1.3.7
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 11:03:23 +01:00
Дамян Минков
6b66c8dd20 fix(config, docs) document feedbackPercentage 2021-03-22 10:56:26 +01:00
luz paz
d3680bbebd fix(misc) follow-up typos
Found via `codespell -q 3 -S ./lang`
2021-03-22 10:41:41 +01:00
dependabot[bot]
7933d4b4d6 chore(deps): bump xmldom from 0.1.27 to 0.5.0
Bumps [xmldom](https://github.com/xmldom/xmldom) from 0.1.27 to 0.5.0.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...0.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 10:36:43 +01:00
Vlad Piersec
e7297714c6 feat(toolbox): Adaptive toolbar on mobile 2021-03-22 11:26:00 +02:00
Saúl Ibarra Corretgé
8da154b185 fix(android) remove leftover package 2021-03-19 12:58:47 +01:00
Saúl Ibarra Corretgé
3c94a5ccfd feat(rn,ui) update in-meeting colors 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
78d4af6bf2 feat(rn,conference) new UI for conference name duration 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
33fc3833f9 fix(rn,labels) don't add extra margin in tile view
There is no need to skip the filmstrip, since it's not there.
2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
b179542c39 fix(rn,labels) top-align with room name field 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
49c38a73aa fix(filmstrip) make sure it's not rendered outside of a safe area 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
fc27300132 fix(rn,filmstrip) simplify thumbnail height calculations 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
57ecdff9eb fix(rn,conference) remove no longer needed margin
We are using a safe area view now.
2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
effa878fa4 fix(rn,filmstrip) simplify visibility calculation 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
9d4e49a5af fix(rn,toolbox) fill gap underneath Toolbox
This is for devices without the home button.
2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
6b4d25c0d3 fix(rn,ui) move top labels to navbar component 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
2f5ab2757f feat(rn,ui) get rid of the gradients 2021-03-19 11:32:00 +01:00
Saúl Ibarra Corretgé
bde26c4fbb fix(icons) never specify fill 2021-03-19 11:32:00 +01:00
trippledave
68c2c9be40 feat(flags) add feature flag for audio-only button 2021-03-19 08:17:37 +01:00
Jaya Allamsetty
5b21051c6b fix(startMuted): Fix unmute on mobile when it is muted by Jicofo on join. 2021-03-18 15:23:54 -04:00
hmuresan
8806269af0 * chore(deps) lib-jitsi-meet@latest
5796d83bb1...087a8e19eb
2021-03-18 19:34:44 +01:00
hmuresan
3a8bd852b2 feat(jwt) log jwt validation errors 2021-03-18 16:58:54 +02:00
Hristo Terezov
f50872285d ref(Filmstrip): Use Thumbnail component. 2021-03-18 09:37:55 -05:00
Jaya Allamsetty
e937e99284 chore(deps) lib-jitsi-meet@latest
* squash: Use different function syntax.
* squash: Fix lint errors.
* Process stats immediately before setting the interval.
* feat(ReceiveVideoController): Add the ability to send constraints in the new format. Add the ability to send the bridge messages for the receiver video constraints in the new format directly.

676c7a9105...5796d83bb1
2021-03-18 10:25:29 -04:00
Mihai-Andrei Uscat
3972e076f0 fix(Chat): Fix modals displaying improperly due to chat.
* Adjust chat font size.
* Adjust invite more button and text size.
* Remove useless constant.
2021-03-18 15:56:20 +02:00
Jonathan Lennox
81cf79e643 In loadtest, make localAudio non-const. (#8829)
(Because we modify it.)
2021-03-18 09:39:13 -04:00
Mihai-Andrei Uscat
a22d054b10 feat(InviteMore): Relocate invite prompt for mobile friendliness. 2021-03-18 14:09:22 +02:00
Vlad Piersec
7fce181080 feat(config): Add config option to allow unsetting local video flip 2021-03-18 09:35:42 +02:00
Vlad Piersec
92735478d1 fix(toolbox): Fix overflow menu & button background 2021-03-18 09:16:43 +02:00
Mihai-Andrei Uscat
7dabfc21b4 feat(Chat): Revamp design.
* ensure keyboard stays open when sending messages on mobile web.
2021-03-18 09:08:34 +02:00
Saúl Ibarra Corretgé
1395f84550 fix(virtual-background) fix tainted canvas when using the CDN
When we use a CDN the images come from an origin different than the site so
unless we mark them for CORS the canvas where they are painted will be tainted.

Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
2021-03-17 16:32:16 +01:00
Vlad Piersec
d080460f9b fix(toolbox): Fix mic disabled icon 2021-03-17 15:23:45 +02:00
tmoldovan8x8
61567f47c0 fix(android) changes the property name for the manifestOutputDirectory 2021-03-17 14:19:43 +01:00
Avram Tudor
4f3058eae2 Merge pull request #8823 from jitsi/tavram/hide-support
fix(jaas) hide support link in invite error for jaas users
2021-03-17 15:16:32 +02:00
Tudor-Ovidiu Avram
3a073d9af4 fix(jaas) hide support link in invite error for jaas users 2021-03-17 11:53:58 +02:00
Mihai-Andrei Uscat
aef0287605 feat(ToggleCamera): Implement for web. 2021-03-17 10:44:18 +02:00
Vlad Piersec
bb19567efa fix(prejoin): Use localFlipX on prejoin screen 2021-03-17 09:19:55 +01:00
Saúl Ibarra Corretgé
79ab973694 chore(deps) update react-native-webrtc
Fix script for downloading bitcode.
2021-03-16 22:09:21 +01:00
Saúl Ibarra Corretgé
7046785ca3 chore(deps) update react-native-webrtc to 1.89.1 2021-03-16 19:57:26 +01:00
Saúl Ibarra Corretgé
b817bd19d5 chore(deps) bump js-utils to 1.0.6
Fixes a harmless but confusing error in postis processing when using the
Bitwarden Chrome extension, for example.
2021-03-16 19:57:03 +01:00
luz paz
817d54b0b9 fix(misc) typos
Found via `codespell -q 3 -S ./lang`
2021-03-16 16:12:12 +01:00
Vlad Piersec
3f0bb6818c fix(toolbox): Fix always on top toolbar 2021-03-16 16:07:49 +01:00
Saúl Ibarra Corretgé
4fa47c8070 fix(virtual-background) use a DOM element for storing the image
THis will reuse the previously cached image and obey the base href.

Ref:
https://stackoverflow.com/questions/6241716/is-there-a-difference-between-new-image-and-document-createelementimg
2021-03-16 11:27:27 +01:00
Saúl Ibarra Corretgé
0dcb8a025b fix(rn,bottomsheet) limit width 2021-03-16 11:19:52 +01:00
tmoldovan8x8
8defaa9aec feat(android): adds timer to OngoingNotification 2021-03-16 12:13:37 +02:00
Vlad Piersec
d214079148 fix(toolbox): Constrain toolbox width on large mobile device 2021-03-16 09:50:49 +01:00
Vlad Piersec
096ee3cb53 fix(toolbox): Background of disabled settings button & tileview button 2021-03-16 09:20:02 +01:00
Vlad Piersec
fd606896b8 fix(toolbox): Fix buttons size in minified mode 2021-03-16 09:32:36 +02:00
372 changed files with 5144 additions and 3332 deletions

View File

@@ -7,6 +7,7 @@ flow-typed/*
libs/*
resources/*
react/features/stream-effects/virtual-background/vendor/*
load-test/*
# 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

View File

@@ -122,7 +122,7 @@ gradle.projectsEvaluated {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.getProcessManifestProvider().get().doLast {
def outputDir = manifestOutputDirectory.get().asFile
def outputDir = multiApkManifestOutputDirectory.get().asFile
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
def charset = 'UTF-8'
def text

View File

@@ -70,7 +70,6 @@ dependencies {
implementation project(':react-native-default-preference')
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-linear-gradient')
implementation project(':react-native-sound')
implementation project(':react-native-svg')
implementation project(':react-native-webrtc')

View File

@@ -36,8 +36,17 @@ public class BroadcastAction {
for (String key : this.data.keySet()) {
try {
// TODO add support for different types of objects
nativeMap.putString(key, this.data.get(key).toString());
if (this.data.get(key) instanceof Boolean) {
nativeMap.putBoolean(key, (Boolean) this.data.get(key));
} else if (this.data.get(key) instanceof Integer) {
nativeMap.putInt(key, (Integer) this.data.get(key));
} else if (this.data.get(key) instanceof Double) {
nativeMap.putDouble(key, (Double) this.data.get(key));
} else if (this.data.get(key) instanceof String) {
nativeMap.putString(key, (String) this.data.get(key));
} else {
throw new Exception("Unsupported extra data type");
}
} catch (Exception e) {
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
}
@@ -66,7 +75,8 @@ public class BroadcastAction {
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");
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED");
private final String action;

View File

@@ -85,7 +85,9 @@ public class BroadcastEvent {
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");
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED");
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
@@ -98,6 +100,7 @@ public class BroadcastEvent {
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 static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
private final String action;
@@ -142,6 +145,8 @@ public class BroadcastEvent {
return CHAT_MESSAGE_RECEIVED;
case CHAT_TOGGLED_NAME:
return CHAT_TOGGLED;
case VIDEO_MUTED_CHANGED_NAME:
return VIDEO_MUTED_CHANGED;
}
return null;

View File

@@ -40,4 +40,10 @@ public class BroadcastIntentHelper {
intent.putExtra("message", message);
return intent;
}
public static Intent buildSetVideoMutedIntent(boolean muted) {
Intent intent = new Intent(BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
intent.putExtra("muted", muted);
return intent;
}
}

View File

@@ -85,6 +85,7 @@ class ExternalAPIModule
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());
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
return constants;
}

View File

@@ -32,6 +32,7 @@ import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import android.app.Activity;
/**
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
@@ -58,6 +59,9 @@ public class JitsiMeetActivity extends FragmentActivity
Intent intent = new Intent(context, JitsiMeetActivity.class);
intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}

View File

@@ -132,6 +132,7 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCurrentConferenceChanged(String conferenceUrl) {
if (conferenceUrl == null) {
stopSelf();
OngoingNotification.resetStartingtime();
JitsiMeetLogger.i(TAG + "Service stopped");
}
}

View File

@@ -43,6 +43,7 @@ class OngoingNotification {
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static long startingTime = 0;
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@@ -85,6 +86,10 @@ class OngoingNotification {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
if (startingTime == 0) {
startingTime = System.currentTimeMillis();
}
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
@@ -92,6 +97,8 @@ class OngoingNotification {
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setWhen(startingTime)
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
@@ -110,6 +117,10 @@ class OngoingNotification {
return builder.build();
}
static void resetStartingtime() {
startingTime = 0;
}
private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, @StringRes int titleId) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
intent.setAction(action.getName());

View File

@@ -178,7 +178,6 @@ class ReactInstanceManagerHolder {
List<ReactPackage> packages
= new ArrayList<>(Arrays.asList(
new com.BV.LinearGradient.LinearGradientPackage(),
new com.calendarevents.CalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),

View File

@@ -19,8 +19,6 @@ include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-linear-gradient'
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
include ':react-native-sound'
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
include ':react-native-splash-screen'

1
app.js
View File

@@ -1,7 +1,6 @@
/* application specific logic */
import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';
import 'olm';

View File

@@ -309,11 +309,6 @@ class ConferenceConnector {
room.join();
}, 5000);
const { password }
= APP.store.getState()['features/base/conference'];
AuthHandler.requireAuth(room, password);
break;
}
@@ -378,7 +373,6 @@ class ConferenceConnector {
if (this.reconnectTimeout !== null) {
clearTimeout(this.reconnectTimeout);
}
AuthHandler.closeAuth();
}
/**
@@ -506,7 +500,7 @@ 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
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
// probably never resolve.
const timeout = browser.isElectron() ? 15000 : 60000;
@@ -568,7 +562,7 @@ export default {
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
// executing GUM with different source. Also at the time of writing 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.
@@ -625,7 +619,7 @@ export default {
// Hide the permissions prompt/overlay as soon as the tracks are
// created. Don't wait for the connection to be made, since in some
// cases, when auth is rquired, for instance, that won't happen until
// cases, when auth is required, for instance, that won't happen until
// the user inputs their credentials, but the dialog would be
// overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
@@ -1399,9 +1393,6 @@ export default {
.then(() => {
this.localVideo = newTrack;
this._setSharingScreen(newTrack);
if (newTrack) {
APP.UI.addLocalVideoStream(newTrack);
}
this.setVideoMuteStatus(this.isLocalVideoMuted());
})
.then(resolve)
@@ -1779,7 +1770,7 @@ export default {
};
}
// Apply the contraints on the desktop track.
// Apply the constraints on the desktop track.
try {
await this.localVideo.track.applyConstraints(desktopResizeConstraints);
} catch (err) {
@@ -1970,7 +1961,7 @@ export default {
}
APP.store.dispatch(updateRemoteParticipantFeatures(user));
logger.log(`USER ${id} connnected:`, user);
logger.log(`USER ${id} connected:`, user);
APP.UI.addUser(user);
});
@@ -2245,7 +2236,7 @@ export default {
});
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
AuthHandler.authenticate(room);
AuthHandler.authenticateExternal(room);
});
APP.UI.addListener(
@@ -2408,7 +2399,11 @@ export default {
// There is no guarantee another event will trigger the update
// immediately and in all situations, for example because a remote
// participant is having connection trouble so no status changes.
APP.UI.updateAllVideos();
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
});
APP.UI.addListener(

View File

@@ -123,7 +123,7 @@ var config = {
// opusMaxAverageBitrate: 20000,
// Enables support for opus-red (redundancy for Opus).
// enableOpusRed: false
// enableOpusRed: false,
// Video
@@ -456,6 +456,10 @@ var config = {
// Enables sending participants' emails (if available) to callstats and other analytics
// enableEmailInStats: false,
// Controls the percentage of automatic feedback shown to participants when callstats is enabled.
// The default value is 100%. If set to 0, no automatic feedback will be requested
// feedbackPercentage: 100,
// Privacy
//
@@ -494,7 +498,7 @@ var config = {
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
// is supported). This setting is deprecated, use preferredCodec instead.
// preferH264: true
// preferH264: true,
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
@@ -536,7 +540,7 @@ var config = {
// The interval at which rtcstats will poll getStats, defaults to 1000ms.
// If the value is set to 0 getStats won't be polled and the rtcstats client
// will only send data related to RTCPeerConnection events.
// rtcstatsPolIInterval: 1000
// rtcstatsPolIInterval: 1000,
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
@@ -559,6 +563,10 @@ var config = {
// Decides whether the start/stop recording audio notifications should play on record.
// disableRecordAudioNotification: false,
// Disables the sounds that play when other participants join or leave the
// conference (if set to true, these sounds will not be played).
// disableJoinLeaveSounds: false,
// Information for the chrome extension banner
// chromeExtensionBanner: {
// // The chrome extension to be installed address
@@ -618,6 +626,10 @@ var config = {
// the menu has option to flip the locally seen video for local presentations
// disableLocalVideoFlip: false,
// A property used to unset the default flip state of the local video.
// When it is set to 'true', the local(self) video will not be mirrored anymore.
// doNotFlipLocalVideo: false,
// Mainly privacy related settings
// Disables all invite functions from the app (share, invite, dial out...etc)
@@ -665,6 +677,9 @@ var config = {
*/
// dynamicBrandingUrl: '',
// Sets the background transparency level. '0' is fully transparent, '1' is opaque.
// backgroundAlpha: 1,
// The URL of the moderated rooms microservice, if available. If it
// is present, a link to the service will be rendered on the welcome page,
// otherwise the app doesn't render it.
@@ -674,13 +689,13 @@ var config = {
// disableTileView: true,
// Hides the conference subject
// hideConferenceSubject: true
// hideConferenceSubject: true,
// Hides the conference timer.
// hideConferenceTimer: true,
// Hides the participants stats
// hideParticipantsStats: true
// hideParticipantsStats: true,
// Sets the conference subject
// subject: 'Conference Subject',

View File

@@ -3,18 +3,21 @@
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from 'jitsi-meet-logger';
import AuthHandler from './modules/UI/authentication/AuthHandler';
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
import { hideLoginDialog } from './react/features/authentication/actions.web';
import { LoginDialog } from './react/features/authentication/components';
import { isTokenAuthEnabled } from './react/features/authentication/functions';
import {
connectionEstablished,
connectionFailed
} from './react/features/base/connection/actions';
import { openDialog } from './react/features/base/dialog/actions';
import {
isFatalJitsiConnectionError,
JitsiConnectionErrors,
JitsiConnectionEvents
} from './react/features/base/lib-jitsi-meet';
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
const logger = Logger.getLogger(__filename);
/**
@@ -80,7 +83,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
* @returns {Promise<JitsiConnection>} connection if
* everything is ok, else error.
*/
function connect(id, password, roomName) {
export function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const { jwt } = APP.store.getState()['features/base/jwt'];
@@ -214,10 +217,39 @@ export function openConnection({ id, password, retry, roomName }) {
const { jwt } = APP.store.getState()['features/base/jwt'];
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
return AuthHandler.requestAuth(roomName, connect);
return requestAuth(roomName);
}
}
throw err;
});
}
/**
* Show Authentication Dialog and try to connect with new credentials.
* If failed to connect because of PASSWORD_REQUIRED error
* then ask for password again.
* @param {string} [roomName] name of the conference room
*
* @returns {Promise<JitsiConnection>}
*/
function requestAuth(roomName) {
const config = APP.store.getState()['features/base/config'];
if (isTokenAuthEnabled(config)) {
// This Promise never resolves as user gets redirected to another URL
return new Promise(() => redirectToTokenAuthService(roomName));
}
return new Promise(resolve => {
const onSuccess = connection => {
APP.store.dispatch(hideLoginDialog());
resolve(connection);
};
APP.store.dispatch(
openDialog(LoginDialog, { onSuccess,
roomName })
);
});
}

View File

@@ -72,8 +72,10 @@
* Keep overflow menu within screen vertical bounds and make it scrollable.
*/
.toolbox-button-wth-dialog > div:nth-child(2) {
background: $menuBG;
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
margin-bottom: 4px;
padding: 0;
overflow-y: auto;
}
@@ -111,15 +113,16 @@
}
}
@media (min-width: 580px) and (max-width: 680px) {
.mobile-browser {
&.shift-right .focus-lock > div > div {
@include full-size-modal-positioner();
}
@media (max-width: 580px) {
// Override Atlaskit inline style for the modal background.
// Important is unfortunately needed for that.
.shift-right .focus-lock [role="dialog"][style] {
background-color: $chatBackgroundColor !important;
}
&.shift-right .focus-lock [role="dialog"] {
@include full-size-modal-dialog();
}
// Remove Atlaskit padding from the chat dialog.
.shift-right .focus-lock [role="dialog"] > div:first-child > div:nth-child(2) {
padding: 0;
}
}

View File

@@ -72,7 +72,7 @@
&--selected {
padding-left: 18px;
background: #131519;
background: $newToolbarBackgroundColor;
}
}
@@ -104,7 +104,7 @@
padding-left: 48px;
&--selected {
background: #131519;
background: $newToolbarBackgroundColor;
padding-left: 18px;
}
}

View File

@@ -55,6 +55,10 @@ body {
fill: white;
}
.disabled .jitsi-icon svg {
fill: #929292;
}
.jitsi-icon.gray svg {
fill: #5E6D7A;
cursor: pointer;

View File

@@ -1,5 +1,5 @@
#sideToolbarContainer {
background-color: $newToolbarBackgroundColor;
background-color: $chatBackgroundColor;
box-sizing: border-box;
color: #FFF;
display: flex;
@@ -105,13 +105,12 @@
}
.chat-header {
background-color: $chatHeaderBackgroundColor;
height: 70px;
position: relative;
width: 100%;
z-index: 1;
display: flex;
justify-content: space-between;
justify-content: flex-end;
padding: 16px;
align-items: center;
box-sizing: border-box;
@@ -123,23 +122,27 @@
.jitsi-icon {
cursor: pointer;
}
.jitsi-icon > svg {
fill: #A4B8D1;
}
}
.chat-input-container {
padding: 0 16px 24px;
padding: 0 16px 16px;
&.populated {
#chat-input {
border: 1px solid #619CF4;
.send-button {
background: #1B67EC;
cursor: pointer;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: #3D82FB;
}
}
&:active {
background: #0852D4;
}
path {
fill: #fff;
}
@@ -151,9 +154,13 @@
#chat-input {
border: 1px solid $chatInputSeparatorColor;
display: flex;
padding: 5px 10px;
padding: 4px;
border-radius: 3px;
&:focus-within {
border: 1px solid #619CF4;
}
* {
background-color: transparent;
}
@@ -177,10 +184,20 @@
}
}
.mobile-browser {
.send-button {
height: 48px;
width: 48px;
.smiley-button {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
width: 40px;
border-radius: 3px;
}
#chat-input .smiley-button {
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: #484A4F;
}
}
}
@@ -197,7 +214,7 @@
border-radius:0;
box-shadow: none;
color: white;
font-size: 15px;
font-size: 14px;
padding: 10px;
overflow-y: auto;
resize: none;
@@ -254,6 +271,14 @@
height: 48px;
}
}
#usermsg {
font-size: 16px;
}
.chatmessage .usermessage {
font-size: 16px;
}
}
.sideToolbarContainer {
@@ -263,8 +288,8 @@
}
.display-name {
font-size: 13px;
font-weight: bold;
font-size: 12px;
font-weight: 600;
margin-bottom: 5px;
white-space: nowrap;
text-overflow: ellipsis;
@@ -292,6 +317,7 @@
.usermessage {
white-space: pre-wrap;
font-size: 14px;
}
&.error {
@@ -314,12 +340,16 @@
}
.messagecontent {
margin: 5px 10px;
margin: 8px;
max-width: 100%;
overflow: hidden;
}
}
.timestamp {
color: #757575;
}
.smiley {
font-size: 14pt;
}
@@ -355,7 +385,9 @@
max-height: 0;
overflow: hidden;
position: absolute;
width: $sidebarWidth;
width: calc(#{$sidebarWidth} - 32px);
margin-bottom: 5px;
margin-left: -5px;
/**
* CSS transitions do not apply for auto dimensions. So to produce the css
@@ -370,9 +402,8 @@
}
#smileysContainer {
background-color: $newToolbarBackgroundColor;
border-bottom: 1px solid;
border-top: 1px solid;
background-color: $chatBackgroundColor;
border-top: 1px solid $chatInputSeparatorColor;
}
}
@@ -478,9 +509,9 @@
&-header {
display: flex;
justify-content: space-between;
justify-content: flex-end;
align-items: center;
margin: 16px 16px 24px;
margin: 16px;
width: calc(100% - 32px);
box-sizing: border-box;
color: #fff;
@@ -491,19 +522,11 @@
.jitsi-icon {
cursor: pointer;
}
.jitsi-icon > svg {
fill: #A4B8D1;
}
}
#chatconversation {
width: 100%;
}
.chat-input-container {
padding: 0 0 24px;
}
}
.touchmove-hack {
@@ -520,6 +543,6 @@
place-items: center;
height: 48px;
width: 48px;
background: #2a3a4b;
background: #36383C;
border-radius: 3px;
}

View File

@@ -9,7 +9,7 @@
.spinner {
margin: 30px;
}
.joining-message {
margin: 10px;
}
@@ -49,7 +49,7 @@
position: fixed;
top: 20;
transition: top 1s ease;
z-index: 100;
z-index: $toolbarZ + 1;
&.toolbox-visible {
// Same as toolbox subject position
@@ -62,31 +62,6 @@
padding: 15px
}
ul {
list-style-type: none;
padding: 0 15px 15px 15px;
li {
align-items: center;
display: flex;
flex-direction: row;
margin: 8px 0;
.details {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-evenly;
margin: 0 30px 0 10px;
}
button {
align-self: unset;
margin: 0 5px;
}
}
}
button {
align-self: stretch;
margin: 8px 0;
@@ -116,3 +91,50 @@
}
}
}
.knocking-participants-container {
list-style-type: none;
max-height: 600px;
overflow-y: scroll;
padding: 0 15px 15px 15px;
}
.knocking-participant {
align-items: center;
display: flex;
flex-direction: row;
margin: 8px 0;
.details {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-evenly;
margin: 0 30px 0 10px;
}
button {
align-self: unset;
margin: 0 5px;
}
}
@media (max-width: 300px) {
#knocking-participant-list {
margin: 0;
text-align: center;
width: 100%;
.avatar {
display: none;
}
}
.knocking-participant {
flex-direction: column;
.details {
margin: 0;
}
}
}

View File

@@ -23,44 +23,10 @@
flex-direction: row;
left: 50%;
position: absolute;
top: 10px;
bottom: 10px;
transform: translateX(-50%);
.toolbox-button {
&:first-child {
.toolbox-icon {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
}
&:nth-child(2) {
svg {
fill: $hangupColor;
}
}
&:last-child {
.toolbox-icon {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
}
.filmstrip-toolbox {
flex-direction: column;
.toolbox-button {
&:nth-child(1) {
svg {
fill: $hangupColor;
}
}
.toolbox-icon {
border-radius: 3px;
}
}
}
}

View File

@@ -3,7 +3,9 @@
**/
.popupmenu {
min-width: 75px;
background-color: $menuBG;
border-radius: 3px;
min-width: 150px;
text-align: left;
padding: 0px;
white-space: nowrap;
@@ -38,6 +40,7 @@
&__text {
display: inline-block;
margin-left: 8px;
vertical-align: middle;
}
@@ -109,6 +112,6 @@ ul.popupmenu {
margin: -16px -24px;
}
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
span.localvideomenu:hover ul.popupmenu, span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important;
}

View File

@@ -39,6 +39,8 @@
}
&.settings-button-small-icon--disabled {
background: #36383C;
&> svg {
fill: #929292;
}

View File

@@ -72,10 +72,6 @@
.toolbox-button-wth-dialog {
display: inline-block;
&> div {
padding: 0;
}
}
}
@@ -102,9 +98,15 @@
}
}
.toolbox-content-wrapper {
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 100%;
}
.toolbox-content-items {
background: #131519;
background: $newToolbarBackgroundColor;
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
border-radius: 6px;
margin: 0 auto;
@@ -165,9 +167,14 @@
cursor: initial;
color: #929292;
&:hover {
background: none;
}
svg {
fill: #929292;
}
}
@media (hover: hover) and (pointer: fine) {
@@ -235,6 +242,11 @@
}
}
@media (max-width: 320px) {
height: 36px;
width: 36px;
}
&.toggled {
background: $newToolbarButtonToggleColor;
}
@@ -294,12 +306,16 @@
}
/**
* On small mobile devices make the toolbar full width.
* On small mobile devices make the toolbar full width and pad the invite prompt.
*/
.toolbox-content-mobile {
@media (max-width: 500px) {
margin-bottom: 0;
.toolbox-content-wrapper {
width: 100%;
}
.toolbox-content-items {
border-radius: 0;
display: flex;
@@ -307,5 +323,13 @@
padding: 6px 0;
width: 100%;
}
.invite-more-container {
margin: 0 16px 8px;
}
.invite-more-container.elevated {
margin-bottom: 52px;
}
}
}

View File

@@ -1,10 +1,11 @@
.transcription-subtitles{
bottom: 10%;
.transcription-subtitles {
bottom: $newToolbarSize + 40px;
font-size: 16px;
font-weight: 1000;
left: 50%;
max-width: 50vw;
opacity: 0.80;
overflow-wrap: break-word;
pointer-events: none;
position: absolute;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
@@ -14,6 +15,11 @@
transform: translateX(-50%);
z-index: $subtitlesZ;
&.lifted {
// Lift subtitle above toolbar+invite box.
bottom: $newToolbarSize + 112px + 40px;
}
span {
background: black;
}

View File

@@ -38,7 +38,7 @@ $presence-idle: rgb(172, 172, 172);
/**
* Toolbar
*/
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
$newToolbarBackgroundColor: #131519;
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.2);
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.15);
$AOTToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
@@ -91,12 +91,12 @@ $modalTextColor: #333;
* Chat
*/
$chatActionsSeparatorColor: rgb(173, 105, 112);
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
$chatBackgroundColor: #131519;
$chatInputSeparatorColor: #A4B8D1;
$chatLocalMessageBackgroundColor: rgb(4, 98, 178);
$chatLocalMessageBackgroundColor: #484A4F;
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
$chatRemoteMessageBackgroundColor: rgb(86, 101, 114);
$sidebarWidth: 375px;
$chatRemoteMessageBackgroundColor: #242528;
$sidebarWidth: 315px;
/**
* Misc.

View File

@@ -400,7 +400,9 @@
}
}
.local-video-menu-trigger,
.remote-video-menu-trigger,
.localvideomenu,
.remotevideomenu
{
display: inline-block;
@@ -418,6 +420,7 @@
cursor: hand;
}
}
.local-video-menu-trigger,
.remote-video-menu-trigger {
margin-top: 7px;
}

View File

@@ -233,6 +233,10 @@ body.welcome-page {
}
}
&.without-footer {
justify-content: start;
}
.welcome-cards-container {
color:#131519;
padding-top: 40px;

View File

@@ -19,7 +19,7 @@
0 0 3px $videoThumbnailSelected;
}
.remotevideomenu > .icon-menu {
.remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
display: none;
}
@@ -32,7 +32,7 @@
box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered;
.remotevideomenu > .icon-menu {
.remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
display: inline-block;
}
}

View File

@@ -121,3 +121,15 @@
align-self: baseline;
}
}
.shift-right #filmstripRemoteVideosContainer {
/**
* Max-width corresponding to the ASPECT_RATIO_BREAKPOINT from features/filmstrip/constants,
* from which we subtract the chat size.
*/
@media only screen and (max-width: calc(500px + #{$sidebarWidth})) {
video {
object-fit: cover;
}
}
}

View File

@@ -43,6 +43,7 @@
* specifically the various status icons.
*/
.remotevideomenu,
.localvideomenu,
.videocontainer__toptoolbar {
z-index: auto;
}

View File

@@ -51,6 +51,7 @@
* and tooltips from getting a new location context due to translate3d.
*/
.connection-indicator,
.local-video-menu-trigger,
.remote-video-menu-trigger,
.indicator-icon-container {
transform: translate3d(0, 0, 0);
@@ -68,7 +69,9 @@
* Move the remote video menu trigger to the bottom left of the video
* thumbnail.
*/
.localvideomenu,
.remotevideomenu,
.local-video-menu-trigger,
.remote-video-menu-trigger {
bottom: 0;
left: 0;
@@ -76,6 +79,7 @@
right: auto;
}
.local-video-menu-trigger,
.remote-video-menu-trigger {
margin-bottom: 7px;
margin-left: $remoteVideoMenuIconMargin;

View File

@@ -1,31 +1,42 @@
.invite-more {
&-container {
margin-bottom: 8px;
transition: margin-bottom 0.3s;
&.elevated {
margin-bottom: 36px;
}
}
&-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: rgba(0, 0, 0, 0.7);
border-radius: 8px;
color: #fff;
font-size: 14px;
line-height: 24px;
font-weight: 600;
position: absolute;
width: 100%;
text-align: center;
z-index: $zindex2;
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
}
&-header {
font-size: 19px;
line-height: 28px;
margin: 24px 0 16px 0;
max-width: 100%;
margin-bottom: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
&-button {
display: flex;
margin: auto;
max-width: 100%;
height: 40px;
box-sizing: border-box;
padding: 8px 16px;
width: fit-content;
width: -moz-fit-content;
height: 24px;
background: #0376DA;
border-radius: 3px;
font-size: 14px;
line-height: 24px;
cursor: pointer;
@media (hover: hover) and (pointer: fine) {
@@ -36,8 +47,9 @@
&-text {
margin-left: 8px;
font-size: 15px;
line-height: 24px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
&-dialog {
@@ -195,3 +207,14 @@
}
}
}
.mobile-browser {
.invite-more-content {
font-size: 16px;
}
.invite-more-button {
height: 48px;
padding: 12px 16px;
}
}

View File

@@ -1,25 +1,68 @@
.virtual-background-dialog{
display: inline-flex;
cursor: pointer;
.thumbnail{
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
}
.thumbnail-selected{
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
border: 2px solid #a4b8d1;
}
.blur-selected{
border: 2px solid #a4b8d1;
}
.virtual-background-none{
.virtual-background-dialog {
display: inline-grid;
grid-template-columns: auto auto auto auto auto auto auto;
max-width: 370px;
cursor: pointer;
.thumbnail {
border-radius: 10px;
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
}
.thumbnail:hover ~ .delete-image-icon {
display: block;
}
.thumbnail-selected {
border-radius: 10px;
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
border: 2px solid #a4b8d1;
}
.blur-selected {
border-radius: 10px;
border: 2px solid #a4b8d1;
}
.virtual-background-none {
font-weight: bold;
padding: 5px;
height: 34px;
width: 34px;
border-radius: 10px;
border: 1px solid #a4b8d1;
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
.none-selected {
font-weight: bold;
padding: 5px;
height: 34px;
width: 34px;
border-radius: 10px;
border: 2px solid #a4b8d1;
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
}
.modal-dialog-form .virtual-background-loading {
overflow: hidden;
}
.file-upload-btn {
display: none;
}
.custom-file-upload {
font-size: x-large;
font-weight: bold;
padding: 5px;
display: inline-block;
padding: 4px;
height: 35px;
width: 35px;
border-radius: 10px;
@@ -27,18 +70,24 @@
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
.none-selected{
font-weight: bold;
padding: 5px;
height: 35px;
width: 35px;
border-radius: 10px;
border: 2px solid #a4b8d1;
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
}
margin-left: 5px;
cursor: pointer;
}
.delete-image-icon {
position: absolute;
display: none;
left: 36;
bottom: 36;
}
.delete-image-icon:hover {
display: block;
}
.thumbnail-container {
position: relative;
}
.loading-content-text{
margin-right: 15px;
}

View File

@@ -1,6 +1,13 @@
/* eslint-disable no-unused-vars, no-var, max-len */
/* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */
/**
* !!!IMPORTANT!!!
*
* This file is considered deprecated. All options will eventually be moved to
* config.js, and no new options should be added here.
*/
var interfaceConfig = {
APP_NAME: 'Jitsi Meet',
AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',

View File

@@ -61,7 +61,6 @@ target 'JitsiMeetSDK' do
pod 'react-native-splash-screen', :path => '../node_modules/react-native-splash-screen'
pod 'react-native-webview', :path => '../node_modules/react-native-webview'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'BVLinearGradient', :path => '../node_modules/react-native-linear-gradient'
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-async-storage/async-storage'
pod 'RNDeviceInfo', :path => '../node_modules/react-native-device-info'
pod 'RNGoogleSignin', :path => '../node_modules/@react-native-community/google-signin'

View File

@@ -5,8 +5,6 @@ PODS:
- AppAuth/Core (1.2.0)
- AppAuth/ExternalUserAgent (1.2.0)
- boost-for-react-native (1.63.0)
- BVLinearGradient (2.5.6):
- React
- CocoaLumberjack (3.5.3):
- CocoaLumberjack/Core (= 3.5.3)
- CocoaLumberjack/Core (3.5.3)
@@ -292,7 +290,7 @@ PODS:
- React
- react-native-splash-screen (3.2.0):
- React
- react-native-webrtc (1.87.3):
- react-native-webrtc (1.89.1):
- React-Core
- react-native-webview (11.0.2):
- React-Core
@@ -371,7 +369,6 @@ PODS:
- Yoga (1.14.0)
DEPENDENCIES:
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- CocoaLumberjack (~> 3.5.3)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
@@ -442,8 +439,6 @@ SPEC REPOS:
- PromisesObjC
EXTERNAL SOURCES:
BVLinearGradient:
:path: "../node_modules/react-native-linear-gradient"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
FBLazyVector:
@@ -526,7 +521,6 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
AppAuth: bce82c76043657c99d91e7882e8a9e1a93650cd4
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
FBLazyVector: ca7f56c8ff6cd8590f7a673d7903b06019805581
@@ -563,7 +557,7 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-webrtc: dc1208bdca2c4d091f7b57859e69332bff6f1986
react-native-webrtc: ccb0c21eb4fb04326648fbdb4a5d49977e2cf274
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
@@ -584,6 +578,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: 5be5132e41831a98362eeed760558227a4df89ae
PODFILE CHECKSUM: d059cebf82da14a53940a16c24c3330752d4b0c8
COCOAPODS: 1.10.1

View File

@@ -35,6 +35,7 @@
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
[builder setFeatureFlag:@"resolution" withValue:@(360)];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
builder.welcomePageEnabled = YES;
@@ -51,7 +52,7 @@
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enabling Firebase");
[FIRApp configure];
// Crashlytics defaults to disabled wirth the FirebaseCrashlyticsCollectionEnabled Info.plist key.
// Crashlytics defaults to disabled with the FirebaseCrashlyticsCollectionEnabled Info.plist key.
[[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:![jitsiMeet isCrashReportingDisabled]];
}

View File

@@ -131,6 +131,10 @@
NSLog(@"%@%@", @"Chat toggled: ", data);
}
- (void)videoMutedChanged:(NSDictionary *)data {
NSLog(@"%@%@", @"Video muted changed: ", data[@"muted"]);
}
#pragma mark - Helpers
- (void)terminate {

View File

@@ -35,7 +35,7 @@ fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
# do the merge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"

View File

@@ -90,7 +90,7 @@ echo "importing dev-key.p12"
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
echo "will set-key-partition-list"
# Fix for OS X Sierra that hungs in the codesign step
# Fix for OS X Sierra that hangs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
echo "done set-key-partition-list"

View File

@@ -26,5 +26,6 @@
- (void)openChat:(NSString*)to;
- (void)closeChat;
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
- (void)sendSetVideoMuted:(BOOL)muted;
@end

View File

@@ -26,6 +26,7 @@ static NSString * const retrieveParticipantsInfoAction = @"org.jitsi.meet.RETRIE
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";
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
@implementation ExternalAPI
@@ -47,7 +48,8 @@ RCT_EXPORT_MODULE();
@"RETRIEVE_PARTICIPANTS_INFO": retrieveParticipantsInfoAction,
@"OPEN_CHAT": openChatAction,
@"CLOSE_CHAT": closeChatAction,
@"SEND_CHAT_MESSAGE": sendChatMessageAction
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
@"SET_VIDEO_MUTED" : setVideoMutedAction
};
};
@@ -70,7 +72,8 @@ RCT_EXPORT_MODULE();
retrieveParticipantsInfoAction,
openChatAction,
closeChatAction,
sendChatMessageAction
sendChatMessageAction,
setVideoMutedAction
];
}
@@ -193,4 +196,11 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
[self sendEventWithName:sendChatMessageAction body:data];
}
- (void)sendSetVideoMuted:(BOOL)muted {
NSDictionary *data = @{ @"muted": [NSNumber numberWithBool:muted]};
[self sendEventWithName:setVideoMutedAction body:data];
}
@end

View File

@@ -44,5 +44,6 @@
- (void)openChat:(NSString * _Nullable)to;
- (void)closeChat;
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
- (void)setVideoMuted:(BOOL)muted;
@end

View File

@@ -155,6 +155,11 @@ static void initializeViewsMap() {
[externalAPI sendChatMessage:message :to];
}
- (void)setVideoMuted:(BOOL)muted {
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
[externalAPI sendSetVideoMuted:muted];
}
#pragma mark Private methods
/**
@@ -184,7 +189,7 @@ static void initializeViewsMap() {
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented bellow introduces imperativeness
// source code. The workaround implemented below introduces imperativeness
// in React Component props by defining a unique value per invocation.
props[@"timestamp"] = @(mach_absolute_time());

View File

@@ -104,4 +104,11 @@
*/
- (void)chatToggled:(NSDictionary *)data;
/**
* Called when videoMuted state changed.
*
* The `data` dictionary contains a `muted` key with state of the videoMuted for the localParticipant.
*/
- (void)videoMutedChanged:(NSDictionary *)data;
@end

View File

@@ -76,7 +76,7 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
// Avoid mute actions ping-pong: if the mute action was caused by
// the JS side (we requested a transaction) don't call the delegate
// method. If it was called by the provder itself (when the user presses
// method. If it was called by the provider itself (when the user presses
// the mute button in the CallKit view) then call the delegate method.
//
// NOTE: don't try to be clever and remove this. Been there, done that.

View File

@@ -29,7 +29,7 @@ public protocol PiPViewCoordinatorDelegate: class {
/// when is presented in Picure in Picture mode.
public class PiPViewCoordinator {
/// Limits the boundries of view position on screen when minimized
/// Limits the boundaries of view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
left: 5,
bottom: 5,

View File

@@ -60,7 +60,7 @@ fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
# do the merge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"

View File

@@ -1,27 +1,52 @@
{
"en": "영어",
"af": "",
"af": "아프리칸스어",
"ar": "아랍어",
"az": "아제르바이잔어",
"bg": "불가리어",
"cs": "체코어",
"ca": "카탈루냐어",
"cs": "체코어",
"da": "덴마크어",
"de": "독일어",
"el": "그리스어",
"enGB": "영어(영국)",
"eo": "에스페란토어",
"es": "스페인어",
"esUS": "스페인어(라틴 아메리카)",
"et": "에스토니아어",
"eu": "바스크어",
"fi": "핀란드어",
"fr": "프랑스어",
"frCA": "프랑스어(캐나다)",
"he": "히브리어",
"mr":"마라티어",
"hr": "크로아티아어",
"hu": "헝가리어",
"hy": "아르메니아어",
"id": "인도네시아어",
"it": "이탈리아어",
"ja": "일본어",
"kab": "커바일어",
"ko": "한국어",
"nb": "노르웨이어",
"oc": "",
"lt": "리투아니아어",
"ml": "말라얄람어",
"lv": "라트비아어",
"nl": "네덜란드어",
"oc": "오크어",
"fa": "페르시아어",
"pl": "폴란드어",
"ptBR": "포르투갈어(브라질)",
"ru": "러시아어",
"ro": "루마니아어",
"sc": "사르데냐어",
"sk": "슬로바키아어",
"sl": "슬로베니아어",
"sr": "세르비아어",
"sv": "스웨덴어",
"th": "태국어",
"tr": "터키어",
"uk": "우크라이나어",
"vi": "베트남어",
"zhCN": "중국어(중국)"
}
"zhCN": "중국어(중국)",
"zhTW": "중국어(대만)"
}

View File

@@ -34,6 +34,7 @@
"oc": "Occitan",
"fa": "Persian",
"pl": "Polish",
"pt": "Portuguese",
"ptBR": "Portuguese (Brazil)",
"ru": "Russian",
"ro": "Romanian",

View File

@@ -562,8 +562,8 @@
"linkCopied": "Link in die Zwischenablage kopiert",
"lookGood": "Ihr Mikrofon scheint zu funktionieren.",
"or": "oder",
"premeeting": "Vorraum",
"showScreen": "Konferenzvorraum aktivieren",
"premeeting": "Vorschau",
"showScreen": "Konferenzvorschau aktivieren",
"startWithPhone": "Mit Telefonaudio starten",
"screenSharingError": "Fehler bei Bildschirmfreigabe:",
"videoOnlyError": "Videofehler:",

View File

@@ -201,9 +201,9 @@
"dismiss": "Rejeter",
"displayNameRequired": "Bonjour ! Quel est votre nom ?",
"done": "Terminé",
"e2eeDescription": "Le cryptage de Bout-en-Bout est actuellement EXPERIMENTAL. Veuillez garder à l'esprit que l'activation du cryptage de Bout-en-Bout désactivera les services fournis côté serveur tels que : l'enregistrement, la diffusion en direct et la participation par téléphone. Gardez également à l'esprit que la réunion ne fonctionnera que pour les personnes qui se joignent à partir de navigateurs prenant en charge les flux insérables.",
"e2eeLabel": "Activer le cryptage de Bout-en-Bout",
"e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le cryptage, ils ne pourront ni vous voir, ni vous entendre.",
"e2eeDescription": "Le chiffrement de Bout-en-Bout est actuellement EXPERIMENTAL. Veuillez garder à l'esprit que l'activation du chiffrement de Bout-en-Bout désactivera les services fournis côté serveur tels que : l'enregistrement, la diffusion en direct et la participation par téléphone. Gardez également à l'esprit que la réunion ne fonctionnera que pour les personnes qui se joignent à partir de navigateurs prenant en charge les flux insérables.",
"e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
"e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le chiffrement, ils ne pourront ni vous voir, ni vous entendre.",
"enterDisplayName": "Merci de saisir votre nom ici",
"error": "Erreur",
"externalInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
@@ -326,7 +326,7 @@
"title": "Document partagé"
},
"e2ee": {
"labelToolTip": "L'audio et la vidéo de cette conférence sont cryptés de Bout-en-Bout"
"labelToolTip": "L'audio et la vidéo de cette conférence sont chiffrés de Bout-en-Bout"
},
"embedMeeting": {
"title": "Intégrer cette réunion"
@@ -757,7 +757,7 @@
"documentClose": "Fermer le document partagé",
"documentOpen": "Ouvrir le document partagé",
"download": "Télécharger nos applications",
"e2ee": "Cryptage de Bout-en-Bout",
"e2ee": "Chiffrement de Bout-en-Bout",
"embedMeeting": "Intégrer la réunion",
"enterFullScreen": "Afficher en plein écran",
"enterTileView": "Accéder au mode mosaïque",

View File

@@ -718,7 +718,7 @@
"join": "Toucher pour rejoindre",
"roomname": "Entrer le nom de la salle"
},
"appDescription": "Profitez de la conversation vidéo avec toute votre équipe. Allez-y, invitez tous ceux que vous connaissez. {{app}} est une solution 100 % libre de conférence vidéo entièrement cryptée que vous pouvez utiliser en tout temps et gratuitement, sans avoir besoin de compte.",
"appDescription": "Profitez de la conversation vidéo avec toute votre équipe. Allez-y, invitez tous ceux que vous connaissez. {{app}} est une solution 100 % libre de conférence vidéo entièrement chiffrée que vous pouvez utiliser en tout temps et gratuitement, sans avoir besoin de compte.",
"audioVideoSwitch": {
"audio": "Téléphone",
"video": "Vidéo"

View File

@@ -61,6 +61,7 @@
"today": "Oggi"
},
"chat": {
"enter": "Entra nella conversazione",
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"messagebox": "Digitare un messaggio",
@@ -240,12 +241,19 @@
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
"muteEveryoneDialog": "Sei sicuro di voler zittire tutti? Non potrai riattivar loro il microfono, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneTitle": "Zittisco tutti?",
"muteEveryoneElsesVideoDialog": "Una volta spente le videocamere, non potrai riaccenderle, ma ogni partecipante potrà farlo in ogni momento.",
"muteEveryoneElsesVideoTitle": "Spegnere tutte le videocamere, tranne quella di {{whom}}?",
"muteEveryonesVideoDialog": "Sei sicuro di voler spegnere le videocamere di tutti? Non potrai riaccenderle, ma ogni partecipante potrà farlo in ogni momento.",
"muteEveryonesVideoTitle": "Vuoi spegnere le videocamere di tutti?",
"muteEveryoneSelf": "te stesso",
"muteEveryoneStartMuted": "Tutti cominciano a microfono spento, d'adesso in avanti",
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Zittisci",
"muteParticipantDialog": "Sei sicuro di voler zittire questo partecipante? Sarà lui a dovere riattivare l'audio, per parlare.",
"muteParticipantTitle": "Zittisco questo partecipante?",
"muteParticipantsVideoButton": "Spegnere videocamera",
"muteParticipantsVideoTitle": "Vuoi spegnere la videocamera di questo partecipante?",
"muteParticipantsVideoBody": "Una volta spenta la videocamera, non potrai riaccenderla, ma lui potrà riattivarla in qualsiasi momento.",
"Ok": "OK",
"passwordLabel": "La riunione è stata bloccata da un partecipante. Immetti la $t(lockRoomPassword) per collegarti, per favore.",
"passwordNotSupported": "Impostare una $t(lockRoomPassword) non è supportato.",
@@ -305,6 +313,7 @@
"unlockRoom": "Togli la $t(lockRoomPassword) alla riunione",
"user": "utente",
"userPassword": "password utente",
"videoLink": "Collegamento video",
"WaitForHostMsg": "La riunione <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, per favore autenticati. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitForHostMsgWOk": "La riunione <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, allora premi OK per autenticarti. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitingForHost": "In attesa dell'organizzatore...",
@@ -323,6 +332,11 @@
"embedMeeting": {
"title": "Incorpora questa riunione"
},
"virtualBackground": {
"title": "Sfondi",
"enableBlur": "Attiva sfocatura",
"removeBackground": "Togli sfondo"
},
"feedback": {
"average": "Media",
"bad": "Scadente",
@@ -483,6 +497,8 @@
"mutedTitle": "Hai l'audio disattivato!",
"mutedRemotelyTitle": "Ti è stato disattivato l'audio da {{participantDisplayName}}!",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono, quando vuoi parlare. Spegni il microfono quando hai finito, per non introdurre rumori di fondo nella riunione.",
"videoMutedRemotelyTitle": "La videocamera ti è stata spenta da {{participantDisplayName}}!",
"videoMutedRemotelyDescription": "Puoi riaccenderla in qualsiasi momento.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata tolta da un altro partecipante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) è stata messa da un altro partecipante",
"raisedHand": "{{name}} vorrebbe intervenire.",
@@ -715,12 +731,16 @@
"moreOptions": "Più opzioni",
"mute": "Attiva/disattiva audio",
"muteEveryone": "Zittisci tutti",
"muteEveryoneElse": "Zittisci tutti gli altri",
"muteEveryonesVideo": "Spegni le videocamere di tutti",
"muteEveryoneElsesVideo": "Spegni le videocamere di tutti gli altri",
"pip": "Attiva/disattiva immagine nellimmagine",
"privateMessage": "Invia messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Attiva/disattiva alzata di mano",
"recording": "Attiva/disattiva registrazione",
"remoteMute": "Zittisci partecipante",
"remoteVideoMute": "Spegni videocamera del partecipante",
"security": "Impostazioni di sicurezza",
"Settings": "Attiva/disattiva impostazioni",
"sharedvideo": "Attiva/disattiva condivisione YouTube",
@@ -733,9 +753,10 @@
"toggleCamera": "Cambia videocamera",
"toggleFilmstrip": "Attiva/disattiva pellicola",
"videomute": "Attiva/disattiva videocamera",
"videoblur": "Attiva/disattiva offuscamento video"
"selectBackground": "Scegli sfondo"
},
"addPeople": "Aggiungi persone alla chiamata",
"audioSettings": "Impostazioni audio",
"audioOnlyOff": "Disabilita modalità per banda limitata",
"audioOnlyOn": "Abilita modalità per banda limitata",
"audioRoute": "Scegli l'uscita audio",
@@ -765,6 +786,7 @@
"moreOptions": "Più opzioni",
"mute": "Attiva / Disattiva microfono",
"muteEveryone": "Zittisci tutti",
"muteEveryonesVideo": "Spegni videocamera di tutti",
"noAudioSignalTitle": "Non arrivano suoni dal tuo microfono!",
"noAudioSignalDesc": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a cambiare dispositivo di input.",
"noAudioSignalDescSuggestion": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a scegliere il dispositivo consigliato.",
@@ -793,7 +815,6 @@
"tileViewToggle": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"videomute": "Attiva / Disattiva videocamera",
"startvideoblur": "Sfoca lo sfondo del video",
"stopvideoblur": "Non sfocare lo sfondo video"
},
"transcribing": {
@@ -849,13 +870,16 @@
"videothumbnail": {
"connectionInfo": "Info connessione",
"domute": "Disattiva audio",
"domuteOthers": "Zittisci tutti gli altri",
"domuteVideo": "Disattiva video",
"domuteOthers": "Zittisci tutti gli altri",
"domuteVideoOfOthers": "Disattiva video di tutti gli altri",
"flip": "Rifletti",
"grantModerator": "Autorizza moderatore",
"kick": "Espelli",
"moderator": "Moderatore",
"mute": "Il partecipante ha il microfono spento",
"muted": "Audio disattivato",
"videoMuted": "Video disattivato",
"remoteControl": "Avvia/ferma il controllo remoto",
"show": "Mostra in primo piano",
"videomute": "Il partecipante ha la videocamera spenta"

View File

@@ -57,10 +57,11 @@
"ongoingMeeting": "진행중인 회의",
"permissionButton": "설정 열기",
"permissionMessage": "앱에 회의를 나열하려면 캘린더 권한이 필요합니다",
"refresh": "달력 새로고침",
"refresh": "캘린더 새로고침",
"today": "오늘"
},
"chat": {
"enter": "채팅방 입장",
"error": "오류 : 메시지가 전송되지 않았습니다. 이유 : {{error}}",
"fieldPlaceHolder": "메세지를 여기에 입력하세요",
"messagebox": "메시지 입력",
@@ -212,7 +213,7 @@
"lockMessage": "회의를 비공개하지 못했습니다",
"lockRoom": "회의 추가 $t(lockRoomPasswordUppercase)",
"lockTitle": "비공개 실패",
"logoutQuestion": "로그 아웃하고 컨퍼런스를 중지하시겠습니까?",
"logoutQuestion": "로그아웃하고 컨퍼런스를 중지하시겠습니까?",
"logoutTitle": "로그아웃",
"maxUsersLimitReached": "회의의 최대 참가자 수에 도달했습니다. 회의 소유자에게 연락하거나 나중에 다시 시도하십시오!",
"maxUsersLimitReachedTitle": "최대 참가자 수에 도달했습니다.",
@@ -222,10 +223,23 @@
"micNotSendingDataTitle": "시스템 설정에 의해 마이크가 음소거되었습니다.",
"micPermissionDeniedError": "마이크를 사용할 수있는 권한을 부여하지 않았습니다. 회의에 계속 참여할 수는 있지만 다른 사람들은 듣지 않습니다. 검색 주소창의 카메라 버튼을 사용하여 문제를 해결하십시오.",
"micUnknownError": "알 수 없는 이유로 마이크를 사용할 수 없습니다",
"muteEveryoneElseDialog": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteEveryoneElseTitle": "{{whom}} 를 제외하고 전부 음소거 하시겠습니까?",
"muteEveryoneDialog": "모든 참가자를 음소거 하시겠습니까? 당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteEveryoneTitle": "모두 음소거 하시겠습니까?",
"muteEveryoneElsesVideoDialog": "당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"muteEveryoneElsesVideoTitle": "{{whom}} 를 제외하고 전부 카메라를 비활성화 하시겠습니까?",
"muteEveryonesVideoDialog": "모든 참가자의 카메라를 비활성화 하시겠습니까? 당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"muteEveryonesVideoDialogOk": "비활성화",
"muteEveryonesVideoTitle": "모든 카메라를 비활성화 하시겠습니까?",
"muteEveryoneStartMuted": "지금부터 모두 음소거 됩니다.",
"muteParticipantBody": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteParticipantButton": "음소거",
"muteParticipantDialog": "",
"muteParticipantDialog": "이 참가자를 음소거 하시겠습니까? 당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteParticipantTitle": "이 참가자를 음소거 하시겠습니까?",
"muteParticipantsVideoButton": "카메라 비활성화",
"muteParticipantsVideoTitle": "이 참가자의 카메라를 비활성화 하시겠습니까?",
"muteParticipantsVideoBody": "당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"Ok": "확인",
"passwordLabel": "잠긴 회의입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
"passwordNotSupported": "회의 비밀번호 설정은 지원되지 않습니다",
@@ -233,7 +247,9 @@
"passwordRequired": "비밀번호 필수",
"popupError": "브라우저가이 사이트의 팝업 창을 차단하고 있습니다. 브라우저의 보안 설정에서 팝업을 활성화하고 다시 시도하십시오.",
"popupErrorTitle": "팝업 차단됨",
"readMore": "더보기",
"recording": "녹화",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "라이브 스트리밍 중에는 사용하실 수 없습니다.",
"recordingDisabledForGuestTooltip": "게스트는 녹화를 시작할 수 없습니다.",
"recordingDisabledTooltip": "녹화가 비활성화 되었습니다.",
"rejoinNow": "지금 재가입",
@@ -297,6 +313,14 @@
"documentSharing": {
"title": "문서 공유"
},
"virtualBackground": {
"title": "배경",
"enableBlur": "흐린 배경 활성화",
"removeBackground": "배경 제거",
"uploadImage": "이미지 업로드",
"pleaseWait": "잠시만 기다려주세요...",
"none": "없음"
},
"feedback": {
"average": "보통",
"bad": "나쁨",
@@ -322,12 +346,12 @@
"dialANumber": "회의에 참여하려면이 번호 중 하나를 누른 다음 PIN을 입력하십시오.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "죄송합니다. 현재 전화를 걸 수 없습니다.",
"dialInNumber": "Dial-in:",
"dialInNumber": "전화 접속:",
"dialInSummaryError": "지금 전화 접속 정보를 가져 오는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오.",
"dialInTollFree": "",
"genericError": "일반적인 오류가 발생했습니다",
"inviteLiveStream": "이 회의의 실시간 스트림을 보려면이 링크를 클릭하십시오: {{url}}",
"invitePhone": "",
"invitePhone": "폰으로 참여하려면, 이것을 누르십시오: {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "회의에 초대되었습니다.",
"inviteURLFirstPartPersonal": "{{name}}이 회의에 초대하였습니다.\n",
@@ -406,9 +430,9 @@
},
"localRecording": {
"clientState": {
"off": "",
"on": "",
"unknown": ""
"off": "꺼짐",
"on": "켜짐",
"unknown": "알 수 없음"
},
"dialogTitle": "",
"duration": "",
@@ -417,7 +441,7 @@
"label": "",
"labelToolTip": "",
"localRecording": "",
"me": "",
"me": "",
"messages": {
"engaged": "",
"finished": "",
@@ -454,6 +478,8 @@
"mutedTitle": "음소거 상태입니다!",
"mutedRemotelyTitle": "{{participantDisplayName}}에 의해 음소거되었습니다!",
"mutedRemotelyDescription": "말할 준비가되면 언제든지 음소거를 해제 할 수 있습니다.",
"videoMutedRemotelyTitle": "{{participantDisplayName}}에 의해 카메라가 비활성화되었습니다!",
"videoMutedRemotelyDescription": "언제든지 카메라를 다시 켤 수 있습니다.",
"passwordRemovedRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 제거했습니다.",
"passwordSetRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 설정했습니다.",
"raisedHand": "{{name}}님이 말하고 싶어합니다.",
@@ -518,6 +544,12 @@
"sectionList": {
"pullToRefresh": "당겨서 새로고침"
},
"security": {
"about": "회의에 $t(lockRoomPassword)를 추가할 수 있습니다. 참가자는 회의에 참여하기 위해 $t(lockRoomPassword)를 입력해야합니다.",
"aboutReadOnly": "방장은 회의에 $t(lockRoomPassword)를 추가할 수 있습니다. 참가자는 회의에 참여하기 위해 $t(lockRoomPassword)를 입력해야합니다.",
"insecureRoomNameWarning": "원하지 않은 참가자가 회의에 참여할 수 있습니다. 보안 옵션 버튼을 통해 회의를 보호하는 것을 고려하십시오.",
"securityOptions": "보안 옵션"
},
"settings": {
"calendar": {
"about": "{{appName}} 캘린더 통합은 예정된 일정을 읽을 수 있도록 캘린더에 안전하게 액세스하는 데 사용됩니다.",
@@ -582,40 +614,49 @@
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "",
"audioOnly": "음성 전용 모드 전환",
"audioRoute": "음성 장비 선택하기",
"callQuality": "",
"cc": "",
"chat": "",
"document": "",
"callQuality": "비디오 품질 관리",
"cc": "자막 사용 전환",
"chat": "채팅창 보이기 전환",
"document": "문서 전환",
"feedback": "피드백 남기기",
"fullScreen": "",
"hangup": "",
"invite": "",
"kick": "",
"fullScreen": "전체 화면 전환",
"hangup": "떠나기",
"help": "도움말",
"invite": "사용자 초대",
"kick": "참가자 추방",
"lobbyButton": "로비 모드 활성화/비활성화",
"localRecording": "",
"lockRoom": "",
"lockRoom": "회의 비밀번호 전환",
"moreActions": "",
"moreActionsMenu": "",
"mute": "",
"mute": "음소거 전환",
"muteEveryone": "모두 음소거",
"muteEveryoneElse": "다른 사람 모두 음소거",
"muteEveryonesVideo": "모든 카메라 비활성화",
"muteEveryoneElsesVideo": "다른 사람의 카메라 모두 비활성화",
"pip": "",
"profile": "",
"raiseHand": "",
"recording": "",
"remoteMute": "",
"Settings": "",
"sharedvideo": "",
"shareRoom": "",
"shareYourScreen": "",
"privateMessage": "비공개 메세지 보내기",
"profile": "프로필 수정",
"raiseHand": "손 들기",
"recording": "녹화 전환",
"remoteMute": "참가자 음소거",
"Settings": "설정 전환",
"sharedvideo": "YouTube 비디오 공유 전환",
"shareRoom": "초대하기",
"shareYourScreen": "화면 공유 전환",
"shortcuts": "단축키 전환",
"show": "",
"speakerStats": "",
"tileView": "",
"speakerStats": "접속자 통계 전환",
"tileView": "타일뷰 전환",
"toggleCamera": "카메라 전환",
"videomute": "",
"videoblur": ""
"videomute": "비디오 비활성화 전환",
"videoblur": "",
"selectBackground": "배경 선택"
},
"addPeople": "통화에 사용자 추가",
"audioSettings": "오디오 설정",
"audioOnlyOff": "음성전용 모드 끄기",
"audioOnlyOn": "음성전용 모드 끄기",
"audioRoute": "음성 장비 선택하기",
@@ -632,6 +673,7 @@
"exitTileView": "타일보기 종료",
"feedback": "피드백 남기기",
"hangup": "떠나기",
"help": "도움말",
"invite": "초대",
"login": "로그인",
"logout": "로그아웃",
@@ -646,6 +688,7 @@
"profile": "프로필 수정",
"raiseHand": "말하기 요청/해제",
"raiseYourHand": "손 들어주세요",
"security": "보안 옵션",
"Settings": "설정",
"sharedvideo": "YouTube 비디오 공유",
"shareRoom": "초대하기",
@@ -655,11 +698,13 @@
"startSubtitles": "자막 시작",
"stopScreenSharing": "화면 공유 중지",
"stopSubtitles": "자막 중지",
"stopSharedVideo": "UouTube 비디오 공유 중지",
"stopSharedVideo": "YouTube 비디오 공유 중지",
"talkWhileMutedPopup": "음소거 상태입니다.",
"tileViewToggle": "타일뷰 전환",
"toggleCamera": "카메라 전환",
"videomute": "카메라 시작/중지",
"videoSettings": "비디오 설정",
"selectBackground": "배경 선택",
"startvideoblur": "내 배경을 흐리게",
"stopvideoblur": "배경 흐림 비활성화"
},

View File

@@ -61,6 +61,7 @@
"today": "Vandaag"
},
"chat": {
"enter": "Chat openen",
"error": "Fout: uw bericht \"{{originalText}}\" is niet verzonden. Reden: {{error}}",
"fieldPlaceHolder": "Typ hier uw bericht",
"messagebox": "Typ een bericht",
@@ -101,6 +102,8 @@
"address": "Adres:",
"bandwidth": "Geschatte bandbreedte:",
"bitrate": "Bitrate:",
"audio_ssrc": "Audio SSRC:",
"codecs": "Codecs (A/V): ",
"bridgeCount": "Aantal servers: ",
"connectedTo": "Verbonden met:",
"e2e_rtt": "E2E RTT:",
@@ -125,9 +128,12 @@
"remoteport": "Externe poort:",
"remoteport_plural": "Externe poorten:",
"resolution": "Resolutie:",
"savelogs": "Logs opslaan",
"participant_id": "Deelnemer id:",
"status": "Verbinding:",
"transport": "Transport:",
"transport_plural": "Transporten:"
"transport_plural": "Transporten:",
"video_ssrc": "Video SSRC:"
},
"dateUtils": {
"earlier": "Eerder",
@@ -174,6 +180,7 @@
"cameraNotFoundError": "Camera niet gevonden.",
"cameraNotSendingData": "Er is geen toegang tot uw camera verkregen. Controleer of dit apparaat wordt gebruikt door een andere toepassing, selecteer een ander apparaat vanuit de instellingen of probeer de toepassing te herladen.",
"cameraNotSendingDataTitle": "Geen toegang tot camera",
"cameraTimeoutError": "Er heeft een camera timeout opgetreden.",
"cameraPermissionDeniedError": "U hebt geen toestemming verleend om uw camera te gebruiken. U kunt wel deelnemen aan de vergadering, maar anderen kunnen u niet zien. Gebruik de cameraknop in de adresbalk om dit op te lossen.",
"cameraUnknownError": "Kan de camera om een onbekende reden niet gebruiken.",
"cameraUnsupportedResolutionError": "Uw camera ondersteunt de vereiste videoresolutie niet.",
@@ -190,15 +197,13 @@
"connectErrorWithMsg": "Oeps! Er is iets misgegaan en er kon geen verbinding met de vergadering worden gemaakt: {{msg}}",
"connecting": "Verbinding maken",
"contactSupport": "Contact opnemen met ondersteuning",
"copied": "Gekopieerd",
"copy": "Kopiëren",
"dismiss": "Negeren",
"displayNameRequired": "Hallo! Wat is uw naam?",
"done": "Gereed",
"e2eeDescription": "Eind-tot-Eind-Versleuteling is momenteel EXPERIMENTEEL. Houd er rekening mee dat inschakelen van eind-tot-eind-versleuteling de door de server geleverde services zal uitschakelen zoals: opnemen, livestreamen en deelname via telefoon. Houd er ook rekening mee dat de vergadering alleen zal werken voor personen die deelnemen vanaf browsers met ondersteuning voor insertable streams.",
"e2eeLabel": "Sleutel",
"e2eeNoKey": "Geen",
"e2eeToggleSet": "Sleutel instellen",
"e2eeSet": "Instellen",
"e2eeWarning": "WAARSCHUWING: Niet alle deelnemers in deze vergadering lijken ondersteuning te hebben voor eind-tot-eind-versleuteling. Als u het inschakelt zullen zij u niet kunnen zien of horen.",
"enterDisplayName": "Voer hier uw naam in",
"error": "Fout",
@@ -232,6 +237,7 @@
"micNotSendingDataTitle": "Uw microfoon is gedempt door uw systeeminstellingen",
"micPermissionDeniedError": "U hebt geen toestemming verleend om uw microfoon te gebruiken. U kunt wel deelnemen aan de vergadering, maar anderen kunnen u niet horen. Gebruik de cameraknop in de adresbalk om dit op te lossen.",
"micUnknownError": "Kan de microfoon om een onbekende reden niet gebruiken.",
"micTimeoutError": "Kan de microfoon niet gebruiken vanwege een timeout fout.",
"muteEveryoneElseDialog": "Eenmaal gedempt kunt u het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.",
"muteEveryoneElseTitle": "Iedereen dempen behalve {{whom}}?",
"muteEveryoneDialog": "Weet u zeker dat u iedereen wilt dempen? U kunt het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.",
@@ -242,6 +248,13 @@
"muteParticipantButton": "Dempen",
"muteParticipantDialog": "Weet u zeker dat u deze deelnemer wilt dempen? U kunt het dempen niet opheffen, maar deze deelnemer kan dit wel ieder moment zelf doen.",
"muteParticipantTitle": "Deze deelnemer dempen?",
"muteEveryoneElsesVideoDialog": "Als u de camera's uitzet kan u hem niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.",
"muteEveryoneElsesVideoTitle": "De camera van iedereen behalve {{whom}} uitzetten?",
"muteEveryonesVideoDialog": "Weet u zeker dat u iedereen zijn camera uit wilt zetten? Als u de camera's uitzet kan u hem niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.",
"muteEveryonesVideoTitle": "Iedereen zijn camera uitzetten?",
"muteParticipantsVideoButton": "Camera uitzetten",
"muteParticipantsVideoTitle": "Camera van deze deelnemer uitzetten?",
"muteParticipantsVideoBody": "Het is niet mogelijk voor u om de camera weer aan te zetten, de deelnemer kan de camera wel weer aanzetten.",
"Ok": "OK",
"passwordLabel": "De vergadering is vergrendeld door een deelnemer. Voer het $t(lockRoomPassword) in om deel te nemen.",
"passwordNotSupported": "Instellen van een $t(lockRoomPassword) voor de vergadering wordt niet ondersteund.",
@@ -252,7 +265,6 @@
"readMore": "meer",
"recording": "Opnemen",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Niet mogelijk tijdens een livestream",
"recordingDisabledForGuestTooltip": "Gasten kunnen geen opnamen starten.",
"recordingDisabledTooltip": "Opname starten uitgeschakeld.",
"rejoinNow": "Nu opnieuw deelnemen",
"remoteControlAllowedMessage": "{{user}} heeft uw verzoek om extern beheer geaccepteerd.",
@@ -279,12 +291,12 @@
"sendPrivateMessageTitle": "Privé versturen?",
"serviceUnavailable": "Service niet beschikbaar",
"sessTerminated": "Gesprek beëindigd",
"sessionRestarted": "Gesprek herstart door de server",
"Share": "Delen",
"shareVideoLinkError": "Geef een juiste YouTube-link op",
"shareVideoTitle": "Een video delen",
"shareYourScreen": "Uw scherm delen",
"shareYourScreenDisabled": "Schermdeling is uitgeschakeld.",
"shareYourScreenDisabledForGuest": "Gasten kunnen hun scherm niet delen.",
"startLiveStreaming": "Livestream starten",
"startRecording": "Opname starten",
"startRemoteControlErrorMessage": "Er is een fout opgetreden tijdens het starten van de sessie van extern beheer.",
@@ -300,10 +312,11 @@
"tokenAuthFailedTitle": "Authenticering mislukt",
"transcribing": "Transcriberen",
"unlockRoom": "$t(lockRoomPasswordUppercase) voor vergadering verwijderen",
"user": "gebruiker",
"userPassword": "gebruikerswachtwoord",
"videoLink": "Video link",
"WaitForHostMsg": "De vergadering <b>{{room}}</b> is nog niet gestart. Authenticeer uzelf als u de host bent. Anders wacht u tot de host aanwezig is.",
"WaitForHostMsgWOk": "De vergadering <b>{{room}}</b> is nog niet gestart. Als u de host bent, drukt u op 'OK' om uzelf te authenticeren. Anders wacht u tot de host aanwezig is.",
"WaitingForHost": "Wachten op de host...",
"Yes": "Ja",
"yourEntireScreen": "Uw gehele scherm"
},
@@ -316,6 +329,17 @@
"e2ee": {
"labelToolTip": "Audio- en Videocommunicatie in dit gesprek is eind-tot-eind-versleuteld"
},
"embedMeeting": {
"title": "Deze vergadering embedden"
},
"virtualBackground": {
"title": "Achtergronden",
"enableBlur": "Vervagen inschakelen",
"removeBackground": "Verwijder achtergrond",
"uploadImage": "Afbeelding uploaden",
"pleaseWait": "Even geduld a.u.b...",
"none": "Geen"
},
"feedback": {
"average": "Gemiddeld",
"bad": "Slecht",
@@ -390,8 +414,7 @@
"toggleFilmstrip": "Videominiaturen weergeven of verbergen",
"toggleScreensharing": "Wisselen tussen camera en schermdeling",
"toggleShortcuts": "Sneltoetsen weergeven of verbergen",
"videoMute": "Uw camera starten of stoppen",
"videoQuality": "Kwaliteit van gesprek beheren"
"videoMute": "Uw camera starten of stoppen"
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Vanwege een grote vraag zal uw stream beperkt worden tot {{limit}} min. Voor ongelimiteerd streamen, probeer <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -477,6 +500,8 @@
"mutedTitle": "U bent gedempt!",
"mutedRemotelyTitle": "U bent gedempt door {{participantDisplayName}}!",
"mutedRemotelyDescription": "U kunt het dempen altijd opheffen wanneer u klaar bent om te spreken. Demp opnieuw wanneer u klaar bent, om ruis buiten de vergadering te houden.",
"videoMutedRemotelyTitle": "Uw camera is uitgezet door {{participantDisplayName}}!",
"videoMutedRemotelyDescription": "U kan hem ten alle tijden weer aanzetten.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) verwijderd door een andere deelnemer",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) ingesteld door een ander deelnemer",
"raisedHand": "{{name}} zou graag willen spreken.",
@@ -501,6 +526,7 @@
"audioAndVideoError": "Audio- en videofout:",
"audioOnlyError": "Audiofout:",
"audioTrackError": "Kon audiotrack niet aanmaken.",
"audioDeviceProblem": "Er is een probleem met uw microfoon",
"callMe": "Bel me",
"callMeAtNumber": "Bel me op dit nummer:",
"configuringDevices": "Apparaten instellen...",
@@ -668,7 +694,7 @@
"chat": "Chatvenster in- of uitschakelen",
"document": "Gedeeld document in- of uitschakelen",
"download": "Download onze apps",
"e2ee": "Eind-tot-eind-versleuteling",
"embedMeeting": "Vergadering inbedden",
"feedback": "Feedback achterlaten",
"fullScreen": "Volledig scherm in- of uitschakelen",
"grantModerator": "Moderatorrechten verlenen",
@@ -702,9 +728,10 @@
"toggleCamera": "Camera wisselen",
"toggleFilmstrip": "Filmstrip in- of uitschakelen",
"videomute": "Video dempen in- of uitschakelen",
"videoblur": "Video vervagen in- of uitschakelen"
"selectBackground": "Achtergrond selecteren"
},
"addPeople": "Personen aan uw gesprek toevoegen",
"audioSettings": "Audio-instellingen",
"audioOnlyOff": "Lage bandbreedte-modus uitschakelen",
"audioOnlyOn": "Lage bandbreedte-modus inschakelen",
"audioRoute": "Het afspeelapparaat selecteren",
@@ -716,6 +743,7 @@
"documentOpen": "Gedeeld document openen",
"download": "Download onze apps",
"e2ee": "Eind-tot-eind-versleuteling",
"embedMeeting": "Vergadering inbedden",
"enterFullScreen": "Volledig scherm weergeven",
"enterTileView": "Tegelweergave openen",
"exitFullScreen": "Volledig scherm sluiten",
@@ -733,6 +761,7 @@
"moreOptions": "Meer opties",
"mute": "Dempen / Dempen opheffen",
"muteEveryone": "Iedereen dempen",
"muteEveryonesVideo": "Camera's van iedereen uitzetten",
"noAudioSignalTitle": "Er komt geen invoer van uw microfoon!",
"noAudioSignalDesc": "Als u niet met opzet hebt gedempt vanuit systeeminstellingen of hardware, overweeg dan van apparaat te wisselen.",
"noAudioSignalDescSuggestion": "Als u niet met opzet hebt gedempt vanuit systeeminstellingen of hardware, overweeg dan over te schakelen naar het gesuggereerde apparaat.",
@@ -746,7 +775,7 @@
"profile": "Uw profiel bewerken",
"raiseHand": "Uw hand opsteken / laten zakken",
"raiseYourHand": "Uw hand opsteken",
"security": "Beveiligingsoptions",
"security": "Beveiligingsopties",
"Settings": "Instellingen",
"sharedvideo": "Een YouTube-video delen",
"shareRoom": "Iemand uitnodigen",
@@ -761,7 +790,8 @@
"tileViewToggle": "Tegelweergave in- of uitschakelen",
"toggleCamera": "Camera in- of uitschakelen",
"videomute": "Camera starten / stoppen",
"startvideoblur": "Vervaag mijn achtergrond",
"videoSettings": "Instellingen van camera",
"selectBackground": "Achtergrond selecteren",
"stopvideoblur": "Achtergrond vervagen uitschakelen"
},
"transcribing": {
@@ -810,8 +840,6 @@
"ld": "LD",
"ldTooltip": "U bekijkt video in lage resolutie",
"lowDefinition": "Lage resolutie",
"onlyAudioAvailable": "Alleen audio is beschikbaar",
"onlyAudioSupported": "In deze browser wordt alleen audio ondersteund.",
"sd": "SD",
"sdTooltip": "U bekijkt video in standaard resolutie",
"standardDefinition": "Standaard resolutie"
@@ -846,6 +874,8 @@
"getHelp": "Hulp krijgen",
"go": "GAAN",
"goSmall": "GAAN",
"headerTitle": "Jitsi Meet",
"headerSubtitle": "Veilige vergaderingen van hoge kwaliteit",
"info": "Informatie",
"join": "AANMAKEN / DEELNEMEN",
"moderatedMessage": "Of <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">boek een vergadering URL</a> van tevoren waar u de enige moderator bent.",
@@ -858,6 +888,7 @@
"roomname": "Voer naam van ruimte in",
"roomnameHint": "Voer de naam of URL in van de ruimte die u wilt betreden. U kunt een naam verzinnen, maar laat deze wel weten aan de andere deelnemers, zodat zij dezelfde naam invoeren.",
"sendFeedback": "Feedback versturen",
"startMeeting": "Vergadering starten",
"terms": "Voorwaarden",
"title": "Veilige, volledig uitgeruste en geheel gratis videovergaderingen"
},

View File

@@ -4,7 +4,7 @@
"addContacts": "Пригласите других людей",
"copyInvite": "Скопировать приглашение на встречу",
"copyLink": "Скопировать ссылку на встречу",
"copyStream": "Скопировать ссылку на прямую транасляцию",
"copyStream": "Скопировать ссылку на прямую трансляцию",
"countryNotSupported": "Эта страна пока не поддерживается.",
"countryReminder": "Вызов не в США? Пожалуйста, убедитесь, что указали код страны!",
"defaultEmail": "Ваш адрес электронной почты",
@@ -472,7 +472,7 @@
"knockingParticipantList": "Список ожидающих участников",
"nameField": "Введите ваше имя",
"notificationLobbyAccessDenied": "{{originParticipantName}} запретил присоединиться {{targetParticipantName}}",
"notificationLobbyAccessGranted": "{{originParticipantName}}разрешил присоединиться {{targetParticipantName}} ",
"notificationLobbyAccessGranted": "{{originParticipantName}} разрешил присоединиться {{targetParticipantName}} ",
"notificationLobbyDisabled": "Лобби отключено пользователем {{originParticipantName}}",
"notificationLobbyEnabled": "Лобби включено пользователем {{originParticipantName}}",
"notificationTitle": "Лобби",

View File

@@ -113,6 +113,7 @@
"maxEnabledResolution": "send max",
"more": "Zobraziť viac",
"packetloss": "Strata paketov:",
"participant_id": "ID účastníka:",
"quality": {
"good": "Dobré",
"inactive": "Neaktívne",
@@ -316,10 +317,13 @@
"e2ee": {
"labelToolTip": "Zvuková a obrazová komunikácia je koncovo šifrovaná"
},
"embedMeeting": {
"title": "Vložiť toto stretnutie"
},
"feedback": {
"average": "Priemerný",
"bad": "Zlý",
"detailsLabel": "Povedzte nám viac.",
"detailsLabel": "Povedzte nám viac",
"good": "Dobrý",
"rateExperience": "Ohodnoťte dojem",
"veryBad": "Veľmi zlý",
@@ -762,7 +766,8 @@
"toggleCamera": "Zmeniť kameru",
"videomute": "Vypnúť / Zapnúť kameru",
"startvideoblur": "Rozmazať pozadie",
"stopvideoblur": "Ukončiť rozmazanie pozadia"
"stopvideoblur": "Ukončiť rozmazanie pozadia",
"selectBackground": "Vybrať pozadie"
},
"transcribing": {
"ccButtonTooltip": "titulky vypnuť/zapnúť",
@@ -827,7 +832,9 @@
"muted": "Vypnutý mikrofón",
"remoteControl": "Vzdialené ovládanie",
"show": "Ukázať v popredí",
"videomute": "Účastník vypol kameru"
"videomute": "Účastník vypol kameru",
"videoMuted": "Vypnutá kamera",
"domuteVideoOfOthers": "Vypnúť kamery ostatným"
},
"welcomepage": {
"accessibilityLabel": {

View File

@@ -28,6 +28,7 @@
"shareInvite": "Share meeting invitation",
"shareLink": "Share the meeting link to invite others",
"shareStream": "Share the live streaming link",
"sip": "SIP: {{address}}",
"telephone": "Telephone: {{number}}",
"title": "Invite people to this meeting",
"yahooEmail": "Yahoo Email"
@@ -175,6 +176,7 @@
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
"applicationWindow": "Application window",
"authenticationRequired": "Authentication required",
"Back": "Back",
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
"cameraNotFoundError": "Camera was not found.",
@@ -227,6 +229,7 @@
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
"lockTitle": "Lock failed",
"logoutQuestion": "Are you sure you want to logout and stop the conference?",
"login": "Login",
"logoutTitle": "Logout",
"maxUsersLimitReached": "The limit for maximum number of participants has been reached. The conference is full. Please contact the meeting owner or try again later!",
"maxUsersLimitReachedTitle": "Maximum participants limit reached",
@@ -244,6 +247,7 @@
"muteEveryoneElsesVideoDialog": "Once the camera is disabled, you won't be able to turn it back on, but they can turn it back on at any time.",
"muteEveryoneElsesVideoTitle": "Disable everyone's camera except {{whom}}?",
"muteEveryonesVideoDialog": "Are you sure you want to disable everyone's camera? You won't be able to turn it back on, but they can turn it back on at any time.",
"muteEveryonesVideoDialogOk": "Disable",
"muteEveryonesVideoTitle": "Disable everyone's camera?",
"muteEveryoneSelf": "yourself",
"muteEveryoneStartMuted": "Everyone starts muted from now on",
@@ -311,12 +315,13 @@
"tokenAuthFailedTitle": "Authentication failed",
"transcribing": "Transcribing",
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
"user": "user",
"userPassword": "user password",
"user": "User",
"userIdentifier": "User identifier",
"userPassword": "User password",
"videoLink": "Video link",
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
"WaitingForHost": "Waiting for the host ...",
"WaitingForHostTitle": "Waiting for the host ...",
"Yes": "Yes",
"yourEntireScreen": "Your entire screen"
},
@@ -335,7 +340,10 @@
"virtualBackground": {
"title": "Backgrounds",
"enableBlur": "Enable blur",
"removeBackground": "Remove background"
"removeBackground": "Remove background",
"uploadImage": "Upload image",
"pleaseWait": "Please wait...",
"none": "None"
},
"feedback": {
"average": "Average",
@@ -815,6 +823,7 @@
"tileViewToggle": "Toggle tile view",
"toggleCamera": "Toggle camera",
"videomute": "Start / Stop camera",
"videoSettings": "Video settings",
"selectBackground": "Select background"
},
"transcribing": {

View File

@@ -16,7 +16,15 @@ import { overwriteConfig, getWhitelistedJSON } from '../../react/features/base/c
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../react/features/base/media';
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
import {
getLocalParticipant,
getParticipantById,
participantUpdated,
pinParticipant,
kickParticipant
} from '../../react/features/base/participants';
import { updateSettings } from '../../react/features/base/settings';
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
import { openChat } from '../../react/features/chat/actions.web';
import {
@@ -33,8 +41,8 @@ import {
import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
import { RECORDING_TYPES } from '../../react/features/recording/constants';
import { getActiveSession } from '../../react/features/recording/functions';
import { muteAllParticipants } from '../../react/features/remote-video-menu/actions';
import { toggleTileView } from '../../react/features/video-layout';
import { toggleTileView, setTileView } from '../../react/features/video-layout';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality';
import { getJitsiMeetTransport } from '../transport';
@@ -163,10 +171,40 @@ function initCommands() {
sendAnalytics(createApiEvent('film.strip.toggled'));
APP.UI.toggleFilmstrip();
},
'toggle-camera': () => {
if (!isToggleCameraEnabled(APP.store.getState())) {
return;
}
APP.store.dispatch(toggleCamera());
},
'toggle-camera-mirror': () => {
const state = APP.store.getState();
const { localFlipX: currentFlipX } = state['features/base/settings'];
APP.store.dispatch(updateSettings({ localFlipX: !currentFlipX }));
},
'toggle-chat': () => {
sendAnalytics(createApiEvent('chat.toggled'));
APP.UI.toggleChat();
},
'toggle-raise-hand': () => {
const localParticipant = getLocalParticipant(APP.store.getState());
if (!localParticipant) {
return;
}
const { raisedHand } = localParticipant;
sendAnalytics(createApiEvent('raise-hand.toggled'));
APP.store.dispatch(
participantUpdated({
id: APP.conference.getMyUserId(),
local: true,
raisedHand: !raisedHand
})
);
},
/**
* Callback to invoke when the "toggle-share-screen" command is received.
@@ -186,6 +224,9 @@ function initCommands() {
APP.store.dispatch(toggleTileView());
},
'set-tile-view': enabled => {
APP.store.dispatch(setTileView(enabled));
},
'video-hangup': (showFeedbackDialog = true) => {
sendAnalytics(createApiEvent('video.hangup'));
APP.conference.hangup(showFeedbackDialog);
@@ -1124,6 +1165,23 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that recording has started or stopped.
*
* @param {boolean} on - True if recording is on, false otherwise.
* @param {string} mode - Stream or file.
* @param {string} error - Error type or null if success.
* @returns {void}
*/
notifyRecordingStatusChanged(on: boolean, mode: string, error?: string) {
this._sendEvent({
name: 'recording-status-changed',
on,
mode,
error
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -44,14 +44,18 @@ const commands = {
sendEndpointTextMessage: 'send-endpoint-text-message',
sendTones: 'send-tones',
setLargeVideoParticipant: 'set-large-video-participant',
setTileView: 'set-tile-view',
setVideoQuality: 'set-video-quality',
startRecording: 'start-recording',
stopRecording: 'stop-recording',
subject: 'subject',
submitFeedback: 'submit-feedback',
toggleAudio: 'toggle-audio',
toggleCamera: 'toggle-camera',
toggleCameraMirror: 'toggle-camera-mirror',
toggleChat: 'toggle-chat',
toggleFilmStrip: 'toggle-film-strip',
toggleRaiseHand: 'toggle-raise-hand',
toggleShareScreen: 'toggle-share-screen',
toggleTileView: 'toggle-tile-view',
toggleVideo: 'toggle-video'
@@ -86,6 +90,7 @@ const events = {
'password-required': 'passwordRequired',
'proxy-connection-event': 'proxyConnectionEvent',
'raise-hand-updated': 'raiseHandUpdated',
'recording-status-changed': 'recordingStatusChanged',
'video-ready-to-close': 'readyToClose',
'video-conference-joined': 'videoConferenceJoined',
'video-conference-left': 'videoConferenceLeft',
@@ -197,7 +202,7 @@ function parseArguments(args) {
* @param {any} value - The value to be parsed.
* @returns {string|undefined} The parsed value that can be used for setting
* sizes through the style property. If invalid value is passed the method
* retuns undefined.
* returns undefined.
*/
function parseSizeParam(value) {
let parsedValue;

View File

@@ -7,6 +7,7 @@ import EventEmitter from 'events';
import Logger from 'jitsi-meet-logger';
import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { setColorAlpha } from '../../react/features/base/util';
import { toggleChat } from '../../react/features/chat';
import { setDocumentUrl } from '../../react/features/etherpad';
import { setFilmstripVisible } from '../../react/features/filmstrip';
@@ -115,7 +116,6 @@ UI.start = function() {
// Set the defaults for prompt dialogs.
$.prompt.setDefaults({ persistent: false });
VideoLayout.init(eventEmitter);
VideoLayout.initLargeVideo();
// Do not animate the video area on UI start (second argument passed into
@@ -130,17 +130,22 @@ UI.start = function() {
$('body').addClass('mobile-browser');
} else {
$('body').addClass('desktop-browser');
if (config.backgroundAlpha !== undefined) {
const backgroundColor = $('body').css('background-color');
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
$('body').css('background-color', alphaColor);
}
}
if (config.iAmRecorder) {
// in case of iAmSipGateway keep local video visible
if (!config.iAmSipGateway) {
VideoLayout.setLocalVideoVisible(false);
APP.store.dispatch(setNotificationsEnabled(false));
}
APP.store.dispatch(setToolboxEnabled(false));
UI.messageHandler.enablePopups(false);
}
};
@@ -179,14 +184,6 @@ UI.unbindEvents = () => {
$(window).off('resize');
};
/**
* Show local video stream on UI.
* @param {JitsiTrack} track stream to show
*/
UI.addLocalVideoStream = track => {
VideoLayout.changeLocalVideo(track);
};
/**
* Setup and show Etherpad.
* @param {string} name etherpad id
@@ -227,14 +224,6 @@ UI.addUser = function(user) {
}
};
/**
* Update videotype for specified user.
* @param {string} id user id
* @param {string} newVideoType new videotype
*/
UI.onPeerVideoTypeChanged
= (id, newVideoType) => VideoLayout.onVideoTypeChanged(id, newVideoType);
/**
* Updates the user status.
*
@@ -289,19 +278,14 @@ UI.setAudioMuted = function(id) {
* Sets muted video state for participant
*/
UI.setVideoMuted = function(id) {
VideoLayout.onVideoMute(id);
VideoLayout._updateLargeVideoIfDisplayed(id, true);
if (APP.conference.isLocalId(id)) {
APP.conference.updateVideoIconEnabled();
}
};
/**
* Triggers an update of remote video and large video displays so they may pick
* up any state changes that have occurred elsewhere.
*
* @returns {void}
*/
UI.updateAllVideos = () => VideoLayout.updateAllVideos();
UI.updateLargeVideo = (id, forceUpdate) => VideoLayout.updateLargeVideo(id, forceUpdate);
/**
* Adds a listener that would be notified on the given type of event.
@@ -340,8 +324,6 @@ UI.removeListener = function(type, listener) {
*/
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
UI.clickOnVideo = videoNumber => VideoLayout.togglePin(videoNumber);
// Used by torture.
UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));

View File

@@ -1,25 +1,22 @@
/* global APP, config, JitsiMeetJS, Promise */
// @flow
import Logger from 'jitsi-meet-logger';
import { openConnection } from '../../../connection';
import { setJWT } from '../../../react/features/base/jwt';
import {
JitsiConnectionErrors
} from '../../../react/features/base/lib-jitsi-meet';
isTokenAuthEnabled,
getTokenAuthUrl
} from '../../../react/features/authentication/functions';
import { setJWT } from '../../../react/features/base/jwt';
import UIUtil from '../util/UIUtil';
import LoginDialog from './LoginDialog';
let externalAuthWindow;
declare var APP: Object;
const logger = Logger.getLogger(__filename);
let externalAuthWindow;
let authRequiredDialog;
const isTokenAuthEnabled
= typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length;
const getTokenAuthUrl
= JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl);
/**
* Authenticate using external service or just focus
@@ -29,6 +26,8 @@ const getTokenAuthUrl
* @param {string} [lockPassword] password to use if the conference is locked
*/
function doExternalAuth(room, lockPassword) {
const config = APP.store.getState()['features/base/config'];
if (externalAuthWindow) {
externalAuthWindow.focus();
@@ -37,8 +36,8 @@ function doExternalAuth(room, lockPassword) {
if (room.isJoined()) {
let getUrl;
if (isTokenAuthEnabled) {
getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true));
if (isTokenAuthEnabled(config)) {
getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
initJWTTokenListener(room);
} else {
getUrl = room.getExternalAuthUrl(true);
@@ -48,13 +47,13 @@ function doExternalAuth(room, lockPassword) {
url,
() => {
externalAuthWindow = null;
if (!isTokenAuthEnabled) {
if (!isTokenAuthEnabled(config)) {
room.join(lockPassword);
}
}
);
});
} else if (isTokenAuthEnabled) {
} else if (isTokenAuthEnabled(config)) {
redirectToTokenAuthService(room.getName());
} else {
room.getExternalAuthUrl().then(UIUtil.redirect);
@@ -63,20 +62,22 @@ function doExternalAuth(room, lockPassword) {
/**
* Redirect the user to the token authentication service for the login to be
* performed. Once complete it is expected that the service wil bring the user
* performed. Once complete it is expected that the service will bring the user
* back with "?jwt={the JWT token}" query parameter added.
* @param {string} [roomName] the name of the conference room.
*/
function redirectToTokenAuthService(roomName) {
export function redirectToTokenAuthService(roomName: string) {
const config = APP.store.getState()['features/base/config'];
// FIXME: This method will not preserve the other URL params that were
// originally passed.
UIUtil.redirect(getTokenAuthUrl(roomName, false));
UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
}
/**
* Initializes 'message' listener that will wait for a JWT token to be received
* from the token authentication service opened in a popup window.
* @param room the name fo the conference room.
* @param room the name of the conference room.
*/
function initJWTTokenListener(room) {
/**
@@ -108,7 +109,7 @@ function initJWTTokenListener(room) {
roomName, APP.conference._getConferenceOptions());
// Authenticate from the new connection to get
// the session-ID from the focus, which wil then be used
// the session-ID from the focus, which will then be used
// to upgrade current connection's user role
newRoom.room.moderator.authenticate()
@@ -116,7 +117,7 @@ function initJWTTokenListener(room) {
connection.disconnect();
// At this point we'll have session-ID stored in
// the settings. It wil be used in the call below
// the settings. It will be used in the call below
// to upgrade user's role
room.room.moderator.authenticate()
.then(() => {
@@ -157,58 +158,15 @@ function initJWTTokenListener(room) {
}
/**
* Authenticate on the server.
* @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked
*/
function doXmppAuth(room, lockPassword) {
const loginDialog = LoginDialog.showAuthDialog(
/* successCallback */ (id, password) => {
room.authenticateAndUpgradeRole({
id,
password,
roomPassword: lockPassword,
/** Called when the XMPP login succeeds. */
onLoginSuccessful() {
loginDialog.displayConnectionStatus(
'connection.FETCH_SESSION_ID');
}
})
.then(
/* onFulfilled */ () => {
loginDialog.displayConnectionStatus(
'connection.GOT_SESSION_ID');
loginDialog.close();
},
/* onRejected */ error => {
logger.error('authenticateAndUpgradeRole failed', error);
const { authenticationError, connectionError } = error;
if (authenticationError) {
loginDialog.displayError(
'connection.GET_SESSION_ID_ERROR',
{ msg: authenticationError });
} else if (connectionError) {
loginDialog.displayError(connectionError);
}
});
},
/* cancelCallback */ () => loginDialog.close());
}
/**
* Authenticate for the conference.
* Uses external service for auth if conference supports that.
* @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked
*/
function authenticate(room, lockPassword) {
if (isTokenAuthEnabled || room.isExternalAuthEnabled()) {
function authenticateExternal(room: Object, lockPassword: string) {
const config = APP.store.getState()['features/base/config'];
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
doExternalAuth(room, lockPassword);
} else {
doXmppAuth(room, lockPassword);
}
}
@@ -219,7 +177,7 @@ function authenticate(room, lockPassword) {
* @param {string} [lockPassword] password to use if the conference is locked
* @returns {Promise}
*/
function logout(room) {
function logout(room: Object) {
return new Promise(resolve => {
room.room.moderator.logout(resolve);
}).then(url => {
@@ -232,83 +190,7 @@ function logout(room) {
});
}
/**
* Notify user that authentication is required to create the conference.
* @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked
*/
function requireAuth(room, lockPassword) {
if (authRequiredDialog) {
return;
}
authRequiredDialog = LoginDialog.showAuthRequiredDialog(
room.getName(), authenticate.bind(null, room, lockPassword)
);
}
/**
* Close auth-related dialogs if there are any.
*/
function closeAuth() {
if (externalAuthWindow) {
externalAuthWindow.close();
externalAuthWindow = null;
}
if (authRequiredDialog) {
authRequiredDialog.close();
authRequiredDialog = null;
}
}
/**
*
*/
function showXmppPasswordPrompt(roomName, connect) {
return new Promise((resolve, reject) => {
const authDialog = LoginDialog.showAuthDialog(
(id, password) => {
connect(id, password, roomName).then(connection => {
authDialog.close();
resolve(connection);
}, err => {
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
authDialog.displayError(err);
} else {
authDialog.close();
reject(err);
}
});
}
);
});
}
/**
* Show Authentication Dialog and try to connect with new credentials.
* If failed to connect because of PASSWORD_REQUIRED error
* then ask for password again.
* @param {string} [roomName] name of the conference room
* @param {function(id, password, roomName)} [connect] function that returns
* a Promise which resolves with JitsiConnection or fails with one of
* JitsiConnectionErrors.
* @returns {Promise<JitsiConnection>}
*/
function requestAuth(roomName, connect) {
if (isTokenAuthEnabled) {
// This Promise never resolves as user gets redirected to another URL
return new Promise(() => redirectToTokenAuthService(roomName));
}
return showXmppPasswordPrompt(roomName, connect);
}
export default {
authenticate,
requireAuth,
requestAuth,
closeAuth,
authenticateExternal,
logout
};

View File

@@ -212,45 +212,5 @@ export default {
}
return dialog;
},
/**
* Shows a notification that authentication is required to create the
* conference, so the local participant should authenticate or wait for a
* host.
*
* @param {string} room - The name of the conference.
* @param {function} onAuthNow - The callback to invoke if the local
* participant wants to authenticate.
* @returns dialog
*/
showAuthRequiredDialog(room, onAuthNow) {
const msg = APP.translation.generateTranslationHTML(
'[html]dialog.WaitForHostMsg',
{ room }
);
const buttonTxt = APP.translation.generateTranslationHTML(
'dialog.IamHost'
);
const buttons = [ {
title: buttonTxt,
value: 'authNow'
} ];
return APP.UI.messageHandler.openDialog(
'dialog.WaitingForHost',
msg,
true,
buttons,
(e, submitValue) => {
// Do not close the dialog yet.
e.preventDefault();
// Open login popup.
if (submitValue === 'authNow') {
onAuthNow();
}
}
);
}
};

View File

@@ -498,7 +498,7 @@ export default class SharedVideoManager {
* Receives events for local audio mute/unmute by local user.
* @param muted boolena whether it is muted or not.
* @param {boolean} indicates if this mute was a result of user interaction,
* i.e. pressing the mute button or it was programatically triggerred
* i.e. pressing the mute button or it was programmatically triggered
*/
onLocalAudioMuted(muted, userInteraction) {
if (!this.player) {

View File

@@ -1,67 +0,0 @@
/* global $, APP */
/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../react/features/base/i18n';
import { Thumbnail } from '../../../react/features/filmstrip';
import SmallVideo from '../videolayout/SmallVideo';
/* eslint-enable no-unused-vars */
/**
*
*/
export default class SharedVideoThumb extends SmallVideo {
/**
*
* @param {*} participant
*/
constructor(participant) {
super();
this.id = participant.id;
this.isLocal = false;
this.url = participant.id;
this.videoSpanId = 'sharedVideoContainer';
this.container = this.createContainer(this.videoSpanId);
this.$container = $(this.container);
this.renderThumbnail();
this._setThumbnailSize();
this.bindHoverHandler();
this.container.onclick = this._onContainerClick;
}
/**
*
* @param {*} spanId
*/
createContainer(spanId) {
const container = document.createElement('span');
container.id = spanId;
container.className = 'videocontainer';
const remoteVideosContainer
= document.getElementById('filmstripRemoteVideosContainer');
const localVideoContainer
= document.getElementById('localVideoTileViewContainer');
remoteVideosContainer.insertBefore(container, localVideoContainer);
return container;
}
/**
* Renders the thumbnail.
*/
renderThumbnail(isHovered = false) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
</I18nextProvider>
</Provider>, this.container);
}
}

View File

@@ -12,12 +12,6 @@ import {
const logger = Logger.getLogger(__filename);
/**
* Flag for enabling/disabling popups.
* @type {boolean}
*/
let popupEnabled = true;
/**
* Currently displayed two button dialog.
* @type {null}
@@ -167,7 +161,7 @@ const messageHandler = {
let { classes } = options;
if (!popupEnabled || twoButtonDialog) {
if (twoButtonDialog) {
return null;
}
@@ -233,88 +227,6 @@ const messageHandler = {
return $.prompt.getApi();
},
/**
* Shows a message to the user with two buttons: first is given as a
* parameter and the second is Cancel.
*
* @param titleKey the key for the title of the message
* @param msgString the text of the message
* @param persistent boolean value which determines whether the message is
* persistent or not
* @param buttons object with the buttons. The keys must be the name of the
* button and value is the value that will be passed to
* submitFunction
* @param submitFunction function to be called on submit
* @param loadedFunction function to be called after the prompt is fully
* loaded
* @param closeFunction function to be called on dialog close
* @param {object} dontShowAgain - options for dont show again checkbox.
* @param {string} dontShowAgain.id the id of the checkbox.
* @param {string} dontShowAgain.textKey the key for the text displayed
* next to checkbox
* @param {boolean} dontShowAgain.checked if true the checkbox is foing to
* be checked
* @param {Array} dontShowAgain.buttonValues The button values that will
* trigger storing the checkbox value
* @param {string} dontShowAgain.localStorageKey the key for the local
* storage. if not provided dontShowAgain.id will be used.
*/
openDialog(// eslint-disable-line max-params
titleKey,
msgString,
persistent,
buttons,
submitFunction,
loadedFunction,
closeFunction,
dontShowAgain) {
if (!popupEnabled) {
return;
}
if (dontShowTheDialog(dontShowAgain)) {
// Maybe we should pass some parameters here? I'm not sure
// and currently we don't need any parameters.
submitFunction();
return;
}
const args = {
title: this._getFormattedTitleString(titleKey),
persistent,
buttons,
defaultButton: 1,
promptspeed: 0,
loaded() {
if (loadedFunction) {
// eslint-disable-next-line prefer-rest-params
loadedFunction.apply(this, arguments);
}
// Hide the close button
if (persistent) {
$('.jqiclose', this).hide();
}
},
submit: dontShowAgainSubmitFunctionWrapper(
dontShowAgain, submitFunction),
close: closeFunction,
classes: this._getDialogClasses()
};
if (persistent) {
args.closeText = '';
}
const dialog = $.prompt(
msgString + generateDontShowCheckbox(dontShowAgain), args);
APP.translation.translateElement(dialog);
return $.prompt.getApi();
},
/**
* Returns the formatted title string.
*
@@ -358,9 +270,6 @@ const messageHandler = {
* @param translateOptions options passed to translation
*/
openDialogWithStates(statesObject, options, translateOptions) {
if (!popupEnabled) {
return;
}
const { classes, size } = options;
const defaultClasses = this._getDialogClasses(size);
@@ -397,10 +306,6 @@ const messageHandler = {
*/
// eslint-disable-next-line max-params
openCenteredPopup(url, w, h, onPopupClosed) {
if (!popupEnabled) {
return;
}
const l = window.screenX + (window.innerWidth / 2) - (w / 2);
const t = window.screenY + (window.innerHeight / 2) - (h / 2);
const popup = window.open(
@@ -481,19 +386,6 @@ const messageHandler = {
notify(titleKey, messageKey, messageArguments) {
this.participantNotification(
null, titleKey, null, messageKey, messageArguments);
},
enablePopups(enable) {
popupEnabled = enable;
},
/**
* Returns true if dialog is opened
* false otherwise
* @returns {boolean} isOpened
*/
isDialogOpened() {
return Boolean($.prompt.getCurrentStateName());
}
};

View File

@@ -37,7 +37,7 @@ const UIUtil = {
* @param {string} url - The redirect URL.
* NOTE: Currently used to redirect to 3rd party location for
* authentication. In most cases redirectWithStoredParams action must be
* used instead of this method in order to preserve curent URL params.
* used instead of this method in order to preserve current URL params.
*/
redirect(url) {
window.location.href = url;

View File

@@ -25,129 +25,6 @@ const Filmstrip = {
*/
getVerticalFilmstripWidth() {
return isFilmstripVisible(APP.store) ? getVerticalFilmstripVisibleAreaWidth() : 0;
},
/**
* Resizes thumbnails for tile view.
*
* @param {number} width - The new width of the thumbnails.
* @param {number} height - The new height of the thumbnails.
* @param {boolean} forceUpdate
* @returns {void}
*/
resizeThumbnailsForTileView(width, height, forceUpdate = false) {
const thumbs = this._getThumbs(!forceUpdate);
if (thumbs.localThumb) {
thumbs.localThumb.css({
'padding-top': '',
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
if (thumbs.remoteThumbs) {
thumbs.remoteThumbs.css({
'padding-top': '',
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
},
/**
* Resizes thumbnails for horizontal view.
*
* @param {Object} dimensions - The new dimensions of the thumbnails.
* @param {boolean} forceUpdate
* @returns {void}
*/
resizeThumbnailsForHorizontalView({ local = {}, remote = {} }, forceUpdate = false) {
const thumbs = this._getThumbs(!forceUpdate);
if (thumbs.localThumb) {
const { height, width } = local;
thumbs.localThumb.css({
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
if (thumbs.remoteThumbs) {
const { height, width } = remote;
thumbs.remoteThumbs.css({
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
},
/**
* Resizes thumbnails for vertical view.
*
* @returns {void}
*/
resizeThumbnailsForVerticalView() {
const thumbs = this._getThumbs(true);
if (thumbs.localThumb) {
const heightToWidthPercent = 100 / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
thumbs.localThumb.css({
'padding-top': `${heightToWidthPercent}%`,
width: '',
height: '',
'min-width': '',
'min-height': ''
});
}
if (thumbs.remoteThumbs) {
const heightToWidthPercent = 100 / interfaceConfig.REMOTE_THUMBNAIL_RATIO;
thumbs.remoteThumbs.css({
'padding-top': `${heightToWidthPercent}%`,
width: '',
height: '',
'min-width': '',
'min-height': ''
});
}
},
/**
* Returns thumbnails of the filmstrip
* @param onlyVisible
* @returns {object} thumbnails
*/
_getThumbs(onlyVisible = false) {
let selector = 'span';
if (onlyVisible) {
selector += ':visible';
}
const localThumb = $('#localVideoContainer');
const remoteThumbs = $('#filmstripRemoteVideosContainer').children(selector);
// Exclude the local video container if it has been hidden.
if (localThumb.hasClass('hidden')) {
return { remoteThumbs };
}
return { remoteThumbs,
localThumb };
}
};

View File

@@ -22,7 +22,6 @@ import {
import { PresenceLabel } from '../../../react/features/presence-status';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
import { createDeferred } from '../../util/helpers';
import AudioLevels from '../audio_levels/AudioLevels';
@@ -51,21 +50,19 @@ export default class LargeVideoManager {
/**
*
*/
constructor(emitter) {
constructor() {
/**
* The map of <tt>LargeContainer</tt>s where the key is the video
* container type.
* @type {Object.<string, LargeContainer>}
*/
this.containers = {};
this.eventEmitter = emitter;
this.state = VIDEO_CONTAINER_TYPE;
// FIXME: We are passing resizeContainer as parameter which is calling
// Container.resize. Probably there's better way to implement this.
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE), emitter);
this.videoContainer = new VideoContainer(() => this.resizeContainer(VIDEO_CONTAINER_TYPE));
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
// use the same video container to handle desktop tracks
@@ -300,7 +297,6 @@ export default class LargeVideoManager {
// after everything is done check again if there are any pending
// new streams.
this.updateInProcess = false;
this.eventEmitter.emit(UIEvents.LARGE_VIDEO_ID_CHANGED, this.id);
this.scheduleLargeVideoUpdate();
});
}

View File

@@ -1,213 +0,0 @@
/* global $, config, APP */
/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../react/features/base/i18n';
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VideoTrack } from '../../../react/features/base/media';
import { updateSettings } from '../../../react/features/base/settings';
import { getLocalVideoTrack } from '../../../react/features/base/tracks';
import Thumbnail from '../../../react/features/filmstrip/components/web/Thumbnail';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
import SmallVideo from './SmallVideo';
/**
*
*/
export default class LocalVideo extends SmallVideo {
/**
*
* @param {*} emitter
* @param {*} streamEndedCallback
*/
constructor(emitter, streamEndedCallback) {
super();
this.videoSpanId = 'localVideoContainer';
this.streamEndedCallback = streamEndedCallback;
this.container = this.createContainer();
this.$container = $(this.container);
this.isLocal = true;
this._setThumbnailSize();
this.updateDOMLocation();
this.renderThumbnail();
this.localVideoId = null;
this.bindHoverHandler();
if (!config.disableLocalVideoFlip) {
this._buildContextMenu();
}
this.emitter = emitter;
Object.defineProperty(this, 'id', {
get() {
return APP.conference.getMyUserId();
}
});
this.initBrowserSpecificProperties();
this.container.onclick = this._onContainerClick;
}
/**
*
*/
createContainer() {
const containerSpan = document.createElement('span');
containerSpan.classList.add('videocontainer');
containerSpan.id = this.videoSpanId;
return containerSpan;
}
/**
* Renders the thumbnail.
*/
renderThumbnail(isHovered = false) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
</I18nextProvider>
</Provider>, this.container);
}
/**
*
* @param {*} stream
*/
changeVideo(stream) {
this.localVideoId = `localVideo_${stream.getId()}`;
// eslint-disable-next-line eqeqeq
const isVideo = stream.videoType != 'desktop';
const settings = APP.store.getState()['features/base/settings'];
this._enableDisableContextMenu(isVideo);
this.setFlipX(isVideo ? settings.localFlipX : false);
const endedHandler = () => {
this._notifyOfStreamEnded();
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
};
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
}
/**
* Notify any subscribers of the local video stream ending.
*
* @private
* @returns {void}
*/
_notifyOfStreamEnded() {
if (this.streamEndedCallback) {
this.streamEndedCallback(this.id);
}
}
/**
* Shows or hides the local video container.
* @param {boolean} true to make the local video container visible, false
* otherwise
*/
setVisible(visible) {
// We toggle the hidden class as an indication to other interested parties
// that this container has been hidden on purpose.
this.$container.toggleClass('hidden');
// We still show/hide it as we need to overwrite the style property if we
// want our action to take effect. Toggling the display property through
// the above css class didn't succeed in overwriting the style.
if (visible) {
this.$container.show();
} else {
this.$container.hide();
}
}
/**
* Sets the flipX state of the video.
* @param val {boolean} true for flipped otherwise false;
*/
setFlipX(val) {
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
if (!this.localVideoId) {
return;
}
if (val) {
this.selectVideoElement().addClass('flipVideoX');
} else {
this.selectVideoElement().removeClass('flipVideoX');
}
}
/**
* Builds the context menu for the local video.
*/
_buildContextMenu() {
$.contextMenu({
selector: `#${this.videoSpanId}`,
zIndex: 10000,
items: {
flip: {
name: 'Flip',
callback: () => {
const { store } = APP;
const val = !store.getState()['features/base/settings']
.localFlipX;
this.setFlipX(val);
store.dispatch(updateSettings({
localFlipX: val
}));
}
}
},
events: {
show(options) {
options.items.flip.name
= APP.translation.generateTranslationHTML(
'videothumbnail.flip');
}
}
});
}
/**
* Enables or disables the context menu for the local video.
* @param enable {boolean} true for enable, false for disable
*/
_enableDisableContextMenu(enable) {
if (this.$container.contextMenu) {
this.$container.contextMenu(enable);
}
}
/**
* Places the {@code LocalVideo} in the DOM based on the current video layout.
*
* @returns {void}
*/
updateDOMLocation() {
if (!this.container) {
return;
}
if (this.container.parentElement) {
this.container.parentElement.removeChild(this.container);
}
const appendTarget = shouldDisplayTileView(APP.store.getState())
? document.getElementById('localVideoTileViewContainer')
: document.getElementById('filmstripLocalVideoThumbnail');
appendTarget && appendTarget.appendChild(this.container);
}
}

View File

@@ -1,242 +0,0 @@
/* global $, APP, config */
/* eslint-disable no-unused-vars */
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import Logger from 'jitsi-meet-logger';
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../react/features/base/i18n';
import {
JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet';
import { getParticipantById } from '../../../react/features/base/participants';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
import { Thumbnail, isVideoPlayable } from '../../../react/features/filmstrip';
import { PresenceLabel } from '../../../react/features/presence-status';
import { stopController, requestRemoteControl } from '../../../react/features/remote-control';
import { RemoteVideoMenuTriggerButton } from '../../../react/features/remote-video-menu';
/* eslint-enable no-unused-vars */
import UIUtils from '../util/UIUtil';
import SmallVideo from './SmallVideo';
const logger = Logger.getLogger(__filename);
/**
* List of container events that we are going to process, will be added as listener to the
* container for every event in the list. The latest event will be stored in redux.
*/
const containerEvents = [
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
];
/**
*
* @param {*} spanId
*/
function createContainer(spanId) {
const container = document.createElement('span');
container.id = spanId;
container.className = 'videocontainer';
const remoteVideosContainer
= document.getElementById('filmstripRemoteVideosContainer');
const localVideoContainer
= document.getElementById('localVideoTileViewContainer');
remoteVideosContainer.insertBefore(container, localVideoContainer);
return container;
}
/**
*
*/
export default class RemoteVideo extends SmallVideo {
/**
* Creates new instance of the <tt>RemoteVideo</tt>.
* @param user {JitsiParticipant} the user for whom remote video instance will
* be created.
* @constructor
*/
constructor(user) {
super();
this.user = user;
this.id = user.getId();
this.videoSpanId = `participant_${this.id}`;
this.addRemoteVideoContainer();
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
/**
* The flag is set to <tt>true</tt> after the 'canplay' event has been
* triggered on the current video element. It goes back to <tt>false</tt>
* when the stream is removed. It is used to determine whether the video
* playback has ever started.
* @type {boolean}
*/
this._canPlayEventReceived = false;
this.container.onclick = this._onContainerClick;
}
/**
*
*/
addRemoteVideoContainer() {
this.container = createContainer(this.videoSpanId);
this.$container = $(this.container);
this.renderThumbnail();
this._setThumbnailSize();
this.initBrowserSpecificProperties();
return this.container;
}
/**
* Renders the thumbnail.
*/
renderThumbnail(isHovered = false) {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
</I18nextProvider>
</Provider>, this.container);
}
/**
* Removes the remote stream element corresponding to the given stream and
* parent container.
*
* @param stream the MediaStream
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
*/
removeRemoteStreamElement(stream) {
if (!this.container) {
return false;
}
const isVideo = stream.isVideoTrack();
const elementID = `remoteVideo_${stream.getId()}`;
const select = $(`#${elementID}`);
select.remove();
if (isVideo) {
this._canPlayEventReceived = false;
}
logger.info(`Video removed ${this.id}`, select);
this.updateView();
}
/**
* The remote video is considered "playable" once the can play event has been received.
*
* @inheritdoc
* @override
*/
isVideoPlayable() {
return isVideoPlayable(APP.store.getState(), this.id) && this._canPlayEventReceived;
}
/**
* @inheritDoc
*/
updateView() {
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
super.updateView();
}
/**
* Removes RemoteVideo from the page.
*/
remove() {
ReactDOM.unmountComponentAtNode(this.container);
super.remove();
}
/**
*
* @param {*} streamElement
* @param {*} stream
*/
waitForPlayback(streamElement, stream) {
$(streamElement).hide();
const webRtcStream = stream.getOriginalStream();
const isVideo = stream.isVideoTrack();
if (!isVideo || webRtcStream.id === 'mixedmslabel') {
return;
}
const listener = () => {
this._canPlayEventReceived = true;
logger.info(`${this.id} video is now active`, streamElement);
if (streamElement) {
$(streamElement).show();
}
streamElement.removeEventListener('canplay', listener);
// Refresh to show the video
this.updateView();
};
streamElement.addEventListener('canplay', listener);
}
/**
*
* @param {*} stream
*/
addRemoteStreamElement(stream) {
if (!this.container) {
logger.debug('Not attaching remote stream due to no container');
return;
}
const isVideo = stream.isVideoTrack();
if (!stream.getOriginalStream()) {
logger.debug('Remote video stream has no original stream');
return;
}
let streamElement = document.createElement('video');
streamElement.autoplay = !config.testing?.noAutoPlayVideo;
streamElement.id = `remoteVideo_${stream.getId()}`;
streamElement.mute = true;
streamElement.playsInline = true;
// Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement);
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
if (isVideo && isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
containerEvents.forEach(event => {
streamElement.addEventListener(event, cb.bind(this, event));
});
}
}
}

View File

@@ -1,509 +0,0 @@
/* global $, APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import Logger from 'jitsi-meet-logger';
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics';
import { AudioLevelIndicator } from '../../../react/features/audio-level-indicator';
import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar';
import { i18next } from '../../../react/features/base/i18n';
import { MEDIA_TYPE } from '../../../react/features/base/media';
import {
getLocalParticipant,
getParticipantById,
getParticipantCount,
getPinnedParticipant,
pinParticipant
} from '../../../react/features/base/participants';
import {
getLocalVideoTrack,
getTrackByMediaTypeAndParticipant,
isLocalTrackMuted,
isRemoteTrackMuted
} from '../../../react/features/base/tracks';
import { ConnectionIndicator } from '../../../react/features/connection-indicator';
import { DisplayName } from '../../../react/features/display-name';
import {
DominantSpeakerIndicator,
RaisedHandIndicator,
StatusIndicators,
isVideoPlayable
} from '../../../react/features/filmstrip';
import {
LAYOUTS,
getCurrentLayout,
setTileView,
shouldDisplayTileView
} from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
const logger = Logger.getLogger(__filename);
/**
* Display mode constant used when video is being displayed on the small video.
* @type {number}
* @constant
*/
const DISPLAY_VIDEO = 0;
/**
* Display mode constant used when the user's avatar is being displayed on
* the small video.
* @type {number}
* @constant
*/
const DISPLAY_AVATAR = 1;
/**
* Display mode constant used when neither video nor avatar is being displayed
* on the small video. And we just show the display name.
* @type {number}
* @constant
*/
const DISPLAY_BLACKNESS_WITH_NAME = 2;
/**
* Display mode constant used when video is displayed and display name
* at the same time.
* @type {number}
* @constant
*/
const DISPLAY_VIDEO_WITH_NAME = 3;
/**
* Display mode constant used when neither video nor avatar is being displayed
* on the small video. And we just show the display name.
* @type {number}
* @constant
*/
const DISPLAY_AVATAR_WITH_NAME = 4;
/**
*
*/
export default class SmallVideo {
/**
* Constructor.
*/
constructor() {
this.videoIsHovered = false;
this.videoType = undefined;
// Bind event handlers so they are only bound once for every instance.
this.updateView = this.updateView.bind(this);
this._onContainerClick = this._onContainerClick.bind(this);
}
/**
* Returns the identifier of this small video.
*
* @returns the identifier of this small video
*/
getId() {
return this.id;
}
/**
* Indicates if this small video is currently visible.
*
* @return <tt>true</tt> if this small video isn't currently visible and
* <tt>false</tt> - otherwise.
*/
isVisible() {
return this.$container.is(':visible');
}
/**
* Configures hoverIn/hoverOut handlers. Depends on connection indicator.
*/
bindHoverHandler() {
// Add hover handler
this.$container.hover(
() => {
this.videoIsHovered = true;
this.renderThumbnail(true);
this.updateView();
},
() => {
this.videoIsHovered = false;
this.renderThumbnail(false);
this.updateView();
}
);
}
/**
* Renders the thumbnail.
*/
renderThumbnail() {
// Should be implemented by in subclasses.
}
/**
* This is an especially interesting function. A naive reader might think that
* it returns this SmallVideo's "video" element. But it is much more exciting.
* It first finds this video's parent element using jquery, then uses a utility
* from lib-jitsi-meet to extract the video element from it (with two more
* jquery calls), and finally uses jquery again to encapsulate the video element
* in an array. This last step allows (some might prefer "forces") users of
* this function to access the video element via the 0th element of the returned
* array (after checking its length of course!).
*/
selectVideoElement() {
return $($(this.container).find('video')[0]);
}
/**
* Enables / disables the css responsible for focusing/pinning a video
* thumbnail.
*
* @param isFocused indicates if the thumbnail should be focused/pinned or not
*/
focus(isFocused) {
const focusedCssClass = 'videoContainerFocused';
const isFocusClassEnabled = this.$container.hasClass(focusedCssClass);
if (!isFocused && isFocusClassEnabled) {
this.$container.removeClass(focusedCssClass);
} else if (isFocused && !isFocusClassEnabled) {
this.$container.addClass(focusedCssClass);
}
}
/**
*
*/
hasVideo() {
return this.selectVideoElement().length !== 0;
}
/**
* Checks whether the user associated with this <tt>SmallVideo</tt> is currently
* being displayed on the "large video".
*
* @return {boolean} <tt>true</tt> if the user is displayed on the large video
* or <tt>false</tt> otherwise.
*/
isCurrentlyOnLargeVideo() {
return APP.store.getState()['features/large-video']?.participantId === this.id;
}
/**
* Checks whether there is a playable video stream available for the user
* associated with this <tt>SmallVideo</tt>.
*
* @return {boolean} <tt>true</tt> if there is a playable video stream available
* or <tt>false</tt> otherwise.
*/
isVideoPlayable() {
return isVideoPlayable(APP.store.getState(), this.id);
}
/**
* Determines what should be display on the thumbnail.
*
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
* or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
*/
selectDisplayMode(input) {
if (!input.tileViewActive && input.isScreenSharing) {
return input.isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
} else if (input.isCurrentlyOnLargeVideo && !input.tileViewActive) {
// Display name is always and only displayed when user is on the stage
return input.isVideoPlayable && !input.isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
} else if (input.isVideoPlayable && input.hasVideo && !input.isAudioOnly) {
// check hovering and change state to video with name
return input.isHovered ? DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
}
// check hovering and change state to avatar with name
return input.isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
}
/**
* Computes information that determine the display mode.
*
* @returns {Object}
*/
computeDisplayModeInput() {
let isScreenSharing = false;
let connectionStatus;
const state = APP.store.getState();
const id = this.id;
const participant = getParticipantById(state, id);
const isLocal = participant?.local ?? true;
const tracks = state['features/base/tracks'];
const videoTrack
= isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
if (typeof participant !== 'undefined' && !participant.isFakeParticipant && !participant.local) {
isScreenSharing = videoTrack?.videoType === 'desktop';
connectionStatus = participant.connectionStatus;
}
return {
isCurrentlyOnLargeVideo: this.isCurrentlyOnLargeVideo(),
isHovered: this._isHovered(),
isAudioOnly: APP.conference.isAudioOnly(),
tileViewActive: shouldDisplayTileView(state),
isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus,
canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(videoTrack),
isScreenSharing,
videoStreamMuted: videoTrack ? videoTrack.muted : 'no stream'
};
}
/**
* Checks whether current video is considered hovered. Currently it is hovered
* if the mouse is over the video, or if the connection
* indicator is shown(hovered).
* @private
*/
_isHovered() {
return this.videoIsHovered;
}
/**
* Updates the css classes of the thumbnail based on the current state.
*/
updateView() {
this.$container.removeClass((index, classNames) =>
classNames.split(' ').filter(name => name.startsWith('display-')));
const oldDisplayMode = this.displayMode;
let displayModeString = '';
const displayModeInput = this.computeDisplayModeInput();
// Determine whether video, avatar or blackness should be displayed
this.displayMode = this.selectDisplayMode(displayModeInput);
switch (this.displayMode) {
case DISPLAY_AVATAR_WITH_NAME:
displayModeString = 'avatar-with-name';
this.$container.addClass('display-avatar-with-name');
break;
case DISPLAY_BLACKNESS_WITH_NAME:
displayModeString = 'blackness-with-name';
this.$container.addClass('display-name-on-black');
break;
case DISPLAY_VIDEO:
displayModeString = 'video';
this.$container.addClass('display-video');
break;
case DISPLAY_VIDEO_WITH_NAME:
displayModeString = 'video-with-name';
this.$container.addClass('display-name-on-video');
break;
case DISPLAY_AVATAR:
default:
displayModeString = 'avatar';
this.$container.addClass('display-avatar-only');
break;
}
if (this.displayMode !== oldDisplayMode) {
logger.debug(`Displaying ${displayModeString} for ${this.id}, data: [${JSON.stringify(displayModeInput)}]`);
}
if (this.displayMode !== DISPLAY_VIDEO
&& this.displayMode !== DISPLAY_VIDEO_WITH_NAME
&& displayModeInput.tileViewActive
&& displayModeInput.isScreenSharing
&& !displayModeInput.isAudioOnly) {
// send the event
sendAnalytics(createScreenSharingIssueEvent({
source: 'thumbnail',
...displayModeInput
}));
}
}
/**
* Shows or hides the dominant speaker indicator.
* @param show whether to show or hide.
*/
showDominantSpeakerIndicator(show) {
// Don't create and show dominant speaker indicator if
// DISABLE_DOMINANT_SPEAKER_INDICATOR is true
if (interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR) {
return;
}
if (!this.container) {
logger.warn(`Unable to set dominant speaker indicator - ${this.videoSpanId} does not exist`);
return;
}
this.$container.toggleClass('active-speaker', show);
}
/**
* Initalizes any browser specific properties. Currently sets the overflow
* property for Qt browsers on Windows to hidden, thus fixing the following
* problem:
* Some browsers don't have full support of the object-fit property for the
* video element and when we set video object-fit to "cover" the video
* actually overflows the boundaries of its container, so it's important
* to indicate that the "overflow" should be hidden.
*
* Setting this property for all browsers will result in broken audio levels,
* which makes this a temporary solution, before reworking audio levels.
*/
initBrowserSpecificProperties() {
const userAgent = window.navigator.userAgent;
if (userAgent.indexOf('QtWebEngine') > -1
&& (userAgent.indexOf('Windows') > -1 || userAgent.indexOf('Linux') > -1)) {
this.$container.css('overflow', 'hidden');
}
}
/**
* Cleans up components on {@code SmallVideo} and removes itself from the DOM.
*
* @returns {void}
*/
remove() {
logger.log('Remove thumbnail', this.id);
this._unmountThumbnail();
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
}
/**
* Helper function for re-rendering multiple react components of the small
* video.
*
* @returns {void}
*/
rerender() {
this.updateView();
}
/**
* Callback invoked when the thumbnail is clicked and potentially trigger
* pinning of the participant.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {void}
*/
_onContainerClick(event) {
const triggerPin = this._shouldTriggerPin(event);
if (event.stopPropagation && triggerPin) {
event.stopPropagation();
event.preventDefault();
}
if (triggerPin) {
this.togglePin();
}
return false;
}
/**
* Returns whether or not a click event is targeted at certain elements which
* should not trigger a pin.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {boolean}
*/
_shouldTriggerPin(event) {
// TODO Checking the classes is a workround to allow events to bubble into
// the DisplayName component if it was clicked. React's synthetic events
// will fire after jQuery handlers execute, so stop propogation at this
// point will prevent DisplayName from getting click events. This workaround
// should be removeable once LocalVideo is a React Component because then
// the components share the same eventing system.
const $source = $(event.target || event.srcElement);
return $source.parents('.displayNameContainer').length === 0
&& $source.parents('.popover').length === 0
&& !event.target.classList.contains('popover');
}
/**
* Pins the participant displayed by this thumbnail or unpins if already pinned.
*
* @returns {void}
*/
togglePin() {
const pinnedParticipant = getPinnedParticipant(APP.store.getState()) || {};
const participantIdToPin = pinnedParticipant && pinnedParticipant.id === this.id ? null : this.id;
APP.store.dispatch(pinParticipant(participantIdToPin));
}
/**
* Unmounts the thumbnail.
*/
_unmountThumbnail() {
ReactDOM.unmountComponentAtNode(this.container);
}
/**
* Sets the size of the thumbnail.
*/
_setThumbnailSize() {
const layout = getCurrentLayout(APP.store.getState());
const heightToWidthPercent = 100
/ (this.isLocal ? interfaceConfig.LOCAL_THUMBNAIL_RATIO : interfaceConfig.REMOTE_THUMBNAIL_RATIO);
switch (layout) {
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
this.$container.css('padding-top', `${heightToWidthPercent}%`);
break;
}
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
const state = APP.store.getState();
const { local, remote } = state['features/filmstrip'].horizontalViewDimensions;
const size = this.isLocal ? local : remote;
if (typeof size !== 'undefined') {
const { height, width } = size;
this.$container.css({
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
break;
}
case LAYOUTS.TILE_VIEW: {
const state = APP.store.getState();
const { thumbnailSize } = state['features/filmstrip'].tileViewDimensions;
if (typeof thumbnailSize !== 'undefined') {
const { height, width } = thumbnailSize;
this.$container.css({
height: `${height}px`,
'min-height': `${height}px`,
'min-width': `${width}px`,
width: `${width}px`
});
}
break;
}
}
}
}

View File

@@ -9,7 +9,6 @@ import { isTestModeEnabled } from '../../../react/features/base/testing';
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
import Filmstrip from './Filmstrip';
@@ -46,7 +45,7 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
videoSpaceWidth,
videoSpaceHeight) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
// Avoid NaN values caused by division by 0.
return [ 0, 0 ];
}
@@ -94,7 +93,7 @@ function computeCameraVideoSize( // eslint-disable-line max-params
videoSpaceHeight,
videoLayoutFit) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
// Avoid NaN values caused by division by 0.
return [ 0, 0 ];
}
@@ -187,16 +186,13 @@ export class VideoContainer extends LargeContainer {
* Creates new VideoContainer instance.
* @param resizeContainer {Function} function that takes care of the size
* of the video container.
* @param emitter {EventEmitter} the event emitter that will be used by
* this instance.
*/
constructor(resizeContainer, emitter) {
constructor(resizeContainer) {
super();
this.stream = null;
this.userId = null;
this.videoType = null;
this.localFlipX = true;
this.emitter = emitter;
this.resizeContainer = resizeContainer;
/**
@@ -411,7 +407,7 @@ export class VideoContainer extends LargeContainer {
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight);
if (width === 0 || height === 0) {
// We don't need to set 0 for width or height since the visibility is controled by the visibility css prop
// We don't need to set 0 for width or height since the visibility is controlled by the visibility css prop
// on the largeVideoElementsContainer. Also if the width/height of the video element is 0 the attached
// stream won't be played. Normally if we attach a new stream we won't resize the video element until the
// stream has been played. But setting width/height to 0 will prevent the video from playing.
@@ -492,7 +488,7 @@ export class VideoContainer extends LargeContainer {
stream.attach(this.$video[0]);
const flipX = stream.isLocal() && this.localFlipX;
const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing();
this.$video.css({
transform: flipX ? 'scaleX(-1)' : 'none'
@@ -534,7 +530,6 @@ export class VideoContainer extends LargeContainer {
this.$avatar.css('visibility', show ? 'visible' : 'hidden');
this.avatarDisplayed = show;
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, show);
APP.API.notifyLargeVideoVisibilityChanged(show);
}

View File

@@ -4,88 +4,29 @@ import Logger from 'jitsi-meet-logger';
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
import {
getLocalParticipant as getLocalParticipantFromStore,
getPinnedParticipant,
getParticipantById,
pinParticipant
getParticipantById
} from '../../../react/features/base/participants';
import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
import UIEvents from '../../../service/UI/UIEvents';
import { SHARED_VIDEO_CONTAINER_TYPE } from '../shared_video/SharedVideo';
import SharedVideoThumb from '../shared_video/SharedVideoThumb';
import LargeVideoManager from './LargeVideoManager';
import LocalVideo from './LocalVideo';
import RemoteVideo from './RemoteVideo';
import { VIDEO_CONTAINER_TYPE } from './VideoContainer';
const logger = Logger.getLogger(__filename);
const remoteVideos = {};
let localVideoThumbnail = null;
let eventEmitter = null;
let largeVideo;
/**
* flipX state of the localVideo
*/
let localFlipX = null;
/**
* Handler for local flip X changed event.
* @param {Object} val
*/
function onLocalFlipXChanged(val) {
localFlipX = val;
if (largeVideo) {
largeVideo.onLocalFlipXChange(val);
}
}
/**
* Returns an array of all thumbnails in the filmstrip.
*
* @private
* @returns {Array}
*/
function getAllThumbnails() {
return [
...localVideoThumbnail ? [ localVideoThumbnail ] : [],
...Object.values(remoteVideos)
];
}
/**
* Private helper to get the redux representation of the local participant.
*
* @private
* @returns {Object}
*/
function getLocalParticipant() {
return getLocalParticipantFromStore(APP.store.getState());
}
const VideoLayout = {
init(emitter) {
eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(
emitter,
this._updateLargeVideoIfDisplayed.bind(this));
this.registerListeners();
},
/**
* Registering listeners for UI events in Video layout component.
*
* @returns {void}
* Handler for local flip X changed event.
*/
registerListeners() {
eventEmitter.addListener(UIEvents.LOCAL_FLIPX_CHANGED,
onLocalFlipXChanged);
onLocalFlipXChanged() {
if (largeVideo) {
const { store } = APP;
const { localFlipX } = store.getState()['features/base/settings'];
largeVideo.onLocalFlipXChange(localFlipX);
}
},
/**
@@ -95,14 +36,17 @@ const VideoLayout = {
*/
reset() {
this._resetLargeVideo();
this._resetFilmstrip();
},
initLargeVideo() {
this._resetLargeVideo();
largeVideo = new LargeVideoManager(eventEmitter);
if (localFlipX) {
largeVideo = new LargeVideoManager();
const { store } = APP;
const { localFlipX } = store.getState()['features/base/settings'];
if (typeof localFlipX === 'boolean') {
largeVideo.onLocalFlipXChange(localFlipX);
}
largeVideo.updateContainerSize();
@@ -120,55 +64,6 @@ const VideoLayout = {
}
},
changeLocalVideo(stream) {
const localId = getLocalParticipant().id;
this.onVideoTypeChanged(localId, stream.videoType);
localVideoThumbnail.changeVideo(stream);
this._updateLargeVideoIfDisplayed(localId);
},
/**
* Shows/hides local video.
* @param {boolean} true to make the local video visible, false - otherwise
*/
setLocalVideoVisible(visible) {
localVideoThumbnail.setVisible(visible);
},
onRemoteStreamAdded(stream) {
const id = stream.getParticipantId();
const remoteVideo = remoteVideos[id];
logger.debug(`Received a new ${stream.getType()} stream for ${id}`);
if (!remoteVideo) {
logger.debug('No remote video element to add stream');
return;
}
remoteVideo.addRemoteStreamElement(stream);
this.onVideoMute(id);
remoteVideo.updateView();
},
onRemoteStreamRemoved(stream) {
const id = stream.getParticipantId();
const remoteVideo = remoteVideos[id];
// Remote stream may be removed after participant left the conference.
if (remoteVideo) {
remoteVideo.removeRemoteStreamElement(stream);
remoteVideo.updateView();
}
this.updateVideoMutedForNoTracks(id);
},
/**
* FIXME get rid of this method once muted indicator are reactified (by
* making sure that user with no tracks is displayed as muted )
@@ -180,7 +75,7 @@ const VideoLayout = {
const participant = APP.conference.getParticipantById(participantId);
if (participant && !participant.getTracksByMediaType('video').length) {
APP.UI.setVideoMuted(participantId);
VideoLayout._updateLargeVideoIfDisplayed(participantId, true);
}
},
@@ -202,110 +97,12 @@ const VideoLayout = {
return videoTrack?.videoType;
},
isPinned(id) {
return id === this.getPinnedId();
},
getPinnedId() {
const { id } = getPinnedParticipant(APP.store.getState()) || {};
return id || null;
},
/**
* Triggers a thumbnail to pin or unpin itself.
*
* @param {number} videoNumber - The index of the video to toggle pin on.
* @private
*/
togglePin(videoNumber) {
const videos = getAllThumbnails();
const videoView = videos[videoNumber];
videoView && videoView.togglePin();
},
/**
* Callback invoked to update display when the pin participant has changed.
*
* @paramn {string|null} pinnedParticipantID - The participant ID of the
* participant that is pinned or null if no one is pinned.
* @returns {void}
*/
onPinChange(pinnedParticipantID) {
getAllThumbnails().forEach(thumbnail =>
thumbnail.focus(pinnedParticipantID === thumbnail.getId()));
},
/**
* Creates a participant container for the given id.
*
* @param {Object} participant - The redux representation of a remote
* participant.
* @returns {void}
*/
addRemoteParticipantContainer(participant) {
if (!participant || participant.local) {
return;
} else if (participant.isFakeParticipant) {
const sharedVideoThumb = new SharedVideoThumb(participant);
this.addRemoteVideoContainer(participant.id, sharedVideoThumb);
return;
}
const id = participant.id;
const jitsiParticipant = APP.conference.getParticipantById(id);
const remoteVideo = new RemoteVideo(jitsiParticipant);
this.addRemoteVideoContainer(id, remoteVideo);
this.updateVideoMutedForNoTracks(id);
},
/**
* Adds remote video container for the given id and <tt>SmallVideo</tt>.
*
* @param {string} the id of the video to add
* @param {SmallVideo} smallVideo the small video instance to add as a
* remote video
*/
addRemoteVideoContainer(id, remoteVideo) {
remoteVideos[id] = remoteVideo;
// Initialize the view
remoteVideo.updateView();
},
/**
* On video muted event.
*/
onVideoMute(id) {
if (APP.conference.isLocalId(id)) {
localVideoThumbnail && localVideoThumbnail.updateView();
} else {
const remoteVideo = remoteVideos[id];
if (remoteVideo) {
remoteVideo.updateView();
}
}
// large video will show avatar instead of muted stream
this._updateLargeVideoIfDisplayed(id, true);
},
/**
* On dominant speaker changed event.
*
* @param {string} id - The participant ID of the new dominant speaker.
* @returns {void}
*/
onDominantSpeakerChanged(id) {
getAllThumbnails().forEach(thumbnail =>
thumbnail.showDominantSpeakerIndicator(id === thumbnail.getId()));
},
/**
* Shows/hides warning about a user's connectivity issues.
*
@@ -321,12 +118,6 @@ const VideoLayout = {
// We have to trigger full large video update to transition from
// avatar to video on connectivity restored.
this._updateLargeVideoIfDisplayed(id, true);
const remoteVideo = remoteVideos[id];
if (remoteVideo) {
remoteVideo.updateView();
}
},
/**
@@ -339,58 +130,14 @@ const VideoLayout = {
*/
onLastNEndpointsChanged(endpointsLeavingLastN, endpointsEnteringLastN) {
if (endpointsLeavingLastN) {
endpointsLeavingLastN.forEach(this._updateRemoteVideo, this);
endpointsLeavingLastN.forEach(this._updateLargeVideoIfDisplayed, this);
}
if (endpointsEnteringLastN) {
endpointsEnteringLastN.forEach(this._updateRemoteVideo, this);
endpointsEnteringLastN.forEach(this._updateLargeVideoIfDisplayed, this);
}
},
/**
* Updates remote video by id if it exists.
* @param {string} id of the remote video
* @private
*/
_updateRemoteVideo(id) {
const remoteVideo = remoteVideos[id];
if (remoteVideo) {
remoteVideo.updateView();
this._updateLargeVideoIfDisplayed(id);
}
},
removeParticipantContainer(id) {
// Unlock large video
if (this.getPinnedId() === id) {
logger.info('Focused video owner has left the conference');
APP.store.dispatch(pinParticipant(null));
}
const remoteVideo = remoteVideos[id];
if (remoteVideo) {
// Remove remote video
logger.info(`Removing remote video: ${id}`);
delete remoteVideos[id];
remoteVideo.remove();
} else {
logger.warn(`No remote video for ${id}`);
}
},
onVideoTypeChanged(id, newVideoType) {
const remoteVideo = remoteVideos[id];
if (!remoteVideo) {
return;
}
logger.info('Peer video type changed: ', id, newVideoType);
remoteVideo.updateView();
},
/**
* Resizes the video area.
*/
@@ -401,15 +148,6 @@ const VideoLayout = {
}
},
getSmallVideo(id) {
if (APP.conference.isLocalId(id)) {
return localVideoThumbnail;
}
return remoteVideos[id];
},
changeUserAvatar(id, avatarUrl) {
if (this.isCurrentlyOnLarge(id)) {
largeVideo.updateAvatar(avatarUrl);
@@ -432,24 +170,6 @@ const VideoLayout = {
return largeVideo && largeVideo.id === id;
},
/**
* Triggers an update of remote video and large video displays so they may
* pick up any state changes that have occurred elsewhere.
*
* @returns {void}
*/
updateAllVideos() {
const displayedUserId = this.getLargeVideoID();
if (displayedUserId) {
this.updateLargeVideo(displayedUserId, true);
}
Object.keys(remoteVideos).forEach(video => {
remoteVideos[video].updateView();
});
},
updateLargeVideo(id, forceUpdate) {
if (!largeVideo) {
return;
@@ -510,13 +230,6 @@ const VideoLayout = {
return Promise.resolve();
}
const currentId = largeVideo.id;
let oldSmallVideo;
if (currentId) {
oldSmallVideo = this.getSmallVideo(currentId);
}
let containerTypeToShow = type;
// if we are hiding a container and there is focusedVideo
@@ -533,12 +246,7 @@ const VideoLayout = {
}
}
return largeVideo.showContainer(containerTypeToShow)
.then(() => {
if (oldSmallVideo) {
oldSmallVideo && oldSmallVideo.updateView();
}
});
return largeVideo.showContainer(containerTypeToShow);
},
isLargeContainerTypeVisible(type) {
@@ -561,14 +269,6 @@ const VideoLayout = {
return largeVideo;
},
/**
* Sets the flipX state of the local video.
* @param {boolean} true for flipped otherwise false;
*/
setLocalFlipX(val) {
this.localFlipX = val;
},
/**
* Returns the wrapper jquery selector for the largeVideo
* @returns {JQuerySelector} the wrapper jquery selector for the largeVideo
@@ -577,15 +277,6 @@ const VideoLayout = {
return this.getCurrentlyOnLargeContainer().$wrapper;
},
/**
* Returns the number of remove video ids.
*
* @returns {number} The number of remote videos.
*/
getRemoteVideosCount() {
return Object.keys(remoteVideos).length;
},
/**
* Helper method to invoke when the video layout has changed and elements
* have to be re-arranged and resized.
@@ -593,12 +284,7 @@ const VideoLayout = {
* @returns {void}
*/
refreshLayout() {
localVideoThumbnail && localVideoThumbnail.updateDOMLocation();
VideoLayout.resizeVideoArea();
// Rerender the thumbnails since they are dependant on the layout because of the tooltip positioning.
localVideoThumbnail && localVideoThumbnail.rerender();
Object.values(remoteVideos).forEach(remoteVideoThumbnail => remoteVideoThumbnail.rerender());
},
/**
@@ -615,26 +301,6 @@ const VideoLayout = {
largeVideo = null;
},
/**
* Cleans up filmstrip state. While a separate {@code Filmstrip} exists, its
* implementation is mainly for querying and manipulating the DOM while
* state mostly remains in {@code VideoLayout}.
*
* @private
* @returns {void}
*/
_resetFilmstrip() {
Object.keys(remoteVideos).forEach(remoteVideoId => {
this.removeParticipantContainer(remoteVideoId);
delete remoteVideos[remoteVideoId];
});
if (localVideoThumbnail) {
localVideoThumbnail.remove();
localVideoThumbnail = null;
}
},
/**
* Triggers an update of large video if the passed in participant is
* currently displayed on large video.

View File

@@ -9,6 +9,7 @@ import {
sendAnalytics
} from '../../react/features/analytics';
import { toggleDialog } from '../../react/features/base/dialog';
import { clickOnVideo } from '../../react/features/filmstrip/actions';
import { KeyboardShortcutsDialog }
from '../../react/features/keyboard-shortcuts';
import { SpeakerStats } from '../../react/features/speaker-stats';
@@ -54,7 +55,7 @@ const KeyboardShortcut = {
if (_shortcuts.has(key)) {
_shortcuts.get(key).function(e);
} else if (!isNaN(num) && num >= 0 && num <= 9) {
APP.UI.clickOnVideo(num);
APP.store.dispatch(clickOnVideo(num));
}
}

486
package-lock.json generated
View File

@@ -2586,9 +2586,9 @@
}
},
"@jitsi/js-utils": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.5.tgz",
"integrity": "sha512-1APQyuqQaYDR+W7cdgzsaBo6x8dpF8sfelcBf3ngNU3Jd+DzuuwUvCMTbr2+cCuy6w59ZAuQ7e2ixCnnOXOW4Q==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.6.tgz",
"integrity": "sha512-XdLvgzhEhMsrzHBDgBydvWneGXSZ8ycg+dEK9bs2CLQhH5A9cHOOToDl9p/skts7WHQp4C8nkhsNzWC/cRvszQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -4708,6 +4708,135 @@
}
}
},
"array.prototype.map": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.3.tgz",
"integrity": "sha512-nNcb30v0wfDyIe26Yif3PcV1JXQp4zEeEfupG7L4SRjnD6HLbO5b2a7eVSba53bOx4YCHYMBHt+Fp4vYstneRA==",
"requires": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1",
"es-array-method-boxes-properly": "^1.0.0",
"is-string": "^1.0.5"
},
"dependencies": {
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "^1.0.12"
}
},
"es-abstract": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
"integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.2",
"is-callable": "^1.2.3",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.2",
"is-string": "^1.0.5",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.0"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"is-callable": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
"integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ=="
},
"is-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
"requires": {
"call-bind": "^1.0.2",
"has-symbols": "^1.0.1"
}
},
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"requires": {
"has-symbols": "^1.0.1"
}
},
"object-inspect": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object.assign": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
"requires": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
}
},
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
}
}
},
"arrify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
@@ -5566,6 +5695,15 @@
"unset-value": "^1.0.0"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"caller-callsite": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
@@ -7144,6 +7282,46 @@
"is-regex": "^1.0.4"
}
},
"es-array-method-boxes-properly": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
},
"es-get-iterator": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
"integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.0",
"has-symbols": "^1.0.1",
"is-arguments": "^1.1.0",
"is-map": "^2.0.2",
"is-set": "^2.0.2",
"is-string": "^1.0.5",
"isarray": "^2.0.5"
},
"dependencies": {
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"is-arguments": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz",
"integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==",
"requires": {
"call-bind": "^1.0.0"
}
},
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
}
}
},
"es-to-primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
@@ -8482,12 +8660,6 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"optional": true
},
"ini": {
"version": "1.3.5",
"resolved": false,
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": false,
@@ -8808,6 +8980,31 @@
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
},
"dependencies": {
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
}
}
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@@ -8951,6 +9148,11 @@
"function-bind": "^1.0.2"
}
},
"has-bigints": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
},
"has-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
@@ -9427,10 +9629,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
"integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="
},
"inquirer": {
"version": "3.3.0",
@@ -9577,6 +9778,11 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-bigint": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz",
"integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg=="
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -9586,6 +9792,14 @@
"binary-extensions": "^2.0.0"
}
},
"is-boolean-object": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
"integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
"requires": {
"call-bind": "^1.0.0"
}
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@@ -9708,6 +9922,16 @@
"is-extglob": "^2.1.1"
}
},
"is-map": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
"integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg=="
},
"is-negative-zero": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w=="
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@@ -9716,6 +9940,11 @@
"kind-of": "^3.0.2"
}
},
"is-number-object": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw=="
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
@@ -9772,11 +10001,21 @@
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
"dev": true
},
"is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
"integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g=="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-string": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ=="
},
"is-svg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz",
@@ -9830,6 +10069,20 @@
"whatwg-fetch": ">=0.10.0"
}
},
"iterate-iterator": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz",
"integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw=="
},
"iterate-value": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
"integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
"requires": {
"es-get-iterator": "^1.0.2",
"iterate-iterator": "^1.0.1"
}
},
"jQuery-Impromptu": {
"version": "github:trentrichardson/jQuery-Impromptu#753c2833f62f9c00301dd8b75af03599dc4f2ee8",
"from": "github:trentrichardson/jQuery-Impromptu#v6.0.0"
@@ -10090,11 +10343,6 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
},
"jquery-contextmenu": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/jquery-contextmenu/-/jquery-contextmenu-2.4.5.tgz",
"integrity": "sha1-5lrOBg2M2tTQ5d94FdDXA55RdFA="
},
"jquery-i18next": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/jquery-i18next/-/jquery-i18next-1.2.1.tgz",
@@ -10265,8 +10513,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"from": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"version": "github:jitsi/lib-jitsi-meet#cd53f249c532fdceb296c37facc4acc846677a9e",
"from": "github:jitsi/lib-jitsi-meet#cd53f249c532fdceb296c37facc4acc846677a9e",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -10283,7 +10531,7 @@
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "github:jitsi/strophejs-plugin-stream-management#001cf02bef2357234e1ac5d163611b4d60bf2b6a",
"uuid": "8.1.0",
"webrtc-adapter": "7.5.0"
"webrtc-adapter": "7.7.1"
},
"dependencies": {
"@jitsi/js-utils": {
@@ -12571,6 +12819,13 @@
"base64-js": "^1.2.3",
"xmlbuilder": "^9.0.7",
"xmldom": "0.1.x"
},
"dependencies": {
"xmldom": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ=="
}
}
},
"plugin-error": {
@@ -13204,6 +13459,136 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
"promise.allsettled": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.4.tgz",
"integrity": "sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==",
"requires": {
"array.prototype.map": "^1.0.3",
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.2",
"get-intrinsic": "^1.0.2",
"iterate-value": "^1.0.2"
},
"dependencies": {
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "^1.0.12"
}
},
"es-abstract": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
"integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.2",
"is-callable": "^1.2.3",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.2",
"is-string": "^1.0.5",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.0"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"is-callable": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
"integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ=="
},
"is-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
"requires": {
"call-bind": "^1.0.2",
"has-symbols": "^1.0.1"
}
},
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"requires": {
"has-symbols": "^1.0.1"
}
},
"object-inspect": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object.assign": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
"requires": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
}
},
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
}
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
@@ -13961,11 +14346,6 @@
"resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-4.0.0.tgz",
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
},
"react-native-linear-gradient": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz",
"integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg=="
},
"react-native-sound": {
"version": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190",
"from": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190"
@@ -14077,8 +14457,8 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
"version": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"from": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"version": "github:react-native-webrtc/react-native-webrtc#1343580c0322f265ff0fb22722ac5501c5fd77ad",
"from": "github:react-native-webrtc/react-native-webrtc#1343580c0322f265ff0fb22722ac5501c5fd77ad",
"requires": {
"base64-js": "^1.1.2",
"cross-os": "^1.3.0",
@@ -16684,6 +17064,17 @@
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
},
"unbox-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz",
"integrity": "sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==",
"requires": {
"function-bind": "^1.1.1",
"has-bigints": "^1.0.0",
"has-symbols": "^1.0.0",
"which-boxed-primitive": "^1.0.1"
}
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@@ -18397,9 +18788,9 @@
}
},
"webrtc-adapter": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.0.tgz",
"integrity": "sha512-cUqlw310uLLSYvO8FTNCVmGWSMlMt6vuSDkcYL1nW+RUvAILJ3jEIvAUgFQU5EFGnU+mf9/No14BFv3U+hoxBQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz",
"integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==",
"requires": {
"rtcpeerconnection-shim": "^1.2.15",
"sdp": "^2.12.0"
@@ -18454,6 +18845,33 @@
"isexe": "^2.0.0"
}
},
"which-boxed-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
"requires": {
"is-bigint": "^1.0.1",
"is-boolean-object": "^1.1.0",
"is-number-object": "^1.0.4",
"is-string": "^1.0.5",
"is-symbol": "^1.0.3"
},
"dependencies": {
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"requires": {
"has-symbols": "^1.0.1"
}
}
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
@@ -18570,9 +18988,9 @@
}
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
"integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz",
"integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA=="
},
"xpipe": {
"version": "1.0.5",

View File

@@ -32,7 +32,7 @@
"@atlaskit/theme": "11.0.2",
"@atlaskit/toggle": "12.0.3",
"@atlaskit/tooltip": "17.1.2",
"@jitsi/js-utils": "1.0.5",
"@jitsi/js-utils": "1.0.6",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.13.2",
"@react-native-community/google-signin": "3.0.1",
@@ -51,17 +51,17 @@
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"jquery": "3.5.1",
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#cd53f249c532fdceb296c37facc4acc846677a9e",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
"moment-duration-format": "2.2.2",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"pixelmatch": "5.1.0",
"promise.allsettled": "1.0.4",
"punycode": "2.1.1",
"react": "16.12",
"react-dom": "16.12",
@@ -77,14 +77,13 @@
"react-native-device-info": "8.0.0",
"react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-sound": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190",
"react-native-splash-screen": "3.2.0",
"react-native-svg": "12.1.0",
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#1343580c0322f265ff0fb22722ac5501c5fd77ad",
"react-native-webview": "11.0.2",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",
@@ -99,7 +98,7 @@
"uuid": "3.1.0",
"wasm-check": "2.0.1",
"windows-iana": "^3.1.0",
"xmldom": "0.1.27",
"xmldom": "0.5.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {

View File

@@ -113,7 +113,7 @@ export default class AudioMuteButton
}
/**
* Indicates if audio is currently muted ot nor.
* Indicates if audio is currently muted or not.
*
* @override
* @protected

View File

@@ -48,7 +48,7 @@ export default class Toolbar extends Component<Props> {
return (
<div
className = { `always-on-top-toolbox ${className}` }
className = { `toolbox-content-items always-on-top-toolbox ${className}` }
onMouseOut = { onMouseOut }
onMouseOver = { onMouseOver }>
<AudioMuteButton />

View File

@@ -100,7 +100,7 @@ export default class VideoMuteButton
}
/**
* Indicates if video is currently muted ot nor.
* Indicates if video is currently muted or not.
*
* @override
* @protected

View File

@@ -185,7 +185,7 @@ export function createRecentClickedEvent(eventName, attributes = {}) {
}
/**
* Creates an event which indicate an action occured in the chrome extension banner.
* Creates an event which indicate an action occurred in the chrome extension banner.
*
* @param {boolean} installPressed - Whether the user pressed install or `x` - cancel.
* @param {Object} attributes - Attributes to attach to the event.
@@ -460,7 +460,7 @@ export function createLocalTracksDurationEvent(duration) {
/**
* Creates an event which indicates that an action related to recording has
* occured.
* occurred.
*
* @param {string} action - The action (e.g. 'start' or 'stop').
* @param {string} type - The recording type (e.g. 'file' or 'live').

View File

@@ -82,7 +82,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_CONFIG:
if (navigator.product === 'ReactNative') {
// Reseting the analytics is currently not needed for web because
// Resetting the analytics is currently not needed for web because
// the user will be redirected to another page and new instance of
// Analytics will be created and initialized.
resetAnalytics();

View File

@@ -73,7 +73,7 @@ export class AbstractApp extends BaseApp<Props, *> {
}
/**
* Creates an extra {@link ReactElement}s to be added (unconditionaly)
* Creates an extra {@link ReactElement}s to be added (unconditionally)
* alongside the main element.
*
* @abstract

View File

@@ -157,7 +157,7 @@ export class App extends AbstractApp {
// it is preferred because it is at a later step of the
// error/exception handling and it is specific to fatal
// errors/exceptions which were observed to kill the app. The
// solution implemented bellow was tested on Android only so it is
// solution implemented below was tested on Android only so it is
// considered safest to use it there only.
return;
}

View File

@@ -1,5 +1,6 @@
// @flow
import '../authentication/middleware';
import '../base/devices/middleware';
import '../e2ee/middleware';
import '../external-api/middleware';

View File

@@ -1,6 +1,7 @@
// @flow
import '../analytics/reducer';
import '../authentication/reducer';
import '../base/app/reducer';
import '../base/audio-only/reducer';
import '../base/color-scheme/reducer';

View File

@@ -1,6 +1,5 @@
// @flow
import '../authentication/reducer';
import '../mobile/audio-mode/reducer';
import '../mobile/background/reducer';
import '../mobile/call-integration/reducer';

View File

@@ -3,9 +3,9 @@
import type { Dispatch } from 'redux';
import { appNavigate } from '../app/actions';
import { checkIfCanJoin, conferenceLeft } from '../base/conference';
import { connectionFailed } from '../base/connection';
import { openDialog } from '../base/dialog';
import { checkIfCanJoin, conferenceLeft } from '../base/conference/actions';
import { connectionFailed } from '../base/connection/actions.native';
import { openDialog } from '../base/dialog/actions';
import { set } from '../base/redux';
import {

View File

@@ -0,0 +1,67 @@
// @flow
import { maybeRedirectToWelcomePage } from '../app/actions';
import { hideDialog, openDialog } from '../base/dialog/actions';
import {
CANCEL_LOGIN
} from './actionTypes';
import { WaitForOwnerDialog, LoginDialog } from './components';
/**
* Cancels {@ink LoginDialog}.
*
* @returns {{
* type: CANCEL_LOGIN
* }}
*/
export function cancelLogin() {
return {
type: CANCEL_LOGIN
};
}
/**
* Cancels authentication, closes {@link WaitForOwnerDialog}
* and navigates back to the welcome page.
*
* @returns {Function}
*/
export function cancelWaitForOwner() {
return (dispatch: Function) => {
dispatch(maybeRedirectToWelcomePage());
};
}
/**
* Hides a authentication dialog where the local participant
* should authenticate.
*
* @returns {Function}.
*/
export function hideLoginDialog() {
return hideDialog(LoginDialog);
}
/**
* Shows a authentication dialog where the local participant
* should authenticate.
*
* @returns {Function}.
*/
export function openLoginDialog() {
return openDialog(LoginDialog);
}
/**
* Shows a notification dialog that authentication is required to create the.
* Conference, so the local participant should authenticate or wait for a
* host.
*
* @returns {Function}.
*/
export function openWaitForOwnerDialog() {
return openDialog(WaitForOwnerDialog);
}

View File

@@ -0,0 +1 @@
export * from './native';

View File

@@ -0,0 +1 @@
export * from './web';

View File

@@ -1,2 +1 @@
export { default as LoginDialog } from './LoginDialog';
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
export * from './_';

View File

@@ -5,20 +5,20 @@ import { Text, TextInput, View } from 'react-native';
import { connect as reduxConnect } from 'react-redux';
import type { Dispatch } from 'redux';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { toJid } from '../../base/connection';
import { connect } from '../../base/connection/actions.native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { toJid } from '../../../base/connection';
import { connect } from '../../../base/connection/actions.native';
import {
CustomSubmitDialog,
FIELD_UNDERLINE,
PLACEHOLDER_COLOR,
_abstractMapStateToProps,
inputDialog as inputDialogStyle
} from '../../base/dialog';
import { translate } from '../../base/i18n';
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
import type { StyleType } from '../../base/styles';
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
} from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
import type { StyleType } from '../../../base/styles';
import { authenticateAndUpgradeRole, cancelLogin } from '../../actions.native';
// Register styles.
import './styles';

View File

@@ -3,10 +3,10 @@
import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { ConfirmDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { connect } from '../../base/redux';
import { cancelWaitForOwner, _openLoginDialog } from '../actions';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { cancelWaitForOwner, _openLoginDialog } from '../../actions.native';
/**
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
@@ -107,9 +107,7 @@ class WaitForOwnerDialog extends Component<Props> {
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _room: string
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const { authRequired } = state['features/base/conference'];

View File

@@ -0,0 +1,2 @@
export { default as LoginDialog } from './LoginDialog';
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';

View File

@@ -1,5 +1,5 @@
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
import { BoxModel } from '../../base/styles';
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel } from '../../../base/styles';
/**
* The styles of the authentication feature.

View File

@@ -0,0 +1,313 @@
// @flow
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { connect } from '../../../../../connection';
import { toJid } from '../../../base/connection/functions';
import { Dialog } from '../../../base/dialog';
import { translate, translateToHTML } from '../../../base/i18n';
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
import { connect as reduxConnect } from '../../../base/redux';
import { authenticateAndUpgradeRole } from '../../actions.native';
import { cancelLogin } from '../../actions.web';
/**
* The type of the React {@code Component} props of {@link LoginDialog}.
*/
type Props = {
/**
* {@link JitsiConference} that needs authentication - will hold a valid
* value in XMPP login + guest access mode.
*/
_conference: Object,
/**
* The server hosts specified in the global config.
*/
_configHosts: Object,
/**
* Indicates if the dialog should display "connecting" status message.
*/
_connecting: boolean,
/**
* The error which occurred during login/authentication.
*/
_error: Object,
/**
* The progress in the floating range between 0 and 1 of the authenticating
* and upgrading the role of the local participant/user.
*/
_progress: number,
/**
* Redux store dispatch method.
*/
dispatch: Dispatch<any>,
/**
* Invoked when username and password are submitted.
*/
onSuccess: Function,
/**
* Conference room name.
*/
roomName: string,
/**
* Invoked to obtain translated strings.
*/
t: Function
}
/**
* The type of the React {@code Component} state of {@link LoginDialog}.
*/
type State = {
/**
* The user entered password for the conference.
*/
password: string,
/**
* The user entered local participant name.
*/
username: string,
/**
* Authentication process starts before joining the conference room.
*/
loginStarted: boolean
}
/**
* Component that renders the login in conference dialog.
*
* @returns {React$Element<any>}
*/
class LoginDialog extends Component<Props, State> {
/**
* Initializes a new {@code LoginDialog} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
username: '',
password: '',
loginStarted: false
};
this._onCancelLogin = this._onCancelLogin.bind(this);
this._onLogin = this._onLogin.bind(this);
this._onChange = this._onChange.bind(this);
}
_onCancelLogin: () => void;
/**
* Called when the cancel button is clicked.
*
* @private
* @returns {void}
*/
_onCancelLogin() {
const { dispatch } = this.props;
dispatch(cancelLogin());
}
_onLogin: () => void;
/**
* Notifies this LoginDialog that the login button (OK) has been pressed by
* the user.
*
* @private
* @returns {void}
*/
_onLogin() {
const {
_conference: conference,
_configHosts: configHosts,
roomName,
onSuccess,
dispatch
} = this.props;
const { password, username } = this.state;
const jid = toJid(username, configHosts);
if (conference) {
dispatch(authenticateAndUpgradeRole(jid, password, conference));
} else {
this.setState({
loginStarted: true
});
connect(jid, password, roomName)
.then(connection => {
onSuccess && onSuccess(connection);
})
.catch(() => {
this.setState({
loginStarted: false
});
});
}
}
_onChange: Object => void;
/**
* Callback for the onChange event of the field.
*
* @param {Object} evt - The static event.
* @returns {void}
*/
_onChange(evt: Object) {
this.setState({
[evt.target.name]: evt.target.value
});
}
/**
* Renders an optional message, if applicable.
*
* @returns {ReactElement}
* @private
*/
renderMessage() {
const {
_configHosts: configHosts,
_connecting: connecting,
_error: error,
_progress: progress,
t
} = this.props;
const { username, password } = this.state;
const messageOptions = {};
let messageKey;
if (progress && progress >= 0.5) {
messageKey = t('connection.FETCH_SESSION_ID');
} else if (error) {
const { name } = error;
if (name === JitsiConnectionErrors.PASSWORD_REQUIRED) {
const { credentials } = error;
if (credentials
&& credentials.jid === toJid(username, configHosts)
&& credentials.password === password) {
messageKey = t('dialog.incorrectPassword');
}
} else if (name) {
messageKey = t('dialog.connectErrorWithMsg');
messageOptions.msg = `${name} ${error.message}`;
}
} else if (connecting) {
messageKey = t('connection.CONNECTING');
}
if (messageKey) {
return (
<span>
{ translateToHTML(t, messageKey, messageOptions) }
</span>
);
}
return null;
}
/**
* Implements {@Component#render}.
*
* @inheritdoc
*/
render() {
const {
_connecting: connecting,
t
} = this.props;
const { password, loginStarted, username } = this.state;
return (
<Dialog
okDisabled = {
connecting
|| loginStarted
|| !password
|| !username
}
okKey = { t('dialog.login') }
onCancel = { this._onCancelLogin }
onSubmit = { this._onLogin }
titleKey = { t('dialog.authenticationRequired') }
width = { 'small' }>
<TextField
autoFocus = { true }
className = 'input-control'
compact = { false }
label = { t('dialog.user') }
name = 'username'
onChange = { this._onChange }
placeholder = { t('dialog.userIdentifier') }
shouldFitContainer = { true }
type = 'text'
value = { username } />
<TextField
className = 'input-control'
compact = { false }
label = { t('dialog.userPassword') }
name = 'password'
onChange = { this._onChange }
shouldFitContainer = { true }
type = 'password'
value = { password } />
{ this.renderMessage() }
</Dialog>
);
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code LoginDialog} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function mapStateToProps(state) {
const {
error: authenticateAndUpgradeRoleError,
progress,
thenableWithCancel
} = state['features/authentication'];
const { authRequired } = state['features/base/conference'];
const { hosts: configHosts } = state['features/base/config'];
const {
connecting,
error: connectionError
} = state['features/base/connection'];
return {
_conference: authRequired,
_configHosts: configHosts,
_connecting: connecting || thenableWithCancel,
_error: connectionError || authenticateAndUpgradeRoleError,
_progress: progress
};
}
export default translate(reduxConnect(mapStateToProps)(LoginDialog));

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