Compare commits

...

152 Commits
1663 ... 1808

Author SHA1 Message Date
bbaldino
9a46896600 Merge pull request #1402 from jitsi/p2p_ver2
P2P ver2
2017-03-22 16:10:13 -07:00
paweldomas
fba086134d add default STUN servers to config.js 2017-03-22 11:23:30 -05:00
paweldomas
2973364c02 feat(stats - show more): local p2p transport indication
Will show (direct) next to the UPD or TCP transport type if we're
running on P2P connection.
2017-03-22 11:23:30 -05:00
paweldomas
542bb7caed doc: add FIXME 2017-03-22 11:23:29 -05:00
paweldomas
fb47b6ae21 feat: add test P2P methods 2017-03-22 11:23:29 -05:00
hristoterezov
aeb301c8d5 feat(iframe_api): Add jwt token parameter 2017-03-21 22:34:44 +01:00
yanas
704e14f008 Handle last n in the client (#1389)
* Handle last n in the client

* fix(LargeVideoManager.js): Fixes check for low bandwidth. Needs more work

* fix(LargeVideoManager.js): Fixes the Shared Video test.

* fix(LargeVideoManager): Fix shared video view and remove last n checks.

* fix(LargeVideoManager): Fixes jsdoc comment

* fix(RemoteVideo): Fix connection status check

* fix(LargeVideoManager,RemoteVideo): Syntax errors
2017-03-21 12:14:13 -05:00
Lyubo Marinov
d1050d6b02 Update NPM dependencies/packages 2017-03-21 09:22:53 -05:00
Aaron van Meerten
afc96808e8 added support of static directory in debian install and Makefile for source package 2017-03-20 19:22:06 -05:00
Aaron van Meerten
dc2bae4ae1 Merge pull request #1420 from jitsi/static-content-folder
Moves all static content/files in a new folder.
2017-03-20 17:13:10 -05:00
damencho
1d7da21e48 Moves all static content/files in a new folder.
Also clears debian package from including *.js files from source roote folder, files which were not used.
2017-03-20 16:05:11 -05:00
Ilya Daynatovich
affd965d5d Remove an unnecessary file 2017-03-20 14:42:54 -05:00
Leonard Kim
989161159d Modal dialog for displaying dominant speaker times 2017-03-20 12:47:20 -05:00
Ilya Daynatovich
59a74153dc Toolbar notice as React Component 2017-03-20 11:27:08 -05:00
yanas
6690c269ef Merge pull request #1413 from jitsi/fix-missing-translation
Fixes wrong i18n key for somebody.
2017-03-20 11:03:45 -05:00
George Politis
b7fd10b905 Merge pull request #1412 from jitsi/framerate-update
Updates framerate using local statistics.
2017-03-17 17:17:24 -05:00
damencho
08e1cf1b7e Fixes wrong i18n key for somebody. 2017-03-17 16:43:43 -05:00
damencho
54d891afa7 Updates framerate using local statistics. 2017-03-17 16:10:45 -05:00
Aaron van Meerten
ae41782cd4 Merge pull request #1410 from jitsi/letsencrypt-script
Adds a script which install certificates from let's encrypt.
2017-03-17 15:53:52 -05:00
damencho
8591fe00b6 Adds a script which install certificates from let's encrypt.
The script looks for nginx, apache2 or jetty configuration and edits the first one found. Nginx and apache2 will be reloaded, while jvb will be stopped, configured and started again.
2017-03-17 14:49:10 -05:00
Saúl Ibarra Corretgé
92f58cb3c1 doc: add information about how to contribute
Inspired by the document at jitsi/jitsi.
2017-03-17 08:35:56 -05:00
Saúl Ibarra Corretgé
4ad98ca505 doc: fix typo 2017-03-17 10:48:38 +01:00
yanas
b9374bde6b Merge pull request #1383 from jitsi/fix-wrong-pass-on-auth
Fix incorrect password dialog message when using authentication.
2017-03-16 14:13:18 -05:00
pierreozoux
1ff29384b3 Add a network schemas
This is to help understanding how things are wired.
2017-03-16 11:30:28 -05:00
Saúl Ibarra Corretgé
4fc714ff10 lang: don't use " " for the default policy text
Firefox (at least) renders it verbatim.
2017-03-16 11:16:14 -05:00
damencho
51f0c8a388 Adds base dialog implementation. 2017-03-15 16:33:04 -05:00
damencho
d01a65f73d Fixes stats, using wrong object members in latest update. 2017-03-15 15:48:13 -05:00
damencho
65239f9ffe Adds frame rate to statistics bubble. 2017-03-15 13:31:42 -05:00
damencho
e5cefcce70 Updates transport type.
Updates transport type to show multiple values as we do for addresses and ports.
2017-03-14 15:51:11 -05:00
Ingo Bauersachs
8002b5ec6a Add Esperanto 2017-03-14 20:36:09 +01:00
jitsi-pootle
a575f5cc77 New files added from translate.jitsi.org based on templates 2017-03-14 19:44:37 +00:00
ibauersachs
ab3a80e076 Commit from translate.jitsi.org by user ibauersachs.: 317 of 317 strings translated (0 fuzzy). 2017-03-14 19:44:07 +00:00
ibauersachs
dda3798ba9 Commit from translate.jitsi.org by user ibauersachs.: 317 of 317 strings translated (0 fuzzy). 2017-03-14 19:43:50 +00:00
Lyubo Marinov
e6f906b9ca [RN] Fix undefined.avatarID in ParticipantView 2017-03-08 17:05:55 -06:00
hristoterezov
d74e43ddcc [RN] fix(Avatar): Match the implementation for web 2017-03-08 13:21:34 -06:00
Lyubo Marinov
23ddce122b Comply w/ coding style 2017-03-07 21:54:37 -06:00
hristoterezov
814bd26c07 feat(Avatar): Implement Avatar for web 2017-03-07 21:54:37 -06:00
damencho
2e4b39c19c Fixes loading jquery-i18next. 2017-03-07 17:30:20 -06:00
Дамян Минков
3ee65748bb Merge pull request #1382 from jitsi/load_error_handler_placeholder
Post load error handler
2017-03-07 17:29:30 -06:00
damencho
5f387737a1 Fix incorrect password dialog message when using authentication. 2017-03-07 16:42:36 -06:00
paweldomas
4fa800b87a feat(index.html): post load error handler
Adds a placeholder which allows to write a plugin for executing some
code after the "load error handler" is triggered. A function named
"postLoadErrorHandler" should be defined in one of
the "#include virtual" files.
2017-03-07 16:29:50 -06:00
Emil Ivov
9338b3cf94 Adds links to mobile builds 2017-03-07 15:44:10 -06:00
Lyubo Marinov
45e09af692 react-native 0.42.0 2017-03-07 15:09:39 -06:00
yanas
9d32f48ab8 [IOS Assets] Fixes launch screen 480x3 image 2017-03-07 13:12:26 -06:00
Lyubo Marinov
290e7baead Stick to the exact package versions for the direct dependencies 2017-03-02 21:46:43 -06:00
Любомир Маринов
e780ae00d0 Merge pull request #1360 from jitsi/move_avatar
ref(avatar): Move Avatar and ParticipantView to base/participants
2017-03-02 21:17:41 -06:00
hristoterezov
9ea224412d ref(avatar): Move Avatar and Participant view to base/participants 2017-03-02 16:57:43 -06:00
ibauersachs
cd8ae07698 Commit from translate.jitsi.org by user ibauersachs.: 317 of 317 strings translated (0 fuzzy). 2017-03-02 21:36:45 +00:00
Ingo Bauersachs
433a73e13d Add Norwegian Bokmal 2017-03-02 22:14:06 +01:00
jitsi-pootle
1e558f4da6 New files added from translate.jitsi.org based on templates 2017-03-02 21:21:41 +00:00
Lyubo Marinov
aef6e33c91 [RN] Fix remote JS debugging 2017-03-01 21:33:49 -06:00
Lyubo Marinov
acd83ede2f [RN] Third-party ES6 Symbol ponyfill 2017-03-01 21:31:43 -06:00
Lyubo Marinov
bd51613e62 [RN] Support the for...of statement in lib-jitsi-meet 2017-03-01 21:30:21 -06:00
ibauersachs
246cb39003 Commit from translate.jitsi.org by user ibauersachs.: 306 of 306 strings translated (0 fuzzy). 2017-03-01 21:07:11 +00:00
Lyubo Marinov
3b54c527b6 Remove obsolete source code 2017-02-28 23:22:03 -06:00
Lyubo Marinov
18368fefaa Comply w/ coding style 2017-02-28 23:22:02 -06:00
damencho
c361e1e31a Translate react strings.
Split language detectors to be web/native dependent. Take care of strings that contain html.
2017-02-28 13:16:42 -06:00
damencho
e3d4152e32 Adds react-i18next and its provider to react.
Adds translate function with default namespaces and options.
2017-02-28 13:13:47 -06:00
damencho
d861ba1876 Moves translation to react and use i18next language detectors. 2017-02-28 13:13:47 -06:00
George Politis
c942017b73 Merge pull request #1341 from saghul/doc-disableRtx
config: switch default disableRtx to false
2017-02-28 08:52:33 -06:00
Lyubo Marinov
743d12e326 Default to mobile app promotion 2017-02-28 00:18:52 -06:00
Lyubo Marinov
d371a3d5fd Fix TypeError: undefined is not an object 2017-02-28 00:18:52 -06:00
Lyubo Marinov
e1056126e6 Fix TypeError: undefined is not an object 2017-02-28 00:18:51 -06:00
Lyubo Marinov
72c267fbf3 Fix the human-readable text 2017-02-28 00:18:51 -06:00
Lyubo Marinov
0ed85b9d25 Replace features/unsupported-browser SET_UNSUPPORTED_BROWSER with features/base/lib-jitsi-meet SET_WEBRTC_READY
The error raised by JitsiMeetJS.init() is already in the state of
features/base/lib-jitsi-meet so it's not a good design to store the same
error in the state of features/unsupported-browser.
2017-02-28 00:18:51 -06:00
Ilya Daynatovich
a8877d82b6 Rename style component; Use of status codes instead of flags in conference init 2017-02-28 00:18:51 -06:00
Ilya Daynatovich
8896b0adf3 Fix problem with dialogs 2017-02-28 00:18:50 -06:00
Ilya Daynatovich
60b14e9b45 Some fixes mentioned in the PR 2017-02-28 00:18:50 -06:00
Ilya Daynatovich
631e853b40 Update register/unregister listeners logic of some components in the old app 2017-02-28 00:18:50 -06:00
Ilya Daynatovich
b409c8cc2f Fix reload regression 2017-02-27 21:50:21 -06:00
Ilya Daynatovich
905212b109 Enable flow for written code 2017-02-27 21:50:20 -06:00
Ilya Daynatovich
05b7df26e6 Add no mobile app component 2017-02-27 21:50:20 -06:00
Ilya Daynatovich
1268afd3f8 Added unsuported browser and plugin required pages 2017-02-27 21:50:20 -06:00
Lyubo Marinov
c1b9b7a623 Prevent undefined JitsiMeetJS 2017-02-27 21:35:34 -06:00
Lyubo Marinov
0b9160fb75 LIB_DID_DISPOSE, LIB_DID_INIT, LIB_WILL_DISPOSE, LIB_WILL_INIT 2017-02-27 16:45:53 -06:00
George Politis
93c9419392 Merge pull request #1362 from bgrozev/no-extension-on-ff-52
config: Don't require an extension for firefox >=52.
2017-02-27 16:36:31 -06:00
Boris Grozev
6121bcf171 config: Don't require an extension for firefox >=52.
Starting with firefox 52, no extension is required for screensharing.
See https://wiki.mozilla.org/Screensharing
2017-02-27 16:08:13 -06:00
Lyubo Marinov
702144180c Be consistent, simplify the source code 2017-02-25 19:00:35 -06:00
Lyubo Marinov
d2b2f98941 Fix typo 2017-02-25 18:58:23 -06:00
Lyubo Marinov
ec95956e25 [RN] Prepare for modifications to unsupported-browser 2017-02-24 13:08:49 -06:00
Lyubo Marinov
d6d7ce1b67 [RN] Move preferH264 where it will be in effect (in the future) 2017-02-24 12:59:30 -06:00
bgrozev
4cb36b0a5d Merge pull request #1337 from saghul/cleanup-adaptive-simulcast
cleanup: remove old adaptive simulcast config option
2017-02-24 11:43:22 -06:00
George Politis
2b3aea76a9 doc: Nukes influxdb.md because influx support has been nuked. 2017-02-23 17:22:06 -06:00
Lyubo Marinov
f50a31b4e8 [RN] Simplify the source code 2017-02-23 17:14:04 -06:00
Saúl Ibarra Corretgé
b226c3aca3 [RN] Fix loading config.js from URLs with a non-standard port
`host` contains the hostname:port portion, whereas `hostname` is just the
hostname, not including the port.
2017-02-23 17:14:04 -06:00
yanas
4979666a89 Merge pull request #1342 from jitsi/filmstriponly_transparent
fix(filmstriponly): Set the background to transparent
2017-02-23 16:11:35 -06:00
yanas
c9636f85b9 Merge pull request #1314 from virtuacoplenny/lenny/audio-slider
Volume slider for remote participant audio elements
2017-02-23 15:48:52 -06:00
hristoterezov
436bc87a86 fix(overlay): comments after review 2017-02-23 13:50:09 -06:00
Lyubo Marinov
e89c2b242d Android plugin for Gradle 2.2.3
Recent versions of the Android plugin for Gradle started to automatically
download the SDK build tools dependency if it is not installed already.
So it is no longer necessary to have the developer of the Android app
install the SDK build tools dependency in advance.
2017-02-23 13:14:19 -06:00
Leonard Kim
02b26a65bb Volume slider for remote participant audio elements 2017-02-23 09:01:40 -08:00
Дамян Минков
6a3e4bb59f Merge pull request #1311 from saghul/resources
Move miscellaneous files to resources
2017-02-23 06:42:15 -08:00
Saúl Ibarra Corretgé
b01ad360da Move miscellaneous files to resources 2017-02-23 10:01:19 +01:00
Ilya Daynatovich
c7f3740099 Fix IE redirect problem 2017-02-22 23:36:06 -06:00
Дамян Минков
554595acd7 Merge pull request #1348 from jitsi/restyle-range-inputs
Re-styles range inputs for Chrome and FF.
2017-02-22 21:44:46 -06:00
yanas
ee4ddd5446 Fixes indentation 2017-02-22 17:14:09 -06:00
yanas
ebab617a12 Re-styles range inputs for Chrome and FF. 2017-02-22 16:49:56 -06:00
Lyubo Marinov
bc5d92a452 [RN] Prefer H.264 2017-02-22 08:28:19 -06:00
Saúl Ibarra Corretgé
2f388dfb6a Fix warning about missing key prop
When rendering using a for loop each child whould have a key prop.
2017-02-22 12:08:12 +01:00
Boris Grozev
73a0197eb2 doc: Adds a note on installing ios-deploy on MacOS 10.11. 2017-02-21 14:43:59 -06:00
hristoterezov
b6990e9e5d fix(filmstriponly): Set the background to transparent 2017-02-21 13:45:46 -06:00
Lyubo Marinov
26e119bfc2 Comply w/ coding style 2017-02-21 13:33:25 -06:00
Saúl Ibarra Corretgé
9f866ae608 config: switch default disableRtx to false 2017-02-21 20:02:55 +01:00
Saúl Ibarra Corretgé
023359b9d2 [RN] Avoid rendering Container if not visible
This solves the issue of view clipping on Android, plus it seems to be the RN
convention unless the views are very large and memory hungry.
2017-02-21 11:13:36 -06:00
bgrozev
2128d047e1 Merge pull request #1339 from saghul/fix-doc-links
doc: fix markdown link syntax in mobile docs
2017-02-21 10:49:27 -06:00
Saúl Ibarra Corretgé
a89349c5b9 doc: fix markdown link syntax in mobile docs 2017-02-21 17:37:56 +01:00
Lyubo Marinov
d109b8beb6 Comply w/ coding style 2017-02-21 09:39:59 -06:00
Saúl Ibarra Corretgé
9b40572921 [RN] Fix Android immersive mode when coming from the background
Fixes an issue where immersive mode would be enabled when coming back from the
background on the welcome screen.

Re-fixes c57e713, which was not correct.
2017-02-21 09:13:30 -06:00
Lyubo Marinov
aaf7a38cce Detail comment 2017-02-21 09:09:07 -06:00
Saúl Ibarra Corretgé
1ed0759a50 [RN] Temporarily disable camera toggling button
It doesn't work properly and gives a very bad user experience. Disble it until
all underlying issues in react-native-webrtc are ironed out.
2017-02-21 08:33:31 -06:00
Saúl Ibarra Corretgé
213b73da6e cleanup: remove old adaptive simulcast config option 2017-02-20 15:46:41 +01:00
Saúl Ibarra Corretgé
5b6985fc5c [RN] Fix use of undefined APP
On RN we don't use the global APP object, so don't save the store there unless
it's defined, which is the case in the current web version. Also, check for
undefined explicitly, since a "if (!APP)" check will throw a ReferenceError.
2017-02-20 11:16:01 +01:00
Lyubomir Marinov
538af01bf5 Comply w/ coding style 2017-02-18 21:57:38 -06:00
hristoterezov
92d0589a37 ref(overlay): The overlays to use React 2017-02-18 17:03:50 -06:00
Lyubomir Marinov
f3269070b2 [iOS] iPad support 2017-02-18 11:36:14 -06:00
Lyubomir Marinov
d93bd3eda7 [RN] Use a default host when only a room name is specified
The mobile app remembers the domain which hosted the last conference. If
the user specified a full URL first and specified a room name only the
second time, it was not obvious that the second conference would be
hosted on the domain of the first conference.
2017-02-18 10:04:08 -06:00
Yana Stamcheva
0dbbc5d8b6 [Android] Circular app/launcher icon(s) 2017-02-17 13:54:01 -06:00
George Politis
08efd5ecab Merge pull request #1327 from saghul/doc-resolution
doc: document resolution config option and set it to 720 by default
2017-02-17 11:33:04 -06:00
Lyubomir Marinov
dba1bcb0e3 [RN] Increment short app version from 1.2 to 1.3
Now that Apple have approved build 1.2.199 for release in the App Store,
the short app version needs to be incremented; otherwise, no new builds
can be uploaded to TestFlight and, respectively, for release in the App
Store.
2017-02-17 09:32:24 -06:00
yanas
348403abff Merge pull request #1326 from jitsi/fix-manual-tooltips
Fixes manual triggered tooltips text and no hover.
2017-02-17 09:26:58 -06:00
Paweł Domas
c290cf45b7 Merge pull request #1328 from saghul/cleanup-adaptive-lastn
cleanup: remove adaptive las N config option
2017-02-17 09:14:47 -06:00
Saúl Ibarra Corretgé
175c8e6e50 cleanup: remove adaptive las N config option
The feature has been replaced so the option no longer applies.
2017-02-17 15:33:20 +01:00
Saúl Ibarra Corretgé
f90667b23c doc: document resolution config option and set it to 720 by default 2017-02-17 13:53:41 +01:00
damencho
cf69d591e4 Fixes manual triggered tooltips text and no hover. 2017-02-17 00:14:58 +02:00
Lyubomir Marinov
e599491583 Remove duplication 2017-02-16 15:17:05 -06:00
Lyubomir Marinov
d1520773cf Improve consistency 2017-02-16 15:16:17 -06:00
Saúl Ibarra Corretgé
573ca97b6c [RN] Add workaround for Android view clipping
Looks like Android gets confused as to what surface to blit when we hide or
show toolbars. Setting a border on the container, seems to force the entire
area to blit properly.

Other attempted approaches, with no success:
- zIndex of -100
- width and height of 0
- opacity of 0 and setting 'disabled' on touch containers

This patch applies the workaround in the welcome page and conference containers.
2017-02-16 14:37:42 -06:00
Lyubomir Marinov
0d97f14a1a flow 2017-02-16 13:59:28 -06:00
Lyubomir Marinov
b8f28abfdf [RN] Fix incorrect JitsiMeetJS.init error handling 2017-02-16 13:59:12 -06:00
Lyubomir Marinov
9ac7c97e67 [RN] Enforce mandatory mobile app-specific config 2017-02-16 13:58:39 -06:00
Lyubomir Marinov
52b3eaacb5 [RN] Fix passing config.js to JitsiMeetJS.init 2017-02-16 13:51:01 -06:00
ibauersachs
9b01ae6db9 Commit from translate.jitsi.org by user ibauersachs.: 306 of 306 strings translated (0 fuzzy). 2017-02-16 06:55:14 +00:00
Ingo Bauersachs
469487ad36 Add Chinese (China) 2017-02-16 07:39:57 +01:00
jitsi-pootle
176c3c1601 New files added from translate.jitsi.org based on templates 2017-02-16 06:46:38 +00:00
yanas
94391234cc Merge pull request #1322 from jitsi/livestream_help_link
livestream link help link
2017-02-15 22:35:57 -06:00
yanas
d84901f196 Font size adjustment and moving link to config param 2017-02-15 17:57:57 -06:00
yanas
c6b117565d Merge branch 'livestream_help_link' of https://github.com/jitsi/jitsi-meet into livestream_help_link 2017-02-15 16:41:49 -06:00
yanas
2a9124acb5 Merge pull request #1316 from jitsi/custom-button-tooltips
Uses tooltip to show custom popups for mute mic button.
2017-02-15 16:20:36 -06:00
Lyubomir Marinov
401a783d6a Coding style consistency
Includes automatic recommended Xcode project file fixes.
2017-02-15 13:48:56 -06:00
Lyubomir Marinov
39483a30b6 Polyfill Element.innerHTML
Lib-jitsi-meet uses jQuery's .append method to manipulate Jingle. The
method in question invokes the getter and setter of Element.innerHTML.
Unfortunately, xmldom which we use in React Native to polyfill DOM does
not polyfill Element.innerHTML. So polyfill it ourselves.
2017-02-15 13:18:21 -06:00
Lyubomir Marinov
0e2a07f8d7 Stick to react-native-background-timer 1.0.0
Recently expose-loader broke us when it updated from 0.7.1 to 0.7.2 and
we decided to stick to exact versions.
2017-02-15 13:18:20 -06:00
Saúl Ibarra Corretgé
36f5b0218d [RN] Fix running timers in the background
Turns out React Native's timers (setTimeout / setInterval) don't run while the
app is in the background: https://github.com/facebook/react-native/issues/167

This patch replaces the global timer functions with those from the
react-native-background-timer package, which work in the background.

These timers won't magically make an application work in the background, but
they will run if an application already happens to run in the background. That's
our case while in a conference, so these timers will run, allowing XMPP pings to
be sent and the conference to stay up as long as the user desires.
2017-02-15 11:50:54 -06:00
damencho
a1b3c56de7 Uses tooltip to show custom popups for mute mic button/shared video button. 2017-02-15 11:29:26 +02:00
Aaron van Meerten
6d664f133e Cosmetic fixes for column length lint errors 2017-02-14 17:11:31 -06:00
Aaron van Meerten
732a433ec1 livestream link to provide more context to users on where to retrieve their stream key 2017-02-14 16:07:12 -06:00
Lyubomir Marinov
f7dcd1ba2c Stick to expose-loader 0.7.1
0.7.2 causes a ReferenceError: jQuery is not defined in autosize.
2017-02-14 08:07:49 -06:00
Lyubomir Marinov
55a8b44224 Consistent middleware and reducer imports 2017-02-10 11:04:40 -06:00
Lyubomir Marinov
e29db31d91 Comply w/ coding style 2017-02-10 10:13:39 -06:00
Lyubomir Marinov
183d3c3ca4 Fix a possible undefined state usage 2017-02-10 00:47:55 -06:00
Saúl Ibarra Corretgé
c57e713696 [RN] Fix full-screen mode when coming back from the background
On Android the status and navigation bars are shown again after coming back from
the background, so enter full-screen mode again if needed.
2017-02-10 00:44:37 -06:00
Saúl Ibarra Corretgé
4519f26adf [RN] Mute local video when app is in the background 2017-02-10 00:44:37 -06:00
bgrozev
c26f9cc01f Merge pull request #1301 from jitsi/video-thumbnail-margin
Lower the margin between video thumbnails
2017-02-09 11:43:55 -06:00
yanas
f6f730b994 Lower the margin between video thumbnails 2017-02-06 15:34:05 -06:00
242 changed files with 7238 additions and 2570 deletions

View File

@@ -28,6 +28,8 @@ node_modules/react-native/flow
flow/
[options]
emoji=true
module.system=haste
experimental.strict_type_args=true
@@ -40,11 +42,11 @@ suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-7]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-7]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
unsafe.enable_getters_and_setters=true
[version]
^0.37.0
^0.38.0

1
.gitignore vendored
View File

@@ -53,7 +53,6 @@ yarn-error.log
#
buck-out/
\.buckd/
android/app/libs
*.keystore
# fastlane

View File

@@ -5,8 +5,14 @@ debian/
libs/
node_modules/
# The following are checked by ESLint which supersedes JSHint.
# The following are checked by ESLint with the maximum configuration which
# supersedes JSHint.
flow-typed/
react/
# The following are checked by ESLint with the minimum configuration which does
# not supersede JSHint but take advantage of advanced language features such as
# Facebook Flow which are not supported by JSHint.
modules/translation/translation.js
analytics.js

33
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,33 @@
# How to contribute
We would love to have your help. Before you start working however, please read
and follow this short guide.
# Reporting Issues
Before you open an issue on GitHub, please discuss it on one of our
[mailing lists](https://jitsi.org/Development/MailingLists) and wait for
confirmation from one of the committers. Once you have that confirmation,
please proceed to reporting the issue on GitHub, while providing as much
information as possible. Mention the version of Jitsi Meet, Jicofo and JVB
you are using, and explain (as detailed as you can) how the problem can
be reproduced.
# Code contributions
Found a bug and know how to fix it? Great! Please read on.
## Contributor License Agreement
While the Jitsi projects are released under the
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
ensure that we can continue making these projects available under an Open Source license,
we need you to sign our Apache-based contributor
license agreement as either a [corporation](https://jitsi.org/ccla) or an
[individual](https://jitsi.org/icla). If you cannot accept the terms laid out
in the agreement, unfortunately, we cannot accept your contribution.
## Creating Pull Requests
- Make sure your code passes the linter rules beforehand. The linter is exeuted
automatically when committing code.
- Perform **one** logical change per pull request.
- Maintain a clean list of commits, squash them if necessary.
- Rebase your topic branch on top of the master branch before creating the pull
request.

View File

@@ -55,7 +55,7 @@ deploy-local:
source-package:
mkdir -p source_package/jitsi-meet/css && \
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
rm -rf source_package

View File

@@ -19,6 +19,10 @@ You can download Debian/Ubuntu binaries:
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianTestingRepository))
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
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
Jitsi Meet uses [Browserify](http://browserify.org). If you want to make changes in the code you need to [install Browserify](http://browserify.org/#install). Browserify requires [nodejs](http://nodejs.org).
@@ -82,6 +86,11 @@ npm unlink lib-jitsi-meet
npm install
```
## Contributing
If you are looking to contribute to Jitsi Meet, first of all, thank you! Please
see our [guidelines for contributing](CONTRIBUTING.md).
## Embedding in external applications
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).

View File

@@ -91,7 +91,7 @@ android {
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt("${version}")
versionName "1.2.${version}"
versionName "1.3.${version}"
ndk {
abiFilters 'armeabi-v7a', 'x86'
}
@@ -139,6 +139,7 @@ if (project.hasProperty('JITSI_SIGNING')
}
dependencies {
compile project(':react-native-background-timer')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-vector-icons')

View File

@@ -29,7 +29,8 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:name=".MainActivity">
android:name=".MainActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -29,6 +29,7 @@ public class MainApplication extends Application implements ReactApplication {
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

View File

@@ -1,6 +1,8 @@
rootProject.name = 'jitsi-meet-react'
include ':app'
include ':react-native-background-timer'
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/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

@@ -20,6 +20,30 @@ import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
import {
AVATAR_ID_COMMAND,
AVATAR_URL_COMMAND,
conferenceFailed,
conferenceJoined,
conferenceLeft,
EMAIL_COMMAND
} from './react/features/base/conference';
import {
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
import {
changeParticipantAvatarID,
changeParticipantAvatarURL,
changeParticipantEmail,
participantJoined,
participantLeft,
participantRoleChanged
} from './react/features/base/participants';
import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
} from './react/features/overlay';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -46,12 +70,12 @@ import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
* Known custom conference commands.
*/
const commands = {
EMAIL: "email",
AVATAR_URL: "avatar-url",
AVATAR_ID: "avatar-id",
AVATAR_ID: AVATAR_ID_COMMAND,
AVATAR_URL: AVATAR_URL_COMMAND,
CUSTOM_ROLE: "custom-role",
EMAIL: EMAIL_COMMAND,
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
SHARED_VIDEO: "shared-video"
};
/**
@@ -91,7 +115,10 @@ function createInitialLocalTracksAndConnect(roomName) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
browser =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browser))
);
// First try to retrieve both audio and video.
let tryCreateLocalTracks = createLocalTracks(
@@ -109,8 +136,7 @@ function createInitialLocalTracksAndConnect(roomName) {
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
.then(([tracks, con]) => {
APP.UI.hideUserMediaPermissionsGuidanceOverlay();
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
if (audioAndVideoError) {
if (audioOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only
@@ -139,6 +165,29 @@ function sendData (command, value) {
room.sendCommand(command, {value: value});
}
/**
* Sets up initially the properties of the local participant - email, avatarID,
* avatarURL, displayName, etc.
*/
function _setupLocalParticipantProperties() {
const email = APP.settings.getEmail();
email && sendData(commands.EMAIL, email);
const avatarUrl = APP.settings.getAvatarUrl();
avatarUrl && sendData(commands.AVATAR_URL, avatarUrl);
if (!email && !avatarUrl) {
sendData(commands.AVATAR_ID, APP.settings.getAvatarId());
}
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
nick = APP.UI.askForNickname();
APP.settings.setDisplayName(nick);
}
nick && room.setDisplayName(nick);
}
/**
* Get user nickname by user id.
* @param {string} id user id
@@ -203,8 +252,8 @@ function maybeRedirectToWelcomePage(options) {
// save whether current user is guest or not, before navigating
// to close page
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
assignWindowLocationPathname(
options.feedbackSubmitted ? "close.html" : "close2.html");
assignWindowLocationPathname('static/'
+ (options.feedbackSubmitted ? "close.html" : "close2.html"));
return;
}
@@ -334,6 +383,7 @@ class ConferenceConnector {
this._reject(err);
}
_onConferenceFailed(err, ...params) {
APP.store.dispatch(conferenceFailed(room, err, ...params));
logger.error('CONFERENCE FAILED:', err, ...params);
APP.UI.hideRingOverLay();
switch (err) {
@@ -352,7 +402,7 @@ class ConferenceConnector {
case ConferenceErrors.NOT_ALLOWED_ERROR:
{
// let's show some auth not allowed page
assignWindowLocationPathname('authError.html');
assignWindowLocationPathname('static/authError.html');
}
break;
@@ -408,8 +458,6 @@ class ConferenceConnector {
// the app. Both the errors above are unrecoverable from the library
// perspective.
room.leave().then(() => connection.disconnect());
APP.UI.showPageReloadOverlay(
false /* not a network type of failure */, err);
break;
case ConferenceErrors.CONFERENCE_MAX_USERS:
@@ -466,6 +514,23 @@ function disconnect() {
return Promise.resolve();
}
/**
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
*
* @param {JitsiMeetJS.connection.error} error - The reported error.
* @returns {void}
* @private
*/
function _connectionFailedHandler(error) {
if (isFatalJitsiConnectionError(error)) {
APP.connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED,
_connectionFailedHandler);
if (room)
room.leave();
}
}
export default {
isModerator: false,
audioMuted: false,
@@ -518,11 +583,13 @@ export default {
return createInitialLocalTracksAndConnect(options.roomName);
}).then(([tracks, con]) => {
logger.log('initialized with %s local tracks', tracks.length);
con.addEventListener(
ConnectionEvents.CONNECTION_FAILED,
_connectionFailedHandler);
APP.connection = connection = con;
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
APP.remoteControl.init();
this._bindConnectionFailedHandler(con);
this._createRoom(tracks);
if (UIUtil.isButtonEnabled('contacts')
@@ -561,47 +628,6 @@ export default {
isLocalId (id) {
return this.getMyUserId() === id;
},
/**
* Binds a handler that will handle the case when the connection is dropped
* in the middle of the conference.
* @param {JitsiConnection} connection the connection to which the handler
* will be bound to.
* @private
*/
_bindConnectionFailedHandler (connection) {
const handler = function (error, errMsg) {
/* eslint-disable no-case-declarations */
switch (error) {
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
case ConnectionErrors.OTHER_ERROR:
case ConnectionErrors.SERVER_ERROR:
logger.error("XMPP connection error: " + errMsg);
// From all of the cases above only CONNECTION_DROPPED_ERROR
// is considered a network type of failure
const isNetworkFailure
= error === ConnectionErrors.CONNECTION_DROPPED_ERROR;
APP.UI.showPageReloadOverlay(
isNetworkFailure,
"xmpp-conn-dropped:" + errMsg);
connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED, handler);
// FIXME it feels like the conference should be stopped
// by lib-jitsi-meet
if (room)
room.leave();
break;
}
/* eslint-enable no-case-declarations */
};
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, handler);
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
@@ -676,7 +702,7 @@ export default {
* false.
*/
isCallstatsEnabled () {
return room.isCallstatsEnabled();
return room && room.isCallstatsEnabled();
},
/**
* Sends the given feedback through CallStats if enabled.
@@ -688,6 +714,18 @@ export default {
sendFeedback (overallFeedback, detailedFeedback) {
return room.sendFeedback (overallFeedback, detailedFeedback);
},
/**
* Get speaker stats that track total dominant speaker time.
*
* @returns {object} A hash with keys being user ids and values being the
* library's SpeakerStats model used for calculating time as dominant
* speaker.
*/
getSpeakerStats() {
return room.getSpeakerStats();
},
/**
* Returns the connection times stored in the library.
*/
@@ -703,6 +741,39 @@ export default {
return this._room
&& this._room.getConnectionState();
},
/**
* Obtains current P2P ICE connection state.
* @return {string|null} ICE connection state or <tt>null</tt> if there's no
* P2P connection
*/
getP2PConnectionState () {
return this._room
&& this._room.getP2PConnectionState();
},
/**
* Starts P2P (for tests only)
* @private
*/
_startP2P () {
try {
this._room && this._room.startP2PSession();
} catch (error) {
logger.error("Start P2P failed", error);
throw error;
}
},
/**
* Stops P2P (for tests only)
* @private
*/
_stopP2P () {
try {
this._room && this._room.stopP2PSession();
} catch (error) {
logger.error("Stop P2P failed", error);
throw error;
}
},
/**
* Checks whether or not our connection is currently in interrupted and
* reconnect attempts are in progress.
@@ -887,21 +958,7 @@ export default {
this.invite = new Invite(room);
this._room = room; // FIXME do not use this
let email = APP.settings.getEmail();
email && sendData(this.commands.defaults.EMAIL, email);
let avatarUrl = APP.settings.getAvatarUrl();
avatarUrl && sendData(this.commands.defaults.AVATAR_URL,
avatarUrl);
!email && sendData(
this.commands.defaults.AVATAR_ID, APP.settings.getAvatarId());
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
nick = APP.UI.askForNickname();
APP.settings.setDisplayName(nick);
}
nick && room.setDisplayName(nick);
_setupLocalParticipantProperties();
this._setupListeners();
},
@@ -1128,11 +1185,17 @@ export default {
_setupListeners () {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
APP.store.dispatch(conferenceJoined(room));
APP.UI.mucJoined();
APP.API.notifyConferenceJoined(APP.conference.roomName);
APP.UI.markVideoInterrupted(false);
});
room.on(
ConferenceEvents.CONFERENCE_LEFT,
(...args) => APP.store.dispatch(conferenceLeft(room, ...args)));
room.on(
ConferenceEvents.AUTH_STATUS_CHANGED,
function (authEnabled, authLogin) {
@@ -1146,6 +1209,12 @@ export default {
if (user.isHidden())
return;
APP.store.dispatch(participantJoined({
id,
name: user.getDisplayName(),
role: user.getRole()
}));
logger.log('USER %s connnected', id, user);
APP.API.notifyUserJoined(id);
APP.UI.addUser(user);
@@ -1154,6 +1223,7 @@ export default {
APP.UI.updateUserRole(user);
});
room.on(ConferenceEvents.USER_LEFT, (id, user) => {
APP.store.dispatch(participantLeft(id, user));
logger.log('USER %s LEFT', id, user);
APP.API.notifyUserLeft(id);
APP.UI.removeUser(id, user.getDisplayName());
@@ -1162,6 +1232,7 @@ export default {
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
APP.store.dispatch(participantRoleChanged(id, role));
if (this.isLocalId(id)) {
logger.info(`My role changed, new role: ${role}`);
if (this.isModerator !== room.isModerator()) {
@@ -1229,7 +1300,8 @@ export default {
room.on(ConferenceEvents.TALK_WHILE_MUTED, () => {
APP.UI.showToolbar(6000);
UIUtil.animateShowElement($("#talkWhileMutedPopup"), true, 5000);
APP.UI.showCustomToolbarPopup('#talkWhileMutedPopup', true, 5000);
});
/*
@@ -1244,8 +1316,9 @@ export default {
*/
room.on(
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
APP.UI.handleLastNEndpoints(ids, enteringIds);
APP.UI.handleLastNEndpoints(ids, enteringIds);
});
room.on(
ConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
(id, isActive) => {
@@ -1364,6 +1437,7 @@ export default {
});
room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
APP.store.dispatch(suspendDetected());
// After wake up, we will be in a state where conference is left
// there will be dialog shown to user.
// We do not want video/audio as we show an overlay and after it
@@ -1384,9 +1458,6 @@ export default {
if (localAudio) {
localAudio.dispose();
}
// show overlay
APP.UI.showSuspendedOverlay();
});
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {
@@ -1424,17 +1495,22 @@ export default {
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.store.dispatch(changeParticipantEmail(from, data.value));
APP.UI.setUserEmail(from, data.value);
});
room.addCommandListener(
this.commands.defaults.AVATAR_URL,
(data, from) => {
APP.store.dispatch(
changeParticipantAvatarURL(from, data.value));
APP.UI.setUserAvatarUrl(from, data.value);
});
room.addCommandListener(this.commands.defaults.AVATAR_ID,
(data, from) => {
APP.store.dispatch(
changeParticipantAvatarID(from, data.value));
APP.UI.setUserAvatarID(from, data.value);
});
@@ -1845,6 +1921,7 @@ export default {
if (email === APP.settings.getEmail()) {
return;
}
APP.store.dispatch(changeParticipantEmail(room.myUserId(), email));
APP.settings.setEmail(email);
APP.UI.setUserEmail(room.myUserId(), email);
@@ -1861,6 +1938,7 @@ export default {
if (url === APP.settings.getAvatarUrl()) {
return;
}
APP.store.dispatch(changeParticipantAvatarURL(room.myUserId(), url));
APP.settings.setAvatarUrl(url);
APP.UI.setUserAvatarUrl(room.myUserId(), url);
@@ -1896,5 +1974,18 @@ export default {
*/
removeListener (eventName, listener) {
eventEmitter.removeListener(eventName, listener);
},
/**
* Checks if the participant given by participantId is currently in the
* last N set if there's one supported.
*
* @param participantId the identifier of the participant
* @returns {boolean} {true} if the participant given by the participantId
* is currently in the last N set or if there's no last N set at this point
* and {false} otherwise
*/
isInLastN (participantId) {
return room.isInLastN(participantId);
}
};

View File

@@ -20,6 +20,13 @@ var config = { // eslint-disable-line no-unused-vars
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
//defaultSipNumber: '', // Default SIP number
// The STUN servers that will be used in the peer to peer connections
p2pStunServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
{ urls: "stun:stun2.l.google.com:19302" }
],
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: null,
// Whether desktop sharing should be disabled on Chrome.
@@ -40,7 +47,7 @@ var config = { // eslint-disable-line no-unused-vars
// up to and including 41. On Firefox 42 and higher, we will run without the
// extension.
// If set to -1, an extension will be required for all versions of Firefox.
desktopSharingFirefoxMaxVersionExtRequired: -1,
desktopSharingFirefoxMaxVersionExtRequired: 51,
// The URL to the Firefox extension for desktop sharing.
desktopSharingFirefoxExtensionURL: null,
@@ -53,8 +60,6 @@ var config = { // eslint-disable-line no-unused-vars
disableStats: false,
disableAudioLevels: false,
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
//disableAdaptiveSimulcast: false,
enableRecording: false,
enableWelcomePage: true,
//enableClosePage: false, // enabling the close page will ignore the welcome
@@ -79,6 +84,17 @@ var config = { // eslint-disable-line no-unused-vars
enableUserRolesBasedOnToken: false,
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
disableSuspendVideo: true,
// disables or enables RTX (RFC 4588).
disableRtx: true
// disables or enables RTX (RFC 4588) (defaults to false).
disableRtx: false,
// Sets the preferred resolution (height) for local video. Defaults to 360.
resolution: 720,
// Enables peer to peer mode. When enabled system will try to establish
// direct connection given that there are exactly 2 participants in
// the room. If that succeeds the conference will stop sending data through
// the JVB and use the peer to peer connection instead. When 3rd participant
// joins the conference will be moved back to the JVB connection.
//enableP2P: true
// How long we're going to wait, before going back to P2P after
// the 3rd participant has left the conference (to filter out page reload)
//backToP2PDelay: 5
};

View File

@@ -4,6 +4,14 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
import AuthHandler from './modules/UI/authentication/AuthHandler';
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
import {
connectionEstablished,
connectionFailed
} from './react/features/base/connection';
import {
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -67,6 +75,18 @@ function connect(id, password, roomName) {
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
);
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
function connectionFailedHandler(error, errMsg) {
APP.store.dispatch(connectionFailed(connection, error, errMsg));
if (isFatalJitsiConnectionError(error)) {
connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
}
}
function unsubscribe() {
connection.removeEventListener(
@@ -80,6 +100,7 @@ function connect(id, password, roomName) {
}
function handleConnectionEstablished() {
APP.store.dispatch(connectionEstablished(connection));
unsubscribe();
resolve(connection);
}

View File

@@ -3,21 +3,25 @@
user-select: none;
}
html, body{
margin:0px;
height:100%;
color: $defaultColor;
body {
margin: 0px;
width: 100%;
height: 100%;
font-size: 12px;
font-weight: 400;
background: #000000;
overflow: hidden;
color: $defaultColor;
background: $defaultBackground;
&.filmstrip-only {
background: transparent;
}
}
p {
margin: 0;
}
html, body, input, textarea, keygen, select, button {
body, input, textarea, keygen, select, button {
font-family: $baseFontFamily !important;
}
@@ -122,6 +126,7 @@ form {
z-index: $tooltipsZ;
&-inner {
background-color: $tooltipBg;
max-width: 350px;
}
&-arrow {

View File

@@ -1,34 +1,10 @@
/*Initialize*/
ul.loginmenu {
font-family: $baseFontFamily;
line-height: normal;
display:none;
div.loginmenu {
position: absolute;
margin: 0;
padding: 5px;
padding-bottom: 7px;
top: 45px;
left: -5px;
background-color: rgba(0,0,0,0.9);
border: 1px solid rgba(256, 256, 256, 0.2);
border-radius:8px;
}
ul.loginmenu li {
list-style-type: none;
padding: 7px;
color: #fff;
font-size: 11pt;
cursor: default;
white-space: pre;
}
ul.loginmenu:after {
content: url('../images/dropdownPointer.png');
display: block;
position: absolute;
top: -7px;
left: 18px;
top: 40px;
left: 20px;
}
a.disabled {
@@ -36,23 +12,7 @@ a.disabled {
pointer-events: none;
}
.loginmenuPadding {
width: 50px;
height: 30px;
position: absolute;
top: -30px;
left: 0px;
}
.loginmenu.extendedToolbarPopup {
left: 55px;
top: 0px;
}
ul.loginmenu.extendedToolbarPopup:after {
content: url('../images/leftDropdownPointer.png');
display: block;
position: absolute;
top: 18px;
left: -7px;
top: 20px;
left: 40px;
}

View File

@@ -2,52 +2,52 @@
* Animation mixin.
*/
@mixin animation($animate...) {
$max: length($animate);
$animations: '';
$max: length($animate);
$animations: '';
@for $i from 1 through $max {
$animations: #{$animations + nth($animate, $i)};
@for $i from 1 through $max {
$animations: #{$animations + nth($animate, $i)};
@if $i < $max {
$animations: #{$animations + ", "};
@if $i < $max {
$animations: #{$animations + ", "};
}
}
}
-webkit-animation: $animations;
-moz-animation: $animations;
-o-animation: $animations;
animation: $animations;
-webkit-animation: $animations;
-moz-animation: $animations;
-o-animation: $animations;
animation: $animations;
}
@mixin flex() {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
/**
* Keyframes mixin.
*/
@mixin keyframes($animationName) {
@-webkit-keyframes #{$animationName} {
@content;
}
@-moz-keyframes #{$animationName} {
@content;
}
@-o-keyframes #{$animationName} {
@content;
}
@keyframes #{$animationName} {
@content;
}
@-webkit-keyframes #{$animationName} {
@content;
}
@-moz-keyframes #{$animationName} {
@content;
}
@-o-keyframes #{$animationName} {
@content;
}
@keyframes #{$animationName} {
@content;
}
}
@mixin circle($diameter) {
width: $diameter;
height: $diameter;
border-radius: 50%;
width: $diameter;
height: $diameter;
border-radius: 50%;
}
/**
@@ -60,10 +60,10 @@
}
@mixin absoluteAligning() {
top: 50%;
left: 50%;
position: absolute;
@include transform(translate(-50%, -50%));
top: 50%;
left: 50%;
position: absolute;
@include transform(translate(-50%, -50%));
}
/**
@@ -75,81 +75,115 @@
}
@mixin transform($func) {
-moz-transform: $func;
-ms-transform: $func;
-webkit-transform: $func;
-o-transform: $func;
transform: $func;
-moz-transform: $func;
-ms-transform: $func;
-webkit-transform: $func;
-o-transform: $func;
transform: $func;
}
@mixin transition($transition...) {
-moz-transition: $transition;
-o-transition: $transition;
-webkit-transition: $transition;
transition: $transition;
-moz-transition: $transition;
-o-transition: $transition;
-webkit-transition: $transition;
transition: $transition;
}
/**
* Mixin styling placeholder
**/
* Mixin styling a placeholder.
**/
@mixin placeholder() {
$selectors: (
"::-webkit-input-placeholder",
"::-moz-placeholder",
":-moz-placeholder",
":-ms-input-placeholder"
);
$selectors: (
"::-webkit-input-placeholder",
"::-moz-placeholder",
":-moz-placeholder",
":-ms-input-placeholder"
);
@each $selector in $selectors {
#{$selector} {
@content;
@each $selector in $selectors {
#{$selector} {
@content;
}
}
}
/**
* Mixin styling a slider track for different browsers.
**/
@mixin slider() {
$selectors: (
"input[type=range]::-webkit-slider-runnable-track",
"input[type=range]::-moz-range-track",
"input[type=range]::-ms-track"
);
@each $selector in $selectors {
#{$selector} {
@content;
}
}
}
/**
* Mixin styling a slider thumb for different browsers.
**/
@mixin slider-thumb() {
$selectors: (
"input[type=range]::-webkit-slider-thumb",
"input[type=range]::-moz-range-thumb",
"input[type=range]::-ms-thumb"
);
@each $selector in $selectors {
#{$selector} {
@content;
}
}
}
@mixin box-shadow($h, $y, $blur, $color, $inset: false) {
@if $inset {
-webkit-box-shadow: inset $h $y $blur $color;
-moz-box-shadow: inset $h $y $blur $color;
box-shadow: inset $h $y $blur $color;
} @else {
-webkit-box-shadow: $h $y $blur $color;
-moz-box-shadow: $h $y $blur $color;
box-shadow: $h $y $blur $color;
}
@if $inset {
-webkit-box-shadow: inset $h $y $blur $color;
-moz-box-shadow: inset $h $y $blur $color;
box-shadow: inset $h $y $blur $color;
} @else {
-webkit-box-shadow: $h $y $blur $color;
-moz-box-shadow: $h $y $blur $color;
box-shadow: $h $y $blur $color;
}
}
@mixin no-box-shadow {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
@mixin box-sizing($box-model) {
-webkit-box-sizing: $box-model; // Safari <= 5
-moz-box-sizing: $box-model; // Firefox <= 19
box-sizing: $box-model;
-webkit-box-sizing: $box-model; // Safari <= 5
-moz-box-sizing: $box-model; // Firefox <= 19
box-sizing: $box-model;
}
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
border-radius: $radius;
/* stops bg color from leaking outside the border: */
background-clip: padding-box;
-webkit-border-radius: $radius;
border-radius: $radius;
/* stops bg color from leaking outside the border: */
background-clip: padding-box;
}
@mixin opacity($opacity) {
opacity: $opacity;
$opacity-ie: $opacity * 100;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=$opacity-ie);
filter: alpha(opacity=$opacity-ie); //IE8
opacity: $opacity;
$opacity-ie: $opacity * 100;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=$opacity-ie);
filter: alpha(opacity=$opacity-ie); //IE8
}
@mixin text-truncate {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/**
@@ -157,5 +191,5 @@
* (opacity) value.
*/
@mixin transparentBg($color, $alpha) {
background-color: rgba(red($color), green($color), blue($color), $alpha);
background-color: rgba(red($color), green($color), blue($color), $alpha);
}

View File

@@ -1,11 +1,12 @@
#notice {
.notice {
position: relative;
z-index: 3;
margin-top: 6px;
&__message {
background-color: #000000;
color: white;
padding: 3px;
border-radius: 5px;
}
}
#noticeText {
background-color: #000000;
color: white;
padding: 3px;
border-radius: 5px;
}

View File

@@ -23,7 +23,8 @@
}
// Link Appearance
&__link {
&__link,
&__contents {
display: block;
box-sizing: border-box;
text-decoration: none;
@@ -40,11 +41,16 @@
}
}
&__text {
&__text,
&__slider {
display: inline-block;
vertical-align: middle;
}
&__slider {
width: 50px;
}
&__icon {
vertical-align: middle;
position: relative;

View File

@@ -1,10 +1,3 @@
html, body {
width: 100%;
height:100%;
color: $defaultColor;
background: $defaultBackground;
}
.redirectPageMessage {
width: 30%;
margin: 20% auto;

View File

@@ -16,7 +16,7 @@ $defaultToolbarSize: 50px;
$thumbnailToolbarHeight: 22px;
$thumbnailIndicatorBorder: 2px;
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 5px;
$thumbnailVideoMargin: 2px;
$thumbnailsBorder: 2px;
$thumbnailVideoBorder: 2px;
$hideFilmstripButtonWidth: 17px;
@@ -129,8 +129,13 @@ $linkFontColor: #489afe;
$linkHoverFontColor: #287ade;
/**
* Landing
* Unsupported browser
*/
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
$unsupportedBrowserButtonBgColor: #ff9a00;
$unsupportedBrowserTextColor: #4a4a4a;
$unsupportedBrowserTextSmallFontSize: 17px;
$unsupportedBrowserTitleColor: #fff;
$unsupportedBrowserTitleFontSize: 24px;
$unsupportedDesktopBrowserTextColor: rgba(255, 255, 255, 0.7);
$unsupportedDesktopBrowserTextFontSize: 21px;

View File

@@ -0,0 +1,41 @@
/**
* Disable the default webkit styles for range inputs (sliders).
*/
input[type=range]{
-webkit-appearance: none;
background: none;
}
/**
* Disable the default focus styles for webkit range inputs (sliders).
*/
input[type=range]:focus {
outline: none;
}
/**
* Include the mixin for a range input style.
*/
@include slider {
background: $sliderTrackBackground;
border: none;
border-radius: 3px;
cursor: pointer;
height: 6px;
width: 100%;
}
/**
* Include the mixin for a range input thumb style.
*/
@include slider-thumb {
-webkit-appearance: none;
background: white;
border: 1px solid $sliderThumbBackground;
border-radius: 50%;
box-shadow: 0px 0px 1px $sliderThumbBackground;
cursor: pointer;
height: 14px;
margin-top: -4px;
width: 14px;
}

View File

@@ -8,4 +8,13 @@
text-decoration: underline;
@include transition(color .1s ease-in);
}
}
/**
* Helper links are links that are meant to open a documentation page or more
* detailed info.
*/
.helper-link {
@extend .link;
font-size: 12px;
}

View File

@@ -39,6 +39,7 @@
@import 'reload_overlay/reload_overlay';
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'modals/speaker_stats/speaker_stats';
@import 'videolayout_default';
@import 'notice';
@import 'popup_menu';
@@ -60,14 +61,14 @@
@import 'components/link';
@import 'shortcuts/main';
@import 'components/button-control';
@import 'components/_input-control.scss';
@import 'components/input-control';
@import 'components/input-slider';
@import "modals/invite/invite";
@import "connection-info";
@import 'aui-components/dropdown';
@import '404';
@import 'policy';
@import 'filmstrip';
@import 'unsupported-browser/unsupported-desktop-browser';
@import 'unsupported-browser/unsupported-mobile-browser';
@import 'unsupported-browser/main';
/* Modules END */

View File

@@ -0,0 +1,56 @@
.speaker-stats {
list-style: none;
padding: 0;
color: $auiDialogColor;
width: 100%;
font-weight: 500;
.speaker-stats-item__status-dot {
position: relative;
display: block;
width: 9px;
height: 9px;
border-radius: 50%;
margin: 0 auto;
&.status-active {
background: green;
}
&.status-inactive {
background: gray;
}
}
.status-user-left {
color: $placeHolderColor;
}
.speaker-stats-item__status,
.speaker-stats-item__name,
.speaker-stats-item__time {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.speaker-stats-item__status {
width: 5%;
}
.speaker-stats-item__name {
width: 40%;
}
.speaker-stats-item__time {
width: 55%;
}
.speaker-stats-item:nth-child(even) {
background: whitesmoke;
}
.speaker-stats-item__name,
.speaker-stats-item__time {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -4,7 +4,7 @@
line-height: 20px;
}
.reload_overlay_msg {
.reload_overlay_text {
display: block;
font-size: 12px;
line-height: 30px;
@@ -13,4 +13,4 @@
#reloadProgressBar {
width: 180px;
margin: 5px auto;
}
}

View File

@@ -8,6 +8,8 @@ $baseLight: #FFFFFF;
*/
$controlBackground: $baseLight;
$controlColor: #333333;
$sliderTrackBackground: #474747;
$sliderThumbBackground: #3572b0;
/**
* Buttons

View File

@@ -0,0 +1,3 @@
@import 'no-mobile-app';
@import 'unsupported-desktop-browser';
@import 'unsupported-mobile-browser';

View File

@@ -0,0 +1,21 @@
.no-mobile-app {
margin: 30% auto 0;
max-width: 25em;
text-align: center;
width: auto;
&__title {
border-bottom: 1px solid $auiBorderColor;
color: $unsupportedBrowserTitleColor;
font-weight: 400;
letter-spacing: 0.5px;
padding-bottom: em(17, 24);
}
&__description {
font-size: $unsupportedBrowserTextSmallFontSize;
font-weight: 300;
letter-spacing: 1px;
margin-top: 1em;
}
}

View File

@@ -1,132 +1,39 @@
.supported-browser {
color: #929391;
display: inline-block;
font-size: 20px;
margin: 1em 7px;
vertical-align: middle;
width: 138px;
.unsupported-desktop-browser {
@include absoluteAligning();
&__button {
background-color: #62c82a;
border: 1px solid #3c8117;
border-radius: 10px;
color: #FFFFFF;
font-size: 12px;
height: 26px;
margin: 15px auto 0px auto;
padding-top: 13px;
text-align: center;
width: 115px;
display: block;
text-align: center;
&__title {
color: $unsupportedBrowserTitleColor;
font-weight: 300;
font-size: $unsupportedBrowserTitleFontSize;
letter-spacing: 1px;
}
&__description {
color: $unsupportedDesktopBrowserTextColor;
font-size: $unsupportedDesktopBrowserTextFontSize;
font-weight: 300;
letter-spacing: 1px;
margin-top: 16px;
&_small {
@extend .unsupported-desktop-browser__description;
font-size: $unsupportedBrowserTextSmallFontSize;
}
}
&__link {
color: #087dba;
text-decoration: none;
color: $linkFontColor;
@include transition(color .1s ease-out);
&:hover {
color: $linkHoverFontColor;
cursor: pointer;
text-decoration: none;
@include transition(color .1s ease-in);
}
&:active {
text-decoration: none;
}
&:focus {
text-decoration: none;
}
}
&-list
{
margin: 0 auto;
}
&__logo {
margin: 20px auto 0px auto;
&_chrome {
background-image: url('../../images/chrome.png');
height: 78px;
width: 78px;
}
&_chromium {
background-image: url('../../images/chromium.png');
height: 78px;
width: 77px;
}
&_firefox {
background-image: url('../../images/firefox.png');
height: 80px;
width: 86px;
}
&_opera {
background-image: url('../../images/opera.png');
height: 78px;
width: 73px;
}
&_ie {
background-image: url('../../images/ie.png');
height: 78px;
width: 80px;
}
&_safari {
background-image: url('../../images/safari.png');
height: 79px;
width: 78px;
}
}
&__text
{
line-height: 1.2em;
&_small {
font-size: small;
}
}
&__tile {
background-color: #e8e8e8;
border: 1px solid #cfcfcf;
border-radius: 10px;
height: 163px;
margin-top: 5px;
width: 138px;
}
}
.unsupported-desktop-browser {
display: block;
height: 565px;
margin: auto;
overflow:hidden;
position: absolute;
text-align: center;
top: 0; left: 0; bottom: 0; right: 0;
width:500px;
&__page {
display:inline-block;
font-size: 28px;
padding-top: 25px;
vertical-align:middle;
}
&__title {
margin: 0 auto;
width: 20em;
}
&-wrapper {
background: #fff;
display: block;
height: 100%;
position: absolute;
width: 100%;
}
}

View File

@@ -1 +1 @@
prosody-plugins/ /usr/share/jitsi-meet/
resources/prosody-plugins/ /usr/share/jitsi-meet/

View File

@@ -65,7 +65,7 @@ case "$1" in
# SSL for nginx
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
UPLOADED_CERT_CHOICE="A certificate is available and the files are uploaded on the server"
UPLOADED_CERT_CHOICE="I want to use my own certificate"
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
@@ -223,6 +223,13 @@ case "$1" in
invoke-rc.d apache2 reload
fi
echo "----------------"
echo ""
echo "You can now switch to a Lets Encrypt certificate. To do so, execute:"
echo "/usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh"
echo ""
echo "----------------"
# and we're done with debconf
db_stop
;;

View File

@@ -1,9 +1,10 @@
Template: jitsi-meet/cert-choice
Type: select
__Choices: Self-signed certificate will be generated, A certificate is available and the files are uploaded on the server
__Choices: Generate a new self-signed certificate (You will later get a chance to obtain a Let's encrypt certificate), I want to use my own certificate
_Description: SSL certificate for the Jitsi Meet instance
Jitsi Meet is best to be set up with an SSL certificate.
Having no certificate, a self-signed one will be generated.
By choosing self-signed you will later have a chance to install Lets Encrypt certificates.
Having a certificate signed by a recognised CA, it can be uploaded on the server
and point its location. The default filenames will be /etc/ssl/--domain.name--.key
for the key and /etc/ssl/--domain.name--.crt for the certificate.

View File

@@ -1,11 +1,14 @@
*.js /usr/share/jitsi-meet/
interface_config.js /usr/share/jitsi-meet/
logging_config.js /usr/share/jitsi-meet/
*.json /usr/share/jitsi-meet/
*.html /usr/share/jitsi-meet/
*.ico /usr/share/jitsi-meet/
libs /usr/share/jitsi-meet/
static /usr/share/jitsi-meet/
css/all.css /usr/share/jitsi-meet/css/
sounds /usr/share/jitsi-meet/
fonts /usr/share/jitsi-meet/
images /usr/share/jitsi-meet/
lang /usr/share/jitsi-meet/
connection_optimization /usr/share/jitsi-meet/
resources/*.sh /usr/share/jitsi-meet/scripts/

View File

@@ -20,13 +20,13 @@ msgstr ""
#. Type: select
#. Choices
#: ../jitsi-meet-web-config.templates:1001
msgid "Self-signed certificate will be generated"
msgid "Generate a new self-signed certificate (You will later get a chance to obtain a Let's encrypt certificate)"
msgstr ""
#. Type: select
#. Choices
#: ../jitsi-meet-web-config.templates:1001
msgid "A certificate is available and the files are uploaded on the server"
msgid "I want to use my own certificate"
msgstr ""
#. Type: select

View File

@@ -40,6 +40,14 @@ var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
```
You can also pass jwt token to Jitsi Meet:
```javascript
var jwt = "<jwt_token>";
var noSsl = false;
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite, noSsl, jwt);
```
## Controlling the embedded Jitsi Meet Conference
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:

View File

@@ -20,7 +20,7 @@ server {
root /usr/share/jitsi-meet;
index index.html index.htm;
error_page 404 /404.html;
error_page 404 /static/404.html;
location /config.js {
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;

View File

@@ -29,6 +29,8 @@
Allow from all
</Directory>
ErrorDocument 404 /static/404.html
Alias "/config.js" "/etc/jitsi/meet/jitsi-meet.example.com-config.js"
<Location /config.js>
Require all granted

View File

@@ -1,29 +0,0 @@
# Overview
Jitsi Meet supports logging to an [InfluxDB](http://influxdb.com/) database.
# Configuration
The following needs to be done to enable this functionality.
## Install InfluxDB
The details are outside the scope of the document, see http://influxdb.com/download/ .
## Create an InfluxDB database
Use the InfluxDB admin interface (running on port 8083) and create a database. In this example we name it <code>jitsi_database</code>
## Enable logging for Jitsi Videobridge
Add the following properties to <code>/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties</code>.
- org.jitsi.videobridge.log.INFLUX_DB_ENABLED=true
- org.jitsi.videobridge.log.INFLUX_URL_BASE=http://influxdb.example.com:8086
- org.jitsi.videobridge.log.INFLUX_DATABASE=jitsi_database
- org.jitsi.videobridge.log.INFLUX_USER=user
- org.jitsi.videobridge.log.INFLUX_PASS=pass
## Enable logging for Jicofo
Add the same properties as above to <code>/usr/share/jicofo/.sip-communicator/sip-communicator.properties</code>.
## Enable logging for Jitsi Meet itself
Change "logStats" to "true" in <code>/etc/jitsi/meet/you-domain.config.js</code> or the <code>config.js</code> file used in your installation.
# User interface
You can explore the database using the [Jiloin](https://github.com/jitsi/jiloin) web interface.

View File

@@ -6,6 +6,34 @@ change references to that to match your host, and generate some passwords for
There are also some complete [example config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/) available, mentioned in each section.
## Network description
This how the network look like:
```
+ +
| |
| |
v |
443 |
+-------+ |
| | |
| NginX | |
| | |
+--+-+--+ |
| | |
+------------+ | | +--------------+ |
| | | | | | |
| jitsi-meet +<---+ +--->+ prosody/xmpp | |
| |files 5280 | | |
+------------+ +--------------+ v
5222,5347^ ^5347 4443
+--------+ | | +-------------+
| | | | | |
| jicofo +----^ ^----+ videobridge |
| | | |
+--------+ +-------------+
```
## Install prosody
```sh
apt-get install prosody
@@ -76,7 +104,9 @@ Add a new file `jitsi.example.com` in `/etc/nginx/sites-available` (see also the
server_names_hash_bucket_size 64;
server {
listen 80;
listen 443;
# tls configuration that is not covered in this guide
# we recommend the use of https://certbot.eff.org/
server_name jitsi.example.com;
# set the root
root /srv/jitsi.example.com;

View File

@@ -1,9 +1,9 @@
# Jitsi Meet mobile apps
Jitsi Meet can also be built as a standalone mobile application for
iOS and Android. It uses the [React Native]() framework.
iOS and Android. It uses the [React Native] framework.
First make sure the [React Native dependencies]() are installed.
First make sure the [React Native dependencies] are installed.
**NOTE**: This document assumes the app is being built on a macOS system.
@@ -22,6 +22,8 @@ work properly with the native plugins we require.
npm install -g ios-deploy
```
You may need to add ```--unsafe-perm=true``` if you are running on [Mac OS 10.11 or greater](https://github.com/phonegap/ios-deploy#os-x-1011-el-capitan-or-greater).
2. Build the app
There are 2 ways to build the app: using the CLI or using Xcode.
@@ -62,8 +64,8 @@ work properly with the native plugins we require.
## Android
The [React Native dependencies]() page has very detailed information on how to
setup [Android Studio]() and the required components for getting the necessary
The [React Native dependencies] page has very detailed information on how to
setup [Android Studio] and the required components for getting the necessary
build environment. Make sure you follow it closely.
1. Building the app
@@ -79,7 +81,7 @@ build environment. Make sure you follow it closely.
## Debugging
The official documentation on [debugging]() is quite extensive, it is the
The official documentation on [debugging] is quite extensive, it is the
preferred method for debugging.
**NOTE**: When using Chrome Developer Tools for debugging the JavaScript code

36
flow-typed/npm/react-i18next_v2.x.x.js vendored Normal file
View File

@@ -0,0 +1,36 @@
// flow-typed signature: 57cf34196930be78935a42e5c8ac3cb6
// flow-typed version: ae6284e7b7/react-i18next_v2.x.x/flow_>=v0.36.x_<=v0.39.x
declare module 'react-i18next' {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type Locales = string | Array<string>;
declare type StatelessComponent<P> = (props: P) => ?React$Element<any>;
declare type Comp<P> = StatelessComponent<P> | Class<React$Component<*, P, *>>;
declare type Translator<OP, P> = {
(component: StatelessComponent<P>): Class<React$Component<void, OP, void>>;
<Def, St>(component: Class<React$Component<Def, P, St>>): Class<React$Component<Def, OP, St>>;
}
declare function translate<OP, P>(locales: Locales): Translator<OP, P>;
declare type NamespacesProps = {
components: Array<Comp<*>>,
i18n: { loadNamespaces: Function },
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare type ProviderProps = { i18n: Object, children: React$Element<any> };
declare var I18nextProvider: Class<React$Component<void, ProviderProps, void>>;
declare type InterpolateProps = {
children?: React$Element<any>,
className?: string,
};
declare var Interpolate: Class<React$Component<void, InterpolateProps, void>>;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

View File

@@ -113,6 +113,11 @@
window.setTimeout(
function () { window.location.replace(href); }, delay);
// Call extra handler if defined.
if (typeof postLoadErrorHandler === "function") {
postLoadErrorHandler();
}
};
window.removeEventListener(
'error', loadErrHandler, true /* capture phase */);
@@ -122,7 +127,7 @@
'error', loadErrHandler, true /* capture phase type of listener */);
</script>
<script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="utils.js?v=1"></script>
<script src="static/utils.js?v=1"></script>
<!--#include virtual="connection_optimization/connection_optimization.html" -->
<script src="connection_optimization/do_external_connect.js?v=1"></script>
<script><!--#include virtual="/interface_config.js" --></script>

View File

@@ -70,5 +70,16 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
POLICY_LOGO: null,
LOCAL_THUMBNAIL_RATIO: 16/9, //16:9
REMOTE_THUMBNAIL_RATIO: 1 //1:1
REMOTE_THUMBNAIL_RATIO: 1, //1:1
// Documentation reference for the live streaming feature.
LIVE_STREAMING_HELP_LINK: "https://jitsi.org/live",
/**
* Whether the mobile app Jitsi Meet is to be promoted to participants
* attempting to join a conference in a mobile Web browser. If undefined,
* default to true.
*
* @type {boolean}
*/
MOBILE_APP_PROMO: true
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -2,53 +2,103 @@
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-40@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-60@2x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "AppIcon-29@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "AppIcon-40@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "AppIcon-76@1x.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "AppIcon-76@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "AppIcon-83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@@ -1,22 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchScreen-480@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchScreen-480@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "LaunchScreen-480@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2</string>
<string>1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -80,9 +80,10 @@
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

View File

@@ -25,6 +25,7 @@
2602576C1D0A7703001E3363 /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2602576B1D0A7703001E3363 /* jitsi.ttf */; };
3847F11906B4479A9162628F /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 821D8ABD506944B4BDBB069B /* libRNVectorIcons.a */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
7FAD39BE09A84D6AB0ABACA8 /* libRNBackgroundTimer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A0018BBB2C4FD5A4F9CE71 /* libRNBackgroundTimer.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */; };
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B30EF2301DC0ED7C00690F45 /* WebRTC.framework */; };
@@ -184,6 +185,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
B332D04E1E54E3170086EA16 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNBackgroundTimer;
};
B3BA19D41DC6B37B00BCD481 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
@@ -263,6 +271,7 @@
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = "<group>"; };
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = "<group>"; };
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = "<group>"; };
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNBackgroundTimer.xcodeproj; path = "../node_modules/react-native-background-timer/ios/RNBackgroundTimer.xcodeproj"; sourceTree = "<group>"; };
0B42DFAD1E2FD90700111B12 /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioMode.m; path = app/AudioMode.m; sourceTree = "<group>"; };
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = "<group>"; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
@@ -277,6 +286,7 @@
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; };
22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
2602576B1D0A7703001E3363 /* jitsi.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = jitsi.ttf; path = ../android/app/src/main/assets/fonts/jitsi.ttf; sourceTree = "<group>"; };
27A0018BBB2C4FD5A4F9CE71 /* libRNBackgroundTimer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNBackgroundTimer.a; sourceTree = "<group>"; };
5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = KCKeepAwake.xcodeproj; path = "../node_modules/react-native-keep-awake/ios/KCKeepAwake.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
@@ -315,7 +325,7 @@
BF9643881C34FBC800B0BBDF /* GLKit.framework in Frameworks */,
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */,
BF96438E1C34FBE100B0BBDF /* VideoToolbox.framework in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
@@ -327,8 +337,9 @@
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
BFC745141CB829B300673F38 /* libRCTWebRTC.a in Frameworks */,
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
7FAD39BE09A84D6AB0ABACA8 /* libRNBackgroundTimer.a in Frameworks */,
3847F11906B4479A9162628F /* libRNVectorIcons.a in Frameworks */,
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -488,6 +499,7 @@
BFC7450D1CB829A700673F38 /* RCTWebRTC.xcodeproj */,
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */,
22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */,
);
name = Libraries;
@@ -524,6 +536,14 @@
name = Products;
sourceTree = "<group>";
};
B332D0301E54E3170086EA16 /* Products */ = {
isa = PBXGroup;
children = (
B332D04F1E54E3170086EA16 /* libRNBackgroundTimer.a */,
);
name = Products;
sourceTree = "<group>";
};
B3BA19B71DC6B02F00BCD481 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -653,6 +673,10 @@
ProductGroup = 146834001AC3E56700842450 /* Products */;
ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
},
{
ProductGroup = B332D0301E54E3170086EA16 /* Products */;
ProjectRef = 0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */;
},
{
ProductGroup = 26D589F81D0B42EE00FC396B /* Products */;
ProjectRef = 22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */;
@@ -806,6 +830,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B332D04F1E54E3170086EA16 /* libRNBackgroundTimer.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNBackgroundTimer.a;
remoteRef = B332D04E1E54E3170086EA16 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B3BA19D51DC6B37B00BCD481 /* libRCTImage-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -952,6 +983,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -969,6 +1001,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -988,6 +1021,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -1005,6 +1039,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -1050,6 +1085,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -1095,6 +1131,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);

View File

@@ -13,5 +13,7 @@
"sk": "Slowakisch",
"sl": "Slowenisch",
"sv": "Schwedisch",
"tr": "Türkisch"
"tr": "Türkisch",
"zhCN": "Chinesisch (China)",
"nb": "Norwegisch (Bokmal)"
}

19
lang/languages-eo.json Normal file
View File

@@ -0,0 +1,19 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": "",
"zhCN": "",
"nb": ""
}

18
lang/languages-nb.json Normal file
View File

@@ -0,0 +1,18 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": "",
"zhCN": ""
}

View File

@@ -13,5 +13,7 @@
"sk": "Eslovac",
"sl": "Eslovèn",
"sv": "Suedés",
"tr": "Turc"
"tr": "Turc",
"zhCN": "Chinés (China)",
"nb": "Norvegian Bokmål"
}

View File

@@ -6,12 +6,14 @@
"fr": "Francês",
"hy": "Armênio",
"it": "Italiano",
"oc": "Provençal",
"oc": "Occitano",
"pl": "Polonês",
"ptBR": "Português (Brasil)",
"ru": "Russo",
"sk": "Eslovaco",
"sl": "Esloveno",
"sv": "Sueco",
"tr": "Turco"
"tr": "Turco",
"zhCN": "Chinês (China)",
"nb": "Bokmal norueguês"
}

View File

@@ -1,5 +1,5 @@
{
"en": "English (английский)",
"en": "Английский",
"bg": "Болгарский",
"de": "Немецкий",
"es": "Испанский",
@@ -7,11 +7,12 @@
"hy": "Армянский",
"it": "Итальянский",
"oc": "Окситанский",
"pl": "",
"pl": "Польский",
"ptBR": "Португальский (Бразилия)",
"ru": "",
"ru": "Русский",
"sk": "Словацкий",
"sl": "Словенский",
"sv": "Шведский",
"tr": "Турецкий"
"tr": "Турецкий",
"zhCN": "Китайский (Китай)"
}

17
lang/languages-zhCN.json Normal file
View File

@@ -0,0 +1,17 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": ""
}

View File

@@ -13,5 +13,8 @@
"sk": "Slovak",
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish"
"tr": "Turkish",
"zhCN": "Chinese (China)",
"nb": "Norwegian Bokmal",
"eo": "Esperanto"
}

View File

@@ -39,41 +39,46 @@
"videoMute": "Kamera starten oder stoppen"
},
"welcomepage": {
"go": "Los",
"roomname": "Konferenzname eingeben",
"disable": "Diesen Hinweis nicht mehr anzeigen",
"feature1": {
"title": "Einfach zu benutzen",
"content": "Kein Download nötig. __app__ läuft direkt im Browser. Einfach die Konferenzadresse teilen und los geht's."
"content": "Kein Download nötig. __app__ läuft direkt im Browser. Einfach die Konferenzadresse teilen und los geht's.",
"title": "Einfach zu benutzen"
},
"feature2": {
"title": "Niedrige Bandbreite",
"content": "Videokonferenzen mit mehreren Teilnehmen mit weniger als 128Kpbs. Bildschirmfreigaben und Telefonkonferenzen kommen sogar mit noch weniger Bandbreite aus."
"content": "Videokonferenzen mit mehreren Teilnehmen mit weniger als 128Kpbs. Bildschirmfreigaben und Telefonkonferenzen kommen sogar mit noch weniger Bandbreite aus.",
"title": "Niedrige Bandbreite"
},
"feature3": {
"title": "Open Source",
"content": "__app__ steht unter der Apache Lizenz. Es steht ihnen frei __app__ gemäss dieser Lizenz herunterzuladen, zu verändern oder zu verbreiten."
"content": "__app__ steht unter der Apache Lizenz. Es steht ihnen frei __app__ gemäss dieser Lizenz herunterzuladen, zu verändern oder zu verbreiten.",
"title": "Open Source"
},
"feature4": {
"title": "Unbegrenzte Anzahl Benutzer",
"content": "Es gibt keine künstliche Beschränkung der Anzahl der Benutzer oder Konferenzteilnehmer. Die Leistung des Servers und die Bandbreite sind die einzigen limitierenden Faktoren."
"content": "Es gibt keine künstliche Beschränkung der Anzahl der Benutzer oder Konferenzteilnehmer. Die Leistung des Servers und die Bandbreite sind die einzigen limitierenden Faktoren.",
"title": "Unbegrenzte Anzahl Benutzer"
},
"feature5": {
"title": "Bildschirmfreigabe",
"content": "Es ist ganz einfach den Bildschirm zu teilen. __app__ ist ideal für Online-Präsentationen, Vorlesungen und Fernwartungsanfragen."
"content": "Es ist ganz einfach den Bildschirm zu teilen. __app__ ist ideal für Online-Präsentationen, Vorlesungen und Fernwartungsanfragen.",
"title": "Bildschirmfreigabe"
},
"feature6": {
"title": "Sichere Konferenzen",
"content": "Privatsphäre gewünscht? __app__ Konferenzen können mit einem Passwort geschützt werden um ungebetene Gäste fernzuhalten und Unterbrechungen zu vermeiden."
"content": "Privatsphäre gewünscht? __app__ Konferenzen können mit einem Passwort geschützt werden um ungebetene Gäste fernzuhalten und Unterbrechungen zu vermeiden.",
"title": "Sichere Konferenzen"
},
"feature7": {
"title": "Freigegebene Notizen",
"content": "__app__ verwendet Etherpad, ein Editor zur kollaborativen Bearbeitung von Texten."
"content": "__app__ verwendet Etherpad, ein Editor zur kollaborativen Bearbeitung von Texten.",
"title": "Freigegebene Notizen"
},
"feature8": {
"title": "Benutzungsstatistiken",
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden."
}
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden.",
"title": "Benutzungsstatistiken"
},
"go": "Los",
"join": "Beitreten",
"privacy": "Privatsphäre",
"roomname": "Konferenzname eingeben",
"roomnamePlaceHolder": "Konferenzname",
"sendFeedback": "Senden Sie uns Ihr Feedback",
"terms": "Bedingungen"
},
"startupoverlay": {
"policyText": "&nbsp;",
@@ -110,6 +115,13 @@
"profile": "Profil bearbeiten",
"raiseHand": "Hand erheben"
},
"unsupportedBrowser": {
"appInstalled": "oder wenn die App bereits installiert ist<br/>, <strong>dann</strong>",
"appNotInstalled": "Sie benötigen <strong>__app__</strong> um der Konferenz mit dem Mobilgerät beizutreten",
"downloadApp": "App herunterladen",
"joinConversation": "Der Konferenz beitreten",
"startConference": "Konferenz starten"
},
"bottomtoolbar": {
"chat": "Chat öffnen / schliessen",
"filmstrip": "Videos anzeigen / verbergen",
@@ -154,7 +166,8 @@
"kick": "Hinauswerfen",
"muted": "Stummgeschaltet",
"domute": "Stummschalten",
"flip": "Spiegeln"
"flip": "Spiegeln",
"remoteControl": "Fernsteuerung"
},
"connectionindicator": {
"header": "Verbindungsdaten",
@@ -312,7 +325,12 @@
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden.",
"muteParticipantTitle": "Teilnehmer stummschalten?",
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
"muteParticipantButton": "Stummschalten"
"muteParticipantButton": "Stummschalten",
"remoteControlTitle": "Fernsteuerung",
"remoteControlDeniedMessage": "__user__ hat die Anfrage zur Fernsteuerung verweigert.",
"remoteControlAllowedMessage": "__user__ hat die Anfrage zur Fernsteuerung angenommen.",
"remoteControlErrorMessage": "Beim Anfordern der Fernsteuerungsberechtigung von __user__ ist ein Fehler aufgetreten.",
"remoteControlStopMessage": "Die Fernsteuerung wurde beendet."
},
"\u0005dialog": {},
"email": {
@@ -375,6 +393,7 @@
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
"buttonTooltip": "Live-Stream starten / stoppen",
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
"streamIdHelp": "Wo ist die Stream-ID zu finden?",
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"busy": "All Aufnahmegeräte sind momentan ausgelastet. Bitte versuchen Sie es später noch einmal."
}

374
lang/main-eo.json Normal file
View File

@@ -0,0 +1,374 @@
{
"contactlist": "",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": ""
},
"welcomepage": {
"disable": "",
"feature1": {
"content": "",
"title": ""
},
"feature2": {
"content": "",
"title": ""
},
"feature3": {
"content": "",
"title": ""
},
"feature4": {
"content": "",
"title": ""
},
"feature5": {
"content": "",
"title": ""
},
"feature6": {
"content": "",
"title": ""
},
"feature7": {
"content": "",
"title": ""
},
"feature8": {
"content": "",
"title": ""
},
"go": "",
"join": "",
"privacy": "",
"roomname": "",
"roomnamePlaceHolder": "",
"sendFeedback": "",
"terms": ""
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"mute": "",
"videomute": "",
"authenticate": "",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"sharedvideo": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": "",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "",
"cameraDisabled": "",
"micDisabled": "",
"filmstrip": "",
"profile": "",
"raiseHand": ""
},
"unsupportedBrowser": {
"appInstalled": "",
"appNotInstalled": "",
"downloadApp": "",
"joinConversation": "",
"startConference": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "",
"update": "",
"name": "",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"noDevice": "",
"noPermission": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": "",
"setPasswordLabel": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"kickMessage": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"error": "",
"roomLocked": "",
"addPassword": "",
"createPassword": "",
"detectext": "",
"failtoinstall": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"reconnectNow": "",
"conferenceReloadTimeLeft": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"Dial": "",
"sipMsg": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"settings1": "",
"settings2": "",
"settings3": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"remoteControlStopMessage": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"pending": "",
"on": "",
"off": "",
"failedToStart": "",
"buttonTooltip": "",
"error": "",
"unavailable": ""
},
"liveStreaming": {
"pending": "",
"on": "",
"off": "",
"unavailable": "",
"failedToStart": "",
"buttonTooltip": "",
"streamIdRequired": "",
"streamIdHelp": "",
"error": "",
"busy": ""
}
}

362
lang/main-nb.json Normal file
View File

@@ -0,0 +1,362 @@
{
"contactlist": "",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": ""
},
"welcomepage": {
"go": "",
"roomname": "",
"disable": "",
"feature1": {
"title": "",
"content": ""
},
"feature2": {
"title": "",
"content": ""
},
"feature3": {
"title": "",
"content": ""
},
"feature4": {
"title": "",
"content": ""
},
"feature5": {
"title": "",
"content": ""
},
"feature6": {
"title": "",
"content": ""
},
"feature7": {
"title": "",
"content": ""
},
"feature8": {
"title": "",
"content": ""
}
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"mute": "",
"videomute": "",
"authenticate": "",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"sharedvideo": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": "",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "",
"cameraDisabled": "",
"micDisabled": "",
"filmstrip": "",
"profile": "",
"raiseHand": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "",
"update": "",
"name": "",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"noDevice": "",
"noPermission": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": "",
"setPasswordLabel": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"kickMessage": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"error": "",
"roomLocked": "",
"addPassword": "",
"createPassword": "",
"detectext": "",
"failtoinstall": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"reconnectNow": "",
"conferenceReloadTimeLeft": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"Dial": "",
"sipMsg": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"settings1": "",
"settings2": "",
"settings3": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"remoteControlStopMessage": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"pending": "",
"on": "",
"off": "",
"failedToStart": "",
"buttonTooltip": "",
"error": "",
"unavailable": ""
},
"liveStreaming": {
"pending": "",
"on": "",
"off": "",
"unavailable": "",
"failedToStart": "",
"buttonTooltip": "",
"streamIdRequired": "",
"streamIdHelp": "",
"error": "",
"busy": ""
}
}

View File

@@ -5,12 +5,12 @@
"roomUnlocked": "Qual que siá amb lo ligam pòt participar",
"passwordSetRemotely": "causit per qualqu'un mai",
"connectionsettings": "Paramètres de connexion",
"poweredby": "Produit per",
"feedback": "Donatz-nos vòstre vejaire",
"poweredby": "produit per",
"feedback": "Donatz-nos lo vòstre vejaire",
"inviteUrlDefaultMsg": "Vòstra conferéncia es en cors de creacion...",
"me": "ieu",
"speaker": "Nautparlaire",
"raisedHand": "Volriatz charrar",
"raisedHand": "Volriá charrar",
"defaultNickname": "ex. Joan Delpuèch",
"defaultLink": "ex. __url__",
"callingName": "__name__",
@@ -39,41 +39,46 @@
"videoMute": "Aviar o arrestar vòstra camerà"
},
"welcomepage": {
"go": "Crear",
"roomname": "Sasissètz un nom de sala",
"disable": "Afichar pas mai aquesta pagina",
"feature1": {
"title": "De bon utilizar",
"content": "Cap de telecargament pas requesit. __app__ s'utiliza dirèctament dempuèi vòstre navigador. Partejar simplament l'URL de vòstra conferéncia amb los autres per començar."
"content": "Cap de telecargament pas requesit. __app__ s'utiliza dirèctament dempuèi vòstre navigador. Partejar simplament l'URL de vòstra conferéncia amb los autres per començar.",
"title": "De bon utilizar"
},
"feature2": {
"title": "Benda passanta febla",
"content": "Las videoconferéncias de mantun participant necessitan mens de 128 kbps. Lo partiment d'ecran e las conferéncias amb solament d'àudio son possiblas amb plan mens de debit."
"content": "Las videoconferéncias de mantun participant necessitan mens de 128 kbps. Lo partiment d'ecran e las conferéncias amb solament d'àudio son possiblas amb plan mens de debit.",
"title": "Benda passanta febla"
},
"feature3": {
"title": "Open source",
"content": "__app__ es jos licéncia Apache. Sètz liure de telecargar, d'utilizar, de modificar e de partejar __app__ segon aquesta licéncia liura."
"content": "__app__ es jos licéncia Apache. Sètz liure de telecargar, d'utilizar, de modificar e de partejar __app__ segon aquesta licéncia liura.",
"title": "Open source"
},
"feature4": {
"title": "Nombre d'utilizaires illimitat",
"content": "I a pas de restriccions artificialas a prepaus del nombre d'utilizaires o de participants a una conferéncia. La poténcia del servidor e la benda passanta son los sols factors limitants."
"content": "I a pas de restriccions artificialas a prepaus del nombre d'utilizaires o de participants a una conferéncia. La poténcia del servidor e la benda passanta son los sols factors limitants.",
"title": "Nombre d'utilizaires illimitat"
},
"feature5": {
"title": "Partiment d'ecran",
"content": "Es aisit de partejar vòstre ecran amb d'autras personas. __app__ es ideal per las presentacions en linha, los corses, e las sessions de supòrt tecnic."
"content": "Es aisit de partejar vòstre ecran amb d'autras personas. __app__ es ideal per las presentacions en linha, los corses, e las sessions de supòrt tecnic.",
"title": "Partiment d'ecran"
},
"feature6": {
"title": "Salas securizadas",
"content": "Besonh de confidencialitat ? Las salas de conferéncia __app__ pòdon èsser securizadas per un senhal per exclure los convidats pas desirats, e prevenir de las interrupcions."
"content": "Besonh de confidencialitat ? Las salas de conferéncia __app__ pòdon èsser securizadas per un senhal per exclure los convidats pas desirats, e prevenir de las interrupcions.",
"title": "Salas securizadas"
},
"feature7": {
"title": "Nòtas partejadas",
"content": "__app__ prepausa Etherpad, un editor de tèxte collaboratiu en temps real qu'es parfèit pels procèsses verbals, l'edicion d'articles e plan mai encara."
"content": "__app__ prepausa Etherpad, un editor de tèxte collaboratiu en temps real qu'es parfèit pels procèsses verbals, l'edicion d'articles e plan mai encara.",
"title": "Nòtas partejadas"
},
"feature8": {
"title": "Estatisticas d'utilizacion",
"content": "Aprenètz mai a prepaus de vòstres utilizaires amb una integracion aisida de Piwik, Google Analytics e d'autres sistèmas d'estatisticas e supervision d'utilizacion."
}
"content": "Aprenètz mai a prepaus de vòstres utilizaires amb una integracion aisida de Piwik, Google Analytics e d'autres sistèmas d'estatisticas e supervision d'utilizacion.",
"title": "Estatisticas d'utilizacion"
},
"go": "Crear",
"join": "PARTICIPATZ",
"privacy": "Vida privada",
"roomname": "Sasissètz un nom de sala",
"roomnamePlaceHolder": "nom de la sala",
"sendFeedback": "Mandar vòstra opinion",
"terms": "Tèrmes"
},
"startupoverlay": {
"policyText": "&nbsp;",
@@ -108,7 +113,14 @@
"micDisabled": "Lo microfòn es pas disponible",
"filmstrip": "Mostrar / escondre vidèos",
"profile": "Modificar vòstre perfil",
"raiseHand": "Demandar / Demandar pas mai la paraula"
"raiseHand": "o se l'avètz ja<br /><strong>alara</strong>"
},
"unsupportedBrowser": {
"appInstalled": "o se ja l'avètz<br /><strong>alara</strong>",
"appNotInstalled": "Vos cal <strong>__app__</strong> per participar a la conversacion a partir de vòstre mobil",
"downloadApp": "Telecargar l'aplicacion",
"joinConversation": "Participar a la conversacion",
"startConference": "Començar una conferéncia"
},
"bottomtoolbar": {
"chat": "Dobrir / tampar lo chat",
@@ -126,15 +138,15 @@
"title": "Paramètres",
"update": "Mesa a jorn",
"name": "Nom",
"startAudioMuted": "Tot lo mond comença sens son",
"startVideoMuted": "Tot lo mond comença escondut",
"selectCamera": "Camèra",
"startAudioMuted": "Començan totes sens son",
"startVideoMuted": "Començan totes sens vidèo",
"selectCamera": "Camerà",
"selectMic": "Microfòn",
"selectAudioOutput": "Sortida àudio",
"followMe": "Tot lo mond me sèc",
"noDevice": "Pas cap",
"noPermission": "La permission d'utilizar l'aparelh es pas estada donada",
"cameraAndMic": "Camèra e microfòn",
"cameraAndMic": "Camerà e microfòn",
"moderator": "MODERATOR",
"password": "DEFINIR UN SENHAL",
"audioVideo": "ÀUDIO E VIDÈO",
@@ -149,12 +161,13 @@
"videothumbnail": {
"editnickname": "Clicatz per modificar<br/>vòstre nom",
"moderator": "Lo proprietari de<br/>aquesta conferéncia",
"videomute": "Un participant a<br/>arrestat sa camèra.",
"videomute": "Un participant a<br/>arrestat sa camerà.",
"mute": "Un participant a copat son micro",
"kick": "Exclure",
"muted": "Mut",
"domute": "Copar lo son",
"flip": "Revirar"
"flip": "Revirar",
"remoteControl": "Contraròtle alonhat"
},
"connectionindicator": {
"header": "Donadas de connexion",
@@ -162,7 +175,7 @@
"packetloss": "Pèrda de paquets :",
"resolution": "Resolucion :",
"less": "Amagar lo detalh",
"more": "Mostrar lo deta1h",
"more": "Ne veire mai",
"address": "Adreça :",
"remoteport": "Pòrt distant :",
"remoteport_plural": "Pòrts distants :",
@@ -208,7 +221,7 @@
"createPassword": "Crear un senhal",
"detectext": "Una error s'es produita pendent la deteccion de l'extension de partiment d'ecran.",
"failtoinstall": "Fracàs de l'installacion de l'extension de partiment d'ecran",
"failedpermissions": "Fracàs d'obtencion de las permissions per utilizar lo micro e/o la camèra.",
"failedpermissions": "Fracàs d'obtencion de las permissions per utilizar lo micro e/o la camerà.",
"conferenceReloadTitle": "Malurosament, quicòm truquèt",
"conferenceReloadMsg": "Sèm a trobar una solucion per aquò",
"conferenceDisconnectTitle": "Sètz estat desconnectat. Vos cal benlèu verificar vòstra connexion.",
@@ -224,7 +237,7 @@
"internalError": "Ops ! Quicòm a pas fonccionat. L'error seguenta s'es produsida : [setRemoteDescription]",
"unableToSwitch": "Impossible de cambiar lo flux vidèo.",
"SLDFailure": "Ops! Quicòm a trucat e lo micro es pas estat copat! (Fracàs SLD)",
"SRDFailure": "Ops! Quicòm a trucat e la camèra es pas estada copada! (Fracàs SRD)",
"SRDFailure": "Ops ! Quicòm a trucat e la camerà es pas estada arrestada ! (Fracàs SRD)",
"oops": "Ops !",
"currentPassword": "L'actual senhal es",
"passwordLabel": "Senhal",
@@ -298,21 +311,26 @@
"cameraErrorPresent": "I a agut una error pendent la connexion a la camerà.",
"cameraUnsupportedResolutionError": "Vòstra camerà pren pas en carga la resolucion vidèo que cal.",
"cameraUnknownError": "Impossible d'emplegar la camerà per una rason desconeguda.",
"cameraPermissionDeniedError": "La camèra es pas estada trobada.",
"cameraNotFoundError": "La camèra es pas estada trobada.",
"cameraPermissionDeniedError": "La camerà es pas estada trobada.",
"cameraNotFoundError": "La camerà es pas estada trobada.",
"cameraConstraintFailedError": "Vòstra camerà satisfà pas totas las constrentas necessàrias.",
"micUnknownError": "Impossible d'utilizar lo microfòn per una rason desconeguda.",
"micPermissionDeniedError": "Avètz pas donat l'autorizacion d'utilizar vòstre microfòn. Podètz encara participar a la conferéncia mai los demai vos ausiràn pas. Utilizatz lo boton del microfòn dins la barra d'adreça per resòlvre aquò.",
"micNotFoundError": "Lo microfòn es pas estat trobat.",
"micConstraintFailedError": "Vòstre microfòn satisfà pas totas las constrentas necessàrias.",
"micNotSendingData": "Podèm pas aver l'accès a vòstre microfòn. Mercés de ne causir un autre dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
"cameraNotSendingData": "Podèm pas aver l'accès a vòstra camèra. Mercés de verificar se una autra aplicacion es pas a l'utilizar, causissètz una autra camèra dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
"cameraNotSendingData": "Podèm pas aver l'accès a vòstra camerà. Mercés de verificar se una autra aplicacion es pas a l'utilizar, causissètz una autra camerà dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
"goToStore": "Anar sul webstore",
"externalInstallationTitle": "Extension requesida :",
"externalInstallationMsg": "Avètz d'installar nòstra extension de partiment d'ecran.",
"muteParticipantTitle": "Copar lo micro als participants ?",
"muteParticipantBody": "Poiretz pas lo tornar activar lo microfòn, mai eles pòdon o far quand vòlon.",
"muteParticipantButton": "Copar lo son"
"muteParticipantButton": "Copar lo son",
"remoteControlTitle": "Contraròtle alonhat",
"remoteControlDeniedMessage": "__user__ a refusat vòstra demanda de contraròtle alonhat !",
"remoteControlAllowedMessage": "__user__ a acceptat vòstra demanda de contraròtle alonhat !",
"remoteControlErrorMessage": "Error al moment de demandar lo contraròtle alonhat a __user__ !",
"remoteControlStopMessage": "La session de contraròtle alonhat es acabada !"
},
"email": {
"sharedKey": [
@@ -371,6 +389,7 @@
"failedToStart": "Lo dirècte a pas capitat de s'aviar",
"buttonTooltip": "Aviar / arrestar lo dirècte",
"streamIdRequired": "Mercé de completar lo stream id per aviar lo dirècte.",
"streamIdHelp": "Ont tròbi aquò ?",
"error": "Fracàs de la transmission en dirècte. Mercés de tornar ensajar.",
"busy": "Tots los enresgistraires son ocupats. Mercés de tornar ensajar mai tard."
}

View File

@@ -39,41 +39,46 @@
"videoMute": "Iniciar ou parar sua câmera"
},
"welcomepage": {
"go": "IR",
"roomname": "Digite o nome da sala",
"disable": "Não mostre esta página novamente",
"feature1": {
"title": "Simples para usar",
"content": "Não precisa baixar nada. __app__ trabalha diretamente no seu navegador. Simplesmente compartilhe sua URL da conferência com outros para começar."
"content": "Não precisa baixar nada. __app__ trabalha diretamente no seu navegador. Simplesmente compartilhe sua URL da conferência com outros para começar.",
"title": "Simples para usar"
},
"feature2": {
"title": "Largura de banda baixa",
"content": "Conferências de vídeo de multipartes trabalha com tão pouco quanto 128 kbps. Compartilhamento de tela e conferências de áudio somente são possíveis com muito menos."
"content": "Conferências de vídeo de multipartes trabalha com tão pouco quanto 128 kbps. Compartilhamento de tela e conferências de áudio somente são possíveis com muito menos.",
"title": "Largura de banda baixa"
},
"feature3": {
"title": "Código aberto",
"content": "__app__ é licenciado sob a Licença Apache. Você é livre para baixar, usar, modificar e compartilhar ela com a mesma licença."
"content": "__app__ é licenciado sob a Licença Apache. Você é livre para baixar, usar, modificar e compartilhar ela com a mesma licença.",
"title": "Código aberto"
},
"feature4": {
"title": "Usuários ilimitados",
"content": "Aqui não há restrições artificiais no número de usuários ou participantes da conferência. A potência do servidor e largura de banda são os únicos fatores limitantes."
"content": "Aqui não há restrições artificiais no número de usuários ou participantes da conferência. A potência do servidor e largura de banda são os únicos fatores limitantes.",
"title": "Usuários ilimitados"
},
"feature5": {
"title": "Compartilhamento de tela",
"content": "É fácil compartilhar sua tela com outros. __app__ é ideal para apresentações online, leituras, e sessões de suporte técnico."
"content": "É fácil compartilhar sua tela com outros. __app__ é ideal para apresentações online, leituras, e sessões de suporte técnico.",
"title": "Compartilhamento de tela"
},
"feature6": {
"title": "Salas seguras",
"content": "Precisa alguma privacidade? Salas de conferência do __app__ podem ser seguras com uma senha para excluir visitantes indesejados e prevenir interrupções."
"content": "Precisa alguma privacidade? Salas de conferência do __app__ podem ser seguras com uma senha para excluir visitantes indesejados e prevenir interrupções.",
"title": "Salas seguras"
},
"feature7": {
"title": "Notas compartilhadas",
"content": "__app_ disponibiliza o Etherpad, um editor de texto colaborativo em tempo real, que é ótimo para reuniões rápidas, escrevendo artigos, e mais."
"content": "__app_ disponibiliza o Etherpad, um editor de texto colaborativo em tempo real, que é ótimo para reuniões rápidas, escrevendo artigos, e mais.",
"title": "Notas compartilhadas"
},
"feature8": {
"title": "Estatísticas de uso",
"content": "Aprenda sobre seus usuários através de integração fácil com o Piwik, Google Analytics, e outros sistemas de monitoramento e estatísticas."
}
"content": "Aprenda sobre seus usuários através de integração fácil com o Piwik, Google Analytics, e outros sistemas de monitoramento e estatísticas.",
"title": "Estatísticas de uso"
},
"go": "IR",
"join": "Entrar",
"privacy": "Política de Privacidade",
"roomname": "Digite o nome da sala",
"roomnamePlaceHolder": "Nome da sala",
"sendFeedback": "Enviar comentários",
"terms": "Termos"
},
"startupoverlay": {
"policyText": "&nbsp;",
@@ -110,6 +115,13 @@
"profile": "Editar seu perfil",
"raiseHand": "Erguer o baixar sua mão"
},
"unsupportedBrowser": {
"appInstalled": "ou se você já tenha isso<br /> <strong>então</strong>",
"appNotInstalled": "Você precisa do <strong>__app__</strong> para começar um bate-papo no seu celular",
"downloadApp": "Baixe o Aplicativo",
"joinConversation": "Entre no bate-papo",
"startConference": "Comece uma conferência"
},
"bottomtoolbar": {
"chat": "Abrir / fechar bate-papo",
"filmstrip": "Mostrar/ocultar vídeos",
@@ -154,7 +166,8 @@
"kick": "Chutar fora",
"muted": "Mudo",
"domute": "Mudo",
"flip": "Inverter"
"flip": "Inverter",
"remoteControl": "Controle remoto"
},
"connectionindicator": {
"header": "Dados da conexão",
@@ -312,7 +325,12 @@
"externalInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela.",
"muteParticipantTitle": "Deixar mudo este participante?",
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
"muteParticipantButton": "Mudo"
"muteParticipantButton": "Mudo",
"remoteControlTitle": "Controle remoto",
"remoteControlDeniedMessage": "__user__ rejeitou sua requisição de controle remoto!",
"remoteControlAllowedMessage": "__user__ aceitou sua requisição de controle remoto!",
"remoteControlErrorMessage": "Um erro ocorreu enquanto tentava requerer a permissão de controle remoto de __user__!",
"remoteControlStopMessage": "A sessão de controle remoto terminou!"
},
"email": {
"sharedKey": [
@@ -371,6 +389,7 @@
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
"buttonTooltip": "Iniciar / parar live stream",
"streamIdRequired": "Por favor digite o id da transmissão ao vivo para começar a transmitir.",
"streamIdHelp": "Aonde eu encontro isto?",
"error": "Falha na transmissão ao vivo. Tente novamente",
"busy": "Todos os gravadores estão ocupados no momento. Por favor, tente mais tarde."
}

View File

@@ -1,9 +1,9 @@
{
"contactlist": "",
"contactlist": "Участники (__pcount__)",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"roomLocked": "Вызывающие должны ввести пароль",
"roomUnlocked": "Любой, владеющий ссылкой, может присоединиться",
"passwordSetRemotely": "установлен другим участником",
"connectionsettings": "Настройки подключения",
"poweredby": "работает на",
"feedback": "Оставьте нам свой отзыв",
@@ -13,7 +13,7 @@
"raisedHand": "Хочет говорить",
"defaultNickname": "напр. Яна Цветочкина",
"defaultLink": "напр. __url__",
"callingName": "",
"callingName": "__name__",
"userMedia": {
"react-nativeGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону нажатием на кнопку <b><i>Разрешить</i></b>",
"chromeGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону нажатием на кнопку <b><i>Разрешить</i></b>",
@@ -25,18 +25,18 @@
"nwjsGrantPermissions": "Пожалуйста дайте разрешение на доступ к камере и микрофону"
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": ""
"keyboardShortcuts": "Комбинации клавиш",
"raiseHand": "Поднять или опустить руку",
"pushToTalk": "Нажмите, чтобы говорить",
"toggleScreensharing": "Переключиться между камерой и совместным использованием экрана",
"toggleFilmstrip": "Показать или скрыть видео",
"toggleShortcuts": "Показать или скрыть это справочное меню",
"focusLocal": "Фокус на ваше видео",
"focusRemote": "Фокус на видео другого абонента",
"toggleChat": "Открыть или закрыть чат",
"mute": "Заглушить или включить микрофон",
"fullScreen": "Войти или выйти из полноэкранного режима",
"videoMute": "Включить или выключить вашу камеру"
},
"welcomepage": {
"go": "Вперед!",
@@ -51,11 +51,11 @@
"content": "Многопользовательским видеоконференциям достаточно скорости передачи данных в 128 Кбит/с. Демонстрация экрана или аудиоконференции требуют и того меньше."
},
"feature3": {
"title": "Открытый исходный код",
"title": "Исходный код открыт",
"content": "__app__ лицензирован под Apache License. Вы можете свободно скачивать, использовать, изменять это ПО в соответствии с условиями лицензии."
},
"feature4": {
"title": "Неограниченное количество пользовательниц",
"title": "Количество пользовательниц не ограничено",
"content": "Нет никаких искусственных ограничений по количеству пользовательниц или участников конференций. Вас отграничивают только мощность сервера и качество соединения."
},
"feature5": {
@@ -76,44 +76,44 @@
}
},
"startupoverlay": {
"policyText": "",
"title": ""
"policyText": "&nbsp;",
"title": "__app__ нуждается в использовании вашего микрофона и камеры."
},
"suspendedoverlay": {
"title": "",
"rejoinKeyTitle": ""
"title": "Ваш видеозвонок был прерван, потому что этот компьютер пошёл спать.",
"rejoinKeyTitle": "Присоединиться повторно"
},
"toolbar": {
"mute": "Вкл. / Выкл. звук",
"videomute": "",
"videomute": "Вкл / Выкл камеру",
"authenticate": "Аутентифицировать",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"lock": "Заблокировать / разблокировать комнату",
"invite": "Поделиться ссылкой",
"chat": "Открыть / Закрыть чат",
"etherpad": "Открыть / Закрыть общий документ",
"sharedvideo": "Поделиться YouTube видео",
"sharescreen": "",
"fullscreen": "",
"sharescreen": "Начать / Завершить совместное использование экрана",
"fullscreen": "Вкл / Выкл полноэкранный режим",
"sip": "Набрать SIP номер",
"Settings": "Настройки",
"hangup": "",
"hangup": "Покинуть",
"login": "Войти",
"logout": "Завершить сеанс",
"dialpad": "",
"dialpad": "Открыть / Закрыть клавиатуру для набора номера",
"sharedVideoMutedPopup": "У видео, которым Вы поделились, отключён звук, чтобы вы могли говорить с остальными.",
"micMutedPopup": "Ваш микрофон отключён, чтобы вы могли сосредоточиться на видео, которым поделились.",
"talkWhileMutedPopup": "",
"talkWhileMutedPopup": "Пытаетесь говорить? Вы приглушены.",
"unableToUnmutePopup": "Вы не можете включить звук, потому что включено видео.",
"cameraDisabled": "Камера недоступна",
"micDisabled": "Микрофон недоступен",
"filmstrip": "",
"profile": "",
"raiseHand": ""
"filmstrip": "Показать / Скрыть видео",
"profile": "Редактировать ваш профиль",
"raiseHand": "Поднять / Опустить вашу руку"
},
"bottomtoolbar": {
"chat": "Открыть / Закрыть чат",
"filmstrip": "",
"contactlist": ""
"filmstrip": "Показать / Скрыть видео",
"contactlist": "Просмотреть и пригласить участников"
},
"chat": {
"nickname": {
@@ -126,38 +126,39 @@
"title": "Настройки",
"update": "Обновить",
"name": "Имя",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"startAudioMuted": "Каждый начинает глушиться",
"startVideoMuted": "Все начинают скрываться",
"selectCamera": "Камера",
"selectMic": "Микрофон",
"selectAudioOutput": "Звуковой выход",
"followMe": "Каждый следует за мной",
"noDevice": "Нет",
"noPermission": "Нет прав пользоваться устройством",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": "",
"setPasswordLabel": ""
"cameraAndMic": "Камера и микрофон",
"moderator": "МОДЕРАТОР",
"password": "УСТАНОВИТЬ ПАРОЛЬ",
"audioVideo": "АУДИО И ВИДЕО",
"setPasswordLabel": "Заблокировать вашу комнату с помощью пароля."
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
"title": "Профиль",
"setDisplayNameLabel": "Установить ваше отображаемое имя",
"setEmailLabel": "Установить электронную почту для gravatar",
"setEmailInput": "Введите электронную почту"
},
"videothumbnail": {
"editnickname": "Нажми, чтобы<br/>поменять имя экрана",
"moderator": "Хозяйка конференции.",
"videomute": "",
"videomute": "Участник<br/>остановил камеру",
"mute": "Без звука",
"kick": "Прогнать",
"muted": "Звук выключен",
"domute": "Выключить звук",
"flip": "Отразить"
"flip": "Отразить",
"remoteControl": "Дистанционное управление"
},
"connectionindicator": {
"header": "",
"header": "Данные соединения",
"bitrate": "Битрейт",
"packetloss": "Потеря пакетов:",
"resolution": "Разрешение:",
@@ -195,49 +196,49 @@
"raisedHand": "Хочу высказаться."
},
"dialog": {
"add": "",
"add": "Добавить",
"kickMessage": "Фигасе! Вас прогнали со встречи!",
"popupError": "Ваш браузер блокирует всплывающие окна на этом сайте. Пожалуйста разрешите всплывающие окна в настройках безопасности и попробуйте снова.",
"passwordErrorTitle": "",
"passwordErrorTitle": "Ошибка пароля",
"passwordError": "Этот разговор сейчас защищён паролем. Только хозяйка конференции может устанавливать пароль.",
"passwordError2": "Эта конференция защищена паролем. Только хозяйка конференции может устанавливать пароль.",
"connectError": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией.",
"connectErrorWithMsg": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией: __msg__",
"incorrectPassword": "",
"incorrectPassword": "Неверный пароль",
"connecting": "Идёт подключение",
"copy": "",
"copy": "Копировать",
"error": "Ошибка",
"roomLocked": "",
"addPassword": "",
"createPassword": "",
"roomLocked": "Этот звонок блокируется. Новые звонящие должны иметь ссылку и ввести пароль, чтобы присоединиться",
"addPassword": "Добавить пароль",
"createPassword": "Создать пароль",
"detectext": "Ошибка при попытке определить расширение для совместного использования экрана.",
"failtoinstall": "Невозможно установить расширение для совместного использования рабочего стола",
"failedpermissions": "Невозможно получить права на использование локального микрофона и/или камеры.",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"reconnectNow": "",
"conferenceReloadTimeLeft": "",
"conferenceReloadTitle": "К сожалению, что-то пошло не так",
"conferenceReloadMsg": "Мы пытаемся исправить это",
"conferenceDisconnectTitle": "Вы были отсоединены. Вы можете проверить подключение к сети.",
"conferenceDisconnectMsg": "Переподключение в...",
"reconnectNow": "Переподключиться сейчас",
"conferenceReloadTimeLeft": "__seconds__ сек.",
"maxUsersLimitReached": "Достигнут максимум количества участников конференции. Конференция заполнена. Пожалуйста попробуйте позже!",
"lockTitle": "Блокировка не удалась",
"lockMessage": "Не удалось запереть конференцию",
"warning": "Внимание",
"passwordNotSupported": "Пароли для комнат сейчас не поддерживаются.",
"internalErrorTitle": "",
"internalError": "",
"internalErrorTitle": "Внутренняя ошибка",
"internalError": "Ой! Что-то пошло не так. Возникла следующая ошибка: [setRemoteDescription]",
"unableToSwitch": "Невозможно сменить видео трансляцию.",
"SLDFailure": "Ёпрст! Что-то пошло не так и мы не можем отключить звук! (ошибка SLD)",
"SRDFailure": "Ёпрст! Что-то пошло не так и мы не можем остановить видео! (ошибка SRD)",
"oops": "Ёпрст!",
"currentPassword": "",
"passwordLabel": "",
"currentPassword": "Текущим паролем является",
"passwordLabel": "Пароль",
"defaultError": "Какая-то ошибка",
"passwordRequired": "Требуется пароль",
"Ok": "Ok",
"done": "",
"done": "Готово",
"Remove": "Удалить",
"removePassword": "",
"removePassword": "Удалить пароль",
"shareVideoTitle": "Поделиться видео",
"shareVideoLinkError": "Пожалуйста введите корректную youtube ссылку.",
"removeSharedVideoTitle": "Удалить общее видео",
@@ -247,7 +248,7 @@
"WaitForHostMsg": "Конференция <b>__room__ </b> ещё не началась. Если вы её хост - аутентифицируйтесь. Или сидите ждите хоста.",
"IamHost": "Я хост",
"Cancel": "Отменить",
"Submit": "",
"Submit": "Принять",
"retry": "Повторить",
"logoutTitle": "Завершить сеанс",
"logoutQuestion": "Вы уверены, что хотите выйти и остановить конференцию?",
@@ -262,29 +263,29 @@
"sipMsg": "Введите SIP-номер",
"passwordCheck": "Вы уверены, что хотите удалить ваш пароль?",
"passwordMsg": "Введите пароль для вашей комнаты",
"shareLink": "",
"shareLink": "Поделитесь ссылкой на звонок",
"settings1": "Настройка Вашей конференции",
"settings2": "Участница подключилась без звука",
"settings3": "Нужны имена<br/><br/>Установите пароль, чтобы запереть Вашу комнату:",
"yourPassword": "",
"yourPassword": "Введите новый пароль",
"Back": "Назад",
"serviceUnavailable": "Служба недоступна",
"gracefulShutdown": "Сервис закрыт на переучёт. Пожалуйста попробуйте позже.",
"Yes": "Да",
"reservationError": "Ошибка системы резервации",
"reservationErrorMsg": "Код ошибки: __code__, сообщение: __msg__",
"password": "",
"password": "Введите пароль",
"userPassword": "пароль пользователя",
"token": "токен",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"tokenAuthFailedTitle": "Ошибка аутентификации",
"tokenAuthFailed": "Извините, вам не разрешено присоединиться к этому звонку.",
"displayNameRequired": "Требуется отображаемое имя",
"enterDisplayName": "Пожалуйста, введите Ваше имя экрана",
"extensionRequired": "Требуется расширение:",
"firefoxExtensionPrompt": "Нужно установить расширение Firefox, чтобы совместно пользоваться экраном. Попробуйте позже, скачав его <a href='__url__'>отсюда</a>!",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"rateExperience": "Пожалуйста, оцените ваш опыт встречи.",
"feedbackHelp": "Ваша поддержка поможет нам улучшить опыт видео.",
"feedbackQuestion": "Расскажите нам о вашем звонке!",
"thankYou": "Спасибо за использование __appName__!",
"sorryFeedback": "Мы удручены услышанным. Может расскажете поподробнее?",
"liveStreaming": "Трансляция",
@@ -295,7 +296,7 @@
"stopLiveStreaming": "Остановить трансляцию",
"stopRecording": "Остановить запись",
"doNotShowWarningAgain": "Больше не показывать это предупреждение",
"doNotShowMessageAgain": "",
"doNotShowMessageAgain": "Не показывать больше это сообщение",
"permissionDenied": "Доступ запрещён",
"screenSharingPermissionDeniedError": "У Вас нет прав совместно использовать Ваш экран",
"micErrorPresent": "Произошла ошибка при подключении к Вашему микрофону",
@@ -303,20 +304,25 @@
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение.",
"cameraUnknownError": "Не могу использовать камеру по неизвестной причине.",
"cameraPermissionDeniedError": "У вас нет прав на использование камеры. Вы можете участвовать в конференции, но другие не будут Вас видеть. Используйте значок с камерой в строке адреса, чтобы устранить проблему.",
"cameraNotFoundError": "",
"cameraNotFoundError": "Камера не была найдена.",
"cameraConstraintFailedError": "Ваша камера не отвечает некоторым требованиям.",
"micUnknownError": "Не могу пользоваться микрофоном по непонятным причинам.",
"micPermissionDeniedError": "Вы не дали прав на использование микрофона. Вы все-равно можете присоединиться к конференции, но никто не будет Вас слышать. Используйте иконку с камерой в адресной строке браузера, чтобы исправить это.",
"micNotFoundError": "",
"micNotFoundError": "Микрофон не был найден.",
"micConstraintFailedError": "Ваш микрофон не отвечает некоторым необходимым требованиям.",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "Выключить звук"
"micNotSendingData": "Мы не можем получить доступ к вашему микрофону. Пожалуйста, выберите другое устройство из меню настроек или попробуйте перезапустить приложение.",
"cameraNotSendingData": "Мы не можем получить доступ к вашей камере. Пожалуйста, проверьте, используется ли это устройство другим приложением, выберите другое устройство из меню настроек или же перезапустите приложение.",
"goToStore": "Перейти к интернет-магазину",
"externalInstallationTitle": "Требуется расширение",
"externalInstallationMsg": "Вам необходимо установить наше дополнение для совместного использования рабочего стола.",
"muteParticipantTitle": "Приглушить этого участника?",
"muteParticipantBody": "Вы не сможете перестать глушить их, но они могут сделать это сами в любое время.",
"muteParticipantButton": "Выключить звук",
"remoteControlTitle": "Дистанционное управление",
"remoteControlDeniedMessage": "__user__ отклонил ваш запрос на дистанционное управление!",
"remoteControlAllowedMessage": "__user__ принял ваш запрос на дистанционное управление!",
"remoteControlErrorMessage": "Произошла ошибка при попытке запросить разрешения удалённого управления от __user__!",
"remoteControlStopMessage": "Сессия дистанционного управления завершена!"
},
"email": {
"sharedKey": [
@@ -363,7 +369,7 @@
"on": "Запись",
"off": "Запись остановлена",
"failedToStart": "Ошибка при начале записи",
"buttonTooltip": "",
"buttonTooltip": "Начать / Остановить запись",
"error": "Ошибка записи. Попробуйте позже.",
"unavailable": "Сервис записи сейчас недоступен. Попробуйте позже."
},
@@ -373,8 +379,9 @@
"off": "Трансляция остановлена",
"unavailable": "Служба трансляций сейчас недоступна. Попробуйте позже.",
"failedToStart": "Трансляция видео не может быть начата",
"buttonTooltip": "",
"buttonTooltip": "Начать / Остановить прямую трансляцию",
"streamIdRequired": "Пожалуйста введите идентификатор трансляции, чтобы запустить её.",
"streamIdHelp": "Где я могу найти это?",
"error": "Не удалось начать трансляцию. Попробуйте снова.",
"busy": "Все рекордеры сейчас заняты. Попробуйте позже."
}

355
lang/main-zhCN.json Normal file
View File

@@ -0,0 +1,355 @@
{
"contactlist": "",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": ""
},
"welcomepage": {
"go": "",
"roomname": "",
"disable": "",
"feature1": {
"title": "",
"content": ""
},
"feature2": {
"title": "",
"content": ""
},
"feature3": {
"title": "",
"content": ""
},
"feature4": {
"title": "",
"content": ""
},
"feature5": {
"title": "",
"content": ""
},
"feature6": {
"title": "",
"content": ""
},
"feature7": {
"title": "",
"content": ""
},
"feature8": {
"title": "",
"content": ""
}
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"mute": "",
"videomute": "",
"authenticate": "",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"sharedvideo": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": "",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "",
"cameraDisabled": "",
"micDisabled": "",
"filmstrip": "",
"profile": "",
"raiseHand": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "",
"update": "",
"name": "",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"noDevice": "",
"noPermission": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": "",
"setPasswordLabel": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport_plural": "",
"remoteport": "",
"localport_plural": "",
"localport": "",
"localaddress_plural": "",
"localaddress": "",
"remoteaddress_plural": "",
"remoteaddress": "",
"transport": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"kickMessage": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"error": "",
"roomLocked": "",
"addPassword": "",
"createPassword": "",
"detectext": "",
"failtoinstall": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"reconnectNow": "",
"conferenceReloadTimeLeft": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"Dial": "",
"sipMsg": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"settings1": "",
"settings2": "",
"settings3": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"pending": "",
"on": "",
"off": "",
"failedToStart": "",
"buttonTooltip": "",
"error": "",
"unavailable": ""
},
"liveStreaming": {
"pending": "",
"on": "",
"off": "",
"unavailable": "",
"failedToStart": "",
"buttonTooltip": "",
"streamIdRequired": "",
"error": "",
"busy": ""
}
}

View File

@@ -36,47 +36,53 @@
"toggleChat": "Open or close the chat",
"mute": "Mute or unmute your microphone",
"fullScreen": "Enter or exit full screen",
"videoMute": "Start or stop your camera"
"videoMute": "Start or stop your camera",
"showSpeakerStats": "Show speaker stats"
},
"welcomepage":{
"go": "GO",
"roomname": "Enter room name",
"disable": "Don't show this page again",
"feature1": {
"title": "Simple to use",
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started."
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started.",
"title": "Simple to use"
},
"feature2": {
"title": "Low bandwidth",
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less."
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.",
"title": "Low bandwidth"
},
"feature3": {
"title": "Open source",
"content": "__app__ is licensed under the Apache License. You are free to download, use, modify, and share it as per this license."
"content": "__app__ is licensed under the Apache License. You are free to download, use, modify, and share it as per this license.",
"title": "Open source"
},
"feature4": {
"title": "Unlimited users",
"content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors."
"content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.",
"title": "Unlimited users"
},
"feature5": {
"title": "Screen sharing",
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions."
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions.",
"title": "Screen sharing"
},
"feature6": {
"title": "Secure rooms",
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions."
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.",
"title": "Secure rooms"
},
"feature7": {
"title": "Shared notes",
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more."
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.",
"title": "Shared notes"
},
"feature8": {
"title": "Usage statistics",
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems."
}
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.",
"title": "Usage statistics"
},
"go": "GO",
"join": "JOIN",
"privacy": "Privacy",
"roomname": "Enter room name",
"roomnamePlaceHolder": "room name",
"sendFeedback": "Send feedback",
"terms": "Terms"
},
"startupoverlay": {
"policyText": "&nbsp;",
"policyText": " ",
"title": "__app__ needs to use your microphone and camera."
},
"suspendedoverlay": {
@@ -110,6 +116,13 @@
"profile": "Edit your profile",
"raiseHand": "Raise / Lower your hand"
},
"unsupportedBrowser": {
"appInstalled": "or if you already have it<br /><strong>then</strong>",
"appNotInstalled": "You need <strong>__app__</strong> to join a conversation on your mobile",
"downloadApp": "Download the App",
"joinConversation": "Join the conversation",
"startConference": "Start a conference"
},
"bottomtoolbar": {
"chat": "Open / close chat",
"filmstrip": "Show / hide videos",
@@ -165,6 +178,7 @@
"bitrate": "Bitrate:",
"packetloss": "Packet loss:",
"resolution": "Resolution:",
"framerate": "Frame rate:",
"less": "Show less",
"more": "Show more",
"address": "Address:",
@@ -177,8 +191,10 @@
"remoteaddress": "Remote address:",
"remoteaddress_plural": "Remote addresses:",
"transport": "Transport:",
"transport_plural": "Transports:",
"bandwidth": "Estimated bandwidth:",
"na": "Come back here for connection information once the conference starts"
"na": "Come back here for connection information once the conference starts",
"direct": " (direct)"
},
"notify": {
"disconnected": "disconnected",
@@ -189,7 +205,7 @@
"focus": "Conference focus",
"focusFail": "__component__ not available - retry in __ms__ sec",
"grantedTo": "Moderator rights granted to __to__!",
"grantedToUnknown": "Moderator rights granted to $t(somebody)!",
"grantedToUnknown": "Moderator rights granted to $t(notify.somebody)!",
"muted": "You have started the conversation muted.",
"mutedTitle": "You're muted!",
"raisedHand": "Would like to speak."
@@ -321,7 +337,8 @@
"remoteControlDeniedMessage": "__user__ rejected your remote control request!",
"remoteControlAllowedMessage": "__user__ accepted your remote control request!",
"remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",
"remoteControlStopMessage": "The remote control session ended!"
"remoteControlStopMessage": "The remote control session ended!",
"close": "Close"
},
"email":
{
@@ -366,7 +383,9 @@
"FETCH_SESSION_ID": "Obtaining session-id...",
"GOT_SESSION_ID": "Obtaining session-id... Done",
"GET_SESSION_ID_ERROR": "Get session-id error: __code__",
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..."
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues...",
"LOW_BANDWIDTH": "Video for __displayName__ has been turned off to save bandwidth"
},
"recording":
{
@@ -387,7 +406,17 @@
"failedToStart": "Live streaming failed to start",
"buttonTooltip": "Start / Stop live stream",
"streamIdRequired": "Please fill in the stream id in order to launch the live streaming.",
"streamIdHelp": "Where do I find this?",
"error": "Live streaming failed. Please try again.",
"busy": "All recorders are currently busy. Please try again later."
},
"speakerStats":
{
"hours": "__count__h",
"minutes": "__count__m",
"name": "Name",
"seconds": "__count__s",
"speakerStats": "Speaker Stats",
"speakerTime": "Speaker Time"
}
}

View File

@@ -93,10 +93,12 @@ function changeParticipantNumber(APIInstance, number) {
* @param interfaceConfigOverwrite object containing configuration options
* defined in interface_config.js to be overridden.
* @param noSsl if the value is true https won't be used
* @param {string} [jwt] the JWT token if needed by jitsi-meet for
* authentication.
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite, noSsl) {
configOverwrite, interfaceConfigOverwrite, noSsl, jwt) {
if (!width || width < MIN_WIDTH)
width = MIN_WIDTH;
if (!height || height < MIN_HEIGHT)
@@ -121,6 +123,11 @@ function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
if(room_name)
this.url += room_name;
if (jwt) {
this.url += '?jwt=' + jwt;
}
this.url += "#jitsi_meet_external_api_id=" + id;
var key;

View File

@@ -15,18 +15,13 @@ import UIEvents from "../../service/UI/UIEvents";
import EtherpadManager from './etherpad/Etherpad';
import SharedVideoManager from './shared_video/SharedVideo';
import Recording from "./recording/Recording";
import GumPermissionsOverlay
from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
import VideoLayout from "./videolayout/VideoLayout";
import FilmStrip from "./videolayout/FilmStrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Profile from "./side_pannels/profile/Profile";
import Settings from "./../settings/Settings";
import RingOverlay from "./ring_overlay/RingOverlay";
import { randomInt } from "../../react/features/base/util/randomUtil";
import UIErrors from './UIErrors';
import { debounce } from "../util/helpers";
@@ -40,6 +35,17 @@ import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
/**
* Whether an overlay is visible or not.
*
* FIXME: This is temporary solution. Don't use this variable!
* Should be removed when all the code is move to react.
*
* @type {boolean}
* @public
*/
UI.overlayVisible = false;
let etherpadManager;
let sharedVideoManager;
@@ -77,57 +83,6 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
= "dialog.micNotSendingData";
/**
* Prompt user for nickname.
*/
function promptDisplayName() {
let labelKey = 'dialog.enterDisplayName';
let message = (
`<div class="form-control">
<label data-i18n="${labelKey}" class="form-control__label"></label>
<input name="displayName" type="text"
data-i18n="[placeholder]defaultNickname"
class="input-control" autofocus>
</div>`
);
// Don't use a translation string, because we're too early in the call and
// the translation may not be initialised.
let buttons = {Ok:true};
let dialog = messageHandler.openDialog(
'dialog.displayNameRequired',
message,
true,
buttons,
function (e, v, m, f) {
e.preventDefault();
if (v) {
let displayName = f.displayName;
if (displayName) {
UI.inputDisplayNameHandler(displayName);
dialog.close();
return;
}
}
},
function () {
let form = $.prompt.getPrompt();
let input = form.find("input[name='displayName']");
input.focus();
let button = form.find("button");
button.attr("disabled", "disabled");
input.keyup(function () {
if (input.val()) {
button.removeAttr("disabled");
} else {
button.attr("disabled", "disabled");
}
});
}
);
}
/**
* Initialize toolbars with side panels.
*/
@@ -298,9 +253,7 @@ UI.mucJoined = function () {
/***
* Handler for toggling filmstrip
*/
UI.handleToggleFilmStrip = () => {
UI.toggleFilmStrip();
};
UI.handleToggleFilmStrip = () => UI.toggleFilmStrip();
/**
* Sets tooltip defaults.
@@ -318,69 +271,6 @@ function _setTooltipDefaults() {
};
}
/**
* Setup some UI event listeners.
*/
function registerListeners() {
UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
if (etherpadManager) {
etherpadManager.toggleEtherpad();
}
});
UI.addListener(UIEvents.SHARED_VIDEO_CLICKED, function () {
if (sharedVideoManager) {
sharedVideoManager.toggleSharedVideo();
}
});
UI.addListener(UIEvents.TOGGLE_FULLSCREEN, UI.toggleFullScreen);
UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat);
UI.addListener(UIEvents.TOGGLE_SETTINGS, function () {
UI.toggleSidePanel("settings_container");
});
UI.addListener(UIEvents.TOGGLE_CONTACT_LIST, UI.toggleContactList);
UI.addListener( UIEvents.TOGGLE_PROFILE, function() {
if(APP.tokenData.isGuest)
UI.toggleSidePanel("profile_container");
});
UI.addListener(UIEvents.TOGGLE_FILM_STRIP, UI.handleToggleFilmStrip);
UI.addListener(UIEvents.FOLLOW_ME_ENABLED, function (isEnabled) {
if (followMeHandler)
followMeHandler.enableFollowMe(isEnabled);
});
}
/**
* Setup some DOM event listeners.
*/
function bindEvents() {
function onResize() {
SideContainerToggler.resize();
VideoLayout.resizeVideoArea();
}
// Resize and reposition videos in full screen mode.
$(document).on(
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
() => {
eventEmitter.emit( UIEvents.FULLSCREEN_TOGGLED,
UIUtil.isFullScreen());
onResize();
}
);
$(window).resize(onResize);
}
/**
* Returns the shared document manager object.
* @return {EtherpadManager} the shared document manager object
@@ -404,8 +294,6 @@ UI.start = function () {
// Set the defaults for tooltips.
_setTooltipDefaults();
registerListeners();
ToolbarToggler.init();
SideContainerToggler.init(eventEmitter);
FilmStrip.init(eventEmitter);
@@ -416,7 +304,6 @@ UI.start = function () {
}
VideoLayout.resizeVideoArea(true, true);
bindEvents();
sharedVideoManager = new SharedVideoManager(eventEmitter);
if (!interfaceConfig.filmStripOnly) {
let debouncedShowToolbar = debounce(() => {
@@ -428,13 +315,8 @@ UI.start = function () {
// Initialise the recording module.
if (config.enableRecording)
Recording.init(eventEmitter, config.recordingType);
// Display notice message at the top of the toolbar
if (config.noticeMessage) {
$('#noticeText').text(config.noticeMessage);
UIUtil.setVisible('notice', true);
}
} else {
$("body").addClass("filmstrip-only");
UIUtil.setVisible('mainToolbarContainer', false);
FilmStrip.setupFilmStripOnly();
messageHandler.enableNotifications(false);
@@ -443,12 +325,6 @@ UI.start = function () {
document.title = interfaceConfig.APP_NAME;
if(config.requireDisplayName) {
if (!APP.settings.getDisplayName()) {
promptDisplayName();
}
}
if (!interfaceConfig.filmStripOnly) {
toastr.options = {
"closeButton": true,
@@ -473,17 +349,57 @@ UI.start = function () {
if(APP.tokenData.callee) {
UI.showRingOverlay();
}
};
// Return true to indicate that the UI has been fully started and
// conference ready.
return true;
/**
* Setup some UI event listeners.
*/
UI.registerListeners
= () => UIListeners.forEach((value, key) => UI.addListener(key, value));
/**
* Unregister some UI event listeners.
*/
UI.unregisterListeners
= () => UIListeners.forEach((value, key) => UI.removeListener(key, value));
/**
* Setup some DOM event listeners.
*/
UI.bindEvents = () => {
function onResize() {
SideContainerToggler.resize();
VideoLayout.resizeVideoArea();
}
// Resize and reposition videos in full screen mode.
$(document).on(
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
() => {
eventEmitter.emit(
UIEvents.FULLSCREEN_TOGGLED,
UIUtil.isFullScreen());
onResize();
});
$(window).resize(onResize);
};
/**
* Unbind some DOM event listeners.
*/
UI.unbindEvents = () => {
$(document).off(
'webkitfullscreenchange mozfullscreenchange fullscreenchange');
$(window).off('resize');
};
/**
* Show local stream on UI.
* @param {JitsiTrack} track stream to show
*/
UI.addLocalStream = function (track) {
UI.addLocalStream = track => {
switch (track.getType()) {
case 'audio':
VideoLayout.changeLocalAudio(track);
@@ -502,31 +418,25 @@ UI.addLocalStream = function (track) {
* Show remote stream on UI.
* @param {JitsiTrack} track stream to show
*/
UI.addRemoteStream = function (track) {
VideoLayout.onRemoteStreamAdded(track);
};
UI.addRemoteStream = track => VideoLayout.onRemoteStreamAdded(track);
/**
* Removed remote stream from UI.
* @param {JitsiTrack} track stream to remove
*/
UI.removeRemoteStream = function (track) {
VideoLayout.onRemoteStreamRemoved(track);
};
UI.removeRemoteStream = track => VideoLayout.onRemoteStreamRemoved(track);
/**
* Update chat subject.
* @param {string} subject new chat subject
*/
UI.setSubject = function (subject) {
Chat.setSubject(subject);
};
UI.setSubject = subject => Chat.setSubject(subject);
/**
* Setup and show Etherpad.
* @param {string} name etherpad id
*/
UI.initEtherpad = function (name) {
UI.initEtherpad = name => {
if (etherpadManager || !config.etherpad_base || !name) {
return;
}
@@ -540,9 +450,7 @@ UI.initEtherpad = function (name) {
* Returns the shared document manager object.
* @return {EtherpadManager} the shared document manager object
*/
UI.getSharedDocumentManager = function () {
return etherpadManager;
};
UI.getSharedDocumentManager = () => etherpadManager;
/**
* Show user on UI.
@@ -600,15 +508,14 @@ UI.removeUser = function (id, displayName) {
* @param {string} id user id
* @param {string} newVideoType new videotype
*/
UI.onPeerVideoTypeChanged = (id, newVideoType) => {
VideoLayout.onVideoTypeChanged(id, newVideoType);
};
UI.onPeerVideoTypeChanged
= (id, newVideoType) => VideoLayout.onVideoTypeChanged(id, newVideoType);
/**
* Update local user role and show notification if user is moderator.
* @param {boolean} isModerator if local user is moderator or not
*/
UI.updateLocalRole = function (isModerator) {
UI.updateLocalRole = isModerator => {
VideoLayout.showModeratorIndicator();
Toolbar.showSipCallButton(isModerator);
@@ -631,7 +538,7 @@ UI.updateLocalRole = function (isModerator) {
* and notifies user who is the moderator
* @param user to check for moderator
*/
UI.updateUserRole = function (user) {
UI.updateUserRole = user => {
VideoLayout.showModeratorIndicator();
// We don't need to show moderator notifications when the focus (moderator)
@@ -651,18 +558,14 @@ UI.updateUserRole = function (user) {
} else {
messageHandler.notify(
'', 'notify.somebody',
'connected', 'notify.grantedToUnknown', {}
);
'connected', 'notify.grantedToUnknown');
}
};
/**
* Toggles smileys in the chat.
*/
UI.toggleSmileys = function () {
Chat.toggleSmileys();
};
UI.toggleSmileys = () => Chat.toggleSmileys();
/**
* Toggles film strip.
@@ -677,32 +580,24 @@ UI.toggleFilmStrip = function () {
* Indicates if the film strip is currently visible or not.
* @returns {true} if the film strip is currently visible, otherwise
*/
UI.isFilmStripVisible = function () {
return FilmStrip.isFilmStripVisible();
};
UI.isFilmStripVisible = () => FilmStrip.isFilmStripVisible();
/**
* Toggles chat panel.
*/
UI.toggleChat = function () {
UI.toggleSidePanel("chat_container");
};
UI.toggleChat = () => UI.toggleSidePanel("chat_container");
/**
* Toggles contact list panel.
*/
UI.toggleContactList = function () {
UI.toggleSidePanel("contacts_container");
};
UI.toggleContactList = () => UI.toggleSidePanel("contacts_container");
/**
* Toggles the given side panel.
*
* @param {String} sidePanelId the identifier of the side panel to toggle
*/
UI.toggleSidePanel = function (sidePanelId) {
SideContainerToggler.toggle(sidePanelId);
};
UI.toggleSidePanel = sidePanelId => SideContainerToggler.toggle(sidePanelId);
/**
@@ -712,6 +607,17 @@ UI.inputDisplayNameHandler = function (newDisplayName) {
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
};
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
UI.showCustomToolbarPopup = function (popupSelectorID, show, timeout) {
eventEmitter.emit(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
popupSelectorID, show, timeout);
};
/**
* Return the type of the remote video.
* @param jid the jid for the remote video
@@ -946,6 +852,59 @@ UI.participantConnectionStatusChanged = function (id, isActive) {
VideoLayout.onParticipantConnectionStatusChanged(id, isActive);
};
/**
* Prompt user for nickname.
*/
UI.promptDisplayName = () => {
const labelKey = 'dialog.enterDisplayName';
const message = (
`<div class="form-control">
<label data-i18n="${labelKey}" class="form-control__label"></label>
<input name="displayName" type="text"
data-i18n="[placeholder]defaultNickname"
class="input-control" autofocus>
</div>`
);
// Don't use a translation string, because we're too early in the call and
// the translation may not be initialised.
const buttons = { Ok: true };
const dialog = messageHandler.openDialog(
'dialog.displayNameRequired',
message,
true,
buttons,
(e, v, m, f) => {
e.preventDefault();
if (v) {
const displayName = f.displayName;
if (displayName) {
UI.inputDisplayNameHandler(displayName);
dialog.close();
return;
}
}
},
() => {
const form = $.prompt.getPrompt();
const input = form.find("input[name='displayName']");
const button = form.find("button");
input.focus();
button.attr("disabled", "disabled");
input.keyup(() => {
if (input.val()) {
button.removeAttr("disabled");
} else {
button.attr("disabled", "disabled");
}
});
}
);
};
/**
* Update audio level visualization for specified user.
* @param {string} id user id
@@ -1076,20 +1035,6 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
);
};
/**
* Notify the user that the video conferencing service is badly broken and
* the page should be reloaded.
*
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's caused by
* network related failure or <tt>false</tt> when it's the infrastructure.
* @param {string} a label string identifying the reason for the page reload
* which will be included in details of the log event.
*/
UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
// Reload the page after 10 - 30 seconds
PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
};
/**
* Updates auth info on the UI.
* @param {boolean} isAuthEnabled if authentication is enabled
@@ -1370,9 +1315,7 @@ UI.onSharedVideoStop = function (id, attributes) {
* @param {boolean} enabled indicates if the camera button should be enabled
* or disabled
*/
UI.setCameraButtonEnabled = function (enabled) {
Toolbar.setVideoIconEnabled(enabled);
};
UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
/**
* Enables / disables microphone toolbar button.
@@ -1380,9 +1323,7 @@ UI.setCameraButtonEnabled = function (enabled) {
* @param {boolean} enabled indicates if the microphone button should be
* enabled or disabled
*/
UI.setMicrophoneButtonEnabled = function (enabled) {
Toolbar.setAudioIconEnabled(enabled);
};
UI.setMicrophoneButtonEnabled = enabled => Toolbar.setAudioIconEnabled(enabled);
UI.showRingOverlay = function () {
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
@@ -1403,10 +1344,7 @@ UI.hideRingOverLay = function () {
* @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
*/
UI.isOverlayVisible = function () {
return RingOverlay.isVisible()
|| SuspendedOverlay.isVisible()
|| PageReloadOverlay.isVisible()
|| GumPermissionsOverlay.isVisible();
return RingOverlay.isVisible() || this.overlayVisible;
};
/**
@@ -1414,38 +1352,42 @@ UI.isOverlayVisible = function () {
*
* @returns {*|boolean} {true} if the ring overlay is visible, {false} otherwise
*/
UI.isRingOverlayVisible = function () {
return RingOverlay.isVisible();
};
/**
* Shows browser-specific overlay with guidance how to proceed with gUM prompt.
* @param {string} browser - name of browser for which to show the guidance
* overlay.
*/
UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
GumPermissionsOverlay.show(browser);
};
/**
* Shows suspended overlay with a button to rejoin conference.
*/
UI.showSuspendedOverlay = function () {
SuspendedOverlay.show();
};
/**
* Hides browser-specific overlay with guidance how to proceed with gUM prompt.
*/
UI.hideUserMediaPermissionsGuidanceOverlay = function () {
GumPermissionsOverlay.hide();
};
UI.isRingOverlayVisible = () => RingOverlay.isVisible();
/**
* Handles user's features changes.
*/
UI.onUserFeaturesChanged = function (user) {
VideoLayout.onUserFeaturesChanged(user);
};
UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
const UIListeners = new Map([
[
UIEvents.ETHERPAD_CLICKED,
() => etherpadManager && etherpadManager.toggleEtherpad()
], [
UIEvents.SHARED_VIDEO_CLICKED,
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
], [
UIEvents.TOGGLE_FULLSCREEN,
UI.toggleFullScreen
], [
UIEvents.TOGGLE_CHAT,
UI.toggleChat
], [
UIEvents.TOGGLE_SETTINGS,
() => UI.toggleSidePanel("settings_container")
], [
UIEvents.TOGGLE_CONTACT_LIST,
UI.toggleContactList
], [
UIEvents.TOGGLE_PROFILE,
() => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
], [
UIEvents.TOGGLE_FILM_STRIP,
UI.handleToggleFilmStrip
], [
UIEvents.FOLLOW_ME_ENABLED,
enabled => (followMeHandler && followMeHandler.enableFollowMe(enabled))
]
]);
module.exports = UI;

View File

@@ -1,4 +1,5 @@
/* global $, APP, config */
/* global $, APP, config, JitsiMeetJS */
const ConnectionErrors = JitsiMeetJS.errors.connection;
/**
* Build html for "password required" dialog.
@@ -127,14 +128,30 @@ function LoginDialog(successCallback, cancelCallback) {
/**
* Displays error message in 'finished' state which allows either to cancel
* or retry.
* @param messageKey the key to the message to be displayed.
* @param error the key to the error to be displayed.
* @param options the options to the error message (optional)
*/
this.displayError = function (messageKey, options) {
this.displayError = function (error, options) {
let finishedState = connDialog.getState('finished');
let errorMessageElem = finishedState.find('#errorMessage');
let messageKey;
if (error === ConnectionErrors.PASSWORD_REQUIRED) {
// this is a password required error, as login window was already
// open once, this means username or password is wrong
messageKey = 'dialog.incorrectPassword';
}
else {
messageKey = 'dialog.connectErrorWithMsg';
if (!options)
options = {};
options.msg = error;
}
errorMessageElem.attr("data-i18n", messageKey);
APP.translation.translateElement($(errorMessageElem), options);

View File

@@ -21,8 +21,9 @@
* SOFTWARE.
*/
/* global MD5, config, interfaceConfig, APP */
const logger = require("jitsi-meet-logger").getLogger(__filename);
/* global APP */
import { getAvatarURL } from '../../../react/features/base/participants';
let users = {};
@@ -64,7 +65,7 @@ export default {
* @param url the url for the avatar
*/
setUserAvatarUrl: function (id, url) {
this._setUserProp(id, "url", url);
this._setUserProp(id, "avatarUrl", url);
},
/**
@@ -82,57 +83,19 @@ export default {
* @param {string} userId user id
*/
getAvatarUrl: function (userId) {
if (config.disableThirdPartyRequests) {
return 'images/avatar2.png';
}
let user;
if (!userId || APP.conference.isLocalId(userId)) {
userId = "local";
user = users.local;
userId = APP.conference.getMyUserId();
} else {
user = users[userId];
}
let avatarId = null;
const user = users[userId];
// The priority is url, email and lowest is avatarId
if(user) {
if(user.url)
return user.url;
if (user.email)
avatarId = user.email;
else {
avatarId = user.avatarId;
}
}
// If the ID looks like an email, we'll use gravatar.
// Otherwise, it's a random avatar, and we'll use the configured
// URL.
let random = !avatarId || avatarId.indexOf('@') < 0;
if (!avatarId) {
logger.warn(
`No avatar stored yet for ${userId} - using ID as avatar ID`);
avatarId = userId;
}
avatarId = MD5.hexdigest(avatarId.trim().toLowerCase());
let urlPref = null;
let urlSuf = null;
if (!random) {
urlPref = 'https://www.gravatar.com/avatar/';
urlSuf = "?d=wavatar&size=200";
}
else if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) {
urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
}
else {
urlPref = 'https://api.adorable.io/avatars/200/';
urlSuf = ".png";
}
return urlPref + avatarId + urlSuf;
return getAvatarURL({
avatarID: user ? user.avatarId : undefined,
avatarURL: user ? user.avatarUrl : undefined,
email: user ? user.email : undefined,
id: userId
});
}
};

View File

@@ -1,90 +0,0 @@
/* global interfaceConfig */
import Overlay from '../overlay/Overlay';
/**
* An overlay with guidance how to proceed with gUM prompt.
*/
class GUMOverlayImpl extends Overlay {
/**
* Constructs overlay with guidance how to proceed with gUM prompt.
* @param {string} browser - name of browser for which to construct the
* guidance overlay.
* @override
*/
constructor(browser) {
super();
this.browser = browser;
}
/**
* @inheritDoc
*/
_buildOverlayContent() {
let textKey = `userMedia.${this.browser}GrantPermissions`;
let titleKey = 'startupoverlay.title';
let titleOptions = '{ "postProcess": "resolveAppName" }';
let policyTextKey = 'startupoverlay.policyText';
let policyLogo = '';
let policyLogoSrc = interfaceConfig.POLICY_LOGO;
if (policyLogoSrc) {
policyLogo += (
`<div class="policy__logo">
<img src="${policyLogoSrc}"/>
</div>`
);
}
return (
`<div class="inlay">
<span class="inlay__icon icon-microphone"></span>
<span class="inlay__icon icon-camera"></span>
<h3 class="inlay__title" data-i18n="${titleKey}"
data-i18n-options='${titleOptions}'></h3>
<span class='inlay__text'data-i18n='[html]${textKey}'></span>
</div>
<div class="policy overlay__policy">
<p class="policy__text" data-i18n="[html]${policyTextKey}"></p>
${policyLogo}
</div>`
);
}
}
/**
* Stores GUM overlay instance.
* @type {GUMOverlayImpl}
*/
let overlay;
export default {
/**
* Checks whether the overlay is currently visible.
* @return {boolean} <tt>true</tt> if the overlay is visible
* or <tt>false</tt> otherwise.
*/
isVisible () {
return overlay && overlay.isVisible();
},
/**
* Shows browser-specific overlay with guidance how to proceed with
* gUM prompt.
* @param {string} browser - name of browser for which to show the
* guidance overlay.
*/
show(browser) {
if (!overlay) {
overlay = new GUMOverlayImpl(browser);
}
overlay.show();
},
/**
* Hides browser-specific overlay with guidance how to proceed with
* gUM prompt.
*/
hide() {
overlay && overlay.hide();
}
};

View File

@@ -1,94 +0,0 @@
/* global $, APP */
/**
* Base class for overlay components - the components which are displayed on
* top of the application with semi-transparent background covering the whole
* screen.
*/
export default class Overlay{
/**
* Creates new <tt>Overlay</tt> instance.
*/
constructor() {
/**
*
* @type {jQuery}
*/
this.$overlay = null;
/**
* Indicates if this overlay should use the light look & feel or the
* standard one.
* @type {boolean}
*/
this.isLightOverlay = false;
}
/**
* Template method which should be used by subclasses to provide the overlay
* content. The contents provided by this method are later subject to
* the translation using {@link APP.translation.translateElement}.
* @return {string} HTML representation of the overlay dialog contents.
* @protected
*/
_buildOverlayContent() {
return '';
}
/**
* Constructs the HTML body of the overlay dialog.
*
* @private
*/
_buildOverlayHtml() {
let overlayContent = this._buildOverlayContent();
let containerClass = this.isLightOverlay ? "overlay__container-light"
: "overlay__container";
this.$overlay = $(`
<div class=${containerClass}>
<div class='overlay__content'>
${overlayContent}
</div>
</div>`);
APP.translation.translateElement(this.$overlay);
}
/**
* Checks whether the page reload overlay has been displayed.
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
* visible or <tt>false</tt> otherwise.
*/
isVisible() {
return this.$overlay && this.$overlay.parents('body').length > 0;
}
/**
* Template method called just after the overlay is displayed for the first
* time.
* @protected
*/
_onShow() {
// To be overridden by subclasses.
}
/**
* Shows the overlay dialog and attaches the underlying HTML representation
* to the DOM.
*/
show() {
!this.$overlay && this._buildOverlayHtml();
if (!this.isVisible()) {
this.$overlay.appendTo('body');
this._onShow();
}
}
/**
* Hides the overlay dialog and detaches it's HTML representation from
* the DOM.
*/
hide() {
this.$overlay && this.$overlay.detach();
}
}

View File

@@ -51,6 +51,9 @@ function _requestLiveStreamId() {
const streamIdRequired
= APP.translation.generateTranslationHTML(
"liveStreaming.streamIdRequired");
const streamIdHelp
= APP.translation.generateTranslationHTML(
"liveStreaming.streamIdHelp");
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({
@@ -60,7 +63,11 @@ function _requestLiveStreamId() {
`<input class="input-control"
name="streamId" type="text"
data-i18n="[placeholder]dialog.streamKey"
autofocus>`,
autofocus><div style="text-align: right">
<a class="helper-link" target="_new"
href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">`
+ streamIdHelp
+ `</a></div>`,
persistent: false,
buttons: [
{title: cancelButton, value: false},

View File

@@ -1,175 +0,0 @@
/* global $, APP, AJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import Overlay from "../overlay/Overlay";
/**
* An overlay dialog which is shown before the conference is reloaded. Shows
* a warning message and counts down towards the reload.
*/
class PageReloadOverlayImpl extends Overlay{
/**
* Creates new <tt>PageReloadOverlayImpl</tt>
* @param {number} timeoutSeconds how long the overlay dialog will be
* displayed, before the conference will be reloaded.
* @param {string} title the title of the overlay message
* @param {string} message the message of the overlay
* @param {string} buttonHtml the button html or an empty string if there's
* no button
* @param {boolean} isLightOverlay indicates if the overlay should be a
* light overlay or a standard one
*/
constructor(timeoutSeconds, title, message, buttonHtml, isLightOverlay) {
super();
/**
* Conference reload counter in seconds.
* @type {number}
*/
this.timeLeft = timeoutSeconds;
/**
* Conference reload timeout in seconds.
* @type {number}
*/
this.timeout = timeoutSeconds;
this.title = title;
this.message = message;
this.buttonHtml = buttonHtml;
this.isLightOverlay = isLightOverlay;
}
/**
* Constructs overlay body with the warning message and count down towards
* the conference reload.
* @override
*/
_buildOverlayContent() {
return `<div class="inlay">
<span data-i18n=${this.title}
class='reload_overlay_title'></span>
<span data-i18n=${this.message}
class='reload_overlay_msg'></span>
<div>
<div id='reloadProgressBar'
class="aui-progress-indicator">
<span class="aui-progress-indicator-value"></span>
</div>
<span id='reloadSecRemaining'
data-i18n="dialog.conferenceReloadTimeLeft"
class='reload_overlay_msg'>
</span>
</div>
${this.buttonHtml}
</div>`;
}
/**
* Updates the progress indicator position and the label with the time left.
*/
updateDisplay() {
APP.translation.translateElement(
$("#reloadSecRemaining"), { seconds: this.timeLeft });
const ratio = (this.timeout - this.timeLeft) / this.timeout;
AJS.progressBars.update("#reloadProgressBar", ratio);
}
/**
* Starts the reload countdown with the animation.
* @override
*/
_onShow() {
$("#reconnectNow").click(() => {
APP.ConferenceUrl.reload();
});
// Initialize displays
this.updateDisplay();
var intervalId = window.setInterval(function() {
if (this.timeLeft >= 1) {
this.timeLeft -= 1;
}
this.updateDisplay();
if (this.timeLeft === 0) {
window.clearInterval(intervalId);
APP.ConferenceUrl.reload();
}
}.bind(this), 1000);
logger.info(
"The conference will be reloaded after "
+ this.timeLeft + " seconds.");
}
}
/**
* Holds the page reload overlay instance.
*
* {@type PageReloadOverlayImpl}
*/
let overlay;
/**
* Checks whether the page reload overlay has been displayed.
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
* visible or <tt>false</tt> otherwise.
*/
export function isVisible() {
return overlay && overlay.isVisible();
}
/**
* Shows the page reload overlay which will do the conference reload after
* the given amount of time.
*
* @param {number} timeoutSeconds how many seconds before the conference
* reload will happen.
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's
* caused by network related failure or <tt>false</tt> when it's
* the infrastructure.
* @param {string} reason a label string identifying the reason for the page
* reload which will be included in details of the log event
*/
export function show(timeoutSeconds, isNetworkFailure, reason) {
let title;
let message;
let buttonHtml;
let isLightOverlay;
if (isNetworkFailure) {
title = "dialog.conferenceDisconnectTitle";
message = "dialog.conferenceDisconnectMsg";
buttonHtml
= `<button id="reconnectNow" data-i18n="dialog.reconnectNow"
class="button-control button-control_primary
button-control_center"></button>`;
isLightOverlay = true;
}
else {
title = "dialog.conferenceReloadTitle";
message = "dialog.conferenceReloadMsg";
buttonHtml = "";
isLightOverlay = false;
}
if (!overlay) {
overlay = new PageReloadOverlayImpl(timeoutSeconds,
title,
message,
buttonHtml,
isLightOverlay);
}
// Log the page reload event
if (!this.isVisible()) {
// FIXME (CallStats - issue) this event will not make it to
// the CallStats, because the log queue is not flushed, before
// "fabric terminated" is sent to the backed
APP.conference.logEvent(
'page.reload', undefined /* value */, reason /* label */);
}
overlay.show();
}

View File

@@ -541,7 +541,7 @@ export default class SharedVideoManager {
if(show)
this.showSharedVideoMutedPopup(false);
UIUtil.animateShowElement($("#micMutedPopup"), show, 5000);
APP.UI.showCustomToolbarPopup('#micMutedPopup', show, 5000);
}
/**
@@ -554,7 +554,7 @@ export default class SharedVideoManager {
if(show)
this.showMicMutedPopup(false);
UIUtil.animateShowElement($("#sharedVideoMutedPopup"), show, 5000);
APP.UI.showCustomToolbarPopup('#sharedVideoMutedPopup', show, 5000);
}
}

View File

@@ -164,9 +164,7 @@ function resizeChatConversation() {
* @param id {string} input id
*/
function deferredFocus(id){
setTimeout(function (){
$(`#${id}`).focus();
}, 400);
setTimeout(() => $(`#${id}`).focus(), 400);
}
/**
* Chat related user interface.
@@ -353,10 +351,17 @@ var Chat = {
* Scrolls chat to the bottom.
*/
scrollChatToBottom () {
setTimeout(function () {
$('#chatconversation').scrollTop(
$('#chatconversation')[0].scrollHeight);
}, 5);
setTimeout(
() => {
const chatconversation = $('#chatconversation');
// XXX Prevent TypeError: undefined is not an object when the
// Web browser does not support WebRTC (yet).
chatconversation.length > 0
&& chatconversation.scrollTop(
chatconversation[0].scrollHeight);
},
5);
}
};

View File

@@ -1,8 +1,9 @@
/* global $, APP, AJS, interfaceConfig, JitsiMeetJS */
import { LANGUAGES } from "../../../../react/features/base/i18n";
import UIUtil from "../../util/UIUtil";
import UIEvents from "../../../../service/UI/UIEvents";
import languages from "../../../../service/translation/languages";
import Settings from '../../../settings/Settings';
const sidePanelsContainerId = 'sideToolbarContainer';
@@ -145,7 +146,7 @@ export default {
let selectInput;
selectEl.html(generateLanguagesOptions(
languages.getLanguages(),
LANGUAGES,
APP.translation.getCurrentLanguage()
));
initSelect2(selectEl, () => {

View File

@@ -1,63 +0,0 @@
/* global $, APP */
import Overlay from '../overlay/Overlay';
/**
* An overlay dialog which is shown when a suspended event is detected.
*/
class SuspendedOverlayImpl extends Overlay{
/**
* Creates new <tt>SuspendedOverlayImpl</tt>
*/
constructor() {
super();
$(document).on('click', '#rejoin', () => {
APP.ConferenceUrl.reload();
});
}
/**
* Constructs overlay body with the message and a button to rejoin.
* @override
*/
_buildOverlayContent() {
return (
`<div class="inlay">
<span class="inlay__icon icon-microphone"></span>
<span class="inlay__icon icon-camera"></span>
<h3 class="inlay__title" data-i18n="suspendedoverlay.title"></h3>
<button id="rejoin"
data-i18n="suspendedoverlay.rejoinKeyTitle"
class="inlay__button button-control button-control_primary">
</button>
</div>`);
}
}
/**
* Holds the page suspended overlay instance.
*
* {@type SuspendedOverlayImpl}
*/
let overlay;
export default {
/**
* Checks whether the page suspended overlay has been displayed.
* @return {boolean} <tt>true</tt> if the page suspended overlay is
* currently visible or <tt>false</tt> otherwise.
*/
isVisible() {
return overlay && overlay.isVisible();
},
/**
* Shows the page suspended overlay.
*/
show() {
if (!overlay) {
overlay = new SuspendedOverlayImpl();
}
overlay.show();
}
};

View File

@@ -1,4 +1,4 @@
/* global APP, $, config, interfaceConfig, JitsiMeetJS */
/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import SideContainerToggler from "../side_pannels/SideContainerToggler";
@@ -26,8 +26,8 @@ const buttonHandlers = {
if (sharedVideoManager
&& sharedVideoManager.isSharedVideoVolumeOn()
&& !sharedVideoManager.isSharedVideoOwner()) {
UIUtil.animateShowElement(
$("#unableToUnmutePopup"), true, 5000);
APP.UI.showCustomToolbarPopup(
'#unableToUnmutePopup', true, 5000);
}
else {
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
@@ -120,19 +120,19 @@ const defaultToolbarButtons = {
shortcutDescription: "keyboardShortcuts.mute",
popups: [
{
id: "micMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.micMutedPopup"
id: 'micMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.micMutedPopup'
},
{
id: "unableToUnmutePopup",
className: "loginmenu",
dataAttr: "[html]toolbar.unableToUnmutePopup"
id: 'unableToUnmutePopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.unableToUnmutePopup'
},
{
id: "talkWhileMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.talkWhileMutedPopup"
id: 'talkWhileMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.talkWhileMutedPopup'
}
],
content: "Mute / Unmute",
@@ -263,11 +263,14 @@ const defaultToolbarButtons = {
id: 'toolbar_button_sharedvideo',
tooltipKey: 'toolbar.sharedvideo',
className: 'button icon-shared-video',
html: `<ul id="sharedVideoMutedPopup"
class="loginmenu extendedToolbarPopup">
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
</ul>
`
popups: [
{
id: 'sharedVideoMutedPopup',
className: 'loginmenu extendedToolbarPopup',
dataAttr: '[title]toolbar.sharedVideoMutedPopup',
dataAttrPosition: 'w'
}
]
},
'sip': {
id: 'toolbar_button_sip',
@@ -325,6 +328,39 @@ function getToolbarButtonPlace (btn) {
'extended';
}
/**
* Event handler for side toolbar container toggled event.
*
* @param {string} containerId - ID of the container.
* @param {boolean} isVisible - Flag showing whether container
* is visible.
* @returns {void}
*/
function onSideToolbarContainerToggled(containerId, isVisible) {
Toolbar._handleSideToolbarContainerToggled(containerId, isVisible);
}
/**
* Event handler for local raise hand changed event.
*
* @param {boolean} isRaisedHand - Flag showing whether hand is raised.
* @returns {void}
*/
function onLocalRaiseHandChanged(isRaisedHand) {
Toolbar._setToggledState("toolbar_button_raisehand", isRaisedHand);
}
/**
* Event handler for full screen toggled event.
*
* @param {boolean} isFullScreen - Flag showing whether app in full
* screen mode.
* @returns {void}
*/
function onFullScreenToggled(isFullScreen) {
Toolbar._handleFullScreenToggled(isFullScreen);
}
Toolbar = {
init (eventEmitter) {
emitter = eventEmitter;
@@ -333,6 +369,9 @@ Toolbar = {
this.toolbarSelector = $("#mainToolbarContainer");
this.extendedToolbarSelector = $("#extendedToolbar");
// Unregister listeners in case of reinitialization.
this.unregisterListeners();
// Initialise the toolbar buttons.
// The main toolbar will only take into account
// it's own configuration from interface_config.
@@ -342,20 +381,11 @@ Toolbar = {
this._setButtonHandlers();
APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
(containerId, isVisible) => {
Toolbar._handleSideToolbarContainerToggled( containerId,
isVisible);
});
this.registerListeners();
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
(isRaisedHand) => {
this._setToggledState("toolbar_button_raisehand", isRaisedHand);
});
APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED,
(isFullScreen) => {
Toolbar._handleFullScreenToggled(isFullScreen);
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
(popupID, show, timeout) => {
Toolbar._showCustomToolbarPopup(popupID, show, timeout);
});
if(!APP.tokenData.isGuest) {
@@ -364,6 +394,35 @@ Toolbar = {
document.getElementById('toolbar_button_profile'));
}
},
/**
* Register listeners for UI events of toolbar component.
*
* @returns {void}
*/
registerListeners() {
APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
onSideToolbarContainerToggled);
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
onLocalRaiseHandChanged);
APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED, onFullScreenToggled);
},
/**
* Unregisters handlers for UI events of Toolbar component.
*
* @returns {void}
*/
unregisterListeners() {
APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
onSideToolbarContainerToggled);
APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
onLocalRaiseHandChanged);
APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
onFullScreenToggled);
},
/**
* Enables / disables the toolbar.
* @param {e} set to {true} to enable the toolbar or {false}
@@ -724,17 +783,52 @@ Toolbar = {
_addPopups(buttonElement, popups = []) {
popups.forEach((popup) => {
let popupElement = document.createElement("ul");
const popupElement = document.createElement('div');
popupElement.id = popup.id;
popupElement.className = popup.className;
let liElement = document.createElement("li");
liElement.setAttribute("data-i18n", popup.dataAttr);
popupElement.appendChild(liElement);
popupElement.setAttribute('data-i18n', popup.dataAttr);
let gravity = 'n';
if (popup.dataAttrPosition)
gravity = popup.dataAttrPosition;
// use custom attribute to save gravity option
// we use 'data-tooltip' in UIUtil to activate all tooltips
// but we want these to be manually triggered
popupElement.setAttribute('tooltip-gravity', gravity);
APP.translation.translateElement($(popupElement));
buttonElement.appendChild(popupElement);
});
},
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
_showCustomToolbarPopup(popupSelectorID, show, timeout) {
const gravity = $(popupSelectorID).attr('tooltip-gravity');
AJS.$(popupSelectorID)
.tooltip({
trigger: 'manual',
html: true,
gravity: gravity,
title: 'title'});
if (show) {
AJS.$(popupSelectorID).tooltip('show');
setTimeout(function () {
// hide the tooltip
AJS.$(popupSelectorID).tooltip('hide');
}, timeout);
} else {
AJS.$(popupSelectorID).tooltip('hide');
}
},
/**
* Sets the toggled state of the given element depending on the isToggled
* parameter.
*

View File

@@ -36,6 +36,7 @@ function ConnectionIndicator(videoContainer, videoId) {
this.resolution = null;
this.isResolutionHD = null;
this.transport = [];
this.framerate = null;
this.popover = null;
this.id = videoId;
this.create();
@@ -88,12 +89,17 @@ ConnectionIndicator.prototype.generateText = function () {
}
// GENERATE RESOLUTIONS STRING
let resolutions = this.resolution || {};
let resolutionStr = Object.keys(resolutions).map(function (ssrc) {
const resolutions = this.resolution || {};
const resolutionStr = Object.keys(resolutions).map(ssrc => {
let {width, height} = resolutions[ssrc];
return `${width}x${height}`;
}).join(', ') || 'N/A';
const framerates = this.framerate || {};
const frameRateStr = Object.keys(framerates).map(ssrc =>
framerates[ssrc]
).join(', ') || 'N/A';
let result = (
`<table class="connection-info__container" style='width:100%'>
<tr>
@@ -119,6 +125,14 @@ ConnectionIndicator.prototype.generateText = function () {
${resolutionStr}
</td>
</tr>
<tr>
<td>
<span data-i18n='connectionindicator.framerate'></span>
</td>
<td>
${frameRateStr}
</td>
</tr>
</table>`);
if(this.videoContainer.videoSpanId == "localVideoContainer") {
@@ -150,7 +164,12 @@ ConnectionIndicator.prototype.generateText = function () {
"data-i18n='connectionindicator.address'></span></td>" +
"<td> N/A</td></tr>";
} else {
var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
var data = {
remoteIP: [],
localIP:[],
remotePort:[],
localPort:[],
transportType:[]};
for(i = 0; i < this.transport.length; i++) {
var ip = ConnectionIndicator.getIP(this.transport[i].ip);
var port = ConnectionIndicator.getPort(this.transport[i].ip);
@@ -173,8 +192,15 @@ ConnectionIndicator.prototype.generateText = function () {
if(data.localPort.indexOf(localPort) == -1) {
data.localPort.push(localPort);
}
if(data.transportType.indexOf(this.transport[i].type) == -1) {
data.transportType.push(this.transport[i].type);
}
}
// All of the transports should be either P2P or JVB
const isP2P = this.transport.length ? this.transport[0].p2p : false;
var local_address_key = "connectionindicator.localaddress";
var remote_address_key = "connectionindicator.remoteaddress";
var localTransport =
@@ -215,9 +241,18 @@ ConnectionIndicator.prototype.generateText = function () {
transport += "</td></tr>";
transport += localTransport + "</td></tr>";
transport +="<tr>" +
"<td><span data-i18n='connectionindicator.transport'>" +
"</span></td>" +
"<td>" + this.transport[0].type + "</td></tr>";
"<td><span data-i18n='connectionindicator.transport' "
+ " data-i18n-options='" +
JSON.stringify({count: data.transportType.length})
+ "'></span></td>" +
"<td>"
+ ConnectionIndicator.getStringFromArray(data.transportType);
// Append (direct) to indicate the P2P type of transport
if (isP2P) {
transport += "<span data-i18n='connectionindicator.direct'>";
}
// Close "type" column and end table row
transport += "</td></tr>";
}
@@ -321,17 +356,17 @@ ConnectionIndicator.prototype.remove = function() {
* the user is having connectivity issues.
*/
ConnectionIndicator.prototype.updateConnectionStatusIndicator
= function (isActive) {
this.isConnectionActive = isActive;
if (this.isConnectionActive) {
$(this.interruptedIndicator).hide();
$(this.emptyIcon).show();
$(this.fullIcon).show();
} else {
$(this.interruptedIndicator).show();
$(this.emptyIcon).hide();
$(this.fullIcon).hide();
}
= function (isActive) {
this.isConnectionActive = isActive;
if (this.isConnectionActive) {
$(this.interruptedIndicator).hide();
$(this.emptyIcon).show();
$(this.fullIcon).show();
} else {
$(this.interruptedIndicator).show();
$(this.emptyIcon).hide();
$(this.fullIcon).hide();
}
};
/**
@@ -358,6 +393,8 @@ ConnectionIndicator.prototype.updateConnectionQuality =
if (object.resolution) {
this.resolution = object.resolution;
}
if (object.framerate)
this.framerate = object.framerate;
}
let width = qualityToWidth.find(x => percent >= x.percent);
@@ -380,6 +417,15 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
this.updatePopoverData();
};
/**
* Updates the framerate
* @param framerate the new resolution
*/
ConnectionIndicator.prototype.updateFramerate = function (framerate) {
this.framerate = framerate;
this.updatePopoverData();
};
/**
* Updates the content of the popover if its visible
* @param force to work even if popover is not visible

View File

@@ -127,7 +127,9 @@ export default class LargeVideoManager {
// the video was not rendered, before the connection has failed.
const isHavingConnectivityIssues
= APP.conference.isParticipantConnectionActive(id) === false;
if (isHavingConnectivityIssues
if (videoType === VIDEO_CONTAINER_TYPE
&& isHavingConnectivityIssues
&& (isUserSwitch || !container.wasVideoRendered)) {
showAvatar = true;
}
@@ -155,10 +157,15 @@ export default class LargeVideoManager {
// Make sure no notification about remote failure is shown as
// its UI conflicts with the one for local connection interrupted.
const isConnected = APP.conference.isConnectionInterrupted()
|| !isHavingConnectivityIssues;
this.updateParticipantConnStatusIndication(
id,
APP.conference.isConnectionInterrupted()
|| !isHavingConnectivityIssues);
isConnected,
(isHavingConnectivityIssues)
? "connection.USER_CONNECTION_INTERRUPTED"
: "connection.LOW_BANDWIDTH");
// resolve updateLargeVideo promise after everything is done
promise.then(resolve);
@@ -180,10 +187,11 @@ export default class LargeVideoManager {
* @param {string} id the id of remote participant(MUC nickname)
* @param {boolean} isConnected true if the connection is active or false
* when the user is having connectivity issues.
* @param {string} messageKey the i18n key of the message
*
* @private
*/
updateParticipantConnStatusIndication (id, isConnected) {
updateParticipantConnStatusIndication (id, isConnected, messageKey) {
// Apply grey filter on the large video
this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
@@ -196,7 +204,7 @@ export default class LargeVideoManager {
let displayName
= APP.conference.getParticipantDisplayName(id);
this._setRemoteConnectionMessage(
"connection.USER_CONNECTION_INTERRUPTED",
messageKey,
{ displayName: displayName });
// Show it now only if the VideoContainer is on top
@@ -332,7 +340,7 @@ export default class LargeVideoManager {
*/
showRemoteConnectionMessage (show) {
if (typeof show !== 'boolean') {
show = APP.conference.isParticipantConnectionActive(this.id);
show = !APP.conference.isParticipantConnectionActive(this.id);
}
if (show) {
@@ -458,7 +466,7 @@ export default class LargeVideoManager {
// "avatar" and "video connection" can not be displayed both
// at the same time, but the latter is of higher priority and it
// will hide the avatar one if will be displayed.
this.showRemoteConnectionMessage(/* fet the current state */);
this.showRemoteConnectionMessage(/* fetch the current state */);
this.showLocalConnectionMessage(/* fetch the current state */);
}
});

View File

@@ -28,6 +28,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.emitter = emitter;
this.videoSpanId = `participant_${this.id}`;
SmallVideo.call(this, VideoLayout);
this._audioStreamElement = null;
this.hasRemoteVideoMenu = false;
this._supportsRemoteControl = false;
this.addRemoteVideoContainer();
@@ -200,6 +201,18 @@ RemoteVideo.prototype._generatePopupContent = function () {
popupmenuElement.appendChild(menuItem);
});
// feature check for volume setting as temasys objects cannot adjust volume
if (this._canSetAudioVolume()) {
const volumeScale = 100;
const volumeSlider = this._generatePopupMenuSliderItem({
handler: this._setAudioVolume.bind(this, volumeScale),
icon: 'icon-volume',
initialValue: this._getAudioElement().volume * volumeScale,
maxValue: volumeScale
});
popupmenuElement.appendChild(volumeSlider);
}
APP.translation.translateElement($(popupmenuElement));
return popupmenuElement;
@@ -343,6 +356,74 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) {
return menuItem;
};
/**
* Create a div element with a slider.
*
* @param {object} options - Configuration for the div's display and slider.
* @param {string} options.icon - The classname for the icon to display.
* @param {int} options.maxValue - The maximum value on the slider. The default
* value is 100.
* @param {int} options.initialValue - The value the slider should start at.
* The default value is 0.
* @param {function} options.handler - The callback for slider value changes.
* @returns {Element} A div element with a slider.
*/
RemoteVideo.prototype._generatePopupMenuSliderItem = function (options) {
const template = `<div class='popupmenu__contents'>
<span class='popupmenu__icon'>
<i class=${options.icon}></i>
</span>
<input class='popupmenu__slider'
type='range'
min='0'
max=${options.maxValue || 100}
value=${options.initialValue || 0}>
</input>
</div>`;
const menuItem = document.createElement('li');
menuItem.className = 'popupmenu__item';
menuItem.innerHTML = template;
const slider = menuItem.getElementsByClassName('popupmenu__slider')[0];
slider.oninput = function () {
options.handler(Number(slider.value));
};
return menuItem;
};
/**
* Get the remote participant's audio element.
*
* @returns {Element} audio element
*/
RemoteVideo.prototype._getAudioElement = function () {
return this._audioStreamElement;
};
/**
* Check if the remote participant's audio can have its volume adjusted.
*
* @returns {boolean} true if the volume can be adjusted.
*/
RemoteVideo.prototype._canSetAudioVolume = function () {
const audioElement = this._getAudioElement();
return audioElement && audioElement.volume !== undefined;
};
/**
* Change the remote participant's volume level.
*
* @param {int} scale - The maximum value the slider can go to.
* @param {int} newVal - The value to set the slider to.
*/
RemoteVideo.prototype._setAudioVolume = function (scale, newVal) {
if (this._canSetAudioVolume()) {
this._getAudioElement().volume = newVal / scale;
}
};
/**
* Updates the remote video menu.
*
@@ -379,12 +460,12 @@ RemoteVideo.prototype.setMutedView = function(isMuted) {
* @private
*/
RemoteVideo.prototype._figureOutMutedWhileDisconnected
= function(isDisconnected) {
if (isDisconnected && this.isVideoMuted) {
this.mutedWhileDisconnected = true;
} else if (!isDisconnected && !this.isVideoMuted) {
this.mutedWhileDisconnected = false;
}
= function(isDisconnected) {
if (isDisconnected && this.isVideoMuted) {
this.mutedWhileDisconnected = true;
} else if (!isDisconnected && !this.isVideoMuted) {
this.mutedWhileDisconnected = false;
}
};
/**
@@ -473,8 +554,7 @@ RemoteVideo.prototype.isVideoPlayable = function () {
*/
RemoteVideo.prototype.updateView = function () {
this.updateConnectionStatusIndicator(
null /* will obtain the status from 'conference' */);
this.updateConnectionStatusIndicator();
// This must be called after 'updateConnectionStatusIndicator' because it
// affects the display mode by modifying 'mutedWhileDisconnected' flag
@@ -483,19 +563,13 @@ RemoteVideo.prototype.updateView = function () {
/**
* Updates the UI to reflect user's connectivity status.
* @param isActive {boolean|null} 'true' if user's connection is active or
* 'false' when the use is having some connectivity issues and a warning
* should be displayed. When 'null' is passed then the current value will be
* obtained from the conference instance.
*/
RemoteVideo.prototype.updateConnectionStatusIndicator = function (isActive) {
// Check for initial value if 'isActive' is not defined
if (typeof isActive !== "boolean") {
isActive = this.isConnectionActive();
if (isActive === null) {
// Cancel processing at this point - no update
return;
}
RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
const isActive = this.isConnectionActive();
if (isActive === null) {
// Cancel processing at this point - no update
return;
}
logger.debug(this.id + " thumbnail is connection active ? " + isActive);
@@ -613,44 +687,10 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
}
$(streamElement).click(onClickHandler);
};
/**
* Show/hide peer container for the given id.
*/
RemoteVideo.prototype.showPeerContainer = function (state) {
if (!this.container)
return;
var isHide = state === 'hide';
var resizeThumbnails = false;
if (!isHide) {
if (!$(this.container).is(':visible')) {
resizeThumbnails = true;
$(this.container).show();
}
// Call updateView, so that we'll figure out if avatar
// should be displayed based on video muted status and whether or not
// it's in the lastN set
this.updateView();
if (!isVideo) {
this._audioStreamElement = streamElement;
}
else if ($(this.container).is(':visible') && isHide)
{
resizeThumbnails = true;
$(this.container).hide();
if(this.connectionIndicator)
this.connectionIndicator.hide();
}
if (resizeThumbnails) {
this.VideoLayout.resizeThumbnails();
}
// We want to be able to pin a participant from the contact list, even
// if he's not in the lastN set!
// ContactList.setClickable(id, !isHide);
};
RemoteVideo.prototype.updateResolution = function (resolution) {
@@ -659,6 +699,16 @@ RemoteVideo.prototype.updateResolution = function (resolution) {
}
};
/**
* Updates this video framerate indication.
* @param framerate the value to update
*/
RemoteVideo.prototype.updateFramerate = function (framerate) {
if (this.connectionIndicator) {
this.connectionIndicator.updateFramerate(framerate);
}
};
RemoteVideo.prototype.removeConnectionIndicator = function () {
if (this.connectionIndicator)
this.connectionIndicator.remove();

View File

@@ -1,4 +1,4 @@
/* global $, JitsiMeetJS, interfaceConfig */
/* global $, APP, JitsiMeetJS, interfaceConfig */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import Avatar from "../avatar/Avatar";
@@ -446,7 +446,7 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
SmallVideo.prototype.isVideoPlayable = function() {
return this.videoStream // Is there anything to display ?
&& !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
&& (this.isLocal || this.VideoLayout.isInLastN(this.id));
&& (this.isLocal || APP.conference.isInLastN(this.id));
};
/**

View File

@@ -310,6 +310,12 @@ export class VideoContainer extends LargeContainer {
}
resize (containerWidth, containerHeight, animate = false) {
// XXX Prevent TypeError: undefined is not an object when the Web
// browser does not support WebRTC (yet).
if (this.$video.length === 0) {
return;
}
let [width, height]
= this.getVideoSize(containerWidth, containerHeight);
let { horizontalIndent, verticalIndent }

View File

@@ -1,4 +1,4 @@
/* global config, APP, $, interfaceConfig */
/* global APP, $, interfaceConfig */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import FilmStrip from "./FilmStrip";
@@ -14,10 +14,6 @@ var remoteVideos = {};
var localVideoThumbnail = null;
var currentDominantSpeaker = null;
var localLastNCount = config.channelLastN;
var localLastNSet = [];
var lastNEndpointsCache = [];
var lastNPickupId = null;
var eventEmitter = null;
@@ -60,13 +56,22 @@ function onContactClicked (id) {
// let the bridge adjust its lastN set for myjid and store
// the pinned user in the lastNPickupId variable to be
// picked up later by the lastN changed event handler.
lastNPickupId = id;
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, remoteVideo, true);
}
}
}
/**
* Handler for local flip X changed event.
* @param {Object} val
*/
function onLocalFlipXChanged (val) {
localFlipX = val;
if(largeVideo) {
largeVideo.onLocalFlipXChange(val);
}
}
/**
* Returns the corresponding resource id to the given peer container
* DOM element.
@@ -91,11 +96,10 @@ let largeVideo;
var VideoLayout = {
init (emitter) {
eventEmitter = emitter;
eventEmitter.addListener(UIEvents.LOCAL_FLIPX_CHANGED, function (val) {
localFlipX = val;
if(largeVideo)
largeVideo.onLocalFlipXChange(val);
});
// Unregister listeners in case of reinitialization
this.unregisterListeners();
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
// sets default video type of local video
// FIXME container type is totally different thing from the video type
@@ -104,8 +108,27 @@ var VideoLayout = {
// the local video thumb maybe one pixel
this.resizeThumbnails(false, true);
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
this.lastNCount = config.channelLastN;
this.registerListeners();
},
/**
* Registering listeners for UI events in Video layout component.
*
* @returns {void}
*/
registerListeners() {
eventEmitter.addListener(UIEvents.LOCAL_FLIPX_CHANGED,
onLocalFlipXChanged);
eventEmitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
},
/**
* Unregistering listeners for UI events in Video layout component.
*
* @returns {void}
*/
unregisterListeners() {
eventEmitter.removeListener(UIEvents.CONTACT_CLICKED, onContactClicked);
},
initLargeVideo () {
@@ -131,14 +154,6 @@ var VideoLayout = {
largeVideo.updateLargeVideoAudioLevel(lvl);
},
isInLastN (resource) {
return this.lastNCount < 0 || // lastN is disabled
// lastNEndpoints cache not built yet
(this.lastNCount > 0 && !lastNEndpointsCache.length) ||
(lastNEndpointsCache &&
lastNEndpointsCache.indexOf(resource) !== -1);
},
changeLocalAudio (stream) {
let localAudio = document.getElementById('localAudio');
localAudio = stream.attach(localAudio);
@@ -426,13 +441,8 @@ var VideoLayout = {
remoteVideo.setVideoType(VIDEO_CONTAINER_TYPE);
}
// In case this is not currently in the last n we don't show it.
if (localLastNCount && localLastNCount > 0 &&
FilmStrip.getThumbs().remoteThumbs.length >= localLastNCount + 2) {
remoteVideo.showPeerContainer('hide');
} else {
VideoLayout.resizeThumbnails(false, true);
}
VideoLayout.resizeThumbnails(false, true);
// Initialize the view
remoteVideo.updateView();
},
@@ -682,142 +692,22 @@ var VideoLayout = {
* endpoints
*/
onLastNEndpointsChanged (lastNEndpoints, endpointsEnteringLastN) {
if (this.lastNCount !== lastNEndpoints.length)
this.lastNCount = lastNEndpoints.length;
lastNEndpointsCache = lastNEndpoints;
Object.keys(remoteVideos).forEach(
id => {
if (lastNEndpoints.length > 0
&& lastNEndpoints.indexOf(id) < 0
|| endpointsEnteringLastN.length > 0
&& endpointsEnteringLastN.indexOf(id) > 0) {
// Say A, B, C, D, E, and F are in a conference and LastN = 3.
//
// If LastN drops to, say, 2, because of adaptivity, then E should see
// thumbnails for A, B and C. A and B are in E's server side LastN set,
// so E sees them. C is only in E's local LastN set.
//
// If F starts talking and LastN = 3, then E should see thumbnails for
// F, A, B. B gets "ejected" from E's server side LastN set, but it
// enters E's local LastN ejecting C.
// Increase the local LastN set size, if necessary.
if (this.lastNCount > localLastNCount) {
localLastNCount = this.lastNCount;
}
// Update the local LastN set preserving the order in which the
// endpoints appeared in the LastN/local LastN set.
var nextLocalLastNSet = lastNEndpoints.slice(0);
for (var i = 0; i < localLastNSet.length; i++) {
if (nextLocalLastNSet.length >= localLastNCount) {
break;
}
var resourceJid = localLastNSet[i];
if (nextLocalLastNSet.indexOf(resourceJid) === -1) {
nextLocalLastNSet.push(resourceJid);
}
}
localLastNSet = nextLocalLastNSet;
var updateLargeVideo = false;
// Handle LastN/local LastN changes.
FilmStrip.getThumbs().remoteThumbs.each(( index, element ) => {
var resourceJid = getPeerContainerResourceId(element);
var smallVideo = remoteVideos[resourceJid];
// We do not want to process any logic for our own(local) video
// because the local participant is never in the lastN set.
// The code of this function might detect that the local participant
// has been dropped out of the lastN set and will update the large
// video
// Detected from avatar tests, where lastN event override
// local video pinning
if(APP.conference.isLocalId(resourceJid))
return;
var isReceived = true;
if (resourceJid &&
lastNEndpoints.indexOf(resourceJid) < 0 &&
localLastNSet.indexOf(resourceJid) < 0) {
logger.log("Remove from last N", resourceJid);
if (smallVideo)
smallVideo.showPeerContainer('hide');
else if (!APP.conference.isLocalId(resourceJid))
logger.error("No remote video for: " + resourceJid);
isReceived = false;
} else if (resourceJid &&
//TOFIX: smallVideo may be undefined
smallVideo.isVisible() &&
lastNEndpoints.indexOf(resourceJid) < 0 &&
localLastNSet.indexOf(resourceJid) >= 0) {
// TOFIX: if we're here we already know that the smallVideo
// exists. Look at the previous FIX above.
if (smallVideo)
smallVideo.showPeerContainer('avatar');
else if (!APP.conference.isLocalId(resourceJid))
logger.error("No remote video for: " + resourceJid);
isReceived = false;
}
if (!isReceived) {
// resourceJid has dropped out of the server side lastN set, so
// it is no longer being received. If resourceJid was being
// displayed in the large video we have to switch to another
// user.
if (!updateLargeVideo &&
this.isCurrentlyOnLarge(resourceJid)) {
updateLargeVideo = true;
}
}
});
if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
endpointsEnteringLastN = lastNEndpoints;
if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
endpointsEnteringLastN.forEach(function (resourceJid) {
var remoteVideo = remoteVideos[resourceJid];
if (remoteVideo)
remoteVideo.showPeerContainer('show');
if (!remoteVideo.isVisible()) {
logger.log("Add to last N", resourceJid);
remoteVideo.addRemoteStreamElement(remoteVideo.videoStream);
if (lastNPickupId == resourceJid) {
// Clean up the lastN pickup id.
lastNPickupId = null;
VideoLayout.handleVideoThumbClicked(resourceJid);
updateLargeVideo = false;
let remoteVideo = (id) ? remoteVideos[id] : null;
if (remoteVideo) {
remoteVideo.updateView();
if (remoteVideo.isCurrentlyOnLargeVideo())
this.updateLargeVideo(id);
}
remoteVideo.waitForPlayback(
remoteVideo.selectVideoElement()[0],
remoteVideo.videoStream);
}
});
}
// The endpoint that was being shown in the large video has dropped out
// of the lastN set and there was no lastN pickup jid. We need to update
// the large video now.
if (updateLargeVideo) {
var resource;
// Find out which endpoint to show in the large video.
for (i = 0; i < lastNEndpoints.length; i++) {
resource = lastNEndpoints[i];
if (!resource || APP.conference.isLocalId(resource))
continue;
// videoSrcToSsrc needs to be update for this call to succeed.
this.updateLargeVideo(resource);
break;
}
}
},
/**
@@ -826,21 +716,37 @@ var VideoLayout = {
* @param object
*/
updateLocalConnectionStats (percent, object) {
let resolutions = object.resolution;
const { framerate, resolution } = object;
object.resolution = resolutions[APP.conference.getMyUserId()];
// FIXME overwrites 'lib-jitsi-meet' internal object
// Why library internal objects are passed as event's args ?
object.resolution = resolution[APP.conference.getMyUserId()];
object.framerate = framerate[APP.conference.getMyUserId()];
localVideoThumbnail.updateStatsIndicator(percent, object);
Object.keys(resolutions).forEach(function (id) {
Object.keys(resolution).forEach(function (id) {
if (APP.conference.isLocalId(id)) {
return;
}
let resolution = resolutions[id];
let resolutionValue = resolution[id];
let remoteVideo = remoteVideos[id];
if (resolution && remoteVideo) {
remoteVideo.updateResolution(resolution);
if (resolutionValue && remoteVideo) {
remoteVideo.updateResolution(resolutionValue);
}
});
Object.keys(framerate).forEach(function (id) {
if (APP.conference.isLocalId(id)) {
return;
}
const framerateValue = framerate[id];
const remoteVideo = remoteVideos[id];
if (framerateValue && remoteVideo) {
remoteVideo.updateFramerate(framerateValue);
}
});
},

View File

@@ -1,5 +1,10 @@
/* global APP, $, JitsiMeetJS */
import {
toggleDialog
} from '../../react/features/base/dialog';
import { SpeakerStats } from '../../react/features/speaker-stats';
/**
* The reference to the shortcut dialogs when opened.
*/
@@ -29,6 +34,12 @@ function initGlobalShortcuts() {
});
KeyboardShortcut._addShortcutToHelp("SPACE","keyboardShortcuts.pushToTalk");
KeyboardShortcut.registerShortcut("T", null, () => {
APP.store.dispatch(toggleDialog(SpeakerStats, {
conference: APP.conference
}));
}, "keyboardShortcuts.showSpeakerStats");
/**
* FIXME: Currently focus keys are directly implemented below in onkeyup.
* They should be moved to the SmallVideo instead.

View File

@@ -3,13 +3,7 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
import UIUtil from '../UI/util/UIUtil';
import jitsiLocalStorage from '../util/JitsiLocalStorage';
function generateUniqueId() {
function _p8() {
return (Math.random().toString(16) + "000000000").substr(2, 8);
}
return _p8() + _p8() + _p8() + _p8();
}
import { randomHexString } from '../../react/features/base/util';
let avatarUrl = '';
@@ -17,14 +11,13 @@ let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem("email") || '');
let avatarId = UIUtil.unescapeHtml(jitsiLocalStorage.getItem("avatarId") || '');
if (!avatarId) {
// if there is no avatar id, we generate a unique one and use it forever
avatarId = generateUniqueId();
avatarId = randomHexString(32);
jitsiLocalStorage.setItem("avatarId", avatarId);
}
let localFlipX = JSON.parse(jitsiLocalStorage.getItem("localFlipX") || true);
let displayName = UIUtil.unescapeHtml(
jitsiLocalStorage.getItem("displayname") || '');
let language = jitsiLocalStorage.getItem("language");
let cameraDeviceId = jitsiLocalStorage.getItem("cameraDeviceId") || '';
let micDeviceId = jitsiLocalStorage.getItem("micDeviceId") || '';
let welcomePageDisabled = JSON.parse(
@@ -113,14 +106,6 @@ export default {
return avatarUrl;
},
getLanguage () {
return language;
},
setLanguage: function (lang) {
language = lang;
jitsiLocalStorage.setItem("language", lang);
},
/**
* Sets new flipX state of local video and saves it to the local storage.
* @param {string} val flipX state of local video

View File

@@ -1,108 +1,56 @@
/* global $, require, config, interfaceConfig */
import i18n from 'i18next';
import XHR from 'i18next-xhr-backend';
/* @flow */
import jqueryI18next from 'jquery-i18next';
import languagesR from "../../lang/languages.json";
import mainR from "../../lang/main.json";
import languages from "../../service/translation/languages";
const DEFAULT_LANG = languages.EN;
import { DEFAULT_LANGUAGE, i18next } from '../../react/features/base/i18n';
const defaultOptions = {
compatibilityAPI: 'v1',
compatibilityJSON: 'v1',
fallbackLng: DEFAULT_LANG,
load: "unspecific",
resGetPath: 'lang/__ns__-__lng__.json',
ns: {
namespaces: ['main', 'languages'],
defaultNs: 'main'
},
lngWhitelist : languages.getLanguages(),
fallbackOnNull: true,
fallbackOnEmpty: true,
useDataAttrOptions: true,
app: interfaceConfig.APP_NAME
};
declare var $: Function;
function initCompleted() {
$("[data-i18n]").localize();
}
function getLangFromQuery() {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == "lang")
{
return pair[1];
}
}
return null;
/**
* Notifies that the {@link i18next} instance has finished its initialization.
*
* @returns {void}
* @private
*/
function _onI18nInitialized() {
$('[data-i18n]').localize();
}
class Translation {
init (settingsLang) {
let options = defaultOptions;
let lang = getLangFromQuery() || settingsLang || config.defaultLanguage;
// XXX If none of the above has been set then the 'lang' will be
// 'undefined' and the i18n lib will try to auto detect user's
// preferred language based on browser's locale.
// The interface config option allows to disable this auto detection
// by specifying the fallback language in that case.
let langDetection = interfaceConfig.LANG_DETECTION;
if (!langDetection && !lang) {
lang = DEFAULT_LANG;
}
if (lang) {
options.lng = lang;
}
i18n.use(XHR)
.use({
type: 'postProcessor',
name: "resolveAppName",
process: (res, key) => {
return i18n.t(key, {app: options.app});
}
})
.init(options, initCompleted);
// adds default language which is preloaded from code
i18n.addResourceBundle(DEFAULT_LANG, 'main', mainR, true, true);
i18n.addResourceBundle(
DEFAULT_LANG, 'languages', languagesR, true, true);
jqueryI18next.init(i18n, $, {useOptionsAttr: true});
addLanguageChangedListener(listener: Function) {
i18next.on('languageChanged', listener);
}
setLanguage (lang) {
if(!lang)
lang = DEFAULT_LANG;
i18n.setLng(lang, defaultOptions, initCompleted);
}
generateTranslationHTML(key: string, options: Object) {
const optAttr
= options ? ` data-i18n-options='${JSON.stringify(options)}'` : '';
getCurrentLanguage () {
return i18n.lng();
}
// XXX i18next expects undefined if options are missing.
const text = i18next.t(key, options ? options : undefined);
translateElement (selector, options) {
// i18next expects undefined if options are missing, check if its null
selector.localize(
options === null ? undefined : options);
}
generateTranslationHTML (key, options) {
let optAttr = options
? ` data-i18n-options='${JSON.stringify(options)}'` : "";
let text = i18n.t(key, options === null ? undefined : options);
return `<span data-i18n="${key}"${optAttr}>${text}</span>`;
}
addLanguageChangedListener(listener) {
i18n.on('languageChanged', listener);
getCurrentLanguage() {
return i18next.lng();
}
init() {
jqueryI18next.init(i18next, $, { useOptionsAttr: true });
if (i18next.isInitialized)
_onI18nInitialized();
else
i18next.on('initialized', _onI18nInitialized);
}
setLanguage(language: string = DEFAULT_LANGUAGE) {
i18next.setLng(language, {}, _onI18nInitialized);
}
translateElement(selector: Object, options: Object) {
// XXX i18next expects undefined if options are missing.
selector.localize(options ? options : undefined);
}
}

View File

@@ -16,69 +16,78 @@
"readmeFilename": "README.md",
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
"dependencies": {
"@atlassian/aui": "^6.0.0",
"@atlassian/aui": "6.0.6",
"@atlaskit/button": "1.0.3",
"@atlaskit/button-group": "1.0.0",
"@atlaskit/modal-dialog": "1.2.4",
"async": "0.9.0",
"autosize": "^1.18.13",
"autosize": "1.18.13",
"bootstrap": "3.1.1",
"i18next": "3.4.4",
"i18next-xhr-backend": "1.1.0",
"es6-iterator": "2.0.1",
"es6-symbol": "3.1.1",
"i18next": "7.1.2",
"i18next-browser-languagedetector": "1.0.1",
"i18next-xhr-backend": "1.4.0",
"jitsi-meet-logger": "jitsi/jitsi-meet-logger",
"jquery": "~2.1.1",
"jquery-contextmenu": "*",
"jquery-i18next": "1.1.0",
"jquery": "2.1.4",
"jquery-contextmenu": "2.4.3",
"jquery-i18next": "1.2.0",
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
"jquery-ui": "1.10.5",
"jssha": "1.5.0",
"jws": "*",
"jws": "3.1.4",
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
"postis": "^2.2.0",
"postis": "2.2.0",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.41.2",
"react-i18next": "2.2.0",
"react-native": "0.42.3",
"react-native-background-timer": "1.0.0",
"react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2",
"react-native-prompt": "^1.0.0",
"react-native-vector-icons": "^4.0.0",
"react-native-keep-awake": "2.0.3",
"react-native-locale-detector": "1.0.1",
"react-native-prompt": "1.0.0",
"react-native-vector-icons": "4.0.0",
"react-native-webrtc": "jitsi/react-native-webrtc",
"react-redux": "^5.0.2",
"redux": "^3.5.2",
"redux-thunk": "^2.1.0",
"react-redux": "5.0.3",
"redux": "3.6.0",
"redux-thunk": "2.2.0",
"retry": "0.6.1",
"strophe": "1.2.4",
"strophejs-plugins": "0.0.7",
"toastr": "^2.0.3",
"toastr": "2.1.2",
"url-polyfill": "github/url-polyfill",
"xmldom": "^0.1.27"
"xmldom": "0.1.27"
},
"devDependencies": {
"babel-core": "^6.18.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-polyfill": "*",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-1": "^6.16.0",
"clean-css": "^3.0.0",
"css-loader": "*",
"eslint": "^3.15.0",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsdoc": "*",
"eslint-plugin-react": "*",
"eslint-plugin-react-native": "^2.2.1",
"expose-loader": "*",
"file-loader": "^0.10.0",
"flow-bin": "^0.37.0",
"haste-resolver-webpack-plugin": "^0.2.2",
"imports-loader": "*",
"babel-core": "6.24.0",
"babel-eslint": "7.2.0",
"babel-loader": "6.4.1",
"babel-polyfill": "6.23.0",
"babel-preset-es2015": "6.24.0",
"babel-preset-react": "6.23.0",
"babel-preset-stage-1": "6.22.0",
"clean-css": "3.4.25",
"css-loader": "0.26.2",
"eslint": "3.18.0",
"eslint-plugin-flowtype": "2.30.3",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-jsdoc": "3.0.0",
"eslint-plugin-react": "6.10.3",
"eslint-plugin-react-native": "2.3.1",
"expose-loader": "0.7.3",
"file-loader": "0.10.1",
"flow-bin": "0.38.0",
"haste-resolver-webpack-plugin": "0.2.2",
"imports-loader": "0.7.1",
"jshint": "2.9.4",
"json-loader": "0.5.4",
"node-sass": "^3.8.0",
"node-sass": "3.13.1",
"precommit-hook": "3.0.0",
"string-replace-loader": "*",
"style-loader": "*",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
"string-replace-loader": "1.0.5",
"style-loader": "0.15.0",
"webpack": "1.14.0",
"webpack-dev-server": "1.16.3"
},
"license": "Apache-2.0",
"scripts": {

View File

@@ -8,7 +8,6 @@ import {
_parseURIString,
init
} from './functions';
import './reducer';
/**
* Temporary solution. Should dispatch actions related to initial settings of
@@ -25,16 +24,26 @@ export function appInit() {
* Triggers an in-app navigation to a different route. Allows navigation to be
* abstracted between the mobile and web versions.
*
* @param {(string|undefined)} urlOrRoom - The URL or room name to which to
* navigate.
* @param {(string|undefined)} uri - The URI to which to navigate. It may be a
* full URL with an http(s) scheme, a full or partial URI with the app-specific
* sheme, or a mere room name.
* @returns {Function}
*/
export function appNavigate(urlOrRoom) {
export function appNavigate(uri) {
return (dispatch, getState) => {
const state = getState();
const oldDomain = getDomain(state);
const { domain, room } = _parseURIString(urlOrRoom);
// eslint-disable-next-line prefer-const
let { domain, room } = _parseURIString(uri);
// If the specified URI does not identify a domain, use the app's
// default.
if (typeof domain === 'undefined') {
domain
= _parseURIString(state['features/app'].app._getDefaultURL())
.domain;
}
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
// currently in a conference and ask her if she wants to close the
@@ -49,7 +58,7 @@ export function appNavigate(urlOrRoom) {
dispatch(setDomain(domain));
// If domain has changed, we need to load the config of the new
// domain and set it, and only after that we can navigate to
// domain and set it, and only after that we can navigate to a
// different route.
loadConfig(`https://${domain}`)
.then(
@@ -93,7 +102,7 @@ export function appNavigate(urlOrRoom) {
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? urlOrRoom
? uri
: room));
}
};

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