Compare commits

...

118 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
fbdd1d2f38 settings: fix loading disableCallIntegration 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
53ebab33b3 android: fix selecting the Bluetooth route
Samsung devices (of course) seem to stick with the earpiece if we first select
Bluetooth but then set speaker to false. Reverse the order to make everyone
happy.

This only applies to the generic and legacy handlers.
2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
a36965e908 android: fix initializing audio device handler modules too early
When ConnectionService is used (the default) we were attaching the handlers too
early, and since attaching them requires that the RNConnectionService module is
loaded, it silently failed. Instead, use the initialize() method, which gets
called after all the Catalyst (aka native) modules have been loaded.
2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
9bf859ee50 android: log a warning if listeners could not be attached 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
24ff6f58a5 android: make code a bit more readable 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
6338624095 android,hack: send custom UA to fool spartan nginx config on meet.jit.si 2019-11-07 15:28:04 +01:00
Saúl Ibarra Corretgé
9634d58d4e Merge branch 'master' into mobile-19.4 2019-11-07 08:40:06 +01:00
Saúl Ibarra Corretgé
ad0064993d ios: enable Swift mobule stability for the SDK target
Supersedes: https://github.com/jitsi/jitsi-meet/pull/4818
Fixes: https://github.com/jitsi/jitsi-meet/issues/4812
2019-11-06 18:30:26 +01:00
Saúl Ibarra Corretgé
458d4acd22 ios: use the "new" Xcode build system
It was introduced in Xcode 9 and made the default in Xcode 10. We were forcing
the use of the legacy version, which doesn't support some new features that we
wish to enable, such as building the SDK for distribution.
2019-11-06 18:30:26 +01:00
Saúl Ibarra Corretgé
8ebc99175c ios: set deployment target on Pods to 10.0
Matches the app / SDK deployment target and avoids compilation warnings.
2019-11-06 18:30:26 +01:00
Vlad Piersec
9889cb2b69 Add conference name as fallback for subject 2019-11-06 17:23:18 +01:00
Saúl Ibarra Corretgé
95a6f1355f uri: avoid using String.prototype.normalize
It crashes on Android. Well, on the JSC version React Native uses on Android.

While we could use this fallback only on Android, we have decided to use it
on all mobile platforms for consistency.
2019-11-06 15:37:52 +01:00
Saúl Ibarra Corretgé
191e530071 uri: avoid using String.prototype.normalize
It crashes on Android. Well, on the JSC version React Native uses on Android.

While we could use this fallback only on Android, we have decided to use it
on all mobile platforms for consistency.
2019-11-06 15:37:14 +01:00
Mihai Uscat
ae30d39b4d feat(PromotionalFooter): Implement 2019-11-06 03:29:49 -08:00
Leonard Kim
c354e46846 chore(deps): update lib so newer FF does not need click for gum 2019-11-06 07:47:14 +00:00
Hristo Terezov
5da4e43e50 fix(settings): respect configWhitelist 2019-11-05 02:13:54 -08:00
Hristo Terezov
eae6f7760f fix(configWhitelist): add startWithAudioMuted. 2019-11-05 02:13:54 -08:00
Mihai Uscat
00161212c8 feat(welcome): Add responsive text to go button 2019-11-04 05:48:55 -08:00
Mark Anthony Sison
8976b92842 doc(install): adds cd command to jitsi-meet installation 2019-11-03 19:46:34 +00:00
Vlad Piersec
c3a6a8fb17 Add participants count 2019-10-31 09:08:59 -07:00
Saúl Ibarra Corretgé
fd3fb7ef55 Merge branch 'master' into mobile-19.4 2019-10-31 16:45:37 +01:00
Saúl Ibarra Corretgé
391e5ca483 deps: react-native@0.61.3 2019-10-31 16:44:31 +01:00
Saúl Ibarra Corretgé
36654cb808 rn: disable H.264 on select devices even when not in P2P
iOS 10 crashes, so don't use it there, in any case.
2019-10-31 16:41:08 +01:00
Saúl Ibarra Corretgé
6d16e087d9 rn: add a new advanced settings section
Currently only 2 options are implemented, mainly aimed at helping troubleshoot
audio related problems:

- Disable native call integration (it disables CallKit / ConnectionService)
- Disable P2P
2019-10-31 16:41:08 +01:00
Saúl Ibarra Corretgé
fe90e5aa8f rn,settings: remove top margin 2019-10-31 16:41:08 +01:00
Saúl Ibarra Corretgé
3c22cd8ef4 rn,android: refactor audio device handling module
Separate each implementation (3 as of this writing) into each own "handler"
class.

This should make the code easier to understand, maintain and extend.
2019-10-31 16:41:08 +01:00
Bettenbuk Zoltan
5429b8568e feat: feature flag for invite functionalities 2019-10-29 11:27:25 +01:00
George Politis
0eccaf9a21 bumps ljm@5521a40aa85cb6f128f8a6dad9b72a5646132484 (#4791) 2019-10-24 14:52:38 +02:00
Aaron van Meerten
be0950c1ec multidomain mapper functionality and examples (#4773)
* first pass at mod_muc_domain open source plus example

* doc - prosody config and config.js examples for mapper
2019-10-24 12:42:11 +01:00
drimovecz
6ecd150f75 Add context user on speaker stats 2019-10-23 09:24:43 +01:00
Mihai Uscat
02fb37189b fix(welcome): Add extra variables 2019-10-22 13:24:44 -07:00
Jaya Allamsetty
8fe2536996 Update LJM for taking the changes for capScreenshareBitrate 2019-10-22 13:28:14 -05:00
Paweł Domas
4b9e156c5d Generic iOS .ipa build script (#4775) 2019-10-22 12:45:28 -05:00
Bettenbuk Zoltan
9265e1ffec ui: web chat facelift 2019-10-22 13:16:00 +02:00
Bettenbuk Zoltan
d11735b04c feat: make the hangup button first 2019-10-21 19:00:12 +02:00
Saúl Ibarra Corretgé
d33b700477 rn,blank-page: refactor BlankPage
- Remove network-activity "feature"
    - It wasn't in use
    - It relied on internal React Native components, bound to break anytime
- Show an infinite loading indicator
- Style it just like the LoadConfigOverlay
    - Since it kinda represents the opposite, an "unload" then SDK is done
2019-10-21 11:17:56 +02:00
Saúl Ibarra Corretgé
6909c6a0f4 android: fix SDK release script for new dependency syntax
Skip the first character, since it's now like ^123456.0.0
2019-10-21 11:13:13 +02:00
Saúl Ibarra Corretgé
97d75c2cb9 android: fix SDK release script for new dependency syntax
Skip the first character, since it's now like ^123456.0.0
2019-10-21 11:12:26 +02:00
Saúl Ibarra Corretgé
287115f4c3 deps: react-native-webrtc@latest
Fixes a crash on iOS when disposing streams.
2019-10-18 14:05:18 +02:00
Saúl Ibarra Corretgé
63a221212b ios: add ability to manually toggle WebRTC logging 2019-10-18 14:05:18 +02:00
Bettenbuk Zoltan
6a916fd0e1 fix: fix filmstrip-only toolbar 2019-10-18 13:16:15 +02:00
yanas
220691d61d Merge pull request #4762 from jitsi/fix-etherpad-follow-me
Fixes showing etherpad in follow-me mode.
2019-10-17 13:44:10 +01:00
damencho
5cafc4bcbd Fixes showing etherpad in follow-me mode. 2019-10-17 13:39:01 +01:00
yanas
b3a78dc2e6 Merge pull request #4761 from zbettenbuk/aot-icons
fix: fix and refactor AoT css
2019-10-17 12:34:13 +01:00
Bettenbuk Zoltan
bebc6eabe5 fix: fix and refactor AoT css 2019-10-17 12:15:29 +02:00
paweldomas
26dc6a4ac2 update logger and LJM to support log timestamps 2019-10-16 15:59:58 -05:00
Hristo Terezov
ff2626723a fix(HelpButton): Improvements. 2019-10-16 13:34:43 -07:00
Mihai Uscat
72bb897269 feat(DownloadOverflowButton): Implement. 2019-10-16 11:30:06 -07:00
damencho
f46387a226 Adds room name validation logic for web. 2019-10-16 17:52:24 +01:00
damencho
a4cbbccb2a Fixes loading recent lists on wrong meeting name stored.
decodeURIComponent is not needed any more and after adding a validation such meeting name should not happen to be stored.
2019-10-16 17:52:24 +01:00
damencho
3e1a008399 Adds copy icon next to the meeting url in info dialog. 2019-10-16 17:52:24 +01:00
Bettenbuk Zoltan
7e70a8c1de feat: make mobile chat messages selectable 2019-10-16 16:05:10 +02:00
Hristo Terezov
8efee04a10 feat(package.json): Node 12 support. 2019-10-16 06:34:44 -07:00
Bettenbuk Zoltan
a35099f949 feat: add chat color scheming 2019-10-16 10:38:28 +02:00
Mihai Uscat
8be02f9ca1 Implement review changes 2019-10-15 06:54:54 -07:00
Mihai Uscat
3c25a4c08c Naming conventions; Add variables 2019-10-15 06:54:54 -07:00
Mihai Uscat
5ade0cad8b feat(welcome): add posibility to extend settings toolbar 2019-10-15 06:54:54 -07:00
Saúl Ibarra Corretgé
0fa6ffc439 deps: react-native-google-signin@3.0.1 2019-10-14 19:12:45 +02:00
Saúl Ibarra Corretgé
c2ed296178 android: make check more resilient
If action is null (observed on some old devices) we'll get an exception.
Reversing the check fixes it since Actions.XXX is statically defined.
2019-10-14 17:45:43 +02:00
Saúl Ibarra Corretgé
febd12b871 ci: use Xcode 11.1 on Travis 2019-10-14 16:47:07 +02:00
Hristo Terezov
0a06e256b7 feat(HelpButton): Mobile support. 2019-10-14 07:35:39 -07:00
Hristo Terezov
f295f60bea feat(HelpOverflowButton): Implement. 2019-10-14 07:35:39 -07:00
Saúl Ibarra Corretgé
4a8f787519 rn: evaluate config.js in a sandboxed environment
We are downloading code off the Internet and executing it on the user's device,
so run it sandboxed to avoid potential bad actors.

Since it's impossible to eval() safely in JS and React Native doesn't offer
something akin to Node's vm module, here we are rolling our own.

On Android it uses the Duktape JavaScript engine and on iOS the builtin
JavaScriptCore engine. The extra JS engine is *only* used for evaluating the
downloaded code and returning a JSON string which is then passed back to RN.
2019-10-14 12:20:58 +02:00
Saúl Ibarra Corretgé
d85b869934 rn: skip loading configured scriptUrls
None of them work on mobile.
2019-10-14 12:20:58 +02:00
Saúl Ibarra Corretgé
35130f0736 rn: refactor loadScript
- use AbortController for setting the fetch timeout
- use async / await syntax for clarify
- set the default timeout to 5s (previously non-existent, aka 0)
- add ability to load but not evaluate a script
2019-10-14 12:20:58 +02:00
Saúl Ibarra Corretgé
1feff9709c config: drop configLocation and getroomnode options
They never worked on mobile and pose an impediment for makinf config.js more
future proof. Specially if we want to move to a non-executable form of
configuration.
2019-10-14 12:20:58 +02:00
Leonard Kim
1010f53a84 fix(config): add whitelisting for interface config
For now all keys are whitelisted.
2019-10-11 09:38:56 -07:00
Saúl Ibarra Corretgé
f7a526f488 rn: fix rendering unnecessary stuff when in PiP mode 2019-10-11 17:17:53 +02:00
Bettenbuk Zoltan
245eb89b85 fix BottomSheet shaking 2019-10-11 15:14:51 +02:00
Hristo Terezov
99de9d0bfa fix(remoteVideo): Attaching video stream. 2019-10-11 04:58:01 -07:00
Saúl Ibarra Corretgé
98698ba89a etherpad: refactor to share code with mobile
- simplify initialization procedure
- set user display name as the Etherpad name\
- use SharedDocumentButton
2019-10-10 11:19:38 +02:00
Saúl Ibarra Corretgé
19d1e3829d rn: add shared document support using Etherpad 2019-10-10 11:19:38 +02:00
Saúl Ibarra Corretgé
612586ed1f deps: react-native-webview@7.4.1 2019-10-10 11:19:38 +02:00
Saúl Ibarra Corretgé
2609e43f29 ios: misc Xcode changes due to an update 2019-10-10 11:19:38 +02:00
Saúl Ibarra Corretgé
c5cd4f534c dial-in-summary: center the loading indicator 2019-10-10 11:19:38 +02:00
Bettenbuk Zoltan
6e10ca5dd2 fix: chat error message 2019-10-09 18:35:09 +02:00
Bettenbuk Zoltan
0fff1c3534 ref: serve makefile libs locally 2019-10-09 11:56:06 +02:00
Bettenbuk Zoltan
42271b1b89 feat: private messages 2019-10-08 18:22:45 +02:00
Bettenbuk Zoltan
f270b50972 fix: remove unnecessary escaping 2019-10-08 18:22:45 +02:00
Saúl Ibarra Corretgé
ab4b6be9d7 rn: throw exception if default conference options set the room
See: https://github.com/jitsi/jitsi-meet/issues/4720
2019-10-08 14:31:13 +02:00
Saúl Ibarra Corretgé
c45ee0230f android: add getters to JitsiMeetConferenceOptions 2019-10-08 14:31:13 +02:00
George Politis
d210f2f2e7 Adds a noAutoPlayVideo configuration option (used in testing). (#4714)
This adds an option to disable video autoplay that will be used mostly with maleus (our selenium-based load testing tool for testing the new bridge). Disabling video rendering lowers the resource utilisation of the selenium nodes.
2019-10-08 11:34:25 +02:00
Bettenbuk Zoltan
13d78d6b49 fix: utf-8 room name case sensitivity 2019-10-08 10:35:19 +02:00
damencho
34a71042c6 Enables Hungarian translation. 2019-10-05 00:11:25 +01:00
damencho
47ecf7d035 Commit from translate.jitsi.org by user damencho.: 601 of 601 strings translated (0 fuzzy). 2019-10-04 23:15:36 +00:00
damencho
bbe8c52778 Commit from translate.jitsi.org by user damencho.: 600 of 601 strings translated (1 fuzzy). 2019-10-04 23:15:24 +00:00
damencho
2011421e9d Commit from translate.jitsi.org by user damencho.: 582 of 601 strings translated (3 fuzzy). 2019-10-04 23:15:08 +00:00
damencho
ce55952ca9 Commit from translate.jitsi.org by user damencho.: 524 of 601 strings translated (5 fuzzy). 2019-10-04 23:14:58 +00:00
damencho
f93482e815 Commit from translate.jitsi.org by user damencho.: 582 of 601 strings translated (0 fuzzy). 2019-10-04 23:14:45 +00:00
Andrei Gavrilescu
761ac6a730 feat: integrate rnnoise based service for voice activity (VAD) detection 2019-10-04 12:55:18 +02:00
Djorkaeff Alexandre
11d3a343e5 ios: use iPhone 8 simulator when archiving JitsiMeet framework
It's available on both Xcode 10 and 11.
2019-10-04 11:00:31 +02:00
Saúl Ibarra Corretgé
9666bf836e ios: update CocoaPods to version 1.8 2019-10-04 11:00:31 +02:00
Saúl Ibarra Corretgé
a6d3b09796 ios: support building with Xcode 11 2019-10-04 11:00:31 +02:00
Дамян Минков
bb0036fdab Adds notifications for who stop/start recording/live streaming. (#4708)
* Adds notifications for who stop/start recording/live streaming.

* Updates to latest lib-jitsi-meet.
2019-10-03 20:35:21 +01:00
paweldomas
8dc0f30a49 ref(NAT64AddrInfoModule): use 'ipv4only.arpa' well known host
defined in RFC7050 instead of 'nat64.jitsi.net' as suggested by
Jonathan Lennox.
2019-10-02 10:49:39 -05:00
Saúl Ibarra Corretgé
1aed08f460 deps: react-native-webrtc@latest
Fixes iOS crashes when restarting the AudioUnit in case of interruption.
2019-10-02 14:44:15 +02:00
Saúl Ibarra Corretgé
afccf6f06d rn: disable H.264 on iOS 10 devices
It crashes like hell. See:
https://bugs.chromium.org/p/webrtc/issues/detail?id=11002
2019-10-02 14:10:37 +02:00
damencho
5cd351a46f Updates rayo filter to add user token info to dial messages.
Adds option to limit number of outgoing calls per user.
2019-09-30 16:53:38 +01:00
damencho
51f257e894 chore(deps): update LJM to 3f7613748d7669cd3fd031bbdf9069e4309f6f56 2019-09-30 11:53:30 +01:00
Saúl Ibarra Corretgé
ac06892bb4 android,ios: now working on versions 19.4 / 2.4 2019-09-26 17:33:52 +02:00
Saúl Ibarra Corretgé
fd8473cb52 deps: react-native-webrtc@latest
Avoid Android crashes.
2019-09-26 17:33:52 +02:00
damencho
3f40257f89 chore(deps): update LJM to a61941fc9a1927daedc1c9446e4036702964b38a
Uses sendBeacon to send xmpp presence unavailable on unloading the window(leaving the conference).
2019-09-26 16:20:57 +01:00
paweldomas
14509adff2 chore(deps): update LJM to dda16f607ed190a0664d643d511cc87f9bfceaf1
It was referencing LJM from a PR branch instead of the master.
2019-09-25 16:57:06 -05:00
Bettenbuk Zoltan
c472537ecf update RN to 61 2019-09-25 17:31:52 +02:00
Jaya Allamsetty
d40fce741a Fix the WebGL memory leak for Blur effect in Chrome 77 and up (#4652) 2019-09-24 06:50:10 -07:00
Saúl Ibarra Corretgé
944e8f8353 android: fix NPE when handling onHostPause
If the Activity is put into the background before the ReactContext is created we
get an NPE here. While the window might be short, it's thechnically possible to
hit this, as our Crashlytics reports show.
2019-09-24 09:49:52 +02:00
Saúl Ibarra Corretgé
7d972a50f2 ios: set logger subsystem and category
The subsystem is set to the bundle ID and the category to "JitsiMeetSDK".
2019-09-24 09:49:33 +02:00
Leonard Kim
92e7be34e3 fix(icons): scope white svgs to Icon components 2019-09-20 07:50:41 -07:00
Bettenbuk Zoltan
f5dba929a5 fix: add support for functional component type icons 2019-09-20 16:35:56 +02:00
Saúl Ibarra Corretgé
64d2885233 android: raise SDK version 2019-09-20 10:25:42 +02:00
Saúl Ibarra Corretgé
4ce65ae7a7 android: make reportConnectedOutgoingCall return a Promise
The call-integration middleware relies on it returning it, as iOS does.
2019-09-20 10:25:42 +02:00
Saúl Ibarra Corretgé
07bf95f838 doc: add info on reporting security issues 2019-09-19 13:49:46 +02:00
Saúl Ibarra Corretgé
3469d5dc4f doc: move development sections to standalone file 2019-09-19 13:49:46 +02:00
Leonard Kim
8c0f942ae1 add more logging around attaching videos to thumbnails 2019-09-19 10:46:36 +02:00
Leonard Kim
b8aa74f212 fix(large-video): bring back workaround for selecting on conference join
Bring back the workaround introduced in afd2aea7
but removed in 21dcc41d. On conference join,
several other actions have already been fired
that try to set the large video participant
and select the participant on the bridge.
The problem is there is no conference during
these actions so the select participant
never fires. Then subsequent actions do not
fire select participant because the large
video participant has not changed.
2019-09-18 15:00:13 -07:00
George Politis
2a7c6681ad ref: Changes how isVideoPlayable is computed.
This commit changes how the SmallVideo.isVideoPlayable method works.

1st we remove the check on the video stream muted field (materialized with the
!this.videoStream.isMuted() guard). This check is redundant as it is
already materialized in the !this.isVideoMuted check (the isVideoMuted
field is updated with the return value of the videoStream.isMuted()
method).

2nd we return false if we're in audio only mode, because it's
(obviously) undesirable to have a playable video when in audio only
mode.
2019-09-17 18:18:44 +02:00
George Politis
324a9eba91 minor change in debug log wording 2019-09-17 18:18:44 +02:00
George Politis
fb1ed22c6c ref: Tweak logging logic for thumbnail display mode and tile view. 2019-09-17 18:18:44 +02:00
234 changed files with 10378 additions and 6783 deletions

View File

@@ -11,6 +11,9 @@ node_modules/react-native/Libraries/react-native/React.js
; "node_modules/react-native" but in the source repo it is in the root
node_modules/react-native/Libraries/react-native/React.js
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
@@ -112,4 +115,4 @@ untyped-import
untyped-type-import
[version]
^0.98.0
^0.104.0

View File

@@ -0,0 +1,11 @@
---
name: Security issues
about: Please email security@jitsi.org
---
We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.

View File

@@ -1,4 +1,4 @@
osx_image: xcode10.2
osx_image: xcode11.1
language: objective-c
script:
- "./ios/travis-ci/build-ipa.sh"

View File

@@ -3,6 +3,7 @@ CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
NODE_SASS = ./node_modules/.bin/node-sass
NPM = npm
OUTPUT_DIR = .
@@ -20,7 +21,7 @@ compile:
clean:
rm -fr $(BUILD_DIR)
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -47,6 +48,8 @@ deploy-appbundle:
$(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/video-blur-effect.min.js \
$(BUILD_DIR)/video-blur-effect.min.map \
$(BUILD_DIR)/rnnoise-processor.min.js \
$(BUILD_DIR)/rnnoise-processor.min.map \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:
@@ -63,6 +66,11 @@ deploy-libflac:
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
$(DEPLOY_DIR)
deploy-rnnoise-binary:
cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
@@ -71,7 +79,7 @@ deploy-css:
deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
$(WEBPACK_DEV_SERVER)
source-package:

View File

@@ -31,82 +31,9 @@ You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
## Building the sources
## Development
Node.js >= 10 and npm >= 6 are required.
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
```
To build the Jitsi Meet application, just type
```
make
```
### Working with the library sources (lib-jitsi-meet)
By default the library is build from its git repository sources. The default dependency path in package.json is :
```json
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
```
To work with local copy you must change the path to:
```json
"lib-jitsi-meet": "file:///Users/name/local-lib-jitsi-meet-copy",
```
To make the project you must force it to take the sources as 'npm update':
```
npm install lib-jitsi-meet --force && make
```
Or if you are making only changes to the library:
```
npm install lib-jitsi-meet --force && make deploy-lib-jitsi-meet
```
Alternative way is to use [npm link](https://docs.npmjs.com/cli/link).
It allows to link `lib-jitsi-meet` dependency to local source in few steps:
```bash
cd lib-jitsi-meet
#### create global symlink for lib-jitsi-meet package
npm link
cd ../jitsi-meet
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
npm link lib-jitsi-meet
```
After changes in local `lib-jitsi-meet` repository, you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link. It is no longer the case with version 6.x.
If you do not want to use local repository anymore you should run
```bash
cd jitsi-meet
npm unlink lib-jitsi-meet
npm install
```
### Running with webpack-dev-server for development
Use it at the CLI, type
```
make dev
```
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
```
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
make dev
```
The app should be running at https://localhost:8080/
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
## Contributing
@@ -118,7 +45,8 @@ see our [guidelines for contributing](CONTRIBUTING.md).
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
## Security
WebRTC does not provide a way of conducting multi-party conversations with end-to-end encryption.
WebRTC does not (yet) provide a way of conducting multi-party conversations with end-to-end encryption.
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
@@ -130,9 +58,13 @@ Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [8x8](https://8x8.com).
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
## Security issues
We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
## Acknowledgements

View File

@@ -20,5 +20,5 @@
android.useAndroidX=true
android.enableJetifier=true
appVersion=19.3.0
sdkVersion=2.3.0
appVersion=19.4.0
sdkVersion=2.4.0

View File

@@ -10,7 +10,7 @@ MVN_HTTP=0
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1)
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
DO_GIT_TAG=${GIT_TAG:-0}
if [[ $THE_MVN_REPO == http* ]]; then

View File

@@ -45,6 +45,7 @@ dependencies {
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
if (!rootProject.ext.libreBuild) {
implementation 'com.amplitude:android-sdk:2.14.1'

View File

@@ -0,0 +1,165 @@
/*
* Copyright @ 2017-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.content.Context;
import android.os.Build;
import android.telecom.CallAudioState;
import androidx.annotation.RequiresApi;
import java.util.HashSet;
import java.util.Set;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* Android versions >= O when ConnectionService is enabled.
*/
@RequiresApi(Build.VERSION_CODES.O)
class AudioDeviceHandlerConnectionService implements
AudioModeModule.AudioDeviceHandlerInterface,
RNConnectionService.CallAudioStateListener {
private final static String TAG = AudioDeviceHandlerConnectionService.class.getSimpleName();
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
* no match is found.
*/
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return CallAudioState.ROUTE_EARPIECE;
}
switch (audioDevice) {
case AudioModeModule.DEVICE_BLUETOOTH:
return CallAudioState.ROUTE_BLUETOOTH;
case AudioModeModule.DEVICE_EARPIECE:
return CallAudioState.ROUTE_EARPIECE;
case AudioModeModule.DEVICE_HEADPHONES:
return CallAudioState.ROUTE_WIRED_HEADSET;
case AudioModeModule.DEVICE_SPEAKER:
return CallAudioState.ROUTE_SPEAKER;
default:
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
return CallAudioState.ROUTE_EARPIECE;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
devices.add(AudioModeModule.DEVICE_EARPIECE);
}
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
devices.add(AudioModeModule.DEVICE_SPEAKER);
}
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(AudioModeModule.DEVICE_HEADPHONES);
}
return devices;
}
/**
* Used to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
* the {@code availableDevices} on each update.
*/
private int supportedRouteMask = -1;
public AudioDeviceHandlerConnectionService() {
}
@Override
public void onCallAudioStateChange(final CallAudioState state) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
boolean audioRouteChanged
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
int newSupportedRoutes = state.getSupportedRouteMask();
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
Set<String> devices = routesToDeviceNames(supportedRouteMask);
module.replaceDevices(devices);
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
}
if (audioRouteChanged || audioDevicesChanged) {
module.resetSelectedDevice();
module.updateAudioRoute();
}
}
});
}
@Override
public void start(Context context, AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) {
rcs.setCallAudioStateListener(this);
} else {
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
}
}
@Override
public void stop() {
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) {
rcs.setCallAudioStateListener(null);
} else {
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
}
}
public void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
@Override
public boolean setMode(int mode) {
return true;
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright @ 2017-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.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.HashSet;
import java.util.Set;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* all post-M Android versions. This handler can be used on any Android versions >= M, but by
* default it's only used on versions < O, since versions >= O use ConnectionService, but it
* can be disabled.
*/
@RequiresApi(Build.VERSION_CODES.M)
class AudioDeviceHandlerGeneric implements
AudioModeModule.AudioDeviceHandlerInterface,
AudioManager.OnAudioFocusChangeListener {
private final static String TAG = AudioDeviceHandlerGeneric.class.getSimpleName();
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
*/
private static final int TYPE_USB_HEADSET = 22;
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private AudioManager audioManager;
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(AudioModeModule.DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(AudioModeModule.DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_USB_HEADSET:
devices.add(AudioModeModule.DEVICE_HEADPHONES);
break;
}
}
module.replaceDevices(devices);
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
module.updateAudioRoute();
}
};
private final android.media.AudioDeviceCallback audioDeviceCallback =
new android.media.AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices removed");
onAudioDeviceChange();
}
};
public AudioDeviceHandlerGeneric() {
}
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the audio thread.
*/
private void onAudioDeviceChange() {
module.runInAudioThread(onAudioDeviceChangeRunner);
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(final int focusChange) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.updateAudioRoute();
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
audioFocusLost = true;
break;
}
}
}
});
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
@Override
public void start(Context context, AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
// Setup runtime device change detection.
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
// Do an initial detection.
onAudioDeviceChange();
}
@Override
public void stop() {
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback);
}
@Override
public void setAudioRoute(String device) {
// Turn speaker on / off
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
// Turn bluetooth on / off
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
}
@Override
public boolean setMode(int mode) {
if (mode == AudioModeModule.DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright @ 2017-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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* legacy (pre-M) Android versions.
*/
class AudioDeviceHandlerLegacy implements
AudioModeModule.AudioDeviceHandlerInterface,
AudioManager.OnAudioFocusChangeListener,
BluetoothHeadsetMonitor.Listener {
private final static String TAG = AudioDeviceHandlerLegacy.class.getSimpleName();
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private AudioManager audioManager;
/**
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
* old (< M) Android versions.
*/
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
public AudioDeviceHandlerLegacy() {
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*/
@Override
public void onBluetoothDeviceChange(final boolean deviceAvailable) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
if (deviceAvailable) {
module.addDevice(AudioModeModule.DEVICE_BLUETOOTH);
} else {
module.removeDevice(AudioModeModule.DEVICE_BLUETOOTH);
}
module.updateAudioRoute();
}
});
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*/
private void onHeadsetDeviceChange() {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
module.addDevice(AudioModeModule.DEVICE_HEADPHONES);
} else {
module.removeDevice(AudioModeModule.DEVICE_HEADPHONES);
}
module.updateAudioRoute();
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(final int focusChange) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.updateAudioRoute();
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
audioFocusLost = true;
break;
}
}
}
});
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
@Override
public void start(Context context, AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
// Setup runtime device change detection.
//
// Detect changes in wired headset connections.
IntentFilter wiredHeadSetFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG);
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
onHeadsetDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
// Detect Bluetooth device changes.
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(context, this);
// On Android < M, detect if we have an earpiece.
PackageManager pm = context.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
module.addDevice(AudioModeModule.DEVICE_EARPIECE);
}
// Always assume there is a speaker.
module.addDevice(AudioModeModule.DEVICE_SPEAKER);
}
@Override
public void stop() {
bluetoothHeadsetMonitor.stop();
bluetoothHeadsetMonitor = null;
}
@Override
public void setAudioRoute(String device) {
// Turn speaker on / off
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
// Turn bluetooth on / off
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
}
@Override
public boolean setMode(int mode) {
if (mode == AudioModeModule.DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2017-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.
@@ -16,16 +16,8 @@
package org.jitsi.meet.sdk;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@@ -61,9 +53,7 @@ import java.util.concurrent.Executors;
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule
implements AudioManager.OnAudioFocusChangeListener {
class AudioModeModule extends ReactContextBaseJavaModule {
public static final String NAME = "AudioMode";
/**
@@ -75,161 +65,33 @@ class AudioModeModule extends ReactContextBaseJavaModule
* - VIDEO_CALL: Used for video calls. It will use the speaker by default,
* unless a wired or Bluetooth headset is connected.
*/
private static final int DEFAULT = 0;
private static final int AUDIO_CALL = 1;
private static final int VIDEO_CALL = 2;
/**
* Constant defining the action for plugging in a headset. This is used on
* our device detection system for API < 23.
*/
private static final String ACTION_HEADSET_PLUG
= (Build.VERSION.SDK_INT >= 21)
? AudioManager.ACTION_HEADSET_PLUG
: Intent.ACTION_HEADSET_PLUG;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
*/
private static final int TYPE_USB_HEADSET = 22;
static final int DEFAULT = 0;
static final int AUDIO_CALL = 1;
static final int VIDEO_CALL = 2;
/**
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
*/
static final String TAG = NAME;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
* no match is found.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
switch (audioDevice) {
case DEVICE_BLUETOOTH:
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
case DEVICE_EARPIECE:
return android.telecom.CallAudioState.ROUTE_EARPIECE;
case DEVICE_HEADPHONES:
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
case DEVICE_SPEAKER:
return android.telecom.CallAudioState.ROUTE_SPEAKER;
default:
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
devices.add(DEVICE_EARPIECE);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
devices.add(DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
devices.add(DEVICE_SPEAKER);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(DEVICE_HEADPHONES);
}
return devices;
}
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
static boolean useConnectionService() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
return supportsConnectionService && useConnectionService_;
}
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private final AudioManager audioManager;
/**
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
* old (< M) Android versions.
*/
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
private AudioDeviceHandlerInterface audioDeviceHandler;
/**
* {@link ExecutorService} for running all audio operations on a dedicated
* thread.
*/
private static final ExecutorService executor
= Executors.newSingleThreadExecutor();
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos
= audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_USB_HEADSET:
devices.add(DEVICE_HEADPHONES);
break;
}
}
availableDevices = devices;
JitsiMeetLogger.i(TAG + " Available audio devices: " +
availableDevices.toString());
// Reset user selection
userSelectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
/**
* Audio mode currently in use.
@@ -239,10 +101,10 @@ class AudioModeModule extends ReactContextBaseJavaModule
/**
* Audio device types.
*/
private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
private static final String DEVICE_EARPIECE = "EARPIECE";
private static final String DEVICE_HEADPHONES = "HEADPHONES";
private static final String DEVICE_SPEAKER = "SPEAKER";
static final String DEVICE_BLUETOOTH = "BLUETOOTH";
static final String DEVICE_EARPIECE = "EARPIECE";
static final String DEVICE_HEADPHONES = "HEADPHONES";
static final String DEVICE_SPEAKER = "SPEAKER";
/**
* Device change event.
@@ -259,15 +121,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
*/
private String selectedDevice;
/**
* Used on API >= 26 to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
* the {@link #availableDevices} on each update.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private int supportedRouteMask;
/**
* User selected device. When null the default is used depending on the
* mode.
@@ -283,31 +136,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
*/
public AudioModeModule(ReactApplicationContext reactContext) {
super(reactContext);
audioManager
= (AudioManager)
reactContext.getSystemService(Context.AUDIO_SERVICE);
// Starting Oreo the ConnectionImpl from ConnectionService is used to
// detect the available devices.
if (!useConnectionService()) {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Do an initial detection on Android >= M.
onAudioDeviceChange();
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
}
/**
@@ -364,132 +192,36 @@ class AudioModeModule extends ReactContextBaseJavaModule
}
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the main thread.
*
* Only used on Android >= M.
*/
void onAudioDeviceChange() {
runInAudioThread(onAudioDeviceChangeRunner);
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*
* Only used on Android < M. Runs on the main thread.
*/
void onBluetoothDeviceChange() {
if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
availableDevices.add(DEVICE_BLUETOOTH);
} else {
availableDevices.remove(DEVICE_BLUETOOTH);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*
* Only used on Android < M.
*/
void onHeadsetDeviceChange() {
runInAudioThread(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
availableDevices.add(DEVICE_HEADPHONES);
} else {
availableDevices.remove(DEVICE_HEADPHONES);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
});
}
@RequiresApi(api = Build.VERSION_CODES.O)
void onCallAudioStateChange(Object callAudioState_) {
final android.telecom.CallAudioState callAudioState
= (android.telecom.CallAudioState)callAudioState_;
runInAudioThread(new Runnable() {
@Override
public void run() {
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
boolean audioDevicesChanged
= supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
availableDevices = routesToDeviceNames(supportedRouteMask);
JitsiMeetLogger.i(TAG + " Available audio devices: "
+ availableDevices.toString());
}
boolean audioRouteChanged
= audioDeviceToRouteInt(selectedDevice)
!= callAudioState.getRoute();
if (audioRouteChanged || audioDevicesChanged) {
// Reset user selection
userSelectedDevice = null;
// If the OS changes the Audio Route or Devices we could have lost
// the selected audio device
selectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
* Initializes the audio device handler module. This function is called *after* all Catalyst
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
* needs access to another Catalyst module, so doing this in the constructor would be too early.
*/
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
updateAudioRoute(mode);
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
audioFocusLost = true;
break;
public void initialize() {
setAudioDeviceHandler();
}
private void setAudioDeviceHandler() {
if (audioDeviceHandler != null) {
audioDeviceHandler.stop();
}
if (useConnectionService()) {
audioDeviceHandler = new AudioDeviceHandlerConnectionService();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
audioDeviceHandler = new AudioDeviceHandlerGeneric();
} else {
audioDeviceHandler = new AudioDeviceHandlerLegacy();
}
audioDeviceHandler.start(getReactApplicationContext(), this);
}
/**
* Helper function to run operations on a dedicated thread.
* @param runnable
*/
public void runInAudioThread(Runnable runnable) {
void runInAudioThread(Runnable runnable) {
executor.execute(runnable);
}
@@ -518,46 +250,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
});
}
/**
* The API >= 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
/**
* The API < 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
private void setAudioRoutePreO(String audioDevice) {
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
/**
* Public method to set the current audio mode.
*
@@ -587,70 +279,22 @@ class AudioModeModule extends ReactContextBaseJavaModule
AudioModeModule.this.mode = mode;
promise.resolve(null);
} else {
promise.reject(
"setMode",
"Failed to set audio mode to " + mode);
promise.reject("setMode", "Failed to set audio mode to " + mode);
}
}
});
}
/**
* Setup the audio route change detection mechanism. We use the
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
* Sets whether ConnectionService should be used (if available) for setting the audio mode
* or not.
*
* @param use Boolean indicator of where it should be used or not.
*/
private void setupAudioRouteChangeDetection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setupAudioRouteChangeDetectionM();
} else {
setupAudioRouteChangeDetectionPreM();
}
}
/**
* Audio route change detection mechanism for 23 >= Android API < 26.
*/
@TargetApi(Build.VERSION_CODES.M)
private void setupAudioRouteChangeDetectionM() {
android.media.AudioDeviceCallback audioDeviceCallback =
new android.media.AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices removed");
onAudioDeviceChange();
}
};
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
}
/**
* Audio route change detection mechanism for Android API < 23.
*/
private void setupAudioRouteChangeDetectionPreM() {
Context context = getReactApplicationContext();
// Detect changes in wired headset connections.
IntentFilter wiredHeadSetFilter = new IntentFilter(ACTION_HEADSET_PLUG);
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
onHeadsetDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
// Detect Bluetooth device changes.
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(this, context);
@ReactMethod
public void setUseConnectionService(boolean use) {
useConnectionService_ = use;
setAudioDeviceHandler();
}
/**
@@ -663,14 +307,11 @@ class AudioModeModule extends ReactContextBaseJavaModule
private boolean updateAudioRoute(int mode) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
if (!audioDeviceHandler.setMode(mode)) {
return false;
}
if (mode == DEFAULT) {
if (!useConnectionService()) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
}
selectedDevice = null;
userSelectedDevice = null;
@@ -678,20 +319,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
return true;
}
if (!useConnectionService()) {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}
}
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
@@ -706,8 +333,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
}
// Consider the user's selection
if (userSelectedDevice != null
&& availableDevices.contains(userSelectedDevice)) {
if (userSelectedDevice != null && availableDevices.contains(userSelectedDevice)) {
audioDevice = userSelectedDevice;
}
@@ -720,13 +346,97 @@ class AudioModeModule extends ReactContextBaseJavaModule
selectedDevice = audioDevice;
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
if (useConnectionService()) {
setAudioRoute(audioDevice);
} else {
setAudioRoutePreO(audioDevice);
}
audioDeviceHandler.setAudioRoute(audioDevice);
notifyDevicesChanged();
return true;
}
/**
* Gets the currently selected audio device.
*
* @return The selected audio device.
*/
String getSelectedDevice() {
return selectedDevice;
}
/**
* Resets the current device selection.
*/
void resetSelectedDevice() {
selectedDevice = null;
userSelectedDevice = null;
}
/**
* Adds a new device to the list of available devices.
*
* @param device The new device.
*/
void addDevice(String device) {
availableDevices.add(device);
resetSelectedDevice();
}
/**
* Removes a device from the list of available devices.
*
* @param device The old device to the removed.
*/
void removeDevice(String device) {
availableDevices.remove(device);
resetSelectedDevice();
}
/**
* Replaces the current list of available devices with a new one.
*
* @param devices The new devices list.
*/
void replaceDevices(Set<String> devices) {
availableDevices = devices;
resetSelectedDevice();
}
/**
* Re-sets the current audio route. Needed when devices changes have happened.
*/
void updateAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Interface for the modules implementing the actual audio device management.
*/
interface AudioDeviceHandlerInterface {
/**
* Start detecting audio device changes.
* @param context Android {@link Context} where detection should take place.
* @param audioModeModule Reference to the main {@link AudioModeModule}.
*/
void start(Context context, AudioModeModule audioModeModule);
/**
* Stop audio device detection.
*/
void stop();
/**
* Set the appropriate route for the given audio device.
*
* @param device Audio device for which the route must be set.
*/
void setAudioRoute(String device);
/**
* Set the given audio mode.
*
* @param mode The new audio mode to be used.
* @return Whether the operation was successful or not.
*/
boolean setMode(int mode);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2017-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.
@@ -33,68 +33,43 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* about device changes when this occurs.
*/
class BluetoothHeadsetMonitor {
/**
* {@link AudioModeModule} where this monitor reports.
*/
private final AudioModeModule audioModeModule;
private final static String TAG = BluetoothHeadsetMonitor.class.getSimpleName();
/**
* The {@link Context} in which {@link #audioModeModule} executes.
* The {@link Context} in which this module executes.
*/
private final Context context;
/**
* Reference to the {@link BluetoothAdapter} object, used to access Bluetooth functionality.
*/
private BluetoothAdapter adapter;
/**
* Reference to a proxy object which allows us to query connected devices.
*/
private BluetoothHeadset headset;
/**
* Flag indicating if there are any Bluetooth headset devices currently
* available.
* receiver registered for receiving Bluetooth connection state changes.
*/
private boolean headsetAvailable = false;
private BroadcastReceiver receiver;
/**
* Helper for running Bluetooth operations on the main thread.
* Listener for receiving Bluetooth device change events.
*/
private final Runnable updateDevicesRunnable
= new Runnable() {
@Override
public void run() {
headsetAvailable
= (headset != null)
&& !headset.getConnectedDevices().isEmpty();
audioModeModule.onBluetoothDeviceChange();
}
};
private Listener listener;
public BluetoothHeadsetMonitor(
AudioModeModule audioModeModule,
Context context) {
this.audioModeModule = audioModeModule;
public BluetoothHeadsetMonitor(Context context, Listener listener) {
this.context = context;
AudioManager audioManager
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
JitsiMeetLogger.w(AudioModeModule.TAG + " Bluetooth SCO is not available");
return;
}
if (getBluetoothHeadsetProfileProxy()) {
registerBluetoothReceiver();
// Initial detection.
updateDevices();
}
this.listener = listener;
}
private boolean getBluetoothHeadsetProfileProxy() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
JitsiMeetLogger.w(AudioModeModule.TAG + " Device doesn't support Bluetooth");
JitsiMeetLogger.w(TAG + " Device doesn't support Bluetooth");
return false;
}
@@ -104,9 +79,7 @@ class BluetoothHeadsetMonitor {
BluetoothProfile.ServiceListener listener
= new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(
int profile,
BluetoothProfile proxy) {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
headset = (BluetoothHeadset) proxy;
updateDevices();
@@ -120,21 +93,7 @@ class BluetoothHeadsetMonitor {
}
};
return
adapter.getProfileProxy(
context,
listener,
BluetoothProfile.HEADSET);
}
/**
* Returns the current headset availability.
*
* @return {@code true} if there is a Bluetooth headset connected;
* {@code false}, otherwise.
*/
public boolean isHeadsetAvailable() {
return headsetAvailable;
return adapter.getProfileProxy(context, listener, BluetoothProfile.HEADSET);
}
private void onBluetoothReceiverReceive(Context context, Intent intent) {
@@ -149,7 +108,7 @@ class BluetoothHeadsetMonitor {
switch (state) {
case BluetoothHeadset.STATE_CONNECTED:
case BluetoothHeadset.STATE_DISCONNECTED:
JitsiMeetLogger.d(AudioModeModule.TAG + " BT headset connection state changed: " + state);
JitsiMeetLogger.d(TAG + " BT headset connection state changed: " + state);
updateDevices();
break;
}
@@ -157,13 +116,12 @@ class BluetoothHeadsetMonitor {
// XXX: This action will be fired when the connection established
// with a Bluetooth headset (called a SCO connection) changes state.
// When the SCO connection is active we route audio to it.
int state
= intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
switch (state) {
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
JitsiMeetLogger.d(AudioModeModule.TAG + " BT SCO connection state changed: " + state);
JitsiMeetLogger.d(TAG + " BT SCO connection state changed: " + state);
updateDevices();
break;
}
@@ -171,24 +129,63 @@ class BluetoothHeadsetMonitor {
}
private void registerBluetoothReceiver() {
BroadcastReceiver receiver = new BroadcastReceiver() {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBluetoothReceiverReceive(context, intent);
}
};
IntentFilter filter = new IntentFilter();
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
context.registerReceiver(receiver, filter);
}
/**
* Detects if there are new devices connected / disconnected and fires the
* {@link AudioModeModule#onAudioDeviceChange()} callback.
* {@link Listener} registered event.
*/
private void updateDevices() {
audioModeModule.runInAudioThread(updateDevicesRunnable);
boolean headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty();
listener.onBluetoothDeviceChange(headsetAvailable);
}
public void start() {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
JitsiMeetLogger.w(TAG + " Bluetooth SCO is not available");
return;
}
if (!getBluetoothHeadsetProfileProxy()) {
JitsiMeetLogger.w(TAG + " Couldn't get BT profile proxy");
return;
}
registerBluetoothReceiver();
// Initial detection.
updateDevices();
}
public void stop() {
if (receiver != null) {
context.unregisterReceiver(receiver);
}
if (adapter != null && headset != null) {
adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset);
}
receiver = null;
headset = null;
adapter = null;
}
interface Listener {
void onBluetoothDeviceChange(boolean deviceAvailable);
}
}

View File

@@ -403,11 +403,9 @@ public class ConnectionService extends android.telecom.ConnectionService {
@Override
public void onCallAudioStateChanged(CallAudioState state) {
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
AudioModeModule audioModeModule
= ReactInstanceManagerHolder
.getNativeModule(AudioModeModule.class);
if (audioModeModule != null) {
audioModeModule.onCallAudioStateChange(state);
RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (module != null) {
module.onCallAudioStateChange(state);
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 com.facebook.react.bridge.Promise;
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;
import com.squareup.duktape.Duktape;
@ReactModule(name = JavaScriptSandboxModule.NAME)
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
public static final String NAME = "JavaScriptSandbox";
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Evaluates the given code in a Duktape VM.
* @param code - The code that needs to evaluated.
* @param promise - Resolved with the output in case of success or rejected with an exception
* in case of failure.
*/
@ReactMethod
public void evaluate(String code, Promise promise) {
Duktape vm = Duktape.create();
try {
Object res = vm.evaluate(code);
promise.resolve(res.toString());
} catch (Throwable tr) {
promise.reject(tr);
} finally {
vm.close();
}
}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -33,6 +33,9 @@ public class JitsiMeet {
}
public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) {
if (options != null && options.getRoom() != null) {
throw new RuntimeException("'room' must be null in the default conference options");
}
defaultConferenceOptions = options;
}

View File

@@ -122,7 +122,7 @@ public class JitsiMeetActivityDelegate {
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
// Why this happens is a mystery wrapped in an enigma.
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (activity == reactContext.getCurrentActivity()) {
if (reactContext != null && activity == reactContext.getCurrentActivity()) {
reactInstanceManager.onHostPause(activity);
}
}

View File

@@ -72,6 +72,46 @@ public class JitsiMeetConferenceOptions implements Parcelable {
*/
private JitsiMeetUserInfo userInfo;
public URL getServerURL() {
return serverURL;
}
public String getRoom() {
return room;
}
public String getSubject() {
return subject;
}
public String getToken() {
return token;
}
public Bundle getColorScheme() {
return colorScheme;
}
public Bundle getFeatureFlags() {
return featureFlags;
}
public boolean getAudioMuted() {
return audioMuted;
}
public boolean getAudioOnly() {
return audioOnly;
}
public boolean getVideoMuted() {
return videoMuted;
}
public JitsiMeetUserInfo getUserInfo() {
return userInfo;
}
/**
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
*/

View File

@@ -87,7 +87,7 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent.getAction();
if (action.equals(Actions.START)) {
if (Actions.START.equals(action)) {
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
if (notification == null) {
stopSelf();
@@ -96,7 +96,7 @@ public class JitsiMeetOngoingConferenceService extends Service
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
} else if (action.equals(Actions.HANGUP)) {
} else if (Actions.HANGUP.equals(action)) {
JitsiMeetLogger.i(TAG + " Hangup requested");
// Abort all ongoing calls
if (AudioModeModule.useConnectionService()) {

View File

@@ -29,13 +29,18 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService
extends ReactContextBaseJavaModule {
class RNConnectionService extends ReactContextBaseJavaModule {
public static final String NAME = "ConnectionService";
private static final String TAG = ConnectionService.TAG;
/**
* Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
* and other modules such as {@link AudioModeModule}.
*/
private CallAudioStateListener callAudioStateListener;
/**
* Sets the audio route on all existing {@link android.telecom.Connection}s
*
@@ -144,9 +149,10 @@ class RNConnectionService
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportConnectedOutgoingCall(String callUUID) {
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
ConnectionService.setConnectionActive(callUUID);
promise.resolve(null);
}
@Override
@@ -167,4 +173,28 @@ class RNConnectionService
public void updateCall(String callUUID, ReadableMap callState) {
ConnectionService.updateCall(callUUID, callState);
}
public CallAudioStateListener getCallAudioStateListener() {
return callAudioStateListener;
}
public void setCallAudioStateListener(CallAudioStateListener callAudioStateListener) {
this.callAudioStateListener = callAudioStateListener;
}
/**
* Handler for call state changes. {@code ConnectionServiceImpl} will call this handler when the
* call audio state changes.
*
* @param callAudioState The current call's audio state.
*/
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState) {
if (callAudioStateListener != null) {
callAudioStateListener.onCallAudioStateChange(callAudioState);
}
}
interface CallAudioStateListener {
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
}
}

View File

@@ -67,6 +67,7 @@ class ReactInstanceManagerHolder {
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),

View File

@@ -43,7 +43,7 @@ public class NAT64AddrInfoModule
* The host for which the module wil try to resolve both IPv4 and IPv6
* addresses in order to figure out the NAT64 prefix.
*/
private final static String HOST = "nat64.jitsi.net";
private final static String HOST = "ipv4only.arpa";
/**
* How long is the {@link NAT64AddrInfo} instance valid.

View File

@@ -10,7 +10,7 @@ project(':react-native-community-async-storage').projectDir = new File(rootProje
include ':react-native-community_netinfo'
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
include ':react-native-google-signin'
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android')
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'

View File

@@ -104,7 +104,6 @@ import {
trackRemoved
} from './react/features/base/tracks';
import { getJitsiMeetGlobalNS } from './react/features/base/util';
import { addMessage } from './react/features/chat';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import {
@@ -114,7 +113,6 @@ import {
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { suspendDetected } from './react/features/power-monitor';
import { setSharedVideoStatus } from './react/features/shared-video';
import { isButtonEnabled } from './react/features/toolbox';
import { endpointMessageReceived } from './react/features/subtitles';
const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -244,8 +242,6 @@ class ConferenceConnector {
this._handleConferenceJoined.bind(this));
room.on(JitsiConferenceEvents.CONFERENCE_FAILED,
this._onConferenceFailed.bind(this));
room.on(JitsiConferenceEvents.CONFERENCE_ERROR,
this._onConferenceError.bind(this));
}
/**
@@ -348,31 +344,6 @@ class ConferenceConnector {
}
}
/**
*
*/
_onConferenceError(err, ...params) {
logger.error('CONFERENCE Error:', err, params);
switch (err) {
case JitsiConferenceErrors.CHAT_ERROR:
logger.error('Chat error.', err);
if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
const [ code, msg ] = params;
APP.store.dispatch(addMessage({
hasRead: true,
error: code,
message: msg,
messageType: 'error',
timestamp: Date.now()
}));
}
break;
default:
logger.error('Unknown error.', err);
}
}
/**
*
*/

View File

@@ -1,16 +1,6 @@
/* eslint-disable no-unused-vars, no-var */
var config = {
// Configuration
//
// Alternative location for the configuration.
// configLocation: './config.json',
// Custom function which given the URL path should return a room name.
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
// Connection
//
@@ -60,6 +50,10 @@ var config = {
// Enables the test specific features consumed by jitsi-meet-torture
// testMode: false
// Disables the auto-play behavior of *all* newly created video element.
// This is useful when the client runs on a host with limited resources.
// noAutoPlayVideo: false
},
// Disables ICE/UDP by filtering out local and remote UDP candidates in
@@ -425,6 +419,16 @@ var config = {
// the menu has option to flip the locally seen video for local presentations
// disableLocalVideoFlip: false
// Deployment specific URLs.
// deploymentUrls: {
// // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
// // user documentation.
// userDocumentationURL: 'https://docs.example.com/video-meetings.html',
// // If specified a 'Download our apps' button will be displayed in the overflow menu with a link
// // to the specified URL for an app download page.
// downloadAppsUrl: 'https://docs.example.com/our-apps.html'
// }
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold

View File

@@ -33,7 +33,7 @@ body {
}
}
svg {
.jitsi-icon svg {
fill: white;
}

View File

@@ -80,6 +80,28 @@
}
}
#chat-recipient {
align-items: center;
background-color: $chatPrivateMessageBackgroundColor;
display: flex;
flex-direction: row;
font-weight: 100;
padding: 10px;
span {
color: white;
display: flex;
flex: 1;
}
div {
svg {
cursor: pointer;
fill: white
}
}
}
.chat-header {
background-color: $chatHeaderBackgroundColor;
height: 70px;
@@ -111,6 +133,7 @@
#chat-input {
border-top: 1px solid $chatInputSeparatorColor;
display: flex;
padding: 5px 10px;
* {
background-color: transparent;
@@ -131,8 +154,7 @@
box-shadow: none;
color: white;
font-size: 15px;
line-height: 30px;
padding: 5px;
padding: 10px;
overflow-y: auto;
resize: none;
width: 100%;
@@ -162,6 +184,7 @@
.display-name {
font-size: 13px;
font-weight: bold;
margin-bottom: 5px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -175,7 +198,6 @@
color: white;
margin-top: 3px;
max-width: 100%;
padding-bottom: 3px;
position: relative;
&.localuser {
@@ -196,6 +218,15 @@
padding: 0;
}
}
.privatemessagenotice {
font-size: 11px;
font-weight: 100;
}
.messagecontent {
margin: 5px 10px;
}
}
.smiley {
@@ -228,6 +259,7 @@
.smileys-panel {
bottom: 100%;
box-sizing: border-box;
background-color: rgba(0, 0, 0, .6) !important;
height: auto;
max-height: 0;
overflow: hidden;
@@ -270,10 +302,6 @@
cursor: pointer;
}
#usermsg::-webkit-input-placeholder {
line-height: 30px;
}
#usermsg::-webkit-scrollbar-track-piece {
background: #3a3a3a;
}
@@ -288,6 +316,10 @@
.chatmessage {
background-color: $chatLocalMessageBackgroundColor;
border-radius: 6px 0px 6px 6px;
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
}
.display-name {
@@ -301,8 +333,9 @@
&.error {
.chatmessage {
background-color: $defaultWarningColor;
border-radius: 0px;
color: red;
font-weight: 100;
}
.display-name {
@@ -312,6 +345,25 @@
.chatmessage-wrapper {
max-width: 100%;
.replywrapper {
display: flex;
flex-direction: row;
align-items: center;
.messageactions {
align-self: stretch;
border-left: 1px solid $chatActionsSeparatorColor;
display: flex;
flex-direction: column;
justify-content: center;
padding: 5px;
.toolbox-icon {
cursor: pointer;
}
}
}
}
.chatmessage {
@@ -320,6 +372,9 @@
display: inline-block;
margin-top: 3px;
color: white;
padding: 8px;
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
}
}

66
css/_mini_toolbox.scss Normal file
View File

@@ -0,0 +1,66 @@
.filmstrip-toolbox,
.always-on-top-toolbox {
background-color: $newToolbarBackgroundColor;
border-radius: 3px;
display: flex;
z-index: $toolbarZ;
.toolbox-icon {
cursor: pointer;
padding: 7px;
&.toggled {
background: $AOTToolbarButtonToggleColor;
}
&.disabled {
cursor: initial;
}
}
}
.always-on-top-toolbox {
flex-direction: row;
left: 50%;
position: absolute;
top: 10px;
transform: translateX(-50%);
.toolbox-button {
&:first-child {
.toolbox-icon {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
}
&:nth-child(2) {
svg {
fill: $hangupColor;
}
}
&:last-child {
.toolbox-icon {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
}
.filmstrip-toolbox {
flex-direction: column;
.toolbox-button {
&:nth-child(1) {
svg {
fill: $hangupColor;
}
}
.toolbox-icon {
border-radius: 3px;
}
}
}

View File

@@ -0,0 +1,26 @@
.participants-count {
background: #fff;
border-radius: 4px;
color: #5e6d7a;
cursor: pointer;
display: inline-block;
font-size: 13px;
line-height: 20px;
margin-left: 16px;
padding: 4px 8px;
pointer-events: auto;
&-number {
margin-right: 8px;
vertical-align: middle;
}
&-icon {
background: url('../images/user-groups.svg');
background-repeat: no-repeat;
display: inline-block;
height: 16px;
width: 16px;
vertical-align: middle;
}
}

View File

@@ -6,7 +6,7 @@
min-width: 75px;
text-align: left;
padding: 0px;
width: 150px;
width: 180px;
white-space: nowrap;
&__item {
@@ -87,6 +87,7 @@
display: inline-block;
min-width: 20px;
height: 100%;
padding-right: 10px;
> * {
@include absoluteAligning();

View File

@@ -0,0 +1 @@
/** Insert custom CSS for any additional content in the promotional footer **/

View File

@@ -9,7 +9,7 @@
text-align: center;
font-size: 17px;
color: #fff;
z-index: $toolbarBackgroundZ;
z-index: $zindex10;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
@@ -19,4 +19,8 @@
&.visible {
top: 0px;
}
&-text {
vertical-align: middle;
}
}

View File

@@ -270,89 +270,6 @@
}
}
.always-on-top-toolbox,
.filmstrip-toolbox {
background-color: $newToolbarBackgroundColor;
box-sizing: border-box;
display: flex;
flex-direction: column;
z-index: $toolbarZ;
i {
cursor: pointer;
display: block;
}
i:hover {
background-color: $AOTToolbarButtonHoverColor;
}
i.toggled {
background: $AOTToolbarButtonToggleColor;
}
i.toggled:hover:not(.disabled) {
background-color: $AOTToolbarButtonHoverColor;
}
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
text-align: center;
}
border-radius: 3px;
}
.always-on-top-toolbox {
flex-direction: row;
left: 50%;
position: absolute;
top: 10px;
transform: translateX(-50%);
z-index: $toolbarZ;
i {
font-size: $newToolbarFontSize;
height: $newToolbarSize;
line-height: $newToolbarSize;
width: $newToolbarSize;
}
.disabled {
cursor: initial;
}
.toolbox-button:first-child i {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.toolbox-button:last-child i {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
.filmstrip-toolbox {
i {
font-size: 1.9em;
height: 37px;
line-height: 37px;
width: 37px;
}
.toolbox-button:first-child i {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.toolbox-button:last-child i {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
}
/**
* START of fade in animation for main toolbar
*/

View File

@@ -28,6 +28,7 @@ $defaultColor: #F1F1F1;
$defaultSideBarFontColor: #44A5FF;
$defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultWarningColor: rgb(215, 121, 118);
/**
* Toolbar
@@ -85,10 +86,12 @@ $modalTextColor: #333;
/**
* Chat
*/
$chatActionsSeparatorColor: rgb(173, 105, 112);
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
$chatInputSeparatorColor: #A4B8D1;
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
$chatLocalMessageBackgroundColor: rgb(4, 98, 178);
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
$chatRemoteMessageBackgroundColor: rgb(86, 101, 114);
$sidebarWidth: 375px;
/**
@@ -163,11 +166,93 @@ $watermarkHeight: 74px;
*/
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
$welcomePageBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
$welcomePageTitleColor: #fff;
$welcomePageHeaderBackground: none;
$welcomePageHeaderBackgroundSmall: none;
$welcomePageHeaderBackgroundPosition: none;
$welcomePageHeaderBackgroundRepeat: none;
$welcomePageHeaderBackgroundSize: none;
$welcomePageHeaderPaddingBottom: 0px;
$welcomePageHeaderTextMarginTop: 35px;
$welcomePageHeaderTextMarginBottom: 35px;
$welcomePageHeaderTextTitleMarginBottom: 16px;
$welcomePageHeaderTextTitleFontSize: 2.5rem;
$welcomePageHeaderTextTitleFontWeight: 500;
$welcomePageHeaderTextTitleLineHeight: 1.18;
$welcomePageHeaderTextTitleOpacity: 1;
$welcomePageHeaderTextDescriptionDisplay: inherit;
$welcomePageHeaderTextDescriptionFontSize: 1rem;
$welcomePageHeaderTextDescriptionFontWeight: 400;
$welcomePageHeaderTextDescriptionLineHeight: 24px;
$welcomePageHeaderTextDescriptionMarginBottom: 20px;
$welcomePageHeaderTextDescriptionAlignSelf: inherit;
$welcomePageEnterRoomWidth: 680px;
$welcomePageEnterRoomPadding: 25px 30px;
$welcomePageEnterRoomBorderRadius: 0px;
$welcomePageEnterRoomInputContainerPadding: 0 8px 5px 0px;
$welcomePageEnterRoomInputContainerBorderWidth: 0px 0px 2px 0px;
$welcomePageEnterRoomInputContainerBorderStyle: solid;
$welcomePageEnterRoomInputContainerBorderImage: linear-gradient(to right, #dee1e6, #fff) 1;
$welcomePageEnterRoomTitleDisplay: inherit;
$welcomePageTabContainerDisplay: flex;
$welcomePageTabContentDisplay: inherit;
$welcomePageTabButtonsDisplay: flex;
$welcomePageTabDisplay: block;
$welcomePageButtonWidth: 51px;
$welcomePageButtonMinWidth: inherit;
$welcomePageButtonFontSize: 14px;
$welcomePageButtonHeight: 35px;
$welcomePageButtonFontWeight: inherit;
$welcomePageButtonBorderRadius: 4px;
$welcomePageButtonLineHeight: 35px;
/**
* Deep-linking page variables.
*/
$deepLinkingMobileLogoHeight: 40px;
$deepLinkingMobileHeaderBackground: #f1f2f5;
$deepLinkingMobileLinkColor: inherit;
$deepLinkingMobileTextFontSize: inherit;
$deepLinkingMobileTextLineHeight: inherit;
$deepLinkingDialInConferenceIdMargin: 10px 0 10px 0;
$deepLinkingDialInConferenceIdPadding: inherit;
$deepLinkingDialInConferenceIdBackgroundColor: inherit;
$deepLinkingDialInConferenceIdBorderRadius: inherit;
$deepLinkingDialInConferenceNameFontSize: inherit;
$deepLinkingDialInConferenceNameLineHeight: inherit;
$deepLinkingDialInConferenceNameMarginBottom: none;
$deepLinkingDialInConferenceNameFontWeight: inherit;
$deepLinkingDialInConferenceDescriptionFontSize: 0.8em;
$deepLinkingDialInConferenceDescriptionLineHeight: inherit;
$deepLinkingDialInConferenceDescriptionMarginBottom: none;
$deepLinkingDialInConferencePinFontSize: inherit;
$deepLinkingDialInConferencePinLineHeight: inherit;
$depLinkingMobileHrefLineHeight: 2.2857142857142856em;
$deepLinkingMobileHrefFontWeight: bolder;
$deepLinkingMobileHrefFontSize: inherit;
$deepLinkingMobileButtonHeight: 2.2857142857142856em;
$deepLinkingMobileButtonLineHeight: 2.2857142857142856em;
$deepLinkingMobileButtonMargin: 18px auto 10px;
$deepLinkingMobileButtonWidth: auto;
$deepLinkingMobileButtonFontWeight: bold;
$deepLinkingMobileButtonFontSize: inherit;
$primaryDeepLinkingMobileButtonBorderRadius: inherit;

View File

@@ -4,7 +4,7 @@ body.welcome-page {
}
.welcome {
background-image: $welcomePageHeaderBackground;
background-image: $welcomePageBackground;
display: flex;
flex-direction: column;
font-family: $welcomePageFontFamily;
@@ -13,6 +13,11 @@ body.welcome-page {
position: relative;
.header {
background-image: $welcomePageHeaderBackground;
background-position: $welcomePageHeaderBackgroundPosition;
background-repeat: $welcomePageHeaderBackgroundRepeat;
background-size: $welcomePageHeaderBackgroundSize;
padding-bottom: $welcomePageHeaderPaddingBottom;
align-items: center;
display: flex;
flex-direction: column;
@@ -24,8 +29,8 @@ body.welcome-page {
.header-text {
display: flex;
flex-direction: column;
margin-top: $watermarkHeight + 35;
margin-bottom: 35px;
margin-top: $watermarkHeight + $welcomePageHeaderTextMarginTop;
margin-bottom: $welcomePageHeaderTextMarginBottom;
max-width: calc(100% - 40px);
width: 650px;
z-index: $zindex2;
@@ -33,41 +38,45 @@ body.welcome-page {
.header-text-title {
color: $welcomePageTitleColor;
font-size: 2.5rem;
font-weight: 500;
line-height: 1.18;
margin-bottom: 16px;
font-size: $welcomePageHeaderTextTitleFontSize;
font-weight: $welcomePageHeaderTextTitleFontWeight;
line-height: $welcomePageHeaderTextTitleLineHeight;
margin-bottom: $welcomePageHeaderTextTitleMarginBottom;
opacity: $welcomePageHeaderTextTitleOpacity;
}
.header-text-description {
display: $welcomePageHeaderTextDescriptionDisplay;
color: $welcomePageDescriptionColor;
font-size: 1rem;
font-weight: 400;
line-height: 24px;
margin-bottom: 20px;
font-size: $welcomePageHeaderTextDescriptionFontSize;
font-weight: $welcomePageHeaderTextDescriptionFontWeight;
line-height: $welcomePageHeaderTextDescriptionLineHeight;
margin-bottom: $welcomePageHeaderTextDescriptionMarginBottom;
align-self: $welcomePageHeaderTextDescriptionAlignSelf;
}
#enter_room {
display: flex;
align-items: center;
max-width: calc(100% - 40px);
width: 680px;
width: $welcomePageEnterRoomWidth;
z-index: $zindex2;
background-color: #fff;
padding: 25px 30px;
padding: $welcomePageEnterRoomPadding;
border-radius: $welcomePageEnterRoomBorderRadius;
.enter-room-input-container {
width: 100%;
padding-right: 8px;
padding-bottom: 5px;
padding: $welcomePageEnterRoomInputContainerPadding;
text-align: left;
color: #253858;
height: fit-content;
border-width: 0px 0px 2px 0px;
border-style: solid;
border-image: linear-gradient(to right, #dee1e6, #fff) 1;
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
border-image: $welcomePageEnterRoomInputContainerBorderImage;
.enter-room-title {
display: $welcomePageEnterRoomTitleDisplay;
font-size: 18px;
font-weight: bold;
padding-bottom: 5px;
@@ -94,10 +103,11 @@ body.welcome-page {
min-height: 354px;
width: 710px;
background: #75A7E7;
display: flex;
display: $welcomePageTabContainerDisplay;
flex-direction: column;
.tab-content{
display: $welcomePageTabContentDisplay;
margin: 5px 0px;
overflow: hidden;
flex-grow: 1;
@@ -111,13 +121,14 @@ body.welcome-page {
.tab-buttons {
font-size: 18px;
color: #FFFFFF;
display: flex;
display: $welcomePageTabButtonsDisplay;
flex-grow: 0;
flex-direction: row;
min-height: 54px;
width: 100%;
.tab {
display: $welcomePageTabDisplay;
text-align: center;
background: rgba(9,30,66,0.37);
height: 55px;
@@ -138,15 +149,17 @@ body.welcome-page {
}
.welcome-page-button {
width: 51px;
height: 35px;
font-size: 14px;
width: $welcomePageButtonWidth;
min-width: $welcomePageButtonMinWidth;
height: $welcomePageButtonHeight;
font-size: $welcomePageButtonFontSize;
font-weight: $welcomePageButtonFontWeight;
background: #0074E0;
border-radius: 4px;
border-radius: $welcomePageButtonBorderRadius;
color: #FFFFFF;
text-align: center;
vertical-align: middle;
line-height: 35px;
line-height: $welcomePageButtonLineHeight;
cursor: pointer;
}

View File

@@ -0,0 +1 @@
/** Insert custom CSS for any additional content in the welcome page settings toolbar **/

View File

@@ -19,7 +19,8 @@
}
a {
text-decoration: none
text-decoration: none;
color: $deepLinkingMobileLinkColor;
}
&__body {
@@ -41,6 +42,8 @@
&__text {
font-weight: bolder;
font-size: $deepLinkingMobileTextFontSize;
line-height: $deepLinkingMobileTextLineHeight;
padding: 10px 10px 0px 10px;
}
@@ -65,11 +68,28 @@
}
.dial-in-conference-id {
margin: 10px 0 10px 0;
margin: $deepLinkingDialInConferenceIdMargin;
padding: $deepLinkingDialInConferenceIdPadding;
background-color: $deepLinkingDialInConferenceIdBackgroundColor;
border-radius: $deepLinkingDialInConferenceIdBorderRadius;
}
.dial-in-conference-name {
font-size: $deepLinkingDialInConferenceNameFontSize;
line-height: $deepLinkingDialInConferenceNameLineHeight;
margin-bottom: $deepLinkingDialInConferenceNameMarginBottom;
font-weight: $deepLinkingDialInConferenceNameFontWeight;
}
.dial-in-conference-description {
font-size: 0.8em;
font-size: $deepLinkingDialInConferenceDescriptionFontSize;
line-height: $deepLinkingDialInConferenceDescriptionLineHeight;
margin-bottom: $deepLinkingDialInConferenceDescriptionMarginBottom;
}
.dial-in-conference-pin {
font-size: $deepLinkingDialInConferencePinFontSize;
line-height: $deepLinkingDialInConferencePinLineHeight;
}
.toll-free-list {
@@ -88,25 +108,27 @@
&__href {
height: 2.2857142857142856em;
line-height: 2.2857142857142856em;
line-height: $depLinkingMobileHrefLineHeight;
margin: 18px auto 20px;
max-width: 300px;
width: auto;
font-weight: bolder;
font-weight: $deepLinkingMobileHrefFontWeight;
font-size: $deepLinkingMobileHrefFontSize;
}
&__button {
border: 0;
height: 2.2857142857142856em;
line-height: 2.2857142857142856em;
margin: 18px auto 10px;
height: $deepLinkingMobileButtonHeight;
line-height: $deepLinkingMobileButtonLineHeight;
margin: $deepLinkingMobileButtonMargin;
padding: 0px 10px 0px 10px;
max-width: 300px;
width: auto;
width: $deepLinkingMobileButtonWidth;
@include border-radius(3px);
background-color: $unsupportedBrowserButtonBgColor;
color: #505F79;
font-weight: bold;
font-weight: $deepLinkingMobileButtonFontWeight;
font-size: $deepLinkingMobileButtonFontSize;
&:active {
background-color: $unsupportedBrowserButtonBgColor;
@@ -115,7 +137,7 @@
&_primary {
background-color: $primaryUnsupportedBrowserButtonBgColor;
color: #FFFFFF;
border-radius: $primaryDeepLinkingMobileButtonBorderRadius;
&:active {
background-color: $primaryUnsupportedBrowserButtonBgColor;
}

View File

@@ -32,6 +32,7 @@ $flagsImagePath: "../images/";
@import 'overlay/overlay';
@import 'inlay';
@import 'reload_overlay/reload_overlay';
@import 'mini_toolbox';
@import 'modals/desktop-picker/desktop-picker';
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';
@@ -44,6 +45,7 @@ $flagsImagePath: "../images/";
@import 'videolayout_default';
@import 'notice';
@import 'subject';
@import 'participants-count';
@import 'popup_menu';
@import 'recording';
@import 'login_menu';
@@ -51,6 +53,7 @@ $flagsImagePath: "../images/";
@import 'ringing/ringing';
@import 'welcome_page';
@import 'welcome_page_content';
@import 'welcome_page_settings_toolbar';
@import 'toolbars';
@import 'jquery.contextMenu';
@import 'keyboard-shortcuts';
@@ -81,5 +84,6 @@ $flagsImagePath: "../images/";
@import 'third-party-branding/google';
@import 'third-party-branding/microsoft';
@import 'avatar';
@import 'promotional-footer';
/* Modules END */

View File

@@ -63,6 +63,8 @@
width: -webkit-max-content;
word-break: break-all;
max-width: 400px;
display: flex;
align-items: center;
}
.info-dialog-dial-in {
@@ -86,6 +88,15 @@
cursor: inherit;
}
.info-dialog-url-icon {
display: inline-block;
margin-left: 5px;
svg {
cursor: pointer;
}
}
.info-dialog-title {
font-weight: bold;
margin-bottom: 10px;
@@ -214,4 +225,10 @@
-moz-user-select: text;
-webkit-user-select: text;
}
.info-dialog-url-text-unselectable {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
}

View File

@@ -19,29 +19,37 @@ server {
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
root /usr/share/jitsi-meet;
ssi on;
index index.html index.htm;
error_page 404 /static/404.html;
location /config.js {
location = /config.js {
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
}
location /external_api.js {
location = /external_api.js {
alias /usr/share/jitsi-meet/libs/external_api.min.js;
}
location ~ ^/([a-zA-Z0-9=\?]+)$ {
rewrite ^/(.*)$ / break;
}
location / {
ssi on;
#ensure all static content can always be found first
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
{
add_header 'Access-Control-Allow-Origin' '*';
alias /usr/share/jitsi-meet/$1/$2;
}
# BOSH
location /http-bind {
location = /http-bind {
proxy_pass http://localhost:5280/http-bind;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
}
location ~ ^/([^?&:’“]+)$ {
try_files $uri @root_path;
}
location @root_path {
rewrite ^/(.*)$ / break;
}
}

78
doc/development.md Normal file
View File

@@ -0,0 +1,78 @@
# Developing Jitsi Meet
## Building the sources
Node.js >= 10 and npm >= 6 are required.
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
```
To build the Jitsi Meet application, just type
```
make
```
### Working with the library sources (lib-jitsi-meet)
By default the library is build from its git repository sources. The default dependency path in package.json is :
```json
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
```
To work with local copy you must change the path to:
```json
"lib-jitsi-meet": "file:///Users/name/local-lib-jitsi-meet-copy",
```
To make the project you must force it to take the sources as 'npm update':
```
npm install lib-jitsi-meet --force && make
```
Or if you are making only changes to the library:
```
npm install lib-jitsi-meet --force && make deploy-lib-jitsi-meet
```
Alternative way is to use [npm link](https://docs.npmjs.com/cli/link).
It allows to link `lib-jitsi-meet` dependency to local source in few steps:
```bash
cd lib-jitsi-meet
#### create global symlink for lib-jitsi-meet package
npm link
cd ../jitsi-meet
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
npm link lib-jitsi-meet
```
After changes in local `lib-jitsi-meet` repository, you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link. It is no longer the case with version 6.x.
If you do not want to use local repository anymore you should run
```bash
cd jitsi-meet
npm unlink lib-jitsi-meet
npm install
```
### Running with webpack-dev-server for development
Use it at the CLI, type
```
make dev
```
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
```
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
make dev
```
The app should be running at https://localhost:8080/

View File

@@ -0,0 +1,14 @@
var subdomain = "<!--# echo var="subdomain" default="" -->";
if (subdomain) {
subdomain = subdomain.substr(0,subdomain.length-1).split('.').join('_').toLowerCase() + '.';
}
var config = {
hosts: {
domain: 'jitsi.example.com',
muc: 'conference.'+subdomain+'jitsi.example.com', // FIXME: use XEP-0030
focus: 'focus.jitsi.example.com',
},
useNicks: false,
bosh: '//jitsi.example.com/http-bind' // FIXME: use xep-0156 for that
};

View File

@@ -0,0 +1,67 @@
server {
listen 80;
server_name jitsi.example.com;
# set the root
root /srv/jitsi.example.com;
# ssi on with javascript for multidomain variables in config.js
ssi on;
ssi_types application/x-javascript application/javascript;
index index.html;
set $prefix "";
# BOSH
location /http-bind {
proxy_pass http://localhost:5280/http-bind;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
}
# xmpp websockets
location /xmpp-websocket {
proxy_pass http://localhost:5280/xmpp-websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
tcp_nodelay on;
}
location ~ ^/([^/?&:'"]+)$ {
try_files $uri @root_path;
}
location @root_path {
rewrite ^/(.*)$ / break;
}
location / {
ssi on;
}
location ~ ^/([^/?&:'"]+)/config.js$
{
set $subdomain "$1.";
set $subdir "$1/";
alias /etc/jitsi/meet/{{jitsi_meet_domain_name}}-config.js;
}
#Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
location ~ ^/([^/?&:'"]+)/(.*)$ {
set $subdomain "$1.";
set $subdir "$1/";
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
}
# BOSH for subdomains
location ~ ^/([^/?&:'"]+)/http-bind {
set $subdomain "$1.";
set $subdir "$1/";
set $prefix "$1";
rewrite ^/(.*)$ /http-bind;
}
}

View File

@@ -0,0 +1,220 @@
-- Prosody XMPP Server Configuration
--
-- Information on configuring Prosody can be found on our
-- website at http://prosody.im/doc/configure
--
-- Tip: You can check that the syntax of this file is correct
-- when you have finished by running: prosodyctl check config
-- If there are any errors, it will let you know what and where
-- they are, otherwise it will keep quiet.
--
-- Good luck, and happy Jabbering!
---------- Server-wide settings ----------
-- Settings in this section apply to the whole server and are the default settings
-- for any virtual hosts
-- This is a (by default, empty) list of accounts that are admins
-- for the server. Note that you must create the accounts separately
-- (see http://prosody.im/doc/creating_accounts for info)
-- Example: admins = { "user1@example.com", "user2@example.net" }
admins = { }
daemonize = true
cross_domain_bosh = true;
component_ports = { 5347 }
--component_interface = "192.168.0.10"
-- Enable use of libevent for better performance under high load
-- For more information see: http://prosody.im/doc/libevent
--use_libevent = true
-- This is the list of modules Prosody will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
-- Documentation on modules can be found at: http://prosody.im/doc/modules
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"dialback"; -- s2s dialback support
"disco"; -- Service discovery
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
-- Not essential, but recommended
"private"; -- Private XML storage (for room bookmarks, etc.)
"vcard"; -- Allow users to set vCards
-- These are commented by default as they have a performance impact
--"privacy"; -- Support privacy lists
"compression"; -- Stream compression (requires the lua-zlib package installed)
-- Nice to have
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"pep"; -- Enables users to publish their mood, activity, playing music and more
"register"; -- Allow users to register on this server using a client and change passwords
-- Admin interfaces
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
-- HTTP modules
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
--"http_files"; -- Serve static files from a directory over HTTP
-- Other specific functionality
--"groups"; -- Shared roster support
--"announce"; -- Send announcement to all online users
--"welcome"; -- Welcome users who register accounts
--"watchregistrations"; -- Alert admins of registrations
--"motd"; -- Send a message to users when they log in
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
-- jitsi
"smacks";
"carbons";
"mam";
"lastactivity";
"offline";
"pubsub";
"adhoc";
"websocket";
"http_altconnect";
-- include domain mapper as global level module
"muc_domain_mapper";
}
-- domain mapper options, must at least have domain base set to use the mapper
muc_mapper_domain_base = "jitsi.example.com";
-- These modules are auto-loaded, but should you want
-- to disable them then uncomment them here:
modules_disabled = {
-- "offline"; -- Store offline messages
-- "c2s"; -- Handle client connections
-- "s2s"; -- Handle server-to-server connections
}
-- Disable account creation by default, for security
-- For more information see http://prosody.im/doc/creating_accounts
allow_registration = false
-- These are the SSL/TLS-related settings. If you don't want
-- to use SSL/TLS, you may comment or remove this
ssl = {
key = "/etc/prosody/certs/localhost.key";
certificate = "/etc/prosody/certs/localhost.crt";
}
-- Force clients to use encrypted connections? This option will
-- prevent clients from authenticating unless they are using encryption.
-- c2s_require_encryption = true
-- Force certificate authentication for server-to-server connections?
-- This provides ideal security, but requires servers you communicate
-- with to support encryption AND present valid, trusted certificates.
-- NOTE: Your version of LuaSec must support certificate verification!
-- For more information see http://prosody.im/doc/s2s#security
-- s2s_secure_auth = false
-- Many servers don't support encryption or have invalid or self-signed
-- certificates. You can list domains here that will not be required to
-- authenticate using certificates. They will be authenticated using DNS.
--s2s_insecure_domains = { "gmail.com" }
-- Even if you leave s2s_secure_auth disabled, you can still require valid
-- certificates for some domains by specifying a list here.
--s2s_secure_domains = { "jabber.org" }
-- Required for init scripts and prosodyctl
pidfile = "/var/run/prosody/prosody.pid"
-- Select the authentication backend to use. The 'internal' providers
-- use Prosody's configured data storage to store the authentication data.
-- To allow Prosody to offer secure authentication mechanisms to clients, the
-- default provider stores passwords in plaintext. If you do not trust your
-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
-- for information about using the hashed backend.
-- authentication = "internal_plain"
authentication = "internal_hashed"
-- Select the storage backend to use. By default Prosody uses flat files
-- in its configured data directory, but it also supports more backends
-- through modules. An "sql" backend is included by default, but requires
-- additional dependencies. See http://prosody.im/doc/storage for more info.
--storage = "sql" -- Default is "internal"
-- For the "sql" backend, you can uncomment *one* of the below to configure:
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
-- Logging configuration
-- For advanced logging see http://prosody.im/doc/logging
log = {
info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
error = "/var/log/prosody/prosody.err";
"*syslog";
}
----------- Virtual hosts -----------
-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
-- Settings under each VirtualHost entry apply *only* to that host.
--VirtualHost "localhost"
VirtualHost "jitsi.example.com"
-- enabled = false -- Remove this line to enable this host
authentication = "anonymous"
-- Assign this host a certificate for TLS, otherwise it would use the one
-- set in the global section (if any).
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
-- use the global one.
ssl = {
key = "/var/lib/prosody/jitsi.example.com.key";
certificate = "/var/lib/prosody/jitsi.example.com.crt";
}
c2s_require_encryption = false
VirtualHost "auth.jitsi.example.com"
ssl = {
key = "/var/lib/prosody/auth.jitsi.example.com.key";
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
}
authentication = "internal_plain"
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.
-- For more information on components, see http://prosody.im/doc/components
---Set up a MUC (multi-user chat) room server on conference.example.com:
--Component "conference.example.com" "muc"
-- Set up a SOCKS5 bytestream proxy for server-proxied file transfers:
--Component "proxy.example.com" "proxy65"
---Set up an external component (default component port is 5347)
--
-- External components allow adding various services, such as gateways/
-- transports to other networks like ICQ, MSN and Yahoo. For more info
-- see: http://prosody.im/doc/components#adding_an_external_component
--
--Component "gateway.example.com"
-- component_secret = "password"
Component "conference.jitsi.example.com" "muc"
modules_enabled = { "muc_domain_mapper" }
Component "jitsi-videobridge.jitsi.example.com"
component_secret = "IfGaish6"

View File

@@ -210,6 +210,7 @@ Checkout and configure Jitsi Meet:
cd /srv
git clone https://github.com/jitsi/jitsi-meet.git
mv jitsi-meet/ jitsi.example.com
cd jitsi.example.com
npm install
make
```

3
images/user-groups.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33331 2C6.28101 2 7.09675 2.56499 7.46207 3.37651C7.00766 3.45023 6.58406 3.61583 6.21095 3.85361C6.04111 3.54356 5.71176 3.33333 5.33331 3.33333C4.78103 3.33333 4.33331 3.78105 4.33331 4.33333C4.33331 4.75895 4.59921 5.12246 4.97395 5.26682C4.77672 5.69245 4.66665 6.16671 4.66665 6.66667L4.66678 6.6967C3.12249 6.85332 2.66665 7.65415 2.66665 9.83333C2.66665 9.89666 2.66835 9.95222 2.67088 10H3.13441C2.977 10.3982 2.86114 10.8423 2.7841 11.3333H2.33331C1.66665 11.3333 1.33331 10.8333 1.33331 9.83333C1.33331 7.60559 1.88097 6.20498 3.39417 5.63152C3.14521 5.26038 2.99998 4.81382 2.99998 4.33333C2.99998 3.04467 4.04465 2 5.33331 2ZM9.78901 3.85361C9.4159 3.61583 8.9923 3.45023 8.53788 3.37651C8.90321 2.56499 9.71895 2 10.6666 2C11.9553 2 13 3.04467 13 4.33333C13 4.81382 12.8547 5.26038 12.6058 5.63152C14.119 6.20498 14.6666 7.60559 14.6666 9.83333C14.6666 10.8333 14.3333 11.3333 13.6666 11.3333H13.2159C13.1388 10.8423 13.023 10.3982 12.8656 10H13.3291C13.3316 9.95222 13.3333 9.89666 13.3333 9.83333C13.3333 7.65415 12.8775 6.85332 11.3332 6.6967L11.3333 6.66667C11.3333 6.1667 11.2232 5.69245 11.026 5.26682C11.4008 5.12246 11.6666 4.75895 11.6666 4.33333C11.6666 3.78105 11.2189 3.33333 10.6666 3.33333C10.2882 3.33333 9.95885 3.54356 9.78901 3.85361ZM4.49998 14.6667C3.7222 14.6667 3.33331 14.1111 3.33331 13C3.33331 10.4598 4.0062 8.8875 5.87888 8.28308C5.5366 7.83462 5.33331 7.27438 5.33331 6.66667C5.33331 5.19391 6.52722 4 7.99998 4C9.47274 4 10.6666 5.19391 10.6666 6.66667C10.6666 7.27438 10.4634 7.83462 10.1211 8.28308C11.9938 8.8875 12.6666 10.4598 12.6666 13C12.6666 14.1111 12.2778 14.6667 11.5 14.6667H4.49998ZM9.33331 6.66667C9.33331 7.40305 8.73636 8 7.99998 8C7.2636 8 6.66665 7.40305 6.66665 6.66667C6.66665 5.93029 7.2636 5.33333 7.99998 5.33333C8.73636 5.33333 9.33331 5.93029 9.33331 6.66667ZM11.3333 13C11.3333 13.1426 11.3252 13.2536 11.3152 13.3333H4.68477C4.67476 13.2536 4.66665 13.1426 4.66665 13C4.66665 10.1957 5.42021 9.33333 7.99998 9.33333C10.5797 9.33333 11.3333 10.1957 11.3333 13Z" fill="#5E6D7A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -148,6 +148,7 @@
<!--#include virtual="title.html" -->
<!--#include virtual="plugin.head.html" -->
<!--#include virtual="static/welcomePageAdditionalContent.html" -->
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
</head>
<body>
<div id="react"></div>

View File

@@ -27,6 +27,7 @@ var interfaceConfig = {
SHOW_DEEP_LINKING_IMAGE: false,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
DISPLAY_WELCOME_PAGE_CONTENT: true,
DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,
APP_NAME: 'Jitsi Meet',
NATIVE_APP_NAME: 'Jitsi Meet',
PROVIDER_NAME: 'Jitsi',
@@ -50,7 +51,7 @@ var interfaceConfig = {
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'videobackgroundblur'
'tileview', 'videobackgroundblur', 'download', 'help'
],
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
@@ -221,6 +222,13 @@ var interfaceConfig = {
* milliseconds, those notifications should remain displayed.
*/
// ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT: 15000,
// List of undocumented settings
/**
INDICATOR_FONT_SIZES
MOBILE_DYNAMIC_LINK
PHONE_NUMBER_REGEX
*/
};
/* eslint-enable no-unused-vars, no-var, max-len */

View File

@@ -17,9 +17,20 @@ target 'JitsiMeet' do
# React Native and its dependencies
#
pod 'FBLazyVector', :path => '../node_modules/react-native/Libraries/FBLazyVector/'
pod 'FBReactNativeSpec', :path => '../node_modules/react-native/Libraries/FBReactNativeSpec/'
pod 'RCTRequired', :path => '../node_modules/react-native/Libraries/RCTRequired/'
pod 'RCTTypeSafety', :path => '../node_modules/react-native/Libraries/TypeSafety/'
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/React'
pod 'React-DevSupport', :path => '../node_modules/react-native/React'
pod 'ReactCommon', :path => '../node_modules/react-native/ReactCommon', :subspecs => [
'turbomodule'
]
pod 'React-Core', :path => '../node_modules/react-native/', :subspecs => [
'CoreModulesHeaders',
'DevSupport',
'RCTWebSocket'
]
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
@@ -29,13 +40,12 @@ target 'JitsiMeet' do
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket'
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
@@ -52,7 +62,7 @@ target 'JitsiMeet' do
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'BVLinearGradient', :path => '../node_modules/react-native-linear-gradient'
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-community/async-storage'
pod 'RNGoogleSignin', :path => '../node_modules/react-native-google-signin'
pod 'RNGoogleSignin', :path => '../node_modules/@react-native-community/google-signin'
pod 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNSVG', :path => '../node_modules/react-native-svg'
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
@@ -71,6 +81,8 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'YES'
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
end
end
end

View File

@@ -1,5 +1,10 @@
PODS:
- Amplitude-iOS (4.0.4)
- AppAuth (1.2.0):
- AppAuth/Core (= 1.2.0)
- AppAuth/ExternalUserAgent (= 1.2.0)
- AppAuth/Core (1.2.0)
- AppAuth/ExternalUserAgent (1.2.0)
- boost-for-react-native (1.63.0)
- BVLinearGradient (2.5.6):
- React
@@ -10,6 +15,14 @@ PODS:
- Fabric (~> 1.9.0)
- DoubleConversion (1.1.6)
- Fabric (1.9.0)
- FBLazyVector (0.61.3)
- FBReactNativeSpec (0.61.3):
- Folly (= 2018.10.22.00)
- RCTRequired (= 0.61.3)
- RCTTypeSafety (= 0.61.3)
- React-Core (= 0.61.3)
- React-jsi (= 0.61.3)
- ReactCommon/turbomodule/core (= 0.61.3)
- Firebase/Core (5.18.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 5.7.0)
@@ -54,18 +67,10 @@ PODS:
- GoogleUtilities/Network (~> 5.2)
- "GoogleUtilities/NSData+zlib (~> 5.2)"
- nanopb (~> 0.3)
- GoogleSignIn (4.4.0):
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
- GoogleSignIn (5.0.1):
- AppAuth (~> 1.2)
- GTMAppAuth (~> 1.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleToolboxForMac/DebugUtils (2.2.0):
- GoogleToolboxForMac/Defines (= 2.2.0)
- GoogleToolboxForMac/Defines (2.2.0)
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.2.0)":
- GoogleToolboxForMac/DebugUtils (= 2.2.0)
- GoogleToolboxForMac/Defines (= 2.2.0)
- "GoogleToolboxForMac/NSString+URLArguments (= 2.2.0)"
- "GoogleToolboxForMac/NSString+URLArguments (2.2.0)"
- GoogleUtilities/AppDelegateSwizzler (5.4.1):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
@@ -84,58 +89,183 @@ PODS:
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (5.4.1):
- GoogleUtilities/Logger
- GTMSessionFetcher/Core (1.2.1)
- GTMAppAuth (1.0.0):
- AppAuth/Core (~> 1.0)
- GTMSessionFetcher (~> 1.1)
- GTMSessionFetcher (1.2.2):
- GTMSessionFetcher/Full (= 1.2.2)
- GTMSessionFetcher/Core (1.2.2)
- GTMSessionFetcher/Full (1.2.2):
- GTMSessionFetcher/Core (= 1.2.2)
- nanopb (0.3.901):
- nanopb/decode (= 0.3.901)
- nanopb/encode (= 0.3.901)
- nanopb/decode (0.3.901)
- nanopb/encode (0.3.901)
- ObjectiveDropboxOfficial (3.9.4)
- React (0.60.5):
- React-Core (= 0.60.5)
- React-DevSupport (= 0.60.5)
- React-RCTActionSheet (= 0.60.5)
- React-RCTAnimation (= 0.60.5)
- React-RCTBlob (= 0.60.5)
- React-RCTImage (= 0.60.5)
- React-RCTLinking (= 0.60.5)
- React-RCTNetwork (= 0.60.5)
- React-RCTSettings (= 0.60.5)
- React-RCTText (= 0.60.5)
- React-RCTVibration (= 0.60.5)
- React-RCTWebSocket (= 0.60.5)
- React-Core (0.60.5):
- RCTRequired (0.61.3)
- RCTTypeSafety (0.61.3):
- FBLazyVector (= 0.61.3)
- Folly (= 2018.10.22.00)
- React-cxxreact (= 0.60.5)
- React-jsiexecutor (= 0.60.5)
- yoga (= 0.60.5.React)
- React-cxxreact (0.60.5):
- RCTRequired (= 0.61.3)
- React-Core (= 0.61.3)
- React (0.61.3):
- React-Core (= 0.61.3)
- React-Core/DevSupport (= 0.61.3)
- React-Core/RCTWebSocket (= 0.61.3)
- React-RCTActionSheet (= 0.61.3)
- React-RCTAnimation (= 0.61.3)
- React-RCTBlob (= 0.61.3)
- React-RCTImage (= 0.61.3)
- React-RCTLinking (= 0.61.3)
- React-RCTNetwork (= 0.61.3)
- React-RCTSettings (= 0.61.3)
- React-RCTText (= 0.61.3)
- React-RCTVibration (= 0.61.3)
- React-Core (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/CoreModulesHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/Default (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/DevSupport (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default (= 0.61.3)
- React-Core/RCTWebSocket (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- React-jsinspector (= 0.61.3)
- Yoga
- React-Core/RCTActionSheetHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTAnimationHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTBlobHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTImageHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTLinkingHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTNetworkHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTSettingsHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTTextHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTVibrationHeaders (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-Core/RCTWebSocket (0.61.3):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsiexecutor (= 0.61.3)
- Yoga
- React-CoreModules (0.61.3):
- FBReactNativeSpec (= 0.61.3)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.61.3)
- React-Core/CoreModulesHeaders (= 0.61.3)
- React-RCTImage (= 0.61.3)
- ReactCommon/turbomodule/core (= 0.61.3)
- React-cxxreact (0.61.3):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-jsinspector (= 0.60.5)
- React-DevSupport (0.60.5):
- React-Core (= 0.60.5)
- React-RCTWebSocket (= 0.60.5)
- React-jsi (0.60.5):
- React-jsinspector (= 0.61.3)
- React-jsi (0.61.3):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-jsi/Default (= 0.60.5)
- React-jsi/Default (0.60.5):
- React-jsi/Default (= 0.61.3)
- React-jsi/Default (0.61.3):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-jsiexecutor (0.60.5):
- React-jsiexecutor (0.61.3):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-cxxreact (= 0.60.5)
- React-jsi (= 0.60.5)
- React-jsinspector (0.60.5)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- React-jsinspector (0.61.3)
- react-native-background-timer (2.1.1):
- React
- react-native-calendar-events (1.7.3):
@@ -146,35 +276,66 @@ PODS:
- React
- react-native-webrtc (1.75.0):
- React
- react-native-webview (5.8.1):
- react-native-webview (7.4.1):
- React
- React-RCTActionSheet (0.60.5):
- React-Core (= 0.60.5)
- React-RCTAnimation (0.60.5):
- React-Core (= 0.60.5)
- React-RCTBlob (0.60.5):
- React-Core (= 0.60.5)
- React-RCTNetwork (= 0.60.5)
- React-RCTWebSocket (= 0.60.5)
- React-RCTImage (0.60.5):
- React-Core (= 0.60.5)
- React-RCTNetwork (= 0.60.5)
- React-RCTLinking (0.60.5):
- React-Core (= 0.60.5)
- React-RCTNetwork (0.60.5):
- React-Core (= 0.60.5)
- React-RCTSettings (0.60.5):
- React-Core (= 0.60.5)
- React-RCTText (0.60.5):
- React-Core (= 0.60.5)
- React-RCTVibration (0.60.5):
- React-Core (= 0.60.5)
- React-RCTWebSocket (0.60.5):
- React-Core (= 0.60.5)
- React-RCTActionSheet (0.61.3):
- React-Core/RCTActionSheetHeaders (= 0.61.3)
- React-RCTAnimation (0.61.3):
- React-Core/RCTAnimationHeaders (= 0.61.3)
- React-RCTBlob (0.61.3):
- React-Core/RCTBlobHeaders (= 0.61.3)
- React-Core/RCTWebSocket (= 0.61.3)
- React-jsi (= 0.61.3)
- React-RCTNetwork (= 0.61.3)
- React-RCTImage (0.61.3):
- React-Core/RCTImageHeaders (= 0.61.3)
- React-RCTNetwork (= 0.61.3)
- React-RCTLinking (0.61.3):
- React-Core/RCTLinkingHeaders (= 0.61.3)
- React-RCTNetwork (0.61.3):
- React-Core/RCTNetworkHeaders (= 0.61.3)
- React-RCTSettings (0.61.3):
- React-Core/RCTSettingsHeaders (= 0.61.3)
- React-RCTText (0.61.3):
- React-Core/RCTTextHeaders (= 0.61.3)
- React-RCTVibration (0.61.3):
- React-Core/RCTVibrationHeaders (= 0.61.3)
- ReactCommon/jscallinvoker (0.61.3):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-cxxreact (= 0.61.3)
- ReactCommon/turbomodule (0.61.3):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-Core (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- ReactCommon/jscallinvoker (= 0.61.3)
- ReactCommon/turbomodule/core (= 0.61.3)
- ReactCommon/turbomodule/samples (= 0.61.3)
- ReactCommon/turbomodule/core (0.61.3):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-Core (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- ReactCommon/jscallinvoker (= 0.61.3)
- ReactCommon/turbomodule/samples (0.61.3):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-Core (= 0.61.3)
- React-cxxreact (= 0.61.3)
- React-jsi (= 0.61.3)
- ReactCommon/jscallinvoker (= 0.61.3)
- ReactCommon/turbomodule/core (= 0.61.3)
- RNCAsyncStorage (1.3.4):
- React
- RNGoogleSignin (2.0.0):
- GoogleSignIn (~> 4.4.0)
- RNGoogleSignin (3.0.1):
- GoogleSignIn (~> 5.0.0)
- React
- RNSound (0.11.0):
- React
@@ -185,7 +346,7 @@ PODS:
- React
- RNWatch (0.2.0):
- React
- yoga (0.60.5.React)
- Yoga (1.14.0)
DEPENDENCIES:
- Amplitude-iOS (~> 4.0.4)
@@ -194,15 +355,21 @@ DEPENDENCIES:
- Crashlytics (~> 3.12.0)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- Fabric (~> 1.9.0)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec/`)
- Firebase/Core (~> 5.18.0)
- Firebase/DynamicLinks (~> 5.18.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- ObjectiveDropboxOfficial (~> 3.9.4)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired/`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety/`)
- React (from `../node_modules/react-native/`)
- React-Core (from `../node_modules/react-native/React`)
- React-Core/CoreModulesHeaders (from `../node_modules/react-native/`)
- React-Core/DevSupport (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
- React-DevSupport (from `../node_modules/react-native/React`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
@@ -221,16 +388,16 @@ DEPENDENCIES:
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-RCTWebSocket (from `../node_modules/react-native/Libraries/WebSocket`)
- ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
- "RNGoogleSignin (from `../node_modules/@react-native-community/google-signin`)"
- RNSound (from `../node_modules/react-native-sound`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNWatch (from `../node_modules/react-native-watch-connectivity`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
https://github.com/CocoaPods/Specs.git:
- Amplitude-iOS
- boost-for-react-native
- CocoaLumberjack
@@ -243,30 +410,40 @@ SPEC REPOS:
- FirebaseDynamicLinks
- FirebaseInstanceID
- GoogleAppMeasurement
- GoogleSignIn
- GoogleToolboxForMac
- GoogleUtilities
- GTMSessionFetcher
- nanopb
- ObjectiveDropboxOfficial
trunk:
- AppAuth
- GoogleSignIn
- GTMAppAuth
- GTMSessionFetcher
EXTERNAL SOURCES:
BVLinearGradient:
:path: "../node_modules/react-native-linear-gradient"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector/"
FBReactNativeSpec:
:path: "../node_modules/react-native/Libraries/FBReactNativeSpec/"
Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
RCTRequired:
:path: "../node_modules/react-native/Libraries/RCTRequired/"
RCTTypeSafety:
:path: "../node_modules/react-native/Libraries/TypeSafety/"
React:
:path: "../node_modules/react-native/"
React-Core:
:path: "../node_modules/react-native/React"
:path: "../node_modules/react-native/"
React-CoreModules:
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
:path: "../node_modules/react-native/ReactCommon/cxxreact"
React-DevSupport:
:path: "../node_modules/react-native/React"
React-jsi:
:path: "../node_modules/react-native/ReactCommon/jsi"
React-jsiexecutor:
@@ -303,29 +480,32 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Text"
React-RCTVibration:
:path: "../node_modules/react-native/Libraries/Vibration"
React-RCTWebSocket:
:path: "../node_modules/react-native/Libraries/WebSocket"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNGoogleSignin:
:path: "../node_modules/react-native-google-signin"
:path: "../node_modules/@react-native-community/google-signin"
RNSound:
:path: "../node_modules/react-native-sound"
RNSVG:
:path: "../node_modules/react-native-svg"
RNWatch:
:path: "../node_modules/react-native-watch-connectivity"
yoga:
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
Amplitude-iOS: 2ad4d7270c99186236c1272a3a9425463b1ae1a7
AppAuth: bce82c76043657c99d91e7882e8a9e1a93650cd4
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
Crashlytics: 07fb167b1694128c1c9a5a5cc319b0e9c3ca0933
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
Fabric: f988e33c97f08930a413e08123064d2e5f68d655
FBLazyVector: 5bc5b1606fc9a7ac6956de049f6e30901ed31c49
FBReactNativeSpec: f7be9bcc5ce259f7c39509f3f4caf59020d11d4c
Firebase: 02f3281965c075426141a0ce1277e9de6649cab9
FirebaseAnalytics: 23851fe602c872130a2c5c55040b302120346cc2
FirebaseAnalyticsInterop: efbe45c8385ec626e29f9525e5ebd38520dfb6c1
@@ -335,42 +515,44 @@ SPEC CHECKSUMS:
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: 6cf307834da065863f9faf4c0de0a936d81dd832
GoogleSignIn: 7ff245e1a7b26d379099d3243a562f5747e23d39
GoogleToolboxForMac: ff31605b7d66400dcec09bed5861689aebadda4d
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
GoogleUtilities: 1e25823cbf46540b4284f6ef8e17b3a68ee12bbc
GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
React: 53c53c4d99097af47cf60594b8706b4e3321e722
React-Core: ba421f6b4f4cbe2fb17c0b6fc675f87622e78a64
React-cxxreact: 8384287780c4999351ad9b6e7a149d9ed10a2395
React-DevSupport: 197fb409737cff2c4f9986e77c220d7452cb9f9f
React-jsi: 4d8c9efb6312a9725b18d6fc818ffc103f60fec2
React-jsiexecutor: 90ad2f9db09513fc763bc757fdc3c4ff8bde2a30
React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4
RCTRequired: a72523286ea3381f97b28d87529c265baad3ad7d
RCTTypeSafety: e3cc0537400222250f0be37bd69f4b339d3c0a0f
React: 3dc877fc32548b0c7108ca7f301466f4956cbff8
React-Core: ca94e2e7d22cdcc266a405c4d2ad5e5675145776
React-CoreModules: aa415458b5d7dacd10ac1b324d679f6e17cd8685
React-cxxreact: bac5da3d62ee98abd3c1bf7338a7cc6205da7f69
React-jsi: 8bcf5836caa8a759c135ab9ef97f3e023a7b94af
React-jsiexecutor: ae078e9df9c65bcdcf68f9a17656657932d95528
React-jsinspector: a8939cc6909607eb5e8a5ecfff7c6226984e174d
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
react-native-calendar-events: 2fe35a9294af05de0ed819d3a1b5dac048d2c010
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-webrtc: c5e3d631179a933548a8e49bddbd8fad02586095
react-native-webview: a95842e3f351a6d2c8bc8bcc9eab689c7e7e5ad4
React-RCTActionSheet: b0f1ea83f4bf75fb966eae9bfc47b78c8d3efd90
React-RCTAnimation: 359ba1b5690b1e87cc173558a78e82d35919333e
React-RCTBlob: 5e2b55f76e9a1c7ae52b826923502ddc3238df24
React-RCTImage: f5f1c50922164e89bdda67bcd0153952a5cfe719
React-RCTLinking: d0ecbd791e9ddddc41fa1f66b0255de90e8ee1e9
React-RCTNetwork: e26946300b0ab7bb6c4a6348090e93fa21f33a9d
React-RCTSettings: d0d37cb521b7470c998595a44f05847777cc3f42
React-RCTText: b074d89033583d4f2eb5faf7ea2db3a13c7553a2
React-RCTVibration: 2105b2e0e2b66a6408fc69a46c8a7fb5b2fdade0
React-RCTWebSocket: cd932a16b7214898b6b7f788c8bddb3637246ac4
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
React-RCTActionSheet: 94671eef55b01a93be735605822ef712d5ea208e
React-RCTAnimation: 524ae33e73de9c0fe6501a7a4bda8e01d26499d9
React-RCTBlob: 5481c2db702f57207af7e7a9b32d90524b821b72
React-RCTImage: b472cc0606f8a7c1ac270d6ccc57123a09439a32
React-RCTLinking: 9cfc7bfdfda078489736695ac476de1f265b9f82
React-RCTNetwork: 967547e4eeac92e55d41573a82da7fff4003052a
React-RCTSettings: 6ab7911172056b5077dacd9240f057eeeb1b121b
React-RCTText: b8f895b94aa0e7778fef28d13f3d71eed4a10c3d
React-RCTVibration: 262588c97551b0b1c675468cda857466ba5af18f
ReactCommon: c2c63d9290b422ca6ad5b3663073a015dd892ae9
RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323
RNGoogleSignin: d030c6c6591db24c3cee649f64c7babf0a1699a0
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
RNSVG: aac12785382e8fd4f28d072fe640612e34914631
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
yoga: 312528f5bbbba37b4dcea5ef00e8b4033fdd9411
Yoga: 02036f6383c0008edb7ef0773a0e6beb6ce82bd1
PODFILE CHECKSUM: 86bb4d2bc94c6c76b971b9a33e5b2ced9bbfb09f
PODFILE CHECKSUM: 63c90b1d33cd96709fb72bad6be440ae9c3deecb
COCOAPODS: 1.7.2
COCOAPODS: 1.8.1

View File

@@ -41,8 +41,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -52,8 +50,8 @@
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -75,8 +73,6 @@
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

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

View File

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

View File

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

103
ios/ci/build-ipa.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
set -e
# Mandatory arguments with no default values provided:
# PR_REPO_SLUG - the Github name of the repo to be merged into the origin/master
# PR_BRANCH - the branch to be merged, if set to "master" no merge will happen
# IPA_DEPLOY_LOCATION - the location understandable by the "scp" command
# executed at the end of the script to deploy the output .ipa file
# LIB_JITSI_MEET_PKG (optional) - the npm package for lib-jitsi-meet which will
# be put in place of the current version in the package.json file.
#
# Other than that the script requires the following env variables to be set:
#
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
# in the script.
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
# place of "YOUR_TEAM_ID".
function echoAndExit1() {
echo $1
exit 1
}
if [ -z $PR_REPO_SLUG ]; then
echoAndExit1 "No PR_REPO_SLUG defined"
fi
if [ -z $PR_BRANCH ]; then
echoAndExit1 "No PR_BRANCH defined"
fi
if [ -z $IPA_DEPLOY_LOCATION ]; then
echoAndExit1 "No IPA_DEPLOY_LOCATION defined"
fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin master
git checkout master
git pull https://github.com/${PR_REPO_SLUG}.git $PR_BRANCH --no-edit
fi
# Link this lib-jitsi-meet checkout in jitsi-meet through the package.json
if [ ! -z ${LIB_JITSI_MEET_PKG} ];
then
echo "Adjusting lib-jitsi-meet package in package.json to ${LIB_JITSI_MEET_PKG}"
# escape for the sed
LIB_JITSI_MEET_PKG=$(echo $LIB_JITSI_MEET_PKG | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')
sed -i.bak -e "s/\"lib-jitsi-meet.*/\"lib-jitsi-meet\"\: \"${LIB_JITSI_MEET_PKG}\",/g" package.json
echo "Package.json lib-jitsi-meet line:"
grep lib-jitsi-meet package.json
else
echo "LIB_JITSI_MEET_PKG var not set - will not modify the package.json"
fi
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
# certificates
CERT_DIR="ios/ci/certs"
mkdir -p $CERT_DIR
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
chmod 0600 ${CERT_DIR}/id_rsa
ssh-add ${CERT_DIR}/id_rsa
npm install
# Ever since the Apple Watch app has been added the bitcode for WebRTC needs to be downloaded in order to build successfully
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
cd ios
pod install --repo-update --no-ansi
cd ..
mkdir -p /tmp/jitsi-meet/
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
if [ ! -z ${SCP_PROXY_HOST} ];
then
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
else
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
fi
rm -r /tmp/jitsi-meet/
rm -r $CERT_DIR

100
ios/ci/setup-certificates.sh Executable file
View File

@@ -0,0 +1,100 @@
# The script is based on tutorial written by Antonis Tsakiridis published at:
# https://medium.com/@atsakiridis/continuous-deployment-for-ios-using-travis-ci-55dcea342d9
#
# APPLE_CERT_URL - the URL pointing to Apple certificate (set to
# http://developer.apple.com/certificationauthority/AppleWWDRCA.cer by default)
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
# in the script.
# IOS_DEV_CERT_KEY_URL - URL pointing to provisioning profile certificate key
# file (development-key.p12.enc from the tutorial) encrypted with the
# $ENCRYPTION_PASSWORD.
# IOS_DEV_CERT_URL - URL pointing to provisioning profile certificate file
# (development-cert.cer.enc from the tutorial) encrypted with the
# $ENCRYPTION_PASSWORD.
# IOS_DEV_PROV_PROFILE_URL - URL pointing to provisioning profile file
# (profile-development-olympus.mobileprovision.enc from the tutorial) encrypted
# IOS_DEV_WATCH_PROV_PROFILE_URL - URL pointing to watch app provisioning profile file(encrypted).
# with the $ENCRYPTION_PASSWORD.
# IOS_SIGNING_CERT_PASSWORD - the password to the provisioning profile
# certificate key (used to open development-key.p12 from the tutorial).
function echoAndExit1() {
echo $1
exit 1
}
CERT_DIR=$1
if [ -z $CERT_DIR ]; then
echoAndExit1 "First argument must be certificates directory"
fi
if [ -z $APPLE_CERT_URL ]; then
APPLE_CERT_URL="http://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
fi
if [ -z $DEPLOY_SSH_CERT_URL ]; then
echoAndExit1 "DEPLOY_SSH_CERT_URL env var is not defined"
fi
if [ -z $ENCRYPTION_PASSWORD ]; then
echoAndExit1 "ENCRYPTION_PASSWORD env var is not defined"
fi
if [ -z $IOS_DEV_CERT_KEY_URL ]; then
echoAndExit1 "IOS_DEV_CERT_KEY_URL env var is not defined"
fi
if [ -z $IOS_DEV_CERT_URL ]; then
echoAndExit1 "IOS_DEV_CERT_URL env var is not defined"
fi
if [ -z $IOS_DEV_PROV_PROFILE_URL ]; then
echoAndExit1 "IOS_DEV_PROV_PROFILE_URL env var is not defined"
fi
if [ -z $IOS_DEV_WATCH_PROV_PROFILE_URL ]; then
echoAndExit1 "IOS_DEV_WATCH_PROV_PROFILE_URL env var is not defined"
fi
if [ -z $IOS_SIGNING_CERT_PASSWORD ]; then
echoAndExit1 "IOS_SIGNING_CERT_PASSWORD env var is not defined"
fi
# certificates
curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
security create-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security default-keychain -s ios-build.keychain
security unlock-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
echo "importing Apple cert"
security import ${CERT_DIR}/AppleWWDRCA.cer -k ios-build.keychain -A
echo "importing dev-cert.cer"
security import ${CERT_DIR}/dev-cert.cer -k ios-build.keychain -A
echo "importing dev-key.p12"
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
echo "will set-key-partition-list"
# Fix for OS X Sierra that hungs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
echo "done set-key-partition-list"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

View File

@@ -2,7 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -42,6 +42,7 @@
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; };
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AAC92317FFCD00290BEC /* LogUtils.h */; };
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */; };
DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -104,6 +105,7 @@
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = "<group>"; };
DE65AAC92317FFCD00290BEC /* LogUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogUtils.h; sourceTree = "<group>"; };
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetBaseLogHandler+Private.h"; sourceTree = "<group>"; };
DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetUserInfo.h; sourceTree = "<group>"; };
@@ -190,6 +192,7 @@
A4A934E7212F3AB8001E9388 /* dropbox */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
DE438CD82350934700DD541D /* JavaScriptSandbox.m */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
DEFE535321FB1BF800011A3A /* JitsiMeet.m */,
@@ -493,6 +496,7 @@
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */,
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -623,6 +627,7 @@
baseConfigurationReference = 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -634,15 +639,18 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -651,6 +659,7 @@
baseConfigurationReference = 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -662,14 +671,17 @@
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};

View File

@@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -81,7 +77,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "exec &gt; /tmp/${PROJECT_NAME}_archive.log 2&gt;&amp;1&#10;&#10;UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal&#10;&#10;if [ &quot;true&quot; == ${ALREADYINVOKED:-false} ]&#10;then&#10;echo &quot;RECURSION: Detected, stopping&quot;&#10;else&#10;export ALREADYINVOKED=&quot;true&quot;&#10;&#10;# make sure the output directory exists&#10;mkdir -p &quot;${UNIVERSAL_OUTPUTFOLDER}&quot;&#10;&#10;echo &quot;Building for iPhoneSimulator&quot;&#10;xcodebuild -workspace &quot;${WORKSPACE_PATH}&quot; -scheme &quot;${TARGET_NAME}&quot; -configuration ${CONFIGURATION} -sdk iphonesimulator -destination &apos;platform=iOS Simulator,name=iPhone 6&apos; ONLY_ACTIVE_ARCH=NO ARCHS=&apos;i386 x86_64&apos; BUILD_DIR=&quot;${BUILD_DIR}&quot; BUILD_ROOT=&quot;${BUILD_ROOT}&quot; ENABLE_BITCODE=YES OTHER_CFLAGS=&quot;-fembed-bitcode&quot; BITCODE_GENERATION_MODE=bitcode clean build&#10;&#10;# Step 1. Copy the framework structure (from iphoneos build) to the universal folder&#10;echo &quot;Copying to output folder&quot;&#10;cp -R &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FULL_PRODUCT_NAME}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/&quot;&#10;&#10;# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory&#10;SIMULATOR_SWIFT_MODULES_DIR=&quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/.&quot;&#10;if [ -d &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; ]; then&#10;cp -R &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule&quot;&#10;fi&#10;&#10;# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory&#10;echo &quot;Combining executables&quot;&#10;lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${EXECUTABLE_PATH}&quot;&#10;&#10;# Step 4. Create universal binaries for embedded frameworks&#10;#for SUB_FRAMEWORK in $( ls &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks&quot; ); do&#10;#BINARY_NAME=&quot;${SUB_FRAMEWORK%.*}&quot;&#10;#lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SUB_FRAMEWORK}/${BINARY_NAME}&quot; &quot;${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}&quot;&#10;#done&#10;&#10;# Step 5. Convenience step to copy the framework to the project&apos;s directory&#10;echo &quot;Copying to project dir&quot;&#10;yes | cp -Rf &quot;${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}&quot; &quot;${PROJECT_DIR}&quot;&#10;&#10;fi">
scriptText = "exec &gt; /tmp/${PROJECT_NAME}_archive.log 2&gt;&amp;1&#10;&#10;UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal&#10;&#10;if [ &quot;true&quot; == ${ALREADYINVOKED:-false} ]&#10;then&#10;echo &quot;RECURSION: Detected, stopping&quot;&#10;else&#10;export ALREADYINVOKED=&quot;true&quot;&#10;&#10;# make sure the output directory exists&#10;mkdir -p &quot;${UNIVERSAL_OUTPUTFOLDER}&quot;&#10;&#10;echo &quot;Building for iPhoneSimulator&quot;&#10;xcodebuild -workspace &quot;${WORKSPACE_PATH}&quot; -scheme &quot;${TARGET_NAME}&quot; -configuration ${CONFIGURATION} -sdk iphonesimulator -destination &apos;platform=iOS Simulator,name=iPhone 8&apos; ONLY_ACTIVE_ARCH=NO ARCHS=&apos;i386 x86_64&apos; BUILD_DIR=&quot;${BUILD_DIR}&quot; BUILD_ROOT=&quot;${BUILD_ROOT}&quot; ENABLE_BITCODE=YES OTHER_CFLAGS=&quot;-fembed-bitcode&quot; BITCODE_GENERATION_MODE=bitcode build&#10;&#10;# Step 1. Copy the framework structure (from iphoneos build) to the universal folder&#10;echo &quot;Copying to output folder&quot;&#10;cp -R &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FULL_PRODUCT_NAME}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/&quot;&#10;&#10;# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory&#10;SIMULATOR_SWIFT_MODULES_DIR=&quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/.&quot;&#10;if [ -d &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; ]; then&#10;cp -R &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule&quot;&#10;fi&#10;&#10;# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory&#10;echo &quot;Combining executables&quot;&#10;lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${EXECUTABLE_PATH}&quot;&#10;&#10;# Step 4. Create universal binaries for embedded frameworks&#10;#for SUB_FRAMEWORK in $( ls &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks&quot; ); do&#10;#BINARY_NAME=&quot;${SUB_FRAMEWORK%.*}&quot;&#10;#lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SUB_FRAMEWORK}/${BINARY_NAME}&quot; &quot;${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}&quot;&#10;#done&#10;&#10;# Step 5. Convenience step to copy the framework to the project&apos;s directory&#10;echo &quot;Copying to project dir&quot;&#10;yes | cp -Rf &quot;${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}&quot; &quot;${PROJECT_DIR}&quot;&#10;&#10;fi&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

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

View File

@@ -0,0 +1,55 @@
/*
* 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.
*/
@import JavaScriptCore;
#import <React/RCTBridgeModule.h>
@interface JavaScriptSandbox : NSObject<RCTBridgeModule>
@end
@implementation JavaScriptSandbox
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return NO;
}
#pragma mark - Exported methods
RCT_EXPORT_METHOD(evaluate:(NSString *)code
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
__block BOOL hasError = NO;
JSContext *ctx = [[JSContext alloc] init];
ctx.exceptionHandler = ^(JSContext *context, JSValue *exception) {
hasError = YES;
reject(@"evaluate", [exception toString], nil);
};
JSValue *ret = [ctx evaluateScript:code];
if (!hasError) {
NSString *result = [ret toString];
if (result == nil) {
reject(@"evaluate", @"Error in string coercion", nil);
} else {
resolve(result);
}
}
}
@end

View File

@@ -37,7 +37,6 @@
* List of domains used for universal linking.
*/
@property (copy, nonatomic, nullable) NSArray<NSString *> *universalLinkDomains;
/**
* Default conference options used for all conferences. These options will be merged
* with those passed to JitsiMeetView.join when joining a conference.

View File

@@ -24,6 +24,7 @@
#import "ReactUtils.h"
#import <RNGoogleSignin/RNGoogleSignin.h>
#import <WebRTC/RTCLogging.h>
@implementation JitsiMeet {
@@ -54,6 +55,11 @@
// Register a log handler for React.
registerReactLogHandler();
#if 0
// Enable WebRTC logs
RTCSetMinDebugLogLevel(RTCLoggingSeverityInfo);
#endif
}
return self;
@@ -90,8 +96,7 @@
if ([RNGoogleSignin application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]) {
options:options]) {
return YES;
}
@@ -179,6 +184,15 @@
return _universalLinkDomains ? _universalLinkDomains : @[];
}
- (void)setDefaultConferenceOptions:(JitsiMeetConferenceOptions *)defaultConferenceOptions {
if (defaultConferenceOptions != nil && _defaultConferenceOptions.room != nil) {
@throw [NSException exceptionWithName:@"RuntimeError"
reason:@"'room' must be null in the default conference options"
userInfo:nil];
}
_defaultConferenceOptions = defaultConferenceOptions;
}
#pragma mark - Private API methods
- (NSDictionary *)getDefaultProps {

View File

@@ -26,7 +26,9 @@
*/
__attribute__((constructor))
static void initializeLogger() {
[DDLog addLogger:[DDOSLogger sharedInstance]];
NSString *mainBundleId = [NSBundle mainBundle].bundleIdentifier;
DDOSLogger *osLogger = [[DDOSLogger alloc] initWithSubsystem:mainBundleId category:@"JitsiMeetSDK"];
[DDLog addLogger:osLogger];
}
+ (void)addHandler:(JitsiMeetBaseLogHandler *)handler {

View File

@@ -37,6 +37,7 @@ set -e
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
# place of "YOUR_TEAM_ID".
# Travis will not print the last echo if there's no sleep 1
function echoSleepAndExit1() {
echo $1
@@ -57,10 +58,6 @@ if [ -z $IPA_DEPLOY_LOCATION ]; then
echoSleepAndExit1 "No IPA_DEPLOY_LOCATION defined"
fi
if [ -z $APPLE_CERT_URL ]; then
APPLE_CERT_URL="http://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
@@ -88,47 +85,17 @@ fi
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
# certificates
#certificates
CERT_DIR="ios/travis-ci/certs"
mkdir $CERT_DIR
mkdir -p $CERT_DIR
./ios/ci/setup-certificates.sh $CERT_DIR
curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
chmod 0600 ${CERT_DIR}/id_rsa
security create-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security default-keychain -s ios-build.keychain
security unlock-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
echo "importing Apple cert"
security import ${CERT_DIR}/AppleWWDRCA.cer -k ios-build.keychain -A
echo "importing dev-cert.cer"
security import ${CERT_DIR}/dev-cert.cer -k ios-build.keychain -A
echo "importing dev-key.p12"
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
echo "will set-key-partition-list"
# Fix for OS X Sierra that hungs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
echo "done set-key-partition-list"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
ssh-add ${CERT_DIR}/id_rsa
npm install
@@ -136,24 +103,21 @@ npm install
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
cd ios
pod update
pod install
pod install --repo-update --no-ansi
cd ..
mkdir -p /tmp/jitsi-meet/
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/travis-ci/build-ipa.plist.template > ios/travis-ci/build-ipa.plist
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/travis-ci/build-ipa.plist
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
ssh-add ${CERT_DIR}/id_rsa
if [ ! -z ${SCP_PROXY_HOST} ];
then
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"

View File

@@ -1,27 +1,31 @@
{
"en": "Английски",
"af": "Африканс",
"az": "Азербайджански",
"bg": "Български",
"ca": "Каталонски",
"cs": "Чешки",
"de": "Немски",
"el": "Гръцки",
"enGB": "Английски (Великобритания)",
"eo": "Есперанто",
"es": "Испански",
"esUS": "Испански (Латинска Америка)",
"fi": "Фински",
"fr": "Френски",
"frCA": "Френски (Канада)",
"hr": "Хърватски",
"hy": "Арменски",
"it": "Италиански",
"ja": "Японски",
"ko": "Корейски",
"nb": "Норвежки букмол",
"nl": "Нидерландски",
"oc": "Окситански",
"pl": "Полски",
"ptBR": "Португалски (Бразилия)",
"ru": "Руски",
"sk": "Словашки",
"sl": "Словенски",
"sv": "Шведски",
"tr": "Турски",
"vi": "Виетнамски",
"zhCN": "Китайски (Китай)"
"zhCN": "Китайски (Китай)",
"zhTW": "Тайвански"
}

View File

@@ -1,27 +1,31 @@
{
"en": "Angol",
"af": "",
"az": "",
"bg": "Bolgár",
"cs": "",
"de": "Német",
"el": "",
"eo": "Eszperantó",
"es": "Spanyol",
"fr": "Francia",
"hy": "Örmény",
"it": "Olasz",
"ja": "",
"ko": "",
"nb": "Norvég bokmal",
"oc": "Okszitán",
"pl": "Lengyel",
"ptBR": "Portugál (Brazil)",
"ru": "Orosz",
"sk": "Szlovák",
"sl": "Szlovén",
"sv": "Svéd",
"tr": "Török",
"vi": "",
"zhCN": "Kínai (Kína)"
"en": "angol",
"af": "afrikaans",
"bg": "bolgár",
"ca": "katalán",
"cs": "cseh",
"de": "német",
"el": "görög",
"enGB": "angol (Egyesült Királyság)",
"eo": "eszperantó",
"es": "spanyol",
"esUS": "spanyol (Latin-Amerika)",
"fi": "finn",
"fr": "francia",
"frCA": "francia (kanadai)",
"hr": "horvát",
"hy": "örmény",
"it": "olasz",
"ja": "japán",
"ko": "koreai",
"nl": "holland",
"oc": "okszitán",
"pl": "lengyel",
"ptBR": "portugál (Brazil)",
"ru": "orosz",
"sv": "svéd",
"tr": "török",
"vi": "vietnámi",
"zhCN": "kínai (Kína)",
"zhTW": "kínai (Tajvan)"
}

View File

@@ -2,23 +2,23 @@
"en": "Anglés",
"af": "Afrikaans",
"bg": "Bulgar",
"ca": "",
"ca": "Catalan",
"cs": "Chèc",
"de": "Aleman",
"el": "Grèc",
"enGB": "",
"enGB": "Anglés (Reialme Unit)",
"eo": "Esperanto",
"es": "Castelhan",
"esUS": "",
"fi": "",
"esUS": "Espanhòl (America latina)",
"fi": "Finés",
"fr": "Francés",
"frCA": "",
"hr": "",
"frCA": "Francés (Canadian)",
"hr": "Croat",
"hy": "Armenian",
"it": "Italian",
"ja": "Japonés",
"ko": "Corean",
"nl": "",
"nl": "Neerlandés",
"oc": "Occitan",
"pl": "Polonés",
"ptBR": "Portugués (Brasil)",
@@ -27,5 +27,5 @@
"tr": "Turc",
"vi": "Vietnamian",
"zhCN": "Chinés (China)",
"zhTW": ""
"zhTW": "Chinés (Taiwan)"
}

View File

@@ -1,27 +1,31 @@
{
"en": "Anglik",
"af": "",
"az": "Azerski",
"en": "Angielski",
"af": "Afrykanerski",
"bg": "Bułgarski",
"ca": "",
"cs": "Czeski",
"de": "Niemiecki",
"el": "Grecki",
"enGB": "",
"eo": "Esperanto",
"es": "Hiszpański",
"esUS": "",
"fi": "",
"fr": "Francuski",
"frCA": "",
"hr": "",
"hy": "Ormiański",
"it": "Włoski",
"ja": "Japoński",
"ko": "Koreański",
"nb": "Norweski Bokmal",
"nl": "",
"oc": "Oksytański",
"pl": "Polski",
"ptBR": "portugalski (brazylijski)",
"ptBR": "Portugalski (brazylijski)",
"ru": "Rosyjski",
"sk": "Słowacki",
"sl": "Słoweński",
"sv": "Szwedzki",
"tr": "Turecki",
"vi": "Wietnamski",
"zhCN": "Chiński (Chiny)"
"zhCN": "Chiński (Chiny)",
"zhTW": ""
}

View File

@@ -1,27 +1,31 @@
{
"en": "Inglês",
"af": "Africâner",
"az": "Azerbaijanês",
"bg": "Búlgaro",
"ca": "",
"cs": "Checo",
"de": "Alemão",
"el": "Grego",
"enGB": "",
"eo": "Esperanto",
"es": "Espanhol",
"esUS": "",
"fi": "",
"fr": "Francês",
"frCA": "",
"hr": "",
"hy": "Armênio",
"it": "Italiano",
"ja": "Japonês",
"ko": "Coreano",
"nb": "Bokmal norueguês",
"nl": "",
"oc": "Occitano",
"pl": "Polonês",
"ptBR": "Português (Brasil)",
"ru": "Russo",
"sk": "Eslovaco",
"sl": "Esloveno",
"sv": "Sueco",
"tr": "Turco",
"vi": "Vietnamita",
"zhCN": "Chinês (China)"
"zhCN": "Chinês (China)",
"zhTW": ""
}

View File

@@ -14,6 +14,7 @@
"fr": "French",
"frCA": "French (Canadian)",
"hr": "Croatian",
"hu": "Hungarian",
"hy": "Armenian",
"it": "Italian",
"ja": "Japanese",

View File

@@ -4,8 +4,8 @@
"countryNotSupported": "Желаната дестинация не се поддържа.",
"countryReminder": "Международно обаждане? Започнете номера с международният код!",
"disabled": "Не можете да каните хора.",
"failedToAdd": "",
"footerText": "Изходящиите разговори не са разрешени.",
"failedToAdd": "Неуспешно добавяне на участници",
"footerText": "Изходящите разговори не са разрешени.",
"loading": "Търсене на хора и телефонни номера.",
"loadingNumber": "Валидиране на номера",
"loadingPeople": "Търсене на хора",
@@ -13,48 +13,49 @@
"noValidNumbers": "Моля въведете телефонен номер",
"searchNumbers": "Добавяне на номера",
"searchPeople": "Търсене на хора",
"searchPeopleAndNumbers": "",
"telephone": "",
"title": ""
"searchPeopleAndNumbers": "Търсене на участници или добавяне с телефони номера",
"telephone": "Телефон: {{number}}",
"title": "Добавяне на участници в срещата"
},
"audioDevices": {
"bluetooth": "",
"bluetooth": "Bluetooth",
"headphones": "Слушалки",
"phone": "Телефон",
"speaker": "Говорещ"
"speaker": "Говорещ",
"none": "Няма налични устройства за звук"
},
"audioOnly": {
"audioOnly": "Само звук"
"audioOnly": "Нисък дебит"
},
"calendarSync": {
"addMeetingURL": "",
"confirmAddLink": "",
"addMeetingURL": "Добавяне на връзка за среща",
"confirmAddLink": "Искате ли да добавите връзка към това събитие?",
"error": {
"appConfiguration": "",
"generic": "",
"notSignedIn": ""
"appConfiguration": "Интеграцията с календара не е настроена.",
"generic": "Грешка, моля проверете настройката за календара или го обновете.",
"notSignedIn": "Грешка при идентификация за изтегляне на събития. Моля проверете настройките на календара и опитайте отново."
},
"join": "",
"joinTooltip": "",
"nextMeeting": "",
"noEvents": "",
"ongoingMeeting": "",
"permissionButton": "",
"permissionMessage": "",
"refresh": "",
"today": ""
"join": "Влизане",
"joinTooltip": "Влизане в срещата",
"nextMeeting": "следваща среща",
"noEvents": "Няма насрочени бъдещи събития.",
"ongoingMeeting": "настояща среща",
"permissionButton": "Отваряне на настройки",
"permissionMessage": "За показване на срещите ви е нужно позволение за ползване на календара.",
"refresh": "Обновяване на календара",
"today": "Днес"
},
"chat": {
"error": "",
"messagebox": "",
"error": "Грешка: вашето съобщение \"{{originalText}}\" не е бе изпратено. Поради: {{error}}",
"messagebox": "Въведете съобщение",
"nickname": {
"popover": "Избор на име",
"title": ""
"title": "Въведете име за да обменяте съобщения"
},
"title": ""
"title": "Текстови съобщения"
},
"connectingOverlay": {
"joiningRoom": ""
"joiningRoom": "Свързване с вашата среща..."
},
"connection": {
"ATTACHED": "Прикрепен",
@@ -72,8 +73,8 @@
"address": "Адрес:",
"bandwidth": "Предполагаема скорост:",
"bitrate": "Скорост:",
"bridgeCount": "",
"connectedTo": "",
"bridgeCount": "Брой сървъри:",
"connectedTo": "Свързан към:",
"framerate": "Кадри в секунда:",
"less": "Скриване",
"localaddress": "Локален адрес:",
@@ -96,26 +97,25 @@
"resolution": "Резолюция:",
"status": "Връзка:",
"transport": "Транспорт:",
"transport_plural": "Транспорти:",
"turn": " (обръщане)"
"transport_plural": "Транспорти:"
},
"dateUtils": {
"earlier": "",
"today": "",
"yesterday": ""
"earlier": "По-рано",
"today": "Днес",
"yesterday": "Вчера"
},
"deepLinking": {
"appNotInstalled": "",
"description": "",
"descriptionWithoutWeb": "",
"downloadApp": "Сваляне не приложението",
"launchWebButton": "",
"openApp": "",
"title": "",
"tryAgainButton": ""
"appNotInstalled": "Имате нужда от мобилното приложение {{app}} за влизане в тази среща от телефона.",
"description": "Нищо не се случва? Опитахме се да заредим срещата в приложението {{app}}. Пробвайте отново или влезте чрез уеб приложението {{app}}.",
"descriptionWithoutWeb": "Нищо не се случва? Опитахме се да заредим срещата в приложението {{app}}.",
"downloadApp": "Свалете приложението",
"launchWebButton": "Заредете уеб страницата",
"openApp": "Продължете към приложението",
"title": "Зареждане на срещата в {{app}}...",
"tryAgainButton": "Пробвайте отново"
},
"\u0005deepLinking": {},
"defaultLink": "напр. {{url}}",
"defaultNickname": "напр. Иван Иванов",
"deviceError": {
"cameraError": "Камерата е недостъпна",
"cameraPermission": "Грешка при получаване на разрешение за достъп до камерата",
@@ -126,14 +126,14 @@
"noPermission": "Не е получено разрешение",
"previewUnavailable": "Няма възможност за преглед",
"selectADevice": "Изберете устройство",
"testAudio": ""
"testAudio": "Пусни пробен звук"
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Излъчване на живо"
},
"allow": "Разрешаване",
"alreadySharedVideoMsg": "",
"alreadySharedVideoMsg": "Друг участник вече е споделил видео. Тази среща позволява само едно споделено видео.",
"alreadySharedVideoTitle": "Разрешено е споделянето само на едно видео в даден момент",
"applicationWindow": "Прозореца на програмата",
"Back": "Назад",
@@ -150,8 +150,8 @@
"conferenceDisconnectTitle": "Връзката се разпадна.",
"conferenceReloadMsg": "Опитваме се да оправим нещата. Повторно свързване след {{seconds}} сек…",
"conferenceReloadTitle": "За съжаление, нещо се обърка.",
"confirm": "",
"confirmNo": "",
"confirm": "Потвърждение",
"confirmNo": "Не",
"confirmYes": "Да",
"connectError": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията.",
"connectErrorWithMsg": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията: {{msg}}",
@@ -159,66 +159,66 @@
"contactSupport": "Връзка с отдела по поддръжка",
"copy": "Копиране",
"dismiss": "Отхвърляне",
"displayNameRequired": "",
"displayNameRequired": "Здравей! Как се казваш?",
"done": "Готово",
"enterDisplayName": "",
"enterDisplayName": "Моля въведете вашето име",
"error": "Грешка",
"externalInstallationMsg": "Трябва да инсталирате разширението за споделяне на екрана.",
"externalInstallationTitle": "Нужно е разширение",
"goToStore": "Към магазина в Интернет",
"gracefulShutdown": "Услугата временно не е достъпна поради профилактика. Моля опитайте по-късно.",
"IamHost": "Аз съм домакина",
"incorrectRoomLockPassword": "",
"incorrectRoomLockPassword": "Грешна парола",
"incorrectPassword": "Неправилно потребителско име или парола",
"inlineInstallationMsg": "Трябва да инсталирате разширението за споделяне на екрана.",
"inlineInstallExtension": "Инсталиране сега",
"internalError": "Опа! Нещо се обърка. Възникна следната грешка: {{error}}",
"internalErrorTitle": "Вътрешна грешка",
"kickMessage": "",
"kickParticipantButton": "",
"kickParticipantDialog": "",
"kickParticipantTitle": "",
"kickTitle": "",
"kickMessage": "Може да се свържете с {{participantDisplayName}} за повече подробности.",
"kickParticipantButton": "Изгони",
"kickParticipantDialog": "Сигурни ли сте че искате да изгоните участника?",
"kickParticipantTitle": "Изгонване на този участник?",
"kickTitle": "Ауч! {{participantDisplayName}} ви изгони от тази среща",
"liveStreaming": "Излъчване на живо",
"liveStreamingDisabledForGuestTooltip": "",
"liveStreamingDisabledTooltip": "",
"liveStreamingDisabledForGuestTooltip": "Гостите не могат да стартират излъчване на живо.",
"liveStreamingDisabledTooltip": "Излъчването на живо е деактивирано.",
"lockMessage": "Неуспешно заключване на конференцията.",
"lockRoom": "",
"lockRoom": "Добавяне $t(lockRoomPasswordUppercase) за срещата",
"lockTitle": "Неуспешно заключване",
"logoutQuestion": "Сигурни ли сте, че искате да излезете и да прекъснете конференцията?",
"logoutTitle": "Изход",
"maxUsersLimitReached": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "Лимитът за максимален брой участници бе достигнат. Капацитета на срещата е запълнен. Моля свържете се с организатора или опитайте по-късно!",
"maxUsersLimitReachedTitle": "Достигнат е лимита за максимален брой участници",
"micConstraintFailedError": "Микрофонът Ви не покрива някои от изискванията.",
"micNotFoundError": "Не е открит микрофон.",
"micNotSendingData": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "Пуснете микрофона си от системните настройки на компютъра ви.",
"micNotSendingDataTitle": "Микрофона ви е спрян от системните настройки",
"micPermissionDeniedError": "Не сте дали разрешение за използване на микрофона. Ще можете да се присъедините в беседата, но другите няма да Ви чуват. Използвайте бутона с камерата в адресната лента, за да оправите това.",
"micUnknownError": "Не възможен достъп до микрофона по неясна причина.",
"micUnknownError": "Невъзможен достъп до микрофона по неясна причина.",
"muteParticipantBody": "Вие няма да можете да спрете заглушаването на участника, но той ще може да го направи по всяко време.",
"muteParticipantButton": "Изключи микрофона",
"muteParticipantDialog": "",
"muteParticipantTitle": "",
"muteParticipantDialog": "Сигурни ли сте че искате да заглушите този участник? Няма да можете да пуснете обратно звука му, но участника ще може да направи това сам.",
"muteParticipantTitle": "Спиране звука на участник?",
"Ok": "Готово",
"passwordLabel": "",
"passwordNotSupported": "Задаването на парола за срещата не се поддържа.",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"passwordLabel": "Парола",
"passwordNotSupported": "Задаването на $t(lockRoomPassword) за срещата не се поддържа.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) не се поддържа",
"passwordRequired": "Изисква се $t(lockRoomPassword) ",
"popupError": "Браузърът Ви блокира изскачащите прозорци от този уеб сайт. Моля, разрешете изскачащите прозорци от настройките за сигурност на браузъра си и след това опитайте отново.",
"popupErrorTitle": "Блокиран изскачащ прозорец",
"recording": "Запис",
"recordingDisabledForGuestTooltip": "",
"recordingDisabledTooltip": "",
"recordingDisabledForGuestTooltip": "Гостите не могат да стартират запис",
"recordingDisabledTooltip": "Стартирането на запис е спряно",
"rejoinNow": "Повторно присъединяване сега",
"remoteControlAllowedMessage": "{{user}} прие заявката Ви за отдалечено управление!",
"remoteControlDeniedMessage": "{{user}} отказа заявката Ви за отдалечено управление!",
"remoteControlErrorMessage": "Възникна грешка при опита за искана на разрешение за отдалечено управление от {{user}}!",
"remoteControlErrorMessage": "Възникна грешка при опита за искане на разрешение за отдалечено управление от {{user}}!",
"remoteControlRequestMessage": "Ще позволите ли на {{user}} да управлява отдалечено компютъра Ви?",
"remoteControlShareScreenWarning": "Ако натиснете „Разрешаване“, ще споделите екрана си!",
"remoteControlStopMessage": "Сесията за отдалечено управление приключи!",
"remoteControlTitle": "Отдалечено управление на компютъра",
"Remove": "Премахване",
"removePassword": "",
"removePassword": "Премахване на $t(lockRoomPassword)",
"removeSharedVideoMsg": "Наистина ли искате да премахнете споделеното си видео?",
"removeSharedVideoTitle": "Край на споделянето на видео",
"reservationError": "Грешка в системата за резервации",
@@ -226,8 +226,8 @@
"retry": "Повторен опит",
"screenSharingFailedToInstall": "Опа! Разширението за споделяне на екрана не успя да се инсталира.",
"screenSharingFailedToInstallTitle": "Разширението за споделяне на екрана не успя да се инсталира",
"screenSharingFirefoxPermissionDeniedError": "",
"screenSharingFirefoxPermissionDeniedTitle": "",
"screenSharingFirefoxPermissionDeniedError": "Нещо се обърка докато се опитвахме да споделим екрана. Моля уверете се че сте дали права за това.",
"screenSharingFirefoxPermissionDeniedTitle": "Упс! Не успяхме да стартираме споделянето на екрана!",
"screenSharingPermissionDeniedError": "Опа! Нещо се обърка с разрешенията на разширението за споделяне на екрана. Моля, презаредете и опитайте отново.",
"serviceUnavailable": "Услугата не е налична",
"sessTerminated": "Разговорът приключи",
@@ -235,91 +235,86 @@
"shareVideoLinkError": "Моля въведете правилна връзка към YouTube.",
"shareVideoTitle": "Сподели видео",
"shareYourScreen": "Споделяне на екрана",
"shareYourScreenDisabled": "",
"shareYourScreenDisabledForGuest": "",
"shareYourScreenDisabled": "Споделянето на екрана не се поддържа.",
"shareYourScreenDisabledForGuest": "Гостите не могат да споделят екрана.",
"startLiveStreaming": "Започване на излъчване на живо",
"startRecording": "Край на записа",
"startRecording": "Стартиране на запис",
"startRemoteControlErrorMessage": "Възникна грешка при опита за започване на сесията за отдалечено управление!",
"stopLiveStreaming": "Спиране на излъчването на живо",
"stopRecording": "Край на записа",
"stopRecordingWarning": "Наистина ли искате да спрем записа?",
"stopStreamingWarning": "Наистина ли искате да спрете излъчването на живо?",
"streamKey": "",
"streamKey": "Ключ за излъчване на живо",
"Submit": "Изпращане",
"thankYou": "Благодарим, че използвахте {{appName}}!",
"token": "код за достъп",
"tokenAuthFailed": "Съжаляваме, но не можете да се присъедините към този разговор.",
"tokenAuthFailedTitle": "Неуспешна идентификация",
"transcribing": "",
"unlockRoom": "",
"transcribing": "Транскрипция",
"unlockRoom": "Премахване $t(lockRoomPassword) от срещата",
"userPassword": "потребителска парола",
"WaitForHostMsg": "",
"WaitForHostMsgWOk": "",
"WaitForHostMsg": "Конференцията <b>{{room}}</b> все още не е започнала. Ако сте домакинът тогава се идентифицирайте. В противен случай изчакайте докато домакинът пристигне.",
"WaitForHostMsgWOk": "Конференцията <b>{{room}}</b> все още не е започнала. Ако сте домакинът тогава натиснете бутона за да се идентифицирате. В противен случай изчакайте докато домакинът пристигне.",
"WaitingForHost": "Чакаме домакина ...",
"Yes": "Да",
"yourEntireScreen": "Целия екран"
},
"\u0005dialog": {
"accessibilityLabel": {}
},
"dialOut": {
"statusMessage": "в момента е {{status}}"
},
"feedback": {
"average": "Средно",
"bad": "Лошо",
"detailsLabel": "",
"detailsLabel": "Разкажете ни повече.",
"good": "Добра",
"rateExperience": "Моля, оценете качеството на срещата.",
"veryBad": "Много лошо",
"veryGood": "Много добра"
},
"\u0005feedback": {},
"incomingCall": {
"answer": "",
"audioCallTitle": "",
"answer": "Вдигни",
"audioCallTitle": "Входящ разговор",
"decline": "Отхвърляне",
"productLabel": "",
"videoCallTitle": ""
"productLabel": "от Jitsi Meet",
"videoCallTitle": "Входящ видео разговор"
},
"info": {
"accessibilityLabel": "",
"addPassword": "",
"cancelPassword": "",
"conferenceURL": "",
"country": "",
"dialANumber": "",
"dialInConferenceID": "",
"dialInNotSupported": "",
"dialInNumber": "",
"dialInSummaryError": "",
"dialInTollFree": "",
"genericError": "",
"inviteLiveStream": "",
"invitePhone": "",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "",
"inviteURLFirstPartPersonal": "",
"inviteURLSecondPart": "",
"liveStreamURL": "Излъчване на живо",
"moreNumbers": "",
"noNumbers": "",
"accessibilityLabel": "Покажи информация",
"addPassword": "Добави $t(lockRoomPassword)",
"cancelPassword": "Премахни $t(lockRoomPassword)",
"conferenceURL": "Връзка:",
"country": "Страна",
"dialANumber": "За влизане в срещата, наберете един от изброените номера и въведете кода.",
"dialInConferenceID": "Код:",
"dialInNotSupported": "Съжаляваме, обаждането в момента не се поддържа. ",
"dialInNumber": "Тел:",
"dialInSummaryError": "Проблем при достъпа на информация за опциите за влизане през телефон. Моля опитайте отново по-късно.",
"dialInTollFree": "Безплатен",
"genericError": "Упс, нещо се случи.",
"inviteLiveStream": "За да видите предаването на живо на срещата, използвйте тази връзка: {{url}}",
"invitePhone": "За влизане през телефон, използвайте: {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "Търсене на друг номер за набиране?\nВижте още номера: : {{url}}\n\n\nАко вече сте набрали от телефон в стаята, влезте без да е пуснат звука: {{silentUrl}}",
"inviteURLFirstPartGeneral": "Поканени сте да се присъедините към среща.",
"inviteURLFirstPartPersonal": "{{name}} ви кани за среща.\n",
"inviteURLSecondPart": "\nВлезте в срещата:\n{{url}}\n",
"liveStreamURL": "Излъчване на живо:",
"moreNumbers": "Повече номера",
"noNumbers": "Няма номера за набиране.",
"noPassword": "Няма",
"noRoom": "",
"numbers": "",
"password": "",
"noRoom": "Няма посочена стая за информация за номера за набиране.",
"numbers": "Номера",
"password": "$t(lockRoomPasswordUppercase):",
"title": "Споделяне",
"tooltip": "",
"label": ""
"tooltip": "Споделете връзката и информацията за номера свързани със срещата",
"label": "Информация за срещата"
},
"\u0005info": {},
"inviteDialog": {
"alertText": "",
"alertText": "Не успях да поканя участниците.",
"header": "Покани",
"searchCallOnlyPlaceholder": "Въведете телефонен номер",
"searchPeopleOnlyPlaceholder": "",
"searchPlaceholder": "",
"send": ""
"searchPeopleOnlyPlaceholder": "Търсене на участници",
"searchPlaceholder": "Участник или телефонен номер",
"send": "Изпрати"
},
"inlineDialogFailure": {
"msg": "Имаше грешка.",
@@ -329,181 +324,182 @@
},
"keyboardShortcuts": {
"focusLocal": "Фокусиране върху Вашето видео",
"focusRemote": "Фокусиране върху видеото на друг участник",
"focusRemote": "Фокусирай видеото на друг участник",
"fullScreen": "Влизане/излизане от режим на цял екран",
"keyboardShortcuts": "Клавишни комбинации",
"localRecording": "",
"localRecording": "Показване или скриване на контролите за локален запис",
"mute": "Спиране/пускане на микрофона",
"pushToTalk": "Натиснете, за да говорите",
"raiseHand": "Вдигнете или свалете ръка",
"showSpeakerStats": "Показване на статистика за говорителя",
"toggleChat": "Отваряне/скриване на текстовите съобщения",
"toggleFilmstrip": "",
"toggleFilmstrip": "Показване или скриване на видео миниатюрите",
"toggleScreensharing": "Смяна между камера и споделен екран",
"toggleShortcuts": "",
"videoMute": "Пускане/спиране на камерата"
"toggleShortcuts": "Показване или скриване на клавишните комбинации",
"videoMute": "Пускане/спиране на камерата",
"videoQuality": "Управление на качество на обаждането"
},
"\u0005keyboardShortcuts": {},
"liveStreaming": {
"busy": "Работим върху това да освободим ресурси за излъчване. Моля, опитайте отново след няколко минути.",
"busyTitle": "Всички излъчватели в момента са заети.",
"changeSignIn": "",
"choose": "",
"chooseCTA": "",
"enterStreamKey": "",
"changeSignIn": "Смяна на акаунти.",
"choose": "Изберете предаване на живо",
"chooseCTA": "Изберете опция за предаване. Влезли сте като {{email}}.",
"enterStreamKey": "Въведете ключа от YouTube за предаване на живо.",
"error": "Излъчването на живо беше неуспешно. Моля, опитайте отново.",
"errorAPI": "",
"errorLiveStreamNotEnabled": "",
"expandedOff": "",
"expandedOn": "",
"expandedPending": "",
"errorAPI": "Изникна проблем с връзката към YouTube. Моля опитайте отново.",
"errorLiveStreamNotEnabled": "Предаването на живо не е пуснато за {{email}}. Моля активирайте го или сменете акаунта.",
"expandedOff": "Предаването на живо бе спряно",
"expandedOn": "Срещата се излъчва на живо в YouTube.",
"expandedPending": "Излъчването на живо се стартира...",
"failedToStart": "Излъчването на живо не успя да започне",
"getStreamKeyManually": "",
"invalidStreamKey": "",
"getStreamKeyManually": "Не успяхме да открием никакво предаване на живо. Опитайте да вземете ключа за такова от YouTube.",
"invalidStreamKey": "Ключът за предаване на живо е грешен.",
"off": "Край на излъчването на живо",
"offBy": "{{name}} спря излъчването на живо",
"on": "Излъчване на живо",
"onBy": "{{name}} пусна излъчване на живо",
"pending": "Започване на излъчването на живо…",
"serviceName": "",
"signedInAs": "",
"signIn": "",
"signInCTA": "",
"serviceName": "Предаване на живо",
"signedInAs": "В момента сте влезли като:",
"signIn": "Влезте с Гугъл",
"signInCTA": "Влезте или въведете ключът за излъчване на живо от YouTube.",
"signOut": "",
"start": "Започване на излъчване на живо",
"streamIdHelp": "",
"start": "Започни излъчване на живо",
"streamIdHelp": "Какво е това?",
"unavailableTitle": "Излъчването на живо е недостъпно"
},
"\u0005liveStreaming": {},
"localRecording": {
"clientState": {
"off": "",
"on": "",
"unknown": ""
"off": "Изключено",
"on": "Включено",
"unknown": "Непознат"
},
"dialogTitle": "",
"duration": "",
"durationNA": "",
"encoding": "",
"label": "",
"labelToolTip": "",
"localRecording": "",
"dialogTitle": "Управление на локален запис",
"duration": "Продължителност",
"durationNA": "Няма",
"encoding": "Кодек",
"label": "Етикет",
"labelToolTip": "Локалния запис е включен",
"localRecording": "Локален запис",
"me": "Аз",
"messages": {
"engaged": "",
"finished": "",
"finishedModerator": "",
"notModerator": ""
"engaged": "Локалния запис е включен.",
"finished": "Записа на сесията {{token}} приключи. Моля изпратете записа на вашият домакин.",
"finishedModerator": "Сесията {{token}} за запис приключи. Локалният запис беше запазен. Моля поканете останалите участници да ви изпратят техните записи.",
"notModerator": "Нямате права да пускате спирате локален запис."
},
"moderator": "Модератор",
"no": "",
"no": "Не",
"participant": "Участник",
"participantStats": "",
"sessionToken": "",
"start": "Край на записа",
"stop": "Край на записа",
"participantStats": "Статистика на участник",
"sessionToken": "Тоукън за сесията",
"start": "Започни запис",
"stop": "Спри записа",
"yes": "Да"
},
"\u0005localRecording": {},
"lockRoomPassword": "парола",
"lockRoomPasswordUppercase": "Парола",
"me": "аз",
"notify": {
"connectedOneMember": "",
"connectedThreePlusMembers": "",
"connectedTwoMembers": "",
"disconnected": "Връзка:",
"connectedOneMember": "{{name}} влезна в срещата",
"connectedThreePlusMembers": "{{name}} и още {{count}} влезнаха в срещата",
"connectedTwoMembers": "{{first}} и {{second}} влезнаха в срещата",
"disconnected": "Напусна срещата",
"focus": "Конферентен фокус",
"focusFail": "{{component}} не е на раположения - следващ опит след {{ms}} секунди",
"focusFail": "{{component}} не е на раположение - следващ опит след {{ms}} секунди",
"grantedTo": "Даване на роля модератор на {{to}}!",
"invitedOneMember": "",
"invitedThreePlusMembers": "",
"invitedTwoMembers": "",
"kickParticipant": "",
"invitedOneMember": "{{name}} бе поканен",
"invitedThreePlusMembers": "{{name}} и още {{count}} бяха поканени",
"invitedTwoMembers": "{{first}} и {{second}} бяха поканени",
"kickParticipant": "{{kicked}} беше изгонен от {{kicker}}",
"me": "Аз",
"moderator": "Придобихте права на модератор!",
"muted": "Започвате разговора без звук.",
"mutedTitle": "Звукът ви е спрян!",
"mutedRemotelyTitle": "",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"raisedHand": "",
"mutedRemotelyTitle": "Микрофонът ви бе спрян от {{participantDisplayName}}!",
"mutedRemotelyDescription": "Винаги можете да пуснете микрофона си когато сте готови да говорите. Заглушете го отново за да не изпращате шум в срещата.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) е премахната от друг потребител",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) създадена от друг потребител",
"raisedHand": "{{name}} иска думата.",
"somebody": "Някой",
"startSilentTitle": "",
"startSilentDescription": "",
"suboptimalExperienceDescription": "",
"suboptimalExperienceTitle": "",
"unmute": "",
"newDeviceCameraTitle": "",
"newDeviceAudioTitle": "",
"newDeviceAction": ""
"startSilentTitle": "Влязохте с опция да не чувате аудио!",
"startSilentDescription": "Влезте повторно за да пуснете звука",
"suboptimalBrowserWarning": "Опасяваме се, че няма да можете да се насладите на срещата. Работим по въпроса, междувременно използвайте някой от <a href='static/recommendedBrowsers.html' target='_blank'>напълно поддържаните браузъри</a>.",
"suboptimalExperienceTitle": "Внимание",
"unmute": "Пускане на микрофона",
"newDeviceCameraTitle": "Засечена е нова камера",
"newDeviceAudioTitle": "Ново аудио устройство е засечено",
"newDeviceAction": "Използвай"
},
"passwordSetRemotely": "",
"passwordDigitsOnly": "",
"passwordSetRemotely": "зададена от друг участник",
"passwordDigitsOnly": "До {{number}} цифри",
"poweredby": "с подкрепата на",
"presenceStatus": {
"busy": "",
"calling": "",
"busy": "Зает",
"calling": "Обаждане...",
"connected": "Свързан",
"connecting": "Свързване",
"connecting2": "Свързване",
"connecting": "Свързване...",
"connecting2": "Свързване*...",
"disconnected": "Изключен",
"expired": "",
"ignored": "",
"initializingCall": "",
"invited": "Покани",
"rejected": "",
"ringing": ""
"expired": "Изтекъл",
"ignored": "Пренебрегнат",
"initializingCall": "Свързване на обаждането...",
"invited": "Поканен",
"rejected": "Отхвърлен",
"ringing": "Звъни..."
},
"\u0005presenceStatus": {},
"profile": {
"setDisplayNameLabel": "Задайте екранното си име",
"setEmailInput": "Въведете е-поща",
"setEmailLabel": "Задайте е-пощата си в „gravatar“",
"title": "Профил"
},
"raisedHand": "Иска думата",
"recording": {
"authDropboxText": "",
"availableSpace": "",
"beta": "",
"authDropboxText": "Качете в Dropbox",
"availableSpace": "Налично място: {{spaceLeft}} MB (приблизително {{duration}} минути запис)",
"beta": "БЕТА",
"busy": "Работим върху това да освободим ресурси за запис. Моля, опитайте отново след няколко минути.",
"busyTitle": "Всички възможности за запис в момента са заети",
"error": "Грешка при опит за запис. Моля опитайте отново.",
"expandedOff": "Записът спрян",
"expandedOn": "",
"expandedPending": "Записът започна",
"expandedOff": "Записът спря",
"expandedOn": "Срещата се записва в момента",
"expandedPending": "Записът започва...",
"failedToStart": "Неуспешен опит за записване",
"fileSharingdescription": "",
"live": "",
"loggedIn": "",
"fileSharingdescription": "Споделете записа с участниците в срещата",
"live": "На Живо",
"loggedIn": "Влезли сте като {{userName}}",
"off": "Записът спрян",
"offBy": "{{name}} спря записът",
"on": "Запис",
"pending": "",
"rec": "",
"serviceDescription": "",
"serviceName": "",
"signIn": "",
"signOut": "",
"unavailable": "",
"onBy": "{{name}} пусна запис",
"pending": "Стартира запис на срещата...",
"rec": "ЗАПИС",
"serviceDescription": "Записът ви ще се запише от специална записваща услуга",
"serviceName": "Записваща услуга",
"signIn": "Влизане",
"signOut": "Излизане",
"unavailable": "Упс! В момент {{serviceName}} е недостъпна. В момента се опитваме да решим проблема. Моля опитайте отново малко по-късно.",
"unavailableTitle": "Записът е невъзможен"
},
"\u0005recording": {},
"sectionList": {
"pullToRefresh": ""
"pullToRefresh": "Издърпай за да се обнови"
},
"settings": {
"calendar": {
"about": "",
"disconnect": "Изключен",
"microsoftSignIn": "",
"signedIn": "",
"about": "Календарната интеграция на {{appName}} сигурно достъпва вашия календар за да покаже настъпващите събития.",
"disconnect": "Разкачи",
"microsoftSignIn": "Влез с Microsoft акаунт",
"signedIn": "В момента достъпва календара с {{email}}. Натиснете бутона Разкачи за да спрете достъпа.",
"title": ""
},
"devices": "",
"devices": "Устройства",
"followMe": "Всички ме следват",
"language": "",
"loggedIn": "",
"language": "Език",
"loggedIn": "Влезли сте като {{name}}",
"moderator": "Модератор",
"more": "",
"more": "Повече",
"name": "Име",
"noDevice": "Няма",
"selectAudioOutput": "Звуков изход",
@@ -513,27 +509,24 @@
"startVideoMuted": "Всички започват скрити",
"title": "Настройки"
},
"\u0005settings": {
"calendar": {}
},
"settingsView": {
"alertOk": "",
"alertOk": "Потвърди",
"alertTitle": "Внимание",
"alertURLText": "",
"buildInfoSection": "",
"conferenceSection": "",
"displayName": "",
"email": "",
"alertURLText": "Въведената връзка за сървър е невалидна",
"buildInfoSection": "Информация за програмата",
"conferenceSection": "Конференция",
"displayName": "Име",
"email": "Поща",
"header": "Настройки",
"profileSection": "Профил",
"serverURL": "",
"startWithAudioMuted": "",
"startWithVideoMuted": "",
"version": ""
"serverURL": "Линк на сървъра",
"startWithAudioMuted": "Започни със спрян звук",
"startWithVideoMuted": "Започни със спряно видео",
"version": "Версия"
},
"share": {
"dialInfoText": "",
"mainText": ""
"dialInfoText": "\n\n=====\n\nИскате да наберете от телефона?\n\n{{defaultDialInNumber}}Използвайте този линк за повече номера\n{{dialInfoPageUrl}}",
"mainText": "Използвайте линка за да влезете в срещата:\n{{roomUrl}}"
},
"speaker": "Говорещ",
"speakerStats": {
@@ -555,99 +548,95 @@
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "",
"audioOnly": "Пускане на режим само с звук",
"audioRoute": "",
"callQuality": "",
"cc": "",
"chat": "",
"document": "Отваряне/затваряне на споделен документ",
"callQuality": "Управление на качестото на обаждането",
"cc": "Пускане на субтитри",
"chat": "Активиране на прозорец за съобщения",
"document": "Показване на споделен документ",
"feedback": "",
"fullScreen": "",
"hangup": "",
"fullScreen": "Пускане/Спиране на изглед в цял екран",
"hangup": "Напускане на срещата",
"invite": "Поканете участници",
"kick": "",
"localRecording": "",
"lockRoom": "",
"moreActions": "",
"moreActionsMenu": "",
"mute": "",
"pip": "",
"kick": "Изгони участник",
"localRecording": "Показване на контроли за локален запис",
"lockRoom": "Смяна парола на среща",
"moreActions": "Показване на меню с повече опции",
"moreActionsMenu": "Меню с повече опции",
"mute": "Пускане/спиране на видеото",
"pip": "Пускане на Картина-в-Картина",
"profile": "Редактиране на профила",
"raiseHand": "",
"recording": "",
"remoteMute": "",
"Settings": "",
"sharedvideo": "",
"raiseHand": "Смяна искане на думата",
"recording": "Пускане/спиране на запис",
"remoteMute": "Заглуши участник",
"Settings": "Промяна на настройки",
"sharedvideo": "Споделяне на YouTube видео",
"shareRoom": "",
"shareYourScreen": "",
"shortcuts": "",
"shareYourScreen": "Споделяне на екрана",
"shortcuts": "Бързи клавиши",
"show": "",
"speakerStats": "",
"speakerStats": "Показване на статистики за участниците",
"tileView": "",
"toggleCamera": "",
"videomute": "",
"videoblur": ""
"videomute": "Пускане/спиране на видеото",
"videoblur": "Пускане/спиране на замъгляване на видеото"
},
"addPeople": "Добавяне на участници в разговора",
"audioOnlyOff": "",
"audioOnlyOn": "",
"audioRoute": "",
"audioOnlyOff": "Спиране режима с нисък трафик",
"audioOnlyOn": "Пускане режима с нисък трафик",
"audioRoute": "Изберете устройство за звук",
"authenticate": "Идентификация",
"callQuality": "",
"callQuality": "Промяна качеството на видеото",
"chat": "Отваряне/затваряне на текстовите съобщения",
"closeChat": "",
"documentClose": "Отваряне/затваряне на споделен документ",
"documentOpen": "Отваряне/затваряне на споделен документ",
"enterFullScreen": "",
"enterTileView": "",
"exitFullScreen": "",
"exitTileView": "",
"feedback": "",
"closeChat": "Затваряне на съобщенията",
"documentClose": "Затваряне на споделеният документ",
"documentOpen": "Отваряне на споделен документ",
"enterFullScreen": "Вижте на цял екран",
"enterTileView": "Влизане в изглед галерия",
"exitFullScreen": "Изход от цял екран",
"exitTileView": "Спиране на изглед галерия",
"feedback": "Отзиви",
"hangup": "Напускане",
"invite": "Поканете участници",
"login": "Влез",
"logout": "Изход",
"lowerYourHand": "",
"moreActions": "",
"lowerYourHand": "Махни искането на думата",
"moreActions": "Още опции",
"mute": "Спиране/пускане на микрофона",
"openChat": "",
"pip": "",
"openChat": "Отвори съобщенията",
"pip": "Пусни Картина-в-Картина",
"profile": "Редактиране на профила",
"raiseHand": "Вдигане/сваляне на ръка",
"raiseYourHand": "Вдигни ръка.",
"raiseYourHand": "Поискай думата",
"Settings": "Настройки",
"sharedvideo": "Пускане/спиране на споделянето на екрана",
"shareRoom": "",
"shortcuts": "",
"speakerStats": "Статистика на говорителя",
"startScreenSharing": "",
"startSubtitles": "",
"stopScreenSharing": "",
"stopSubtitles": "",
"stopSharedVideo": "",
"shareRoom": "Добавете някого",
"shortcuts": "Виж бързите клавиши",
"speakerStats": "Статистика за говорителите",
"startScreenSharing": "Започни споделяне на екрана",
"startSubtitles": "Пускане на субтитри",
"stopScreenSharing": "Спиране споделяне на екрана",
"stopSubtitles": "Спиране на субтитри",
"stopSharedVideo": "Спиране на YouTube видео",
"talkWhileMutedPopup": "Опитвате се да говорите? В момента микрофонът Ви е заглушен.",
"tileViewToggle": "",
"toggleCamera": "",
"tileViewToggle": "Превключване на изглед галерия",
"toggleCamera": "Пускане/спиране на камера",
"videomute": "Пускане/спиране на камерата",
"startvideoblur": "",
"stopvideoblur": ""
},
"\u0005toolbar": {
"accessibilityLabel": {}
"startvideoblur": "Замъгли фона ми",
"stopvideoblur": "Спиране замъгляването на фона"
},
"transcribing": {
"ccButtonTooltip": "",
"error": "Грешка при опит за запис. Моля опитайте отново.",
"expandedLabel": "",
"failedToStart": "",
"labelToolTip": "",
"off": "",
"pending": "",
"start": "",
"stop": "",
"tr": ""
"ccButtonTooltip": "Пускане / Спиране на субтитри",
"error": "Грешка при опит за транскрибиране. Моля опитайте отново.",
"expandedLabel": "Транскрибирането е пуснато",
"failedToStart": "Транскрибирането не успя при пускане",
"labelToolTip": "Тази среща се транскрибира",
"off": "Транскрибирането спря",
"pending": "Стартира се транскрибиране на срещата...",
"start": "Започва показване на субтитри",
"stop": "Спира показване на субтитри",
"tr": "СУБ"
},
"\u0005transcribing": {},
"userMedia": {
"androidGrantPermissions": "Изберете <b><i>Разрешаване</i></b>, когато браузърът Ви помоли за разрешение.",
"chromeGrantPermissions": "Изберете <b><i>Разрешаване</i></b>, когато браузърът Ви помоли за разрешение.",
@@ -661,31 +650,34 @@
"safariGrantPermissions": "Изберете <b><i>Добре</i></b>, когато браузърът Ви помоли за разрешение."
},
"videoSIPGW": {
"busy": "",
"busyTitle": "",
"errorAlreadyInvited": "",
"errorInvite": "",
"errorInviteFailed": "",
"errorInviteFailedTitle": "",
"errorInviteTitle": "",
"pending": ""
"busy": "Работим по освобождаване на ресурси. Моля, опитайте след няколко минути.",
"busyTitle": "Услугата за стаи в момента е заета",
"errorAlreadyInvited": "{{displayName}} вече е поканен",
"errorInvite": "Конференцията не е стартирана. Моля, опитайте по-късно.",
"errorInviteFailed": "Работим по разрешаването на проблем. Моля, опитайте по-късно.",
"errorInviteFailedTitle": "Добавянето на {{displayName}} не успя",
"errorInviteTitle": "Грешка при добавяне на стая",
"pending": "{{displayName}} бе поканен"
},
"videoStatus": {
"audioOnly": "",
"audioOnlyExpanded": "",
"callQuality": "",
"audioOnly": "АУДИО",
"audioOnlyExpanded": "Вие сте в режим на нисък трафик. В този режим ще получавате само аудио или споделени екрани.",
"callQuality": "Качество на видеото",
"hd": "ВК",
"hdTooltip": "Гледате високо качество на видеото",
"highDefinition": "Високо качество",
"labelTooiltipNoVideo": "",
"labelTooltipAudioOnly": "Включен е режим само със звук",
"labelTooiltipNoVideo": "Няма видео",
"labelTooltipAudioOnly": "Пуснат режим на нисък трафик",
"ld": "НК",
"ldTooltip": "Виждате ниско качество на видеото",
"lowDefinition": "Ниско качество",
"onlyAudioAvailable": "",
"onlyAudioSupported": "",
"onlyAudioAvailable": "Само аудио е налично",
"onlyAudioSupported": "Този браузър поддържа само аудио.",
"p2pEnabled": "Вкл. директно свързване",
"p2pVideoQualityDescription": "",
"p2pVideoQualityDescription": "В директна връзка, получаваното качество може да се сменя между високо и само аудио. Останалите настройки ще са достъпни когато връзката не е директна.",
"recHighDefinitionOnly": "Ще се предпочита високо качество.",
"sd": "СК",
"sdTooltip": "Гледате стандартно качество на видеото",
"standardDefinition": "Стандартно качество"
},
"videothumbnail": {
@@ -693,38 +685,39 @@
"flip": "Огледално",
"kick": "Изгони",
"moderator": "Модератор",
"mute": "Учасника е с изключен микрофон",
"mute": "Участника е с изключен микрофон",
"muted": "Изключен микрофон",
"remoteControl": "Отдалечено управление",
"show": "",
"videomute": ""
"show": "Покажи на главния екран",
"videomute": "Участник е спрял камерата си"
},
"welcomepage": {
"accessibilityLabel": {
"join": "",
"join": "Натиснете за да влезете",
"roomname": "Въведете име на стаята"
},
"appDescription": "",
"appDescription": "Хайде на видео разговорите с целият ви екип. Всъщност, поканете всички познати. {{app}} е напълно защитено, 100% решение за видеоконференции с отворен код което може да ползвате по цял ден, всеки ден, безплатно - без да ви е нужна регистрация.",
"audioVideoSwitch": {
"audio": "",
"video": ""
"audio": "Глас",
"video": "Видео"
},
"calendar": "",
"connectCalendarButton": "",
"connectCalendarText": "",
"enterRoomTitle": "",
"calendar": "Календар",
"connectCalendarButton": "Свържете вашия календар",
"connectCalendarText": "Свържете вашия календар за да видите срещите си в {{app}}. Добавяйки {{provider}} срещите в календара си ще можете да ги старирате с едно докосване.",
"enterRoomTitle": "Започни нова среща",
"onlyAsciiAllowed": "Името на срещата може да съдържа само латински букви и цифри.",
"go": "НАПРЕД",
"join": "ПРИСЪЕДИНЯВАНЕ",
"info": "",
"info": "Информация",
"privacy": "Поверителност",
"recentList": "",
"recentListDelete": "",
"recentListEmpty": "",
"reducedUIText": "",
"recentList": "Скорошни срещи",
"recentListDelete": "Изтрий",
"recentListEmpty": "Списъка с скорошни срещи е празен. След като участвате в някоя среща, ще я намерите тук.",
"reducedUIText": "Добре дошли в {{app}}!",
"roomname": "Въведете име на стаята",
"roomnameHint": "",
"roomnameHint": "Въведете името или връзката на стаята в която искате да влезете. Също може да си измислите име. Само го споделете с някой, за да може и той да въведе същото име за да се срещнете.",
"sendFeedback": "Изпращане на отзиви",
"terms": "Условия",
"title": ""
"title": "Сигурна, с много възможности, и напълно безплатна платформа за видео конференции"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
"headphones": "Escotadors",
"phone": "Telefòn",
"speaker": "Nautparlaire",
"none": ""
"none": "Cap de periferic àudio pas disponible"
},
"audioOnly": {
"audioOnly": "Benda passanta febla"
@@ -182,7 +182,7 @@
"liveStreamingDisabledForGuestTooltip": "Los convidats pòdon pas aviar una difusion en dirècte",
"liveStreamingDisabledTooltip": "Difusion en dirècte desactivada.",
"lockMessage": "Impossible de verrolhar la conferéncia.",
"lockRoom": "",
"lockRoom": "Ajustar un $t(lockRoomPasswordUppercase) a la conferéncia",
"lockTitle": "Fracàs del verrolhatge",
"logoutQuestion": "Sètz segur que vos volètz desconnectar e arrestar la conferéncia ?",
"logoutTitle": "Desconnexion",
@@ -199,10 +199,10 @@
"muteParticipantDialog": "",
"muteParticipantTitle": "Copar lo micro als participants ?",
"Ok": "D'acòrdi",
"passwordLabel": "",
"passwordNotSupported": "Ajustar un senhal a una conferéncia es pas suportat.",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"passwordLabel": "SENHAL",
"passwordNotSupported": "Ajustar un $t(lockRoomPassword) a una conferéncia es pas suportat.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) pas suportat",
"passwordRequired": "$t(lockRoomPasswordUppercase) requesit",
"popupError": "Vòstre navigator bloca las fenèstras que sorgisson a partir d'aqueste site. Mercés d'activar aquelas fenèstras dins los paramètres de vòstre navigator e de tornar ensajar.",
"popupErrorTitle": "Fenèstra que sorgís blocada",
"recording": "Enregistrament",
@@ -217,7 +217,7 @@
"remoteControlStopMessage": "La session de contraròtle alonhat es acabada !",
"remoteControlTitle": "Contraròtle a distància",
"Remove": "Suprimir",
"removePassword": "",
"removePassword": "Suprimir lo",
"removeSharedVideoMsg": "Sètz segur que volètz suprimir vòstra vidèo partejada ?",
"removeSharedVideoTitle": "Suprimir la vidèo partejada",
"reservationError": "Error del sistèma de reservacion",
@@ -250,7 +250,7 @@
"tokenAuthFailed": "O planhèm, sètz pas autorizat a rejónher l'apèl.",
"tokenAuthFailedTitle": "Fracàs de l'autentificacion",
"transcribing": "Transcripcion",
"unlockRoom": "",
"unlockRoom": "Suprimir lo $t(lockRoomPassword) de la conferéncia",
"userPassword": "senhal utilizaire",
"WaitForHostMsg": "La conferéncia <b>{{room}}</b> a pas encara començat. Se sètz lòst volgatz ben vos identificar. Autrament esperatz quarribe lòste.",
"WaitForHostMsgWOk": "La conferéncia <b>{{room}}</b> a pas encara començat. Se sètz lòst volgatz ben clicar Ok per vos identificar. Autrament esperatz quarribe lòste.",
@@ -279,8 +279,8 @@
},
"info": {
"accessibilityLabel": "Mostrar las info",
"addPassword": "",
"cancelPassword": "",
"addPassword": "Ajustar un $t(lockRoomPassword)",
"cancelPassword": "Anullar lo $t(lockRoomPassword)",
"conferenceURL": "Ligam:",
"country": "País",
"dialANumber": "Per participar a la conferéncia, sonatz un daquestes numèros puèi picatz lo senhal.",
@@ -355,7 +355,9 @@
"getStreamKeyManually": "",
"invalidStreamKey": "La clau de difusion en dirècte es benlèu pas corrècta.",
"off": "La difusion en dirècte es estada arrestada",
"offBy": "",
"on": "La difusion en dirècte es estada arrestada",
"onBy": "",
"pending": "Començar lo dirècte...",
"serviceName": "Servici de difusion en dirècte",
"signedInAs": "Sètz connectat coma :",
@@ -416,8 +418,8 @@
"mutedTitle": "Sètz en mut !",
"mutedRemotelyTitle": "{{participantDisplayName}} vos a mes en silenci !",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) tirat per un autre participant",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definit per un autre participant",
"raisedHand": "{{name}} volriá parlar.",
"somebody": "Qualqu'un",
"startSilentTitle": "Avètz jonch sens cap de sortida àudio !",
@@ -468,7 +470,9 @@
"live": "DIRÈCTE",
"loggedIn": "Session a {{userName}}",
"off": "Enregistrament arrestar",
"offBy": "",
"on": "Enregistrament",
"onBy": "",
"pending": "Preparacion de lenregistrament de la conferéncia...",
"rec": "ENRG",
"serviceDescription": "Vòstre enregistrament serà salvagardat pel servici dedicat.",
@@ -508,7 +512,7 @@
"alertOk": "Dacòrdi",
"alertTitle": "Avertiment",
"alertURLText": "LURL del servidor es pas valida",
"buildInfoSection": "",
"buildInfoSection": "Informacions de generacion",
"conferenceSection": "Conferéncia",
"displayName": "Escais-nom",
"email": "Corrièl",
@@ -553,7 +557,7 @@
"fullScreen": "Passar al ecran complèt",
"hangup": "Quitar la sonada",
"invite": "Convidar de monde",
"kick": "",
"kick": "Exclure un participan ",
"localRecording": "Passar al panèl denregistraments locals",
"lockRoom": "Tirar lo senhal de la conferéncia",
"moreActions": "Passar al menú mai daccions",
@@ -563,7 +567,7 @@
"profile": "Modificar vòstre perfil",
"raiseHand": "Demandar la paraula",
"recording": "Passar al enregistraments",
"remoteMute": "",
"remoteMute": "Copar lo son del participant",
"Settings": "Passar als paramètres",
"sharedvideo": "Passar al partatge de vidèo Youtube",
"shareRoom": "Convidar qualquun",
@@ -577,8 +581,8 @@
"videoblur": ""
},
"addPeople": "Ajustar de monde a vòstra sonada",
"audioOnlyOff": "",
"audioOnlyOn": "",
"audioOnlyOff": "Desactivar lo mòde connexion febla",
"audioOnlyOn": "Activar lo mòde connexion febla",
"audioRoute": "Seleccionar lo periferic àudio",
"authenticate": "Autentificatz-vos",
"callQuality": "Gerir la qualitat vidèo",
@@ -587,9 +591,9 @@
"documentClose": "Tampar los documents partejats",
"documentOpen": "Dobrir los documents partejats",
"enterFullScreen": "Veire lecran complèt",
"enterTileView": "",
"enterTileView": "Dintrar dins la vista mosaïca",
"exitFullScreen": "Sortir de lecran complèt",
"exitTileView": "",
"exitTileView": "Quitar la vista mosaïca",
"feedback": "Daissar un comentari",
"hangup": "Quitar",
"invite": "Convidar de monde",
@@ -617,8 +621,8 @@
"tileViewToggle": "Activar/Desactivar la vista en mosaïc",
"toggleCamera": "Passar a la camèra",
"videomute": "Aviar / Arrestar la camèra",
"startvideoblur": "",
"stopvideoblur": ""
"startvideoblur": "Trebolar mon rèire-plan",
"stopvideoblur": "Desactivar lo borrolatge del rèire-plan"
},
"transcribing": {
"ccButtonTooltip": "Aviar / Arrestat los sostítols",
@@ -656,13 +660,13 @@
},
"videoStatus": {
"audioOnly": "AUD",
"audioOnlyExpanded": "",
"audioOnlyExpanded": "Sètz en mòde connexion febla. Amb aqueste mòde recebretz pas que làudio e lo partatge decran.",
"callQuality": "Qualitat vidèo",
"hd": "HD",
"hdTooltip": "Difusion vidèo en nauta definicion",
"highDefinition": "Nauta definicion",
"labelTooiltipNoVideo": "Pas cap de vidèo",
"labelTooltipAudioOnly": "",
"labelTooltipAudioOnly": "Mòde connexion febla activat",
"ld": "Bassa definicion",
"ldTooltip": "Difusion vidèo en bassa definicion",
"lowDefinition": "Bassa definicion",
@@ -684,7 +688,7 @@
"muted": "Mut",
"remoteControl": "Contraròtle alonhat",
"show": "",
"videomute": ""
"videomute": "Lo participant a arrestat la camèra"
},
"welcomepage": {
"accessibilityLabel": {
@@ -698,11 +702,12 @@
},
"calendar": "Calendari",
"connectCalendarButton": "Connectar lo calendari",
"connectCalendarText": "",
"connectCalendarText": "Connectatz vòstre calendièr per veire vòstras reünions dins {{app}}. Ajustatz tanben las reünions de {{provider}} a vòstre calendièr e aviatz-las amb un sol clic.",
"enterRoomTitle": "Començar una nòva conferéncia",
"onlyAsciiAllowed": "",
"go": "Crear",
"join": "PARTICIPATZ",
"info": "",
"info": "Infor",
"privacy": "Vida privada",
"recentList": "Recents",
"recentListDelete": "Suprimits",

File diff suppressed because it is too large Load Diff

View File

@@ -21,10 +21,11 @@
"bluetooth": "Bluetooth",
"headphones": "Fones de ouvido",
"phone": "Celular",
"speaker": "Apresentador"
"speaker": "Alto-falantes",
"none": ""
},
"audioOnly": {
"audioOnly": "Somente áudio"
"audioOnly": "Largura de banda baixa"
},
"calendarSync": {
"addMeetingURL": "Adicionar um link da reunião",
@@ -49,9 +50,9 @@
"messagebox": "Digite uma mensagem",
"nickname": {
"popover": "Escolha um apelido",
"title": "Digite um apelido para usar o chat"
"title": "Digite um apelido para usar o bate-papo"
},
"title": "Chat"
"title": "Bate-papo"
},
"connectingOverlay": {
"joiningRoom": "Conectando você à reunião…"
@@ -96,8 +97,7 @@
"resolution": "Resolução:",
"status": "Conexão:",
"transport": "Transporte:",
"transport_plural": "Transportes:",
"turn": " (virar)"
"transport_plural": "Transportes:"
},
"dateUtils": {
"earlier": "Mais cedo",
@@ -107,7 +107,7 @@
"deepLinking": {
"appNotInstalled": "Você precisa do aplicativo móvel {{app}} para participar da reunião no seu telefone.",
"description": "Nada acontece? Estamos tentando iniciar sua reunião no aplicativo desktop {{app}}. Tente novamente ou inicie ele na aplicação web {{app}}.",
"descriptionWithoutWeb": "",
"descriptionWithoutWeb": "Nada aconteceu? Tentamos iniciar sua reunião no aplicativo de desktop {{app}}.",
"downloadApp": "Baixe o Aplicativo",
"launchWebButton": "Iniciar na web",
"openApp": "Continue na aplicação",
@@ -115,6 +115,7 @@
"tryAgainButton": "Tente novamente no desktop"
},
"defaultLink": "ex.: {{url}}",
"defaultNickname": "ex.: João Pedro",
"deviceError": {
"cameraError": "Falha ao acessar sua câmera",
"cameraPermission": "Erro ao obter permissão para a câmera",
@@ -132,7 +133,7 @@
"liveStreaming": "Transmissão ao vivo"
},
"allow": "Permitir",
"alreadySharedVideoMsg": "",
"alreadySharedVideoMsg": "Outro participante já está compartilhando um vídeo. Esta conferência permite apenas um vídeo compartilhado por vez.",
"alreadySharedVideoTitle": "Somente um vídeo compartilhado é permitido por vez",
"applicationWindow": "Janela de aplicativo",
"Back": "Voltar",
@@ -158,51 +159,51 @@
"contactSupport": "Contate o suporte",
"copy": "Copiar",
"dismiss": "Dispensar",
"displayNameRequired": "",
"displayNameRequired": "Oi! Qual o seu nome?",
"done": "Feito",
"enterDisplayName": "",
"enterDisplayName": "Digite seu nome aqui",
"error": "Erro",
"externalInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela.",
"externalInstallationTitle": "Extensão requerida",
"goToStore": "Vá para a loja virtual",
"gracefulShutdown": "O sistema está em manutenção. Por favor tente novamente mais tarde.",
"IamHost": "Eu sou o anfitrião",
"incorrectRoomLockPassword": "",
"incorrectRoomLockPassword": "Senha incorreta",
"incorrectPassword": "Usuário ou senha incorretos",
"inlineInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela.",
"inlineInstallExtension": "Instalar agora",
"internalError": "Oops! Alguma coisa está errada. O seguinte erro ocorreu: {{error}}",
"internalErrorTitle": "Erro interno",
"kickMessage": "",
"kickMessage": "Você pode contatar com {{participantDisplayName}} para obter mais detalhes.",
"kickParticipantButton": "Remover",
"kickParticipantDialog": "Tem certeza de que deseja remover este participante?",
"kickParticipantTitle": "Deixar mudo este participante?",
"kickTitle": "",
"kickParticipantTitle": "Chutar este participante?",
"kickTitle": "Ai! {{participantDisplayName}} expulsou você da reunião",
"liveStreaming": "Transmissão ao Vivo",
"liveStreamingDisabledForGuestTooltip": "Visitantes não podem iniciar transmissão ao vivo.",
"liveStreamingDisabledTooltip": "Iniciar transmissão ao vivo desativada.",
"lockMessage": "Falha ao travar a conferência.",
"lockRoom": "",
"lockRoom": "Adicionar reunião $t(lockRoomPasswordUppercase)",
"lockTitle": "Bloqueio falhou",
"logoutQuestion": "Deseja encerrar a sessão e finalizar a conferência?",
"logoutTitle": "Encerrar sessão",
"maxUsersLimitReached": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "O limite para o número máximo de participantes foi atingido. A conferência está cheia. Entre em contato com o proprietário da reunião ou tente novamente mais tarde!",
"maxUsersLimitReachedTitle": "Limite máximo de participantes atingido",
"micConstraintFailedError": "Seu microfone não satisfaz algumas condições necessárias.",
"micNotFoundError": "O microfone não foi encontrado.",
"micNotSendingData": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "Vá para as configurações do seu computador para ativar o som do microfone e ajustar seu nível",
"micNotSendingDataTitle": "Seu microfone está mudo pelas configurações do sistema",
"micPermissionDeniedError": "Não foi permitido acessar o seu microfone. Você ainda pode entrar na conferência, mas sem enviar áudio. Clique no botão do microfone para tentar reparar.",
"micUnknownError": "Não pode usar o microfone por uma razão desconhecida.",
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
"muteParticipantButton": "Mudo",
"muteParticipantDialog": "Tem certeza de que deseja silenciar este participante? Você não poderá desativar a opção silenciar dele, mas ele poderá fazer isso quando desejar.",
"muteParticipantDialog": "Tem certeza de que deseja silenciar este participante? Você não poderá desfazer isso, mas o participante pode reabilitar o áudio a qualquer momento.",
"muteParticipantTitle": "Deixar mudo este participante?",
"Ok": "Ok",
"passwordLabel": "",
"passwordNotSupported": "Configuração de senha para a reunião não é suportada.",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"passwordLabel": "$t(lockRoomPasswordUppercase)",
"passwordNotSupported": "A configuração de uma reunião $t(lockRoomPassword) não é suportada.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) não suportado",
"passwordRequired": "$t(lockRoomPasswordUppercase) requerido",
"popupError": "Seu navegador está bloqueando janelas popup deste site. Habilite os popups nas configurações de segurança no seu navegador e tente novamente.",
"popupErrorTitle": "Popup bloqueado",
"recording": "Gravando",
@@ -217,7 +218,7 @@
"remoteControlStopMessage": "A sessão de controle remoto terminou!",
"remoteControlTitle": "Conexão de área de trabalho remota",
"Remove": "Remover",
"removePassword": "",
"removePassword": "Remove $t(lockRoomPassword)",
"removeSharedVideoMsg": "Deseja remover seu vídeo compartilhado?",
"removeSharedVideoTitle": "Remover vídeo compartilhado",
"reservationError": "Erro de sistema de reserva",
@@ -250,7 +251,7 @@
"tokenAuthFailed": "Desculpe, você não está autorizado a entrar nesta chamada.",
"tokenAuthFailedTitle": "Falha de autenticação",
"transcribing": "Transcrevendo",
"unlockRoom": "",
"unlockRoom": "Remove a reunião $t(lockRoomPassword)",
"userPassword": "senha do usuário",
"WaitForHostMsg": "A conferência <b>{{room}}</b> ainda não começou. Se você é o anfitrião, faça a autenticação. Do contrário, aguarde a chegada do anfitrião.",
"WaitForHostMsgWOk": "A conferência <b>{{room}}</b> ainda não começou. Se você é o anfitrião, pressione Ok para autenticar. Do contrário, aguarde a chegada do anfitrião.",
@@ -279,8 +280,8 @@
},
"info": {
"accessibilityLabel": "Mostrar info",
"addPassword": "",
"cancelPassword": "",
"addPassword": "Adicione $t(lockRoomPassword)",
"cancelPassword": "Cancela $t(lockRoomPassword)",
"conferenceURL": "Link:",
"country": "País",
"dialANumber": "Para entrar na reunião, disque um desses números e depois insira o PIN.",
@@ -291,18 +292,18 @@
"dialInTollFree": "Chamada gratuita",
"genericError": "Oops, alguma coisa deu errado.",
"inviteLiveStream": "Para ver a transmissão ao vivo da reunião, clique no link: {{url}}",
"invitePhone": "",
"invitePhoneAlternatives": "",
"invitePhone": "Para participar por telefone, toque aqui: {{number}} ,, {{conferenceID}} # \\ n",
"invitePhoneAlternatives": "Procurando um número de discagem diferente?\nVeja os números de discagem da reunião: {{url}} \n\n\nSe você também estiver discando através de um telefone da sala, participe sem conectar-se ao áudio: {{silentUrl}}",
"inviteURLFirstPartGeneral": "Você foi convidado para uma reunião.",
"inviteURLFirstPartPersonal": "",
"inviteURLSecondPart": "",
"inviteURLFirstPartPersonal": "{{name}} está convidando você para uma reunião.\n",
"inviteURLSecondPart": "\nEntre na reunião:\n{{url}}\n",
"liveStreamURL": "Transmissão ao vivo:",
"moreNumbers": "Mais números",
"noNumbers": "Sem números de discagem.",
"noPassword": "Nenhum",
"noRoom": "Nenhuma sala foi especificada para entrar.",
"numbers": "Números de discagem",
"password": "",
"password": "$t(lockRoomPasswordUppercase):",
"title": "Compartilhar",
"tooltip": "Compartilhar link e discagem para esta reunião",
"label": "Informações da reunião"
@@ -335,7 +336,8 @@
"toggleFilmstrip": "Mostrar ou ocultar miniaturas de vídeo",
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela",
"toggleShortcuts": "Mostrar ou ocultar atalhos de teclado",
"videoMute": "Iniciar ou parar sua câmera"
"videoMute": "Iniciar ou parar sua câmera",
"videoQuality": "Gerenciar qualidade da chamada"
},
"liveStreaming": {
"busy": "Estamos trabalhando para liberar os recursos de transmissão. Tente novamente em alguns minutos.",
@@ -349,15 +351,17 @@
"errorLiveStreamNotEnabled": "Transmissão ao vivo não está ativada em {{email}}. Ative a transmissão ao vivo ou registre numa conta com transmissão ao vivo ativada.",
"expandedOff": "A transmissão ao vivo foi encerrada",
"expandedOn": "A reunião está sendo transmitida pelo YouTube.",
"expandedPending": "A transmissão ao vivo está sendo iniciada…",
"expandedPending": "Iniciando a transmissão ao vivo...",
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
"getStreamKeyManually": "",
"getStreamKeyManually": "Não conseguimos buscar nenhuma transmissão ao vivo. Tente obter sua chave de transmissão ao vivo no YouTube.",
"invalidStreamKey": "A senha para transmissão ao vivo pode estar incorreta.",
"off": "Transmissão ao vivo encerrada",
"offBy": "",
"on": "Transmissão ao Vivo",
"onBy": "",
"pending": "Iniciando Transmissão ao Vivo...",
"serviceName": "Serviço de Transmissão ao Vivo",
"signedInAs": "Você está conectado atualmente como:",
"signedInAs": "Você está conectado como:",
"signIn": "Faça login no Google",
"signInCTA": "Faça login ou insira sua chave de transmissão ao vivo do YouTube.",
"signOut": "Sair",
@@ -367,16 +371,16 @@
},
"localRecording": {
"clientState": {
"off": "Off",
"on": "On",
"off": "Desligado",
"on": "Ligado",
"unknown": "Desconhecido"
},
"dialogTitle": "Controles da Gravação Local",
"duration": "Duração",
"durationNA": "N/A",
"durationNA": "N/D",
"encoding": "Codificando",
"label": "LOR",
"labelToolTip": "Gravação local está envolvida",
"labelToolTip": "Gravação local ativada",
"localRecording": "Gravação local",
"me": "Eu",
"messages": {
@@ -406,30 +410,30 @@
"focusFail": "{{component}} não disponĩvel - tente em {{ms}} seg.",
"grantedTo": "Direitos de moderador concedido para {{to}}!",
"invitedOneMember": "{{displayName}} foi convidado",
"invitedThreePlusMembers": "",
"invitedTwoMembers": "",
"kickParticipant": "",
"invitedThreePlusMembers": "{{name}} e {{count}} outros foram convidados",
"invitedTwoMembers": "{{first}} e {{second}} foram convidados",
"kickParticipant": "{{kicked}} foi chutado por {{kicker}}",
"me": "Eu",
"moderator": "Direitos de moderador concedidos!",
"muted": "Você iniciou uma conversa em mudo.",
"mutedTitle": "Você está mudo!",
"mutedRemotelyTitle": "",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"mutedRemotelyTitle": "Você foi silenciado por {{participantDisplayName}}!",
"mutedRemotelyDescription": "Você sempre pode ativar o som quando estiver pronto para falar. Retire o som quando terminar para manter o ruído longe da reunião.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
"raisedHand": "{{name}} gostaria de falar.",
"somebody": "Alguém",
"startSilentTitle": "",
"startSilentDescription": "",
"suboptimalExperienceDescription": "Eer ... temos medo de que sua experiência com o {{appName}} não seja tão boa aqui. Estamos procurando maneiras de melhorar isso, mas até lá tente usar um dos <a href='static/recommendedBrowsers.html' target='_blank'> navegadores totalmente compatíveis</a>.",
"startSilentTitle": "Você entrou sem saída de áudio!",
"startSilentDescription": "Volte à reunião para habilitar o áudio",
"suboptimalBrowserWarning": "",
"suboptimalExperienceTitle": "Alerta do navegador",
"unmute": "",
"unmute": "Ativar som",
"newDeviceCameraTitle": "Nova câmera detectada",
"newDeviceAudioTitle": "Novo dispositivo de áudio detectado",
"newDeviceAction": "Usar"
},
"passwordSetRemotely": "Definido por outro participante",
"passwordDigitsOnly": "",
"passwordDigitsOnly": "Até {{number}} dígitos",
"poweredby": "distribuído por",
"presenceStatus": {
"busy": "Ocupado",
@@ -443,7 +447,7 @@
"initializingCall": "Iniciando Chamada...",
"invited": "Convidar",
"rejected": "Rejeitado",
"ringing": "Chamando..."
"ringing": "Tocando..."
},
"profile": {
"setDisplayNameLabel": "Definir seu nome de exibição",
@@ -451,6 +455,7 @@
"setEmailLabel": "Definir seu email de gravatar",
"title": "Perfil"
},
"raisedHand": "Gostaria de falar",
"recording": {
"authDropboxText": "Enviar para o Dropbox.",
"availableSpace": "Espaço disponível: {{spaceLeft}} MB (aproximadamente {{duration}} minutos de gravação)",
@@ -466,12 +471,14 @@
"live": "AOVIVO",
"loggedIn": "Conectado como {{userName}}",
"off": "Gravação parada",
"offBy": "",
"on": "Gravando",
"onBy": "",
"pending": "Preparando para gravar a reunião...",
"rec": "REC",
"serviceDescription": "Sua gravação será salva pelo serviço de gravação",
"serviceName": "Serviço de gravação",
"signIn": "entrar",
"signIn": "Entrar",
"signOut": "Sair",
"unavailable": "Oops! O {{serviceName}} está indisponível. Estamos trabalhando para resolver o problema. Por favor, tente mais tarde.",
"unavailableTitle": "Gravação indisponível"
@@ -518,10 +525,10 @@
"version": "Versão"
},
"share": {
"dialInfoText": "",
"dialInfoText": "\n\n=====\n\nDeseja apenas discar no seu telefone?\n\n{{defaultDialInNumber}}Clique neste link para ver os números de telefone para esta reunião\n{{dialInfoPageUrl}}",
"mainText": "Clique no seguinte link para entrar na reunião:{{roomUrl}}\n"
},
"speaker": "Apresentador",
"speaker": "Alto-falantes",
"speakerStats": {
"hours": "{{count}}h",
"minutes": "{{count}}m",
@@ -543,7 +550,7 @@
"accessibilityLabel": {
"audioOnly": "Alternar para apenas áudio",
"audioRoute": "Selecionar o dispositivo de som",
"callQuality": "Gerenciar qualidade da chamada",
"callQuality": "Gerenciar qualidade do vídeo",
"cc": "Alternar legendas",
"chat": "Alternar para janela de chat",
"document": "Alternar para documento compartilhado",
@@ -567,19 +574,19 @@
"shareRoom": "Convidar alguém",
"shareYourScreen": "Alternar compartilhamento de tela",
"shortcuts": "Alternar atalhos",
"show": "",
"show": "Mostrar no palco",
"speakerStats": "Alternar estatísticas do apresentador",
"tileView": "Alternar visualização em blocos",
"toggleCamera": "Alternar câmera",
"videomute": "Alternar mudo do vídeo",
"videoblur": ""
"videoblur": "Alternar desfoque de vídeo"
},
"addPeople": "Adicionar pessoas à sua chamada",
"audioOnlyOff": "Desativar modo somente áudio",
"audioOnlyOn": "Desativar modo somente áudio",
"audioOnlyOff": "",
"audioOnlyOn": "",
"audioRoute": "Selecionar o dispositivo de som",
"authenticate": "Autenticar",
"callQuality": "Gerenciar qualidade da chamada",
"callQuality": "Gerenciar qualidade do vídeo",
"chat": "Abrir ou fechar o bate-papo",
"closeChat": "Fechar chat",
"documentClose": "Fechar documento compartilhado",
@@ -615,19 +622,19 @@
"tileViewToggle": "Alternar visualização em blocos",
"toggleCamera": "Alternar câmera",
"videomute": "Iniciar ou parar a câmera",
"startvideoblur": "",
"stopvideoblur": ""
"startvideoblur": "Desfocar meu plano de fundo",
"stopvideoblur": "Desativar desfoque de fundo"
},
"transcribing": {
"ccButtonTooltip": "Iniciar/parar legendas",
"error": "Transcrição falhou. Tente novamente.",
"expandedLabel": "Transcrição ligada",
"expandedLabel": "Transcrição ativada",
"failedToStart": "Transcrição falhou ao iniciar",
"labelToolTip": "A reunião esta sendo transcrita",
"off": "Transcrição parada",
"pending": "Preparando a transcrição da reunião...",
"start": "Iniciar / Parar de mostrar as legendas",
"stop": "Iniciar / Parar de mostrar as legendas",
"start": "Exibir legendas",
"stop": "Não exibir legendas",
"tr": "TR"
},
"userMedia": {
@@ -654,20 +661,23 @@
},
"videoStatus": {
"audioOnly": "AUD",
"audioOnlyExpanded": "Você está no modo somente áudio. Esse modo economiza internet mas não permite ver o vídeo dos outros.",
"callQuality": "",
"audioOnlyExpanded": "",
"callQuality": "Qualidade de vídeo",
"hd": "HD",
"hdTooltip": "Ver vídeo em alta definição",
"highDefinition": "Alta definição (HD)",
"labelTooiltipNoVideo": "Sem vídeo",
"labelTooltipAudioOnly": "Modo somente de áudio habilitado",
"labelTooltipAudioOnly": "",
"ld": "LD",
"ldTooltip": "Ver vídeo em baixa definição",
"lowDefinition": "Baixa definição (LD)",
"onlyAudioAvailable": "Somente áudio disponível",
"onlyAudioSupported": "Suportamos somente áudio neste navegador.",
"p2pEnabled": "Ponto-a-ponto habilitada",
"p2pVideoQualityDescription": "",
"p2pVideoQualityDescription": "No modo ponto a ponto, a qualidade do vídeo recebido só pode ser alternada entre alta e apenas áudio. Outras configurações não serão respeitadas até que o ponto a ponto seja encerrado.",
"recHighDefinitionOnly": "Preferência para alta definição",
"sd": "SD",
"sdTooltip": "Ver vídeo em definição padrão",
"standardDefinition": "Definição padrão"
},
"videothumbnail": {
@@ -678,8 +688,8 @@
"mute": "Participante está mudo",
"muted": "Mudo",
"remoteControl": "Controle remoto",
"show": "",
"videomute": ""
"show": "Mostrar no palco",
"videomute": "O participante parou a câmera"
},
"welcomepage": {
"accessibilityLabel": {
@@ -695,6 +705,7 @@
"connectCalendarButton": "Conectar seu calendário",
"connectCalendarText": "Conecte seu calendário para ver todas as reuniões em {{app}}. Além disso, adicione reuniões de {{provider}} ao seu calendário e inicie-as com apenas um clique.",
"enterRoomTitle": "Iniciar uma nova reunião",
"onlyAsciiAllowed": "",
"go": "IR",
"join": "Entrar",
"info": "Informações",
@@ -702,7 +713,7 @@
"recentList": "Recente",
"recentListDelete": "Remover",
"recentListEmpty": "Sua lista recente está vazia. As reuniões que você realizar serão exibidas aqui.",
"reducedUIText": "",
"reducedUIText": "Bem-vindo ao {{app}}!",
"roomname": "Digite o nome da sala",
"roomnameHint": "Digite o nome ou a URL da sala que você deseja entrar. Você pode digitar um nome, e apenas deixe para as pessoas que você quer se reunir digitem o mesmo nome.",
"sendFeedback": "Enviar comentários",

View File

@@ -46,13 +46,16 @@
"today": "Today"
},
"chat": {
"error": "Error: your message \"{{originalText}}\" was not sent. Reason: {{error}}",
"error": "Error: your message was not sent. Reason: {{error}}",
"messagebox": "Type a message",
"messageTo": "Private message to {{recipient}}",
"nickname": {
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat"
},
"title": "Chat"
"privateNotice": "Private message to {{recipient}}",
"title": "Chat",
"you": "you"
},
"connectingOverlay": {
"joiningRoom": "Connecting you to your meeting..."
@@ -235,6 +238,10 @@
"screenSharingFirefoxPermissionDeniedError": "Something went wrong while we were trying to share your screen. Please make sure that you have given us permission to do so. ",
"screenSharingFirefoxPermissionDeniedTitle": "Oops! We werent able to start screen sharing!",
"screenSharingPermissionDeniedError": "Oops! Something went wrong with your screen sharing extension permissions. Please reload and try again.",
"sendPrivateMessage": "You recently received a private message. Did you intend to reply to that privately, or you want to send your message to the group?",
"sendPrivateMessageCancel": "Send to the group",
"sendPrivateMessageOk": "Send privately",
"sendPrivateMessageTitle": "Send privately?",
"serviceUnavailable": "Service unavailable",
"sessTerminated": "Call terminated",
"Share": "Share",
@@ -268,6 +275,9 @@
"dialOut": {
"statusMessage": "is now {{status}}"
},
"documentSharing" : {
"title": "Shared Document"
},
"feedback": {
"average": "Average",
"bad": "Bad",
@@ -362,7 +372,9 @@
"getStreamKeyManually": "We werent able to fetch any live streams. Try getting your live stream key from YouTube.",
"invalidStreamKey": "Live stream key may be incorrect.",
"off": "Live Streaming stopped",
"offBy": "{{name}} stopped the live streaming",
"on": "Live Streaming",
"onBy": "{{name}} started the live streaming",
"pending": "Starting Live Stream...",
"serviceName": "Live Streaming service",
"signedInAs": "You are currently signed in as:",
@@ -475,7 +487,9 @@
"live": "LIVE",
"loggedIn": "Logged in as {{userName}}",
"off": "Recording stopped",
"offBy": "{{name}} stopped the recording",
"on": "Recording",
"onBy": "{{name}} started the recording",
"pending": "Preparing to record the meeting...",
"rec": "REC",
"serviceDescription": "Your recording will be saved by the recording service",
@@ -512,16 +526,20 @@
"title": "Settings"
},
"settingsView": {
"advanced": "Advanced",
"alertOk": "OK",
"alertTitle": "Warning",
"alertURLText": "The entered server URL is invalid",
"buildInfoSection": "Build Information",
"conferenceSection": "Conference",
"disableCallIntegration": "Disable native call integration",
"disableP2P": "Disable Peer-To-Peer mode",
"displayName": "Display name",
"email": "Email",
"header": "Settings",
"profileSection": "Profile",
"serverURL": "Server URL",
"showAdvanced": "Show advanced settings",
"startWithAudioMuted": "Start with audio muted",
"startWithVideoMuted": "Start with video muted",
"version": "Version"
@@ -556,9 +574,11 @@
"cc": "Toggle subtitles",
"chat": "Toggle chat window",
"document": "Toggle shared document",
"download": "Download our apps",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
"hangup": "Leave the call",
"help": "Help",
"invite": "Invite people",
"kick": "Kick participant",
"localRecording": "Toggle local recording controls",
@@ -567,6 +587,7 @@
"moreActionsMenu": "More actions menu",
"mute": "Toggle mute audio",
"pip": "Toggle Picture-in-Picture mode",
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Toggle raise hand",
"recording": "Toggle recording",
@@ -593,12 +614,14 @@
"closeChat": "Close chat",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
"enterFullScreen": "View full screen",
"enterTileView": "Enter tile view",
"exitFullScreen": "Exit full screen",
"exitTileView": "Exit tile view",
"feedback": "Leave feedback",
"hangup": "Leave",
"help": "Help",
"invite": "Invite people",
"login": "Login",
"logout": "Logout",
@@ -607,6 +630,7 @@
"mute": "Mute / Unmute",
"openChat": "Open chat",
"pip": "Enter Picture-in-Picture mode",
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise / Lower your hand",
"raiseYourHand": "Raise your hand",
@@ -707,8 +731,9 @@
"connectCalendarButton": "Connect your calendar",
"connectCalendarText": "Connect your calendar to view all your meetings in {{app}}. Plus, add {{provider}} meetings to your calendar and start them with one click.",
"enterRoomTitle": "Start a new meeting",
"onlyAsciiAllowed": "Meeting name should only contain latin characters and numbers.",
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",
"go": "GO",
"goSmall": "GO",
"join": "JOIN",
"info": "Info",
"privacy": "Privacy",

View File

@@ -15,7 +15,7 @@ import Filmstrip from './videolayout/Filmstrip';
import { getLocalParticipant } from '../../react/features/base/participants';
import { toggleChat } from '../../react/features/chat';
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
import { setDocumentUrl } from '../../react/features/etherpad';
import { setFilmstripVisible } from '../../react/features/filmstrip';
import { setNotificationsEnabled } from '../../react/features/notifications';
import {
@@ -240,10 +240,12 @@ UI.initEtherpad = name => {
return;
}
logger.log('Etherpad is enabled');
etherpadManager
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
APP.store.dispatch(setEtherpadHasInitialzied());
etherpadManager = new EtherpadManager(eventEmitter);
const url = new URL(name, config.etherpad_base);
APP.store.dispatch(setDocumentUrl(url.toString()));
};
/**

View File

@@ -1,22 +1,12 @@
/* global $, APP, interfaceConfig */
import { setDocumentEditingState } from '../../../react/features/etherpad';
import { getSharedDocumentUrl, setDocumentEditingState } from '../../../react/features/etherpad';
import { getToolboxHeight } from '../../../react/features/toolbox';
import VideoLayout from '../videolayout/VideoLayout';
import LargeContainer from '../videolayout/LargeContainer';
import Filmstrip from '../videolayout/Filmstrip';
/**
* Etherpad options.
*/
const options = $.param({
showControls: true,
showChat: false,
showLineNumbers: true,
useMonospaceFont: false
});
/**
*
*/
@@ -70,13 +60,13 @@ class Etherpad extends LargeContainer {
/**
* Creates new Etherpad object
*/
constructor(domain, name) {
constructor(url) {
super();
const iframe = document.createElement('iframe');
iframe.id = 'etherpadIFrame';
iframe.src = `${domain + name}?${options}`;
iframe.src = url;
iframe.frameBorder = 0;
iframe.scrolling = 'no';
iframe.width = DEFAULT_WIDTH;
@@ -199,13 +189,7 @@ export default class EtherpadManager {
/**
*
*/
constructor(domain, name, eventEmitter) {
if (!domain || !name) {
throw new Error('missing domain or name');
}
this.domain = domain;
this.name = name;
constructor(eventEmitter) {
this.eventEmitter = eventEmitter;
this.etherpad = null;
}
@@ -228,7 +212,7 @@ export default class EtherpadManager {
* Create new Etherpad frame.
*/
openEtherpad() {
this.etherpad = new Etherpad(this.domain, this.name);
this.etherpad = new Etherpad(getSharedDocumentUrl(APP.store.getState));
VideoLayout.addLargeVideoContainer(
ETHERPAD_CONTAINER_TYPE,
this.etherpad

View File

@@ -273,7 +273,7 @@ LocalVideo.prototype._updateVideoElement = function() {
// case video does not autoplay.
const video = this.container.querySelector('video');
video && video.play();
video && !config.testing?.noAutoPlayVideo && video.play();
};
export default LocalVideo;

View File

@@ -7,6 +7,7 @@ import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import { createThumbnailOffsetParentIsNullEvent, sendAnalytics } from '../../../react/features/analytics';
import { i18next } from '../../../react/features/base/i18n';
import {
JitsiParticipantConnectionStatus
@@ -486,6 +487,8 @@ RemoteVideo.prototype.hasVideoStarted = function() {
RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
if (!this.container) {
logger.debug('Not attaching remote stream due to no container');
return;
}
@@ -498,6 +501,8 @@ RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
}
if (!stream.getOriginalStream()) {
logger.debug('Remote video stream has no original stream');
return;
}
@@ -508,11 +513,31 @@ RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
$(streamElement).hide();
// If the container is currently visible
// we attach the stream to the element.
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
// TODO: Remove once we verify that this.container.offsetParent === null was the reason for not attached video
// streams to the thumbnail.
if (isVideo && this.container.offsetParent === null) {
sendAnalytics(createThumbnailOffsetParentIsNullEvent(this.id));
const parentNodesDisplayProps = [
'#filmstripRemoteVideosContainer',
'#filmstripRemoteVideos',
'#remoteVideos',
'.filmstrip',
'#videospace',
'#videoconference_page',
'#react'
].map(selector => `${selector} - ${$(selector).css('display')}`);
const videoConferencePageParent = $('#videoconference_page').parent();
const reactDiv = document.getElementById('react');
parentNodesDisplayProps.push(
`${videoConferencePageParent.attr('class')} - ${videoConferencePageParent.css('display')}`);
parentNodesDisplayProps.push(`this.container - ${this.$container.css('display')}`);
logger.debug(`this.container.offsetParent is null [user: ${this.id}, ${
parentNodesDisplayProps.join(', ')}, #react.offsetParent - ${
reactDiv && reactDiv.offsetParent !== null ? 'not null' : 'null'}]`);
}
if (!isVideo) {

View File

@@ -199,7 +199,7 @@ SmallVideo.createStreamElement = function(stream) {
element.muted = true;
}
element.autoplay = true;
element.autoplay = !config.testing?.noAutoPlayVideo;
element.id = SmallVideo.getStreamElementID(stream);
return element;
@@ -511,7 +511,7 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function() {
* or <tt>false</tt> otherwise.
*/
SmallVideo.prototype.isVideoPlayable = function() {
return this.videoStream && !this.isVideoMuted && !this.videoStream.isMuted();
return this.videoStream && !this.isVideoMuted && !APP.conference.isAudioOnly();
};
/**
@@ -520,40 +520,33 @@ SmallVideo.prototype.isVideoPlayable = function() {
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
* or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
*/
SmallVideo.prototype.selectDisplayMode = function() {
const isAudioOnly = APP.conference.isAudioOnly();
const tileViewEnabled = shouldDisplayTileView(APP.store.getState());
const isVideoPlayable = this.isVideoPlayable();
const hasVideo = Boolean(this.selectVideoElement().length);
SmallVideo.prototype.selectDisplayMode = function(input) {
// Display name is always and only displayed when user is on the stage
if (this.isCurrentlyOnLargeVideo() && !tileViewEnabled) {
return isVideoPlayable && !isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
} else if (isVideoPlayable && hasVideo && !isAudioOnly) {
if (input.isCurrentlyOnLargeVideo && !input.tileViewEnabled) {
return input.isVideoPlayable && !input.isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
} else if (input.isVideoPlayable && input.hasVideo && !input.isAudioOnly) {
// check hovering and change state to video with name
return this._isHovered() ? DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
return input.isHovered ? DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
}
// check hovering and change state to avatar with name
return this._isHovered() ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
return input.isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
};
/**
* Prints information about the current display mode.
* Computes information that determine the display mode.
*
* @param {string} mode - The current mode.
* @returns {void}
* @returns {Object}
*/
SmallVideo.prototype._printDisplayModeInfo = function(mode) {
const isAudioOnly = APP.conference.isAudioOnly();
const tileViewEnabled = shouldDisplayTileView(APP.store.getState());
const isVideoPlayable = this.isVideoPlayable();
const hasVideo = Boolean(this.selectVideoElement().length);
const displayModeInfo = {
isAudioOnly,
tileViewEnabled,
isVideoPlayable,
hasVideo,
SmallVideo.prototype.computeDisplayModeInput = function() {
return {
isCurrentlyOnLargeVideo: this.isCurrentlyOnLargeVideo(),
isHovered: this._isHovered(),
isAudioOnly: APP.conference.isAudioOnly(),
tileViewEnabled: shouldDisplayTileView(APP.store.getState()),
isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus: APP.conference.getParticipantConnectionStatus(this.id),
mutedWhileDisconnected: this.mutedWhileDisconnected,
wasVideoPlayed: this.wasVideoPlayed,
@@ -561,8 +554,6 @@ SmallVideo.prototype._printDisplayModeInfo = function(mode) {
isVideoMuted: this.isVideoMuted,
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
};
logger.debug(`Displaying ${mode} for ${this.id}, reason: [${JSON.stringify(displayModeInfo)}]`);
};
/**
@@ -596,8 +587,10 @@ SmallVideo.prototype.updateView = function() {
const oldDisplayMode = this.displayMode;
let displayModeString = '';
const displayModeInput = this.computeDisplayModeInput();
// Determine whether video, avatar or blackness should be displayed
this.displayMode = this.selectDisplayMode();
this.displayMode = this.selectDisplayMode(displayModeInput);
switch (this.displayMode) {
case DISPLAY_AVATAR_WITH_NAME:
@@ -624,7 +617,7 @@ SmallVideo.prototype.updateView = function() {
}
if (this.displayMode !== oldDisplayMode) {
this._printDisplayModeInfo(displayModeString);
logger.debug(`Displaying ${displayModeString} for ${this.id}, data: [${JSON.stringify(displayModeInput)}]`);
}
};

View File

@@ -200,7 +200,11 @@ const VideoLayout = {
const id = stream.getParticipantId();
const remoteVideo = remoteVideos[id];
logger.debug(`Received a new ${stream.getType()} stream for ${id}`);
if (!remoteVideo) {
logger.debug('No remote video element to add stream');
return;
}

4996
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,10 +35,11 @@
"@atlaskit/tooltip": "12.1.13",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4",
"@react-native-community/google-signin": "3.0.1",
"@react-native-community/netinfo": "4.1.5",
"@svgr/webpack": "4.3.2",
"@tensorflow-models/body-pix": "^1.0.1",
"@tensorflow/tfjs": "^1.1.2",
"@tensorflow-models/body-pix": "1.1.2",
"@tensorflow/tfjs": "1.2.9",
"@webcomponents/url": "0.7.1",
"amplitude-js": "4.5.2",
"bc-css-flags": "3.0.0",
@@ -48,7 +49,7 @@
"i18next-browser-languagedetector": "3.0.1",
"i18next-xhr-backend": "3.0.0",
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#a885cc98688ef2c3972284bda901596a26ffee52",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"jquery": "3.4.0",
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.1",
@@ -56,22 +57,21 @@
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f43c0c4bbfeb09ed816356eb5088bd61d40b91d5",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",
"moment-duration-format": "2.2.2",
"postis": "2.2.0",
"react": "16.8.6",
"react-dom": "16.8.6",
"react": "16.9",
"react-dom": "16.9",
"react-emoji-render": "1.0.0",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "0.60.5",
"react-native": "0.61.3",
"react-native-background-timer": "2.1.1",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2",
"react-native-callstats": "3.61.0",
"react-native-google-signin": "2.0.0",
"react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6",
@@ -80,13 +80,14 @@
"react-native-svg-transformer": "0.13.0",
"react-native-swipeout": "2.3.6",
"react-native-watch-connectivity": "0.2.0",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#b1275ccf1e0e083d2a5c238e7d21c37657766463",
"react-native-webview": "5.8.1",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
"react-native-webview": "7.4.1",
"react-redux": "7.1.0",
"react-textarea-autosize": "7.1.0",
"react-transition-group": "2.4.0",
"redux": "4.0.4",
"redux-thunk": "2.2.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#db96d11f175a22ef56c7db1ba9550835b716e615",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "3.1.0",
@@ -117,18 +118,19 @@
"eslint-plugin-react": "7.11.1",
"eslint-plugin-react-native": "3.3.0",
"expose-loader": "0.7.5",
"flow-bin": "0.98.0",
"flow-bin": "0.104.0",
"imports-loader": "0.7.1",
"jetifier": "1.6.4",
"metro-react-native-babel-preset": "0.56.0",
"node-sass": "4.10.0",
"node-sass": "4.12.0",
"precommit-hook": "3.0.0",
"string-replace-loader": "2.1.1",
"style-loader": "0.19.0",
"webpack": "4.26.1",
"unorm": "1.6.0",
"webpack": "4.27.1",
"webpack-bundle-analyzer": "3.4.1",
"webpack-cli": "3.1.2",
"webpack-dev-server": "3.1.14"
"webpack-dev-server": "3.8.2"
},
"engines": {
"node": ">=8.0.0",

View File

@@ -692,6 +692,21 @@ export function createSyncTrackStateEvent(mediaType, muted) {
};
}
/**
* Creates an event that indicates the thumbnail offset parent is null.
*
* @param {string} id - The id of the user related to the thumbnail.
* @returns {Object} The event in a format suitable for sending via sendAnalytics.
*/
export function createThumbnailOffsetParentIsNullEvent(id) {
return {
action: 'OffsetParentIsNull',
attributes: {
id
}
};
}
/**
* Creates an event associated with a toolbar button being clicked/pressed. By
* convention, where appropriate an attribute named 'enable' should be used to

View File

@@ -15,6 +15,7 @@ import { connect, disconnect, setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { createDesiredLocalTracks } from '../base/tracks';
import {
getBackendSafeRoomName,
getLocationContextRoot,
parseURIString,
toURLString
@@ -85,7 +86,7 @@ export function appNavigate(uri: ?string) {
let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment.
room && (url += `?room=${room.toLowerCase()}`);
room && (url += `?room=${getBackendSafeRoomName(room)}`);
let config;

View File

@@ -6,7 +6,7 @@ import '../../analytics';
import '../../authentication';
import { setColorScheme } from '../../base/color-scheme';
import { DialogContainer } from '../../base/dialog';
import { updateFlags } from '../../base/flags';
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
import '../../base/jwt';
import { Platform } from '../../base/react';
import {
@@ -100,6 +100,13 @@ export class App extends AbstractApp {
dispatch(setColorScheme(this.props.colorScheme));
dispatch(updateFlags(this.props.flags));
dispatch(updateSettings(this.props.userInfo || {}));
// Update settings with feature-flag.
const callIntegrationEnabled = this.props.flags[CALL_INTEGRATION_ENABLED];
if (typeof callIntegrationEnabled !== 'undefined') {
dispatch(updateSettings({ disableCallIntegration: !callIntegrationEnabled }));
}
});
}

View File

@@ -16,7 +16,7 @@ import {
} from '../../base/dialog';
import { translate } from '../../base/i18n';
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
import { StyleType } from '../../base/styles';
import type { StyleType } from '../../base/styles';
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
import styles from './styles';

View File

@@ -42,6 +42,6 @@ export default class AbstractStatelessAvatar<P: Props> extends PureComponent<P>
* @returns {boolean}
*/
_isIcon(iconProp: ?string | ?Object): boolean {
return Boolean(iconProp) && typeof iconProp === 'object';
return Boolean(iconProp) && (typeof iconProp === 'object' || typeof iconProp === 'function');
}
}

View File

@@ -153,6 +153,8 @@ class ColorSchemeRegistry {
const colorScheme = toState(stateful)['features/base/color-scheme'];
return {
...defaultScheme._defaultTheme,
...colorScheme._defaultTheme,
...defaultScheme[componentName],
...colorScheme[componentName]
}[colorDefinition];

View File

@@ -6,18 +6,26 @@ import { ColorPalette, getRGBAFormat } from '../styles';
* The default color scheme of the application.
*/
export default {
'BottomSheet': {
'_defaultTheme': {
// Generic app theme colors that are used accross the entire app.
// All scheme definitions below inherit these values.
background: 'rgb(255, 255, 255)',
icon: '#1c2025',
label: '#1c2025'
icon: 'rgb(28, 32, 37)',
text: 'rgb(28, 32, 37)'
},
'Chat': {
displayName: 'rgb(94, 109, 121)',
localMsgBackground: 'rgb(215, 230, 249)',
privateMsgBackground: 'rgb(250, 219, 219)',
privateMsgNotice: 'rgb(186, 39, 58)',
remoteMsgBackground: 'rgb(241, 242, 246)',
replyBorder: 'rgb(219, 197, 200)',
replyIcon: 'rgb(94, 109, 121)'
},
'Dialog': {
background: 'rgb(255, 255, 255)',
border: 'rgba(0, 3, 6, 0.6)',
buttonBackground: ColorPalette.blue,
buttonLabel: ColorPalette.white,
icon: '#1c2025',
text: '#1c2025'
buttonLabel: ColorPalette.white
},
'Header': {
background: ColorPalette.blue,
@@ -30,8 +38,7 @@ export default {
background: 'rgb(42, 58, 75)'
},
'LoadConfigOverlay': {
background: 'rgb(249, 249, 249)',
text: 'rgb(28, 32, 37)'
background: 'rgb(249, 249, 249)'
},
'Thumbnail': {
activeParticipantHighlight: 'rgb(81, 214, 170)',

View File

@@ -23,7 +23,10 @@ import {
participantUpdated
} from '../participants';
import { getLocalTracks, trackAdded, trackRemoved } from '../tracks';
import { getJitsiMeetGlobalNS } from '../util';
import {
getBackendSafeRoomName,
getJitsiMeetGlobalNS
} from '../util';
import {
AUTH_STATUS_CHANGED,
@@ -74,6 +77,11 @@ declare var APP: Object;
* @returns {void}
*/
function _addConferenceListeners(conference, dispatch) {
// A simple logger for conference errors received through
// the listener. These errors are not handled now, but logged.
conference.on(JitsiConferenceEvents.CONFERENCE_ERROR,
error => logger.error('Conference error.', error));
// Dispatches into features/base/conference follow:
conference.on(
@@ -388,8 +396,7 @@ export function createConference() {
const conference
= connection.initJitsiConference(
// XXX Lib-jitsi-meet does not accept uppercase letters.
room.toLowerCase(), {
getBackendSafeRoomName(room), {
...state['features/base/config'],
applicationName: getName(),
getWiFiStatsMethod: getJitsiMeetGlobalNS().getWiFiStats,

View File

@@ -34,3 +34,17 @@ export const LOAD_CONFIG_ERROR = 'LOAD_CONFIG_ERROR';
* }
*/
export const SET_CONFIG = 'SET_CONFIG';
/**
* The redux action which updates the configuration represented by the feature
* base/config. The configuration is defined and consumed by the library
* lib-jitsi-meet but some of its properties are consumed by the application
* jitsi-meet as well. A merge operation is performed between the existing config
* and the passed object.
*
* {
* type: _UPDATE_CONFIG,
* config: Object
* }
*/
export const _UPDATE_CONFIG = '_UPDATE_CONFIG';

View File

@@ -0,0 +1,138 @@
/**
* The config keys to whitelist, the keys that can be overridden.
* Currently we can only whitelist the first part of the properties, like
* 'p2p.useStunTurn' and 'p2p.enabled' we whitelist all p2p options.
* The whitelist is used only for config.js.
*
* @type Array
*/
export default [
'_desktopSharingSourceDevice',
'_peerConnStatusOutOfLastNTimeout',
'_peerConnStatusRtcMuteTimeout',
'abTesting',
'analytics.disabled',
'autoRecord',
'autoRecordToken',
'avgRtpStatsN',
'callFlowsEnabled',
'callStatsConfIDNamespace',
'callStatsID',
'callStatsSecret',
/**
* The display name of the CallKit call representing the conference/meeting
* associated with this config.js including while the call is ongoing in the
* UI presented by CallKit and in the system-wide call history. The property
* is meant for use cases in which the room name is not desirable as a
* display name for CallKit purposes and the desired display name is not
* provided in the form of a JWT callee. As the value is associated with a
* conference/meeting, the value makes sense not as a deployment-wide
* configuration, only as a runtime configuration override/overwrite
* provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callDisplayName',
/**
* The handle
* ({@link https://developer.apple.com/documentation/callkit/cxhandle}) of
* the CallKit call representing the conference/meeting associated with this
* config.js. The property is meant for use cases in which the room URL is
* not desirable as the handle for CallKit purposes. As the value is
* associated with a conference/meeting, the value makes sense not as a
* deployment-wide configuration, only as a runtime configuration
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callHandle',
/**
* The UUID of the CallKit call representing the conference/meeting
* associated with this config.js. The property is meant for use cases in
* which Jitsi Meet is to work with a CallKit call created outside of Jitsi
* Meet and to be adopted by Jitsi Meet such as, for example, an incoming
* and/or outgoing CallKit call created by Jitsi Meet SDK for iOS
* clients/consumers prior to giving control to Jitsi Meet. As the value is
* associated with a conference/meeting, the value makes sense not as a
* deployment-wide configuration, only as a runtime configuration
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callUUID',
'channelLastN',
'constraints',
'debug',
'debugAudioLevels',
'defaultLanguage',
'desktopSharingChromeDisabled',
'desktopSharingChromeExtId',
'desktopSharingChromeMinExtVersion',
'desktopSharingChromeSources',
'desktopSharingFrameRate',
'desktopSharingFirefoxDisabled',
'desktopSharingSources',
'disable1On1Mode',
'disableAEC',
'disableAGC',
'disableAP',
'disableAudioLevels',
'disableDeepLinking',
'disableH264',
'disableHPF',
'disableNS',
'disableRemoteControl',
'disableRtx',
'disableSuspendVideo',
'displayJids',
'e2eping',
'enableDisplayNameInStats',
'enableLayerSuspension',
'enableLipSync',
'disableLocalVideoFlip',
'enableRemb',
'enableStatsID',
'enableTalkWhileMuted',
'enableTcc',
'etherpad_base',
'failICE',
'fileRecordingsEnabled',
'firefox_fake_device',
'forceJVB121Ratio',
'gatherStats',
'googleApiApplicationClientID',
'hiddenDomain',
'hosts',
'iAmRecorder',
'iAmSipGateway',
'iceTransportPolicy',
'ignoreStartMuted',
'liveStreamingEnabled',
'localRecording',
'minParticipants',
'nick',
'openBridgeChannel',
'p2p',
'preferH264',
'requireDisplayName',
'resolution',
'startAudioMuted',
'startAudioOnly',
'startBitrate',
'startSilent',
'startScreenSharing',
'startVideoMuted',
'startWithAudioMuted',
'startWithVideoMuted',
'subject',
'testing',
'useIPv6',
'useNicks',
'useStunTurn',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable'
];

View File

@@ -2,152 +2,12 @@
import _ from 'lodash';
import CONFIG_WHITELIST from './configWhitelist';
import { _CONFIG_STORE_PREFIX } from './constants';
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
import parseURLParams from './parseURLParams';
import logger from './logger';
declare var $: Object;
/**
* The config keys to whitelist, the keys that can be overridden.
* Currently we can only whitelist the first part of the properties, like
* 'p2p.useStunTurn' and 'p2p.enabled' we whitelist all p2p options.
* The whitelist is used only for config.js.
*
* @private
* @type Array
*/
const WHITELISTED_KEYS = [
'_desktopSharingSourceDevice',
'_peerConnStatusOutOfLastNTimeout',
'_peerConnStatusRtcMuteTimeout',
'abTesting',
'analytics.disabled',
'autoRecord',
'autoRecordToken',
'avgRtpStatsN',
'callFlowsEnabled',
'callStatsConfIDNamespace',
'callStatsID',
'callStatsSecret',
/**
* The display name of the CallKit call representing the conference/meeting
* associated with this config.js including while the call is ongoing in the
* UI presented by CallKit and in the system-wide call history. The property
* is meant for use cases in which the room name is not desirable as a
* display name for CallKit purposes and the desired display name is not
* provided in the form of a JWT callee. As the value is associated with a
* conference/meeting, the value makes sense not as a deployment-wide
* configuration, only as a runtime configuration override/overwrite
* provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callDisplayName',
/**
* The handle
* ({@link https://developer.apple.com/documentation/callkit/cxhandle}) of
* the CallKit call representing the conference/meeting associated with this
* config.js. The property is meant for use cases in which the room URL is
* not desirable as the handle for CallKit purposes. As the value is
* associated with a conference/meeting, the value makes sense not as a
* deployment-wide configuration, only as a runtime configuration
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callHandle',
/**
* The UUID of the CallKit call representing the conference/meeting
* associated with this config.js. The property is meant for use cases in
* which Jitsi Meet is to work with a CallKit call created outside of Jitsi
* Meet and to be adopted by Jitsi Meet such as, for example, an incoming
* and/or outgoing CallKit call created by Jitsi Meet SDK for iOS
* clients/consumers prior to giving control to Jitsi Meet. As the value is
* associated with a conference/meeting, the value makes sense not as a
* deployment-wide configuration, only as a runtime configuration
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
*
* @type string
*/
'callUUID',
'channelLastN',
'constraints',
'debug',
'debugAudioLevels',
'defaultLanguage',
'desktopSharingChromeDisabled',
'desktopSharingChromeExtId',
'desktopSharingChromeMinExtVersion',
'desktopSharingChromeSources',
'desktopSharingFrameRate',
'desktopSharingFirefoxDisabled',
'desktopSharingSources',
'disable1On1Mode',
'disableAEC',
'disableAGC',
'disableAP',
'disableAudioLevels',
'disableDeepLinking',
'disableH264',
'disableHPF',
'disableNS',
'disableRemoteControl',
'disableRtx',
'disableSuspendVideo',
'displayJids',
'e2eping',
'enableDisplayNameInStats',
'enableLayerSuspension',
'enableLipSync',
'disableLocalVideoFlip',
'enableRemb',
'enableStatsID',
'enableTalkWhileMuted',
'enableTcc',
'etherpad_base',
'failICE',
'fileRecordingsEnabled',
'firefox_fake_device',
'forceJVB121Ratio',
'gatherStats',
'googleApiApplicationClientID',
'hiddenDomain',
'hosts',
'iAmRecorder',
'iAmSipGateway',
'iceTransportPolicy',
'ignoreStartMuted',
'liveStreamingEnabled',
'localRecording',
'minParticipants',
'nick',
'openBridgeChannel',
'p2p',
'preferH264',
'requireDisplayName',
'resolution',
'startAudioMuted',
'startAudioOnly',
'startBitrate',
'startSilent',
'startScreenSharing',
'startVideoMuted',
'startWithAudioMuted',
'startWithVideoMuted',
'subject',
'testing',
'useIPv6',
'useNicks',
'useStunTurn',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable'
];
// XXX The functions getRoomName and parseURLParams are split out of
// functions.js because they are bundled in both app.bundle and
// do_external_connect, webpack 1 does not support tree shaking, and we don't
@@ -178,69 +38,6 @@ export function createFakeConfig(baseURL: string) {
};
}
/**
* Promise wrapper on obtain config method. When HttpConfigFetch will be moved
* to React app it's better to use load config instead.
*
* @param {string} location - URL of the domain from which the config is to be
* obtained.
* @param {string} room - Room name.
* @private
* @returns {Promise<void>}
*/
export function obtainConfig(location: string, room: string): Promise<void> {
return new Promise((resolve, reject) =>
_obtainConfig(location, room, (success, error) => {
success ? resolve() : reject(error);
})
);
}
/**
* Sends HTTP POST request to specified {@code endpoint}. In request the name
* of the room is included in JSON format:
* {
* "rooomName": "someroom12345"
* }.
*
* @param {string} endpoint - The name of HTTP endpoint to which to send
* the HTTP POST request.
* @param {string} roomName - The name of the conference room for which config
* is requested.
* @param {Function} complete - The callback to invoke upon success or failure.
* @returns {void}
*/
function _obtainConfig(endpoint: string, roomName: string, complete: Function) {
logger.info(`Send config request to ${endpoint} for room: ${roomName}`);
$.ajax(
endpoint,
{
contentType: 'application/json',
data: JSON.stringify({ roomName }),
dataType: 'json',
method: 'POST',
error(jqXHR, textStatus, errorThrown) {
logger.error('Get config error: ', jqXHR, errorThrown);
complete(false, `Get config response status: ${textStatus}`);
},
success(data) {
const { config, interfaceConfig, loggingConfig } = window;
try {
overrideConfigJSON(
config, interfaceConfig, loggingConfig,
data);
complete(true);
} catch (e) {
logger.error('Parse config error: ', e);
complete(false, e);
}
}
}
);
}
/* eslint-disable max-params, no-shadow */
/**
@@ -306,8 +103,8 @@ export function overrideConfigJSON(
/* eslint-enable max-params, no-shadow */
/**
* Whitelist only config.js, skips this for others configs
* (interfaceConfig, loggingConfig).
* Apply whitelist filtering for configs with whitelists, skips this for others
* configs (loggingConfig).
* Only extracts overridden values for keys we allow to be overridden.
*
* @param {string} configName - The config name, one of config,
@@ -318,11 +115,13 @@ export function overrideConfigJSON(
* that are whitelisted.
*/
function _getWhitelistedJSON(configName, configJSON) {
if (configName !== 'config') {
return configJSON;
if (configName === 'interfaceConfig') {
return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST);
} else if (configName === 'config') {
return _.pick(configJSON, CONFIG_WHITELIST);
}
return _.pick(configJSON, WHITELISTED_KEYS);
return configJSON;
}
/**

View File

@@ -11,8 +11,8 @@ export * from './functions.any';
* @returns {void}
*/
export function _cleanupConfig(config: Object) {
config.analytics.scriptURLs = [];
if (NativeModules.AppInfo.LIBRE_BUILD) {
config.analytics.scriptURLs = [];
delete config.analytics.amplitudeAPPKey;
delete config.analytics.googleAnalyticsTrackingId;
delete config.callStatsID;

View File

@@ -1,6 +1,6 @@
/* @flow */
// @flow
declare var config: Object;
import { getBackendSafeRoomName } from '../util';
/**
* Builds and returns the room name.
@@ -8,22 +8,10 @@ declare var config: Object;
* @returns {string}
*/
export default function getRoomName(): ?string {
const { getroomnode } = config;
const path = window.location.pathname;
let roomName;
// Determine the room node from the URL.
if (getroomnode && typeof getroomnode === 'function') {
roomName = getroomnode.call(config, path);
} else {
// Fall back to the default strategy of making assumptions about how the
// URL maps to the room (name). It currently assumes a deployment in
// which the last non-directory component of the path (name) is the
// room.
roomName
= path.substring(path.lastIndexOf('/') + 1).toLowerCase()
|| undefined;
}
// The last non-directory component of the path (name) is the room.
const roomName = path.substring(path.lastIndexOf('/') + 1) || undefined;
return roomName;
return getBackendSafeRoomName(roomName);
}

View File

@@ -1,5 +1,6 @@
export * from './actions';
export * from './actionTypes';
export { default as CONFIG_WHITELIST } from './configWhitelist';
export * from './functions';
import './middleware';

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