Compare commits

...

164 Commits

Author SHA1 Message Date
Horatiu Muresan
29dbcb309d fix(drawer-menu) Make drawer menu accessible on small height (#14026) 2023-11-07 13:06:03 +02:00
Calinteodor
8a4990d9ae sdk(react-native-sdk): rnsdk screenshare android fix (#13884)
sdk(react-native-sdk): rnsdk screenshare android fix
2023-11-07 12:22:02 +02:00
Дамян Минков
0e55cbbda6 Clean up prosody modules with some extra checks (#14020)
* fix: Adds check for jitsi_meet_room not being string.

Oct 20 12:22:50 mod_bosh        error   Traceback[bosh]: /usr/share/jitsi-meet/prosody-plugins/token/util.lib.lua:336: bad argument #1 to 'lower' (string expected, got userdata)
        stack traceback:
        [C]: in function 'lower'
        /usr/share/jitsi-meet/prosody-plugins/token/util.lib.lua:336: in function 'verify_room'
        ...re/jitsi-meet/prosody-plugins/mod_token_verification.lua:78: in function 'verify_user'

* fix: Adds check for missing speaker stats for occupant.

error   Traceback[c2s]: ...itsi-meet/prosody-plugins/mod_speakerstats_component.lua:124: attempt to index field '?' (a nil value)
        stack traceback:
        ...itsi-meet/prosody-plugins/mod_speakerstats_component.lua:124: in function '?'

* fix: Nil check for breakout_rooms.

c2saaaad95a16c0 error   Traceback[c2s]: ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:345: attempt to index local 'main_room' (a nil value)
        stack traceback:
        ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:345: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:496: in function </usr/lib/prosody/modules/muc/muc.lib.lua:492>

* fix: Adds nil check in allowners.

c2saaaae3024810 error   Traceback[c2s]: /usr/share/jitsi-meet/prosody-plugins/mod_muc_allowners.lua:171: attempt to index local 'room' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_muc_allowners.lua:171: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>

* fix: Adds nil check in lobby.

mod_bosh        error   Traceback[bosh]: ...share/jitsi-meet/prosody-plugins/mod_muc_lobby_rooms.lua:168: attempt to index local 'lobby_room' (a nil value)
        stack traceback:
        ...share/jitsi-meet/prosody-plugins/mod_muc_lobby_rooms.lua:168: in function '?'
        /usr/share/lua/5.2/prosody/util/filters.lua:25: in function 'filter'
        /usr/lib/prosody/modules/mod_bosh.lua:361: in function 'send'
        /usr/lib/prosody/modules/muc/mod_muc.lua:495: in function '?'

* fix: Fixes nil error in fmuc.

s2sinaaaaf2817260       error   Traceback[s2s]: /usr/share/jitsi-meet/prosody-plugins/mod_fmuc.lua:295: attempt to index local 'occupant' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_fmuc.lua:295: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:1201: in function </usr/lib/prosody/modules/muc/muc.lib.lua:1194>

* fix: Fixes nil occupant.

c2s55f4d5411dd0 error   Traceback[c2s]: /usr/share/jitsi-meet/prosody-plugins/mod_muc_flip.lua:120: attempt to index local 'kicked_occupant' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_muc_flip.lua:120: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:791: in function </usr/lib/prosody/modules/muc/muc.lib.lua:616>

* fix: Fixes caching main room.

Objects should not be set in room._data as this field is being serialized and we see errors like.

error   Traceback[c2s]: /usr/share/lua/5.2/prosody/util/serialization.lua:34: Can't serialize userdata
        stack traceback:
        [C]: in function 'error'
        /usr/share/lua/5.2/prosody/util/serialization.lua:34: in function </usr/share/lua/5.2/prosody/util/serialization.lua:33>
        (...tail calls...)
        /usr/share/lua/5.2/prosody/util/serialization.lua:199: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:197: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:197: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:219: in function </usr/share/lua/5.2/prosody/util/serialization.lua:217>
        (...tail calls...)
        /usr/lib/prosody/modules/mod_storage_memory.lua:42: in function </usr/lib/prosody/modules/mod_storage_memory.lua:40>
        (...tail calls...)
        ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:207: in function 'create_breakout_room'

* fix: Fixes calling save_occupant after changing its role.

* squash: Fixed passed value to type.
2023-11-06 15:31:59 -06:00
damencho
6da94aecf2 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1718.0.0+30be4f6f...v1719.0.0+f8a18cf0
2023-11-06 09:40:48 -06:00
Calinteodor
2a3c962e88 feat(recent-list): fix undefined error that breaks visitor joining (#14024)
* feat(recent-list): fix undefined error that breaks visitor joining

* feat(recent-list): revert variable name change

* feat(recent-list): fixed linter
2023-11-06 09:40:28 -06:00
AHMAD KADRI
34f1eb60f4 Accessibility: add validation warning on room name (#14009)
feat(accessibility): add validation warning on room name
2023-11-06 10:59:51 +02:00
Jaya Allamsetty
4115ebe856 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1717.0.0+7b6ce949...v1718.0.0+30be4f6f
2023-11-02 15:36:11 -04:00
Horatiu Muresan
d7dadfc157 feat(facing-mode) add config for initial camera facing mode (#14013) 2023-11-02 16:20:38 +02:00
Erin Yuki Schlarb
2851eeeab3 fix: Make room_metadata Prosody module depend on the required jitsi_session module
Without this room_metadata will silently discard all room metadata client requests assuming that they didn’t come from Jitsi meet clients.

Fixes #14001
2023-11-01 17:06:26 -05:00
Muhammed Ajmal M
84d75f2ae8 fix(screen-sharing) Self view of SC sized correctly initially (#13992) 2023-11-01 18:32:34 +02:00
damencho
73b3309adf feat: Adds leave rate limit to muc_rate_limit. 2023-10-31 15:59:23 -05:00
Jaya Allamsetty
e2de06f60d chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1716.0.0+93c167d3...v1717.0.0+7b6ce949
2023-10-31 14:35:31 -04:00
damencho
cdc7962d11 feat: Adds region parameter to dial out authorize requests. 2023-10-31 11:45:06 -05:00
Saúl Ibarra Corretgé
59242e1217 feat(external-api) introduce a "ready" event
It's fired when the API is ready, and it signals the embedder that they
can reveal the meeting from behind an overlay, for example.

The astute reader might notice we are currently sending a
'browser-support' event roughly at the same time. The reason for this
new event is plain simply semantics.

In addition the 'onload' handler is faked by calling it when the new
ready event fires. The original onload event is unreliable. It will be
called even when nothing was ever loaded (try loading a page without
internet and be amused).
2023-10-31 16:27:12 +01:00
Saúl Ibarra Corretgé
631e39d4fd feat(external-api) allow vh and vw values as parameters 2023-10-31 16:27:12 +01:00
Julian LADJANI
4290cdf53d fix(breakout-rooms, feature-flags): handle breakout button feature flag on participant pane footer component (#14003)
* fix(breakout-rooms, feature-flags): handle breakout button feature flag on participant pane footer component
2023-10-31 13:53:41 +02:00
damencho
84c1e20216 fix(moderated): Fixes moderators in moderated rooms without tenant. 2023-10-30 17:26:42 -05:00
Saúl Ibarra Corretgé
e6caeb86b0 chore(deps,rn) react-native-webrtc@111.0.6 2023-10-30 15:46:14 +01:00
Saúl Ibarra Corretgé
5854e38a09 fix(rn) allow default server URL to be set from native
On Android we support RestrictionManager, but that already sets it, so
make sure we always save it on the settings.

Editing will be restricted in the Settings dialog if changing it is
restricted anyway.

Fixes: https://github.com/jitsi/jitsi-meet/issues/13994
2023-10-30 15:12:05 +01:00
Saúl Ibarra Corretgé
3e9ee9451f fix(android) fix crash on Android 14
Fixes: https://github.com/jitsi/jitsi-meet/issues/13998
2023-10-30 15:11:19 +01:00
Calinteodor
29d02f0a2b feat(chat/native): fixed keyboard overlapping chat input bar (#13984)
* feat(chat/native): fixed keyboard overlapping chat input bar
2023-10-30 12:59:05 +02:00
David Hall
c780f9bbba Update main-sv.json
Fix typos.
2023-10-29 20:53:01 +01:00
Jaya Allamsetty
d5a0bac0a3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1713.0.0+a1d7b0ea...v1716.0.0+93c167d3
2023-10-28 14:45:24 -04:00
5idereal
f0187cc0f8 lang: update zhTW translation (#13986)
* update zhTW translation

* Update main.json
2023-10-26 10:47:22 -05:00
damencho
4708d894cc fix: Adds a nil check in visitors module.
Sep 22 22:06:01 mod_bosh        error   Traceback[bosh]: /usr/share/jitsi-meet/prosody-plugins/mod_visitors.lua:305: attempt to index field '?' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_visitors.lua:305: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
2023-10-26 09:38:23 -05:00
damencho
f38d120406 fix(visitors): Bumps queue size for waiting for jicofo.
500 is the maximum meeting participants we test and support.
2023-10-24 18:27:16 -05:00
damencho
53960baf76 fix(visitors): Fixes filtering initial msgs to main participants.
Filters initial msg for <subject/>.
2023-10-24 18:27:16 -05:00
Jaya Allamsetty
a0f061aa6f chore(deps): Update lib-jitsi-meet. 2023-10-23 17:42:32 -04:00
Jaya Allamsetty
f2fb525d0a ref(config) Drop forceJVB121Ratio from config.js 2023-10-23 17:42:32 -04:00
Jaya Allamsetty
5a59bee597 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1711.0.0+8ec3b736...v1712.0.0+540aed1e
2023-10-23 16:10:10 -04:00
Дамян Минков
07b903d887 feat(visitors): Adds an option to request to be visitor based on jwt. (#13977)
* feat(visitors): Adds an option to request to be visitor based on jwt.

* squash: Updates ljm.
2023-10-23 12:07:03 -05:00
Mihaela Dumitru
1a39315001 feat(whiteboard) expose the excalidraw api (#13974) 2023-10-23 09:22:42 +03:00
Gabriel Borlea
97e5f00dae fix(electron-screensharing): simplify the proccess (#13967)
* fix(electron-screensharing): simplify the proccess
2023-10-20 19:55:06 +03:00
Дамян Минков
bae77f21f8 feat: Adds event for parsed jwt and check for required token.context (#13973)
* squash: Remove tabs.

* feat: Adds a check for context required in jwt.

* feat: Adds an event to notify for parsed jwt.
2023-10-20 08:50:38 -05:00
José Luís Andrade
24d788f333 Update Portuguese translation 2023-10-20 06:50:20 -05:00
damencho
c4d553c605 feat: Filter iq rayo respects the actor of grant moderation. 2023-10-18 14:00:21 -05:00
Mehmet
fa64e2e67c fix(visitors): informs visitor nodes when a participant is kicked. (#13951)
* fix(visitors): informs visitor nodes when a participant is kicked.

* remove hooking muc-broadcast-presence event and create a stanza in muc-occupant-left event.
2023-10-17 07:54:02 -05:00
Damien Fetis
94c29180e4 fix(whiteboard) fix room id generation
Fixes: https://github.com/jitsi/jitsi-meet/issues/13921
2023-10-17 11:09:34 +02:00
dependabot[bot]
b864d91572 chore(deps): bump @babel/traverse in /react-native-sdk
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.15 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 17:57:49 +02:00
Saúl Ibarra Corretgé
2006182a2a fix(deps) run npm audit fix 2023-10-16 17:57:25 +02:00
Saúl Ibarra Corretgé
8fc3de416c feat(config) add ability to prefer BOSH over WebSocket
There might be cases where we'd want to enforce it.
2023-10-16 17:56:34 +02:00
Jaya Allamsetty
4c5787511e chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1698.0.0+03cb3ce8...v1704.0.0+d3efd197
2023-10-16 11:12:19 -04:00
Duduman Bogdan Vlad
8a2e4bc628 feat(screenshare) - add web security fix for electron (#13096)
use send the share screen sources using the external api

---------

Co-authored-by: Gabriel Borlea <gabriel.borlea@8x8.com>
2023-10-16 14:59:55 +03:00
Calinteodor
f78ebbb9a9 feat(settings/native/android): Fixed scroll inside conference settings screen (#13956)
* feat(settings/native/android): fixed scroll inside conference settings screen.
2023-10-16 14:10:52 +03:00
Calin-Teodor
4cc4c25691 feat(prejoin/native): fix display name input on prejoin 2023-10-16 13:13:30 +03:00
Avram Tudor
d02c7dc3a7 i18n: change iframe disabled message based on current hostname (#13950)
* i18n: change iframe disabled message based on current hostname

This will allow us to remove translation overrides from branded repo

* fix linter
2023-10-13 15:05:24 +03:00
Mihaela Dumitru
8741ee771e fix(language/config) deprecate defaultLanguage (#13949) 2023-10-13 14:06:59 +03:00
Mihaela Dumitru
006e8463cd feat(whiteboard) add user limit (#13870) 2023-10-13 13:41:31 +03:00
Saúl Ibarra Corretgé
86e295e9bc fix(conference) clear raised hands when conference changes
Fixes: https://github.com/jitsi/jitsi-meet-sdk-samples/issues/175
2023-10-12 15:03:59 +02:00
keremoge
07bade2557 feat(deps,rn) update React Native to version 0.69.12 2023-10-12 13:18:40 +03:00
Hristo Terezov
0becc890d8 feat(track-state): Log on add/remove/mute/owner. 2023-10-11 16:39:06 -05:00
Hristo Terezov
a1ce6f1ce5 fed(UI): remove UI.setAudioMuted 2023-10-11 16:39:06 -05:00
otbutz
43a7d00c63 web: enable http2 support 2023-10-11 18:22:12 +02:00
Calinteodor
9c04ba767c feat(breakout-rooms/native): separate breakout rooms from participants (#13920)
feat(breakout-rooms/native): separate breakout rooms from participants
2023-10-11 17:34:49 +03:00
Calinteodor
7e1d10fb4d sdk(react-native-sdk): update readme 2023-10-10 12:03:43 +02:00
Saúl Ibarra Corretgé
4ce2280e31 fix(rnsdk,build) run npm install after syncing deps 2023-10-09 15:21:32 +02:00
Saúl Ibarra Corretgé
2918a89d35 fix(rnsdk,build) don't commit the result after bumping version
Just like the other version bumping scripts
2023-10-09 15:21:32 +02:00
malik tekin
8f1c83edfd fix(lang) update Turkish translation
The Turkish translation of the "adjust for" is "ayarla". It was misspelled as "ayala".
2023-10-09 11:01:24 +02:00
eemehmet
106452d857 fix(visitors): Fixes duplicated messages sent from guest domain. 2023-10-06 10:33:23 -05:00
Horatiu Muresan
a4d3fb6c70 fix(notifications) Fix case when description is react component instance (#13919) 2023-10-06 15:20:56 +03:00
Saúl Ibarra Corretgé
a7af01b9e3 fix(screen-sharing) remove stop screen sharing icon 2023-10-06 10:31:47 +02:00
emrah
f7f434ab55 fix(config): add missing notification keys into the list 2023-10-06 10:31:23 +02:00
Esra Hatice YILMAZ
09c0854779 fix(breakout-rooms) fix race condition in timer handling 2023-10-05 22:56:53 +02:00
Horatiu Muresan
b4d12d74f7 fix(aot) remove dependency to store (#13910) 2023-10-05 13:05:15 +03:00
Gabriel Borlea
50b064907a fix(environment): optimal browsers list 2023-10-05 11:14:50 +03:00
dependabot[bot]
b9d6a0f269 chore(deps): bump postcss and css-loader
Bumps [postcss](https://github.com/postcss/postcss) to 8.4.31 and updates ancestor dependency [css-loader](https://github.com/webpack-contrib/css-loader). These dependencies need to be updated together.


Updates `postcss` from 7.0.39 to 8.4.31
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.39...8.4.31)

Updates `css-loader` from 3.6.0 to 6.8.1
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.6.0...v6.8.1)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
- dependency-name: css-loader
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-05 10:04:58 +02:00
Calinteodor
2414e57260 feat(shared-video/native): use local logger (#13886)
* feat(shared-video/native): use local logger
2023-10-04 13:13:54 +03:00
Horatiu Muresan
6c41ddb622 fix(aot) fix icons creating deps to store (#13901) 2023-10-03 17:31:04 +03:00
damencho
55e75d56fd fix: Fixes skipping the lobby for two times in a row for jibri.
An issue where a livestreaming is started for a second time in a meeting with lobby turned on.
2023-10-02 11:22:27 -05:00
Calinteodor
32ac299422 feat(authentication/native): hide login button for 8x8.vc (#13881)
* feat(authentication): hide login button for 8x8.vc
2023-10-02 18:03:57 +03:00
Christoph Settgast
cb7146f954 lang: update German translation 2023-09-29 12:28:22 -05:00
damencho
144c1ce4f4 fix: Fixes passing the hash params in state for token auth URL. 2023-09-29 12:11:52 -05:00
Gabriel Borlea
2102d6eda1 chore(deps): update js-utils to 2.2.1 2023-09-29 18:45:36 +03:00
damencho
1f8e3fe26f fix: Fixes wait for host to respect moderated tenants.
The correct place to check for tenant value is jitsi_meet_domain and not jitsi_meet_context_group.
2023-09-29 09:54:02 -05:00
Andrei Gavrilescu
8b0285a9d7 chore(deps) lib-jitsi-meet@latest (#13891)
https://github.com/jitsi/lib-jitsi-meet/compare/v1695.0.0+51c2187b...v1698.0.0+03cb3ce8
2023-09-29 17:02:31 +03:00
Mihaela Dumitru
b546d01c2d fix(prejoin) improve display name handling relative to configs (#13865) 2023-09-29 16:17:35 +03:00
Saúl Ibarra Corretgé
7bf3e7df1d fix(rn,polyfills) remove no longer needed polyfill
Fixes: https://github.com/jitsi/jitsi-meet/issues/13807
2023-09-28 12:23:09 +02:00
damencho
f9ac965e18 feat: Updates for jwt when room claim is not required.
This allows mod_token_verification to be used with token missing room claim (firebase) and jitsi tokens with the claim.
2023-09-27 19:29:16 -05:00
Gabriel Borlea
d70412166c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1694.0.0+a0145343...v1695.0.0+51c2187b
2023-09-27 16:20:19 +03:00
Дамян Минков
a843406cb0 feat: Check jwt expiration and redirects to auth url if any. (#13879)
* feat: Check jwt expiration and redirects to auth url if any.

It may happen that the jwt had expired while being in the meeting and there is a network issue requiring to reload.

* squash: Fixes lint error.

* squash: Fixes comments.
2023-09-27 07:40:07 -05:00
AHMAD KADRI
58115477a2 Improve accessibility in breakout rooms list (#13669)
Improve accessibility in breakout rooms list
2023-09-27 12:13:03 +03:00
Hristo Terezov
e1dc573c3c fix(GUM):set deviceId only when the device exists 2023-09-26 12:53:08 -05:00
Hristo Terezov
c025102511 feat(devices): Filter MS Teams Audio device 2023-09-26 12:53:08 -05:00
Gabriel Borlea
54d052de73 chore: update js-utils with new ua-parser (#13877)
* chore: update js-utils with new ua-parser

* chore(deps) lib-jitsi-meet@latest
2023-09-26 19:31:26 +03:00
Javier García
7e633f0136 fix: Also check single quote on jitsi-meet-tokens install (#13869)
Fixes #13768.
2023-09-26 11:25:38 -05:00
Gabriel Borlea
4b4bc1c823 chore(deps) lib-jitsi-meet@latest (#13871)
https://github.com/jitsi/lib-jitsi-meet/compare/v1691.0.0+255d8f49...v1693.0.0+c3a086f8
2023-09-25 18:21:58 +03:00
Saúl Ibarra Corretgé
767e23f34c fix(android,deps) update GMS native dependencies
Should fix this error:

~~~
Fatal Exception: java.lang.IllegalArgumentException: org.jitsi.meet: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
       at android.app.PendingIntent.checkFlags(PendingIntent.java:402)
       at android.app.PendingIntent.getActivityAsUser(PendingIntent.java:485)
       at android.app.PendingIntent.getActivity(PendingIntent.java:471)
       at android.app.PendingIntent.getActivity(PendingIntent.java:435)
       at com.google.android.gms.common.GoogleApiAvailabilityLight.getErrorResolutionPendingIntent(com.google.android.gms:play-services-basement@@17.5.0:25)
       at com.google.android.gms.common.GoogleApiAvailabilityLight.getErrorResolutionPendingIntent(com.google.android.gms:play-services-basement@@17.5.0:21)
       at com.google.android.gms.common.GoogleApiAvailability.getErrorResolutionPendingIntent(com.google.android.gms:play-services-base@@17.5.0:170)
       at com.google.android.gms.common.GoogleApiAvailability.getErrorResolutionPendingIntent(com.google.android.gms:play-services-base@@17.5.0:173)
       at com.google.android.gms.common.GoogleApiAvailability.zaa(com.google.android.gms:play-services-base@@17.5.0:112)
       at com.google.android.gms.common.api.internal.GoogleApiManager.zaa(com.google.android.gms:play-services-base@@17.5.0:252)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zaa(com.google.android.gms:play-services-base@@17.5.0:109)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.onConnectionFailed(com.google.android.gms:play-services-base@@17.5.0:75)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zai(com.google.android.gms:play-services-base@@17.5.0:263)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zaa(com.google.android.gms:play-services-base@@17.5.0:133)
       at com.google.android.gms.common.api.internal.GoogleApiManager.handleMessage(com.google.android.gms:play-services-base@@17.5.0:164)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loopOnce(Looper.java:240)
       at android.os.Looper.loop(Looper.java:351)
       at android.os.HandlerThread.run(HandlerThread.java:67)
~~~
2023-09-25 16:46:42 +02:00
Saúl Ibarra Corretgé
b003d28cc5 fix(ios) remove duplicate dependency
We get JitsiWebRTC transitively through the react-native-webrtc Pod now.
2023-09-25 16:44:08 +02:00
Saúl Ibarra Corretgé
91c8e9bd86 chore(deps,rnsdk) add missing dependency 2023-09-25 17:11:21 +03:00
Saúl Ibarra Corretgé
1f52c0b49f chore(deps,rnsdk) sync dependencies 2023-09-25 17:11:21 +03:00
Calin-Teodor
16fd4d4411 sdk(react-native-sdk): update script to check if dep versions are valid 2023-09-25 14:03:04 +03:00
Saúl Ibarra Corretgé
b8a669ad21 feat(android) disable ConnectionService by default
Our app has had it disabled for quite a while, it makes sense the SDKs
do that too.

Fixes: https://github.com/jitsi/jitsi-meet/issues/13800
2023-09-23 22:01:28 +02:00
damencho
f0cb33a627 fix: Fixes missing import in wait for host module. 2023-09-21 14:45:12 -05:00
damencho
b5b7019325 fix: Adds check for missing main_room. 2023-09-21 12:17:53 -05:00
damencho
7ccd68eb18 feat: Introduces passing state to the token authUrl.
Fixes jitsi/jitsi-meet-electron#902.
2023-09-21 12:17:53 -05:00
damencho
44b0ac57eb feat: Updates base64-js dependency. 2023-09-21 12:17:53 -05:00
damencho
a411b7c969 fix: Fixes check for health check room. 2023-09-21 12:17:53 -05:00
damencho
fc8ce532f6 feat: Hides any error from the UI for the DialIn info app.
If the conference mapper return an error we show it on deeplinking page. In case the conf mapper receives non authenticated request it may return an error and this is normal so hide it from that page.
2023-09-21 12:17:53 -05:00
damencho
ef56b3c5b6 feat: Adds an event for host arrived. 2023-09-21 12:17:53 -05:00
Saúl Ibarra Corretgé
37e13804a5 fix(ios) fix compilation with Xcode 14.3
Fixes: https://github.com/jitsi/jitsi-meet/issues/13274
2023-09-21 19:17:15 +02:00
Saúl Ibarra Corretgé
8b209b3c6e fix(external-api) add policy to support the Compute Pressure API
https://w3c.github.io/compute-pressure/#policy-control
2023-09-21 17:15:36 +02:00
Mihaela Dumitru
cb26042d08 fix(virtual-background) display current settings (#13857) 2023-09-21 15:45:17 +03:00
Javier
2952d1cde8 Fix disable virtual background feature, now hides the feature everywhere 2023-09-20 09:05:10 -05:00
Licaon_Kter
8a7f456560 feat(android) add Fastlane metadata
Fix https://github.com/jitsi/jitsi-meet/issues/11786
2023-09-20 11:46:56 +02:00
Thomas Egebrand Gram
f74b6cd82f fix (mobile-layout) change "vh" to "dvh" for all layouts (#13840)
* Convert all vh units to dvh; fixing layout for mobile browsers such as Chrome for Android.
2023-09-20 12:19:54 +03:00
Calinteodor
d04515c35a feat(prejoin/native): fixed screen header hooks warning (#13845)
* feat(prejoin/native): fixed screen header hooks warning
2023-09-20 11:40:28 +03:00
Jaya Allamsetty
2aca0ce110 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1689.0.0+0d5c73d1...v1691.0.0+255d8f49
2023-09-19 18:24:02 -04:00
Calinteodor
d0e49b27a1 feat(app/native): rework appNavigate so callkit video button does not end the call (#13814)
* feat(app/native): rework appNavigate so callkit video button does not end the call
2023-09-19 20:47:29 +03:00
Jaya Allamsetty
d97c365aed chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1687.0.0+cafe30d7...v1689.0.0+0d5c73d1
2023-09-19 12:54:18 -04:00
damencho
8304e77a04 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1686.0.0+1b5830f1...v1687.0.0+cafe30d7
2023-09-14 16:02:29 +02:00
Saúl Ibarra Corretgé
b1db315582 fix(authentication) fix moderator logout 2023-09-14 13:44:08 +02:00
Shawn
4e785dd982 fix(config): missing colon in example for lobby config 2023-09-14 12:19:35 +02:00
damencho
40f5afcf43 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1685.0.0+02c54a23...v1686.0.0+1b5830f1
2023-09-13 16:27:06 -05:00
Saúl Ibarra Corretgé
de2688bb33 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1683.0.0+fc7775bc...v1685.0.0+02c54a23
2023-09-13 00:10:08 +02:00
Saúl Ibarra Corretgé
76db09303b fix(rn,conference) avoid starting to knock twice
The auto-knock process is started by the lobby middleware.
2023-09-12 23:20:41 +02:00
José Luís Andrade
ea4e20f9a7 lang: Update Portuguese translation (#13818) 2023-09-11 10:34:03 -05:00
rca
01a74856a3 Fix missing closing tag for identity node in presence stanza 2023-09-11 10:03:09 -05:00
Mihaela Dumitru
36045100bf feat(branding) add support for custom icons 2023-09-11 09:11:06 +02:00
Hristo Terezov
cc344cb548 chore(LJM): Update 2023-09-08 16:01:28 -05:00
Hristo Terezov
a2624952a0 feat(iframe-api): replace ice servers 2023-09-08 16:01:28 -05:00
Calinteodor
b8259e00dc sdk(react-native-sdk): created script that compares dep with peerDep versions (#13789)
sdk(react-native-sdk): created script that compares app dep with RNSDK peerDep versions
2023-09-08 20:26:25 +03:00
Horatiu Muresan
463c823d3b fix(toolbar-buttons) Show tileview in toolbar when separate reactions button (#13810) 2023-09-08 14:44:32 +03:00
Shawn
5a6f3ead5a feat(external-api) Add functions to query supported commands/events 2023-09-08 09:31:23 +02:00
Andrei Gavrilescu
1b4d666af3 chore(deps) lib-jitsi-meet@latest (#13801)
https://github.com/jitsi/lib-jitsi-meet/compare/v1681.0.0+6cd397fa...v1682.0.0+9832ef2c
2023-09-06 16:51:44 +03:00
Andrei Gavrilescu
77d299338a feat(rtcstats): use rtcstats from lib-jitsi-meet (#13693)
* added option to use rtctstats from lib-jitsi-meet

* Addressed review feedback:
- moved rtcstats function into JitsiMeetJS.rtcstats
- changed from callbacks to events

* moved rtcstatsUseLibJitsi from analytics to testing

* fixed linting errors

* use ljm rtcstats

* remove debug logs, additional dependencies

* fix ts and dependency

* address code review

---------

Co-authored-by: Nils Ohlmeier <github@ohlmeier.org>
2023-09-06 16:00:53 +03:00
Horatiu Muresan
33fc6e2f3f fix(disable-filmstrip) Fix disabling filmstrim through config
- there was a problem with pinning the participants from the Participants pane with the previous approach
2023-09-06 14:30:13 +03:00
Calinteodor
a95eaa6c2e feat(base/ui): Native buttons UI fixes (#13788)
* feat(base/ui): native buttons UI fixes and improvements
2023-09-05 16:36:09 +03:00
Andrei Gavrilescu
5a3947bb23 feat(amplitude) add amplitude UTM tracking option 2023-09-05 13:20:31 +02:00
Calinteodor
f84a561d9e sdk(react-native-sdk): Update rnsdk peer deps (#13793)
* sdk(react-native-sdk): prepare_sdk script updates regarding deps and peer deps
2023-09-05 14:09:13 +03:00
Robert Pintilii
295878ffff ref(styles) Move some SCSS to JSS (#13568) 2023-09-05 11:20:01 +03:00
Saúl Ibarra Corretgé
609942654a fix(android) disable full-screen when screen-sharing
Fixes not being able to put the app in background mode easily on Android
13.

Fixes: https://github.com/jitsi/jitsi-meet/issues/13513
2023-09-04 19:21:20 +02:00
Calin-Teodor
60ad0196c3 ref(dependency): latest react-native-video and device-info updates 2023-09-04 19:30:12 +03:00
Calin-Teodor
caea6966ef ref(dependency): reverted react-native-dialog dep update 2023-09-04 19:30:12 +03:00
Calin-Teodor
d4c269f7cb ref(dependency): updated native fixDeviceID 2023-09-04 19:30:12 +03:00
Calin-Teodor
54a1ee53b4 ref(dependency): reverted react-native-dialog dep updates 2023-09-04 19:30:12 +03:00
Calin-Teodor
2c51e8ac06 ref(dependency): regenerated podfile.lock file 2023-09-04 19:30:12 +03:00
Calin-Teodor
3cbd69eef2 ref(dependency): update deps after rebase 2023-09-04 19:30:12 +03:00
Calin-Teodor
ee539644d8 ref(dependency): replaced DeviceInfo.getUniqueId with getUniqueId 2023-09-04 19:30:12 +03:00
Calin-Teodor
465263bc97 fixed linter 2023-09-04 19:30:12 +03:00
Calin-Teodor
1def65eb90 ref(dependency): update react native device info dependency 2023-09-04 19:30:12 +03:00
Calin-Teodor
746be98bfc ref(dependency): update react native gesture handler dependency 2023-09-04 19:30:12 +03:00
Calin-Teodor
99b58dd318 ref(dependency): fixed rebase conflict 2023-09-04 19:30:12 +03:00
Gabriel Borlea
df3ef0d895 fix(video-select): remove video preview from device selection and fix video switch on mobile browsers (#13780)
* fix(video-select): remove video preview from device selection and fix video switch on android browsers

* simplify if statement

* add for all mobile devices the stop stream

* move mobile check to middleware

* code review
2023-09-04 16:27:04 +03:00
Saúl Ibarra Corretgé
83e4042668 fix(android) bump target API level to 33
It is now required by the Play Store to target an API released within a
year of $NOW to be able to push updates.
2023-09-04 12:47:08 +02:00
Saúl Ibarra Corretgé
c6e87568b6 chore(deps) react-antive-webview@13.5.1
Requirement for bumping Android API target to 33.
2023-09-04 12:47:08 +02:00
Дамян Минков
0170c65c7b feat: Sends conference request over http before connecting to xmpp (#13725)
* feat: Moves redirected event to connection events.

* feat: Pass room name when connecting.

We need the room name we will join to be able to send the http conference request from ljm.

* squash: Drops dispatching redirected action.

* squash: Updates ljm.
2023-08-29 14:13:04 -05:00
Hristo Terezov
a7c1ccec71 fix: Attempt to fix setSinkId failures. 2023-08-29 13:25:48 -05:00
Hristo Terezov
1adbebf9dc fix(logger): Prevent JSON stringify errors 2023-08-29 12:26:48 -05:00
Mihaela Dumitru
9d68cb52b3 fix(virtual-background) standardize options object (#13760) 2023-08-29 14:02:30 +03:00
Gabriel Borlea
44272b650c fix(rn, participants): set badge horizontal padding 2023-08-29 13:53:03 +03:00
Gabriel Borlea
5ce96d379a fix(rn, settings): row text wrapping 2023-08-29 13:53:03 +03:00
Gabriel Borlea
173c5fe430 fix(rn, settings): fix arrow back navigation for lang selection 2023-08-29 13:53:03 +03:00
Mihaela Dumitru
e10595c3ed fix(breakout-rooms) allow spaces when renaming (#13761) 2023-08-29 13:37:56 +03:00
Calin-Teodor
9138f56701 feat(chat): fixed action import for abstract component 2023-08-28 17:06:34 +03:00
Avram Tudor
974e2a5106 ref: improve handling for room destroyed events (#13591)
* ref: improve handling for room destroyed events

* add missing translation

* code review

* implement kick handling

* implement native handling

* fix tests

* code review changes

* add dialog testId

* fix end conf for react native

* fix lobby test

* add translation for lobby closing

---------

Co-authored-by: Gabriel Borlea <gabriel.borlea@8x8.com>
2023-08-28 15:14:03 +03:00
Horatiu Muresan
509cf661f5 feat(filmstrip) Add config for disabling vertical filmstrip (#13752) 2023-08-28 14:44:45 +03:00
nbeck.indy
25fdea9984 fix(video-menu) hide Grant Moderator inside breakout rooms on native 2023-08-24 11:52:53 +03:00
Calin-Teodor
9979e470fc feat(authentication): fix normal authentication 2023-08-24 11:44:44 +03:00
damencho
2a492f5036 feat(authentication): Fixes logging out on web.
It was hanging up and canceling visiting the logout page.
2023-08-23 10:35:06 -05:00
Hristo Terezov
baf1f01e44 fix(jitsi-local-storage): remove debug log. 2023-08-23 09:14:51 -05:00
damencho
1f8dc944e3 feat(authentication): Changes wait for owner cancel txt.
When in lobby and waiting for host cancel just hides the dialog and leave you waiting in the lobby that is enabled.
2023-08-22 21:51:41 -05:00
damencho
dc07c6fede feat(authentication): Hides password button from lobby on waiting for host. 2023-08-22 21:51:41 -05:00
damencho
94a63f8aea feat(authentication): Fixes logout on web. 2023-08-22 21:51:41 -05:00
Horatiu Muresan
a47cb595db fix(localFlipX) Fix localFlipX for large video (#13728)
- fixed case when localFlipX was taken from store on it`s value update, before the new value was set into store - so always taking the previous value instead of updated one
2023-08-18 17:37:07 +03:00
281 changed files with 8776 additions and 5197 deletions

4
.gitignore vendored
View File

@@ -99,6 +99,10 @@ tsconfig.json
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

View File

@@ -82,14 +82,14 @@ dependencies {
if (!rootProject.ext.libreBuild) {
// Sync with react-native-google-signin
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'com.google.android.gms:play-services-auth:20.5.0'
// Firebase
// - Crashlytics
// - Dynamic Links
implementation 'com.google.firebase:firebase-analytics:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
implementation 'com.google.firebase:firebase-analytics:21.3.0'
implementation 'com.google.firebase:firebase-crashlytics:18.4.3'
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
}
implementation project(':sdk')

View File

@@ -152,7 +152,6 @@ public class MainActivity extends JitsiMeetActivity {
= new JitsiMeetConferenceOptions.Builder()
.setServerURL(buildURL(defaultURL))
.setFeatureFlag("welcomepage.enabled", true)
.setFeatureFlag("call-integration.enabled", false)
.setFeatureFlag("resolution", 360)
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
.build();

View File

@@ -10,29 +10,24 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
}
}
ext {
kotlinVersion = "1.7.0"
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
buildToolsVersion = "33.0.2"
compileSdkVersion = 33
minSdkVersion = 24
targetSdkVersion = 32
targetSdkVersion = 33
supportLibVersion = "28.0.0"
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
}
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
// The Maven artifact groupdId of the third-party react-native modules which
// The Maven artifact groupId of the third-party react-native modules which
// Jitsi Meet SDK for Android depends on and which are not available in
// third-party Maven repositories so we have to deploy to a Maven repository
// of ours.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -65,7 +65,11 @@ import {
updateDeviceList
} from './react/features/base/devices/actions.web';
import {
areDevicesDifferent,
filterIgnoredDevices,
flattenAvailableDevices,
getDefaultDeviceId,
logDevices,
setAudioOutputDeviceId
} from './react/features/base/devices/functions.web';
import {
@@ -130,6 +134,7 @@ import {
isUserInteractionRequiredForUnmute
} from './react/features/base/tracks/functions';
import { downloadJSON } from './react/features/base/util/downloadJSON';
import { openLeaveReasonDialog } from './react/features/conference/actions.web';
import { showDesktopPicker } from './react/features/desktop-picker/actions';
import { appendSuffix } from './react/features/display-name/functions';
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
@@ -631,7 +636,7 @@ export default {
// so that the user can try unmute later on and add audio/video
// to the conference
if (!tracks.find(t => t.isAudioTrack())) {
this.setAudioMuteStatus(true);
this.updateAudioIconEnabled();
}
if (!tracks.find(t => t.isVideoTrack())) {
@@ -840,7 +845,7 @@ export default {
// This will only modify base/media.audio.muted which is then synced
// up with the track at the end of local tracks initialization.
muteLocalAudio(mute);
this.setAudioMuteStatus(mute);
this.updateAudioIconEnabled();
return;
} else if (this.isLocalAudioMuted() === mute) {
@@ -1389,7 +1394,7 @@ export default {
APP.store.dispatch(
replaceLocalTrack(oldTrack, newTrack, room))
.then(() => {
this.setAudioMuteStatus(this.isLocalAudioMuted());
this.updateAudioIconEnabled();
})
.then(resolve)
.catch(reject)
@@ -2240,19 +2245,28 @@ export default {
* @returns {Promise}
*/
async _onDeviceListChanged(devices) {
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
const state = APP.store.getState();
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = state['features/base/devices'].availableDevices;
APP.store.dispatch(updateDeviceList(devices));
if (!areDevicesDifferent(flattenAvailableDevices(oldDevices), filteredDevices)) {
return Promise.resolve();
}
logDevices(ignoredDevices, 'Ignored devices on device list changed:');
const localAudio = getLocalJitsiAudioTrack(state);
const localVideo = getLocalJitsiVideoTrack(state);
APP.store.dispatch(updateDeviceList(filteredDevices));
// Firefox users can choose their preferred device in the gUM prompt. In that case
// we should respect that and not attempt to switch to the preferred device from
// our settings.
const newLabelsOnly = mediaDeviceHelper.newDeviceListAddedLabelsOnly(oldDevices, devices);
const newLabelsOnly = mediaDeviceHelper.newDeviceListAddedLabelsOnly(oldDevices, filteredDevices);
const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices,
filteredDevices,
localVideo,
localAudio,
newLabelsOnly);
@@ -2385,7 +2399,7 @@ export default {
return Promise.all(promises)
.then(() => {
APP.UI.onAvailableDevicesChanged(devices);
APP.UI.onAvailableDevicesChanged(filteredDevices);
});
},
@@ -2428,9 +2442,10 @@ export default {
/**
* Disconnect from the conference and optionally request user feedback.
* @param {boolean} [requestFeedback=false] if user feedback should be
* @param {string} [hangupReason] the reason for leaving the meeting
* requested
*/
hangup(requestFeedback = false) {
async hangup(requestFeedback = false, hangupReason) {
APP.store.dispatch(disableReceiver());
this._stopProxyConnection();
@@ -2447,36 +2462,33 @@ export default {
APP.UI.removeAllListeners();
let requestFeedbackPromise;
let feedbackResult = {};
if (requestFeedback) {
requestFeedbackPromise
= APP.store.dispatch(maybeOpenFeedbackDialog(room))
// false because the thank you dialog shouldn't be displayed
.catch(() => Promise.resolve(false));
} else {
requestFeedbackPromise = Promise.resolve(true);
try {
feedbackResult = await APP.store.dispatch(maybeOpenFeedbackDialog(room, hangupReason));
} catch (err) { // eslint-disable-line no-empty
}
}
Promise.all([
requestFeedbackPromise,
this.leaveRoom()
])
.then(values => {
this._room = undefined;
room = undefined;
if (!feedbackResult.wasDialogShown && hangupReason) {
await APP.store.dispatch(openLeaveReasonDialog(hangupReason));
}
/**
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
* and let the page take care of sending the message, since there will be
* a redirect to the page regardlessly.
*/
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
APP.API.notifyReadyToClose();
}
APP.store.dispatch(maybeRedirectToWelcomePage(values[0]));
});
await this.leaveRoom();
this._room = undefined;
room = undefined;
/**
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
* and let the page take care of sending the message, since there will be
* a redirect to the page anyway.
*/
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
APP.API.notifyReadyToClose();
}
APP.store.dispatch(maybeRedirectToWelcomePage(feedbackResult));
},
/**
@@ -2663,15 +2675,6 @@ export default {
APP.UI.setVideoMuted(this.getMyUserId());
},
/**
* Sets the audio muted status.
*
* @param {boolean} muted - New muted status.
*/
setAudioMuteStatus(muted) {
APP.UI.setAudioMuted(this.getMyUserId(), muted);
},
/**
* Dispatches the passed in feedback for submission. The submitted score
* should be a number inclusively between 1 through 5, or -1 for no score.

View File

@@ -51,6 +51,9 @@ var config = {
// Websocket URL (XMPP)
// websocket: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
// Whether BOSH should be preferred over WebSocket if both are configured.
// preferBosh: false,
// The real JID of focus participant - can be overridden here
// Do not change username - FIXME: Make focus username configurable
// https://github.com/jitsi/jitsi-meet/issues/7376
@@ -215,6 +218,9 @@ var config = {
// Video
// Sets the default camera facing mode.
// cameraFacingMode: 'user',
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,
@@ -588,7 +594,7 @@ var config = {
// },
// Configs for the lobby screen.
// lobby {
// lobby: {
// // If Lobby is enabled, it starts knocking automatically. Replaces `autoKnockLobby`.
// autoKnock: false,
// // Enables the lobby chat. Replaces `enableLobbyChat`.
@@ -632,6 +638,7 @@ var config = {
// hideDominantSpeakerBadge: false,
// Default language for the user interface. Cannot be overwritten.
// DEPRECATED! Use the `lang` iframe option directly instead.
// defaultLanguage: 'en',
// Disables profile and the edit of all fields from the profile settings (display name and email)
@@ -1015,6 +1022,10 @@ var config = {
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>',
// Enables Amplitude UTM tracking:
// Default value is false.
// amplitudeIncludeUTM: false,
// Obfuscates room name sent to analytics (amplitude, rtcstats)
// Default value is false.
// obfuscateRoomName: false,
@@ -1294,6 +1305,16 @@ var config = {
// A list of images that can be used as video backgrounds.
// When this field is present, the default images will be replaced with those provided.
virtualBackgrounds: ['https://example.com/img.jpg'],
// Object containing customized icons that should replace the default ones.
// The keys need to be the exact same icon names used in here:
// https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/icons/svg/index.ts
// To avoid having the icons trimmed or displayed in an unexpected way, please provide svg
// files containing svg xml icons in the size that the default icons come in.
customIcons: {
IconArrowUp: 'https://example.com/arrow-up.svg',
IconDownload: 'https://example.com/download.svg',
IconRemoteControlStart: 'https://example.com/remote-start.svg',
},
// Object containing a theme's properties. It also supports partial overwrites of the main theme.
// For a list of all possible theme tokens and their current defaults, please check:
// https://github.com/jitsi/jitsi-meet/tree/master/resources/custom-theme/custom-theme.json
@@ -1427,6 +1448,31 @@ var config = {
// dialInConfCodeUrl is the conference mapper converting a meeting id to a PIN used for dial-in
// or the other way around (more info in resources/cloud-api.swagger)
// You can use external service for authentication that will redirect back passing a jwt token
// You can use tokenAuthUrl config to point to a URL of such service.
// The URL for the service supports few params which will be filled in by the code.
// tokenAuthUrl:
// 'https://myservice.com/auth/{room}?code_challenge_method=S256&code_challenge={code_challenge}&state={state}'
// Supported parameters in tokenAuthUrl:
// {room} - will be replaced with the room name
// {code_challenge} - (A web only). A oauth 2.0 code challenge that will be sent to the service. See:
// https://datatracker.ietf.org/doc/html/rfc7636. The code verifier will be saved in the sessionStorage
// under key: 'code_verifier'.
// {state} - A json with the current state before redirecting. Keys that are included in the state:
// - room (The current room name as shown in the address bar)
// - roomSafe (the backend safe room name to use (lowercase), that is passed to the backend)
// - tenant (The tenant if any)
// - config.xxx (all config overrides)
// - interfaceConfig.xxx (all interfaceConfig overrides)
// - ios=true (in case ios mobile app is used)
// - android=true (in case android mobile app is used)
// - electron=true (when web is loaded in electron app)
// If there is a logout service you can specify its URL with:
// tokenLogoutUrl: 'https://myservice.com/logout'
// You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -1446,9 +1492,6 @@ var config = {
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
tokenAuthUrl
tokenAuthUrlAutoRedirect
tokenLogoutUrl
*/
/**
@@ -1473,7 +1516,6 @@ var config = {
disableLocalStats
disableNS
enableTalkWhileMuted
forceJVB121Ratio
forceTurnRelay
hiddenDomain
hiddenFromRecorderFeatureEnabled
@@ -1497,6 +1539,7 @@ var config = {
*/
// notifications: [
// 'connection.CONNFAIL', // shown when the connection fails,
// 'dialog.cameraConstraintFailedError', // shown when the camera failed
// 'dialog.cameraNotSendingData', // shown when there's no feed from user's camera
// 'dialog.kickTitle', // shown when user has been kicked
// 'dialog.liveStreaming', // livestreaming notifications (pending, on, off, limits)
@@ -1507,6 +1550,7 @@ var config = {
// 'dialog.recording', // recording notifications (pending, on, off, limits)
// 'dialog.remoteControlTitle', // remote control notifications (allowed, denied, start, stop, error)
// 'dialog.reservationError',
// 'dialog.screenSharingFailedTitle', // shown when the screen sharing failed
// 'dialog.serviceUnavailable', // shown when server is not reachable
// 'dialog.sessTerminated', // shown when there is a failed conference session
// 'dialog.sessionRestarted', // show when a client reload is initiated because of bridge migration
@@ -1519,37 +1563,45 @@ var config = {
// 'liveStreaming.unavailableTitle', // shown when livestreaming service is not reachable
// 'lobby.joinRejectedMessage', // shown when while in a lobby, user's request to join is rejected
// 'lobby.notificationTitle', // shown when lobby is toggled and when join requests are allowed / denied
// 'notify.audioUnmuteBlockedTitle', // shown when mic unmute blocked
// 'notify.chatMessages', // shown when receiving chat messages while the chat window is closed
// 'notify.disconnected', // shown when a participant has left
// 'notify.connectedOneMember', // show when a participant joined
// 'notify.connectedTwoMembers', // show when two participants joined simultaneously
// 'notify.connectedThreePlusMembers', // show when more than 2 participants joined simultaneously
// 'notify.leftOneMember', // show when a participant left
// 'notify.leftTwoMembers', // show when two participants left simultaneously
// 'notify.leftThreePlusMembers', // show when more than 2 participants left simultaneously
// 'notify.grantedTo', // shown when moderator rights were granted to a participant
// 'notify.connectedTwoMembers', // show when two participants joined simultaneously
// 'notify.dataChannelClosed', // shown when the bridge channel has been disconnected
// 'notify.hostAskedUnmute', // shown to participant when host asks them to unmute
// 'notify.invitedOneMember', // shown when 1 participant has been invited
// 'notify.invitedThreePlusMembers', // shown when 3+ participants have been invited
// 'notify.invitedTwoMembers', // shown when 2 participants have been invited
// 'notify.kickParticipant', // shown when a participant is kicked
// 'notify.leftOneMember', // show when a participant left
// 'notify.leftThreePlusMembers', // show when more than 2 participants left simultaneously
// 'notify.leftTwoMembers', // show when two participants left simultaneously
// 'notify.linkToSalesforce', // shown when joining a meeting with salesforce integration
// 'notify.moderationStartedTitle', // shown when AV moderation is activated
// 'notify.moderationStoppedTitle', // shown when AV moderation is deactivated
// 'notify.localRecordingStarted', // shown when the local recording has been started
// 'notify.localRecordingStopped', // shown when the local recording has been stopped
// 'notify.moderationInEffectCSTitle', // shown when user attempts to share content during AV moderation
// 'notify.moderationInEffectTitle', // shown when user attempts to unmute audio during AV moderation
// 'notify.moderationInEffectVideoTitle', // shown when user attempts to enable video during AV moderation
// 'notify.moderationInEffectCSTitle', // shown when user attempts to share content during AV moderation
// 'notify.moderator', // shown when user gets moderator privilege
// 'notify.mutedRemotelyTitle', // shown when user is muted by a remote party
// 'notify.mutedTitle', // shown when user has been muted upon joining,
// 'notify.newDeviceAudioTitle', // prompts the user to use a newly detected audio device
// 'notify.newDeviceCameraTitle', // prompts the user to use a newly detected camera
// 'notify.noiseSuppressionFailedTitle', // shown when failed to start noise suppression
// 'notify.participantWantsToJoin', // shown when lobby is enabled and participant requests to join meeting
// 'notify.participantsWantToJoin', // shown when lobby is enabled and participants request to join meeting
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.screenShareNoAudio', // shown when the audio could not be shared for the selected screen
// 'notify.screenSharingAudioOnlyTitle', // shown when the best performance has been affected by screen sharing
// 'notify.selfViewTitle', // show "You can always un-hide the self-view from settings"
// 'notify.startSilentTitle', // shown when user joined with no audio
// 'notify.suboptimalExperienceTitle', // show the browser warning
// 'notify.unmute', // shown to moderator when user raises hand during AV moderation
// 'notify.videoMutedRemotelyTitle', // shown when user's video is muted by a remote party,
// 'notify.videoUnmuteBlockedTitle', // shown when camera unmute and desktop sharing are blocked
// 'prejoin.errorDialOut',
// 'prejoin.errorDialOutDisconnected',
// 'prejoin.errorDialOutFailed',
@@ -1572,6 +1624,8 @@ var config = {
// disableFilmstripAutohiding: false,
// filmstrip: {
// // Disable the vertical/horizonal filmstrip.
// disabled: false,
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
// disableResizable: false,
@@ -1647,6 +1701,11 @@ var config = {
// // The server used to support whiteboard collaboration.
// // https://github.com/jitsi/excalidraw-backend
// collabServerBaseUrl: 'https://excalidraw-backend.example.com',
// // The user access limit to the whiteboard, introduced as a means
// // to control the performance.
// userLimit: 25,
// // The url for more info about the whiteboard and its usage limitations.
// limitUrl: 'https://example.com/blog/whiteboard-limits,
// },
// The watchRTC initialize config params as described :

View File

@@ -1,62 +0,0 @@
.drawer-portal {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 351;
border-radius: 16px 16px 0 0;
&.notification-portal {
z-index: 901;
}
}
.drawer-portal::after {
content: '';
background-color: #141414;
margin-bottom: env(safe-area-inset-bottom, 0);
}
.drawer-menu-container {
height: 100vh;
display: flex;
align-items: flex-end;
}
.drawer-menu {
overflow-y: auto;
margin-bottom: env(safe-area-inset-bottom, 0);
width: 100%;
&#{&} .overflow-menu {
margin: auto;
font-size: 1.2em;
list-style-type: none;
padding: 0;
height: calc(80vh - 144px - 64px);
overflow-y: auto;
.overflow-menu-item {
box-sizing: border-box;
height: 48px;
padding: 12px 16px;
align-items: center;
color: #fff;
cursor: pointer;
display: flex;
font-size: 16px;
div {
display: flex;
flex-direction: row;
align-items: center;
}
&.disabled {
cursor: initial;
color: #3b475c;
}
}
}
}

View File

@@ -1,42 +1,3 @@
.participants_pane {
background-color: #141414;
flex-shrink: 0;
overflow: hidden;
position: relative;
transition: width .16s ease-in-out;
width: 315px;
z-index: $zindex0;
}
.participants_pane-content {
display: flex;
flex-direction: column;
font-weight: 600;
height: 100%;
width: 315px;
& > *:first-child,
& > *:last-child {
flex-shrink: 0;
}
}
@media (max-width: 580px) {
.participants_pane {
height: 100vh;
height: -webkit-fill-available;
left: 0;
position: fixed;
right: 0;
top: 0;
width: auto;
}
.participants_pane-content {
width: 100%;
}
}
.jitsi-icon {
&-dominant-speaker {
background-color: #1EC26A;

View File

@@ -154,17 +154,17 @@ $reactionCount: 20;
}
70% {
transform: translate(40px, -70vh) scale(1.5);
transform: translate(40px, -70dvh) scale(1.5);
opacity: 1;
}
75% {
transform: translate(40px, -70vh) scale(1.5);
transform: translate(40px, -70dvh) scale(1.5);
opacity: 1;
}
100% {
transform: translate(140px, -50vh) scale(1);
transform: translate(140px, -50dvh) scale(1);
opacity: 0;
}
}
@@ -187,17 +187,17 @@ $reactionCount: 20;
}
70% {
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
opacity: 1;
}
75% {
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
opacity: 1;
}
100% {
transform: translate(#{$bottomX}px, -#{$bottomY}vh) scale(1);
transform: translate(#{$bottomX}px, -#{$bottomY}dvh) scale(1);
opacity: 0;
}
}

View File

@@ -266,10 +266,10 @@
#avatarContainer {
border-radius: 50%;
display: inline-block;
height: 50vh;
margin-top: 25vh;
height: 50dvh;
margin-top: 25dvh;
overflow: hidden;
width: 50vh;
width: 50dvh;
#avatar {
height: 100%;

View File

@@ -10,7 +10,7 @@ body.welcome-page {
flex-direction: column;
font-family: $welcomePageFontFamily;
justify-content: space-between;
min-height: 100vh;
min-height: 100dvh;
position: relative;
.header {
@@ -61,6 +61,35 @@ body.welcome-page {
}
.not-allow-title-character-div {
color: #f03e3e;
background-color: #fff;
font-size: 12px;
font-weight: 600;
margin: 10px 0px 5px 0px;
text-align: $welcomePageHeaderTextAlign;
border-radius: 5px;
padding: 5px;
.not-allow-title-character-text {
float: right;
line-height: 1.9;
};
.jitsi-icon {
margin-right: 9px;
float: left;
svg {
fill:#f03e3e;
& > *:first-child {
fill: none !important;
}
}
}
}
.insecure-room-name-warning {
align-items: center;
color: rgb(215, 121, 118);

View File

@@ -1,6 +1,6 @@
.deep-linking-mobile {
background-color: #fff;
height: 100vh;
height: 100dvh;
overflow: auto;
position: relative;
width: 100vw;

View File

@@ -73,7 +73,6 @@ $flagsImagePath: "../images/";
@import 'modals/invite/invite_more';
@import 'modals/security/security';
@import 'responsive';
@import 'drawer';
@import 'participants-pane';
@import 'reactions-menu';
@import 'plan-limit';

View File

@@ -39,7 +39,7 @@ case "$1" in
echo "Application secret is mandatory"
fi
# Not allowed unix special characters in secret: /, \, ", ', `
if echo "$RET" | grep -q '[/\\\"\`]' ; then
if echo "$RET" | grep -q "[/\\\"\`\']" ; then
echo "Application secret contains invalid characters: /, \\, \", ', \`"
exit 1
fi

View File

@@ -43,8 +43,8 @@ server {
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jitsi-meet.example.com;
# Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration

View File

@@ -107,8 +107,8 @@ var interfaceConfig = {
// Names of browsers which should show a warning stating the current browser
// has a suboptimal experience. Browsers which are not listed as optimal or
// unsupported are considered suboptimal. Valid values are:
// chrome, chromium, edge, electron, firefox, nwjs, opera, safari
OPTIMAL_BROWSERS: [ 'chrome', 'chromium', 'firefox', 'nwjs', 'electron', 'safari' ],
// chrome, chromium, electron, firefox , safari, webkit
OPTIMAL_BROWSERS: [ 'chrome', 'chromium', 'firefox', 'electron', 'safari', 'webkit' ],
POLICY_LOGO: null,
PROVIDER_NAME: 'Jitsi',

View File

@@ -46,7 +46,6 @@ target 'JitsiMeetSDK' do
pod 'CocoaLumberjack', '3.7.2'
pod 'ObjectiveDropboxOfficial', '6.2.3'
pod 'JitsiWebRTC', '~> 111.0.0'
end
target 'JitsiMeetSDKLite' do
@@ -86,6 +85,7 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
end
end
end

View File

@@ -3,25 +3,25 @@ PODS:
- amplitude-react-native (2.7.0):
- Amplitude (= 8.7.1)
- React-Core
- AppAuth (1.6.1):
- AppAuth/Core (= 1.6.1)
- AppAuth/ExternalUserAgent (= 1.6.1)
- AppAuth/Core (1.6.1)
- AppAuth/ExternalUserAgent (1.6.1):
- AppAuth (1.6.2):
- AppAuth/Core (= 1.6.2)
- AppAuth/ExternalUserAgent (= 1.6.2)
- AppAuth/Core (1.6.2)
- AppAuth/ExternalUserAgent (1.6.2):
- AppAuth/Core
- boost (1.76.0)
- CocoaLumberjack (3.7.2):
- CocoaLumberjack/Core (= 3.7.2)
- CocoaLumberjack/Core (3.7.2)
- DoubleConversion (1.1.6)
- FBLazyVector (0.69.11)
- FBReactNativeSpec (0.69.11):
- FBLazyVector (0.69.12)
- FBReactNativeSpec (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTRequired (= 0.69.11)
- RCTTypeSafety (= 0.69.11)
- React-Core (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- RCTRequired (= 0.69.12)
- RCTTypeSafety (= 0.69.12)
- React-Core (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- Firebase/Analytics (8.15.0):
- Firebase/Core
- Firebase/Core (8.15.0):
@@ -103,56 +103,59 @@ PODS:
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleDataTransport (9.2.2):
- GoogleDataTransport (9.2.5):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleSignIn (6.2.4):
- GoogleSignIn (7.0.0):
- AppAuth (~> 1.5)
- GTMAppAuth (~> 1.3)
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
- GTMAppAuth (< 3.0, >= 1.3)
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
- GoogleUtilities/AppDelegateSwizzler (7.11.5):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.11.1):
- GoogleUtilities/Environment (7.11.5):
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.11.1):
- GoogleUtilities/Logger (7.11.5):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (7.11.1):
- GoogleUtilities/MethodSwizzler (7.11.5):
- GoogleUtilities/Logger
- GoogleUtilities/Network (7.11.1):
- GoogleUtilities/Network (7.11.5):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.11.1)"
- GoogleUtilities/Reachability (7.11.1):
- "GoogleUtilities/NSData+zlib (7.11.5)"
- GoogleUtilities/Reachability (7.11.5):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.11.1):
- GoogleUtilities/UserDefaults (7.11.5):
- GoogleUtilities/Logger
- GTMAppAuth (1.3.1):
- GTMAppAuth (2.0.0):
- AppAuth/Core (~> 1.6)
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
- GTMSessionFetcher/Core (2.3.0)
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
- GTMSessionFetcher/Core (3.1.1)
- JitsiWebRTC (111.0.2)
- libwebp (1.2.4):
- libwebp/demux (= 1.2.4)
- libwebp/mux (= 1.2.4)
- libwebp/webp (= 1.2.4)
- libwebp/demux (1.2.4):
- libwebp (1.3.1):
- libwebp/demux (= 1.3.1)
- libwebp/mux (= 1.3.1)
- libwebp/sharpyuv (= 1.3.1)
- libwebp/webp (= 1.3.1)
- libwebp/demux (1.3.1):
- libwebp/webp
- libwebp/mux (1.2.4):
- libwebp/mux (1.3.1):
- libwebp/demux
- libwebp/webp (1.2.4)
- libwebp/sharpyuv (1.3.1)
- libwebp/webp (1.3.1):
- libwebp/sharpyuv
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
- nanopb/decode (2.30908.0)
- nanopb/encode (2.30908.0)
- ObjectiveDropboxOfficial (6.2.3)
- PromisesObjC (2.2.0)
- PromisesSwift (2.2.0):
- PromisesObjC (= 2.2.0)
- PromisesObjC (2.3.1)
- PromisesSwift (2.3.1):
- PromisesObjC (= 2.3.1)
- RCT-Folly (2021.06.28.00-v2):
- boost
- DoubleConversion
@@ -164,329 +167,327 @@ PODS:
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- RCTRequired (0.69.11)
- RCTTypeSafety (0.69.11):
- FBLazyVector (= 0.69.11)
- RCTRequired (= 0.69.11)
- React-Core (= 0.69.11)
- React (0.69.11):
- React-Core (= 0.69.11)
- React-Core/DevSupport (= 0.69.11)
- React-Core/RCTWebSocket (= 0.69.11)
- React-RCTActionSheet (= 0.69.11)
- React-RCTAnimation (= 0.69.11)
- React-RCTBlob (= 0.69.11)
- React-RCTImage (= 0.69.11)
- React-RCTLinking (= 0.69.11)
- React-RCTNetwork (= 0.69.11)
- React-RCTSettings (= 0.69.11)
- React-RCTText (= 0.69.11)
- React-RCTVibration (= 0.69.11)
- React-bridging (0.69.11):
- RCTRequired (0.69.12)
- RCTTypeSafety (0.69.12):
- FBLazyVector (= 0.69.12)
- RCTRequired (= 0.69.12)
- React-Core (= 0.69.12)
- React (0.69.12):
- React-Core (= 0.69.12)
- React-Core/DevSupport (= 0.69.12)
- React-Core/RCTWebSocket (= 0.69.12)
- React-RCTActionSheet (= 0.69.12)
- React-RCTAnimation (= 0.69.12)
- React-RCTBlob (= 0.69.12)
- React-RCTImage (= 0.69.12)
- React-RCTLinking (= 0.69.12)
- React-RCTNetwork (= 0.69.12)
- React-RCTSettings (= 0.69.12)
- React-RCTText (= 0.69.12)
- React-RCTVibration (= 0.69.12)
- React-bridging (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- React-jsi (= 0.69.11)
- React-callinvoker (0.69.11)
- React-Codegen (0.69.11):
- FBReactNativeSpec (= 0.69.11)
- React-jsi (= 0.69.12)
- React-callinvoker (0.69.12)
- React-Codegen (0.69.12):
- FBReactNativeSpec (= 0.69.12)
- RCT-Folly (= 2021.06.28.00-v2)
- RCTRequired (= 0.69.11)
- RCTTypeSafety (= 0.69.11)
- React-Core (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-Core (0.69.11):
- RCTRequired (= 0.69.12)
- RCTTypeSafety (= 0.69.12)
- React-Core (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-Core (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default (= 0.69.11)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-Core/Default (= 0.69.12)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/CoreModulesHeaders (0.69.11):
- React-Core/CoreModulesHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/Default (0.69.11):
- React-Core/Default (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/DevSupport (0.69.11):
- React-Core/DevSupport (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default (= 0.69.11)
- React-Core/RCTWebSocket (= 0.69.11)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-jsinspector (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-Core/Default (= 0.69.12)
- React-Core/RCTWebSocket (= 0.69.12)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-jsinspector (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTActionSheetHeaders (0.69.11):
- React-Core/RCTActionSheetHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTAnimationHeaders (0.69.11):
- React-Core/RCTAnimationHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTBlobHeaders (0.69.11):
- React-Core/RCTBlobHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTImageHeaders (0.69.11):
- React-Core/RCTImageHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTLinkingHeaders (0.69.11):
- React-Core/RCTLinkingHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTNetworkHeaders (0.69.11):
- React-Core/RCTNetworkHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTSettingsHeaders (0.69.11):
- React-Core/RCTSettingsHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTTextHeaders (0.69.11):
- React-Core/RCTTextHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTVibrationHeaders (0.69.11):
- React-Core/RCTVibrationHeaders (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-Core/RCTWebSocket (0.69.11):
- React-Core/RCTWebSocket (0.69.12):
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-Core/Default (= 0.69.11)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsiexecutor (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-Core/Default (= 0.69.12)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsiexecutor (= 0.69.12)
- React-perflogger (= 0.69.12)
- Yoga
- React-CoreModules (0.69.11):
- React-CoreModules (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTTypeSafety (= 0.69.11)
- React-Codegen (= 0.69.11)
- React-Core/CoreModulesHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- React-RCTImage (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-cxxreact (0.69.11):
- RCTTypeSafety (= 0.69.12)
- React-Codegen (= 0.69.12)
- React-Core/CoreModulesHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- React-RCTImage (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-cxxreact (0.69.12):
- boost (= 1.76.0)
- DoubleConversion
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-callinvoker (= 0.69.11)
- React-jsi (= 0.69.11)
- React-jsinspector (= 0.69.11)
- React-logger (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-runtimeexecutor (= 0.69.11)
- React-jsi (0.69.11):
- React-callinvoker (= 0.69.12)
- React-jsi (= 0.69.12)
- React-jsinspector (= 0.69.12)
- React-logger (= 0.69.12)
- React-perflogger (= 0.69.12)
- React-runtimeexecutor (= 0.69.12)
- React-jsi (0.69.12):
- boost (= 1.76.0)
- DoubleConversion
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-jsi/Default (= 0.69.11)
- React-jsi/Default (0.69.11):
- React-jsi/Default (= 0.69.12)
- React-jsi/Default (0.69.12):
- boost (= 1.76.0)
- DoubleConversion
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-jsiexecutor (0.69.11):
- React-jsiexecutor (0.69.12):
- DoubleConversion
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-jsinspector (0.69.11)
- React-logger (0.69.11):
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-perflogger (= 0.69.12)
- React-jsinspector (0.69.12)
- React-logger (0.69.12):
- glog
- react-native-background-timer (2.4.1):
- React-Core
- react-native-get-random-values (1.7.2):
- react-native-get-random-values (1.9.0):
- React-Core
- react-native-keep-awake (4.0.0):
- React
- react-native-netinfo (7.1.7):
- react-native-netinfo (9.4.1):
- React-Core
- react-native-orientation-locker (1.5.0):
- React-Core
- react-native-pager-view (5.4.9):
- react-native-pager-view (6.2.0):
- React-Core
- react-native-safe-area-context (4.6.4):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- react-native-performance (5.0.0):
- React-Core
- ReactCommon/turbomodule/core
- react-native-slider (4.1.12):
- react-native-safe-area-context (4.7.1):
- React-Core
- react-native-slider (4.4.3):
- React-Core
- react-native-splash-screen (3.3.0):
- React-Core
- react-native-video (6.0.0-alpha.1):
- react-native-video (6.0.0-alpha.7):
- React-Core
- react-native-video/Video (= 6.0.0-alpha.1)
- react-native-video/Video (6.0.0-alpha.1):
- react-native-video/Video (= 6.0.0-alpha.7)
- react-native-video/Video (6.0.0-alpha.7):
- PromisesSwift
- React-Core
- react-native-webrtc (111.0.3):
- react-native-webrtc (111.0.6):
- JitsiWebRTC (~> 111.0.0)
- React-Core
- react-native-webview (11.15.1):
- react-native-webview (13.5.1):
- React-Core
- React-perflogger (0.69.11)
- React-RCTActionSheet (0.69.11):
- React-Core/RCTActionSheetHeaders (= 0.69.11)
- React-RCTAnimation (0.69.11):
- React-perflogger (0.69.12)
- React-RCTActionSheet (0.69.12):
- React-Core/RCTActionSheetHeaders (= 0.69.12)
- React-RCTAnimation (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTTypeSafety (= 0.69.11)
- React-Codegen (= 0.69.11)
- React-Core/RCTAnimationHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTBlob (0.69.11):
- RCTTypeSafety (= 0.69.12)
- React-Codegen (= 0.69.12)
- React-Core/RCTAnimationHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTBlob (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- React-Codegen (= 0.69.11)
- React-Core/RCTBlobHeaders (= 0.69.11)
- React-Core/RCTWebSocket (= 0.69.11)
- React-jsi (= 0.69.11)
- React-RCTNetwork (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTImage (0.69.11):
- React-Codegen (= 0.69.12)
- React-Core/RCTBlobHeaders (= 0.69.12)
- React-Core/RCTWebSocket (= 0.69.12)
- React-jsi (= 0.69.12)
- React-RCTNetwork (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTImage (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTTypeSafety (= 0.69.11)
- React-Codegen (= 0.69.11)
- React-Core/RCTImageHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- React-RCTNetwork (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTLinking (0.69.11):
- React-Codegen (= 0.69.11)
- React-Core/RCTLinkingHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTNetwork (0.69.11):
- RCTTypeSafety (= 0.69.12)
- React-Codegen (= 0.69.12)
- React-Core/RCTImageHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- React-RCTNetwork (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTLinking (0.69.12):
- React-Codegen (= 0.69.12)
- React-Core/RCTLinkingHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTNetwork (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTTypeSafety (= 0.69.11)
- React-Codegen (= 0.69.11)
- React-Core/RCTNetworkHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTSettings (0.69.11):
- RCTTypeSafety (= 0.69.12)
- React-Codegen (= 0.69.12)
- React-Core/RCTNetworkHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTSettings (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTTypeSafety (= 0.69.11)
- React-Codegen (= 0.69.11)
- React-Core/RCTSettingsHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-RCTText (0.69.11):
- React-Core/RCTTextHeaders (= 0.69.11)
- React-RCTVibration (0.69.11):
- RCTTypeSafety (= 0.69.12)
- React-Codegen (= 0.69.12)
- React-Core/RCTSettingsHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-RCTText (0.69.12):
- React-Core/RCTTextHeaders (= 0.69.12)
- React-RCTVibration (0.69.12):
- RCT-Folly (= 2021.06.28.00-v2)
- React-Codegen (= 0.69.11)
- React-Core/RCTVibrationHeaders (= 0.69.11)
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (= 0.69.11)
- React-runtimeexecutor (0.69.11):
- React-jsi (= 0.69.11)
- ReactCommon/turbomodule/core (0.69.11):
- React-Codegen (= 0.69.12)
- React-Core/RCTVibrationHeaders (= 0.69.12)
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (= 0.69.12)
- React-runtimeexecutor (0.69.12):
- React-jsi (= 0.69.12)
- ReactCommon/turbomodule/core (0.69.12):
- DoubleConversion
- glog
- RCT-Folly (= 2021.06.28.00-v2)
- React-bridging (= 0.69.11)
- React-callinvoker (= 0.69.11)
- React-Core (= 0.69.11)
- React-cxxreact (= 0.69.11)
- React-jsi (= 0.69.11)
- React-logger (= 0.69.11)
- React-perflogger (= 0.69.11)
- React-bridging (= 0.69.12)
- React-callinvoker (= 0.69.12)
- React-Core (= 0.69.12)
- React-cxxreact (= 0.69.12)
- React-jsi (= 0.69.12)
- React-logger (= 0.69.12)
- React-perflogger (= 0.69.12)
- RNCalendarEvents (2.2.0):
- React
- RNCAsyncStorage (1.17.3):
- RNCAsyncStorage (1.19.3):
- React-Core
- RNCClipboard (1.5.1):
- React-Core
- RNDefaultPreference (1.4.4):
- React-Core
- RNDeviceInfo (8.4.8):
- RNDeviceInfo (10.9.0):
- React-Core
- RNGestureHandler (2.9.0):
- React-Core
- RNGoogleSignin (9.0.2):
- GoogleSignIn (~> 6.2)
- RNGoogleSignin (10.0.1):
- GoogleSignIn (~> 7.0)
- React-Core
- RNScreens (3.22.0):
- RNScreens (3.24.0):
- React-Core
- React-RCTImage
- RNSound (0.11.1):
- RNSound (0.11.2):
- React-Core
- RNSound/Core (= 0.11.1)
- RNSound/Core (0.11.1):
- RNSound/Core (= 0.11.2)
- RNSound/Core (0.11.2):
- React-Core
- RNSVG (12.4.3):
- RNSVG (13.13.0):
- React-Core
- RNWatch (1.0.11):
- RNWatch (1.1.0):
- React
- Yoga (1.14.0)
@@ -502,7 +503,6 @@ DEPENDENCIES:
- Firebase/DynamicLinks (~> 8.0)
- "giphy-react-native-sdk (from `../node_modules/@giphy/react-native-sdk`)"
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- JitsiWebRTC (~> 111.0.0)
- ObjectiveDropboxOfficial (= 6.2.3)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
@@ -525,6 +525,7 @@ DEPENDENCIES:
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
- react-native-performance (from `../node_modules/react-native-performance`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
@@ -638,6 +639,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-orientation-locker"
react-native-pager-view:
:path: "../node_modules/react-native-pager-view"
react-native-performance:
:path: "../node_modules/react-native-performance"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
@@ -702,12 +705,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
AppAuth: e48b432bb4ba88b10cb2bcc50d7f3af21e78b9c2
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 5c0975e66853436589eae7542f4b956c7e2ef465
FBReactNativeSpec: bb062293e84c33200005312d1807d8cb94a0d66a
FBLazyVector: 6fab494fa11340bd4206edaebed07279a6bafad4
FBReactNativeSpec: 76d7b03876b0ad0b86bc5c84d23af8e64db8e096
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
@@ -720,68 +723,69 @@ SPEC CHECKSUMS:
giphy-react-native-sdk: fcda9639f8ca2cc47e0517b6ef11c19359db5f5a
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
GoogleDataTransport: 8378d1fa8ac49753ea6ce70d65a7cb70ce5f66e6
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
libwebp: 33dc822fbbf4503668d09f7885bbfedc76c45e96
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
RCTRequired: 8e9a57dddc8f8e9e816c67c2d2537271a997137a
RCTTypeSafety: 2b19e268e2036a2c2f6db6deb1ac03e28b1d607a
React: f9478e6390f177ee6b67b87a3c6afea42b39523e
React-bridging: d405ecd3ff80e1d0a4059a11063eaa9ed7a00c58
React-callinvoker: c8ffa61f3f06f486ba6647769fc98f19e25d165a
React-Codegen: 73acfdac1495b91ad5efdd3ab005568263c5def6
React-Core: 7b7c75af4b73fe0ed4e5c3cdb7d79979e81148dc
React-CoreModules: cd6e7efb38162884f08c7afa16fffaf15ff28ae4
React-cxxreact: 51157cc600c9f436a7e623913a03b775305ef86c
React-jsi: 3eeb345c4828d7b132fd38064a305f31b46d4ec3
React-jsiexecutor: 5813455a4a908fb7284aa13307a9e0386e93b0bb
React-jsinspector: 9ca5bf73ed0a195397e45fdbcd507cf7d503c428
React-logger: 700340e325f21ba2a2d6413a61ef14268c7360aa
RCTRequired: b9e53f0512019150020156fa0dacd6583ab838be
RCTTypeSafety: 04b72202bef8302802610dee70bb9407a245b64c
React: 59288a7ca8104eb8002f01378606fe42eeabf4b5
React-bridging: b042b8c217f04e568409786de5f221793be49c31
React-callinvoker: c7b83d582112e2d5a049dc46abf4c64d871b5c45
React-Codegen: 5747238d0446e3ab1deb967e749a2bfde6a5c866
React-Core: d8e1250039d47112513757038d9d9f9b638565c6
React-CoreModules: 63cceb0040ec2b43a258113193be91f934b37f1b
React-cxxreact: 429404aac55d8bffca77328002452fc7fa8b29e8
React-jsi: a8f60feb519ac00085eb9a39d20eaa65c96b51ea
React-jsiexecutor: ce0b9aa647bdf94126eb2ee1f235d329eb8c0aec
React-jsinspector: f275698149311abc8c32ebb97797d6b97c44adde
React-logger: da69d7f1c9501c78cd60776d52a60d7fa5e4d9c2
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
react-native-netinfo: 27f287f2d191693f3b9d01a4273137fcf91c3b5d
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
react-native-safe-area-context: 68b07eabfb0d14547d36f6929c0e98d818064f02
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
react-native-webrtc: 4d1669c2ed29767fe70b0169428b4466589ecf8b
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
React-perflogger: fdee2a0c512167ae4c19c4e230ccf6aa66a6aff0
React-RCTActionSheet: 1cf5fef4e372f1c877969710a51bea4bb25e78fe
React-RCTAnimation: 73816e3acd1f5e3f00166fc7eedb34f6b112f734
React-RCTBlob: 6976c838fb14a1daf75d7c8bb23bae9cbbf726bb
React-RCTImage: ab8a7498f215117f32271698591e4bd932dcf812
React-RCTLinking: e8e78aed2744ab9946cc8ba5716b4938c2efb1e0
React-RCTNetwork: 796f5aed4d932655d292bdc6b40f9502dcdb9542
React-RCTSettings: 7e1cd2a384b45c90caf67464572abe3833b9da3b
React-RCTText: fd6162890828f0761e03c59058fa23c3a21b2e10
React-RCTVibration: 302cfd5cc33669d7abdb7ec6790123baba66e62e
React-runtimeexecutor: 59407514818b2afbb1d7507e4e1ac834d24b0fbd
ReactCommon: b8487da74723562d7368dab27135fd182f00a91c
react-native-video: 967eead48aaa42c25a9e1d65c3b1ab30762a88df
react-native-webrtc: 255a1172fd31525b952b36aef7b8e9a41de325e5
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
React-perflogger: 5ade0a1627352f1647d283e78331819bb46cceae
React-RCTActionSheet: 8e94f1e46e09c7035b81fe56c0ed8d78f3ccd340
React-RCTAnimation: bf2af72f03cf16528db9a830be69fa04b341a1b7
React-RCTBlob: 4d076b8bb55e631ad1280280ecba674fb1e46d16
React-RCTImage: 073dcc1689466851fe120c7f8a3cfe3db0196c9f
React-RCTLinking: 8872818dc894a17bf17cb4b120f76917bf2e9f0a
React-RCTNetwork: 1e9c873f4a210784a4fb752194cb595502112464
React-RCTSettings: 1475a717c54f4a9ed627dffffad2470c4b15a419
React-RCTText: ed34088172126f84130eea859d62fedca0dd7975
React-RCTVibration: c9cd9f21bbcb3b9c6deedbb66f13e373f57dd795
React-runtimeexecutor: ea78653fbc68bd6f2d3f5e7e311bc5a9dc8bfeca
ReactCommon: f4bb9e5209ea5c3c6ab25e100895119e58d6e50a
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNGoogleSignin: 22e468a9474dbcb8618d8847205ad4f0b2575d13
RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
Yoga: 8a90b50af67eaa9fe94fd03e550bfeab06096873
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
PODFILE CHECKSUM: 90720aee51cf2cab2e12611a28dbf55a688e969c
COCOAPODS: 1.12.1

View File

@@ -67,13 +67,16 @@
"renameBreakoutRoom": "Breakout-Raum umbenennen",
"sendToBreakoutRoom": "Anwesende in Breakout-Raum verschieben:"
},
"breakoutList": "Breakout-Liste",
"defaultName": "Breakout-Raum #{{index}}",
"hideParticipantList": "Teilnehmerliste ausblenden",
"mainRoom": "Hauptraum",
"notifications": {
"joined": "Breakout-Raum \"{{name}}\" betreten",
"joinedMainRoom": "Hauptraum betreten",
"joinedTitle": "Breakout-Räume"
}
},
"showParticipantList": "Teilnehmerliste anzeigen"
},
"calendarSync": {
"addMeetingURL": "Konferenzlink hinzufügen",
@@ -256,6 +259,7 @@
"Share": "Teilen",
"Submit": "OK",
"WaitForHostMsg": "Die Konferenz wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitingForHostButton": "Auf Moderation warten",
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
"Yes": "Ja",
"accessibilityLabel": {
@@ -269,6 +273,8 @@
"addMeetingNote": "Notiz zu dieser Konferenz hinzufügen",
"addOptionalNote": "Notiz hinzufügen (optional):",
"allow": "Erlauben",
"allowToggleCameraDialog": "Wollen Sie {{initiatorName}} erlauben, Ihre Kameraauswahl zu ändern?",
"allowToggleCameraTitle": "Änderung der Kamera zulassen?",
"alreadySharedVideoMsg": "Eine andere Person gibt bereits ein Video weiter. Bei dieser Konferenz ist jeweils nur ein geteiltes Video möglich.",
"alreadySharedVideoTitle": "Nur ein geteiltes Video gleichzeitig",
"applicationWindow": "Anwendungsfenster",
@@ -329,6 +335,7 @@
"lockRoom": "Konferenz$t(lockRoomPassword) hinzufügen",
"lockTitle": "Sperren fehlgeschlagen",
"login": "Anmelden",
"loginQuestion": "Sind Sie sicher, dass sie sich anmelden und die Konferenz verlassen möchten?",
"logoutQuestion": "Sind Sie sicher, dass Sie sich abmelden und die Konferenz verlassen möchten?",
"logoutTitle": "Abmelden",
"maxUsersLimitReached": "Das Limit für die maximale Personenzahl ist erreicht. Die Konferenz ist voll. Bitte wenden Sie sich an die Konferenzleitung oder versuchen Sie es später noch einmal!",
@@ -409,6 +416,7 @@
"sendPrivateMessageTitle": "Privat antworten?",
"serviceUnavailable": "Dienst nicht verfügbar",
"sessTerminated": "Konferenz beendet",
"sessTerminatedReason": "Die Konferenz wurde beendet",
"sessionRestarted": "Konferenz neugestartet",
"shareAudio": "Fortfahren",
"shareAudioTitle": "Wie kann Audio geteilt werden",
@@ -440,7 +448,24 @@
"thankYou": "Danke für die Verwendung von {{appName}}!",
"token": "Token",
"tokenAuthFailed": "Sie sind nicht berechtigt, dieser Konferenz beizutreten.",
"tokenAuthFailedReason": {
"audInvalid": "Ungültiger `aud`-Wert. Erwartet wird `jitsi`.",
"contextNotFound": "Das `context`-Objekt fehlt.",
"expInvalid": "Ungültiger `exp`-Wert.",
"featureInvalid": "Ungültiges Feature: {{feature}}, noch nicht implementiert.",
"featureValueInvalid": "Ungültiger Wert für Feature: {{feature}}.",
"featuresNotFound": "Das `features`-Objekt fehlt.",
"headerNotFound": "Header fehlt.",
"issInvalid": "Ungültiger `iss`-Wert. Erwartet wird `chat`.",
"kidMismatch": "Die Key-ID (kid) passt nicht zum sub.",
"kidNotFound": "Fehlende Key-ID (kid).",
"nbfFuture": "Der `nbf`-Wert liegt in der Zukunft.",
"nbfInvalid": "Ungültiger `nbf`-Wert.",
"payloadNotFound": "Fehlende Payload.",
"tokenExpired": "Das Token ist abgelaufen."
},
"tokenAuthFailedTitle": "Authentifizierung fehlgeschlagen",
"tokenAuthFailedWithReasons": "Teilnahme an der Konferenz fehlgeschlagen. Möglicher Grund: {{reason}}",
"tokenAuthUnsupported": "Token-Authentifizierung wird nicht unterstützt.",
"transcribing": "Wird transkribiert",
"unlockRoom": "Konferenz$t(lockRoomPassword) entfernen",
@@ -530,6 +555,7 @@
"password": "$t(lockRoomPasswordUppercase):",
"reachedLimit": "Sie haben die Grenzen Ihres Tarifs erreicht.",
"sip": "SIP-Adresse",
"sipAudioOnly": "SIP-Adresse (nur Ton)",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf"
@@ -636,13 +662,13 @@
"knockingParticipantList": "Liste anklopfender Personen",
"lobbyChatStartedNotification": "{{moderator}} hat einen Lobby-Chat mit {{attendee}} gestartet",
"lobbyChatStartedTitle": "{{moderator}} hat einen Lobby-Chat mit Ihnen gestartet.",
"lobbyClosed": "Die Lobby wurde geschlossen.",
"nameField": "Geben Sie Ihren Namen ein",
"notificationLobbyAccessDenied": "{{targetParticipantName}} wurde von {{originParticipantName}} der Zutritt verwehrt",
"notificationLobbyAccessGranted": "{{targetParticipantName}} wurde von {{originParticipantName}} der Zutritt gestattet",
"notificationLobbyDisabled": "{{originParticipantName}} hat die Lobby deaktiviert",
"notificationLobbyEnabled": "{{originParticipantName}} hat die Lobby aktiviert",
"notificationTitle": "Lobby",
"passwordField": "Konferenzpasswort eingeben",
"passwordJoinButton": "Beitreten",
"reject": "Ablehnen",
"rejectAll": "Alle ablehnen",
@@ -677,6 +703,8 @@
"sessionToken": "Sitzungs-Token",
"start": "Aufnahme starten",
"stop": "Aufnahme stoppen",
"stopping": "Aufnahme wird gestoppt",
"wait": "Bitte warten Sie während wir Ihre Aufnahme speichern",
"yes": "Ja"
},
"lockRoomPassword": "Passwort",
@@ -1059,6 +1087,7 @@
"alertOk": "OK",
"alertTitle": "Warnung",
"alertURLText": "Die angegebene Server-URL ist ungültig",
"apply": "Übernehmen",
"buildInfoSection": "Build-Informationen",
"conferenceSection": "Konferenz",
"disableCallIntegration": "Native Anrufintegration deaktivieren",
@@ -1069,6 +1098,7 @@
"displayNamePlaceholderText": "z.B. Erika Musterfrau",
"email": "E-Mail",
"emailPlaceholderText": "email@beispiel.de",
"gavatarMessage": "Wenn Sie ein Gravatar-Konto mit Ihrer Emailadresse haben, wird dieses als Ihr Profilfoto verwendet.",
"goTo": "Gehe zu",
"header": "Einstellungen",
"help": "Hilfe",

View File

@@ -67,13 +67,18 @@
"renameBreakoutRoom": "Renomear sala",
"sendToBreakoutRoom": "Enviar participante para:"
},
"breakoutList": "lista de salas",
"buttonLabel": "Salas simultâneas",
"defaultName": "Sala #{{index}}",
"hideParticipantList": "Ocultar lista de participantes",
"mainRoom": "Sala principal",
"notifications": {
"joined": "Entrada na sala \"{{name}}\"",
"joinedMainRoom": "Entrada na sala principal",
"joinedTitle": "Salas simultâneas"
}
},
"showParticipantList": "Mostrar lista de participantes",
"title": "Salas simultâneas"
},
"calendarSync": {
"addMeetingURL": "Adicionar um link da reunião",
@@ -250,13 +255,14 @@
"dialog": {
"Back": "Voltar",
"Cancel": "Cancelar",
"IamHost": "Eu sou o anfitrião",
"IamHost": "Iniciar sessão",
"Ok": "OK",
"Remove": "Remover",
"Share": "Partilhar",
"Submit": "Submeter",
"WaitForHostMsg": "A conferência ainda não começou. Se for o anfitrião, por favor autentique. Caso contrário, por favor aguarde que o anfitrião chegue.",
"WaitingForHostTitle": "À espera do anfitrião ...",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
"WaitingForHostTitle": "À espera de um moderador...",
"Yes": "Sim",
"accessibilityLabel": {
"Cancel": "Cancelar (sair da caixa de diálogo)",
@@ -269,6 +275,8 @@
"addMeetingNote": "Acrescentar uma nota sobre esta reunião",
"addOptionalNote": "Adicionar uma nota (opcional):",
"allow": "Permitir",
"allowToggleCameraDialog": "Permite que {{initiatorName}} alterne o modo de visualização da câmara?",
"allowToggleCameraTitle": "Permitir alternar a câmara?",
"alreadySharedVideoMsg": "Outro participante já está a partilhar um vídeo. Esta conferência permite apenas um vídeo partilhado de cada vez.",
"alreadySharedVideoTitle": "Só é permitido um vídeo partilhado de cada vez",
"applicationWindow": "Janela de aplicação",
@@ -329,6 +337,7 @@
"lockRoom": "Adicionar reunião $t(lockRoomPassword)",
"lockTitle": "Bloqueio falhado",
"login": "Entrar",
"loginQuestion": "Tem a certeza de que pretende iniciar sessão e abandonar a conferência?",
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e sair da conferência?",
"logoutTitle": "Sair",
"maxUsersLimitReached": "O limite para o número máximo de participantes foi atingido. A conferência está cheia. Por favor contacte o proprietário da reunião ou tente novamente mais tarde!",
@@ -409,6 +418,7 @@
"sendPrivateMessageTitle": "Enviar em privado?",
"serviceUnavailable": "Serviço indisponível",
"sessTerminated": "Chamada terminada",
"sessTerminatedReason": "A reunião foi encerrada",
"sessionRestarted": "Chamada reiniciada devido a um problema de ligação.",
"shareAudio": "Continuar",
"shareAudioTitle": "Como partilhar áudio",
@@ -440,7 +450,24 @@
"thankYou": "Obrigado por utilizar {{appName}}!",
"token": "token",
"tokenAuthFailed": "Desculpe, não está autorizado a juntar-se a esta chamada.",
"tokenAuthFailedReason": {
"audInvalid": "Valor `aud` inválido. Deveria ser `jitsi`.",
"contextNotFound": "O objeto `context` está em falta na carga útil.",
"expInvalid": "Valor `exp` inválido.",
"featureInvalid": "Funcionalidade inválida: {{feature}}, muito provavelmente ainda não implementada.",
"featureValueInvalid": "Valor inválido para a caraterística: {{feature}}.",
"featuresNotFound": "O objeto `features` está em falta na carga útil.",
"headerNotFound": "Falta o cabeçalho.",
"issInvalid": "Valor `iss` inválido. Deveria ser `chat`.",
"kidMismatch": "O ID da chave (kid) não corresponde ao sub.",
"kidNotFound": "Falta o ID da chave (kid)",
"nbfFuture": "O valor `nbf` está no futuro.",
"nbfInvalid": "Valor `nbf` inválido.",
"payloadNotFound": "Falta a carga útil.",
"tokenExpired": "O token expirou."
},
"tokenAuthFailedTitle": "A autenticação falhou",
"tokenAuthFailedWithReasons": "Lamentamos, mas não está autorizado a participar nesta chamada. Razões possíveis: {{reason}}",
"tokenAuthUnsupported": "O URL de token não é suportado.",
"transcribing": "Transcrição",
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
@@ -455,6 +482,10 @@
"viewUpgradeOptions": "Ver opções de actualização",
"viewUpgradeOptionsContent": "Para obter acesso ilimitado a funcionalidades premium como gravação, transcrições, RTMP Streaming & mais, terá de actualizar o seu plano.",
"viewUpgradeOptionsTitle": "Descobriu uma característica premium!",
"whiteboardLimitContent": "Lamentamos, mas o limite de utilizadores do quadro branco foi atingido.",
"whiteboardLimitReference": "Para mais informações consultar",
"whiteboardLimitReferenceUrl": "o nosso sítio Web",
"whiteboardLimitTitle": "Restrição da utilização do quadro branco",
"yourEntireScreen": "O seu ecrã inteiro"
},
"documentSharing": {
@@ -529,7 +560,8 @@
"numbers": "Números para entrar por chamada telefónica",
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "atingiu o limite do seu plano.",
"sip": "Endereços SIP",
"sip": "Endereço SIP",
"sipAudioOnly": "Endereço SIP só de áudio",
"title": "Partilhar",
"tooltip": "Partilhar link e acesso telefónico para esta reunião",
"upgradeOptions": "Por favor, verifique as opções de atualização em"
@@ -593,15 +625,15 @@
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua transmissão será limitada a {{limit}} min. Para uma tentativa de streaming ilimitada tente <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"off": "Transmissão em direto encerrada",
"offBy": "{{name}} parou a transmissão em direto",
"on": "Transmissão em Direto",
"on": "Iniciada a transmissão em direto",
"onBy": "{{name}} iniciou a transmissão em direto",
"pending": "Iniciando Transmissão em Direto...",
"pending": "Início da transmissão em direto...",
"serviceName": "Serviço de Transmissão em Direto",
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
"signIn": "Faça login no Google",
"signInCTA": "Faça login ou insira sua chave de transmissão em Direto do YouTube.",
"signIn": "Iniciar sessão com o Google",
"signInCTA": "Inicie sessão ou introduza a sua chave de transmissão em direto do YouTube.",
"signOut": "Sair",
"signedInAs": "Está conectado como:",
"signedInAs": "Atualmente, tem sessão iniciada como:",
"start": "Iniciar uma transmissão em direto",
"streamIdHelp": "O que é isso?",
"title": "Transmissão em direto",
@@ -636,13 +668,13 @@
"knockingParticipantList": "Lista de participantes a expulsar",
"lobbyChatStartedNotification": "{{moderator}} iniciou com {{attendee}} uma conversa na sala de espera",
"lobbyChatStartedTitle": "{{moderator}} iniciou consigo uma conversa na sala de espera.",
"lobbyClosed": "A sala de espera foi encerrada.",
"nameField": "Introduza o seu nome",
"notificationLobbyAccessDenied": "{{targetParticipantName}} foi recusada a adesão por {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} foi autorizado a aderir por {{originParticipantName}}",
"notificationLobbyDisabled": "A sala de espera foi desactivada por {{originParticipantName}}",
"notificationLobbyEnabled": "A sala de espera foi activada por {{originParticipantName}}",
"notificationTitle": "Sala de espera",
"passwordField": "Introduza a senha da reunião",
"passwordJoinButton": "Solicitar",
"reject": "Rejeitar",
"rejectAll": "Rejeitar todos",
@@ -701,6 +733,7 @@
"dataChannelClosed": "Deficiência na qualidade do vídeo",
"dataChannelClosedDescription": "O canal de ponte foi desconectado e, portanto, a qualidade do vídeo está limitada à sua configuração mais baixa.",
"disabledIframe": "A incorporação destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondary": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Por favor, use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disconnected": "desconectado",
"displayNotifications": "Mostrar notificações para",
"dontRemindMe": "Não me lembre",
@@ -776,7 +809,9 @@
"videoUnmuteBlockedDescription": "A operação de ligar a câmara e partilhar o ambiente de trabalho foi temporariamente bloqueada devido aos limites do sistema.",
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!",
"viewLobby": "Ver sala de espera",
"waitingParticipants": "{{waitingParticipants}} pessoas"
"waitingParticipants": "{{waitingParticipants}} pessoas",
"whiteboardLimitDescription": "Guarde o seu progresso, pois o limite de utilizadores será atingido em breve e o quadro branco será encerrado.",
"whiteboardLimitTitle": "Utilização do quadro branco"
},
"participantsPane": {
"actions": {
@@ -785,6 +820,7 @@
"askUnmute": "Pedir para ligar o som",
"audioModeration": "Ligar o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"breakoutRooms": "Salas simultâneas",
"invite": "Convidar alguém",
"moreModerationActions": "Mais opções de moderação",
"moreModerationControls": "Mais controlos de moderação",
@@ -1061,6 +1097,7 @@
"alertOk": "OK",
"alertTitle": "Atenção",
"alertURLText": "A URL digitada do servidor é inválida",
"apply": "Aplicar",
"buildInfoSection": "Informações de compilação",
"conferenceSection": "Conferência",
"disableCallIntegration": "Desactivar a integração de chamadas nativas",
@@ -1071,6 +1108,7 @@
"displayNamePlaceholderText": "Ex: João Dias",
"email": "Email",
"emailPlaceholderText": "email@example.com",
"gavatarMessage": "Se o seu e-mail estiver associado a uma conta Gravatar, utilizá-la-emos para apresentar a sua imagem de perfil.",
"goTo": "Ir para",
"header": "Configurações",
"help": "Ajuda",
@@ -1129,7 +1167,7 @@
"audioOnly": "Mudar para apenas áudio",
"audioRoute": "Selecionar o dispositivo de som",
"boo": "Vaia",
"breakoutRoom": "Entrar/Sair da sala",
"breakoutRooms": "Salas simultâneas",
"callQuality": "Gerir a qualidade do vídeo",
"carmode": "Modo de condução",
"cc": "Mudar legendas",
@@ -1254,7 +1292,7 @@
"lobbyButtonDisable": "Desativar sala de espera",
"lobbyButtonEnable": "Ativar sala de espera",
"login": "Iniciar sessão",
"logout": "Encerrar sessão",
"logout": "Terminar sessão",
"lowerYourHand": "Baixar a mão",
"moreActions": "Mais ações",
"moreOptions": "Mais opções",

View File

@@ -391,7 +391,7 @@
"serviceUnavailable": "Tjänsten otillgänglig",
"sessTerminated": "Konferensen avslutades",
"sessionRestarted": "Samtal återstartat av bryggan",
"shareAudio": "Forstätt",
"shareAudio": "Fortsätt",
"shareAudioTitle": "Hur man delar ljud",
"shareAudioWarningD1": "Du måste avsluta din skärmdelning innan du kan dela ditt ljud",
"shareAudioWarningD2": "Du måste starta om din skärmdelning och därefter klicka på \"ljuddelning\"",
@@ -427,13 +427,13 @@
"user": "Användare",
"userIdentifier": "Användar-ID",
"userPassword": "Lösenord",
"verifyParticipantConfirm": "Dem matchar",
"verifyParticipantDismiss": "Dem matchar inte",
"verifyParticipantConfirm": "De matchar",
"verifyParticipantDismiss": "De matchar inte",
"verifyParticipantQuestion": "EXPERIMENTELLT: Fråga deltagaren; {{participantName}} om han/hon kan se samma innehåll, i samma ordning.",
"verifyParticipantTitle": "Användarverifikation",
"videoLink": "Videolänk",
"viewUpgradeOptions": "Se uppgraderings alternativ",
"viewUpgradeOptionsContent": "För att få obegränsad tillgång till premiumfunktioner som inspelning, transkriptioner, RTMP -streaming och mer måste du uppgradera din plan.",
"viewUpgradeOptionsContent": "För att få obegränsad tillgång till premiumfunktioner som inspelning, transkriptioner, RTMP-streaming och mer måste du uppgradera din plan.",
"viewUpgradeOptionsTitle": "Du upptäckte en premiumfunktion!",
"yourEntireScreen": "Helskärm"
},
@@ -458,7 +458,7 @@
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Videomineatyrer"
"heading": "Videominiatyrer"
}
},
"giphy": {
@@ -1278,7 +1278,7 @@
"tileViewToggle": "Öppna eller stäng panelvyn",
"toggleCamera": "Byta kamera",
"unmute": "Slå på ljud",
"videoSettings": "Video inställningar",
"videoSettings": "Videoinställningar",
"videomute": "Inaktivera kameran",
"videounmute": "Aktivera kameran"
},
@@ -1346,9 +1346,9 @@
"videothumbnail": {
"connectionInfo": "Anslutningsinformation",
"domute": "Tysta",
"domuteOthers": "Inkativerad ljud för alla andra",
"domuteOthers": "Inaktivera ljud för alla andra",
"domuteVideo": "Inaktivera kamera",
"domuteVideoOfOthers": "Inkativera kamera för alla andra",
"domuteVideoOfOthers": "Inaktivera kamera för alla andra",
"flip": "Vänd",
"grantModerator": "Godkänn moderator",
"hideSelfView": "Dölj självvyn",

View File

@@ -1251,7 +1251,7 @@
"pending": "{{displayName}} davet edildi"
},
"videoStatus": {
"adjustFor": "Ayala:",
"adjustFor": "Ayarla:",
"audioOnly": "SES",
"audioOnlyExpanded": "Yalnızca ses modundasınız. Bu mod bant genişliğinden tasarruf sağlar, ancak başkalarının videolarını göremezsiniz.",
"bestPerformance": "En iyi performans",

View File

@@ -1,7 +1,7 @@
{
"addPeople": {
"accessibilityLabel": {
"meetingLink": "會議連結: {{url}}"
"meetingLink": "會議連結{{url}}"
},
"add": "邀請",
"addContacts": "邀請您的聯絡人",
@@ -16,7 +16,7 @@
"failedToAdd": "加入與會者失敗",
"googleEmail": "Google Email",
"inviteMoreHeader": "目前只有您一個人在會議中",
"inviteMoreMailSubject": "加入{{appName}}會議",
"inviteMoreMailSubject": "加入 {{appName}} 會議",
"inviteMorePrompt": "邀請更多人",
"linkCopied": "已將連結複製至剪貼簿",
"noResults": "沒有符合的搜尋結果",
@@ -29,7 +29,7 @@
"sipAddresses": "SIP 位址",
"telephone": "電話號碼:{{number}}",
"title": "邀請他人至會議",
"yahooEmail": "Yahoo! Email"
"yahooEmail": "Yahoo Email"
},
"audioDevices": {
"bluetooth": "藍牙",
@@ -56,24 +56,29 @@
},
"breakoutRooms": {
"actions": {
"add": "新增討論室",
"autoAssign": "自動分配至討論室",
"add": "新增分組討論室",
"autoAssign": "自動分配至分組討論室",
"close": "關閉",
"join": "加入",
"leaveBreakoutRoom": "離開討論室",
"leaveBreakoutRoom": "離開分組討論室",
"more": "更多",
"remove": "移除",
"rename": "重新命名",
"renameBreakoutRoom": "重新命名討論室",
"renameBreakoutRoom": "重新命名分組討論室",
"sendToBreakoutRoom": "將與會者移至:"
},
"breakoutList": "分組討論室列表",
"buttonLabel": "分組討論室",
"defaultName": "分組討論室 #{{index}}",
"hideParticipantList": "隱藏與會者列表",
"mainRoom": "主會議室",
"notifications": {
"joined": "正在加入「{{name}}」分組討論室",
"joinedMainRoom": "正在加入主會議室",
"joinedTitle": "分組討論室"
}
},
"showParticipantList": "顯示與會者列表",
"title": "分組討論室"
},
"calendarSync": {
"addMeetingURL": "增加會議連結",
@@ -107,11 +112,11 @@
"enter": "加入聊天室",
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
"fieldPlaceHolder": "在此輸入您的訊息",
"lobbyChatMessageTo": "大廳聊天訊息傳送至{{recipient}}",
"lobbyChatMessageTo": "大廳聊天訊息傳送至 {{recipient}}",
"message": "訊息",
"messageAccessibleTitle": "{{user}}",
"messageAccessibleTitleMe": "",
"messageTo": "傳送私人訊息至{{recipient}}",
"messageAccessibleTitleMe": "",
"messageTo": "傳送私人訊息至 {{recipient}}",
"messagebox": "輸入訊息",
"newMessages": "新訊息",
"nickname": {
@@ -120,7 +125,7 @@
"titleWithPolls": "輸入名稱來使用聊天與投票"
},
"noMessagesMessage": "此會議尚無訊息,在此開始對話聊天!",
"privateNotice": "傳送私人訊息至{{recipient}}",
"privateNotice": "傳送私人訊息至 {{recipient}}",
"sendButton": "傳送",
"smileysPanel": "Emoji 面板",
"tabs": {
@@ -133,7 +138,7 @@
},
"chromeExtensionBanner": {
"buttonText": "安裝 Chrome 擴充功能",
"buttonTextEdge": "安裝 Edge 外掛程式",
"buttonTextEdge": "安裝 Edge 擴充功能",
"close": "關閉",
"dontShowAgain": "不要再問了",
"installExtensionText": "安裝適用於 Google 行事曆及 Office 365 整合的擴充功能"
@@ -151,14 +156,14 @@
"DISCONNECTED": "已斷線",
"DISCONNECTING": "中斷連接中",
"ERROR": "錯誤",
"FETCH_SESSION_ID": "正在取得工作階段ID...",
"GET_SESSION_ID_ERROR": "取得工作階段ID時發生錯誤{{code}}",
"GOT_SESSION_ID": "正在取得工作階段ID...完成",
"LOW_BANDWIDTH": "已關閉{{displayName}}的視訊以節省流量"
"FETCH_SESSION_ID": "正在取得工作階段 ID...",
"GET_SESSION_ID_ERROR": "取得工作階段 ID 時發生錯誤:{{code}}",
"GOT_SESSION_ID": "正在取得工作階段 ID... 完成",
"LOW_BANDWIDTH": "已關閉 {{displayName}} 的視訊以節省頻寬"
},
"connectionindicator": {
"address": "位址:",
"audio_ssrc": "音訊SSRC",
"audio_ssrc": "音訊 SSRC",
"bandwidth": "預估頻寬:",
"bitrate": "連線速率:",
"bridgeCount": "伺服器數量:",
@@ -175,7 +180,7 @@
"more": "顯示更多",
"no": "否",
"packetloss": "封包遺失率:",
"participant_id": "與會者ID",
"participant_id": "與會者 ID",
"quality": {
"good": "良好",
"inactive": "閒置",
@@ -192,7 +197,7 @@
"status": "連線狀態:",
"transport": "傳輸協定:",
"transport_plural": "傳輸:",
"video_ssrc": "視訊SSRC",
"video_ssrc": "視訊 SSRC",
"yes": "是"
},
"dateUtils": {
@@ -201,33 +206,33 @@
"yesterday": "昨天"
},
"deepLinking": {
"appNotInstalled": "您需要在手機上安裝{{app}}行動應用程式才能加入這場會議。",
"description": "什麼事情都沒發生?我們已嘗試在您的{{app}}桌面應用程式開啟會議。請再試一次,或是在{{app}}網路應用程式開啟會議。",
"descriptionNew": "什麼事情都沒發生?我們已嘗試在您的{{app}}桌面應用程式開啟會議。<br /><br />您可以再試一次,或在網頁上啟動。",
"descriptionWithoutWeb": "什麼事情都沒發生?我們已試著將您的會議在桌面應用程式{{app}}中啟動。",
"downloadApp": "下載App",
"downloadMobileApp": "從App Store下載",
"ifDoNotHaveApp": "如果您尚未安裝App",
"ifHaveApp": "如果您已經安裝此App",
"joinInApp": "使用App加入會議",
"joinInAppNew": "在APP中加入",
"appNotInstalled": "您需要在手機上安裝 {{app}} 應用程式才能加入這場會議。",
"description": "什麼事情都沒發生?我們已嘗試在您的 {{app}} 桌面應用程式開啟會議。請再試一次,或是在 {{app}} 網頁應用程式開啟會議。",
"descriptionNew": "什麼事情都沒發生?我們已嘗試在您的 {{app}} 桌面應用程式開啟會議。<br /><br />您可以再試一次,或在網頁上啟動。",
"descriptionWithoutWeb": "什麼事情都沒發生?我們已試著將您的會議在桌面應用程式 {{app}} 中啟動。",
"downloadApp": "下載應用程式",
"downloadMobileApp": "從 App Store 下載",
"ifDoNotHaveApp": "如果您尚未安裝應用程式",
"ifHaveApp": "如果您已經安裝應用程式",
"joinInApp": "使用應用程式加入會議",
"joinInAppNew": "在應用程式中加入",
"joinInBrowser": "在瀏覽器中加入",
"launchMeetingLabel": "您想如何加入此會議?",
"launchWebButton": "在瀏覽器開啟",
"noMobileApp": "您尚未安裝該APP",
"noMobileApp": "您尚未安裝該應用程式",
"termsAndConditions": "繼續操作即表示您同意我們的<a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>條款與條件。</a>",
"title": "正在{{app}}發起您的會議...",
"titleNew": "正在啟您的會議...",
"title": "正在 {{app}} 開啟您的會議...",
"titleNew": "正在啟您的會議...",
"tryAgainButton": "在桌面上再試一次",
"unsupportedBrowser": "您似乎正在使用我們不支援的瀏覽器"
"unsupportedBrowser": "您似乎正在使用我們不支援的瀏覽器"
},
"defaultLink": "例如{{url}}",
"defaultNickname": "例如:Jane Pink",
"defaultLink": "例如 {{url}}",
"defaultNickname": "例如:王小明",
"deviceError": {
"cameraError": "無法存取您的網路攝影機",
"cameraPermission": "取網路攝影機權限時發生錯誤",
"cameraPermission": "取網路攝影機權限時發生錯誤",
"microphoneError": "無法存取您的麥克風",
"microphonePermission": "取麥克風權限時發生錯誤"
"microphonePermission": "取麥克風權限時發生錯誤"
},
"deviceSelection": {
"hid": {
@@ -239,13 +244,13 @@
"noPermission": "未取得權限",
"previewUnavailable": "無法預覽",
"selectADevice": "選擇裝置",
"testAudio": "播放測試聲音"
"testAudio": "測試"
},
"dialIn": {
"screenTitle": "通話記錄"
},
"dialOut": {
"statusMessage": "現在狀態為{{status}}"
"statusMessage": "現在狀態為 {{status}}"
},
"dialog": {
"Back": "返回",
@@ -256,6 +261,7 @@
"Share": "分享",
"Submit": "送出",
"WaitForHostMsg": "此會議尚未開始,如果您是會議主持人,請進行認證並以主持人身分開始會議。",
"WaitingForHostButton": "等待主持人",
"WaitingForHostTitle": "正在等候主持人加入...",
"Yes": "是",
"accessibilityLabel": {
@@ -269,6 +275,8 @@
"addMeetingNote": "新增此會議的備註",
"addOptionalNote": "新增備註(選填):",
"allow": "允許",
"allowToggleCameraDialog": "您要允許 {{initiatorName}} 切換您的鏡頭朝向嗎?",
"allowToggleCameraTitle": "要允許切換鏡頭嗎?",
"alreadySharedVideoMsg": "其他與會者正在分享影像,同一時間只有一個與會者可以分享影像螢幕。",
"alreadySharedVideoTitle": "同一時間只允許一位影像分享",
"applicationWindow": "應用程式視窗",
@@ -277,14 +285,14 @@
"cameraNotFoundError": "找不到網路攝影機。",
"cameraNotSendingData": "我們無法存取您的網路攝影機,請檢查是否有其他應用程式正在使用這個裝置,並從裝置選單裡選擇其他設備或者重新載入。",
"cameraNotSendingDataTitle": "無法存取網路攝影機",
"cameraPermissionDeniedError": "未取得網路攝影機的存取權,您仍可參加會議,但其他人無法看到。按一下網址列中的「攝影機」圖示 ,然後選取「一律允許」選項。",
"cameraPermissionDeniedError": "未取得網路攝影機的存取權,您仍可參加會議,但其他人無法看到。按一下網址列中的「攝影機」圖示 ,然後選取「一律允許」選項。",
"cameraTimeoutError": "無法啟動視訊裝置,連線逾時!",
"cameraUnknownError": "由於不明原因,無法存取網路攝影機。",
"cameraUnsupportedResolutionError": "您的網路攝影機不支援所需的影像解析度。",
"close": "關閉",
"conferenceDisconnectMsg": "請檢查一下網路連線,將在{{seconds}}秒後重新連接...",
"conferenceDisconnectMsg": "請檢查一下網路連線,將在 {{seconds}} 秒後重新連接...",
"conferenceDisconnectTitle": "您已經被中斷連接。",
"conferenceReloadMsg": "我們正試著修復狀況,將在{{seconds}}秒後重新連接...",
"conferenceReloadMsg": "我們正試著修復狀況,將在 {{seconds}} 秒後重新連接...",
"conferenceReloadTitle": "喔哦!好像有東西壞掉囉。",
"confirm": "確認",
"confirmNo": "否",
@@ -298,7 +306,7 @@
"dismiss": "取消",
"displayNameRequired": "嗨!請問大名?",
"done": "完成",
"e2eeDescription": "請注意端對端加密目前是實驗性功能啟用端對端加密將停用部分伺服器端提供的服務例如透過電話加入會議。另外透過網頁版加入會議還需要使用支援Insertable Streams的瀏覽器。",
"e2eeDescription": "請注意,端對端加密目前是實驗性功能,啟用端對端加密將停用部分伺服器端提供的服務,例如:透過電話加入會議。另外,透過網頁版加入會議還需要使用支援 Insertable Streams 的瀏覽器。",
"e2eeDisabledDueToMaxModeDescription": "由於會議中的人數過多,故無法啟用端對端加密。",
"e2eeLabel": "啟用端對端加密",
"e2eeWarning": "警告:看來此會議中不是每位與會者都啟用了端對端加密,如果您啟用了,他們可能無法看或聽到您。",
@@ -307,7 +315,7 @@
"enterDisplayName": "請在此輸入您自己的名字",
"error": "錯誤",
"gracefulShutdown": "服務目前正在維護中,請稍後再試。",
"grantModeratorDialog": "您確定要授予{{participantName}}主持人權限嗎?",
"grantModeratorDialog": "您確定要授予 {{participantName}} 主持人權限嗎?",
"grantModeratorTitle": "授予主持人權限",
"hide": "隱藏",
"hideShareAudioHelper": "不再顯示",
@@ -315,16 +323,16 @@
"incorrectRoomLockPassword": "密碼不符",
"internalError": "喔哦!出現了點問題,發生錯誤:{{error}}",
"internalErrorTitle": "內部錯誤",
"kickMessage": "您可以聯絡{{participantDisplayName}}取得更詳細資訊。",
"kickMessage": "您可以聯絡 {{participantDisplayName}} 取得更詳細資訊。",
"kickParticipantButton": "移除",
"kickParticipantDialog": "您確定要將這位與會者移除嗎?",
"kickParticipantTitle": "移除這位與會者?",
"kickTitle": "噢!{{participantDisplayName}}將您從會議中移除",
"kickTitle": "噢!{{participantDisplayName}} 將您從會議中移除",
"linkMeeting": "連結會議",
"linkMeetingTitle": "將會議連結至 Salesforce",
"liveStreaming": "直播串流中",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "正在錄製,無法使用",
"localUserControls": "本使用者控制",
"localUserControls": "本使用者控制",
"lockMessage": "無法鎖定會議。",
"lockRoom": "新增會議 $t(lockRoomPasswordUppercase)",
"lockTitle": "鎖定失敗",
@@ -346,9 +354,9 @@
"muteEveryoneDialog": "與會者可以隨時解除自己的靜音狀態。",
"muteEveryoneDialogModerationOn": "與會者可以隨時請求發言。",
"muteEveryoneElseDialog": "靜音後,您就不能再解除對方的靜音,但對方可以隨時解除自己的靜音狀態。",
"muteEveryoneElseTitle": "是否要讓除了{{whom}}以外的人靜音?",
"muteEveryoneElseTitle": "是否要讓除了 {{whom}} 以外的人靜音?",
"muteEveryoneElsesVideoDialog": "一旦停用,您就不能再重新開啟對方的網路攝影機,但對方隨時能重新開啟自己的網路攝影機。",
"muteEveryoneElsesVideoTitle": "是否要關閉除了{{whom}}以外的人的網路攝影機?",
"muteEveryoneElsesVideoTitle": "是否要關閉除了 {{whom}} 以外的人的網路攝影機?",
"muteEveryoneSelf": "您自己",
"muteEveryoneStartMuted": "現在所有人皆已靜音",
"muteEveryoneTitle": "要將所有人靜音嗎?",
@@ -364,12 +372,12 @@
"muteParticipantsVideoDialog": "確定要停用這位與會者的網路攝影機?您不能再重新開啟對方的網路攝影機,但他們隨時能重新開啟。",
"muteParticipantsVideoDialogModerationOn": "您確定要關閉此與會者的網路攝影機嗎?您和他都無法再將視訊重新開啟。",
"muteParticipantsVideoTitle": "要關閉此與會者的網路攝影機嗎?",
"noDropboxToken": "沒有有效的 Dropbox token",
"noDropboxToken": "沒有有效的 Dropbox 權杖",
"password": "密碼",
"passwordLabel": "會議已被一位與會者鎖定,請輸入$t(lockRoomPassword)以加入。",
"passwordNotSupported": "尚未支援設定會議$t(lockRoomPassword)。",
"passwordNotSupportedTitle": "尚未支援$t(lockRoomPasswordUppercase)",
"passwordRequired": "需要$t(lockRoomPasswordUppercase)",
"passwordLabel": "會議已被一位與會者鎖定,請輸入 $t(lockRoomPassword) 以加入。",
"passwordNotSupported": "尚未支援設定會議 $t(lockRoomPassword)。",
"passwordNotSupportedTitle": "尚未支援 $t(lockRoomPasswordUppercase)",
"passwordRequired": "需要 $t(lockRoomPasswordUppercase)",
"permissionCameraRequiredError": "參與視訊會議需要存取網路攝影機,請在設定中啟用權限",
"permissionErrorTitle": "需要權限",
"permissionMicRequiredError": "參與音訊會議需要存取麥克風,請在設定中啟用權限",
@@ -378,19 +386,19 @@
"recording": "錄製中",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "正在直播時無法使用",
"rejoinNow": "立即重新加入",
"remoteControlAllowedMessage": "{{user}}接受您進行遠端控制的請求!",
"remoteControlDeniedMessage": "{{user}}拒絕您進行遠端控制的請求!",
"remoteControlErrorMessage": "在嘗試向{{user}}請求遠端控制權限時發生錯誤!",
"remoteControlRequestMessage": "您要允許{{user}}遠端控制您的桌面嗎?",
"remoteControlAllowedMessage": "{{user}} 接受您進行遠端控制的請求!",
"remoteControlDeniedMessage": "{{user}} 拒絕您進行遠端控制的請求!",
"remoteControlErrorMessage": "在嘗試向 {{user}} 請求遠端控制權限時發生錯誤!",
"remoteControlRequestMessage": "您要允許 {{user}} 遠端控制您的桌面嗎?",
"remoteControlShareScreenWarning": "請注意:如果按下「允許」您將會分享自己的螢幕!",
"remoteControlStopMessage": "遠端控制會話已結束!",
"remoteControlTitle": "遠端桌面控制",
"remoteUserControls": "{{username}}的遠端使用者控制",
"remoteUserControls": "{{username}} 的遠端使用者控制",
"removePassword": "移除 $t(lockRoomPassword)",
"removeSharedVideoMsg": "您確定要移除您分享的影像嗎?",
"removeSharedVideoTitle": "移除分享的影像",
"renameBreakoutRoomLabel": "討論室名稱",
"renameBreakoutRoomTitle": "重新命名討論室",
"renameBreakoutRoomLabel": "分組討論室名稱",
"renameBreakoutRoomTitle": "重新命名分組討論室",
"reservationError": "預約系統錯誤",
"reservationErrorMsg": "錯誤代碼:{{code}},訊息:{{msg}}",
"retry": "重試",
@@ -410,6 +418,7 @@
"sendPrivateMessageTitle": "私人回覆?",
"serviceUnavailable": "服務無法使用",
"sessTerminated": "通話已經終止",
"sessTerminatedReason": "會議已經終止",
"sessionRestarted": "通話因連線問題重新啟動。",
"shareAudio": "繼續",
"shareAudioTitle": "如何分享音訊",
@@ -438,7 +447,7 @@
"stopRecordingWarning": "確定要停用錄製嗎?",
"stopStreamingWarning": "確定要停止直播串流嗎?",
"streamKey": "直播串流金鑰",
"thankYou": "感謝您使用{{appName}}",
"thankYou": "感謝您使用 {{appName}}",
"token": "token",
"tokenAuthFailed": "抱歉,您未被允許加入這個通話。",
"tokenAuthFailedReason": {
@@ -450,29 +459,33 @@
"featuresNotFound": "`context` 未在負載中找到。",
"headerNotFound": "標頭缺失。",
"issInvalid": "無效的`iss`值,應為`chat`。",
"kidMismatch": "金鑰IDkid與子項不符。",
"kidNotFound": "缺少金鑰IDkid。",
"kidMismatch": "金鑰 IDkid與子項不符。",
"kidNotFound": "缺少金鑰 IDkid。",
"nbfFuture": "`nbf`值在未來。",
"nbfInvalid": "無效的`nbf`值。",
"payloadNotFound": "未找到負載。",
"tokenExpired": "Token已過期。"
"payloadNotFound": "負載缺失。",
"tokenExpired": "權杖已過期。"
},
"tokenAuthFailedTitle": "驗證失敗",
"tokenAuthFailedWithReasons": "抱歉,您無法參加這個通話,可能原因:{{reason}}",
"tokenAuthUnsupported": "不支援的令牌位址。",
"tokenAuthUnsupported": "不支援權杖網址。",
"transcribing": "轉錄中",
"unlockRoom": "移除會議 $t(lockRoomPassword)",
"user": "使用者",
"userIdentifier": "使用者ID",
"userIdentifier": "使用者 ID",
"userPassword": "使用者密碼",
"verifyParticipantConfirm": "符合",
"verifyParticipantDismiss": "不符合",
"verifyParticipantQuestion": "實驗性功能:詢問與會者{{participantName}}是否以相同順序看到相同內容。",
"verifyParticipantQuestion": "實驗性功能:詢問與會者 {{participantName}} 是否以相同順序看到相同內容。",
"verifyParticipantTitle": "使用者驗證",
"videoLink": "影片連結",
"viewUpgradeOptions": "查看升級方案",
"viewUpgradeOptionsContent": "若要不受限制地使用錄製、逐字稿、RTMP 串流等進階版功能,您需要升級您的方案。",
"viewUpgradeOptionsTitle": "您找到了進階版功能!",
"whiteboardLimitContent": "抱歉,已達到白板使用者限制。",
"whiteboardLimitReference": "若要了解詳情,請前往",
"whiteboardLimitReferenceUrl": "我們的網站",
"whiteboardLimitTitle": "白板使用受限",
"yourEntireScreen": "您的整個螢幕"
},
"documentSharing": {
@@ -521,7 +534,7 @@
"copyNumber": "複製號碼",
"country": "國家",
"dialANumber": "若要參加您的會議,請撥打以下其中一支號碼,然後輸入 PIN 碼。",
"dialInConferenceID": "PIN碼",
"dialInConferenceID": "PIN 碼:",
"dialInNotSupported": "抱歉,目前不支援電話撥入。",
"dialInNumber": "撥入:",
"dialInSummaryError": "目前解析撥入資訊錯誤,請稍後再試一次。",
@@ -536,7 +549,7 @@
"inviteTextiOSPersonal": "{{name}}邀請您加入會議。",
"inviteTextiOSPhone": "若要透過電話加入,請使用此號碼:{{number}},,{{conferenceID}}#。如果您需要其他號碼,點擊此連結以檢視完整列表:{{didUrl}}。",
"inviteURLFirstPartGeneral": "您受邀參加會議。",
"inviteURLFirstPartPersonal": "{{name}}正在邀請您加入會議。\n",
"inviteURLFirstPartPersonal": "{{name}} 正在邀請您加入會議。\n",
"inviteURLSecondPart": "\n加入會議\n{{url}}\n",
"label": "撥入資訊",
"liveStreamURL": "直播串流:",
@@ -547,7 +560,8 @@
"numbers": "撥入號碼",
"password": "$t(lockRoomPasswordUppercase)",
"reachedLimit": "您已達到您的方案上限",
"sip": "SIP位址",
"sip": "SIP 位址",
"sipAudioOnly": "SIP 僅音訊位址",
"title": "分享",
"tooltip": "顯示此會議的連結及電話撥入號碼",
"upgradeOptions": "請查看升級選項於"
@@ -566,7 +580,7 @@
"searchPlaceholder": "與會者或電話號碼",
"send": "傳送"
},
"jitsiHome": "{{logo}}商標,首頁連結",
"jitsiHome": "{{logo}} 商標,首頁連結",
"keyboardShortcuts": {
"focusLocal": "聚焦於自己的影像",
"focusRemote": "聚焦於另一人的影像",
@@ -594,11 +608,11 @@
"busyTitle": "全部直播設備正在忙碌",
"changeSignIn": "切換帳號",
"choose": "選擇直播串流",
"chooseCTA": "請選擇直播串流選項,您目前是以{{email}}身份登入。",
"chooseCTA": "請選擇直播串流選項,您目前是以 {{email}} 身份登入。",
"enterStreamKey": "在此輸入您的 YouTube 直播串流金鑰。",
"error": "直播串流失敗,請重試。",
"errorAPI": "在存取您的 YouTube 直播時發生問題,請重新登入。",
"errorLiveStreamNotEnabled": "直播在{{email}}尚未啟用,請開啟直播串流或登入有啟用直播串流的帳號。",
"errorLiveStreamNotEnabled": "直播在 {{email}} 尚未啟用,請開啟直播串流或登入有啟用直播串流的帳號。",
"expandedOff": "直播已停用",
"expandedOn": "會議目前正在 YouTube 上直播。",
"expandedPending": "直播串流正被啟動...",
@@ -607,12 +621,12 @@
"googlePrivacyPolicy": "Google 隱私權政策",
"inProgress": "正在錄製或直播",
"invalidStreamKey": "直播串流金鑰可能不正確。",
"limitNotificationDescriptionNative": "您的最大直播長度將被限制在{{limit}}分鐘,若要不受限的直播,請使用{{app}}。",
"limitNotificationDescriptionWeb": "由於目前流量過大,您的最大直播長度將被限制在{{limit}}分鐘。若要不受限的直播,請使用<a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>。",
"limitNotificationDescriptionNative": "您的最大直播長度將被限制在 {{limit}} 分鐘,若要不受限的直播,請使用 {{app}}。",
"limitNotificationDescriptionWeb": "由於目前流量過大,您的最大直播長度將被限制在 {{limit}} 分鐘。若要不受限的直播,請使用 <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>。",
"off": "直播串流已停用",
"offBy": "{{name}}停用了直播串流",
"offBy": "{{name}} 停用了直播串流",
"on": "直播串流已啟動",
"onBy": "{{name}}啟動了直播串流",
"onBy": "{{name}} 啟動了直播串流",
"pending": "啟動直播串流...",
"serviceName": "直播串流服務",
"sessionAlreadyActive": "已在錄製或直播此工作階段。",
@@ -652,13 +666,13 @@
"knockButton": "請求加入",
"knockTitle": "有人想要加入會議",
"knockingParticipantList": "請求加入的與會者名單",
"lobbyChatStartedNotification": "{{moderator}}{{attendee}}開始在大廳中聊天",
"lobbyChatStartedTitle": "{{moderator}}與您開始在大廳中聊天。",
"lobbyChatStartedNotification": "{{moderator}}{{attendee}} 開始在大廳中聊天",
"lobbyChatStartedTitle": "{{moderator}} 與您開始在大廳中聊天。",
"nameField": "輸入您的名字",
"notificationLobbyAccessDenied": "{{originParticipantName}}拒絕了{{targetParticipantName}}的加入請求",
"notificationLobbyAccessGranted": "{{originParticipantName}}同意了{{targetParticipantName}}的加入請求",
"notificationLobbyDisabled": "{{originParticipantName}}已停用大廳模式",
"notificationLobbyEnabled": "{{originParticipantName}}已啟用大廳模式",
"notificationLobbyAccessDenied": "{{originParticipantName}} 拒絕了 {{targetParticipantName}} 的加入請求",
"notificationLobbyAccessGranted": "{{originParticipantName}} 同意了 {{targetParticipantName}} 的加入請求",
"notificationLobbyDisabled": "{{originParticipantName}} 已停用大廳模式",
"notificationLobbyEnabled": "{{originParticipantName}} 已啟用大廳模式",
"notificationTitle": "大廳",
"passwordField": "輸入會議密碼",
"passwordJoinButton": "加入",
@@ -673,19 +687,19 @@
"on": "開啟",
"unknown": "未知"
},
"dialogTitle": "本錄製控制",
"dialogTitle": "本錄製控制",
"duration": "時長",
"durationNA": "不適用",
"encoding": "編碼中",
"label": "本錄製",
"labelToolTip": "本錄製已啟用",
"localRecording": "本錄製中",
"label": "本錄製",
"labelToolTip": "本錄製已啟用",
"localRecording": "本錄製中",
"me": "我",
"messages": {
"engaged": "已啟用本錄製。",
"engaged": "已啟用本錄製。",
"finished": "錄製會話 {{token}} 已結束,請將傳送錄製檔案至主持人。",
"finishedModerator": "錄製階段{{token}}已完成,本錄製追蹤已存檔,請要求各與會者提供其錄製檔案。",
"notModerator": "您不是主持人,無法開始或停止本錄製。"
"finishedModerator": "錄製階段{{token}} 已完成,本錄製追蹤已存檔,請要求各與會者提供其錄製檔案。",
"notModerator": "您不是主持人,無法開始或停止本錄製。"
},
"moderator": "主持人",
"no": "否",
@@ -713,36 +727,37 @@
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
"audioUnmuteBlockedTitle": "麥克風解除靜音遭封鎖!",
"chatMessages": "聊天訊息",
"connectedOneMember": "{{name}}加入了會議",
"connectedThreePlusMembers": "{{name}}與其他人加入了會議",
"connectedTwoMembers": "{{first}}與{{second}}加入了會議",
"connectedOneMember": "{{name}} 加入了會議",
"connectedThreePlusMembers": "{{name}} 與其他人加入了會議",
"connectedTwoMembers": "{{first}} 與{{second}} 加入了會議",
"dataChannelClosed": "視訊品質受限",
"dataChannelClosedDescription": "橋接通道已斷開,視訊品質降至最低設定。",
"disabledIframe": "嵌入僅供示範使用,此通話將於 {{timeout}} 分鐘後中斷連線。",
"disabledIframeSecondary": "內嵌 {{domain}} 僅為展示用途,此通話將在 {{timeout}} 分鐘後中斷連線。請使用在正式環境使用 <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi 服務</a>來內嵌!",
"disconnected": "已經中斷連接",
"displayNotifications": "顯示通知給",
"dontRemindMe": "不要再提醒我",
"focus": "會議焦點",
"focusFail": "{{component}}無法使用 - {{ms}}秒後重試",
"focusFail": "{{component}} 無法使用 - {{ms}} 秒後重試",
"gifsMenu": "GIPHY",
"groupTitle": "通知",
"hostAskedUnmute": "主持人希望發言",
"invitedOneMember": "{{name}}已受邀請",
"invitedThreePlusMembers": "{{name}}{{count}}位人員已受邀請",
"invitedTwoMembers": "{{first}}{{second}}已受邀請",
"hostAskedUnmute": "主持人希望發言",
"invitedOneMember": "{{name}} 已受邀請",
"invitedThreePlusMembers": "{{name}}{{count}} 位人員已受邀請",
"invitedTwoMembers": "{{first}}{{second}} 已受邀請",
"joinMeeting": "加入",
"kickParticipant": "{{kicked}}已被{{kicker}}移除會議",
"leftOneMember": "{{name}}已離開會議",
"leftThreePlusMembers": "{{name}}與其他人已離開會議",
"leftTwoMembers": "{{first}}{{second}}已離開會議",
"kickParticipant": "{{kicked}} 已被 {{kicker}} 移除會議",
"leftOneMember": "{{name}} 已離開會議",
"leftThreePlusMembers": "{{name}} 與其他人已離開會議",
"leftTwoMembers": "{{first}}{{second}} 已離開會議",
"linkToSalesforce": "連結至 Salesforce",
"linkToSalesforceDescription": "您可以將會議摘要連結至 Salesforce 物件。",
"linkToSalesforceError": "無法將會議連結至 Salesforce",
"linkToSalesforceKey": "連結此會議",
"linkToSalesforceProgress": "正在將會議連結至 Salesforce...",
"linkToSalesforceSuccess": "會議已連結至 Salesforce",
"localRecordingStarted": "{{name}}已啟用本錄製",
"localRecordingStopped": "{{name}}已停用本錄製",
"localRecordingStarted": "{{name}} 已啟用本錄製",
"localRecordingStopped": "{{name}} 已停用本錄製",
"me": "我",
"moderationInEffectCSDescription": "若要分享您的螢幕,請舉手",
"moderationInEffectCSTitle": "分享螢幕已被主持人停用",
@@ -754,11 +769,11 @@
"moderationRequestFromParticipant": "想要發言",
"moderationStartedTitle": "開始主持",
"moderationStoppedTitle": "停止主持",
"moderationToggleDescription": "由{{participantDisplayName}}",
"moderationToggleDescription": "由 {{participantDisplayName}}",
"moderator": "主持人權限已經取得!",
"muted": "您已經啟動通話,處於靜音。",
"mutedRemotelyDescription": "當您準備好要發言,隨時可以取消靜音。當您結束之後再回復成靜音,保持會議安靜。",
"mutedRemotelyTitle": "您已經被{{participantDisplayName}}設為靜音!",
"mutedRemotelyTitle": "您已經被 {{participantDisplayName}} 設為靜音!",
"mutedTitle": "您目前處於靜音!",
"newDeviceAction": "使用",
"newDeviceAudioTitle": "偵測到新的音效裝置",
@@ -771,11 +786,11 @@
"oldElectronClientDescription3": "",
"participantWantsToJoin": "希望加入會議",
"participantsWantToJoin": "希望加入會議",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase)已被其他與會者移除",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase)由其他與會者設定",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他與會者移除",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) 由其他與會者設定",
"raiseHandAction": "舉手發言",
"raisedHand": "{{name}}想要發言。",
"raisedHands": "{{participantName}}與其他{{raisedHands}}人",
"raisedHands": "{{participantName}} 與其他 {{raisedHands}} 人",
"reactionSounds": "停用音效",
"reactionSoundsForAll": "停用所有音效",
"screenShareNoAudio": "您未在選擇視窗時勾選分享音訊",
@@ -790,11 +805,13 @@
"suboptimalExperienceTitle": "瀏覽器警告",
"unmute": "取消靜音",
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
"videoMutedRemotelyTitle": "您的視訊已被{{participantDisplayName}}停用",
"videoMutedRemotelyTitle": "您的視訊已被 {{participantDisplayName}} 停用",
"videoUnmuteBlockedDescription": "啟用網路攝影機與分享螢幕由於系統限制而被暫時封鎖。",
"videoUnmuteBlockedTitle": "啟用網路攝影機與分享螢幕遭封鎖!",
"viewLobby": "檢視大廳",
"waitingParticipants": "{{waitingParticipants}}人"
"waitingParticipants": "{{waitingParticipants}} 人",
"whiteboardLimitDescription": "由於即將超出使用者限制,白板將關閉,請儲存您的進度。",
"whiteboardLimitTitle": "白板使用情況"
},
"participantsPane": {
"actions": {
@@ -803,6 +820,7 @@
"askUnmute": "要求解除靜音",
"audioModeration": "自我解除靜音",
"blockEveryoneMicCamera": "停用所有人的麥克風和網路攝影機",
"breakoutRooms": "分組討論室",
"invite": "邀請他人",
"moreModerationActions": "更多主持人選項",
"moreModerationControls": "更多主持人操作",
@@ -817,15 +835,15 @@
},
"close": "關閉",
"headings": {
"lobby": "大廳({{count}}人)",
"participantsList": "會議與會者({{count}}人)",
"visitors": "訪客({{count}}人)",
"waitingLobby": "於大廳等候({{count}}人)"
"lobby": "大廳({{count}} 人)",
"participantsList": "會議與會者({{count}} 人)",
"visitors": "訪客({{count}} 人)",
"waitingLobby": "於大廳等候({{count}} 人)"
},
"search": "搜尋與會者",
"title": "與會者"
},
"passwordDigitsOnly": "上限為{{number}}位數",
"passwordDigitsOnly": "上限為 {{number}} 位數",
"passwordSetRemotely": "由其他與會者設定",
"pinParticipant": "{{participantName}} - 釘選",
"pinnedParticipant": "與會者被釘選",
@@ -834,13 +852,13 @@
"skip": "跳過",
"submit": "送出"
},
"by": "由{{ name }}",
"by": "由 {{ name }}",
"create": {
"addOption": "新增選項",
"answerPlaceholder": "選項{{index}}",
"answerPlaceholder": "選項 {{index}}",
"cancel": "取消",
"create": "建立投票",
"pollOption": "選項{{index}}",
"pollOption": "選項 {{index}}",
"pollQuestion": "投票問題",
"questionPlaceholder": "詢問問題",
"removeOption": "移除選項",
@@ -879,7 +897,7 @@
"audioHighQuality": "您的音訊品質極佳。",
"audioLowNoVideo": "您的音訊品質較差,且無視訊功能。",
"goodQuality": "太好了!您的媒體品質良好。",
"noMediaConnectivity": "我們無法為此測試建立媒體連線通常是防火牆或NAT的問題。",
"noMediaConnectivity": "我們無法為此測試建立媒體連線,通常是防火牆或 NAT 的問題。",
"noVideo": "您的視訊畫質將會很糟糕。",
"undetectable": "如果您仍無法在瀏覽器中進行通話,我們建議您檢查喇叭、麥克風、及網路攝影機的設置,確認是否允許瀏覽器存取麥克風及網路攝影機,並將瀏覽器更新到最新版本。如果以上步驟無法解決問題,請聯絡網頁程式的開發者。",
"veryPoorConnection": "您的通話品質將會非常糟糕。",
@@ -958,8 +976,8 @@
},
"recording": {
"authDropboxText": "上傳至 Dropbox",
"availableSpace": "可用空間:{{spaceLeft}}MB錄製時間大約{{duration}}分鐘)",
"beta": "BETA",
"availableSpace": "可用空間:{{spaceLeft}} MB錄製時間大約 {{duration}} 分鐘)",
"beta": "測試版",
"busy": "我們正在釋放錄製資源,請過幾分鐘後再試。",
"busyTitle": "全部錄製目前忙碌",
"copyLink": "複製連結",
@@ -976,15 +994,15 @@
"highlightMomentSuccess": "已精選的時刻",
"highlightMomentSucessDescription": "您的精選時刻將新增至會議摘要。",
"inProgress": "正在錄製或直播",
"limitNotificationDescriptionNative": "由於目前流量過大,您的錄製時間被限制在{{limit}}分鐘。若要無限制的錄製,請試試 <3>{{app}}</3>。",
"limitNotificationDescriptionWeb": "由於目前流量過大,您的錄製時間被限制在{{limit}}分鐘。若要無限制的錄製,請試試 <a href={{url}}rel='noopener noreferrer' target='_blank'>{{app}}</a>。",
"limitNotificationDescriptionNative": "由於目前流量過大,您的錄製時間被限制在 {{limit}} 分鐘。若要無限制的錄製,請試試 <3>{{app}}</3>。",
"limitNotificationDescriptionWeb": "由於目前流量過大,您的錄製時間被限制在 {{limit}} 分鐘。若要無限制的錄製,請試試 <a href={{url}}rel='noopener noreferrer' target='_blank'>{{app}}</a>。",
"linkGenerated": "我們建立了您的錄製檔案的連結。",
"live": "直播",
"localRecordingNoNotificationWarning": "系統不會主動知會與會者錄製已開啟,主持人需另行通知。",
"localRecordingNoVideo": "沒有錄製的視訊",
"localRecordingStartWarning": "請確保在退出會議之前停用錄製以便保存。",
"localRecordingStartWarningTitle": "停用錄製以保存",
"localRecordingVideoStop": "關閉您的視訊也將停止本錄製,確定繼續嗎?",
"localRecordingVideoStop": "關閉您的視訊也將停止本錄製,確定繼續嗎?",
"localRecordingVideoWarning": "錄製視訊必須在開始時啟用",
"localRecordingWarning": "確保選擇目前的分頁以錄製正確的視訊和音訊。錄製目前限制為1GB約可錄製100分鐘。",
"loggedIn": "以 {{userName}} 登入",
@@ -997,7 +1015,7 @@
"onlyRecordSelf": "僅錄製我的音訊和影片串流",
"pending": "正在準備錄製會議...",
"rec": "錄製中",
"saveLocalRecording": "將錄製檔案保存在本BETA",
"saveLocalRecording": "將錄製檔案保存在本機(測試版",
"serviceDescription": "您的錄製會由錄製服務儲存",
"serviceDescriptionCloud": "雲端錄製",
"serviceDescriptionCloudInfo": "已錄製的會議將在 24 小時後自動清除。",
@@ -1007,19 +1025,19 @@
"signOut": "登出",
"surfaceError": "請選擇目前分頁",
"title": "錄製中",
"unavailable": "喔哦!{{serviceName}}目前無法使用,我們正在解決此問題,請稍後再試。",
"unavailable": "喔哦!{{serviceName}} 目前無法使用,我們正在解決此問題,請稍後再試。",
"unavailableTitle": "錄製無法使用",
"uploadToCloud": "上傳至雲端"
},
"screenshareDisplayName": "{{name}}的螢幕",
"screenshareDisplayName": "{{name}} 的螢幕",
"sectionList": {
"pullToRefresh": "下拉以重新整理"
},
"security": {
"about": "您可以新增$t(lockRoomPassword)至您的會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
"aboutReadOnly": "主持人可以新增$t(lockRoomPassword)至會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
"insecureRoomNameWarningNative": "房間名稱不安全,任何人都可能會加入您的會議。{{recommendAction}} 了解有關保護您的會議的更多信息。",
"insecureRoomNameWarningWeb": "房間名稱不安全,任何人都可能會加入您的會議。{{recommendAction}} 在此處了解有關保護您的會議的更多信息 <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">這裡</a>。",
"about": "您可以新增 $t(lockRoomPassword) 至您的會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
"aboutReadOnly": "主持人可以新增 $t(lockRoomPassword) 至會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
"insecureRoomNameWarningNative": "房間名稱不安全,任何人都可能會加入您的會議。{{recommendAction}} 了解有關保護您的會議的更多資訊。",
"insecureRoomNameWarningWeb": "房間名稱不安全,任何人都可能會加入您的會議。{{recommendAction}} 在此處了解有關保護您的會議的更多資訊 <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">這裡</a>。",
"title": "安全性選項",
"unsafeRoomActions": {
"meeting": "請考慮使用安全按鈕保護您的會議。",
@@ -1031,10 +1049,10 @@
"audio": "音訊",
"buttonLabel": "設定",
"calendar": {
"about": "{{appName}}行事曆整合功能可安全地存取您行事曆中即將舉行的活動。",
"about": "{{appName}} 行事曆整合功能可安全地存取您行事曆中即將舉行的活動。",
"disconnect": "中斷連接",
"microsoftSignIn": "使用Microsoft帳號登入",
"signedIn": "目前正在存取{{email}}的行事曆事件,點按下方中斷連接可以停用存取行事曆事件。",
"microsoftSignIn": "使用 Microsoft 帳號登入",
"signedIn": "目前正在存取 {{email}} 的行事曆事件,點按下方中斷連接可以停用存取行事曆事件。",
"title": "行事曆"
},
"desktopShareFramerate": "桌面螢幕分享影格率",
@@ -1079,6 +1097,7 @@
"alertOk": "確定",
"alertTitle": "警告",
"alertURLText": "輸入的伺服器網址無效",
"apply": "套用",
"buildInfoSection": "組建資訊",
"conferenceSection": "會議",
"disableCallIntegration": "停用原生通話整合",
@@ -1086,23 +1105,24 @@
"disableCrashReportingWarning": "您確定要停用錯誤回報功能嗎?變更將在重啟應用程式後生效。",
"disableP2P": "停用點對點模式",
"displayName": "顯示名稱",
"displayNamePlaceholderText": "例如:John Doe",
"displayNamePlaceholderText": "例如:王小明",
"email": "電子郵件",
"emailPlaceholderText": "email@example.com",
"gavatarMessage": "如果您的電子郵件地址與 Gravatar 帳號相關聯,我們會使用 Gravatar 上的個人檔案大頭貼。",
"goTo": "前往",
"header": "設定",
"help": "協助",
"links": "連結",
"privacy": "隱私權",
"profileSection": "簡介",
"sdkVersion": "SDK版本",
"sdkVersion": "SDK 版本",
"serverURL": "伺服器網址",
"showAdvanced": "顯示進階設定",
"startCarModeInLowBandwidthMode": "同時啟用行車模式與低頻寬模式",
"startWithAudioMuted": "啟動並靜音",
"startWithVideoMuted": "啟動並關閉影像",
"terms": "條款",
"version": "APP版本"
"version": "應用程式版本"
},
"share": {
"dialInfoText": "\n\n=====\n\n只想要透過手機撥打加入嗎\n\n撥打{{defaultDialInNumber}}或點此連結來查看此會議的電話撥入號碼\n{{dialInfoPageUrl}}",
@@ -1146,7 +1166,7 @@
"Settings": "切換設定",
"audioOnly": "切換僅音訊",
"audioRoute": "選擇音訊裝置",
"boo": "倒彩",
"boo": "倒彩",
"breakoutRoom": "進入/離開分組討論室",
"callQuality": "管理視訊品質",
"carmode": "行車模式",
@@ -1160,7 +1180,7 @@
"document": "切換檔案分享",
"documentClose": "關閉檔案分享",
"documentOpen": "打開檔案分享",
"download": "下載我們的APP",
"download": "下載我們的應用程式",
"embedMeeting": "嵌入會議",
"endConference": "結束會議(所有人)",
"enterFullScreen": "進入全螢幕",
@@ -1183,7 +1203,7 @@
"like": "比讚",
"linkToSalesforce": "連結至 Salesforce",
"lobbyButton": "啟用/停用大廳模式",
"localRecording": "切換本錄製控制",
"localRecording": "切換本錄製控制",
"lockRoom": "切換會議密碼",
"lowerHand": "放下手",
"moreActions": "更多動作",
@@ -1199,7 +1219,7 @@
"openChat": "打開聊天",
"participants": "打開與會者窗格",
"pip": "切換子母螢幕模式",
"privateMessage": "送私人訊息",
"privateMessage": "送私人訊息",
"profile": "編輯您的個人檔案",
"raiseHand": "舉手",
"reactions": "反應",
@@ -1237,7 +1257,7 @@
"audioRoute": "選擇音訊裝置",
"audioSettings": "音訊設定",
"authenticate": "驗證",
"boo": "倒彩",
"boo": "倒彩",
"callQuality": "管理影像品質",
"chat": "開啟/關閉聊天欄",
"clap": "鼓掌",
@@ -1248,7 +1268,7 @@
"disableReactionSounds": "您可以停用此會議的反應音效",
"documentClose": "關閉分享檔案欄",
"documentOpen": "開啟分享檔案欄",
"download": "下載我們的APP",
"download": "下載我們的應用程式",
"e2ee": "端對端加密",
"embedMeeting": "嵌入會議",
"enableNoiseSuppression": "開啟雜訊抑制",
@@ -1273,7 +1293,7 @@
"lobbyButtonEnable": "啟用大廳模式",
"login": "登入",
"logout": "登出",
"lowerYourHand": "放下的手",
"lowerYourHand": "放下的手",
"moreActions": "更多動作",
"moreOptions": "更多選項",
"mute": "靜音/解除靜音",
@@ -1294,9 +1314,9 @@
"pip": "進入子母螢幕模式",
"privateMessage": "傳送私人訊息",
"profile": "編輯您的個人資料",
"raiseHand": "舉起/放下的手",
"raiseHand": "舉起/放下的手",
"raiseYourHand": "舉手",
"reactionBoo": "傳送倒彩反應",
"reactionBoo": "傳送倒彩反應",
"reactionClap": "傳送鼓掌反應",
"reactionLaugh": "傳送大笑反應",
"reactionLike": "傳送比讚反應",
@@ -1336,7 +1356,7 @@
"labelToolTip": "此會議正在轉錄",
"off": "轉錄已停用",
"pending": "準備轉錄會議...",
"sourceLanguageDesc": "會議語言目前設定為<b>{{sourceLanguage}}</b><br/>您可以在這裡",
"sourceLanguageDesc": "會議語言目前設定為 <b>{{sourceLanguage}}</b><br/> 您可以在這裡",
"sourceLanguageHere": "修改",
"start": "開始顯示字幕",
"stop": "停用顯示字幕",
@@ -1360,12 +1380,12 @@
"videoSIPGW": {
"busy": "我們正在釋放資源,請過幾分鐘後再試。",
"busyTitle": "會議室服務目前忙碌中",
"errorAlreadyInvited": "{{displayName}}已經受邀",
"errorAlreadyInvited": "{{displayName}} 已經受邀",
"errorInvite": "會議尚未開始,請稍候再試。",
"errorInviteFailed": "我們正在努力解決這個問題,請稍後再試。",
"errorInviteFailedTitle": "邀請{{displayName}}失敗",
"errorInviteFailedTitle": "邀請 {{displayName}} 失敗",
"errorInviteTitle": "會議室邀請錯誤",
"pending": "已向{{displayName}}送邀請"
"pending": "已向 {{displayName}}送邀請"
},
"videoStatus": {
"adjustFor": "調整為:",
@@ -1415,7 +1435,7 @@
},
"virtualBackground": {
"accessibilityLabel": {
"currentBackground": "前背景:{{background}}",
"currentBackground": "前背景:{{background}}",
"selectBackground": "選擇背景"
},
"addBackground": "新增背景",
@@ -1438,8 +1458,8 @@
"slightBlur": "輕微模糊",
"title": "虛擬背景",
"uploadedImage": "已上傳的圖片 {{index}}",
"webAssemblyWarning": "不支援WebAssembly",
"webAssemblyWarningDescription": "WebAssembly已停用或不受此瀏覽器支援"
"webAssemblyWarning": "不支援 WebAssembly",
"webAssemblyWarningDescription": "WebAssembly 已停用或不受此瀏覽器支援"
},
"visitors": {
"chatIndicator": "(訪客)",
@@ -1456,14 +1476,14 @@
"roomname": "輸入會議室名稱"
},
"addMeetingName": "新增會議名稱",
"appDescription": "來吧,和您的整個團隊進行視訊會議。不,邀請所有您認識的人進行視訊會議。{{app}}是一套完全加密、100% 開放始碼的視訊會議解決方案。無需註冊帳號,無時無刻不分日夜均可免費使用。",
"appDescription": "來吧,和您的整個團隊進行視訊會議。不,邀請所有您認識的人進行視訊會議。{{app}} 是一套完全加密、100% 開放始碼的視訊會議解決方案。無需註冊帳號,無時無刻不分日夜均可免費使用。",
"audioVideoSwitch": {
"audio": "音訊",
"video": "視訊"
},
"calendar": "行事曆",
"connectCalendarButton": "連接您的行事曆",
"connectCalendarText": "連接您的行事曆來查看在{{app}}中的會議。此外,增加{{provider}}的會議至自己的行事曆,只要點按一下即可啟動。",
"connectCalendarText": "連接您的行事曆來查看在 {{app}} 中的會議。此外,增加 {{provider}} 的會議至自己的行事曆,只要點按一下即可啟動。",
"enterRoomTitle": "啟動新的會議",
"getHelp": "取得協助",
"go": "開始",
@@ -1482,25 +1502,25 @@
"policyLogo": "政策圖示"
},
"meetingsAccessibilityLabel": "會議",
"mobileDownLoadLinkAndroid": "下載 Android 版本的手機應用程式",
"mobileDownLoadLinkFDroid": "前往 F-Droid 下載 Android 版本的手機應用程式",
"mobileDownLoadLinkIos": "下載 iOS 版本的手機應用程式",
"mobileDownLoadLinkAndroid": "下載 Android 版本的應用程式",
"mobileDownLoadLinkFDroid": "前往 F-Droid 下載 Android 版本的應用程式",
"mobileDownLoadLinkIos": "下載 iOS 版本的應用程式",
"moderatedMessage": "或以主持人身份<a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">預先建立會議</a>。",
"privacy": "隱私權",
"recentList": "最近使用",
"recentList": "近期",
"recentListDelete": "刪除",
"recentListEmpty": "目前最近使用是空白的,與您的團隊成員聊天,即在此處找到最近使用過的會議。",
"recentMeetings": "您的最近會議",
"recentListEmpty": "您的近期列表目前是空白的,與您的團隊成員聊天,即在此處找到最近參與過的會議。",
"recentMeetings": "您近期的會議",
"reducedUIText": "歡迎使用{{app}}",
"roomNameAllowedChars": "會議室名稱不應包含以下字元:? & : ' \" % #",
"roomNameAllowedChars": "會議室名稱不應包含以下字元:?、&、:、'、\"、%、#。",
"roomname": "輸入會議室名稱",
"roomnameHint": "請輸入您想加入的會議室名稱或網址,您可以用一個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室。",
"roomnameHint": "請輸入您想加入的會議室名稱或網址,您可以用一個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室。",
"sendFeedback": "傳送回饋",
"settings": "設定",
"startMeeting": "開始會議",
"terms": "條款",
"title": "安全、功能齊全、完全免費的視訊會議",
"upcomingMeetings": "您即將的會議"
"upcomingMeetings": "您即將開始的會議"
},
"whiteboard": {
"accessibilityLabel": {

View File

@@ -67,13 +67,18 @@
"renameBreakoutRoom": "Rename breakout room",
"sendToBreakoutRoom": "Send participant to:"
},
"breakoutList": "Breakout list",
"buttonLabel": "Breakout rooms",
"defaultName": "Breakout room #{{index}}",
"hideParticipantList": "Hide participant list",
"mainRoom": "Main room",
"notifications": {
"joined": "Joining the \"{{name}}\" breakout room",
"joinedMainRoom": "Joining the main room",
"joinedTitle": "Breakout Rooms"
}
},
"showParticipantList": "Show participant list",
"title": "Breakout Rooms"
},
"calendarSync": {
"addMeetingURL": "Add a meeting link",
@@ -256,6 +261,7 @@
"Share": "Share",
"Submit": "Submit",
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
"WaitingForHostButton": "Wait for moderator",
"WaitingForHostTitle": "Waiting for a moderator...",
"Yes": "Yes",
"accessibilityLabel": {
@@ -412,6 +418,7 @@
"sendPrivateMessageTitle": "Send privately?",
"serviceUnavailable": "Service unavailable",
"sessTerminated": "Call terminated",
"sessTerminatedReason": "The meeting has been terminated",
"sessionRestarted": "Call restarted because of a connection issue.",
"shareAudio": "Continue",
"shareAudioTitle": "How to share audio",
@@ -475,6 +482,10 @@
"viewUpgradeOptions": "View upgrade options",
"viewUpgradeOptionsContent": "To get unlimited access to premium features like recording, transcriptions, RTMP Streaming & more, you'll need to upgrade your plan.",
"viewUpgradeOptionsTitle": "You discovered a premium feature!",
"whiteboardLimitContent": "Sorry, the limit of conccurent whiteboard users has been reached.",
"whiteboardLimitReference": "For more information please visit",
"whiteboardLimitReferenceUrl": "our website",
"whiteboardLimitTitle": "Whiteboard usage restricted",
"yourEntireScreen": "Your entire screen"
},
"documentSharing": {
@@ -657,13 +668,13 @@
"knockingParticipantList": "Knocking participant list",
"lobbyChatStartedNotification": "{{moderator}} started a lobby chat with {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} has started a lobby chat with you.",
"lobbyClosed": "The lobby room has been closed.",
"nameField": "Enter your name",
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
"notificationLobbyDisabled": "Lobby has been disabled by {{originParticipantName}}",
"notificationLobbyEnabled": "Lobby has been enabled by {{originParticipantName}}",
"notificationTitle": "Lobby",
"passwordField": "Enter meeting password",
"passwordJoinButton": "Join",
"reject": "Reject",
"rejectAll": "Reject all",
@@ -722,6 +733,7 @@
"dataChannelClosed": "Video quality impaired",
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
"disabledIframeSecondary": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes. Please use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> for production embedding!",
"disconnected": "disconnected",
"displayNotifications": "Display notifications for",
"dontRemindMe": "Do not remind me",
@@ -797,7 +809,9 @@
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"waitingParticipants": "{{waitingParticipants}} people"
"waitingParticipants": "{{waitingParticipants}} people",
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
"whiteboardLimitTitle": "Whiteboard usage"
},
"participantsPane": {
"actions": {
@@ -806,6 +820,7 @@
"askUnmute": "Ask to unmute",
"audioModeration": "Unmute themselves",
"blockEveryoneMicCamera": "Block everyone's mic and camera",
"breakoutRooms": "Breakout rooms",
"invite": "Invite Someone",
"moreModerationActions": "More moderation options",
"moreModerationControls": "More moderation controls",
@@ -1152,7 +1167,7 @@
"audioOnly": "Toggle audio only",
"audioRoute": "Select the sound device",
"boo": "Boo",
"breakoutRoom": "Join/leave breakout room",
"breakoutRooms": "Breakout rooms",
"callQuality": "Manage video quality",
"carmode": "Car Mode",
"cc": "Toggle subtitles",

View File

@@ -0,0 +1,10 @@
Jitsi Meet lets you stay in touch with all your teams, be they family, friends, or colleagues. Instant video conferences, efficiently adapting to your scale.
* Unlimited users: There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
* No account needed.
* Lock-protected rooms: Control the access to your conferences with a password.
* Encrypted by default.
* High quality: Audio and video are delivered with the clarity and richness of Opus and VP8.
* Web browser ready: No downloads are required of your friends to join the conversation. Jitsi Meet works directly within their browsers as well. Simply share your conference URL with others to get started.
* 100% open source: Powered by awesome communities from all over the world. And your friends at 8x8.
* Invite by pretty URLs: You can meet at the easy to remember https://MySite.com/OurConf of your choice instead of joining the hard to remember rooms with seemingly random sequences of numbers and letters in their names.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

View File

@@ -0,0 +1 @@
Secure, Simple and Scalable Video Conferences with state-of-the-art video quality

View File

@@ -993,7 +993,13 @@ function initCommands() {
callback(isP2pActive(APP.store.getState()));
break;
}
case '_new_electron_screensharing_supported': {
callback(true);
break;
}
default:
callback({ error: new Error('UnknownRequestError') });
return false;
}
@@ -1101,7 +1107,11 @@ class API {
this._enabled = true;
initCommands();
this.notifyBrowserSupport(isSupportedBrowser());
// Let the embedder know we are ready.
this._sendEvent({ name: 'ready' });
}
/**
@@ -1141,7 +1151,11 @@ class API {
*/
_sendEvent(event = {}) {
if (this._enabled) {
transport.sendEvent(event);
try {
transport.sendEvent(event);
} catch (error) {
logger.error('Failed to send and IFrame API event', error);
}
}
}
@@ -1275,6 +1289,19 @@ class API {
});
}
/**
* Notify request desktop sources.
*
* @param {Object} options - Object with the options for desktop sources.
* @returns {void}
*/
requestDesktopSources(options) {
return transport.sendRequest({
name: '_request-desktop-sources',
options
});
}
/**
* Notify external application that the video quality setting has changed.
*
@@ -1475,11 +1502,43 @@ class API {
* @param {Array<string>} args - Array of strings composing the log message.
* @returns {void}
*/
notifyLog(logLevel, args) {
notifyLog(logLevel, args = []) {
if (!Array.isArray(args)) {
logger.error('notifyLog received wrong argument types!');
return;
}
// Trying to convert arguments to strings. Otherwise in order to send the event the arguments will be formatted
// with JSON.stringify which can throw an error because of circular objects and we will lose the whole log.
const formattedArguments = [];
args.forEach(arg => {
let formattedArgument = '';
if (arg instanceof Error) {
formattedArgument += `${arg.toString()}: ${arg.stack}`;
} else if (typeof arg === 'object') {
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for strigify is part of the
// public interface and I think it could be useful in some cases I will it for now.
try {
formattedArgument += JSON.stringify(arg);
} catch (error) {
formattedArgument += arg;
}
} else {
formattedArgument += arg;
}
formattedArguments.push(formattedArgument);
});
this._sendEvent({
name: 'log',
logLevel,
args
args: formattedArguments
});
}

View File

@@ -145,6 +145,7 @@ const events = {
'prejoin-screen-loaded': 'prejoinScreenLoaded',
'proxy-connection-event': 'proxyConnectionEvent',
'raise-hand-updated': 'raiseHandUpdated',
'ready': 'ready',
'recording-link-available': 'recordingLinkAvailable',
'recording-status-changed': 'recordingStatusChanged',
'participant-menu-button-clicked': 'participantMenuButtonClick',
@@ -162,6 +163,10 @@ const events = {
'whiteboard-status-changed': 'whiteboardStatusChanged'
};
const requests = {
'_request-desktop-sources': '_requestDesktopSources'
};
/**
* Last id of api object.
*
@@ -269,10 +274,10 @@ function parseArguments(args) {
function parseSizeParam(value) {
let parsedValue;
// This regex parses values of the form 100px, 100em, 100pt or 100%.
// This regex parses values of the form 100px, 100em, 100pt, 100vh, 100vw or 100%.
// Values like 100 or 100px are handled outside of the regex, and
// invalid values will be ignored and the minimum will be used.
const re = /([0-9]*\.?[0-9]+)(em|pt|px|%)$/;
const re = /([0-9]*\.?[0-9]+)(em|pt|px|((d|l|s)?v)(h|w)|%)$/;
if (typeof value === 'string' && String(value).match(re) !== null) {
parsedValue = value;
@@ -305,6 +310,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* configuration options defined in config.js to be overridden.
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
* configuration options defined in interface_config.js to be overridden.
* @param {IIceServers} [options.iceServers] - Object with rules that will be used to modify/remove the existing
* ice server configuration.
* NOTE: This property is currently experimental and may be removed in the future!
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.lang] - The meeting's default language.
@@ -334,6 +342,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
lang = undefined,
onload = undefined,
invitees,
iceServers,
devices,
userInfo,
e2eeKey,
@@ -345,6 +354,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._parentNode = parentNode;
this._url = generateURL(domain, {
configOverwrite,
iceServers,
interfaceConfigOverwrite,
jwt,
lang,
@@ -356,7 +366,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
},
release
});
this._createIFrame(height, width, onload, sandbox);
this._createIFrame(height, width, sandbox);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -366,9 +378,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
})
});
if (Array.isArray(invitees) && invitees.length > 0) {
this.invite(invitees);
}
this._onload = onload;
this._tmpE2EEKey = e2eeKey;
this._isLargeVideoVisible = false;
this._isPrejoinVideoVisible = false;
@@ -387,18 +402,25 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* parseSizeParam for format details.
* @param {number|string} width - The with of the iframe. Check
* parseSizeParam for format details.
* @param {Function} onload - The function that will listen
* for onload event.
* @param {string} sandbox - Sandbox directive for the created iframe, if desired.
* @returns {void}
*
* @private
*/
_createIFrame(height, width, onload, sandbox) {
_createIFrame(height, width, sandbox) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
this._frame.allow = 'camera; microphone; display-capture; autoplay; clipboard-write; hid; screen-wake-lock';
this._frame.allow = [
'autoplay',
'camera',
'clipboard-write',
'compute-pressure',
'display-capture',
'hid',
'microphone',
'screen-wake-lock'
].join('; ');
this._frame.name = frameName;
this._frame.id = frameName;
this._setSize(height, width);
@@ -409,11 +431,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._frame.sandbox = sandbox;
}
if (onload) {
// waits for iframe resources to load
// and fires event when it is done
this._frame.onload = onload;
}
this._frame.src = this._url;
this._frame = this._parentNode.appendChild(this._frame);
@@ -562,6 +579,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const userID = data.id;
switch (name) {
case 'ready': {
// Fake the iframe onload event because it's not reliable.
this._onload?.();
break;
}
case 'video-conference-joined': {
if (typeof this._tmpE2EEKey !== 'undefined') {
@@ -673,6 +696,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return false;
});
this._transport.on('request', (request, callback) => {
const requestName = requests[request.name];
const data = {
...request,
name: requestName
};
if (requestName) {
this.emit(requestName, data, callback);
}
});
}
/**
@@ -1191,6 +1226,24 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return this._numberOfParticipants;
}
/**
* Returns array of commands supported by executeCommand().
*
* @returns {Array<string>} Array of commands.
*/
getSupportedCommands() {
return Object.keys(commands);
}
/**
* Returns array of events supported by addEventListener().
*
* @returns {Array<string>} Array of events.
*/
getSupportedEvents() {
return Object.values(events);
}
/**
* Check if the video is available.
*
@@ -1226,6 +1279,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
});
}
/**
* Returns the state of availability electron share screen via external api.
*
* @returns {Promise}
*/
_isNewElectronScreensharingSupported() {
return this._transport.sendRequest({
name: '_new_electron_screensharing_supported'
});
}
/**
* Pins a participant's video on to the stage view.
*

View File

@@ -203,16 +203,6 @@ UI.toggleFilmstrip = function() {
APP.store.dispatch(setFilmstripVisible(!visible));
};
/**
* Sets muted audio state for participant
*/
UI.setAudioMuted = function(id) {
// FIXME: Maybe this can be removed!
if (APP.conference.isLocalId(id)) {
APP.conference.updateAudioIconEnabled();
}
};
/**
* Sets muted video state for participant
*/

View File

@@ -1,6 +1,7 @@
/* global APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -23,6 +24,8 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
const FADE_DURATION_MS = 300;
const logger = Logger.getLogger(__filename);
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
@@ -489,7 +492,9 @@ export class VideoContainer extends LargeContainer {
}
if (this.video) {
stream.attach(this.video);
stream.attach(this.video).catch(error => {
logger.error(`Attaching the remote track ${stream} has failed with `, error);
});
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.

View File

@@ -23,11 +23,8 @@ const VideoLayout = {
/**
* Handler for local flip X changed event.
*/
onLocalFlipXChanged() {
onLocalFlipXChanged(localFlipX) {
if (largeVideo) {
const { store } = APP;
const { localFlipX } = store.getState()['features/base/settings'];
largeVideo.onLocalFlipXChange(localFlipX);
}
},

View File

@@ -5,6 +5,7 @@ import {
notifyMicError
} from '../../react/features/base/devices/actions.web';
import {
flattenAvailableDevices,
getAudioOutputDeviceId
} from '../../react/features/base/devices/functions.web';
import { updateSettings } from '../../react/features/base/settings/actions';
@@ -186,7 +187,7 @@ export default {
* @returns {boolean}
*/
newDeviceListAddedLabelsOnly(oldDevices, newDevices) {
const oldDevicesFlattend = oldDevices.audioInput.concat(oldDevices.audioOutput).concat(oldDevices.videoInput);
const oldDevicesFlattend = flattenAvailableDevices(oldDevices);
if (oldDevicesFlattend.length !== newDevices.length) {
return false;

3989
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,20 +22,20 @@
"@giphy/js-fetch-api": "4.7.1",
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.1.2",
"@jitsi/logger": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.15/jitsi-excalidraw-0.0.15.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@mui/styles": "5.12.0",
"@react-native-async-storage/async-storage": "1.17.3",
"@react-native-async-storage/async-storage": "1.19.3",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/netinfo": "7.1.7",
"@react-native-community/slider": "4.1.12",
"@react-native-google-signin/google-signin": "9.0.2",
"@react-native-community/netinfo": "9.4.1",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.0.1",
"@react-navigation/bottom-tabs": "6.5.8",
"@react-navigation/elements": "1.3.18",
"@react-navigation/material-top-tabs": "6.6.3",
@@ -48,7 +48,7 @@
"@vladmandic/human-models": "2.5.9",
"@xmldom/xmldom": "0.8.7",
"amplitude-js": "8.2.1",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"bc-css-flags": "3.0.0",
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
@@ -65,7 +65,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1678.0.0+77e6803f/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1719.0.0+f8a18cf0/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -79,34 +79,34 @@
"react-focus-on": "3.8.1",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "0.69.11",
"react-native": "0.69.12",
"react-native-background-timer": "2.4.1",
"react-native-calendar-events": "2.2.0",
"react-native-callstats": "3.73.7",
"react-native-callstats": "3.73.22",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "8.4.8",
"react-native-device-info": "10.9.0",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-gesture-handler": "2.9.0",
"react-native-get-random-values": "1.7.2",
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-pager-view": "5.4.9",
"react-native-paper": "5.1.2",
"react-native-performance": "2.1.0",
"react-native-safe-area-context": "4.6.4",
"react-native-screens": "3.22.0",
"react-native-sound": "0.11.1",
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "12.4.3",
"react-native-svg-transformer": "1.0.0",
"react-native-tab-view": "3.1.1",
"react-native-url-polyfill": "1.3.0",
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
"react-native-watch-connectivity": "1.0.11",
"react-native-webrtc": "111.0.3",
"react-native-webview": "11.15.1",
"react-native-youtube-iframe": "2.2.1",
"react-native-svg": "13.13.0",
"react-native-svg-transformer": "1.1.0",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.6",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"react-textarea-autosize": "8.3.0",
"react-window": "1.8.6",
@@ -115,6 +115,7 @@
"redux-thunk": "2.4.1",
"resemblejs": "4.0.0",
"seamless-scroll-polyfill": "2.1.8",
"semver": "7.5.4",
"tss-react": "4.4.4",
"util": "0.12.1",
"uuid": "8.3.2",
@@ -139,7 +140,7 @@
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14",
"@types/react-linkify": "1.0.1",
"@types/react-native": "0.69.20",
"@types/react-native": "0.69.22",
"@types/react-native-keep-awake": "2.0.3",
"@types/react-native-video": "5.0.14",
"@types/react-redux": "7.1.24",
@@ -156,7 +157,7 @@
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
"clean-css-cli": "4.3.0",
"css-loader": "3.6.0",
"css-loader": "6.8.1",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "37.0.3",

View File

@@ -73,6 +73,13 @@ cd ios && pod install && cd ..
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
```
- In `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
```
This will take care of the screen share feature.
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
@@ -87,3 +94,6 @@ https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
serverURL={'https://meet.jit.si/'}
token={'dkhalhfajhflahlfaahalhfahfsl'} />
```
For more details on how you can use React Native SDK with React Native app, you can follow this link:
https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-react-native-sdk

View File

@@ -0,0 +1,44 @@
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
Context activityContext = context.getCurrentActivity();
JitsiMeetOngoingConferenceService.launch(context, activityContext);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetOngoingConferenceService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
public static void launch(Context context, Context activityContext) {
RNOngoingNotification.createOngoingConferenceNotificationChannel(activityContext);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(RNOngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -21,6 +21,7 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),

View File

@@ -0,0 +1,97 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel(Context activityContext) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (activityContext == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) activityContext.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel("JitsiOngoingConferenceChannel", activityContext.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,8 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "2.0.5",
"@jitsi/logger": "2.0.0",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
"@react-navigation/elements": "1.3.18",
@@ -25,11 +24,11 @@
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-xhr-backend": "3.0.0",
"js-md5": "0.6.1",
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -39,12 +38,12 @@
"react-emoji-render": "1.2.4",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native-callstats": "3.73.7",
"react-native-callstats": "3.73.22",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-svg-transformer": "1.0.0",
"react-native-tab-view": "3.1.1",
"react-native-url-polyfill": "1.3.0",
"react-native-youtube-iframe": "2.2.1",
"react-native-svg-transformer": "1.1.0",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"redux": "4.0.4",
"redux-thunk": "2.4.1",
@@ -56,34 +55,34 @@
"peerDependencies": {
"@amplitude/react-native": "2.7.0",
"@giphy/react-native-sdk": "2.3.0",
"@react-native-async-storage/async-storage": "1.18.2",
"@react-native-async-storage/async-storage": "1.19.3",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/netinfo": "7.1.7",
"@react-native-community/slider": "4.1.12",
"@react-native-google-signin/google-signin": "7.0.4",
"@react-native-community/netinfo": "9.4.1",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.0.1",
"react-native": "*",
"react": "*",
"react-native-background-timer": "2.4.1",
"react-native-calendar-events": "2.2.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "8.4.8",
"react-native-get-random-values": "1.7.2",
"react-native-device-info": "10.9.0",
"react-native-get-random-values": "1.9.0",
"react-native-gesture-handler": "2.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-pager-view": "5.4.9",
"react-native-paper": "4.11.1",
"react-native-performance": "2.1.0",
"react-native-orientation-locker": "https://git@github.com/wonday/react-native-orientation-locker#f483520ea6b64b97002374a9e9f053a5299a062a",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.22.0",
"react-native-sound": "0.11.1",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "12.4.3",
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
"react-native-watch-connectivity": "1.0.11",
"react-native-webrtc": "111.0.1",
"react-native-webview": "11.15.1"
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webview": "13.5.1"
},
"overrides": {
"@xmldom/xmldom": "0.8.7"

View File

@@ -6,7 +6,9 @@ const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
const androidMainSourcePath = '../android/sdk/src/main/res';
const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
const androidMainTargetPath = './android/src/main/res';
const iosSrcPath = '../ios/sdk/src';
const iosDestPath = './ios/src';
@@ -58,11 +60,25 @@ function copyFolderRecursiveSync(source, target) {
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
*/
function mergeDependencyVersions() {
// Updates SDK dependencies to match project dependencies.
for (const key in SDKPackageJSON.dependencies) {
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
}
}
// Updates SDK peer dependencies.
for (const key in packageJSON.dependencies) {
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
// Updates all peer dependencies except react and react-native.
if (key !== 'react' && key !== 'react-native') {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);
@@ -155,6 +171,30 @@ copyFolderRecursiveSync(
`${androidSourcePath}/log`,
`${androidTargetPath}/log`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/values`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-hdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-mdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidSourcePath}/net`,
`${androidTargetPath}/log`

View File

@@ -1,8 +1,10 @@
/* eslint-disable guard-for-in */
/* eslint-disable guard-for-in, no-continue */
/* global __dirname */
const fs = require('fs');
const path = require('path');
const semver = require('semver');
const pathToPackageJSON = path.resolve(__dirname, '../../../package.json');
@@ -10,6 +12,7 @@ const packageJSON = require(pathToPackageJSON);
const RNSDKpackageJSON = require(path.resolve(__dirname, './package.json'));
/**
* Updates dependencies from the app package.json with the peer dependencies of the RNSDK package.json.
*/
@@ -21,6 +24,28 @@ function updateDependencies() {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
}
if (!semver.valid(packageJSON.dependencies[key])) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(`
We changed ${key} version number from ${packageJSON.dependencies[key]} to ${RNSDKpackageJSON.peerDependencies[key]}`
);
continue;
}
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `=${packageJSON.dependencies[key]}`)) {
continue;
}
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `>${packageJSON.dependencies[key]}`)) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(`${key} is now set to ${RNSDKpackageJSON.peerDependencies[key]}`);
}
}
packageJSON.overrides = packageJSON.overrides || {};
@@ -33,6 +58,8 @@ function updateDependencies() {
}
if (!updated) {
console.log('All your dependencies are up to date!');
return;
}

View File

@@ -4,6 +4,7 @@ import React, { Component } from 'react';
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import StatelessAvatar from '../base/avatar/components/web/StatelessAvatar';
import { getAvatarColor, getInitials } from '../base/avatar/functions';
import { DEFAULT_ICON } from '../base/icons/svg/constants';
import Toolbar from './Toolbar';
@@ -184,6 +185,7 @@ export default class AlwaysOnTop extends Component<any, IState> {
<div id = 'avatarContainer'>
<StatelessAvatar
color = { getAvatarColor(displayName, customAvatarBackgrounds) }
iconUser = { DEFAULT_ICON.IconUser }
id = 'avatar'
initials = { getInitials(displayName) }
url = { avatarURL } />)

View File

@@ -2,7 +2,8 @@ import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconMic, IconMicSlash } from '../base/icons/svg';
import { DEFAULT_ICON } from '../base/icons/svg/constants';
import { IProps } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
@@ -31,8 +32,8 @@ type Props = Partial<IProps>;
* Stateless "mute/unmute audio" button for the Always-on-Top windows.
*/
export default class AudioMuteButton extends Component<Props, IState> {
icon = IconMic;
toggledIcon = IconMicSlash;
icon = DEFAULT_ICON.IconMic;
toggledIcon = DEFAULT_ICON.IconMicSlash;
accessibilityLabel = 'Audio mute';
/**

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconHangup } from '../base/icons/svg';
import { DEFAULT_ICON } from '../base/icons/svg/constants';
import { IProps } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
@@ -17,7 +17,7 @@ type Props = Partial<IProps>;
export default class HangupButton extends Component<Props> {
accessibilityLabel = 'Hangup';
icon = IconHangup;
icon = DEFAULT_ICON.IconHangup;
/**
* Initializes a new {@code HangupButton} instance.

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconVideo, IconVideoOff } from '../base/icons/svg';
import { DEFAULT_ICON } from '../base/icons/svg/constants';
import { IProps } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
@@ -32,8 +32,8 @@ type State = {
*/
export default class VideoMuteButton extends Component<Props, State> {
icon = IconVideo;
toggledIcon = IconVideoOff;
icon = DEFAULT_ICON.IconVideo;
toggledIcon = DEFAULT_ICON.IconVideoOff;
accessibilityLabel = 'Video mute';
/**

View File

@@ -934,3 +934,27 @@ export function createGifSentEvent() {
action: 'gif.sent'
};
}
/**
* Creates an event which indicates the whiteboard was opened.
*
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createOpenWhiteboardEvent() {
return {
action: 'whiteboard.open'
};
}
/**
* Creates an event which indicates the whiteboard limit was enforced.
*
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createRestrictWhiteboardEvent() {
return {
action: 'whiteboard.restrict'
};
}

View File

@@ -82,6 +82,7 @@ export async function createHandlers({ getState }: IStore) {
} = config;
const {
amplitudeAPPKey,
amplitudeIncludeUTM,
blackListedEvents,
scriptURLs,
googleAnalyticsTrackingId,
@@ -92,6 +93,7 @@ export async function createHandlers({ getState }: IStore) {
const { group, user } = state['features/base/jwt'];
const handlerConstructorOptions = {
amplitudeAPPKey,
amplitudeIncludeUTM,
blackListedEvents,
envType: deploymentInfo?.envType || 'dev',
googleAnalyticsTrackingId,
@@ -222,7 +224,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>) {
// Set the handlers last, since this triggers emptying of the cache
analytics.setAnalyticsHandlers(handlers);
if (!isMobileBrowser() && browser.isChrome()) {
if (!isMobileBrowser() && browser.isChromiumBased()) {
const bannerCfg = state['features/base/config'].chromeExtensionBanner;
checkChromeExtensionsInstalled(bannerCfg).then(extensionsInstalled => {

View File

@@ -11,6 +11,7 @@ export interface IEvent {
interface IOptions {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
envType?: string;
googleAnalyticsTrackingId?: string;

View File

@@ -15,13 +15,18 @@ export default class AmplitudeHandler extends AbstractHandler {
* Creates new instance of the Amplitude analytics handler.
*
* @param {Object} options -
* @param {string} options.amplitudeAPPKey - The Amplitude app key required
* by the Amplitude API.
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API.
* @param {boolean} options.amplitudeIncludeUTM - Whether to include UTM parameters
* in the Amplitude events.
*/
constructor(options: any) {
super(options);
const { amplitudeAPPKey, user } = options;
const {
amplitudeAPPKey,
amplitudeIncludeUTM: includeUtm = true,
user
} = options;
this._enabled = true;
@@ -43,6 +48,8 @@ export default class AmplitudeHandler extends AbstractHandler {
} else {
const amplitudeOptions: any = {
includeReferrer: true,
includeUtm,
saveParamsReferrerOncePerSession: false,
onError
};

View File

@@ -1,6 +1,9 @@
import { Amplitude } from '@amplitude/react-native';
import DefaultPreference from 'react-native-default-preference';
import DeviceInfo from 'react-native-device-info';
import { getUniqueId } from 'react-native-device-info';
import logger from '../../logger';
/**
* Custom logic for setting the correct device id.
@@ -14,11 +17,17 @@ export async function fixDeviceID(amplitude: Amplitude) {
const current = await DefaultPreference.get('amplitudeDeviceId');
if (current) {
amplitude.setDeviceId(current);
await amplitude.setDeviceId(current);
} else {
const uid = DeviceInfo.getUniqueId();
const uid = await getUniqueId();
amplitude.setDeviceId(uid);
DefaultPreference.set('amplitudeDeviceId', uid);
if (!uid) {
logger.warn('Device ID is not set!');
return;
}
await amplitude.setDeviceId(uid as string);
await DefaultPreference.set('amplitudeDeviceId', uid as string);
}
}

View File

@@ -1,6 +1,14 @@
import { getLocationContextRoot } from '../base/util/uri';
// @ts-ignore
// eslint-disable-next-line
import { openTokenAuthUrl } from '../authentication/actions';
// @ts-ignore
import { getTokenAuthUrl, isTokenAuthEnabled } from '../authentication/functions';
import { getJwtExpirationDate } from '../base/jwt/functions';
import { getLocationContextRoot, parseURIString } from '../base/util/uri';
import { addTrackStateToURL } from './functions.any';
import logger from './logger';
import { IStore } from './types';
/**
@@ -83,4 +91,53 @@ export function reloadWithStoredParams() {
};
}
/**
* Checks whether tokenAuthUrl is set, we have a jwt token that will expire soon
* and redirect to the auth url to obtain new token if this is the case.
*
* @param {Dispatch} dispatch - The Redux dispatch function.
* @param {Function} getState - The Redux state.
* @param {Function} failureCallback - The callback on failure to obtain auth url.
* @returns {boolean} Whether we will redirect or not.
*/
export function maybeRedirectToTokenAuthUrl(
dispatch: IStore['dispatch'], getState: IStore['getState'], failureCallback: Function) {
const state = getState();
const config = state['features/base/config'];
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
if (!isTokenAuthEnabled(config)) {
return false;
}
// if tokenAuthUrl check jwt if is about to expire go through the url to get new token
const jwt = state['features/base/jwt'].jwt;
const expirationDate = getJwtExpirationDate(jwt);
// if there is jwt and its expiration time is less than 3 minutes away
// let's obtain new token
if (expirationDate && expirationDate.getTime() - Date.now() < 3 * 60 * 1000) {
const room = state['features/base/conference'].room;
const { tenant } = parseURIString(locationURL.href) || {};
getTokenAuthUrl(config, room, tenant, true, locationURL)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
return Promise.reject();
}
return dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
})
.catch(() => {
failureCallback();
});
return true;
}
return false;
}

View File

@@ -1,4 +1,5 @@
import { setRoom } from '../base/conference/actions';
import { getConferenceState } from '../base/conference/functions';
import {
configWillLoad,
loadConfigError,
@@ -10,6 +11,7 @@ import {
restoreConfig
} from '../base/config/functions.native';
import { connect, disconnect, setLocationURL } from '../base/connection/actions.native';
import { JITSI_CONNECTION_URL_KEY } from '../base/connection/constants';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks/actions.native';
import isInsecureRoomName from '../base/util/isInsecureRoomName';
@@ -29,6 +31,7 @@ import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions';
import { maybeRedirectToTokenAuthUrl } from './actions.any';
import { addTrackStateToURL, getDefaultURL } from './functions.native';
import logger from './logger';
import { IReloadNowOptions, IStore } from './types';
@@ -72,11 +75,25 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
}
location.protocol || (location.protocol = 'https:');
const { contextRoot, host, room } = location;
const { contextRoot, host, hostname, pathname, room } = location;
const locationURL = new URL(location.toString());
const { conference } = getConferenceState(getState());
if (room) {
navigateRoot(screen.connecting);
if (conference) {
// We need to check if the location is the same with the previous one.
const currentLocationURL = conference?.getConnection()[JITSI_CONNECTION_URL_KEY];
const { hostname: currentHostName, pathname: currentPathName } = currentLocationURL;
if (currentHostName === hostname && currentPathName === pathname) {
logger.warn(`Joining same conference using URL: ${currentLocationURL}`);
return;
}
} else {
navigateRoot(screen.connecting);
}
}
dispatch(disconnect());
@@ -188,10 +205,18 @@ export function reloadNow() {
// @ts-ignore
const newURL = addTrackStateToURL(locationURL, state);
logger.info(`Reloading the conference using URL: ${locationURL}`);
const reloadAction = () => {
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(appNavigate(toURLString(newURL), {
hidePrejoin: true
}));
dispatch(appNavigate(toURLString(newURL), {
hidePrejoin: true
}));
};
if (maybeRedirectToTokenAuthUrl(dispatch, getState, reloadAction)) {
return;
}
reloadAction();
};
}

View File

@@ -15,6 +15,7 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { isWelcomePageEnabled } from '../welcome/functions';
import {
maybeRedirectToTokenAuthUrl,
redirectToStaticPage,
redirectWithStoredParams,
reloadWithStoredParams
@@ -170,8 +171,16 @@ export function reloadNow() {
const state = getState();
const { locationURL } = state['features/base/connection'];
logger.info(`Reloading the conference using URL: ${locationURL}`);
const reloadAction = () => {
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(reloadWithStoredParams());
dispatch(reloadWithStoredParams());
};
if (maybeRedirectToTokenAuthUrl(dispatch, getState, reloadAction)) {
return;
}
reloadAction();
};
}

View File

@@ -7,8 +7,7 @@ import SplashScreen from 'react-native-splash-screen';
import BottomSheetContainer from '../../base/dialog/components/native/BottomSheetContainer';
import DialogContainer from '../../base/dialog/components/native/DialogContainer';
import { updateFlags } from '../../base/flags/actions';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions';
import { CALL_INTEGRATION_ENABLED } from '../../base/flags/constants';
import { clientResized, setSafeAreaInsets } from '../../base/responsive-ui/actions';
import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDetector.native';
import { updateSettings } from '../../base/settings/actions';
@@ -112,13 +111,21 @@ export class App extends AbstractApp<IProps> {
async _extraInit() {
const { dispatch, getState } = this.state.store ?? {};
const { flags = {} } = this.props;
let callIntegrationEnabled = flags[CALL_INTEGRATION_ENABLED as keyof typeof flags];
// CallKit does not work on the simulator, make sure we disable it.
if (Platform.OS === 'ios' && DeviceInfo.isEmulatorSync()) {
flags['call-integration.enabled'] = false;
flags[CALL_INTEGRATION_ENABLED] = false;
callIntegrationEnabled = false;
logger.info('Disabling CallKit because this is a simulator');
}
// Disable Android ConnectionService by default.
if (Platform.OS === 'android' && typeof callIntegrationEnabled === 'undefined') {
flags[CALL_INTEGRATION_ENABLED] = false;
callIntegrationEnabled = false;
}
// We set these early enough so then we avoid any unnecessary re-renders.
dispatch?.(updateFlags(flags));
@@ -145,26 +152,19 @@ export class App extends AbstractApp<IProps> {
await rootNavigationReady;
// Check if serverURL is configured externally and not allowed to change.
const serverURLChangeEnabled = getState && getFeatureFlag(getState(), SERVER_URL_CHANGE_ENABLED, true);
// Update specified server URL.
if (typeof this.props.url !== 'undefined') {
// @ts-ignore
const { serverURL } = this.props.url;
if (!serverURLChangeEnabled) {
// As serverURL is provided externally, so we push it to settings.
if (typeof this.props.url !== 'undefined') {
// @ts-ignore
const { serverURL } = this.props.url;
if (typeof serverURL !== 'undefined') {
dispatch?.(updateSettings({ serverURL }));
}
if (typeof serverURL !== 'undefined') {
dispatch?.(updateSettings({ serverURL }));
}
}
dispatch?.(updateSettings(this.props.userInfo || {}));
// Update settings with feature-flag.
const callIntegrationEnabled = flags[CALL_INTEGRATION_ENABLED as keyof typeof flags];
if (typeof callIntegrationEnabled !== 'undefined') {
dispatch?.(updateSettings({ disableCallIntegration: !callIntegrationEnabled }));
}

View File

@@ -1,12 +1,13 @@
// @ts-expect-error
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
import { getTokenAuthUrl } from '../authentication/functions';
import { getTokenAuthUrl } from '../authentication/functions.web';
import { IStateful } from '../base/app/types';
import { isRoomValid } from '../base/conference/functions';
import { isSupportedBrowser } from '../base/environment/environment';
import { browser } from '../base/lib-jitsi-meet';
import { toState } from '../base/redux/functions';
import { parseURIString } from '../base/util/uri';
import Conference from '../conference/components/web/Conference';
import { getDeepLinkingPage } from '../deep-linking/functions';
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
@@ -52,9 +53,16 @@ function _getWebConferenceRoute(state: IReduxState) {
if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
&& state['features/authentication'].tokenAuthUrlSuccessful
&& !state['features/base/jwt'].jwt && room) {
route.href = getTokenAuthUrl(config, room);
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
return Promise.resolve(route);
return getTokenAuthUrl(config, room, tenant, false, locationURL)
.then((url: string | undefined) => {
route.href = url;
return route;
})
.catch(() => Promise.resolve(route));
}
// Update the location if it doesn't match. This happens when a room is

View File

@@ -27,6 +27,7 @@ import '../connection-indicator/middleware';
import '../deep-linking/middleware';
import '../device-selection/middleware';
import '../display-name/middleware';
import '../dynamic-branding/middleware';
import '../etherpad/middleware';
import '../filmstrip/middleware';
import '../follow-me/middleware';

View File

@@ -1,11 +1,10 @@
import { Linking, Platform } from 'react-native';
import { Linking } from 'react-native';
import { appNavigate } from '../app/actions.native';
import { IStore } from '../app/types';
import { conferenceLeft } from '../base/conference/actions';
import { connectionFailed } from '../base/connection/actions.native';
import { set } from '../base/redux/functions';
import { appendURLHashParam } from '../base/util/uri';
import { CANCEL_LOGIN } from './actionTypes';
import { stopWaitForOwner } from './actions.any';
@@ -85,12 +84,7 @@ export function redirectToDefaultLocation() {
* @returns {Function}
*/
export function openTokenAuthUrl(tokenAuthServiceUrl: string) {
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
// Append ios=true or android=true to the token URL.
url = appendURLHashParam(url, Platform.OS, 'true');
return () => {
Linking.openURL(url);
Linking.openURL(tokenAuthServiceUrl);
};
}

View File

@@ -2,7 +2,6 @@ import { maybeRedirectToWelcomePage } from '../app/actions.web';
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { browser } from '../base/lib-jitsi-meet';
import { appendURLHashParam } from '../base/util/uri';
import { CANCEL_LOGIN } from './actionTypes';
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
@@ -57,14 +56,10 @@ export function redirectToDefaultLocation() {
export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const redirect = () => {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
if (browser.isElectron()) {
url = appendURLHashParam(url, 'electron', 'true');
window.open(url, '_blank');
window.open(tokenAuthServiceUrl, '_blank');
} else {
window.location.href = url;
window.location.href = tokenAuthServiceUrl;
}
};

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { IStore } from '../../../app/types';
import { IReduxState, IStore } from '../../../app/types';
import ConfirmDialog from '../../../base/dialog/components/native/ConfirmDialog';
import { translate } from '../../../base/i18n/functions';
import { cancelWaitForOwner, login } from '../../actions.native';
@@ -12,6 +12,16 @@ import { cancelWaitForOwner, login } from '../../actions.native';
*/
interface IProps {
/**
* Whether to show alternative cancel button text.
*/
_alternativeCancelText?: boolean;
/**
* Is confirm button hidden?
*/
_isConfirmHidden?: boolean;
/**
* Redux store dispatch function.
*/
@@ -51,11 +61,14 @@ class WaitForOwnerDialog extends Component<IProps> {
* @returns {ReactElement}
*/
render() {
const { _isConfirmHidden } = this.props;
return (
<ConfirmDialog
cancelLabel = 'dialog.Cancel'
cancelLabel = { this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }
confirmLabel = 'dialog.IamHost'
descriptionKey = 'dialog.WaitForHostMsg'
isConfirmHidden = { _isConfirmHidden }
onCancel = { this._onCancel }
onSubmit = { this._onLogin } />
);
@@ -79,9 +92,25 @@ class WaitForOwnerDialog extends Component<IProps> {
*/
_onLogin() {
this.props.dispatch(login());
return true;
}
}
export default translate(connect()(WaitForOwnerDialog));
/**
* Maps (parts of) the redux state to the associated
* {@code WaitForOwnerDialog}'s props.
*
* @param {Object} state - The redux state.
* @private
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
const { locationURL } = state['features/base/connection'];
return {
_alternativeCancelText: membersOnly && lobbyWaitingForHost,
_isConfirmHidden: locationURL?.hostname?.includes('8x8.vc')
};
}
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));

View File

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IStore } from '../../../app/types';
import { IReduxState, IStore } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import Dialog from '../../../base/ui/components/web/Dialog';
import { cancelWaitForOwner, login } from '../../actions.web';
@@ -12,6 +12,11 @@ import { cancelWaitForOwner, login } from '../../actions.web';
*/
interface IProps extends WithTranslation {
/**
* Whether to show alternative cancel button text.
*/
_alternativeCancelText?: boolean;
/**
* Redux store dispatch method.
*/
@@ -71,6 +76,8 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
return (
<Dialog
cancel = {{ translationKey:
this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }}
disableBackdropClose = { true }
hideCloseButton = { true }
ok = {{ translationKey: 'dialog.IamHost' }}
@@ -85,4 +92,20 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
}
}
export default translate(connect()(WaitForOwnerDialog));
/**
* Maps (parts of) the redux state to the associated
* {@code WaitForOwnerDialog}'s props.
*
* @param {Object} state - The redux state.
* @private
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
return {
_alternativeCancelText: membersOnly && lobbyWaitingForHost
};
}
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));

View File

@@ -0,0 +1,50 @@
import { IConfig } from '../base/config/configType';
import { getBackendSafeRoomName } from '../base/util/uri';
/**
* Checks if the token for authentication is available.
*
* @param {Object} config - Configuration state object from store.
* @returns {boolean}
*/
export const isTokenAuthEnabled = (config: IConfig): boolean =>
typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0;
/**
* Returns the state that we can add as a parameter to the tokenAuthUrl.
*
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {boolean} skipPrejoin - Whether to skip pre-join page.
* @param {URL} locationURL - The location URL.
* @returns {Object} The state object.
*/
export const _getTokenAuthState = (
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
locationURL: URL): object => {
const state = {
room: roomName,
roomSafe: getBackendSafeRoomName(roomName),
tenant
};
if (skipPrejoin) {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
// @ts-ignore
state['config.prejoinConfig.enabled'] = false;
}
const params = new URLSearchParams(locationURL.hash);
for (const [ key, value ] of params) {
// we allow only config and interfaceConfig overrides in the state
if (key.startsWith('config.') || key.startsWith('interfaceConfig.')) {
// @ts-ignore
state[key] = value;
}
}
return state;
};

View File

@@ -0,0 +1,51 @@
import { Platform } from 'react-native';
import { IConfig } from '../base/config/configType';
import { _getTokenAuthState } from './functions.any';
export * from './functions.any';
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The location URL.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (
config: IConfig,
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
let url = config.tokenAuthUrl;
if (!url || !roomName) {
return Promise.resolve(undefined);
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
// Append ios=true or android=true to the token URL.
// @ts-ignore
state[Platform.OS] = true;
url = url.replace('{state}', encodeURIComponent(JSON.stringify(state)));
}
return Promise.resolve(url.replace('{room}', roomName));
};

View File

@@ -1,39 +0,0 @@
import { IConfig } from '../base/config/configType';
/**
* Checks if the token for authentication is available.
*
* @param {Object} config - Configuration state object from store.
* @returns {boolean}
*/
export const isTokenAuthEnabled = (config: IConfig) =>
typeof config.tokenAuthUrl === 'string'
&& config.tokenAuthUrl.length;
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
* '{roleUpgrade}' - will contain 'true' if the URL will be used for
* the role upgrade scenario, where user connects from anonymous domain and
* then gets upgraded to the moderator by logging-in from the popup window.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
*
* @returns {string|undefined} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (config: IConfig, roomName: string | undefined) => {
const url = config.tokenAuthUrl;
if (typeof url !== 'string' || !roomName) {
return undefined;
}
return url.replace('{room}', roomName);
};

View File

@@ -0,0 +1,95 @@
import base64js from 'base64-js';
import { IConfig } from '../base/config/configType';
import { browser } from '../base/lib-jitsi-meet';
import { _getTokenAuthState } from './functions.any';
export * from './functions.any';
/**
* Based on rfc7636 we need a random string for a code verifier.
*/
const POSSIBLE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
/**
* Crypto random, alternative of Math.random.
*
* @returns {float} A random value.
*/
function _cryptoRandom() {
const typedArray = new Uint8Array(1);
const randomValue = crypto.getRandomValues(typedArray)[0];
return randomValue / Math.pow(2, 8);
}
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The current location URL.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (
config: IConfig,
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
let url = config.tokenAuthUrl;
if (!url || !roomName) {
return Promise.resolve(undefined);
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
if (browser.isElectron()) {
// @ts-ignore
state.electron = true;
}
url = url.replace('{state}', encodeURIComponent(JSON.stringify(state)));
}
url = url.replace('{room}', roomName);
if (url.indexOf('{code_challenge}')) {
let codeVerifier = '';
// random string
for (let i = 0; i < 64; i++) {
codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length));
}
window.sessionStorage.setItem('code_verifier', codeVerifier);
return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
.then(digest => {
// prepare code challenge - base64 encoding without padding as described in:
// https://datatracker.ietf.org/doc/html/rfc7636#appendix-A
const codeChallenge = base64js.fromByteArray(new Uint8Array(digest))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return url ? url.replace('{code_challenge}', codeChallenge) : undefined;
});
}
return Promise.resolve(url);
};

View File

@@ -14,7 +14,7 @@ import {
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { getBackendSafeRoomName } from '../base/util/uri';
import { parseURIString } from '../base/util/uri';
import { openLogoutDialog } from '../settings/actions';
import {
@@ -254,7 +254,9 @@ function _isWaitingForOwner({ getState }: IStore) {
function _handleLogin({ dispatch, getState }: IStore) {
const state = getState();
const config = state['features/base/config'];
const room = getBackendSafeRoomName(state['features/base/conference'].room);
const room = state['features/base/conference'].room;
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
if (!room) {
logger.warn('Cannot handle login, room is undefined!');
@@ -268,16 +270,16 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
// FIXME: This method will not preserve the other URL params that were originally passed.
const tokenAuthServiceUrl = getTokenAuthUrl(config, room);
getTokenAuthUrl(config, room, tenant, true, locationURL)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
return;
}
return;
}
dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
return dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
});
}
/**

View File

@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { IconUser } from '../../icons/svg';
import { getParticipantById } from '../../participants/functions';
import { IParticipant } from '../../participants/types';
import { getAvatarColor, getInitials, isCORSAvatarURL } from '../functions';
@@ -182,6 +183,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
const avatarProps: AbstractProps & {
className?: string;
iconUser?: any;
id?: string;
status?: string;
testId?: string;
@@ -226,6 +228,10 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
avatarProps.initials = initials;
}
if (navigator.product !== 'ReactNative') {
avatarProps.iconUser = IconUser;
}
return (
<StatelessAvatar
{ ...avatarProps } />

View File

@@ -2,7 +2,6 @@ import React, { useCallback } from 'react';
import { makeStyles } from 'tss-react/mui';
import Icon from '../../../icons/components/Icon';
import { IconUser } from '../../../icons/svg';
import { withPixelLineHeight } from '../../../styles/functions.web';
import { isIcon } from '../../functions';
import { IAvatarProps } from '../../types';
@@ -122,6 +121,7 @@ const useStyles = makeStyles()(theme => {
const StatelessAvatar = ({
className,
color,
iconUser,
id,
initials,
onAvatarLoadError,
@@ -212,7 +212,7 @@ const StatelessAvatar = ({
style = { _getAvatarStyle() }>
<Icon
size = { '50%' }
src = { IconUser } />
src = { iconUser } />
</div>
);
};

View File

@@ -5,6 +5,11 @@ export interface IAvatarProps {
*/
color?: string;
/**
* The user icon(browser only).
*/
iconUser?: any;
/**
* Initials to be used to render the initials based avatars.
*/

View File

@@ -2,9 +2,11 @@ import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEve
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
import { endpointMessageReceived } from '../../subtitles/actions.any';
import { setIAmVisitor } from '../../visitors/actions';
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
import { getReplaceParticipant } from '../config/functions';
import { hangup } from '../connection/actions';
import { connect, disconnect, hangup } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
@@ -73,6 +75,7 @@ import {
getConferenceOptions,
getConferenceState,
getCurrentConference,
getVisitorOptions,
sendLocalParticipant
} from './functions';
import logger from './logger';
@@ -983,3 +986,41 @@ export function setAssumedBandwidthBps(assumedBandwidthBps: number) {
assumedBandwidthBps
};
}
/**
* Redirects to a new visitor node.
*
* @param {string | undefined} vnode - The vnode to use or undefined if moving back to the main room.
* @param {string} focusJid - The focus jid to use.
* @param {string} username - The username to use.
* @returns {void}
*/
export function redirect(vnode: string, focusJid: string, username: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const { conference, joining } = getState()['features/base/conference'];
const newConfig = getVisitorOptions(getState, vnode, focusJid, username);
if (!newConfig) {
logger.warn('Not redirected missing params');
return;
}
dispatch(overwriteConfig(newConfig)) // @ts-ignore
.then(() => dispatch(conferenceWillLeave(conference || joining)))
.then(() => dispatch(disconnect()))
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
// we do not clear local tracks on error, so we need to manually clear them
.then(() => dispatch(destroyLocalTracks()))
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
APP.conference.startConference([]);
}
});
};
}

View File

@@ -28,9 +28,10 @@ export const EMAIL_COMMAND = 'email';
*/
export const JITSI_CONFERENCE_URL_KEY = Symbol('url');
export const TRIGGER_READY_TO_CLOSE_REASONS = [
'The meeting has been terminated'
];
export const TRIGGER_READY_TO_CLOSE_REASONS = {
'dialog.sessTerminatedReason': 'The meeting has been terminated',
'lobby.lobbyClosed': 'Lobby room closed.'
};
/**
* Conference leave reasons.
@@ -39,8 +40,3 @@ export const CONFERENCE_LEAVE_REASONS = {
SWITCH_ROOM: 'switch_room',
UNRECOVERABLE_ERROR: 'unrecoverable_error'
};
/**
* Timeout for properly leaving the conference if it was destroyed.
*/
export const CONFERENCE_DESTROYED_LEAVE_TIMEOUT = 10000;

View File

@@ -284,12 +284,12 @@ export function restoreConferenceOptions(stateful: IStateful) {
* Override the global config (that is, window.config) with XMPP configuration required to join as a visitor.
*
* @param {IStateful} stateful - The redux store state.
* @param {Array<string>} params - The received parameters.
* @param {string|undefined} vnode - The received parameters.
* @param {string} focusJid - The received parameters.
* @param {string|undefined} username - The received parameters.
* @returns {Object}
*/
export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
const [ vnode, focusJid, username ] = params;
export function getVisitorOptions(stateful: IStateful, vnode: string, focusJid: string, username: string) {
const config = toState(stateful)['features/base/config'];
if (!config?.hosts) {

View File

@@ -14,12 +14,11 @@ import { reloadNow } from '../../app/actions';
import { IStore } from '../../app/types';
import { removeLobbyChatParticipant } from '../../chat/actions.any';
import { openDisplayNamePrompt } from '../../display-name/actions';
import { readyToClose } from '../../mobile/external-api/actions';
import { showErrorNotification, showWarningNotification } from '../../notifications/actions';
import { showErrorNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { hasDisplayName } from '../../prejoin/utils';
import { stopLocalVideoRecording } from '../../recording/actions.any';
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager';
import { setIAmVisitor } from '../../visitors/actions';
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
@@ -35,7 +34,6 @@ import {
} from '../participants/functions';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
import { destroyLocalTracks } from '../tracks/actions.any';
import { getLocalTracks } from '../tracks/functions.any';
import {
@@ -52,24 +50,17 @@ import {
import {
authStatusChanged,
conferenceFailed,
conferenceWillInit,
conferenceWillLeave,
createConference,
leaveConference,
setLocalSubject,
setSubject
} from './actions';
import {
CONFERENCE_DESTROYED_LEAVE_TIMEOUT,
CONFERENCE_LEAVE_REASONS,
TRIGGER_READY_TO_CLOSE_REASONS
} from './constants';
import { CONFERENCE_LEAVE_REASONS } from './constants';
import {
_addLocalTracksToConference,
_removeLocalTracksFromConference,
forEachConference,
getCurrentConference,
getVisitorOptions,
restoreConferenceOptions
} from './functions';
import logger from './logger';
@@ -153,25 +144,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
// Handle specific failure reasons.
switch (error.name) {
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
const [ reason ] = error.params;
dispatch(showWarningNotification({
description: reason,
titleKey: 'dialog.sessTerminated'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
if (TRIGGER_READY_TO_CLOSE_REASONS.includes(reason)) {
if (typeof APP === 'undefined') {
dispatch(readyToClose());
} else {
APP.API.notifyReadyToClose();
}
setTimeout(() => dispatch(leaveConference()), CONFERENCE_DESTROYED_LEAVE_TIMEOUT);
}
break;
}
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
if (enableForcedReload) {
dispatch(showErrorNotification({
@@ -227,34 +199,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
sendAnalytics(createOfferAnswerFailedEvent());
break;
case JitsiConferenceErrors.REDIRECTED: {
const newConfig = getVisitorOptions(getState, error.params);
if (!newConfig) {
logger.warn('Not redirected missing params');
break;
}
const [ vnode ] = error.params;
dispatch(overwriteConfig(newConfig)) // @ts-ignore
.then(() => dispatch(conferenceWillLeave(conference)))
.then(() => dispatch(disconnect()))
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
// we do not clear local tracks on error, so we need to manually clear them
.then(() => dispatch(destroyLocalTracks()))
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
APP.conference.startConference([]);
}
});
break;
}
}
!error.recoverable
@@ -333,7 +277,9 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
if (requireDisplayName
&& !getLocalParticipant(getState)?.name
&& !conference.isHidden()) {
dispatch(openDisplayNamePrompt(undefined));
dispatch(openDisplayNamePrompt({
validateInput: hasDisplayName
}));
}
return result;

View File

@@ -1 +1,36 @@
import { appNavigate } from '../../app/actions.native';
import { notifyConferenceFailed } from '../../conference/actions.native';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { CONFERENCE_FAILED } from './actionTypes';
import { conferenceLeft } from './actions';
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
import './middleware.any';
MiddlewareRegistry.register(store => next => action => {
const { dispatch } = store;
const { error } = action;
switch (action.type) {
case CONFERENCE_FAILED: {
if (error?.name !== JitsiConferenceErrors.CONFERENCE_DESTROYED) {
break;
}
const [ reason ] = error.params;
const reasonKey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
];
dispatch(notifyConferenceFailed(reasonKey, () => {
dispatch(conferenceLeft(action.conference));
dispatch(appNavigate(undefined));
}));
}
}
return next(action);
});

View File

@@ -1,7 +1,10 @@
import i18next from 'i18next';
import {
setPrejoinPageVisibility,
setSkipPrejoinOnReload
} from '../../prejoin/actions.web';
import { hangup } from '../connection/actions.web';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
@@ -12,7 +15,9 @@ import {
CONFERENCE_LEFT,
KICKED_OUT
} from './actionTypes';
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
import logger from './logger';
import './middleware.any';
let screenLock: WakeLockSentinel | undefined;
@@ -108,6 +113,15 @@ MiddlewareRegistry.register(store => next => action => {
dispatch(setSkipPrejoinOnReload(true));
}
if (errorName === JitsiConferenceErrors.CONFERENCE_DESTROYED) {
const [ reason ] = action.error.params;
const titlekey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
];
dispatch(hangup(true, i18next.t(titlekey) || reason));
}
releaseScreenLock();
break;

View File

@@ -59,6 +59,7 @@ export interface IJitsiConference {
enableLobby: Function;
end: Function;
getBreakoutRooms: Function;
getConnection: Function;
getLocalParticipantProperty: Function;
getLocalTracks: Function;
getMeetingUniqueId: Function;
@@ -137,6 +138,7 @@ export interface IConferenceState {
followMeEnabled?: boolean;
joining?: IJitsiConference;
leaving?: IJitsiConference;
lobbyWaitingForHost?: boolean;
localSubject?: string;
locked?: string;
membersOnly?: IJitsiConference;
@@ -155,11 +157,17 @@ export interface IConferenceState {
export interface IJitsiConferenceRoom {
locked: boolean;
moderator: {
logout: Function;
};
myroomjid: string;
roomjid: string;
xmpp: {
moderator: {
logout: Function;
};
};
}
interface IConferenceFailedError extends Error {
params: Array<any>;
}
/**
@@ -274,7 +282,7 @@ function _authStatusChanged(state: IConferenceState,
* reduction of the specified action.
*/
function _conferenceFailed(state: IConferenceState, { conference, error }: {
conference: IJitsiConference; error: Error; }) {
conference: IJitsiConference; error: IConferenceFailedError; }) {
// The current (similar to getCurrentConference in
// base/conference/functions.any.js) conference which is joining or joined:
const conference_ = state.conference || state.joining;
@@ -286,6 +294,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
let authRequired;
let membersOnly;
let passwordRequired;
let lobbyWaitingForHost;
switch (error.name) {
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED:
@@ -293,9 +302,16 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
break;
case JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED:
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR:
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR: {
membersOnly = conference;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [ _lobbyJid, _lobbyWaitingForHost ] = error.params;
lobbyWaitingForHost = _lobbyWaitingForHost;
break;
}
case JitsiConferenceErrors.PASSWORD_REQUIRED:
passwordRequired = conference;
@@ -309,6 +325,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
error,
joining: undefined,
leaving: undefined,
lobbyWaitingForHost,
/**
* The indicator of how the conference/room is locked. If falsy, the
@@ -365,6 +382,8 @@ function _conferenceJoined(state: IConferenceState, { conference }: { conference
membersOnly: undefined,
leaving: undefined,
lobbyWaitingForHost: undefined,
/**
* The indicator which determines whether the conference is locked.
*

View File

@@ -157,6 +157,13 @@ export interface INoiseSuppressionConfig {
};
}
export interface IWhiteboardConfig {
collabServerBaseUrl?: string;
enabled?: boolean;
limitUrl?: string;
userLimit?: number;
}
export interface IWatchRTCConfiguration {
allowBrowserLogCollection?: boolean;
collectionInterval?: number;
@@ -182,6 +189,7 @@ export interface IConfig {
_screenshotHistoryRegionUrl?: number;
analytics?: {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
disabled?: boolean;
googleAnalyticsTrackingId?: string;
@@ -245,6 +253,7 @@ export interface IConfig {
callStatsID?: string;
callStatsSecret?: string;
callUUID?: string;
cameraFacingMode?: string;
channelLastN?: number;
chromeExtensionBanner?: {
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;
@@ -401,6 +410,7 @@ export interface IConfig {
disableResizable?: boolean;
disableStageFilmstrip?: boolean;
disableTopPanel?: boolean;
disabled?: boolean;
minParticipantCountForTopPanel?: number;
};
firefox_fake_device?: string;
@@ -511,6 +521,7 @@ export interface IConfig {
pcStatsInterval?: number;
peopleSearchQueryTypes?: string[];
peopleSearchUrl?: string;
preferBosh?: boolean;
preferredTranscribeLanguage?: string;
prejoinConfig?: {
enabled?: boolean;
@@ -628,8 +639,5 @@ export interface IConfig {
customUrl?: string;
disabled?: boolean;
};
whiteboard?: {
collabServerBaseUrl?: string;
enabled?: boolean;
};
whiteboard?: IWhiteboardConfig;
}

View File

@@ -74,6 +74,7 @@ export default [
*/
'callUUID',
'cameraFacingMode',
'conferenceInfo',
'channelLastN',
'connectionIndicators',
@@ -158,7 +159,6 @@ export default [
'filmstrip',
'firefox_fake_device',
'flags',
'forceJVB121Ratio',
'forceTurnRelay',
'gatherStats',
'giphy',
@@ -197,6 +197,7 @@ export default [
'participantMenuButtonsWithNotifyClick',
'participantsPane',
'pcStatsInterval',
'preferBosh',
'prejoinConfig',
'prejoinPageEnabled',
'recordingService',

View File

@@ -1,9 +1,11 @@
import _ from 'lodash';
import { IReduxState, IStore } from '../../app/types';
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
import { getCurrentConference } from '../conference/functions';
import { IConfigState } from '../config/reducer';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import { parseURLParams } from '../util/parseURLParams';
import {
appendURLParam,
getBackendSafeRoomName
@@ -18,53 +20,15 @@ import {
} from './actionTypes';
import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger';
import { ConnectionFailedError, IIceServers } from './types';
/**
* The error structure passed to the {@link connectionFailed} action.
*
* Note there was an intention to make the error resemble an Error instance (to
* the extent that jitsi-meet needs it).
* The options that will be passed to the JitsiConnection instance.
*/
export type ConnectionFailedError = {
/**
* The invalid credentials that were used to authenticate and the
* authentication failed.
*/
credentials?: {
/**
* The XMPP user's ID.
*/
jid: string;
/**
* The XMPP user's password.
*/
password: string;
};
/**
* The details about the connection failed event.
*/
details?: Object;
/**
* Error message.
*/
message?: string;
/**
* One of {@link JitsiConnectionError} constants (defined in
* lib-jitsi-meet).
*/
name: string;
/**
* Indicates whether this event is recoverable or not.
*/
recoverable?: boolean;
};
interface IOptions extends IConfigState {
iceServersOverride?: IIceServers;
preferVisitor?: boolean;
}
/**
* Create an action for when the signaling connection has been lost.
@@ -147,9 +111,17 @@ export function connectionFailed(
export function constructOptions(state: IReduxState) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options = _.cloneDeep(state['features/base/config']);
const options: IOptions = _.cloneDeep(state['features/base/config']);
const { bosh } = options;
const { locationURL, preferVisitor } = state['features/base/connection'];
const params = parseURLParams(locationURL || '');
const iceServersOverride = params['iceServers.replace'];
if (iceServersOverride) {
options.iceServersOverride = iceServersOverride;
}
const { bosh, preferBosh } = options;
let { websocket } = options;
// TESTING: Only enable WebSocket for some percentage of users.
@@ -159,6 +131,10 @@ export function constructOptions(state: IReduxState) {
}
}
if (preferBosh) {
websocket = undefined;
}
// WebSocket is preferred over BOSH.
const serviceUrl = websocket || bosh;
@@ -180,6 +156,10 @@ export function constructOptions(state: IReduxState) {
}
}
if (preferVisitor) {
options.preferVisitor = true;
}
return options;
}
@@ -230,6 +210,9 @@ export function _connectInternal(id?: string, password?: string) {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
_onConnectionFailed);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_REDIRECTED,
_onConnectionRedirected);
/**
* Unsubscribe the connection instance from
@@ -298,9 +281,28 @@ export function _connectInternal(id?: string, password?: string) {
resolve(connection);
}
/**
* Rejects external promise when connection fails.
*
* @param {string|undefined} vnode - The vnode to connect to.
* @param {string} focusJid - The focus jid to use.
* @param {string|undefined} username - The username to use when joining. This is after promotion from
* visitor to main participant.
* @private
* @returns {void}
*/
function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
dispatch(redirect(vnode, focusJid, username));
}
// in case of configured http url for conference request we need the room name
const name = getBackendSafeRoomName(state['features/base/conference'].room);
connection.connect({
id,
password
password,
name
});
});
};

View File

@@ -58,9 +58,10 @@ export function connect(id?: string, password?: string) {
*
* @param {boolean} [requestFeedback] - Whether to attempt showing a
* request for call feedback.
* @param {string} [feedbackTitle] - The feedback title.
* @returns {Function}
*/
export function hangup(requestFeedback = false) {
export function hangup(requestFeedback = false, feedbackTitle?: string) {
// XXX For web based version we use conference hanging up logic from the old app.
return async (dispatch: IStore['dispatch']) => {
if (LocalRecordingManager.isRecordingLocally()) {
@@ -76,6 +77,6 @@ export function hangup(requestFeedback = false) {
});
}
return APP.conference.hangup(requestFeedback);
return APP.conference.hangup(requestFeedback, feedbackTitle);
};
}

View File

@@ -1,4 +1,5 @@
import { SET_ROOM } from '../conference/actionTypes';
import { SET_JWT } from '../jwt/actionTypes';
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
import ReducerRegistry from '../redux/ReducerRegistry';
import { assign, set } from '../redux/functions';
@@ -11,7 +12,7 @@ import {
SET_LOCATION_URL,
SHOW_CONNECTION_INFO
} from './actionTypes';
import { ConnectionFailedError } from './actions.any';
import { ConnectionFailedError } from './types';
export interface IConnectionState {
connecting?: any;
@@ -26,6 +27,7 @@ export interface IConnectionState {
error?: ConnectionFailedError;
locationURL?: URL;
passwordRequired?: Object;
preferVisitor?: boolean;
showConnectionInfo?: boolean;
timeEstablished?: number;
}
@@ -49,6 +51,9 @@ ReducerRegistry.register<IConnectionState>(
case CONNECTION_WILL_CONNECT:
return _connectionWillConnect(state, action);
case SET_JWT:
return _setJWT(state, action);
case SET_LOCATION_URL:
return _setLocationURL(state, action);
@@ -84,6 +89,7 @@ function _connectionDisconnected(
return assign(state, {
connecting: undefined,
connection: undefined,
preferVisitor: undefined,
timeEstablished: undefined
});
}
@@ -141,7 +147,8 @@ function _connectionFailed(
error,
passwordRequired:
error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
? connection : undefined
? connection : undefined,
preferVisitor: undefined
});
}
@@ -184,6 +191,22 @@ function _getCurrentConnection(baseConnectionState: IConnectionState): IConnecti
return baseConnectionState.connection || baseConnectionState.connecting;
}
/**
* Reduces a specific redux action {@link SET_JWT} of the feature
* base/connection.
*
* @param {IConnectionState} state - The redux state of the feature base/connection.
* @param {Action} action - The Redux action SET_JWT to reduce.
* @private
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _setJWT(state: IConnectionState, { preferVisitor }: { preferVisitor: boolean; }) {
return assign(state, {
preferVisitor
});
}
/**
* Reduces a specific redux action {@link SET_LOCATION_URL} of the feature
* base/connection.

View File

@@ -0,0 +1,113 @@
/**
* The error structure passed to the {@link connectionFailed} action.
*
* Note there was an intention to make the error resemble an Error instance (to
* the extent that jitsi-meet needs it).
*/
export type ConnectionFailedError = {
/**
* The invalid credentials that were used to authenticate and the
* authentication failed.
*/
credentials?: {
/**
* The XMPP user's ID.
*/
jid: string;
/**
* The XMPP user's password.
*/
password: string;
};
/**
* The details about the connection failed event.
*/
details?: Object;
/**
* Error message.
*/
message?: string;
/**
* One of {@link JitsiConnectionError} constants (defined in
* lib-jitsi-meet).
*/
name: string;
/**
* Indicates whether this event is recoverable or not.
*/
recoverable?: boolean;
};
/**
* The value for the username or credential property.
*/
type ReplaceIceServersField = string | null;
/**
* The value for the urls property.
*/
type IceServerUrls = null | string | Array<string>;
/**
* The types of ice servers.
*/
enum IceServerType {
STUN = 'stun',
TURN = 'turn',
TURNS = 'turns'
}
/**
* Represents a single override rule.
*/
interface IReplaceIceServer {
/**
* The value the credential prop will be replaced with.
*
* NOTE: If the value is null we will remove the credential property in entry that matches the target type. If the
* value is undefined or missing we won't change the credential property in the entry that matches the target type.
*/
credential?: ReplaceIceServersField;
/**
* Target type that will be used to match the already received ice server and modify/remove it based on the values
* of credential, urls and username.
*/
targetType: IceServerType;
/**
* The value the urls prop will be replaced with.
*
* NOTE: If the value is null we will remove the whole entry that matches the target type. If the value is undefined
* or missing we won't change the urls property in the entry that matches the target type.
*/
urls?: IceServerUrls;
/**
* The value the username prop will be replaced with.
*
* NOTE: If the value is null we will remove the username property in entry that matches the target type. If the
* value is undefined or missing we won't change the username property in the entry that matches the target type.
*/
username?: ReplaceIceServersField;
}
/**
* An object with rules for changing the existing ice server configuration.
*/
export interface IIceServers {
/**
* An array of rules for replacing parts from the existing ice server configuration.
*/
replace: Array<IReplaceIceServer>;
}

View File

@@ -16,9 +16,13 @@ import {
} from './actionTypes';
import {
areDeviceLabelsInitialized,
areDevicesDifferent,
filterIgnoredDevices,
flattenAvailableDevices,
getDeviceIdByLabel,
getDeviceLabelById,
getDevicesFromURL,
logDevices,
setAudioOutputDeviceId
} from './functions';
import logger from './logger';
@@ -137,15 +141,21 @@ export function configureInitialDevices() {
* @returns {Function}
*/
export function getAvailableDevices() {
return (dispatch: IStore['dispatch']) => new Promise(resolve => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => new Promise(resolve => {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
mediaDevices.enumerateDevices((devices: MediaDeviceInfo[]) => {
dispatch(updateDeviceList(devices));
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = flattenAvailableDevices(getState()['features/base/devices'].availableDevices);
resolve(devices);
if (areDevicesDifferent(oldDevices, filteredDevices)) {
logDevices(ignoredDevices, 'Ignored devices on device list changed:');
dispatch(updateDeviceList(filteredDevices));
}
resolve(filteredDevices);
});
} else {
resolve([]);

View File

@@ -0,0 +1,8 @@
/**
* Prefixes of devices that will be filtered from the device list.
*
* NOTE: Currently we filter only 'Microsoft Teams Audio Device' virtual device. It seems that it can't be set
* as default device on the OS level and this use case is not handled in the code. If we add more device prefixes that
* can be default devices we should make sure to handle the default device use case.
*/
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [ 'Microsoft Teams Audio Device' ];

View File

@@ -5,6 +5,7 @@ import { ISettingsState } from '../settings/reducer';
import { setNewAudioOutputDevice } from '../sounds/functions.web';
import { parseURLParams } from '../util/parseURLParams';
import { DEVICE_LABEL_PREFIXES_TO_IGNORE } from './constants';
import logger from './logger';
import { IDevicesState } from './types';
@@ -176,6 +177,74 @@ export function filterAudioDevices(devices: MediaDeviceInfo[]) {
return devices.filter(device => device.kind === 'audioinput');
}
/**
* Filters the devices that start with one of the prefixes from DEVICE_LABEL_PREFIXES_TO_IGNORE.
*
* @param {MediaDeviceInfo[]} devices - The devices to be filtered.
* @returns {MediaDeviceInfo[]} - The filtered devices.
*/
export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
const ignoredDevices: MediaDeviceInfo[] = [];
const filteredDevices = devices.filter(device => {
if (!device.label) {
return true;
}
if (DEVICE_LABEL_PREFIXES_TO_IGNORE.find(prefix => device.label?.startsWith(prefix))) {
ignoredDevices.push(device);
return false;
}
return true;
});
return {
filteredDevices,
ignoredDevices
};
}
/**
* Check if the passed device arrays are different.
*
* @param {MediaDeviceInfo[]} devices1 - Array with devices to be compared.
* @param {MediaDeviceInfo[]} devices2 - Array with devices to be compared.
* @returns {boolean} - True if the device arrays are different and false otherwise.
*/
export function areDevicesDifferent(devices1: MediaDeviceInfo[] = [], devices2: MediaDeviceInfo[] = []) {
if (devices1.length !== devices2.length) {
return true;
}
for (let i = 0; i < devices1.length; i++) {
const device1 = devices1[i];
const found = devices2.find(({ deviceId, groupId, kind, label }) =>
device1.deviceId === deviceId
&& device1.groupId === groupId
&& device1.kind === kind
&& device1.label === label
);
if (!found) {
return true;
}
}
return false;
}
/**
* Flattens the availableDevices from redux.
*
* @param {IDevicesState.availableDevices} devices - The available devices from redux.
* @returns {MediaDeviceInfo[]} - The flattened array of devices.
*/
export function flattenAvailableDevices(
{ audioInput = [], audioOutput = [], videoInput = [] }: IDevicesState['availableDevices']) {
return audioInput.concat(audioOutput).concat(videoInput);
}
/**
* We want to strip any device details that are not very user friendly, like usb ids put in brackets at the end.
*
@@ -240,6 +309,35 @@ export function getVideoDeviceIds(state: IReduxState) {
return state['features/base/devices'].availableDevices.videoInput?.map(({ deviceId }) => deviceId);
}
/**
* Converts an array of device info objects into string.
*
* @param {MediaDeviceInfo[]} devices - The devices.
* @returns {string}
*/
function devicesToStr(devices?: MediaDeviceInfo[]) {
return devices?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
}
/**
* Logs an array of devices.
*
* @param {MediaDeviceInfo[]} devices - The array of devices.
* @param {string} title - The title that will be printed in the log.
* @returns {void}
*/
export function logDevices(devices: MediaDeviceInfo[], title = '') {
const deviceList = groupDevicesByKind(devices);
const audioInputs = devicesToStr(deviceList.audioInput);
const audioOutputs = devicesToStr(deviceList.audioOutput);
const videoInputs = devicesToStr(deviceList.videoInput);
logger.debug(`${title}:\n`
+ `audioInput:\n${audioInputs}\n`
+ `audioOutput:\n${audioOutputs}\n`
+ `videoInput:\n${videoInputs}`);
}
/**
* Set device id of the audio output device which is currently in use.
* Empty string stands for default device.

View File

@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
import { MEDIA_TYPE } from '../media/constants';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { updateSettings } from '../settings/actions';
import { getLocalTrack } from '../tracks/functions';
import {
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
@@ -31,11 +33,10 @@ import {
import {
areDeviceLabelsInitialized,
formatDeviceLabel,
groupDevicesByKind,
logDevices,
setAudioOutputDeviceId
} from './functions';
import logger from './logger';
import { IDevicesState } from './types';
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
microphone: {
@@ -60,25 +61,6 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
*/
let permissionsListener: Function | undefined;
/**
* Logs the current device list.
*
* @param {Object} deviceList - Whatever is returned by {@link groupDevicesByKind}.
* @returns {string}
*/
function logDeviceList(deviceList: IDevicesState['availableDevices']) {
const devicesToStr = (list?: MediaDeviceInfo[]) =>
list?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
const audioInputs = devicesToStr(deviceList.audioInput);
const audioOutputs = devicesToStr(deviceList.audioOutput);
const videoInputs = devicesToStr(deviceList.videoInput);
logger.debug('Device list updated:\n'
+ `audioInput:\n${audioInputs}\n`
+ `audioOutput:\n${audioOutputs}\n`
+ `videoInput:\n${videoInputs}`);
}
/**
* Implements the middleware of the feature base/devices.
*
@@ -182,15 +164,22 @@ MiddlewareRegistry.register(store => next => action => {
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
}
break;
case SET_VIDEO_INPUT_DEVICE:
case SET_VIDEO_INPUT_DEVICE: {
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
// on mobile devices the video stream has to be stopped before replacing it
if (isMobileBrowser() && localTrack && !localTrack.muted) {
localTrack.jitsiTrack.stopStream();
}
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(replaceVideoTrackById(action.deviceId));
} else {
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
}
break;
}
case UPDATE_DEVICE_LIST:
logDeviceList(groupDevicesByKind(action.devices));
logDevices(action.devices, 'Device list updated');
if (areDeviceLabelsInitialized(store.getState())) {
return _processPendingRequests(store, next, action);
}

View File

@@ -41,6 +41,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
isConfirmDestructive?: Boolean;
/**
* Whether or not the confirm button is hidden.
*/
isConfirmHidden?: Boolean;
/**
* Dialog title.
*/
@@ -60,7 +65,8 @@ class ConfirmDialog extends AbstractDialog<IProps> {
* @static
*/
static defaultProps = {
isConfirmDestructive: false
isConfirmDestructive: false,
isConfirmHidden: false
};
/**
@@ -95,6 +101,7 @@ class ConfirmDialog extends AbstractDialog<IProps> {
children,
confirmLabel,
isConfirmDestructive,
isConfirmHidden,
t,
title
} = this.props;
@@ -118,10 +125,12 @@ class ConfirmDialog extends AbstractDialog<IProps> {
label = { t(cancelLabel || 'dialog.confirmNo') }
onPress = { this._onCancel }
style = { styles.dialogButton } />
<Dialog.Button
label = { t(confirmLabel || 'dialog.confirmYes') }
onPress = { this._onSubmit }
style = { dialogButtonStyle } />
{
!isConfirmHidden && <Dialog.Button
label = { t(confirmLabel || 'dialog.confirmYes') }
onPress = { this._onSubmit }
style = { dialogButtonStyle } />
}
</Dialog.Container>
);
}

View File

@@ -20,6 +20,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
descriptionKey?: string;
/**
* Whether to display the cancel button.
*/
disableCancel?: boolean;
/**
* An optional initial value to initiate the field with.
*/
@@ -52,6 +57,11 @@ interface IState extends AbstractState {
* The current value of the field.
*/
fieldValue?: string;
/**
* The result of the input validation.
*/
isValid: boolean;
}
/**
@@ -68,6 +78,7 @@ class InputDialog extends AbstractDialog<IProps, IState> {
this.state = {
fieldValue: props.initialValue,
isValid: props.validateInput ? props.validateInput(props.initialValue) : true,
submitting: false
};
@@ -115,10 +126,11 @@ class InputDialog extends AbstractDialog<IProps, IState> {
</Dialog.Description>
)
}
<Dialog.Button
{!this.props.disableCancel && <Dialog.Button
label = { t('dialog.Cancel') }
onPress = { this._onCancel } />
onPress = { this._onCancel } />}
<Dialog.Button
disabled = { !this.state.isValid }
label = { t('dialog.Ok') }
onPress = { this._onSubmitValue } />
</Dialog.Container>
@@ -132,10 +144,14 @@ class InputDialog extends AbstractDialog<IProps, IState> {
* @returns {void}
*/
_onChangeText(fieldValue: string) {
if (this.props.validateInput && !this.props.validateInput(fieldValue)) {
if (this.props.validateInput) {
this.setState({
isValid: this.props.validateInput(fieldValue),
fieldValue
});
return;
}
this.setState({
fieldValue
});

View File

@@ -7,10 +7,11 @@ const { browser } = JitsiMeetJS.util;
const DEFAULT_OPTIMAL_BROWSERS = [
'chrome',
'chromium',
'electron',
'firefox',
'nwjs',
'safari'
'safari',
'webkit'
];
const DEFAULT_UNSUPPORTED_BROWSERS: string[] = [];
@@ -20,9 +21,8 @@ const browserNameToCheck = {
chromium: browser.isChromiumBased.bind(browser),
electron: browser.isElectron.bind(browser),
firefox: browser.isFirefox.bind(browser),
nwjs: browser.isNWJS.bind(browser),
opera: browser.isOpera.bind(browser),
safari: browser.isSafari.bind(browser)
safari: browser.isSafari.bind(browser),
webkit: browser.isWebKitBased.bind(browser)
};
/**

View File

@@ -18,3 +18,4 @@ export function isMobileBrowser() {
export function isIosMobileBrowser() {
return Platform.OS === 'ios';
}

View File

@@ -23,6 +23,12 @@ export const AUDIO_MUTE_BUTTON_ENABLED = 'audio-mute.enabled';
*/
export const AUDIO_ONLY_BUTTON_ENABLED = 'audio-only.enabled';
/**
* Flag indicating that the Breakout Rooms button in the overflow menu is enabled.
* Default: enabled (true).
*/
export const BREAKOUT_ROOMS_BUTTON_ENABLED = 'breakout-rooms.enabled';
/**
* Flag indicating if calendar integration should be enabled.
* Default: enabled (true) on Android, auto-detected on iOS.

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