Compare commits

...

93 Commits

Author SHA1 Message Date
yanas
988ce595bb mend 2018-02-08 20:59:08 -06:00
yanas
8cc6e43b59 WiP emotion animations 2018-02-08 20:58:14 -06:00
Saúl Ibarra Corretgé
bd301403c4 [RN] Fix app startup from a CallKit intent
Story time.  Currently the app can be started in 4 ways:

- just tapping on the icon
- via a deep link
- via a universal link
- via the phone's recent calls list

The last 3 options will make the app join the specified room upon launch. React
Native's Linking module implements the necessary bits to handle deep or
universal linking, but CallKit is out of its scope.

In order to blend any type of app startup mode, a new LaunchOptions module (iOS
only) exports a getInitialURL function, akin to the one in the Linking module,
but taking CallKit instents into consideration. This function is then used to
make app startup with a URL consistent across all different modes.
2018-02-07 10:12:10 -06:00
bgrozev
d481c6f736 chore: Updates lib-jitsi-meet to 5f8c0a662af086e7bcc19c010f1129afc9b6… (#2460)
* chore: Updates lib-jitsi-meet to 5f8c0a662af086e7bcc19c010f1129afc9b6d650

* squash: revert changes to package-lock.json except for the lib-jitsi-meet version change.
2018-02-06 21:21:39 -07:00
Дамян Минков
ba94ba30c5 Handles connection failed event details (passing them to analytics). (#2432)
* Handles connection failed event details (passing them to analytics).

* Fixing comments.

* Updates depending versions to be able to test.

* Fixing comments.

* Fixes wrong jsdoc.
2018-02-06 14:54:21 -08:00
Saúl Ibarra Corretgé
5305557ce5 [RN] Add a "reduced UI" mode
It's detected based on a size threshold.
2018-02-06 15:53:52 -06:00
Lyubo Marinov
c9d8b5c827 Finally! Let there be... responsive-ui!
We started on the way to responsive UI and its design with aspect ratio
and keeping the filmstrip on the short side of the app's visible
rectangle.

Shortly, we're going to introduce reduced UI for Picture-in-Picture. And
that's where we'll need another dimensions-based detector akin to the
aspect ratio detector.

While the AspectRatioDetector, the up-and-coming ReducedUIDetector, and
their base DimensionsDetector are definitely separate abstractions and
implementations not mixed for the purposes of easy extensibility and
maintenance, the three of them are our building blocks on top of which
we'll build our responsive UI.
2018-02-06 15:53:27 -06:00
Saúl Ibarra Corretgé
0ad1c88cd2 [RN] Refactor AspectRatioDetector
Factor out the dimensions detection login into a DimensionsDetector component.
2018-02-06 11:21:12 -06:00
Saúl Ibarra Corretgé
78fbfba573 [iOS] Fix initial CallKit muted state
Turns out this was a bit more involved than I originally thought due to an
interesting (corner) case: IFF the user was never asked about microphone
permissions and the call starts with audio muted, unmuting from the CallKit
interface won't work (iOS won't show the prompt, it fails immediately) and we
need to sync the mute state back.
2018-02-06 10:24:06 -06:00
Saúl Ibarra Corretgé
9e53d40b9c [RN] Honor filmstrip visibility state 2018-02-05 15:55:05 -06:00
Lyubo Marinov
aa314c10ac Coding style: consistent naming, one name per abstraction
Instead of having visible and visibility and setToolboxVisible and
setFilmstripVisibility, have only visible as a name.
2018-02-05 15:55:04 -06:00
Lyubo Marinov
62c9762793 [RN] Protect AbstractApp and localStorage initialization 2018-02-05 15:26:01 -06:00
Lyubo Marinov
d7dddb2509 Introduce base/storage to represent the Web Storage API and persistence-related customizations 2018-02-02 15:13:26 -06:00
Lyubo Marinov
83243d5980 [RN] Fix legacy recent-list storage 2018-02-02 15:13:26 -06:00
Zoltan Bettenbuk
6e05cab46e [RN] Fix legacy recent-list storage 2018-02-02 15:13:26 -06:00
Lyubo Marinov
7954d5fd39 Coding style 2018-02-02 15:13:26 -06:00
zbettenbuk
158cadf4f9 Improve persistency layer 2018-02-02 15:13:26 -06:00
Lyubo Marinov
f35578c803 [RN] Polyfill __filename ASAP 2018-02-02 15:13:26 -06:00
Saúl Ibarra Corretgé
c087e90099 [RN] Fix setReceivedVideoQuality if we are not yet in a conference
It may happen that such action is fired while joining.
2018-02-02 14:33:49 -06:00
Saúl Ibarra Corretgé
da0ae73d10 [RN] Fix pinParticipant if we are not yet in a conference
It may happen that such action is fired while joining.
2018-02-02 14:32:15 -06:00
Saúl Ibarra Corretgé
b4d44f367d [RN] aspect-ratio: preserve mode when width === height
If the view gets resized to a 1:1 aspect ratio, remember the previous mode to
avoid flickering when going back to a larger size or different aspect ratio.
2018-02-02 14:19:08 -06:00
hristoterezov
083f6b400b chore(capabilities.json): deploy 2018-02-01 17:21:52 -06:00
Saúl Ibarra Corretgé
dd5ae49217 Merge pull request #2407 from zbettenbuk/new-welcome-screen
Add URL validation and larger distance behind the back button
2018-01-31 16:26:09 +01:00
zbettenbuk
6a9e6db3be [RN] Validate the URL in app-settings 2018-01-31 16:06:24 +01:00
virtuacoplenny
c4468cb7b8 chore(deps): update lib-jitsi-meet for connection quality logging (#2436) 2018-01-30 14:52:25 -08:00
Leonard Kim
80c4205fb8 chore(deps): update lib-jitsi-meet for ie11 browser caps fix 2018-01-29 16:16:21 -06:00
zbettenbuk
aa9efd6f69 [RN] Improve app-settings back button style 2018-01-29 10:58:46 +01:00
Piérre Reimertz
6f8f64ba48 [iOS] Fix crash if the app display name is not set
Fixes: #2377 #2267 #2158
2018-01-29 10:34:01 +01:00
Leonard Kim
1c3cef1eed fix(notifications): reduce duration of initially muted notification
The current notification for starting muted is 2 minutes, which
may seem like "forever" so reduce it to dismiss faster.
2018-01-28 18:48:56 -06:00
Leonard Kim
2720c76e4d fix(password): do not let guests edit password when roles are enabled
If config.enableUserRolesBasedOnToken is true, only let moderators
and non-guests modify the password. Otherwise, only let moderators
edit the password.
2018-01-28 18:48:24 -06:00
virtuacoplenny
4ab34589c8 Merge pull request #2431 from jitsi/recommended_browsers_link
fix(recommended-browsers): Fix link
2018-01-28 13:41:54 -08:00
hristoterezov
ed36132e94 fix(recommended-browsers): Fix link 2018-01-28 11:51:08 -06:00
virtuacoplenny
f43687944c Merge pull request #2429 from jitsi/fix_chromium
chore(lib-jitsi-meet): Update the version.
2018-01-27 11:45:46 -08:00
hristoterezov
dda0ea0ba9 chore(lib-jitsi-meet): Update the version. 2018-01-27 13:02:46 -06:00
Lyubo Marinov
e1f967869a [RN] Add builtin translations 2018-01-26 12:18:43 -06:00
Saúl Ibarra Corretgé
8673083829 [RN] Add builtin translations
Load all of them as imports, so the packager includes them in the bundle. Then
register them with the i18next library.
2018-01-26 16:27:33 +01:00
Lyubo Marinov
b52e584327 "feat(TPC): append TPC ID to stream IDs" & "fix(RTC): protect from counter overflow" 2018-01-25 11:21:40 -06:00
hristoterezov
4c65262a87 fix(browser-caps): Deploy. 2018-01-25 10:44:24 -06:00
bgrozev
7ce670df0c doc: Add -f to update-ca-certificates 2018-01-25 10:42:43 -06:00
Boris Grozev
f5f341ca9e npm: Updates lib-jitsi-meet to e895c9b86f57a288f644dcc61f81771034b932da. 2018-01-24 14:15:52 -06:00
Lyubo Marinov
f29fbb6757 [iOS] WebRTC 63 with 2 backports to fix a crash 2018-01-24 13:37:54 -06:00
virtuacoplenny
e5e3c6c6c4 Merge pull request #2410 from bgrozev/set-product
feat: Sets the "product" field in the analytics handler constructor.
2018-01-23 15:25:59 -08:00
Boris Grozev
dc92fb5073 feat: Sets the "product" field in the analytics handler constructor. 2018-01-23 16:14:31 -06:00
damencho
2478176f23 Adds uiLoaded event in iframe API, fired when all resources are loaded. 2018-01-23 15:43:49 -06:00
virtuacoplenny
12ec982067 Merge pull request #2400 from jitsi/browser_caps2
fix(lib-jitsi-meet): RTCBrowserType -> browser.
2018-01-22 13:57:50 -08:00
hristoterezov
c9e3e5052d fix(lib-jitsi-meet): RTCBrowserType -> browser. 2018-01-22 15:56:06 -06:00
Emil Ivov
22401614a7 Merge pull request #2401 from virtuacoplenny/lenny/spot-feedback-api
feat(api): expose a way to submit feedback
2018-01-19 16:57:18 -06:00
Leonard Kim
762f529f1d feat(api): expose a way to submit feedback
Spot will need a way to submit call feedback using the iframe
api. For now expose a method on conference.js to submit that
feedback. Exposing on conference.js looks to be the existing
pattern... Also add an event to notify consumers of the iframe
api that feedback was submitted, as postMessage is async
and the notification can at least give some guarantee maybe.

I haven't updated documentation yet as I'm not confident
about this api.
2018-01-19 14:27:44 -08:00
Saúl Ibarra Corretgé
1f6b743bec [RN] Join room when pressing "go" on the keyboard
Improve the experience when joining a room by removing the need to tap the join
button. The keyboard type has also been set to "go", which translated on the
builtin keyboard button label to be "go" (it's builtin, the operating system
translates it). This works on both Android and iOS.
2018-01-19 16:10:51 -06:00
Lyubo Marinov
48f4317adb [RN] Make video track fade-in effect cross-platform 2018-01-19 16:05:59 -06:00
Saúl Ibarra Corretgé
75f6786588 [RN] Make video track fade-in effect cross-platform
Android uses a SurfaceView to render video, which is not quite a View, so the
fade-in animation (which varies the opacity) doesn't work.

Instead, add an opaque black view covering the video, which transitions to
transparent. This creates much smoother transitions on Android, while behaving
the same.

In addition, I removed the flip animation for local tracks, which is no longer
used, since the camera is switched without changing tracks.
2018-01-19 15:32:17 -06:00
Lyubo Marinov
3ec4d67a99 [RN] Strip spaces when parsing URLs 2018-01-19 15:26:13 -06:00
Saúl Ibarra Corretgé
a23eec55e8 [RN] Strip spaces when parsing URLs 2018-01-19 15:14:51 -06:00
Lyubo Marinov
bdf2ecfe4b [iOS] CocoaPods 1.4.0 2018-01-19 15:00:18 -06:00
Lyubo Marinov
fc36759114 [RN] WebRTC 63 2018-01-19 14:56:58 -06:00
Saúl Ibarra Corretgé
9c2849a663 [Android] Enable Java 1.8 compatibility support
It was recently introduced in WebRTC, so we we need to enable it project wide.
As for what features are supportd, see:
https://developer.android.com/studio/write/java8-support.html
2018-01-19 14:05:25 -06:00
Saúl Ibarra Corretgé
98ff20a026 [RN] Simplify initialization of AsyncStorage 2018-01-19 09:41:34 -06:00
Lyubo Marinov
b04661b40b [RN] Revert unintentional disabling of the yellow box 2018-01-18 21:11:24 -06:00
Lyubo Marinov
112c856850 Fix settings screen layout on iOS and add soft back button 2018-01-18 15:45:25 -06:00
zbettenbuk
410dc132e1 Fix settings screen layout on iOS and add soft back button 2018-01-18 10:06:26 -06:00
Saúl Ibarra Corretgé
b7f950f5f7 feat(analytics): shield sending analytics events
Any failure in analytics should not prevent the natural flow of the code. Shield
the function by catching and logging any exception.
2018-01-17 16:46:54 -06:00
Paweł Domas
7ad875e735 fix(RN logging): sync with logging_config.js (#2382) 2018-01-17 10:59:48 -08:00
Saúl Ibarra Corretgé
41aa704e1f [RN] Update React Native to version 0.51.0 2018-01-11 11:04:28 -06:00
Saúl Ibarra Corretgé
e00ea353e8 Merge pull request #2380 from saghul/app-settings-kbd
[RN] Use the "email" keyboard for the email field
2018-01-10 15:52:27 +01:00
Saúl Ibarra Corretgé
6f93424d7c [RN] Use the "email" keyboard for the email field 2018-01-10 14:30:34 +01:00
Saúl Ibarra Corretgé
292f3ab1bd [Android] Fix crash if settings activity cannot be opened
The documentation states this is possible, so make sure we handle such errors.

Ref:
https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
2018-01-09 13:12:53 -06:00
Boris Grozev
a3cb081609 fix: Fixes an exception when the chat button and shortcut are clicked. 2018-01-09 10:36:55 -08:00
Leonard Kim
64c5ae1c48 fix(close): use string concatenation for ie11 2018-01-08 16:26:54 -06:00
bgrozev
259004b8bf fix: Fixes the user ID read from JWT. (#2366) 2018-01-08 11:27:08 -06:00
ibauersachs
4fea22676b Commit from translate.jitsi.org by user ibauersachs.: 410 of 410 strings translated (0 fuzzy). 2018-01-08 16:03:42 +00:00
zbettenbuk
05a492241f Fix local participant details on web 2018-01-05 12:58:58 -06:00
virtuacoplenny
8ec4697a27 Merge pull request #2353 from jitsi/suboptimal_experience
feat(notification): Add suboptimal browser exp notification.
2018-01-04 14:36:58 -08:00
hristoterezov
a357b0cf14 feat(notification): Add suboptimal browser exp notification. 2018-01-04 15:57:17 -06:00
virtuacoplenny
9eff669b0b Merge pull request #2357 from bgrozev/analytics-update
Analytics update
2018-01-04 12:32:07 -08:00
Boris Grozev
c7714959e6 feat: Passes the environment type and subproduct to the analytics handler constructor. 2018-01-04 13:12:48 -06:00
Boris Grozev
e898527294 feat: Passes the "group" from JWT to the analytics handler constructor. 2018-01-04 13:12:48 -06:00
zbettenbuk
bfcd34358b [RN] Add app-settings feature
[RN] Fix PR feedbacks, write persistency docs
2018-01-04 12:53:29 -06:00
bbaldino
871ef9ff0e change TPC logs to info by default 2018-01-04 08:14:40 -06:00
Saúl Ibarra Corretgé
178c8e02ff [RN] Disable the XHR backend on mobile
All language assets will need to be bundled.
2018-01-03 19:18:12 -06:00
bgrozev
43ac039fd6 fix: Adapts to changes in lib-jitsi-meet. (#2354) 2018-01-03 16:07:39 -08:00
bgrozev
090f2f9ccb Restructures the analytics events (#2333)
* ref: Restructures the pinned/unpinned events.

* ref: Refactors the "audio only disabled" event.

* ref: Refactors the "stream switch delay" event.

* ref: Refactors the "select participant failed" event.

* ref: Refactors the "initially muted" events.

* ref: Refactors the screen sharing started/stopped events.

* ref: Restructures the "device list changed" events.

* ref: Restructures the "shared video" events.

* ref: Restructures the "start muted" events.

* ref: Restructures the "start audio only" event.

* ref: Restructures the "sync track state" event.

* ref: Restructures the "callkit" events.

* ref: Restructures the "replace track".

* ref: Restructures keyboard shortcuts events.

* ref: Restructures most of the toolbar events.

* ref: Refactors the API events.

* ref: Restructures the video quality, profile button and invite dialog events.

* ref: Refactors the "device changed" events.

* ref: Refactors the page reload event.

* ref: Removes an unused function.

* ref: Removes a method which is needlessly exposed under a different name.

* ref: Refactors the events from the remote video menu.

* ref: Refactors the events from the profile pane.

* ref: Restructures the recording-related events.

Removes events fired when recording with something other than jibri
(which isn't currently supported anyway).

* ref: Cleans up AnalyticsEvents.js.

* ref: Removes an unused function and adds documentation.

* feat: Adds events for all API calls.

* fix: Addresses feedback.

* fix: Brings back mistakenly removed code.

* fix: Simplifies code and fixes a bug in toggleFilmstrip

when the 'visible' parameter is defined.

* feat: Removes the resolution change application log.

* ref: Uses consistent naming for events' attributes.

Uses "_" as a separator instead of camel case or ".".

* ref: Don't add the user agent and conference name

as permanent properties. The library does this on its own now.

* ref: Adapts the GA handler to changes in lib-jitsi-meet.

* ref: Removes unused fields from the analytics handler initializaiton.

* ref: Renames the google analytics file and add docs.

* fix: Fixes the push-to-talk events and logs.

* npm: Updates lib-jitsi-meet to 515374c8d383cb17df8ed76427e6f0fb5ea6ff1e.

* fix: Fixes a recently introduced bug in the google analytics handler.

* ref: Uses "value" instead of "delay" since this is friendlier to GA.
2018-01-03 13:24:07 -08:00
zbettenbuk
d08bbae770 [RN] Add proper locale support to MomentJS 2018-01-03 10:13:15 -06:00
Дамян Минков
30b51ff384 Adds warning to manual-install doc. 2018-01-02 17:18:06 -06:00
ibauersachs
c109199e06 Commit from translate.jitsi.org by user ibauersachs.: 409 of 410 strings translated (0 fuzzy). 2017-12-29 15:21:18 +00:00
ibauersachs
0ed31f0ae8 Commit from translate.jitsi.org by user ibauersachs.: 399 of 399 strings translated (0 fuzzy). 2017-12-29 15:01:39 +00:00
jitsi-pootle
006e6cc851 New files added from translate.jitsi.org based on templates 2017-12-28 23:44:49 +00:00
Leonard Kim
0680c086df chore(deps): update lib-jitsi-meet for ie11 stringify failure 2017-12-28 14:26:35 -06:00
Leonard Kim
698ec1e2d7 Revert "feat(avatars): use initials service for getting images (#2312)"
There is more avatar work coming down the line for mobile,
which should also affect web, assuming the same getAvatarURL
helper will be used. As such, instead of continuing to
support the initials service and tweaking UI, revert to
make way for the future avatar work.

This reverts commit 2ea5ad68a5.
2017-12-28 14:17:33 -06:00
Leonard Kim
e217c172f8 ref(toolbar): re-wrap buttons for torture tests
The inline classes for the toolbars were re-arranged
to fix non-rounded corners in the always-on-top window's
toolbar. However, those classes were also used by the
torture tests as a way to find stable elements that will
not get blown away by a react re-render. So re-wrap the
buttons with a div that will not get blown away,
add back the inline classes to those divs, and change
the CSS to round the corners in the always-on-top
window's toolbar.
2017-12-26 16:08:40 -06:00
jitsi-pootle
6146c12533 New files added from translate.jitsi.org based on templates 2017-12-26 20:48:41 +00:00
damencho
eb64b74493 Commit from translate.jitsi.org by user damencho.: 399 of 399 strings translated (0 fuzzy). 2017-12-26 20:47:35 +00:00
bgrozev
1255b3349b doc: Adds a note on security (as suggested in #409). (#2322) 2017-12-26 10:49:46 -08:00
virtuacoplenny
2ea5ad68a5 feat(avatars): use initials service for getting images (#2312)
* feat(avatars): use initial service for getting images

* squash: capitalize and minor refactor of string concat
2017-12-21 13:36:00 -06:00
189 changed files with 9159 additions and 4611 deletions

View File

@@ -56,8 +56,8 @@ suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
@@ -76,4 +76,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.56.0
^0.57.0

View File

@@ -35,7 +35,7 @@ deploy-appbundle:
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.map \
$(OUTPUT_DIR)/analytics.js \
$(OUTPUT_DIR)/analytics-ga.js \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:
@@ -43,6 +43,7 @@ deploy-lib-jitsi-meet:
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
deploy-css:

View File

@@ -1,6 +1,6 @@
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
You can also try it out yourself at https://meet.jit.si .
@@ -98,6 +98,21 @@ see our [guidelines for contributing](CONTRIBUTING.md).
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
## Security
WebRTC today does not provide a way of conducting multiparty conversations with
end-to-end encryption. As a matter of fact, unless you consistently vocally
compare DTLS fingerprints with your peers, the same goes for one-to-one calls.
As a result when using a Jitsi Meet instance, your stream is encrypted on the
network but decrypted on the machine that hosts the bridge.
The Jitsi Meet architecture allows you to deploy your own version, including
all server components, and in that case your security guarantees will be roughly
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [Atlassian](https://atlassian.com).
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).

146
analytics-ga.js Normal file
View File

@@ -0,0 +1,146 @@
/* global ga */
(function(ctx) {
/**
*
*/
function Analytics() {
/* eslint-disable */
/**
* Google Analytics
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
/* eslint-enable */
}
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractAction = function(event) {
// Page events have a single 'name' field.
if (event.type === 'page') {
return event.name;
}
// All other events have action, actionSubject, and source fields. All
// three fields are required, and the often jitsi-meet and
// lib-jitsi-meet use the same value when separate values are not
// necessary (i.e. event.action == event.actionSubject).
// Here we concatenate these three fields, but avoid adding the same
// value twice, because it would only make the GA event's action harder
// to read.
let action = event.action;
if (event.actionSubject && event.actionSubject !== event.action) {
// Intentionally use string concatenation as analytics needs to
// work on IE but this file does not go through babel. For some
// reason disabling this globally for the file does not have an
// effect.
// eslint-disable-next-line prefer-template
action = event.actionSubject + '.' + action;
}
if (event.source && event.source !== event.action
&& event.source !== event.action) {
// eslint-disable-next-line prefer-template
action = event.source + '.' + action;
}
return action;
};
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
* suitable value.
* @private
*/
Analytics.prototype._extractValue = function(event) {
let value = event && event.attributes && event.attributes.value;
// Try to extract an integer from the "value" attribute.
value = Math.round(parseFloat(value));
return value;
};
/**
* Extracts the string to use for a Google Analytics event's label field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {string} - The string to use for the 'label' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractLabel = function(event) {
let label = '';
// The label field is limited to 500B. We will concatenate all
// attributes of the event, except the user agent because it may be
// lengthy and is probably included from elsewhere.
for (const property in event.attributes) {
if (property !== 'permanent_user_agent'
&& event.attributes.hasOwnProperty(property)) {
// eslint-disable-next-line prefer-template
label += property + '=' + event.attributes[property] + '&';
}
}
if (label.length > 0) {
label = label.slice(0, -1);
}
return label;
};
/**
* This is the entry point of the API. The function sends an event to
* google analytics. The format of the event is described in
* AnalyticsAdapter in lib-jitsi-meet.
* @param {Object} event - the event in the format specified by
* lib-jitsi-meet.
*/
Analytics.prototype.sendEvent = function(event) {
if (!event) {
return;
}
const gaEvent = {
'eventCategory': 'jitsi-meet',
'eventAction': this._extractAction(event),
'eventLabel': this._extractLabel(event)
};
const value = this._extractValue(event);
if (!isNaN(value)) {
gaEvent.eventValue = value;
}
ga('send', 'event', gaEvent);
};
if (typeof ctx.JitsiMeetJS === 'undefined') {
ctx.JitsiMeetJS = {};
}
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
ctx.JitsiMeetJS.app = {};
}
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
ctx.JitsiMeetJS.app.analyticsHandlers = [];
}
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
})(window);
/* eslint-enable prefer-template */

View File

@@ -1,47 +0,0 @@
/* global ga */
(function(ctx) {
/**
*
*/
function Analytics() {
/* eslint-disable */
/**
* Google Analytics
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
/* eslint-enable */
}
Analytics.prototype.sendEvent = function(action, data) {
// empty label if missing value for it and add the value,
// the value should be integer or null
let value = data.value;
value = value ? Math.round(parseFloat(value)) : null;
const label = data.label || '';
// Intentionally use string concatenation as analytics needs to work on
// IE but this file does not go through babel.
// eslint-disable-next-line prefer-template
ga('send', 'event', 'jit.si', action + '.' + data.browserName,
label, value);
};
if (typeof ctx.JitsiMeetJS === 'undefined') {
ctx.JitsiMeetJS = {};
}
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
ctx.JitsiMeetJS.app = {};
}
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
ctx.JitsiMeetJS.app.analyticsHandlers = [];
}
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
})(window);

View File

@@ -43,6 +43,16 @@ Add the Maven repository
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
Add Java 1.8 compatibility support to your project by adding the following lines
into your `build.gradle` file:
```
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
```
## API
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet

View File

@@ -31,6 +31,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {

View File

@@ -5,11 +5,13 @@
package org.jitsi.meet.sdk;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
@@ -25,7 +27,7 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void open() {
public void open(Promise promise) {
Context context = getReactApplicationContext();
Intent intent = new Intent();
@@ -34,6 +36,15 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
intent.setData(
Uri.fromParts("package", context.getPackageName(), null));
context.startActivity(intent);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// Some devices may give an error here.
// https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
promise.reject(e);
return;
}
promise.resolve(null);
}
}

View File

@@ -16,19 +16,13 @@ import UIUtil from './modules/UI/util/UIUtil';
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
import {
CONFERENCE_AUDIO_INITIALLY_MUTED,
CONFERENCE_SHARING_DESKTOP_START,
CONFERENCE_SHARING_DESKTOP_STOP,
CONFERENCE_VIDEO_INITIALLY_MUTED,
DEVICE_LIST_CHANGED_AUDIO_MUTED,
DEVICE_LIST_CHANGED_VIDEO_MUTED,
SELECT_PARTICIPANT_FAILED,
SETTINGS_CHANGE_DEVICE_AUDIO_OUT,
SETTINGS_CHANGE_DEVICE_AUDIO_IN,
SETTINGS_CHANGE_DEVICE_VIDEO,
STREAM_SWITCH_DELAY,
createDeviceChangedEvent,
createScreenSharingEvent,
createSelectParticipantFailedEvent,
createStreamSwitchDelayEvent,
createTrackMutedEvent,
initAnalytics,
sendAnalyticsEvent
sendAnalytics
} from './react/features/analytics';
import EventEmitter from 'events';
@@ -97,7 +91,10 @@ import {
import { statsEmitter } from './react/features/connection-indicator';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import { maybeOpenFeedbackDialog } from './react/features/feedback';
import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
@@ -741,14 +738,13 @@ export default {
})
.then(([ tracks, con ]) => {
tracks.forEach(track => {
if (track.isAudioTrack() && this.isLocalAudioMuted()) {
sendAnalyticsEvent(CONFERENCE_AUDIO_INITIALLY_MUTED);
logger.log('Audio mute: initially muted');
track.mute();
} else if (track.isVideoTrack()
&& this.isLocalVideoMuted()) {
sendAnalyticsEvent(CONFERENCE_VIDEO_INITIALLY_MUTED);
logger.log('Video mute: initially muted');
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
const mediaType = track.getType();
sendAnalytics(
createTrackMutedEvent(mediaType, 'initial mute'));
logger.log(`${mediaType} mute: initially muted.`);
track.mute();
}
});
@@ -1453,8 +1449,9 @@ export default {
promise = createLocalTracksF({ devices: [ 'video' ] })
.then(([ stream ]) => this.useVideoStream(stream))
.then(() => {
sendAnalyticsEvent(CONFERENCE_SHARING_DESKTOP_STOP);
logger.log('switched back to local video');
sendAnalytics(createScreenSharingEvent('stopped'));
logger.log('Screen sharing stopped, switching to video.');
if (!this.localVideo && wasVideoMuted) {
return Promise.reject('No local video to be muted!');
} else if (wasVideoMuted && this.localVideo) {
@@ -1609,7 +1606,7 @@ export default {
},
/**
* Tries to switch to the screenshairng mode by disposing camera stream and
* Tries to switch to the screensharing mode by disposing camera stream and
* replacing it with a desktop one.
*
* @param {Object} [options] - Screen sharing options that will be passed to
@@ -1632,8 +1629,8 @@ export default {
.then(stream => this.useVideoStream(stream))
.then(() => {
this.videoSwitchInProgress = false;
sendAnalyticsEvent(CONFERENCE_SHARING_DESKTOP_START);
logger.log('sharing local desktop');
sendAnalytics(createScreenSharingEvent('started'));
logger.log('Screen sharing started');
})
.catch(error => {
this.videoSwitchInProgress = false;
@@ -1928,7 +1925,7 @@ export default {
room.selectParticipant(id);
} catch (e) {
sendAnalyticsEvent(SELECT_PARTICIPANT_FAILED);
sendAnalytics(createSelectParticipantFailedEvent(e));
reportError(e);
}
});
@@ -2152,22 +2149,12 @@ export default {
APP.UI.addListener(
UIEvents.RESOLUTION_CHANGED,
(id, oldResolution, newResolution, delay) => {
const logObject = {
id: 'resolution_change',
participant: id,
oldValue: oldResolution,
newValue: newResolution,
delay
};
room.sendApplicationLog(JSON.stringify(logObject));
// We only care about the delay between simulcast streams.
// Longer delays will be caused by something else and will just
// poison the data.
if (delay < 2000) {
sendAnalyticsEvent(STREAM_SWITCH_DELAY, { value: delay });
}
sendAnalytics(createStreamSwitchDelayEvent(
{
'old_resolution': oldResolution,
'new_resolution': newResolution,
value: delay
}));
});
/* eslint-enable max-params */
@@ -2193,7 +2180,7 @@ export default {
cameraDeviceId => {
const videoWasMuted = this.isLocalVideoMuted();
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_VIDEO);
sendAnalytics(createDeviceChangedEvent('video', 'input'));
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId,
@@ -2232,7 +2219,7 @@ export default {
micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted();
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_AUDIO_IN);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
cameraDeviceId: null,
@@ -2262,7 +2249,7 @@ export default {
APP.UI.addListener(
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
audioOutputDeviceId => {
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_AUDIO_OUT);
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
APP.settings.setAudioOutputDeviceId(audioOutputDeviceId)
.then(() => logger.log('changed audio output device'))
.catch(err => {
@@ -2528,7 +2515,9 @@ export default {
// If audio was muted before, or we unplugged current device
// and selected new one, then mute new audio track.
if (audioWasMuted) {
sendAnalyticsEvent(DEVICE_LIST_CHANGED_AUDIO_MUTED);
sendAnalytics(createTrackMutedEvent(
'audio',
'device list changed'));
logger.log('Audio mute: device list changed');
muteLocalAudio(true);
}
@@ -2536,7 +2525,9 @@ export default {
// If video was muted before, or we unplugged current device
// and selected new one, then mute new video track.
if (!this.isSharingScreen && videoWasMuted) {
sendAnalyticsEvent(DEVICE_LIST_CHANGED_VIDEO_MUTED);
sendAnalytics(createTrackMutedEvent(
'video',
'device list changed'));
logger.log('Video mute: device list changed');
muteLocalVideo(true);
}
@@ -2622,37 +2613,6 @@ export default {
}
},
/**
* Log event to callstats and analytics.
* @param {string} name the event name
* @param {int} value the value (it's int because google analytics supports
* only int).
* @param {string} label short text which provides more info about the event
* which allows to distinguish between few event cases of the same name
* NOTE: Should be used after conference.init
*/
logEvent(name, value, label) {
sendAnalyticsEvent(name, {
value,
label
});
if (room) {
room.sendApplicationLog(JSON.stringify({ name,
value,
label }));
}
},
/**
* Methods logs an application event given in the JSON format.
* @param {string} logJSON an event to be logged in JSON format
*/
logJSON(logJSON) {
if (room) {
room.sendApplicationLog(logJSON);
}
},
/**
* Disconnect from the conference and optionally request user feedback.
* @param {boolean} [requestFeedback=false] if user feedback should be
@@ -2841,5 +2801,21 @@ export default {
setAudioMuteStatus(muted) {
APP.UI.setAudioMuted(this.getMyUserId(), muted);
APP.API.notifyAudioMutedStatusChanged(muted);
},
/**
* Dispatches the passed in feedback for submission. The submitted score
* should be a number inclusively between 1 through 5, or -1 for no score.
*
* @param {number} score - a number between 1 and 5 (inclusive) or -1 for no
* score.
* @param {string} message - An optional message to attach to the feedback
* in addition to the score.
* @returns {void}
*/
submitFeedback(score = -1, message = '') {
if (score === -1 || (score >= 1 && score <= 5)) {
APP.store.dispatch(submitFeedback(score, message, room));
}
}
};

View File

@@ -94,7 +94,7 @@ var config = {
// w3c spec-compliant video constraints to use for video capture. Currently
// used by browsers that return true from lib-jitsi-meet's
// RTCBrowserType#usesNewGumFlow. The constraints are independency from
// util#browser#usesNewGumFlow. The constraints are independency from
// this config's resolution value. Defaults to requesting an ideal aspect
// ratio of 16:9 with an ideal resolution of 1080p.
// constraints: {
@@ -307,21 +307,24 @@ var config = {
// backToP2PDelay: 5
},
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
// analyticsScriptUrls: [
// "libs/analytics-ga.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
//
deploymentInfo: {
// shard: "shard1",
// region: "europe",
// userRegion: "asia"
}
// List of undocumented settings used in jitsi-meet
/**
alwaysVisibleToolbar
analyticsScriptUrls
autoEnableDesktopSharing
autoRecord
autoRecordToken

View File

@@ -94,12 +94,15 @@ function connect(id, password, roomName) {
JitsiConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
/* eslint-disable max-params */
/**
*
*/
function connectionFailedHandler(error, message, credentials) {
function connectionFailedHandler(error, message, credentials, details) {
/* eslint-enable max-params */
APP.store.dispatch(
connectionFailed(connection, error, message, credentials));
connectionFailed(
connection, error, message, credentials, details));
if (isFatalJitsiConnectionError(error)) {
connection.removeEventListener(

101
css/_emoji-icons.scss Normal file
View File

@@ -0,0 +1,101 @@
.emoji-icon-background {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
}
.emoji-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 3em;
line-height: 3em;
z-index: 1000;
}
.emoji-icon:before {
content: "\00a0";
}
.emoji-icon-bomb {
background-image: url(../images/emojis/bomb.png);
}
.emoji-icon-bulb {
background-image: url(../images/emojis/bulb.png);
}
.emoji-icon-clap {
background-image: url(../images/emojis/clap.png);
}
.emoji-icon-fistbump {
background-image: url(../images/emojis/fistbump.png);
}
.emoji-icon-heart {
background-image: url(../images/emojis/heart.png);
}
.emoji-icon-highfive {
background-image: url(../images/emojis/highfive.png);
}
.emoji-icon-perfect {
background-image: url(../images/emojis/perfect.png);
}
.emoji-icon-sleep {
background-image: url(../images/emojis/sleep.png);
}
.emoji-icon-smiley1 {
background-image: url(../images/emojis/smiley1.png);
}
.emoji-icon-smiley2 {
background-image: url(../images/emojis/smiley2.png);
}
.emoji-icon-smiley3 {
background-image: url(../images/emojis/smiley3.png);
}
.emoji-icon-smiley4 {
background-image: url(../images/emojis/smiley4.png);
}
.emoji-icon-smiley5 {
background-image: url(../images/emojis/smiley5.png);
}
.emoji-icon-smiley6 {
background-image: url(../images/emojis/smiley6.png);
}
.emoji-icon-smiley7 {
background-image: url(../images/emojis/smiley7.png);
}
.emoji-icon-smiley8 {
background-image: url(../images/emojis/smiley8.png);
}
.emoji-icon-smiley9 {
background-image: url(../images/emojis/smiley9.png);
}
.emoji-icon-smiley10 {
background-image: url(../images/emojis/smiley10.png);
}
.emoji-icon-smiley11 {
background-image: url(../images/emojis/smiley11.png);
}
.emoji-icon-star {
background-image: url(../images/emojis/star.png);
}
.emoji-icon-thumbsdown {
background-image: url(../images/emojis/thumbsdown.png);
}
.emoji-icon-thumbsup {
background-image: url(../images/emojis/thumbsup.png);
}

View File

@@ -24,10 +24,15 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-arrow_back:before {
content: "\e5c4";
}
.icon-event_note:before {
content: "\e616";
}
.icon-navigate_before:before {
content: "\e408";
}
.icon-public:before {
content: "\e80b";
}

View File

@@ -153,10 +153,13 @@
@include transform(translateX(-50%));
> a:first-child.button,
> div:first-child .button {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
> a:last-child.button,
> div:last-child .button {
border-bottom-right-radius: 3px;
border-top-right-radius: 3px;

View File

@@ -517,6 +517,14 @@
width: auto;
}
.emotionsCanvas {
bottom: 0;
height: 50%;
max-height: 200px;
position: absolute;
width: 100%;
}
#videoNotAvailableScreen {
text-align: center;
#avatarContainer {

View File

@@ -30,6 +30,8 @@
@import 'flag-icon';
@import 'emoji-icons';
/* Modules BEGIN */
@import 'dial-out';

View File

@@ -27,6 +27,7 @@ Its constructor gets a number of options:
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
* **jwt**: (optional) [JWT](https://jwt.io/) token.
* **onload**: (optional) handler for the iframe onload event.
Example:

View File

@@ -1,5 +1,10 @@
# Server Installation for Jitsi Meet
:warning: **WARNING:** Manual installation is not recommended. We recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document. The current document describes the steps that are needed to install a working deployment, but steps are easy to mess up, and the debian packages are more up-to-date, where this document sometimes is not updated to latest changes.
This describes configuring a server `jitsi.example.com` running Debian or a Debian Derivative. You will need to
change references to that to match your host, and generate some passwords for
`YOURSECRET1`, `YOURSECRET2` and `YOURSECRET3`.
@@ -93,8 +98,9 @@ prosodyctl cert generate auth.jitsi.example.com
Add auth.jitsi.example.com to the trusted certificates on the local machine:
```sh
ln -sf /var/lib/prosody/auth.jitsi.example.com.crt /usr/local/share/ca-certificates/auth.jitsi.example.com.crt
update-ca-certificates
update-ca-certificates -f
```
Note that the `-f` flag is necessary if there are symlinks left from a previous installation.
Create conference focus user:
```sh

Binary file not shown.

View File

@@ -11,7 +11,9 @@
<glyph unicode="&#xe145;" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
<glyph unicode="&#xe1aa;" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
<glyph unicode="&#xe310;" glyph-name="headset" d="M512 982c212 0 384-172 384-384v-300c0-70-58-128-128-128h-128v342h170v86c0 166-132 298-298 298s-298-132-298-298v-86h170v-342h-128c-70 0-128 58-128 128v300c0 212 172 384 384 384z" />
<glyph unicode="&#xe408;" glyph-name="navigate_before" d="M658 708l-196-196 196-196-60-60-256 256 256 256z" />
<glyph unicode="&#xe425;" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
<glyph unicode="&#xe5c4;" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
<glyph unicode="&#xe5d4;" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
<glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
<glyph unicode="&#xe613;" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,60 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M854 470v84h-520l238 240-60 60-342-342 342-342 60 60-238 240h520z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"arrow_back"
],
"defaultCode": 58820,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "arrow_back",
"id": 45,
"order": 924,
"prevSize": 24,
"code": 58820,
"name": "arrow_back"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 45
},
{
"icon": {
"paths": [
"M658 316l-196 196 196 196-60 60-256-256 256-256z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"navigate_before"
],
"defaultCode": 58376,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "chevron_left, navigate_before",
"id": 152,
"order": 923,
"prevSize": 24,
"code": 58376,
"name": "navigate_before"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 152
},
{
"icon": {
"paths": [
@@ -24,9 +78,9 @@
"code": 59403,
"name": "public"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 605
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
{
"icon": {
@@ -53,7 +107,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 1
},
{
"icon": {
@@ -80,7 +134,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 1
"iconIdx": 2
},
{
"icon": {
@@ -107,7 +161,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 2
"iconIdx": 3
},
{
"icon": {
@@ -134,7 +188,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 3
"iconIdx": 4
},
{
"icon": {
@@ -161,7 +215,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 5
},
{
"icon": {
@@ -188,7 +242,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 5
"iconIdx": 6
},
{
"icon": {
@@ -217,7 +271,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 6
"iconIdx": 7
},
{
"icon": {
@@ -244,7 +298,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 7
"iconIdx": 8
},
{
"icon": {
@@ -271,7 +325,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 8
"iconIdx": 9
},
{
"icon": {
@@ -300,7 +354,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 9
"iconIdx": 10
},
{
"icon": {
@@ -329,7 +383,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 10
"iconIdx": 11
},
{
"icon": {
@@ -358,7 +412,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 11
"iconIdx": 12
},
{
"icon": {
@@ -387,7 +441,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 13
},
{
"icon": {
@@ -416,7 +470,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 14
},
{
"icon": {
@@ -442,7 +496,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 14
"iconIdx": 15
},
{
"icon": {
@@ -468,7 +522,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 15
"iconIdx": 16
},
{
"icon": {
@@ -494,7 +548,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 16
"iconIdx": 17
},
{
"icon": {
@@ -520,7 +574,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 17
"iconIdx": 18
},
{
"icon": {
@@ -546,7 +600,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 18
"iconIdx": 19
},
{
"icon": {
@@ -572,7 +626,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 19
"iconIdx": 20
},
{
"icon": {
@@ -598,7 +652,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 20
"iconIdx": 21
},
{
"icon": {
@@ -616,7 +670,7 @@
"attrs": [],
"properties": {
"id": 10,
"order": 900,
"order": 922,
"ligatures": "expand_less",
"prevSize": 32,
"code": 59679,
@@ -624,7 +678,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 21
"iconIdx": 22
},
{
"icon": {
@@ -650,7 +704,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 23
},
{
"icon": {
@@ -676,7 +730,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 23
"iconIdx": 24
},
{
"icon": {
@@ -702,7 +756,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 24
"iconIdx": 25
},
{
"icon": {
@@ -728,7 +782,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 25
"iconIdx": 26
},
{
"icon": {
@@ -754,7 +808,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 26
"iconIdx": 27
},
{
"icon": {
@@ -780,7 +834,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 27
"iconIdx": 28
},
{
"icon": {
@@ -806,7 +860,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 28
"iconIdx": 29
},
{
"icon": {
@@ -832,7 +886,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 29
"iconIdx": 30
},
{
"icon": {
@@ -858,7 +912,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 30
"iconIdx": 31
},
{
"icon": {
@@ -884,7 +938,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 31
"iconIdx": 32
},
{
"icon": {
@@ -910,7 +964,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 32
"iconIdx": 33
},
{
"icon": {
@@ -936,7 +990,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 33
"iconIdx": 34
},
{
"icon": {
@@ -962,7 +1016,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 34
"iconIdx": 35
},
{
"icon": {
@@ -988,7 +1042,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 35
"iconIdx": 36
},
{
"icon": {
@@ -1014,7 +1068,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 37
},
{
"icon": {
@@ -1040,7 +1094,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 38
},
{
"icon": {
@@ -1066,7 +1120,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 39
},
{
"icon": {
@@ -1092,7 +1146,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 40
},
{
"icon": {
@@ -1118,7 +1172,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 41
},
{
"icon": {
@@ -1144,7 +1198,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 41
"iconIdx": 42
},
{
"icon": {
@@ -1170,7 +1224,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 42
"iconIdx": 43
},
{
"icon": {
@@ -1199,7 +1253,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 43
"iconIdx": 44
},
{
"icon": {
@@ -1229,7 +1283,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 44
"iconIdx": 45
},
{
"icon": {
@@ -1259,7 +1313,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 45
"iconIdx": 46
},
{
"icon": {
@@ -1285,7 +1339,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 46
"iconIdx": 47
},
{
"icon": {
@@ -1311,7 +1365,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 47
"iconIdx": 48
},
{
"icon": {
@@ -1337,7 +1391,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 48
"iconIdx": 49
}
],
"height": 1024,

BIN
images/emojis/bomb.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
images/emojis/bulb.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

BIN
images/emojis/clap.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/emojis/fistbump.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
images/emojis/heart.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

BIN
images/emojis/highfive.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/emojis/perfect.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
images/emojis/sleep.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
images/emojis/smiley1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
images/emojis/smiley10.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

BIN
images/emojis/smiley11.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

BIN
images/emojis/smiley2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

BIN
images/emojis/smiley3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/emojis/smiley4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

BIN
images/emojis/smiley5.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

BIN
images/emojis/smiley6.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

BIN
images/emojis/smiley7.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/emojis/smiley8.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

BIN
images/emojis/smiley9.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

BIN
images/emojis/star.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
images/emojis/thumbsdown.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

BIN
images/emojis/thumbsup.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -1,6 +1,6 @@
PODS:
- React (0.50.4):
- React/Core (= 0.50.4)
- React (0.51.0):
- React/Core (= 0.51.0)
- react-native-background-timer (2.0.0):
- React
- react-native-fetch-blob (0.10.6):
@@ -10,40 +10,40 @@ PODS:
- react-native-locale-detector (1.0.0):
- React
- react-native-webrtc (1.58.2)
- React/BatchedBridge (0.50.4):
- React/BatchedBridge (0.51.0):
- React/Core
- React/cxxreact_legacy
- React/Core (0.50.4):
- yoga (= 0.50.4.React)
- React/cxxreact_legacy (0.50.4):
- React/Core (0.51.0):
- yoga (= 0.51.0.React)
- React/cxxreact_legacy (0.51.0):
- React/jschelpers_legacy
- React/DevSupport (0.50.4):
- React/DevSupport (0.51.0):
- React/Core
- React/RCTWebSocket
- React/fishhook (0.50.4)
- React/jschelpers_legacy (0.50.4)
- React/RCTActionSheet (0.50.4):
- React/fishhook (0.51.0)
- React/jschelpers_legacy (0.51.0)
- React/RCTActionSheet (0.51.0):
- React/Core
- React/RCTAnimation (0.50.4):
- React/RCTAnimation (0.51.0):
- React/Core
- React/RCTBlob (0.50.4):
- React/RCTBlob (0.51.0):
- React/Core
- React/RCTImage (0.50.4):
- React/RCTImage (0.51.0):
- React/Core
- React/RCTNetwork
- React/RCTLinkingIOS (0.50.4):
- React/RCTLinkingIOS (0.51.0):
- React/Core
- React/RCTNetwork (0.50.4):
- React/RCTNetwork (0.51.0):
- React/Core
- React/RCTText (0.50.4):
- React/RCTText (0.51.0):
- React/Core
- React/RCTWebSocket (0.50.4):
- React/RCTWebSocket (0.51.0):
- React/Core
- React/fishhook
- React/RCTBlob
- RNVectorIcons (4.4.2):
- React
- yoga (0.50.4.React)
- yoga (0.51.0.React)
DEPENDENCIES:
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
@@ -83,15 +83,15 @@ EXTERNAL SOURCES:
:path: ../node_modules/react-native/ReactCommon/yoga
SPEC CHECKSUMS:
React: 6d7efef18e0241eb832cf4c085405169a15080ed
react-native-background-timer: 10063c04bf85d7f8811dff8c74399f0aa715245f
react-native-fetch-blob: 2bef9be702de8726f4d7bf58d2345579aaaee60d
react-native-keep-awake: bb4dbb6fd21a7879432f9538b0b7d71398fe9f81
react-native-locale-detector: bb60716189d4c69c459355d0a4804c9b13f65e84
react-native-webrtc: 6fd0b3aa890d7a9b9b4d01d30f958d17ae88a785
RNVectorIcons: c1821d56c775cc5a3bca66c77dfc8cb4a90d27e2
yoga: b9aebf996711e50fc31f5608c10aa108a5a0c29e
React: 541ba768b9855e10cdc76f55427a5cd0653ca806
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
react-native-fetch-blob: 63394b1d7b0781547b3e4463b3195790177b1222
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
react-native-webrtc: bc044ca9530fc802e7533f247aa08fe1b6bf8dc5
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
PODFILE CHECKSUM: fabd6b6c27f8e1849f0668db3f403bf536ac8903
COCOAPODS: 1.3.1
COCOAPODS: 1.4.0

View File

@@ -11,6 +11,7 @@
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
@@ -34,6 +35,7 @@
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
@@ -106,6 +108,7 @@
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
@@ -276,7 +279,17 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -295,6 +308,7 @@
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,

View File

@@ -32,6 +32,12 @@ RCT_EXPORT_MODULE();
NSString *name = infoDictionary[@"CFBundleDisplayName"];
NSString *version = infoDictionary[@"CFBundleShortVersionString"];
if (name == nil) {
name = infoDictionary[@"CFBundleName"];
if (name == nil) {
name = @"";
}
}
if (version == nil) {
version = infoDictionary[@"CFBundleVersion"];
if (version == nil) {

View File

@@ -0,0 +1,83 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Intents/Intents.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
@interface LaunchOptions : NSObject<RCTBridgeModule>
@property (nonatomic, weak) RCTBridge *bridge;
@end
@implementation LaunchOptions
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject) {
id initialURL = nil;
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL *url = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
initialURL = url.absoluteString;
} else {
NSDictionary *userActivityDictionary
= self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity
= [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil) {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
initialURL = userActivity.webpageURL.absoluteString;
} else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
// App was started by a CallKit Intent
INIntent *intent = userActivity.interaction.intent;
NSArray<INPerson *> *contacts;
NSString *url;
BOOL startAudioOnly = NO;
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
contacts = ((INStartAudioCallIntent *) intent).contacts;
startAudioOnly = YES;
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
contacts = ((INStartVideoCallIntent *) intent).contacts;
}
if (contacts && (url = contacts.firstObject.personHandle.value)) {
initialURL
= @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
}
}
}
}
resolve(initialURL != nil ? initialURL : (id)kCFNull);
}
@end

20
lang/languages-az.json Normal file
View File

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

20
lang/languages-fa.json Normal file
View File

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

486
lang/main-az.json Normal file
View File

@@ -0,0 +1,486 @@
{
"contactlist": "",
"contactlist_plural": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": {
"average": "",
"bad": "",
"good": "",
"rateExperience": "",
"veryBad": "",
"veryGood": ""
},
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"audioOnly": {
"audioOnly": "",
"featureToggleDisabled": ""
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "",
"edgeGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": "",
"showSpeakerStats": ""
},
"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": "",
"text": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"addPeople": "",
"audioonly": "",
"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": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"framerate": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"transport_plural": "",
"bandwidth": "",
"na": "",
"turn": "",
"quality": {
"good": "",
"inactive": "",
"lost": "",
"nonoptimal": "",
"poor": ""
},
"status": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"allow": "",
"kickMessage": "",
"popupErrorTitle": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"contactSupport": "",
"error": "",
"createPassword": "",
"detectext": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"dismiss": "",
"rejoinNow": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupportedTitle": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"alreadySharedVideoTitle": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingFailedToInstall": "",
"screenSharingFailedToInstallTitle": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "",
"cameraNotSendingDataTitle": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"inlineInstallationMsg": "",
"inlineInstallExtension": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"unavailable": "",
"unavailableTitle": ""
},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"streamIdRequired": "",
"streamIdHelp": "",
"unavailable": "",
"unavailableTitle": ""
},
"speakerStats": {
"hours": "",
"minutes": "",
"name": "",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
},
"invite": {
"addPassword": "",
"callNumber": "",
"enterID": "",
"howToDialIn": "",
"hidePassword": "",
"inviteTo": "",
"invitedYouTo": "",
"invitePeople": "",
"locked": "",
"showPassword": "",
"unlocked": ""
},
"videoStatus": {
"callQuality": "",
"hd": "",
"highDefinition": "",
"labelTooltipVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"lowDefinition": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"standardDefinition": "",
"qualityButtonTip": ""
},
"dialOut": {
"dial": "",
"dialOut": "",
"statusMessage": "",
"enterPhone": "",
"phoneNotAllowed": ""
},
"addPeople": {
"add": "",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
},
"deviceError": {
"cameraError": "",
"microphoneError": "",
"cameraPermission": "",
"microphonePermission": ""
},
"info": {
"copy": "",
"invite": "",
"title": "",
"tooltip": ""
}
}

View File

@@ -1,9 +1,9 @@
{
"contactlist": "",
"contactlist_plural": "",
"passwordSetRemotely": "добавена от друг участник",
"contactlist": "__count__ участник",
"contactlist_plural": "__count__ участници",
"passwordSetRemotely": "зададена от друг участник",
"connectionsettings": "Настройки на връзката",
"poweredby": "powered by",
"poweredby": "с подкрепата на",
"feedback": {
"average": "Средно",
"bad": "Лошо",
@@ -16,48 +16,47 @@
"me": "аз",
"speaker": "Говорител",
"raisedHand": "Иска думата",
"defaultNickname": "Яна Розова",
"defaultLink": "например __url__",
"callingName": "",
"defaultNickname": "напр. Иван Иванов",
"defaultLink": "напр. __url__",
"callingName": "__name__",
"audioOnly": {
"audioOnly": "Само звук",
"featureToggleDisabled": ""
"featureToggleDisabled": "Превключването на „__feature__“ не е възможно в режим само със звук"
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"firefoxGrantPermissions": "Изберете <b><i>Споделяне на избраното устройство</i></b>, когато браузърът Ви помоли за разрешение.",
"operaGrantPermissions": "Изберете <b><i>Разрешаване</i></b>, когато браузърът Ви помоли за разрешение.",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "",
"edgeGrantPermissions": ""
"safariGrantPermissions": "Изберете <b><i>Добре</i></b>, когато браузърът Ви помоли за разрешение.",
"nwjsGrantPermissions": "Моля, разрешете използването на камерата и микрофона",
"edgeGrantPermissions": "Изберете <b><i>Да</i></b>, когато браузърът Ви помоли за разрешение."
},
"keyboardShortcuts": {
"keyboardShortcuts": "Клавишни комбинации:",
"raiseHand": "",
"pushToTalk": "Push to talk.",
"toggleScreensharing": "Смяна между камера и споделен екран.",
"toggleFilmstrip": "",
"toggleShortcuts": "Показване/скриване на това меню.",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "Отваряне/скриване на чат панела.",
"mute": "Спиране/пускане на микрофона.",
"fullScreen": "Влез / Излез от Пълен екран",
"videoMute": "",
"showSpeakerStats": ""
"keyboardShortcuts": "Клавишни комбинации",
"raiseHand": "Вдигнете или свалете ръка",
"pushToTalk": "Натиснете, за да говорите",
"toggleScreensharing": "Смяна между камера и споделен екран",
"toggleFilmstrip": "Показване/скриване на видеата",
"toggleShortcuts": "Показване/скриване на помощното меню",
"focusLocal": "Фокусиране върху Вашето видео",
"focusRemote": "Фокусиране върху видеото на друг участник",
"toggleChat": "Отваряне/скриване на текстовите съобщения",
"mute": "Спиране/пускане на микрофона",
"fullScreen": "Влизане/излизане от режим на цял екран",
"videoMute": "Пускане/спиране на камерата",
"showSpeakerStats": "Показване на статистика за говорителя"
},
"\u0005keyboardShortcuts": {},
"welcomepage": {
"disable": "Не показвай повече тази страница",
"feature1": {
"content": "Не е необходимо да сваляте нищо. __app__ работи директно във вашия браузър. Просто споделете адреса на вашата конференция с другите за да започнете.",
"content": "Не е необходимо да сваляте нищо. __app__ работи директно във браузъра Ви. Просто споделете адреса на беседата си с другите, за да започнете.",
"title": "Лесен за употреба"
},
"feature2": {
"content": "Видео конференциите могат да работят с по-малко от 128Kbps, а аудио конференциите и конференциите с споделен екран дори с по-малко.",
"content": "Видео беседите могат да работят и при скорост от 128кб/сек. А тези само със звук или за споделяне на екрана дори и при по-ниска.",
"title": "Нисък дебит"
},
"feature3": {
@@ -65,7 +64,7 @@
"title": "Отворен код"
},
"feature4": {
"content": "",
"content": "Няма изкуствени ограничения на броя потребители или участници в беседата. Единствените ограничения са мощността на сървъра и скоростта на мрежовата връзка.",
"title": "Неограничен брой потребители"
},
"feature5": {
@@ -84,64 +83,63 @@
"content": "Научете повече за своите потребители като интегрирате лесно Piwik, Google Analytics и други статистики за изполването.",
"title": "Статистики за използване"
},
"go": "Влез",
"join": "",
"privacy": "",
"go": "НАПРЕД",
"join": "ПРИСЪЕДИНЯВАНЕ",
"privacy": "Поверителност",
"roomname": "Въведете име на стаята",
"roomnamePlaceHolder": "",
"sendFeedback": "",
"terms": ""
"roomnamePlaceHolder": "име на стаята",
"sendFeedback": "Изпращане на отзиви",
"terms": "Условия"
},
"startupoverlay": {
"policyText": "",
"title": ""
"policyText": " ",
"title": "„__app__“ се нуждае от достъп до микрофона и камерата."
},
"suspendedoverlay": {
"title": "",
"text": "",
"rejoinKeyTitle": ""
"title": "Вашият видео разговор беше прекъснат, тъй като компютърът Ви заспа.",
"text": "Натиснете бутона <i>Повторно присъединяване</i>, за да се свържете отново.",
"rejoinKeyTitle": "Повторно присъединяване"
},
"toolbar": {
"addPeople": "",
"audioonly": "",
"mute": "Включи / Изключи микрофона",
"videomute": "Включи / Изключи камерата",
"audioonly": "Включване/изключване на режима само със звук (пести мрежовата връзка)",
"mute": "Спиране/пускане на микрофона",
"videomute": "Пускане/спиране на камерата",
"authenticate": "Идентификация",
"lock": "Заключи / отключи стаята",
"invite": "",
"chat": "Отвори / затвори чат",
"etherpad": "",
"sharedvideo": "Сподели YouTube видео",
"sharescreen": "",
"fullscreen": "Влез / Излез от Пълен екран",
"sip": "Обади се на SIP номер",
"lock": "Заключване/отключване на стаята",
"invite": "Споделете връзката",
"chat": "Отваряне/затваряне на текстовите съобщения",
"etherpad": "Отваряне/затваряне на споделен документ",
"sharedvideo": "Пускане/спиране на споделянето на екрана",
"sharescreen": "Пускане/спиране на споделянето на екрана",
"fullscreen": "Влизане/излизане от режим на цял екран",
"sip": "Обаждане на SIP-номер",
"Settings": "Настройки",
"hangup": "",
"hangup": "Напускане",
"login": "Влез",
"logout": "Изход",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "Не се говори по време на филм!",
"dialpad": "Отваряне/затваряне на клавиатурата за набиране",
"sharedVideoMutedPopup": "Вашето споделено видео беше заглушено, за да можете да говорите с другите участници.",
"micMutedPopup": "Микрофонът Ви беше заглушен, за да можете да се насладите на споделеното видео.",
"talkWhileMutedPopup": "Опитвате се да говорите? В момента микрофонът Ви е заглушен.",
"unableToUnmutePopup": "Не можете да пуснете микрофона докато гледате споделено видео.",
"cameraDisabled": "Не е налична камера",
"micDisabled": "Не е наличен микрофон",
"filmstrip": "",
"profile": "",
"raiseHand": ""
"filmstrip": "Показване/скриване на видеата",
"profile": "Редактиране на профила",
"raiseHand": "Вдигане/сваляне на ръка"
},
"\u0005toolbar": {},
"unsupportedBrowser": {
"appInstalled": "",
"appNotInstalled": "",
"downloadApp": "",
"joinConversation": "",
"startConference": ""
"appInstalled": "или ако вече имате това приложение<br /><strong>тогава</strong>",
"appNotInstalled": "Трябва да имате <strong>__app__</strong>, за да можете да се присъедините към беседата от мобилното си устройство",
"downloadApp": "Сваляне не приложението",
"joinConversation": "Присъединяване",
"startConference": "Започване на беседа"
},
"bottomtoolbar": {
"chat": "Отвори / затвори чат",
"filmstrip": "",
"contactlist": ""
"chat": "Отваряне/затваряне на текстовите съобщения",
"filmstrip": "Показване/скриване на видеата",
"contactlist": "Преглед и покана на участници"
},
"chat": {
"nickname": {
@@ -154,42 +152,42 @@
"title": "Настройки",
"update": "Актуализиране",
"name": "Име",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"startAudioMuted": "Всички започват заглушени",
"startVideoMuted": "Всички започват скрити",
"selectCamera": "Камера",
"selectMic": "Микрофон",
"selectAudioOutput": "Звуков изход",
"followMe": "Всички ме следват",
"noDevice": "Няма",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": ""
"cameraAndMic": "Камера и микрофон",
"moderator": "МОДЕРАТОР",
"password": "ЗАДАВАНЕ НА ПАРОЛА",
"audioVideo": "ЗВУК И ВИДЕО"
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
"title": "Профил",
"setDisplayNameLabel": "Задайте екранното си име",
"setEmailLabel": "Задайте е-пощата си в „gravatar“",
"setEmailInput": "Въведете е-поща"
},
"videothumbnail": {
"moderator": "",
"videomute": "",
"mute": "",
"moderator": "Модератор",
"videomute": "Участникът спря камерата си",
"mute": "Участникът е заглушен",
"kick": "Изгони",
"muted": "Изключен микрофон",
"domute": "Изключи микрофона",
"flip": "Огледално",
"remoteControl": ""
"remoteControl": "Отдалечено управление"
},
"connectionindicator": {
"header": "",
"bitrate": "Дебит:",
"header": "Данни за връзката",
"bitrate": "Скорост:",
"packetloss": "Загуба на пакети:",
"resolution": "Резолюция:",
"framerate": "",
"less": "Скрий",
"more": "Покажи",
"framerate": "Кадри в секунда:",
"less": "Скриване",
"more": "Показване",
"address": "Адрес:",
"remoteport": "Отдалечен порт:",
"remoteport_plural": "Отдалечени портове:",
@@ -200,21 +198,21 @@
"remoteaddress": "Отдалечен адрес:",
"remoteaddress_plural": "Отдалечени адреси:",
"transport": "Транспорт:",
"bandwidth": "Оценка макс. дебит:",
"na": "Върнете се тук за информацията относно вашата връзка, когато започне конференцията",
"turn": "",
"transport_plural": "Транспорти:",
"bandwidth": "Предполагаема скорост:",
"na": "Върнете се тук за информацията относно връзката си, след като започне беседата",
"turn": " (обръщане)",
"quality": {
"good": "",
"inactive": "",
"lost": "",
"nonoptimal": "",
"poor": ""
"good": "Добра",
"inactive": "Няма",
"lost": "Разкачена",
"nonoptimal": "Неоптимална",
"poor": "Лоша"
},
"status": "Свързване"
"status": "Връзка:"
},
"\u0005connectionindicator": {},
"notify": {
"disconnected": "връзката е прекъсната",
"disconnected": "Връзка:",
"moderator": "Придобихте права на модератор!",
"connected": "свързан",
"somebody": "Някой",
@@ -222,73 +220,72 @@
"focus": "Конферентен фокус",
"focusFail": "__component__ не е на раположения - следващ опит след __ms__ секунди",
"grantedTo": "Даване на роля модератор на __to__!",
"grantedToUnknown": "Даване на роля модератор на $t(somebody)!",
"grantedToUnknown": "$t(notify.somebody) получи ролята на модератор!",
"muted": "Започвате разговора без звук.",
"mutedTitle": "Звукът ви е спрян!",
"raisedHand": ""
"raisedHand": "Иска думата."
},
"\u0005notify": {},
"dialog": {
"add": "",
"allow": "",
"kickMessage": "Бяхте изгонен от срещата!",
"popupErrorTitle": "",
"popupError": "",
"passwordErrorTitle": "",
"allow": "Разрешаване",
"kickMessage": "Ох! Бяхте изритан(а) от срещата!",
"popupErrorTitle": "Блокиран изскачащ прозорец",
"popupError": "Браузърът Ви блокира изскачащите прозорци от този уеб сайт. Моля, разрешете изскачащите прозорци от настройките за сигурност на браузъра си и след това опитайте отново.",
"passwordErrorTitle": "Грешка с паролата",
"passwordError": "Стаята е защитена с парола. Само собственикът може да я променя или премахва.",
"passwordError2": "Стаята ви не е защитена с парола. Собственикът би могъл да добави парола.",
"connectError": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията.",
"connectErrorWithMsg": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията: __msg__",
"incorrectPassword": "",
"incorrectPassword": "Неправилно потребителско име или парола",
"connecting": "Свързване",
"copy": "",
"contactSupport": "",
"copy": "Копиране",
"contactSupport": "Връзка с отдела по поддръжка",
"error": "Грешка",
"createPassword": "",
"createPassword": "Създаване на парола",
"detectext": "Възникна грешка при опит да бъде намерено разширението за споделяне на екран.",
"failedpermissions": "Неуспешен опит за получаване на права за използване на микрофон и/или камера.",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"dismiss": "",
"rejoinNow": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "",
"conferenceReloadTitle": "За съжаление, нещо се обърка.",
"conferenceReloadMsg": "Опитваме се да оправим нещата. Повторно свързване след __seconds__ сек…",
"conferenceDisconnectTitle": "Връзката се разпадна.",
"conferenceDisconnectMsg": "Може би трябва да проверите мрежовата си връзка. Повторно свързване след __seconds__ сек…",
"dismiss": "Отхвърляне",
"rejoinNow": "Повторно присъединяване сега",
"maxUsersLimitReachedTitle": "Достигнато е ограничението за максимален брой участници",
"maxUsersLimitReached": "Достигнато е ограничението за максимален брой участници. Беседата е пълна. Моля, свържете се с организатора на срещата или опитайте отново по-късно!",
"lockTitle": "Неуспешно заключване",
"lockMessage": "Неуспешно заключване на конференцията.",
"warning": "Внимание",
"passwordNotSupportedTitle": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"passwordNotSupportedTitle": "Не се поддържа парола",
"passwordNotSupported": "Задаването на парола за срещата не се поддържа.",
"internalErrorTitle": "Вътрешна грешка",
"internalError": "Опа! Нещо се обърка. Възникна следната грешка: __error__",
"unableToSwitch": "Неуспешен опит за смяна на видеото.",
"SLDFailure": "Опа! Нещо се обърка и не успяхме да спрем микрофона! (SLD Failure)",
"SRDFailure": "Опа! Нещо се обърка и не успяхме да спрем камерата! (SRD Failure)",
"oops": "Опа!",
"currentPassword": "",
"passwordLabel": "",
"currentPassword": "Текущата парола е",
"passwordLabel": "Парола",
"defaultError": "Възникна грешка",
"passwordRequired": "Изисква се парола",
"Ok": "ОК",
"done": "Няма",
"Remove": "DELETE DELETE DELETE DELETE DELETE DELETE DELETE DELETE DELETE DELETE ",
"removePassword": "",
"Ok": "Готово",
"done": "Готово",
"Remove": "Премахване",
"removePassword": "Премахване на паролата",
"shareVideoTitle": "Сподели видео",
"shareVideoLinkError": "Моля въведете правилен Prezi линк.",
"shareVideoLinkError": "Моля въведете правилна връзка към YouTube.",
"removeSharedVideoTitle": "Край на споделянето на видео",
"removeSharedVideoMsg": "Сигурни ли сте, че искате да премахнете Prezi?",
"alreadySharedVideoMsg": "",
"alreadySharedVideoTitle": "",
"removeSharedVideoMsg": "Наистина ли искате да премахнете споделеното си видео?",
"alreadySharedVideoMsg": "Друг участник вече споделя видео. Тази беседа позволява споделянето само на едно видео в даден момент.",
"alreadySharedVideoTitle": "Разрешено е споделянето само на едно видео в даден момент",
"WaitingForHost": "Чакаме домакина ...",
"WaitForHostMsg": "<b>__room__ </b> още не е започнал. Ако вие сте домакина моля идентифицирайте се. В противен случай ще изчакаме домакина.",
"WaitForHostMsg": "Беседата <b>__room__ </b> още не е започнала. Ако Вие сте домакинът, моля, идентифицирайте се. В противен случай, моля, изчакайте домакина.",
"IamHost": "Аз съм домакина",
"Cancel": "Отказ",
"Submit": "",
"Submit": "Изпращане",
"retry": "Повторен опит",
"logoutTitle": "Изход",
"logoutQuestion": "Сигурни ли сте, че искате да излезете и да прекъснете конференцията?",
"sessTerminated": "",
"sessTerminated": "Разговорът приключи",
"hungUp": "Затворихте",
"joinAgain": "Присъединете се отново",
"Share": "Споделяне",
@@ -297,75 +294,75 @@
"recordingToken": "Въведете код за достъп за запис на конференцията",
"passwordCheck": "Сигурни ли сте, че искате да махнете паролата?",
"passwordMsg": "Въведете парола, за да заключите стаята",
"shareLink": "",
"yourPassword": "потребителска парола",
"shareLink": "Споделете връзката към разговора",
"yourPassword": "Въведете нова парола",
"Back": "Назад",
"serviceUnavailable": "Услугата не е налична",
"gracefulShutdown": "Услугата временно не е достъпна поради профилактика. Моля опитайте по-късно.",
"Yes": "Да",
"reservationError": "Грешка в системата за резервации",
"reservationErrorMsg": "Грешка номер: __code__, съобщение: __msg__",
"password": "потребителска парола",
"password": "Въведете парола",
"userPassword": "потребителска парола",
"token": "код за достъп",
"tokenAuthFailedTitle": "Неуспешна идентификация",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "Въведете името си:",
"extensionRequired": "Изисква се разширение: ",
"tokenAuthFailed": "Съжаляваме, но не можете да се присъедините към този разговор.",
"displayNameRequired": "Изисква се екранно име",
"enterDisplayName": "Въведете екранното си име",
"extensionRequired": "Нужно е разширение:",
"firefoxExtensionPrompt": "За да споделите екрана си, трябва да инсталирате Firefox разширение. <a href='__url__'>свалете го тук</a> и пробвайте пак!",
"feedbackHelp": "",
"feedbackQuestion": "",
"feedbackHelp": "Вашето мнение ще ни помогне да подобрим видео услугата.",
"feedbackQuestion": "Кажете ни какво мислите за разговора!",
"thankYou": "Благодарим, че използвахте __appName__!",
"sorryFeedback": "Много съжаляваме! Бихте ли ни казали повече?",
"liveStreaming": "Пряко предаване",
"streamKey": "Имке/ключ на потока",
"startLiveStreaming": "Пряко предаване",
"stopStreamingWarning": "Наистина ли искате да спрем прякото предаване?",
"startLiveStreaming": "Започване на излъчване на живо",
"stopStreamingWarning": "Наистина ли искате да спрете излъчването на живо?",
"stopRecordingWarning": "Наистина ли искате да спрем записа?",
"stopLiveStreaming": "Край на прякото предаване",
"stopLiveStreaming": "Спиране на излъчването на живо",
"stopRecording": "Край на записа",
"doNotShowWarningAgain": "Занапред не ме предупреждавай пак",
"doNotShowMessageAgain": "Не показвай повече тази страница",
"doNotShowMessageAgain": "Да не се показва повече това съобщение",
"permissionDenied": "Достъпът е отказан",
"screenSharingFailedToInstall": "",
"screenSharingFailedToInstallTitle": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "Грешка при достъп до микрофона",
"screenSharingFailedToInstall": "Опа! Разширението за споделяне на екрана не успя да се инсталира.",
"screenSharingFailedToInstallTitle": "Разширението за споделяне на екрана не успя да се инсталира",
"screenSharingPermissionDeniedError": "Опа! Нещо се обърка с разрешенията на разширението за споделяне на екрана. Моля, презаредете и опитайте отново.",
"micErrorPresent": "Възникна грешка при свързването с микрофона Ви.",
"cameraErrorPresent": "Грешка при достъп до камерата.",
"cameraUnsupportedResolutionError": "Камерата ви не поддържа тази резолюция",
"cameraUnsupportedResolutionError": "Камерата Ви не поддържа нужната резолюция.",
"cameraUnknownError": "Невъзможен достъп до камерата по неясна причина.",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "Камерата ви не задоволява зададените условия.",
"cameraPermissionDeniedError": "Не сте дали разрешение за използване на камерата. Ще можете да се присъедините в беседата, но другите няма да Ви виждат. Използвайте бутона с камерата в адресната лента, за да оправите това.",
"cameraNotFoundError": "Не е открита камера.",
"cameraConstraintFailedError": "Камерата Ви не покрива някои от изискванията.",
"micUnknownError": "Не възможен достъп до микрофона по неясна причина.",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "Микрофонът не задоволява зададените условия.",
"micNotSendingDataTitle": "",
"micNotSendingData": "",
"cameraNotSendingDataTitle": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "Изисква се разширение: ",
"micPermissionDeniedError": "Не сте дали разрешение за използване на микрофона. Ще можете да се присъедините в беседата, но другите няма да Ви чуват. Използвайте бутона с камерата в адресната лента, за да оправите това.",
"micNotFoundError": "Не е открит микрофон.",
"micConstraintFailedError": "Микрофонът Ви не покрива някои от изискванията.",
"micNotSendingDataTitle": "Микрофонът е недостъпен",
"micNotSendingData": "Микрофонът Ви е недостъпен. Моля, изберете друго устройство от менюто с настройките или опитайте да презаредите приложението.",
"cameraNotSendingDataTitle": "Камерата е недостъпна",
"cameraNotSendingData": "Камерата е недостъпна. Моля, проверете дали друго приложение не използва това устройство, изберете друго устройство от менюто с настройките, или презаредете приложението.",
"goToStore": "Към магазина в Интернет",
"externalInstallationTitle": "Нужно е разширение",
"externalInstallationMsg": "Неуспешна инсталация на разширението за споделяне на екрана.",
"inlineInstallationMsg": "Неуспешна инсталация на разширението за споделяне на екрана.",
"inlineInstallExtension": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"inlineInstallationMsg": "Трябва да инсталирате разширението за споделяне на екрана.",
"inlineInstallExtension": "Инсталиране сега",
"muteParticipantTitle": "Заглушаване на този участник?",
"muteParticipantBody": "Вие няма да можете да спрете заглушаването на участника, но той ще може да го направи по всяко време.",
"muteParticipantButton": "Изключи микрофона",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
"remoteControlTitle": "Отдалечено управление на компютъра",
"remoteControlRequestMessage": "Ще позволите ли на __user__ да управлява отдалечено компютъра Ви?",
"remoteControlShareScreenWarning": "Ако натиснете „Разрешаване“, ще споделите екрана си!",
"remoteControlDeniedMessage": "__user__ отказа заявката Ви за отдалечено управление!",
"remoteControlAllowedMessage": "__user__ прие заявката Ви за отдалечено управление!",
"remoteControlErrorMessage": "Възникна грешка при опита за искана на разрешение за отдалечено управление от __user__!",
"startRemoteControlErrorMessage": "Възникна грешка при опита за започване на сесията за отдалечено управление!",
"remoteControlStopMessage": "Сесията за отдалечено управление приключи!",
"close": "Затваряне",
"shareYourScreen": "Споделяне на екрана",
"yourEntireScreen": "Целия екран",
"applicationWindow": "Прозореца на програмата"
},
"\u0005dialog": {},
"email": {
@@ -379,17 +376,20 @@
],
"subject": "Покана за __appName__ (__conferenceName__)",
"body": [
"Здравей, Бих искал да те поканя в една __appName__ конференция, която създадох.",
"Здравей! Каня те в беседата в „__appName__“, която създадох.",
"",
"",
"Кликни на следния линк за да се присъединиш в конференцията.",
"Последвай следната връзка, за да се присъединиш!",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
"__appName__ поддържа __supportedBrowsers__, така че трябва да използваш един от тези браузъри.",
"В момента „__appName__ поддържа следните браузъри: __supportedBrowsers__, така че трябва да използваш един от тях.",
"",
"",
"Ще се видим след секунда!"
"Ще се видим след малко!"
],
"and": "и"
},
@@ -406,105 +406,104 @@
"ATTACHED": "Прикрепен"
},
"recording": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "Пускане / спиране на запис",
"busy": "Работим върху това да освободим ресурси за запис. Моля, опитайте отново след няколко минути.",
"busyTitle": "Всички възможности за запис в момента са заети",
"buttonTooltip": "Пускане/спиране на записа",
"error": "Грешка при опит за запис. Моля опитайте отново.",
"failedToStart": "Неуспешен опит за записване",
"off": "Записът спрян",
"on": "Запис",
"pending": "Записът изчаква участник.",
"unavailable": "",
"unavailableTitle": ""
"pending": "Записът изчаква присъединяването на участник",
"unavailable": "Опа! Услугата за запис в момента е недостъпна. Работим върху отстраняването на проблема. Моля, опитайте отново по-късно.",
"unavailableTitle": "Записът е невъзможен"
},
"\u0005recording": {},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "Пряко предаване",
"pending": "Започва пряко предаване...",
"streamIdRequired": "",
"streamIdHelp": "",
"unavailable": "",
"unavailableTitle": ""
"busy": "Работим върху това да освободим ресурси за излъчване. Моля, опитайте отново след няколко минути.",
"busyTitle": "Всички излъчватели в момента са заети.",
"buttonTooltip": "Пускане/спиране на излъчването на живо",
"error": "Излъчването на живо беше неуспешно. Моля, опитайте отново.",
"failedToStart": "Излъчването на живо не успя да започне",
"off": "Край на излъчването на живо",
"on": "Излъчване на живо",
"pending": "Започване на излъчването на живо…",
"streamIdRequired": "Моля, попълнете идентификатора на потока, за да може да започне излъчването на живо.",
"streamIdHelp": "Къде да намеря това?",
"unavailable": "Опа! Услугата за излъчване на живо в момента е недостъпна. Работим върху отстраняването на проблема. Моля, опитайте отново по-късно.",
"unavailableTitle": "Излъчването на живо е недостъпно"
},
"speakerStats": {
"hours": "",
"minutes": "",
"hours": "__count__ч",
"minutes": "__count__мин",
"name": "Име",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
"seconds": "__count__сек",
"speakerStats": "Статистика на говорителя",
"speakerTime": "Време на говорене"
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
"deviceSettings": "Настройки на устройството",
"noPermission": "Не е получено разрешение",
"previewUnavailable": "Няма възможност за преглед",
"selectADevice": "Изберете устройство",
"testAudio": "Проба на звука"
},
"invite": {
"addPassword": "",
"callNumber": "",
"enterID": "",
"howToDialIn": "",
"hidePassword": "",
"inviteTo": "",
"invitedYouTo": "",
"invitePeople": "",
"locked": "",
"showPassword": "",
"unlocked": ""
"addPassword": "Добавяне на парола",
"callNumber": "Обаждане на __number__",
"enterID": "Въведете ид. на срещата: __conferenceID__ и след това # за набиране от телефон",
"howToDialIn": "За да се включите, използвайте един от следните номера и ид. на срещата",
"hidePassword": "Скриване на паролата",
"inviteTo": "Поканете участници в __conferenceName__",
"invitedYouTo": "__userName__ Ви покани в беседата __inviteURL__",
"invitePeople": "Поканете участници",
"locked": "Този разговор е заключен. Новите участници трябва да разполагат с връзката и да въведат паролата, за да се включат.",
"showPassword": "Показване на паролата",
"unlocked": "Този разговор е отключен. Всеки, който разполага с връзката, може да се присъедини."
},
"videoStatus": {
"callQuality": "",
"hd": "",
"highDefinition": "",
"labelTooltipVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"lowDefinition": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"standardDefinition": "",
"qualityButtonTip": ""
"callQuality": "Качество на разговора",
"hd": "ВК",
"highDefinition": "Високо качество",
"labelTooltipVideo": "Текущо качество на видеото",
"labelTooltipAudioOnly": "Включен е режим само със звук",
"ld": "НК",
"lowDefinition": "Ниско качество",
"p2pEnabled": "Вкл. директно свързване",
"p2pVideoQualityDescription": "В режима на директно свързване качеството на разговора може да бъде превключено само между ниско и високо качество на звука. Всички други настройки ще бъдат пренебрегнати до излизане от режима на директно свързване.",
"recHighDefinitionOnly": "Ще се предпочита високо качество.",
"sd": "СК",
"standardDefinition": "Стандартно качество",
"qualityButtonTip": "Промяна на качеството на получаваното видео"
},
"dialOut": {
"dial": "Набиране",
"dialOut": "",
"statusMessage": "",
"enterPhone": "",
"phoneNotAllowed": ""
"dialOut": "Обаждане на #",
"statusMessage": "в момента е __status__",
"enterPhone": "Въведете телефонен номер",
"phoneNotAllowed": "Все още не поддържаме това местоназначение! Съжаляваме!"
},
"addPeople": {
"add": "",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
"add": "Добавяне",
"noResults": "Няма резултати",
"searchPlaceholder": "Потърсете хора или стаи, които да добавите",
"title": "Добавяне на участници в разговора",
"failedToAdd": "Добавянето на участници беше неуспешно"
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
"msg": "Имаше грешка.",
"retry": "Опитайте отново",
"support": "Поддръжка",
"supportMsg": "Ако това се случва често, свържете се с нашата"
},
"deviceError": {
"cameraError": "",
"microphoneError": "",
"cameraPermission": "",
"microphonePermission": ""
"cameraError": "Камерата е недостъпна",
"microphoneError": "Микрофонът е недостъпен",
"cameraPermission": "Грешка при получаване на разрешение за достъп до камерата",
"microphonePermission": "Грешка при получаване на разрешение за достъп до микрофона"
},
"info": {
"copy": "",
"invite": "",
"title": "",
"tooltip": ""
"copy": "Копиране на връзката",
"invite": "Покана в „__app__“",
"title": "Информация за достъп до разговора",
"tooltip": "Получете информация за достъп до срещата"
}
}

View File

@@ -2,23 +2,19 @@
"contactlist": "__count__ Teilnehmer",
"contactlist_plural": "",
"passwordSetRemotely": "von einem anderen Teilnehmer gesetzt",
"connectionsettings": "Verbindungseinstellungen",
"poweredby": "Betrieben von",
"feedback": {
"average": "Durschnittlich",
"bad": "Schlecht",
"good": "Gut",
"rateExperience": "Bitte bewerten Sie diese Konferenz.",
"veryBad": "Sehr schlecht",
"veryGood": "Sehr gut"
},
"inviteUrlDefaultMsg": "Die Konferenz wird erstellt...",
"me": "ich",
"speaker": "Sprecher",
"raisedHand": "Möchte sprechen",
"defaultNickname": "Bsp: Heidi Blau",
"defaultLink": "Bsp.: __url__",
"callingName": "__name__",
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Kopfhörer",
"phone": "Telefon",
"speaker": "Sprecher"
},
"audioOnly": {
"audioOnly": "Nur Audio",
"featureToggleDisabled": "Umschalten zwischen __feature__ ist im Audio-Modus deaktiviert"
@@ -130,11 +126,9 @@
"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",
"appNotInstalled": "Mit __app__ auf dem Mobiltelefon teilnehmen.",
"downloadApp": "App herunterladen",
"joinConversation": "Der Konferenz beitreten",
"startConference": "Konferenz starten"
"openApp": "In __app__ fortfahren"
},
"bottomtoolbar": {
"chat": "Chat öffnen / schliessen",
@@ -213,7 +207,9 @@
"notify": {
"disconnected": "getrennt",
"moderator": "Moderatorenrechte vergeben",
"connected": "verbunden",
"connectedOneMember": "__name__ ist beigetreten",
"connectedTwoMembers": "__first__ und __second__ sind beigetreten",
"connectedThreePlusMembers": "__name__ und __count__ sind beigetreten",
"somebody": "Jemand",
"me": "Ich",
"focus": "Konferenz-Organisator",
@@ -321,14 +317,11 @@
"stopRecordingWarning": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
"stopLiveStreaming": "Live-Streaming stoppen",
"stopRecording": "Aufnahme stoppen",
"doNotShowWarningAgain": "Diesen Hinweis nicht mehr anzeigen",
"doNotShowMessageAgain": "Diesen Hinweis nicht mehr anzeigen",
"permissionDenied": "Zugriff verweigert",
"screenSharingFailedToInstall": "Oh! Die Erweiterung für die Bildschirmfreigabe konnte nicht installiert werden.",
"screenSharingFailedToInstallTitle": "Bildschirmfreigabe-Erweiterung konnte nicht installiert werden",
"screenSharingPermissionDeniedError": "Oh! Beim Anfordern der Bildschirmfreigabe-Berechtigungen hat etwas nicht funktioniert. Bitte aktualisieren und erneut versuchen.",
"micErrorPresent": "Fehler beim Verbinden zum Mikrofon.",
"cameraErrorPresent": "Fehler beim Verbinden zur Kamera.",
"cameraUnsupportedResolutionError": "Die Kamera unterstützt die erforderliche Auflösung nicht.",
"cameraUnknownError": "Die Kamera kann aus einem unbekannten Grund nicht verwendet werden.",
"cameraPermissionDeniedError": "Die Berechtigung zur Verwendung der Kamera wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht sehen. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
@@ -416,7 +409,8 @@
"off": "Aufnahme gestoppt",
"on": "Aufnahme",
"pending": "Die Aufnahme wartet auf den Beitritt eines Teilnehmers...",
"unavailable": "Oh! Der Aufnahme-Service ist momentan nicht verfügbar. Wir arbeiten der Behebung des Problems. Bitte später wieder versuchen.",
"serviceName": "Aufnahmedienst",
"unavailable": "Oh! Der __serviceName__ ist aktuell nicht verfügbar. Wir arbeiten an der Behebung des Problems. Bitte versuchen Sie es später noch einmal.",
"unavailableTitle": "Aufnahme nicht verfügbar"
},
"liveStreaming": {
@@ -428,11 +422,23 @@
"off": "Live-Streaming gestoppt",
"on": "Live-Streaming",
"pending": "Live-Stream wird gestartet...",
"serviceName": "Live Streaming-Dienst",
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
"streamIdHelp": "Wo ist die Stream-ID zu finden?",
"unavailable": "Oh! Der Live-Streaming Dienst ist momentan nicht verfügbar. Wir arbeiten an der Behebung des Problems. Bitte später wieder versuchen.",
"unavailableTitle": "Live-Streaming nicht verfügbar"
},
"videoSIPGW": {
"busy": "Es stehen keine freien Ressourcen zur Verfügung. Bitte versuchen Sie es später noch einmal.",
"busyTitle": "Keine freien Ressourcen",
"errorInvite": "Die Konferenz konnte nicht gestartet werden. Bitte versuchen Sie es später noch einmal.",
"errorInviteTitle": "Fehler beim Erstellen der Konferenz",
"errorAlreadyInvited": "__displayName__ ist bereits eingeladen",
"errorInviteFailedTitle": "__displayName__ konnte nicht eingeladen werden",
"errorInviteFailed": "Wir arbeiten an der Behebung des Problems. Bitte versuchen Sie es später noch einmal.",
"pending": "__displayName__ wurde eingeladen",
"serviceName": "Raumkonferenz-Dienst",
"unavailableTitle": "Raumkonferenz-Dienst nicht verfügbar"
},
"speakerStats": {
"hours": "__count__h",
"minutes": "__count__m",
@@ -456,7 +462,7 @@
"hidePassword": "Passwort verstecken",
"inviteTo": "Teilnehmer zu __conferenceName__ einladen",
"invitedYouTo": "Sie wurden von __userName__ zur Konferenz __inviteURL__ eingeladen",
"invitePeople": "Teilnehmer einladen",
"invitePeople": "Einladen",
"locked": "Diese Konferenz ist gesperrt. Neue Teilnehmer müssen das Passwort eingeben um beizutreten.",
"showPassword": "Passwort anzeigen",
"unlocked": "Die Konferenz ist nicht geschützt. Jeder mit dem Link kann der Konferenz beitreten."
@@ -469,6 +475,8 @@
"labelTooltipAudioOnly": "Nur-Audio Modus aktiv",
"ld": "LD",
"lowDefinition": "Niedrige Auflösung",
"onlyAudioAvailable": "Nur Ton",
"onlyAudioSupported": "In diesem Browser wird nur Audio unterstützt.",
"p2pEnabled": "Ende-zu-Ende aktiviert",
"p2pVideoQualityDescription": "Im Ende-zu-Ende Modus kann die Konferenzqualität nur zwischen hoch und nur-Audio gewählt werden. Andere Einstellungen werden nicht beachtet.",
"recHighDefinitionOnly": "Hohe Qualität wird bevorzugt.",
@@ -502,6 +510,14 @@
"cameraPermission": "Fehler beim Bezug der Kamera-Zugriffsberechtigungen",
"microphonePermission": "Fehler beim Bezug der Mikrofon-Zugriffsberechtigungen"
},
"feedback": {
"average": "Durschnittlich",
"bad": "Schlecht",
"good": "Gut",
"rateExperience": "Bitte bewerten Sie diese Konferenz.",
"veryBad": "Sehr schlecht",
"veryGood": "Sehr gut"
},
"info": {
"copy": "Link kopieren",
"invite": "In __app__ einladen",

486
lang/main-fa.json Normal file
View File

@@ -0,0 +1,486 @@
{
"contactlist": "",
"contactlist_plural_undefined": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": {
"average": "",
"bad": "",
"good": "",
"rateExperience": "",
"veryBad": "",
"veryGood": ""
},
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"audioOnly": {
"audioOnly": "",
"featureToggleDisabled": ""
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "",
"edgeGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": "",
"showSpeakerStats": ""
},
"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": "",
"text": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"addPeople": "",
"audioonly": "",
"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": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"framerate": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural_undefined": "",
"localport": "",
"localport_plural_undefined": "",
"localaddress": "",
"localaddress_plural_undefined": "",
"remoteaddress": "",
"remoteaddress_plural_undefined": "",
"transport": "",
"transport_plural_undefined": "",
"bandwidth": "",
"na": "",
"turn": "",
"quality": {
"good": "",
"inactive": "",
"lost": "",
"nonoptimal": "",
"poor": ""
},
"status": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"allow": "",
"kickMessage": "",
"popupErrorTitle": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"contactSupport": "",
"error": "",
"createPassword": "",
"detectext": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"dismiss": "",
"rejoinNow": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupportedTitle": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"alreadySharedVideoTitle": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingFailedToInstall": "",
"screenSharingFailedToInstallTitle": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "",
"cameraNotSendingDataTitle": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"inlineInstallationMsg": "",
"inlineInstallExtension": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"unavailable": "",
"unavailableTitle": ""
},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"streamIdRequired": "",
"streamIdHelp": "",
"unavailable": "",
"unavailableTitle": ""
},
"speakerStats": {
"hours": "",
"minutes": "",
"name": "",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
},
"invite": {
"addPassword": "",
"callNumber": "",
"enterID": "",
"howToDialIn": "",
"hidePassword": "",
"inviteTo": "",
"invitedYouTo": "",
"invitePeople": "",
"locked": "",
"showPassword": "",
"unlocked": ""
},
"videoStatus": {
"callQuality": "",
"hd": "",
"highDefinition": "",
"labelTooltipVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"lowDefinition": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"standardDefinition": "",
"qualityButtonTip": ""
},
"dialOut": {
"dial": "",
"dialOut": "",
"statusMessage": "",
"enterPhone": "",
"phoneNotAllowed": ""
},
"addPeople": {
"add": "",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
},
"deviceError": {
"cameraError": "",
"microphoneError": "",
"cameraPermission": "",
"microphonePermission": ""
},
"info": {
"copy": "",
"invite": "",
"title": "",
"tooltip": ""
}
}

View File

@@ -1,24 +1,20 @@
{
"contactlist": "",
"contactlist": "__count__ Membres",
"contactlist_plural": "",
"passwordSetRemotely": "défini par un autre utilisateur",
"connectionsettings": "Paramètres de connexion",
"passwordSetRemotely": "défini par un autre membre",
"poweredby": "Produit par",
"feedback": {
"average": "Moyen",
"bad": "Mauvais",
"good": "",
"rateExperience": "Veuillez évaluer votre réunion.",
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"inviteUrlDefaultMsg": "Votre conférence est en cours de création...",
"me": "moi",
"speaker": "Haut-parleur",
"raisedHand": "Aimerait prendre la parole",
"defaultNickname": "ex. Jean Dupont",
"defaultLink": "ex. __url__",
"callingName": "__name__",
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Écouteurs",
"phone": "Téléphone",
"speaker": "Haut-parleur"
},
"audioOnly": {
"audioOnly": "Audio uniquement",
"featureToggleDisabled": "Le basculement de __feature__ est désactivé en mode audio uniquement"
@@ -45,7 +41,7 @@
"focusRemote": "Épingler la vidéo des autres",
"toggleChat": "Ouvrir ou fermer le panneau de conversation",
"mute": "Activer ou désactiver le microphone",
"fullScreen": "Activer / Désactiver le plein écran",
"fullScreen": "Activer ou Désactiver le plein écran",
"videoMute": "Démarrer ou arrêter votre caméra",
"showSpeakerStats": "Afficher les statistiques de l'interlocuteur"
},
@@ -64,7 +60,7 @@
"title": "Open source"
},
"feature4": {
"content": "Il n'y a pas de limitation sur le nombre d'utilisateurs ou de conférences. Seuls la puissance et la bande passante du serveur sont des facteurs limitants.",
"content": "Il n'y a pas de limitation sur le nombre d'utilisateurs ou de conférences. Seules la puissance et la bande passante du serveur sont des facteurs limitants.",
"title": "Nombre d'utilisateurs illimité"
},
"feature5": {
@@ -92,16 +88,16 @@
"terms": "Termes"
},
"startupoverlay": {
"policyText": "",
"policyText": " ",
"title": " __app__ a besoin d'accéder à votre microphone et votre caméra."
},
"suspendedoverlay": {
"title": "Votre visioconférence a été arrêté parce que votre ordinateur s'est mis en veille.",
"title": "Votre visioconférence s'est interrompue parce que votre ordinateur s'est mis en veille.",
"text": "Cliquez sur le bouton <i>Rejoindre</i> pour se reconnecter.",
"rejoinKeyTitle": "Rejoindre"
},
"toolbar": {
"addPeople": "",
"addPeople": "Ajouter des personnes à votre appel",
"audioonly": "Activer / Désactiver le mode audio uniquement (économiser de la bande passante)",
"mute": "Muet / Actif",
"videomute": "Démarrer / Arrêter la caméra",
@@ -120,7 +116,7 @@
"logout": "Déconnexion",
"dialpad": "Ouvrir / Fermer le pavé numérique",
"sharedVideoMutedPopup": "Votre vidéo a été coupée pour que vous puissiez parler aux autres participants.",
"micMutedPopup": "Votre microphone est désormais en muet pour pouvoir profiter de la votre vidéo.",
"micMutedPopup": "Votre microphone a été coupé afin que vous puissiez profiter de la vidéo partagée",
"talkWhileMutedPopup": "Vous voulez parler? Vous êtes en muet.",
"unableToUnmutePopup": "Vous ne pouvez pas réactiver votre microphone pendant que la vidéo partagée est activée.",
"cameraDisabled": "La camera n'est pas disponible",
@@ -129,13 +125,10 @@
"profile": "Éditer votre profil",
"raiseHand": "Lever / Baisser la main"
},
"\u0005toolbar": {},
"unsupportedBrowser": {
"appInstalled": "ou si vous l'avez déjà installé<br /><strong></strong>",
"appNotInstalled": "Vous devez installer <strong>__app__</strong> pour rejoindre une conversation sur votre mobile",
"downloadApp": "Télécharger l'Application",
"joinConversation": "Rejoindre la discussion de groupe",
"startConference": "Démarrer une conférence"
"appNotInstalled": "Rejoignez cette réunion avec __app__ sur votre téléphone.",
"downloadApp": "Télécharger l'application",
"openApp": "Continuer sur __app__"
},
"bottomtoolbar": {
"chat": "Ouvrir / fermer le chat",
@@ -178,7 +171,7 @@
"kick": "Exclure",
"muted": "Muet",
"domute": "Couper le micro",
"flip": "",
"flip": "Balancer",
"remoteControl": "Contrôle à distance"
},
"connectionindicator": {
@@ -189,34 +182,35 @@
"framerate": "Images par seconde",
"less": "Cacher le détail",
"more": "Montrer le détail",
"address": "Adresse:",
"address": "Adresse :",
"remoteport": "Port distant:",
"remoteport_plural": "Ports distants:",
"localport": "Port local:",
"localport_plural": "Ports locaux:",
"localaddress": "Adresse locale:",
"localaddress_plural": "Adresses locales:",
"remoteaddress": "Adresse distante:",
"remoteaddress_plural": "Adresses distantes:",
"transport": "Transport:",
"transport_plural": "",
"localport": "Port local :",
"localport_plural": "Ports locaux :",
"localaddress": "Adresse locale :",
"localaddress_plural": "Adresses locales :",
"remoteaddress": "Adresse distante :",
"remoteaddress_plural": "Adresses distantes :",
"transport": "Transport :",
"transport_plural": "Transports :",
"bandwidth": "Bande passante estimée :",
"na": "Revenez ici pour afficher les informations de connexion une fois la conférence démarrée",
"turn": "",
"turn": "(tour)",
"quality": {
"good": "",
"inactive": "",
"lost": "",
"nonoptimal": "",
"poor": ""
"good": "Bien",
"inactive": "Inactif",
"lost": "Perdu",
"nonoptimal": "Non-optimale",
"poor": "Mauvaise"
},
"status": "Connexion en cours"
"status": "Connexion:"
},
"\u0005connectionindicator": {},
"notify": {
"disconnected": "déconnecté",
"moderator": "Droits modérateur accordés !",
"connected": "connecté",
"connectedOneMember": "__name__ connecté",
"connectedTwoMembers": "__first__ et __second__ connectés",
"connectedThreePlusMembers": "__name__ and __count__ others connectés",
"somebody": "Quelqu'un",
"me": "Moi",
"focus": "Focus de conférence",
@@ -225,23 +219,23 @@
"grantedToUnknown": "Droits modérateur accordés à $t(notify.somebody)!",
"muted": "Vous avez commencé la conversation en muet.",
"mutedTitle": "Vous êtes en muet !",
"raisedHand": "Aimerais prendre la parole."
"raisedHand": "Aimerait prendre la parole."
},
"dialog": {
"add": "Ajouter",
"allow": "Autoriser",
"kickMessage": "Oups! Vous avez été renvoyé de la réunion !",
"popupErrorTitle": "",
"popupError": "",
"popupErrorTitle": "Pop-up bloquée",
"popupError": "Votre navigateur bloque les fenêtres pop-up. Veuillez autoriser les fenêtres pop-up dans les paramètres de votre navigateur.",
"passwordErrorTitle": "Problème avec le mot de passe",
"passwordError": "Cette conversation est actuellement protégée par un mot de passe. Seul le propriétaire de la conférence peut définir un mot de passe.",
"passwordError2": "Cette conversation n'est actuellement pas protégé par un mot de passe. Seul le propriétaire de la conférence peut définir un mot de passe.",
"connectError": "Oups! Un problème est survenu et la connexion à la conférence est impossible.",
"connectErrorWithMsg": "Oups! Un problème est survenu et la connexion à la conférence est impossible: __msg__",
"incorrectPassword": "",
"incorrectPassword": "Nom d'utilisateur ou mot de passe incorrect",
"connecting": "Connexion en cours",
"copy": "Copier",
"contactSupport": "",
"contactSupport": "Contacter le support",
"error": "Erreur",
"createPassword": "Créer un mot de passe",
"detectext": "Une erreur est survenue pendant la détection de l'extension de partage d'écran.",
@@ -250,17 +244,17 @@
"conferenceReloadMsg": "Nous somme en train de régler cela. Reconnexion dans __seconds__ sec...",
"conferenceDisconnectTitle": "Vous avez été déconnecté.",
"conferenceDisconnectMsg": "Veuillez vérifier votre connexion réseau. Reconnexion dans __seconds__ sec...",
"dismiss": "",
"dismiss": "Rejeter",
"rejoinNow": "Rejoindre maintenant",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "",
"maxUsersLimitReachedTitle": "Le nombre maximal de participants est atteint",
"maxUsersLimitReached": "Le nombre maximal de participants est atteint. La conférence est complète. Merci de contacter le propriétaire du salon ou réessayer plus tard.",
"lockTitle": "Échec du verrouillage",
"lockMessage": "Impossible de verrouiller la conférence.",
"warning": "Avertissement",
"passwordNotSupportedTitle": "",
"passwordNotSupported": "",
"passwordNotSupportedTitle": "Mot de passe non supporté",
"passwordNotSupported": "La définition d'un mot de passe de conférence n'est pas prise en charge.",
"internalErrorTitle": "Erreur interne",
"internalError": "",
"internalError": "Oups! Quelque chose s'est mal passée. L'erreur suivante s'est produite: __error__",
"unableToSwitch": "Impossible de changer le flux vidéo.",
"SLDFailure": "Oups! Un problème est survenu et le micro n'a pas été coupé! (Échec SLD)",
"SRDFailure": "Oups! Un problème est survenu et la caméra n'a pas été coupée! (Échec SRD)",
@@ -277,8 +271,8 @@
"shareVideoLinkError": "Fournissez s'il vous plaît un lien Youtube fonctionnel.",
"removeSharedVideoTitle": "Supprimer la vidéo partagée",
"removeSharedVideoMsg": "Voulez-vous vraiment supprimer votre vidéo partagée ?",
"alreadySharedVideoMsg": "",
"alreadySharedVideoTitle": "",
"alreadySharedVideoMsg": "Un autre membre partage déjà une vidéo. Cette conférence n'autorise qu'une seule vidéo partagée à la fois.",
"alreadySharedVideoTitle": "Une seule vidéo partagée est autorisée à la fois",
"WaitingForHost": "En attente de l'hôte ...",
"WaitForHostMsg": "La conférence <b>__room__</b> n'a pas encore commencé. Si vous êtes l'hôte, merci de vous authentifier. Sinon, merci d'attendre que l'hôte arrive.",
"IamHost": "Je suis lhôte",
@@ -287,7 +281,7 @@
"retry": "Réessayer",
"logoutTitle": "Déconnexion",
"logoutQuestion": "Voulez-vous vraiment vous déconnecter et arrêter la conférence ?",
"sessTerminated": "",
"sessTerminated": "Appel terminé",
"hungUp": "Vous avez raccroché",
"joinAgain": "Rejoignez à nouveau la conférence",
"Share": "Partager",
@@ -324,14 +318,11 @@
"stopRecordingWarning": "Désirez-vous vraiment arrêter l'enregistrement?",
"stopLiveStreaming": "Arrêter le direct",
"stopRecording": "Arrêter l'enregistrement",
"doNotShowWarningAgain": "Ne plus afficher cet avertissement",
"doNotShowMessageAgain": "Ne plus afficher ce message",
"permissionDenied": "Permission refusée",
"screenSharingFailedToInstall": "",
"screenSharingFailedToInstallTitle": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "Une erreur est survenue lors de la connexion à votre microphone.",
"cameraErrorPresent": "Votre caméra ne satisfait pas certaines des contraintes nécessaires.",
"screenSharingFailedToInstall": "Oups! Votre extension de partage d'écran n'a pas pu être installée.",
"screenSharingFailedToInstallTitle": "L'extension de partage d'écran n'a pas pu être installée",
"screenSharingPermissionDeniedError": "Oups! Une erreur s'est produite avec vos autorisations d'extension de partage d'écran. Veuillez rafraîchir et réessayer.",
"cameraUnsupportedResolutionError": "Votre appareil ne prend pas en charge la résolution vidéo requise.",
"cameraUnknownError": "Vous ne pouvez pas utiliser la caméra pour une raison inconnue.",
"cameraPermissionDeniedError": "Vous n'avez pas autorisé l'utilisation de votre caméra. Vous pouvez toujours participer à la conférence, mais les autres ne vont pas vous voir. Utilisez le bouton de la caméra dans la barre d'adresse pour résoudre ce problème.",
@@ -341,16 +332,16 @@
"micPermissionDeniedError": "Vous n'avez pas autorisé l'utilisation de votre microphone. Vous pouvez toujours participer à la conférence, mais les autres ne vont pas vous entendre. Utilisez le bouton du microphone dans la barre d'adresse pour résoudre ce problème.",
"micNotFoundError": "Le microphone n'a pas été détecté.",
"micConstraintFailedError": "Votre microphone ne satisfait pas certaines des contraintes nécessaires.",
"micNotSendingDataTitle": "",
"micNotSendingData": "",
"cameraNotSendingDataTitle": "",
"cameraNotSendingData": "",
"micNotSendingDataTitle": "Impossible d'accéder au microphone",
"micNotSendingData": "Nous sommes incapables d'accéder à votre microphone. Veuillez sélectionner un autre périphérique dans les paramètres ou rafraîchir la page.",
"cameraNotSendingDataTitle": "Impossible d'accéder à votre caméra",
"cameraNotSendingData": "Nous sommes incapables d'accéder à votre caméra. Veuillez sélectionner un autre périphérique dans les paramètres ou rafraîchir la page",
"goToStore": "Aller sur le webstore",
"externalInstallationTitle": "Extension requise : ",
"externalInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
"inlineInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
"inlineInstallExtension": "",
"muteParticipantTitle": "",
"inlineInstallExtension": "Installer maintenant",
"muteParticipantTitle": "Couper le micro de ce participant?",
"muteParticipantBody": "Vous ne pourrez plus réactiver leurs micros, mais ils peuvent l'activer par eux-même à tout moment.",
"muteParticipantButton": "Couper le micro",
"remoteControlTitle": "Contrôle de bureau à distance",
@@ -359,7 +350,7 @@
"remoteControlDeniedMessage": "__user__ a refusé votre demande de prise en main à distance!",
"remoteControlAllowedMessage": "Une erreur s'est produite lors de la demande dautorisation de prise en main à distance avec __user__!",
"remoteControlErrorMessage": "Une erreur s'est produite lors de la demande dautorisation de prise en main à distance avec __user__!",
"startRemoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "Une erreur est survenue lors de la tentative de démarrage de la session de contrôle à distance!",
"remoteControlStopMessage": "La prise en main à distance est terminée!",
"close": "Fermer",
"shareYourScreen": "Partagez votre écran",
@@ -407,31 +398,43 @@
"ATTACHED": "Attachée"
},
"recording": {
"busy": "",
"busyTitle": "",
"busy": "Nous sommes en train de libérer les ressources d'enregistrement. Réessayez dans quelques minutes.",
"busyTitle": "Tous les enregistreurs sont actuellement occupés",
"buttonTooltip": "Démarrer / Arrêter l'enregistrement",
"error": "Échec de l'enregistrement. Veuillez réessayer.",
"failedToStart": "L'enregistrement n'as pas réussi à démarrer",
"off": "Enregistrement arrêter",
"off": "Enregistrement arrêté",
"on": "Enregistrement",
"pending": "Enregistrement en attente de participant...",
"unavailable": "",
"unavailableTitle": ""
"serviceName": "Service d'enregistrement",
"unavailable": "Oups! Le __serviceName__ est actuellement indisponible. Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"unavailableTitle": "Enregistrement indisponible"
},
"\u0005recording": {},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"busy": "Nous travaillons sur la libération des ressources de Streaming. Veuillez réessayez dans quelques minutes.",
"busyTitle": "Tous les streamers sont actuellement occupés",
"buttonTooltip": "Démarrer / Arrêter le Stream",
"error": "Le Streaming a échoué. Veuillez réessayer.",
"failedToStart": "Le Streaming n'as pas réussi à démarrer",
"off": "Le Streaming a été arrêter",
"on": "Direct",
"pending": "Commencer le direct...",
"streamIdRequired": "",
"serviceName": "Service de diffusion en direct",
"streamIdRequired": "Merci de renseigner le stream id pour lancer le streaming.",
"streamIdHelp": "Où puis-je trouver ceci?",
"unavailable": "",
"unavailableTitle": ""
"unavailableTitle": "Le Streaming est indisponible"
},
"videoSIPGW": {
"busy": "Nous travaillons sur la libération des ressources. Veuillez réessayez dans quelques minutes.",
"busyTitle": "Le service du Salon est actuellement occupé",
"errorInvite": "La conférence n'est pas encore établie. Veuillez réessayer plus tard.",
"errorInviteTitle": "Erreur lors de l'invitation",
"errorAlreadyInvited": "__displayName__ est déjà invité(e)",
"errorInviteFailedTitle": "l'invitation de __displayName__ a échoué",
"errorInviteFailed": "Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"pending": "__displayName__ a été invité(e)",
"serviceName": "Service de salon",
"unavailableTitle": "Service indisponible"
},
"speakerStats": {
"hours": "__count__h",
@@ -442,7 +445,7 @@
"speakerTime": "Temps de l'interlocuteur"
},
"deviceSelection": {
"deviceSettings": "Paramètres du périphérique",
"deviceSettings": "Paramètres",
"noPermission": "Permission non accordée",
"previewUnavailable": "Aperçu non disponible",
"selectADevice": "Sélectionner un périphérique",
@@ -456,56 +459,66 @@
"hidePassword": "Cacher le mot de passe",
"inviteTo": "inviter des participants à __conferenceName__",
"invitedYouTo": "__userName__ vous a invité(e) à la conférence __inviteURL__",
"invitePeople": "",
"invitePeople": "Inviter",
"locked": "Cet appel est verrouillé. les nouveaux interlocuteurs devraient avoir le lien et saisir le mot de passe pour rejoindre.",
"showPassword": "Afficher le mot de passe",
"unlocked": "Cet appel est verrouillé. Tout nouveau participant avec un lien peut rejoindre l'appel."
},
"videoStatus": {
"callQuality": "",
"callQuality": "Qualité de l'appel",
"hd": "HD",
"highDefinition": "",
"labelTooltipVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"lowDefinition": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "SD",
"standardDefinition": "",
"qualityButtonTip": ""
"highDefinition": "Haute définition",
"labelTooltipVideo": "Qualité vidéo actuelle",
"labelTooltipAudioOnly": "Mode audio uniquement activé",
"ld": "BD",
"lowDefinition": "Basse définition",
"onlyAudioAvailable": "Seul l'audio est disponible",
"onlyAudioSupported": "Nous ne supportons que l'audio sur ce navigateur.",
"p2pEnabled": "Peer to Peer activé",
"p2pVideoQualityDescription": "En mode peer to peer, la qualité d'appel reçue ne peut être basculée qu'entre haut et audio. Les autres paramètres ne seront pas respectés tant que l'on n'aura pas quitté peer to peer.",
"recHighDefinitionOnly": "Va préférer la haute définition",
"sd": "MD",
"standardDefinition": "Moyenne Définition",
"qualityButtonTip": "Changer la qualité de vidéo reçue"
},
"dialOut": {
"dial": "Composer",
"dialOut": "",
"dialOut": "Appeler #",
"statusMessage": "est maintenant __status__",
"enterPhone": "Saisissez un numéro de téléphone",
"phoneNotAllowed": "Désolé, nous ne supportons pas encore cette destination!"
},
"addPeople": {
"add": "Ajouter",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
"noResults": "Aucun résultat de recherche correspondant",
"searchPlaceholder": "Rechercher des personnes et des salons à ajouter",
"title": "Ajouter des personnes à votre appel",
"failedToAdd": "Échec de l'ajout de membres"
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
"msg": "Nous avons trébuché un peu.",
"retry": "Réessayer",
"support": "Support",
"supportMsg": "Si cela continue, prenez contact avec"
},
"deviceError": {
"cameraError": "",
"microphoneError": "",
"cameraPermission": "",
"microphonePermission": ""
"cameraError": "Impossible d'accéder à votre caméra",
"microphoneError": "Impossible d'accéder à votre microphone",
"cameraPermission": "Erreur lors de l'obtention de la permission de la caméra ",
"microphonePermission": "Erreur lors de l'obtention de la permission du microphone"
},
"feedback": {
"average": "Moyen",
"bad": "Mauvais",
"good": "Bien",
"rateExperience": "Veuillez évaluer votre réunion.",
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"info": {
"copy": "",
"invite": "",
"title": "",
"tooltip": ""
"copy": "Copier le lien",
"invite": "Inviter à __app__",
"title": "Informations sur la conférence",
"tooltip": "Obtenir des informations d'accès"
}
}

View File

@@ -223,7 +223,9 @@
"grantedToUnknown": "Moderator rights granted to $t(notify.somebody)!",
"muted": "You have started the conversation muted.",
"mutedTitle": "You're muted!",
"raisedHand": "Would like to speak."
"raisedHand": "Would like to speak.",
"suboptimalExperienceTitle": "Browser Warning",
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>."
},
"dialog": {
"add": "Add",
@@ -535,5 +537,18 @@
"invite": "Invite in __app__",
"title": "Call access info",
"tooltip": "Get access info about the meeting"
},
"profileModal": {
"alertOk": "OK",
"alertTitle": "Warning",
"alertURLText": "The entered server URL is invalid",
"conferenceSection": "Conference",
"displayName": "Display name",
"email": "Email",
"header": "Settings",
"profileSection": "Profile",
"serverURL": "Server URL",
"startWithAudioMuted": "Start with audio muted",
"startWithVideoMuted": "Start with video muted"
}
}

View File

@@ -1,5 +1,7 @@
/* eslint-disable no-unused-vars, no-var */
// Logging configuration
// XXX When making any changes to this file make sure to also update it's React
// version at ./react/features/base/logging/reducer.js !!!
var loggingConfig = {
// default log level for the app and lib-jitsi-meet
defaultLogLevel: 'trace',
@@ -7,9 +9,11 @@ var loggingConfig = {
// Option to disable LogCollector (which stores the logs on CallStats)
// disableLogCollector: true,
// Logging level adjustments for verbose modules:
// The following are too verbose in their logging with the
// {@link #defaultLogLevel}:
'modules/statistics/CallStats.js': 'info',
'modules/xmpp/strophe.util.js': 'log',
'modules/statistics/CallStats.js': 'info'
'modules/RTC/TraceablePeerConnection.js': 'info'
};
/* eslint-enable no-unused-vars, no-var */

View File

@@ -3,9 +3,8 @@
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import {
API_TOGGLE_AUDIO,
API_TOGGLE_VIDEO,
sendAnalyticsEvent
createApiEvent,
sendAnalytics
} from '../../react/features/analytics';
import { getJitsiMeetTransport } from '../transport';
@@ -56,25 +55,52 @@ let videoAvailable = true;
*/
function initCommands() {
commands = {
'display-name':
APP.conference.changeLocalDisplayName.bind(APP.conference),
'display-name': displayName => {
sendAnalytics(createApiEvent('display.name.changed'));
APP.conference.changeLocalDisplayName(displayName);
},
'submit-feedback': feedback => {
sendAnalytics(createApiEvent('submit.feedback'));
APP.conference.submitFeedback(feedback.score, feedback.message);
},
'toggle-audio': () => {
sendAnalyticsEvent(API_TOGGLE_AUDIO);
sendAnalytics(createApiEvent('toggle-audio'));
logger.log('Audio toggle: API command received');
APP.conference.toggleAudioMuted(false /* no UI */);
},
'toggle-video': () => {
sendAnalyticsEvent(API_TOGGLE_VIDEO);
sendAnalytics(createApiEvent('toggle-video'));
logger.log('Video toggle: API command received');
APP.conference.toggleVideoMuted(false /* no UI */);
},
'toggle-film-strip': APP.UI.toggleFilmstrip,
'toggle-chat': APP.UI.toggleChat,
'toggle-contact-list': APP.UI.toggleContactList,
'toggle-share-screen': toggleScreenSharing,
'video-hangup': () => APP.conference.hangup(true),
'email': APP.conference.changeLocalEmail,
'avatar-url': APP.conference.changeLocalAvatarUrl
'toggle-film-strip': () => {
sendAnalytics(createApiEvent('film.strip.toggled'));
APP.UI.toggleFilmstrip();
},
'toggle-chat': () => {
sendAnalytics(createApiEvent('chat.toggled'));
APP.UI.toggleChat();
},
'toggle-contact-list': () => {
sendAnalytics(createApiEvent('contact.list.toggled'));
APP.UI.toggleContactList();
},
'toggle-share-screen': () => {
sendAnalytics(createApiEvent('screen.sharing.toggled'));
toggleScreenSharing();
},
'video-hangup': () => {
sendAnalytics(createApiEvent('video.hangup'));
APP.conference.hangup(true);
},
'email': email => {
sendAnalytics(createApiEvent('email.changed'));
APP.conference.changeLocalEmail(email);
},
'avatar-url': avatarUrl => {
sendAnalytics(createApiEvent('avatar.url.changed'));
APP.conference.changeLocalAvatarUrl(avatarUrl);
}
};
transport.on('event', ({ data, name }) => {
if (name && commands[name]) {
@@ -434,6 +460,16 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that conference feedback
* has been submitted. Intended to be used in conjunction with the
* submit-feedback command to get notified if feedback was submitted.
*
* @returns {void}
*/
notifyFeedbackSubmitted() {
this._sendEvent({ name: 'feedback-submitted' });
}
/**
* Disposes the allocated resources.

View File

@@ -21,6 +21,7 @@ const commands = {
displayName: 'display-name',
email: 'email',
hangup: 'video-hangup',
submitFeedback: 'submit-feedback',
toggleAudio: 'toggle-audio',
toggleChat: 'toggle-chat',
toggleContactList: 'toggle-contact-list',
@@ -38,6 +39,7 @@ const events = {
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'display-name-change': 'displayNameChange',
'feedback-submitted': 'feedbackSubmitted',
'incoming-message': 'incomingMessage',
'outgoing-message': 'outgoingMessage',
'participant-joined': 'participantJoined',
@@ -121,7 +123,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
noSSL,
jwt
jwt,
onload
] = args;
return {
@@ -132,7 +135,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
noSSL,
jwt
jwt,
onload
};
case 'object': // new arguments format
return args[0];
@@ -194,6 +198,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* used.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.onload] - The onload function that will listen
* for iframe onload event.
*/
constructor(domain, ...args) {
super();
@@ -205,7 +211,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
configOverwrite = {},
interfaceConfigOverwrite = {},
noSSL = false,
jwt = undefined
jwt = undefined,
onload = undefined
} = parseArguments(args);
this._parentNode = parentNode;
@@ -216,7 +223,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
noSSL,
roomName
});
this._createIFrame(height, width);
this._createIFrame(height, width, onload);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -241,11 +248,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* parseSizeParam for format details.
* @param {number|string} width - The with of the iframe. Check
* parseSizeParam for format details.
* @param {Function} onload - The function that will listen
* for onload event.
* @returns {void}
*
* @private
*/
_createIFrame(height, width) {
_createIFrame(height, width, onload) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
@@ -256,6 +265,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._setSize(height, width);
this._frame.setAttribute('allowFullScreen', 'true');
this._frame.style.border = 0;
if (onload) {
// waits for iframe resources to load
// and fires event when it is done
this._frame.onload = onload;
}
this._frame = this._parentNode.appendChild(this._frame);
}

View File

@@ -142,6 +142,32 @@ UI.toggleFullScreen = function() {
UIUtil.isFullScreen() ? UIUtil.exitFullScreen() : UIUtil.enterFullScreen();
};
/**
* Indicates if we're currently in full screen mode.
*
* @return {boolean} {true} to indicate that we're currently in full screen
* mode, {false} otherwise
*/
UI.isFullScreen = function() {
return UIUtil.isFullScreen();
};
/**
* Returns true if the etherpad window is currently visible.
* @returns {Boolean} - true if the etherpad window is currently visible.
*/
UI.isEtherpadVisible = function() {
return Boolean(etherpadManager && etherpadManager.isVisible());
};
/**
* Returns true if there is a shared video which is being shown (?).
* @returns {boolean} - true if there is a shared video which is being shown.
*/
UI.isSharedVideoShown = function() {
return Boolean(sharedVideoManager && sharedVideoManager.isSharedVideoShown);
};
/**
* Notify user that server has shut down.
*/
@@ -616,11 +642,16 @@ UI.toggleFilmstrip = function() {
};
/**
* Indicates if the filmstrip is currently visible or not.
* @returns {true} if the filmstrip is currently visible, otherwise
* Checks if the filmstrip is currently visible or not.
* @returns {true} if the filmstrip is currently visible, and false otherwise.
*/
UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
/**
* @returns {true} if the chat panel is currently visible, and false otherwise.
*/
UI.isChatVisible = () => Chat.isVisible();
/**
* Toggles chat panel.
*/
@@ -851,8 +882,7 @@ UI.notifyInitiallyMuted = function() {
'notify.mutedTitle',
'connected',
'notify.muted',
null,
120000);
null);
};
/**

View File

@@ -24,11 +24,9 @@ import {
JitsiRecordingStatus
} from '../../../react/features/base/lib-jitsi-meet';
import {
RECORDING_CANCELED,
RECORDING_CLICKED,
RECORDING_STARTED,
RECORDING_STOPPED,
sendAnalyticsEvent
createToolbarEvent,
createRecordingDialogEvent,
sendAnalytics
} from '../../../react/features/analytics';
import { setToolboxEnabled } from '../../../react/features/toolbox';
import { setNotificationsEnabled } from '../../../react/features/notifications';
@@ -452,12 +450,13 @@ const Recording = {
},
// checks whether recording is enabled and whether we have params
// to start automatically recording
// to start automatically recording (XXX: No, it doesn't do that).
checkAutoRecord() {
if (_isRecordingButtonEnabled && config.autoRecord) {
this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
this.predefinedToken);
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ token: this.predefinedToken });
}
},
@@ -467,11 +466,16 @@ const Recording = {
* @returns {void}
*/
_onToolbarButtonClick() {
sendAnalytics(createToolbarEvent(
'recording.button',
{
'dialog_present': Boolean(dialog)
}));
if (dialog) {
return;
}
sendAnalyticsEvent(RECORDING_CLICKED);
switch (this.currentState) {
case JitsiRecordingStatus.ON:
case JitsiRecordingStatus.RETRYING:
@@ -479,7 +483,13 @@ const Recording = {
_showStopRecordingPrompt(this.recordingType).then(
() => {
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
sendAnalyticsEvent(RECORDING_STOPPED);
// The confirm button on the stop recording dialog was
// clicked
sendAnalytics(
createRecordingDialogEvent(
'stop',
'confirm.button'));
},
() => {}); // eslint-disable-line no-empty-function
break;
@@ -492,21 +502,32 @@ const Recording = {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ streamId });
sendAnalyticsEvent(RECORDING_STARTED);
// The confirm button on the start recording dialog was
// clicked
sendAnalytics(
createRecordingDialogEvent(
'start',
'confirm.button'));
})
.catch(reason => {
if (reason === APP.UI.messageHandler.CANCEL) {
sendAnalyticsEvent(RECORDING_CANCELED);
// The cancel button on the start recording dialog was
// clicked
sendAnalytics(
createRecordingDialogEvent(
'start',
'cancel.button'));
} else {
logger.error(reason);
}
});
} else {
// Note that we only fire analytics events for Jibri.
if (this.predefinedToken) {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ token: this.predefinedToken });
sendAnalyticsEvent(RECORDING_STARTED);
return;
}
@@ -515,12 +536,9 @@ const Recording = {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ token });
sendAnalyticsEvent(RECORDING_STARTED);
})
.catch(reason => {
if (reason === APP.UI.messageHandler.CANCEL) {
sendAnalyticsEvent(RECORDING_CANCELED);
} else {
if (reason !== APP.UI.messageHandler.CANCEL) {
logger.error(reason);
}
});

View File

@@ -11,15 +11,8 @@ import LargeContainer from '../videolayout/LargeContainer';
import Filmstrip from '../videolayout/Filmstrip';
import {
SHARED_VIDEO_ALREADY_SHARED,
SHARED_VIDEO_AUDIO_MUTED,
SHARED_VIDEO_AUDIO_UNMUTED,
SHARED_VIDEO_CANCELED,
SHARED_VIDEO_PAUSED,
SHARED_VIDEO_STARTED,
SHARED_VIDEO_STOPPED,
SHARED_VIDEO_VOLUME_CHANGED,
sendAnalyticsEvent
createSharedVideoEvent as createEvent,
sendAnalytics
} from '../../../react/features/analytics';
import {
participantJoined,
@@ -95,11 +88,11 @@ export default class SharedVideoManager {
url => {
this.emitter.emit(
UIEvents.UPDATE_SHARED_VIDEO, url, 'start');
sendAnalyticsEvent(SHARED_VIDEO_STARTED);
sendAnalytics(createEvent('started'));
},
err => {
logger.log('SHARED VIDEO CANCELED', err);
sendAnalyticsEvent(SHARED_VIDEO_CANCELED);
sendAnalytics(createEvent('canceled'));
}
);
@@ -119,7 +112,7 @@ export default class SharedVideoManager {
}
this.emitter.emit(
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop');
sendAnalyticsEvent(SHARED_VIDEO_STOPPED);
sendAnalytics(createEvent('stopped'));
},
() => {}); // eslint-disable-line no-empty-function
} else {
@@ -127,7 +120,7 @@ export default class SharedVideoManager {
descriptionKey: 'dialog.alreadySharedVideoMsg',
titleKey: 'dialog.alreadySharedVideoTitle'
});
sendAnalyticsEvent(SHARED_VIDEO_ALREADY_SHARED);
sendAnalytics(createEvent('already.shared'));
}
}
@@ -236,7 +229,7 @@ export default class SharedVideoManager {
// eslint-disable-next-line eqeqeq
} else if (event.data == YT.PlayerState.PAUSED) {
self.smartAudioUnmute();
sendAnalyticsEvent(SHARED_VIDEO_PAUSED);
sendAnalytics(createEvent('paused'));
}
// eslint-disable-next-line eqeqeq
self.fireSharedVideoEvent(event.data == YT.PlayerState.PAUSED);
@@ -268,7 +261,12 @@ export default class SharedVideoManager {
} else if (event.data.volume <= 0 || event.data.muted) {
self.smartAudioUnmute();
}
sendAnalyticsEvent(SHARED_VIDEO_VOLUME_CHANGED);
sendAnalytics(createEvent(
'volume.changed',
{
volume: event.data.volume,
muted: event.data.muted
}));
};
window.onPlayerReady = function(event) {
@@ -434,8 +432,8 @@ export default class SharedVideoManager {
}
/**
* Updates video, if its not playing and needs starting or
* if its playing and needs to be paysed
* Updates video, if it's not playing and needs starting or if it's playing
* and needs to be paused.
* @param id the id of the sender of the command
* @param url the video url
* @param attributes
@@ -574,7 +572,7 @@ export default class SharedVideoManager {
if (APP.conference.isLocalAudioMuted()
&& !this.mutedWithUserInteraction
&& !this.isSharedVideoVolumeOn()) {
sendAnalyticsEvent(SHARED_VIDEO_AUDIO_UNMUTED);
sendAnalytics(createEvent('audio.unmuted'));
logger.log('Shared video: audio unmuted');
this.emitter.emit(UIEvents.AUDIO_MUTED, false, false);
this.showMicMutedPopup(false);
@@ -588,7 +586,7 @@ export default class SharedVideoManager {
smartAudioMute() {
if (!APP.conference.isLocalAudioMuted()
&& this.isSharedVideoVolumeOn()) {
sendAnalyticsEvent(SHARED_VIDEO_AUDIO_MUTED);
sendAnalytics(createEvent('audio.muted'));
logger.log('Shared video: audio muted');
this.emitter.emit(UIEvents.AUDIO_MUTED, true, false);
this.showMicMutedPopup(true);

View File

@@ -4,9 +4,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
import Settings from '../../../settings/Settings';
import {
AUTHENTICATE_LOGIN_CLICKED,
AUTHENTICATE_LOGOUT_CLICKED,
sendAnalyticsEvent
createProfilePanelButtonEvent,
sendAnalytics
} from '../../../../react/features/analytics';
const sidePanelsContainerId = 'sideToolbarContainer';
@@ -95,7 +94,7 @@ export default {
*
*/
function loginClicked() {
sendAnalyticsEvent(AUTHENTICATE_LOGIN_CLICKED);
sendAnalytics(createProfilePanelButtonEvent('login.button'));
emitter.emit(UIEvents.AUTH_CLICKED);
}
@@ -108,7 +107,7 @@ export default {
const titleKey = 'dialog.logoutTitle';
const msgKey = 'dialog.logoutQuestion';
sendAnalyticsEvent(AUTHENTICATE_LOGOUT_CLICKED);
sendAnalytics(createProfilePanelButtonEvent('logout.button'));
// Ask for confirmation
APP.UI.messageHandler.openTwoButtonDialog({

View File

@@ -1,13 +1,14 @@
/* global $, APP, interfaceConfig */
import { setFilmstripVisibility } from '../../../react/features/filmstrip';
import { setFilmstripVisible } from '../../../react/features/filmstrip';
import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
import {
TOOLBAR_FILMSTRIP_TOGGLED,
sendAnalyticsEvent
createShortcutEvent,
createToolbarEvent,
sendAnalytics
} from '../../../react/features/analytics';
const Filmstrip = {
@@ -75,8 +76,18 @@ const Filmstrip = {
// Firing the event instead of executing toggleFilmstrip method because
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
// to correctly resize the video area.
$('#toggleFilmstripButton').on('click',
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP));
$('#toggleFilmstripButton').on(
'click',
() => {
// The 'enable' parameter is set to true if the action results
// in the filmstrip being hidden.
sendAnalytics(createToolbarEvent(
'toggle.filmstrip.button',
{
enable: this.isFilmstripVisible()
}));
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
});
this._registerToggleFilmstripShortcut();
},
@@ -94,7 +105,14 @@ const Filmstrip = {
// Firing the event instead of executing toggleFilmstrip method because
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
// to correctly resize the video area.
const handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
const handler = () => {
sendAnalytics(createShortcutEvent(
'toggle.filmstrip',
{
enable: this.isFilmstripVisible()
}));
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
};
APP.keyboardshortcut.registerShortcut(
shortcut,
@@ -129,50 +147,43 @@ const Filmstrip = {
},
/**
* Toggles the visibility of the filmstrip.
* Toggles the visibility of the filmstrip, or sets it to a specific value
* if the 'visible' parameter is specified.
*
* @param visible optional {Boolean} which specifies the desired visibility
* of the filmstrip. If not specified, the visibility will be flipped
* (i.e. toggled); otherwise, the visibility will be set to the specified
* value.
* @param {Boolean} sendAnalytics - True to send an analytics event. The
* default value is true.
*
* Note:
* This method shouldn't be executed directly to hide the filmstrip.
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
* to correctly resize the video area.
*/
toggleFilmstrip(visible, sendAnalytics = true) {
const isVisibleDefined = typeof visible === 'boolean';
toggleFilmstrip(visible) {
const wasFilmstripVisible = this.isFilmstripVisible();
if (!isVisibleDefined) {
// eslint-disable-next-line no-param-reassign
visible = this.isFilmstripVisible();
} else if (this.isFilmstripVisible() === visible) {
// If 'visible' is defined and matches the current state, we have
// nothing to do. Otherwise (regardless of whether 'visible' is defined)
// we need to toggle the state.
if (visible === wasFilmstripVisible) {
return;
}
if (sendAnalytics) {
sendAnalyticsEvent(TOOLBAR_FILMSTRIP_TOGGLED);
}
this.filmstrip.toggleClass('hidden');
if (visible) {
if (wasFilmstripVisible) {
this.showMenuUpIcon();
} else {
this.showMenuDownIcon();
}
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
const eventEmitter = this.eventEmitter;
const isFilmstripVisible = this.isFilmstripVisible();
if (eventEmitter) {
eventEmitter.emit(
if (this.eventEmitter) {
this.eventEmitter.emit(
UIEvents.TOGGLED_FILMSTRIP,
this.isFilmstripVisible());
!wasFilmstripVisible);
}
APP.store.dispatch(setFilmstripVisibility(isFilmstripVisible));
APP.store.dispatch(setFilmstripVisible(!wasFilmstripVisible));
},
/**

View File

@@ -2,11 +2,10 @@
import { toggleDialog } from '../../react/features/base/dialog';
import {
SHORTCUT_HELP,
SHORTCUT_SPEAKER_STATS_CLICKED,
SHORTCUT_TALK_CLICKED,
SHORTCUT_TALK_RELEASED,
sendAnalyticsEvent
ACTION_SHORTCUT_PRESSED as PRESSED,
ACTION_SHORTCUT_RELEASED as RELEASED,
createShortcutEvent,
sendAnalytics
} from '../../react/features/analytics';
import { KeyboardShortcutsDialog }
from '../../react/features/keyboard-shortcuts';
@@ -72,8 +71,10 @@ const KeyboardShortcut = {
|| $(':focus').is('textarea'))) {
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
if (APP.conference.isLocalAudioMuted()) {
sendAnalyticsEvent(SHORTCUT_TALK_RELEASED);
logger.log('Talk shortcut released');
sendAnalytics(createShortcutEvent(
'push.to.talk',
PRESSED));
logger.log('Talk shortcut pressed');
APP.conference.muteAudio(false);
}
}
@@ -93,7 +94,7 @@ const KeyboardShortcut = {
* Registers a new shortcut.
*
* @param shortcutChar the shortcut character triggering the action
* @param shortcutAttr the "shortcut" html element attribute mappring an
* @param shortcutAttr the "shortcut" html element attribute mapping an
* element to this shortcut and used to show the shortcut character on the
* element tooltip
* @param exec the function to be executed when the shortcut is pressed
@@ -175,7 +176,7 @@ const KeyboardShortcut = {
*/
_initGlobalShortcuts() {
this.registerShortcut('?', null, () => {
sendAnalyticsEvent(SHORTCUT_HELP);
sendAnalytics(createShortcutEvent('help'));
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
shortcutDescriptions: _shortcutsHelp
}));
@@ -184,15 +185,15 @@ const KeyboardShortcut = {
// register SPACE shortcut in two steps to insure visibility of help
// message
this.registerShortcut(' ', null, () => {
sendAnalyticsEvent(SHORTCUT_TALK_CLICKED);
logger.log('Talk shortcut pressed');
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
logger.log('Talk shortcut released');
APP.conference.muteAudio(true);
});
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
if (!interfaceConfig.filmStripOnly) {
this.registerShortcut('T', null, () => {
sendAnalyticsEvent(SHORTCUT_SPEAKER_STATS_CLICKED);
sendAnalytics(createShortcutEvent('speaker.stats'));
APP.store.dispatch(toggleDialog(SpeakerStats, {
conference: APP.conference
}));

View File

@@ -37,20 +37,20 @@ export default class JitsiMeetLogStorage {
return;
}
let logJSON = `{"log${this.counter}":"\n`;
let logMessage = `{"log${this.counter}":"\n`;
for (let i = 0, len = logEntries.length; i < len; i++) {
const logEntry = logEntries[i];
if (typeof logEntry === 'object') {
// Aggregated message
logJSON += `(${logEntry.count}) ${logEntry.text}\n`;
logMessage += `(${logEntry.count}) ${logEntry.text}\n`;
} else {
// Regular message
logJSON += `${logEntry}\n`;
logMessage += `${logEntry}\n`;
}
}
logJSON += '"}';
logMessage += '"}';
this.counter += 1;
@@ -58,11 +58,11 @@ export default class JitsiMeetLogStorage {
// on the way that could be uninitialized if the storeLogs
// attempt would be made very early (which is unlikely)
try {
APP.conference.logJSON(logJSON);
APP.conference._room.sendApplicationLog(logMessage);
} catch (error) {
// NOTE console is intentional here
console.error(
'Failed to store the logs: ', logJSON, error);
'Failed to store the logs: ', logMessage, error);
}
}
}

5531
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,16 +44,17 @@
"jquery-i18next": "1.2.0",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f71ff66fb26f9dd0b49baeeee239345ba268d46d",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#5f8c0a662af086e7bcc19c010f1129afc9b6d650",
"lodash": "4.17.4",
"moment": "2.19.4",
"nuclear-js": "1.4.0",
"postis": "2.2.0",
"prop-types": "15.6.0",
"react": "16.2.0",
"react-animations": "^1.0.0",
"react-dom": "16.2.0",
"react-i18next": "4.8.0",
"react-native": "0.50.4",
"react-native": "0.51.0",
"react-native-background-timer": "2.0.0",
"react-native-callstats": "3.27.0",
"react-native-fetch-blob": "0.10.8",
@@ -63,7 +64,7 @@
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#cc76092fc4335488a28a9529c8b50afae2c3ecdc",
"react-native-prompt": "1.0.0",
"react-native-vector-icons": "4.4.2",
"react-native-webrtc": "github:jitsi/react-native-webrtc#7c6b167120d2224f62222037ff71deb8ff93bd38",
"react-native-webrtc": "github:jitsi/react-native-webrtc#806435b41fa152a8239ebeb7d002d1c6e979be86",
"react-redux": "5.0.6",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
@@ -91,7 +92,7 @@
"eslint-plugin-react-native": "3.2.0",
"expose-loader": "0.7.4",
"file-loader": "1.1.5",
"flow-bin": "0.56.0",
"flow-bin": "0.57.3",
"imports-loader": "0.7.1",
"node-sass": "3.13.1",
"precommit-hook": "3.0.0",

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,18 @@ import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Sends an analytics event.
* Sends an event through the lib-jitsi-meet AnalyticsAdapter interface.
*
* @inheritdoc
* @param {Object} event - The event to send. It should be formatted as
* described in AnalyticsAdapter.js in lib-jitsi-meet.
* @returns {void}
*/
export function sendAnalyticsEvent(...args: Array<any>) {
analytics.sendEvent(...args);
export function sendAnalytics(event: Object) {
try {
analytics.sendEvent(event);
} catch (e) {
logger.warn(`Error sending analytics event: ${e}`);
}
}
/**
@@ -37,24 +43,21 @@ export function initAnalytics({ getState }: { getState: Function }) {
const state = getState();
const config = state['features/base/config'];
const { analyticsScriptUrls } = config;
const machineId = JitsiMeetJS.getMachineId();
const { user } = state['features/base/jwt'];
const { analyticsScriptUrls, deploymentInfo } = config;
const { group, server, user } = state['features/base/jwt'];
const handlerConstructorOptions = {
product: 'lib-jitsi-meet',
version: JitsiMeetJS.version,
session: machineId,
user: user ? user.id : `uid-${machineId}`,
server: state['features/base/connection'].locationURL.host
envType: (deploymentInfo && deploymentInfo.envType) || 'dev',
group,
product: deploymentInfo && deploymentInfo.product,
subproduct: deploymentInfo && deploymentInfo.environment,
user: user && user.id,
version: JitsiMeetJS.version
};
_loadHandlers(analyticsScriptUrls, handlerConstructorOptions)
.then(handlers => {
const permanentProperties: Object = {
roomName: state['features/base/conference'].room,
userAgent: navigator.userAgent
};
const { group, server } = state['features/base/jwt'];
const roomName = state['features/base/conference'].room;
const permanentProperties = {};
if (server) {
permanentProperties.server = server;
@@ -65,8 +68,6 @@ export function initAnalytics({ getState }: { getState: Function }) {
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
const { deploymentInfo } = config;
if (deploymentInfo) {
for (const key in deploymentInfo) {
if (deploymentInfo.hasOwnProperty(key)) {
@@ -76,6 +77,9 @@ export function initAnalytics({ getState }: { getState: Function }) {
}
analytics.addPermanentProperties(permanentProperties);
analytics.setConferenceName(roomName);
// Set the handlers last, since this triggers emptying of the cache
analytics.setAnalyticsHandlers(handlers);
},
error => analytics.dispose() && logger.error(error));

View File

@@ -0,0 +1,19 @@
/**
* The type of (redux) action which signals the request
* to hide the app settings modal.
*
* {
* type: HIDE_APP_SETTINGS
* }
*/
export const HIDE_APP_SETTINGS = Symbol('HIDE_APP_SETTINGS');
/**
* The type of (redux) action which signals the request
* to show the app settings modal where available.
*
* {
* type: SHOW_APP_SETTINGS
* }
*/
export const SHOW_APP_SETTINGS = Symbol('SHOW_APP_SETTINGS');

View File

@@ -0,0 +1,29 @@
// @flow
import { HIDE_APP_SETTINGS, SHOW_APP_SETTINGS } from './actionTypes';
/**
* Redux-signals the request to hide the app settings modal.
*
* @returns {{
* type: HIDE_APP_SETTINGS
* }}
*/
export function hideAppSettings() {
return {
type: HIDE_APP_SETTINGS
};
}
/**
* Redux-signals the request to open the app settings modal.
*
* @returns {{
* type: SHOW_APP_SETTINGS
* }}
*/
export function showAppSettings() {
return {
type: SHOW_APP_SETTINGS
};
}

View File

@@ -0,0 +1,181 @@
// @flow
import { Component } from 'react';
import { getProfile, updateProfile } from '../../base/profile';
/**
* The type of the React {@code Component} props of {@link AbstractAppSettings}
*/
type Props = {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol,
/**
* The current profile object.
*/
_profile: Object,
/**
* The default URL for when there is no custom URL set in the profile.
*/
_serverURL: string,
/**
* The visibility prop of the settings modal.
*/
_visible: boolean,
/**
* Redux store dispatch function.
*/
dispatch: Dispatch<*>,
/**
* The i18n translate function.
*/
t: Function
};
/**
* Base (abstract) class for container component rendering the app settings
* page.
*
* @abstract
*/
export class AbstractAppSettings extends Component<Props> {
/**
* Initializes a new {@code AbstractAppSettings} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* the component.
*/
constructor(props: Props) {
super(props);
this._onChangeDisplayName = this._onChangeDisplayName.bind(this);
this._onChangeEmail = this._onChangeEmail.bind(this);
this._onChangeServerURL = this._onChangeServerURL.bind(this);
this._onStartAudioMutedChange
= this._onStartAudioMutedChange.bind(this);
this._onStartVideoMutedChange
= this._onStartVideoMutedChange.bind(this);
}
_onChangeDisplayName: (string) => void;
/**
* Handles the display name field value change.
*
* @protected
* @param {string} text - The value typed in the name field.
* @returns {void}
*/
_onChangeDisplayName(text) {
this._updateProfile({
displayName: text
});
}
_onChangeEmail: (string) => void;
/**
* Handles the email field value change.
*
* @protected
* @param {string} text - The value typed in the email field.
* @returns {void}
*/
_onChangeEmail(text) {
this._updateProfile({
email: text
});
}
_onChangeServerURL: (string) => void;
/**
* Handles the server name field value change.
*
* @protected
* @param {string} text - The server URL typed in the server field.
* @returns {void}
*/
_onChangeServerURL(text) {
this._updateProfile({
serverURL: text
});
}
_onStartAudioMutedChange: (boolean) => void;
/**
* Handles the start audio muted change event.
*
* @protected
* @param {boolean} newValue - The new value for the
* start audio muted option.
* @returns {void}
*/
_onStartAudioMutedChange(newValue) {
this._updateProfile({
startWithAudioMuted: newValue
});
}
_onStartVideoMutedChange: (boolean) => void;
/**
* Handles the start video muted change event.
*
* @protected
* @param {boolean} newValue - The new value for the
* start video muted option.
* @returns {void}
*/
_onStartVideoMutedChange(newValue) {
this._updateProfile({
startWithVideoMuted: newValue
});
}
_updateProfile: (Object) => void;
/**
* Updates the persisted profile on any change.
*
* @private
* @param {Object} updateObject - The partial update object for the profile.
* @returns {void}
*/
_updateProfile(updateObject: Object) {
this.props.dispatch(updateProfile({
...this.props._profile,
...updateObject
}));
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code AbstractAppSettings}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
*/
export function _mapStateToProps(state: Object) {
const _serverURL = state['features/app'].app._getDefaultURL();
const _profile = getProfile(state);
return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_profile,
_serverURL,
_visible: state['features/app-settings'].visible
};
}

View File

@@ -0,0 +1,258 @@
// @flow
import React from 'react';
import {
Alert,
Modal,
ScrollView,
Switch,
Text,
TextInput,
View
} from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { getSafetyOffset, isIPad } from '../../base/react';
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
import { _mapStateToProps, AbstractAppSettings } from './AbstractAppSettings';
import { hideAppSettings } from '../actions';
import BackButton from './BackButton.native';
import FormRow from './FormRow.native';
import FormSectionHeader from './FormSectionHeader.native';
import { normalizeUserInputURL } from '../functions';
import styles, { HEADER_PADDING } from './styles';
/**
* The native container rendering the app settings page.
*
* @extends AbstractAppSettings
*/
class AppSettings extends AbstractAppSettings {
_urlField: Object;
/**
* Instantiates a new {@code AppSettings} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._getSafetyPadding = this._getSafetyPadding.bind(this);
this._onBlurServerURL = this._onBlurServerURL.bind(this);
this._onRequestClose = this._onRequestClose.bind(this);
this._setURLFieldReference = this._setURLFieldReference.bind(this);
this._showURLAlert = this._showURLAlert.bind(this);
}
/**
* Implements React's {@link Component#render()}, renders the settings page.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _profile, t } = this.props;
// FIXME: presentationStyle is added to workaround orientation issue on
// iOS
return (
<Modal
animationType = 'slide'
onRequestClose = { this._onRequestClose }
presentationStyle = 'overFullScreen'
supportedOrientations = { [
'landscape',
'portrait'
] }
visible = { this.props._visible }>
<View
style = { [
styles.headerContainer,
this._getSafetyPadding()
] } >
<BackButton
onPress = { this._onRequestClose }
style = { styles.settingsBackButton } />
<Text style = { [ styles.text, styles.headerTitle ] } >
{ t('profileModal.header') }
</Text>
</View>
<ScrollView style = { styles.settingsContainer } >
<FormSectionHeader
i18nLabel = 'profileModal.profileSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.displayName' >
<TextInput
onChangeText = { this._onChangeDisplayName }
placeholder = 'John Doe'
value = { _profile.displayName } />
</FormRow>
<FormRow
i18nLabel = 'profileModal.email' >
<TextInput
keyboardType = { 'email-address' }
onChangeText = { this._onChangeEmail }
placeholder = 'email@example.com'
value = { _profile.email } />
</FormRow>
<FormSectionHeader
i18nLabel = 'profileModal.conferenceSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.serverURL' >
<TextInput
autoCapitalize = 'none'
onBlur = { this._onBlurServerURL }
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }
ref = { this._setURLFieldReference }
value = { _profile.serverURL } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.startWithAudioMuted' >
<Switch
onValueChange = {
this._onStartAudioMutedChange
}
value = {
_profile.startWithAudioMuted
} />
</FormRow>
<FormRow
i18nLabel = 'profileModal.startWithVideoMuted' >
<Switch
onValueChange = {
this._onStartVideoMutedChange
}
value = {
_profile.startWithVideoMuted
} />
</FormRow>
</ScrollView>
</Modal>
);
}
_getSafetyPadding: () => Object;
/**
* Calculates header safety padding for mobile devices. See comment in
* functions.js.
*
* @private
* @returns {Object}
*/
_getSafetyPadding() {
if (isIPad() || this.props._aspectRatio === ASPECT_RATIO_NARROW) {
const safeOffset = Math.max(getSafetyOffset(), HEADER_PADDING);
return {
paddingTop: safeOffset
};
}
return undefined;
}
_onBlurServerURL: () => void;
/**
* Handler the server URL lose focus event. Here we validate the server URL
* and update it to the normalized version, or show an error if incorrect.
*
* @private
* @returns {void}
*/
_onBlurServerURL() {
this._processServerURL(false /* hideOnSuccess */);
}
_onChangeDisplayName: (string) => void;
_onChangeEmail: (string) => void;
_onChangeServerURL: (string) => void;
_onStartAudioMutedChange: (boolean) => void;
_onStartVideoMutedChange: (boolean) => void;
_onRequestClose: () => void;
/**
* Processes the server URL. It normalizes it and an error alert is
* displayed in case it's incorrect.
*
* @param {boolean} hideOnSuccess - True if the dialog should be hidden if
* normalization / validation succeeds, false otherwise.
* @private
* @returns {void}
*/
_processServerURL(hideOnSuccess: boolean) {
const { serverURL } = this.props._profile;
const normalizedURL = normalizeUserInputURL(serverURL);
if (normalizedURL === null) {
this._showURLAlert();
} else {
this._onChangeServerURL(normalizedURL);
if (hideOnSuccess) {
this.props.dispatch(hideAppSettings());
}
}
}
/**
* Handles the back button.
* Also invokes normalizeUserInputURL to validate the URL entered
* by the user.
*
* @returns {void}
*/
_onRequestClose() {
this._processServerURL(true /* hideOnSuccess */);
}
_setURLFieldReference: (React$ElementRef<*> | null) => void;
/**
* Stores a reference to the URL field for later use.
*
* @protected
* @param {Object} component - The field component.
* @returns {void}
*/
_setURLFieldReference(component) {
this._urlField = component;
}
_showURLAlert: () => void;
/**
* Shows an alert telling the user that the URL he/she entered was invalid.
*
* @returns {void}
*/
_showURLAlert() {
const { t } = this.props;
Alert.alert(
t('profileModal.alertTitle'),
t('profileModal.alertURLText'),
[
{
onPress: () => this._urlField.focus(),
text: t('profileModal.alertOk')
}
]
);
}
}
export default translate(connect(_mapStateToProps)(AppSettings));

View File

@@ -0,0 +1,50 @@
// @flow
import React, { Component } from 'react';
import { TouchableOpacity } from 'react-native';
import { Icon } from '../../base/font-icons';
import styles from './styles';
/**
* The type of the React {@code Component} props of {@link BackButton}
*/
type Props = {
/**
* The action to be performed when the button is pressed.
*/
onPress: Function,
/**
* An external style object passed to the component.
*/
style: Object
};
/**
* A component rendering a back button.
*/
export default class BackButton extends Component<Props> {
/**
* Implements React's {@link Component#render()}, renders the button.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<TouchableOpacity
accessibilityLabel = { 'Back' }
onPress = { this.props.onPress }>
<Icon
name = { 'arrow_back' }
style = { [
styles.backIcon,
this.props.style
] } />
</TouchableOpacity>
);
}
}

View File

@@ -0,0 +1,169 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { getSafetyOffset } from '../../base/react';
import { ASPECT_RATIO_WIDE } from '../../base/responsive-ui';
import styles, { ANDROID_UNDERLINE_COLOR, CONTAINER_PADDING } from './styles';
/**
* The type of the React {@code Component} props of {@link FormRow}
*/
type Props = {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol,
/**
*
*/
children: Object,
/**
* Prop to decide if a row separator is to be rendered.
*/
fieldSeparator: boolean,
/**
* The i18n key of the text label of the form field.
*/
i18nLabel: string,
/**
* Invoked to obtain translated strings.
*/
t: Function
}
/**
* Implements a React {@code Component} which renders a standardized row on a
* form. The component should have exactly one child component.
*/
class FormRow extends Component<Props> {
/**
* Initializes a new {@code FormRow} instance.
*
* @param {Object} props - Component properties.
*/
constructor(props) {
super(props);
React.Children.only(this.props.children);
this._getDefaultFieldProps = this._getDefaultFieldProps.bind(this);
this._getRowStyle = this._getRowStyle.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @override
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
// Some field types need additional props to look good and standardized
// on a form.
const newChild = React.cloneElement(
this.props.children,
this._getDefaultFieldProps(this.props.children)
);
return (
<View
style = { this._getRowStyle() } >
<View style = { styles.fieldLabelContainer } >
<Text style = { styles.text } >
{ t(this.props.i18nLabel) }
</Text>
</View>
<View style = { styles.fieldValueContainer } >
{ newChild }
</View>
</View>
);
}
_getDefaultFieldProps: (field: Component<*, *>) => Object;
/**
* Assembles the default props to the field child component of this form
* row.
*
* Currently tested/supported field types:
* - TextInput
* - Switch (needs no addition props ATM).
*
* @private
* @param {Object} field - The field (child) component.
* @returns {Object}
*/
_getDefaultFieldProps(field: Object) {
if (field && field.type) {
switch (field.type.displayName) {
case 'TextInput':
return {
style: styles.textInputField,
underlineColorAndroid: ANDROID_UNDERLINE_COLOR
};
}
}
return {};
}
_getRowStyle: () => Array<Object>;
/**
* Assembles the row style array based on the row's props. For padding, see
* comment in functions.js.
*
* @private
* @returns {Array<Object>}
*/
_getRowStyle() {
const rowStyle = [
styles.fieldContainer
];
if (this.props.fieldSeparator) {
rowStyle.push(styles.fieldSeparator);
}
if (this.props._aspectRatio === ASPECT_RATIO_WIDE) {
const safeOffset = Math.max(
getSafetyOffset() - CONTAINER_PADDING, 0
);
rowStyle.push({
marginLeft: safeOffset,
marginRight: safeOffset
});
}
return rowStyle;
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code FormRow}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
*/
export function _mapStateToProps(state: Object) {
return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}
export default translate(connect(_mapStateToProps)(FormRow));

View File

@@ -0,0 +1,117 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { getSafetyOffset } from '../../base/react';
import { ASPECT_RATIO_WIDE } from '../../base/responsive-ui';
import styles, { CONTAINER_PADDING } from './styles';
/**
* The type of the React {@code Component} props of {@link FormSectionHeader}
*/
type Props = {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol,
/**
* The i18n key of the text label of the section.
*/
i18nLabel: string,
/**
* An external style object passed to the component.
*/
style: Object,
/**
* Invoked to obtain translated strings.
*/
t: Function
}
/**
* Implements a React {@code Component} which renders a section header on a
* form. This calculates the available safe view as well.
*/
class FormSectionHeader extends Component<Props> {
/**
* Initializes a new {@code FormSectionHeader} instance.
*
* @param {Object} props - Component properties.
*/
constructor(props) {
super(props);
this._getSafetyMargin = this._getSafetyMargin.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @override
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<View
style = { [
styles.formSectionTitle,
this.props.style,
this._getSafetyMargin()
] } >
<Text>
{ t(this.props.i18nLabel) }
</Text>
</View>
);
}
_getSafetyMargin: () => Object;
/**
* Calculates the safety margin for this header. See comment in
* functions.js.
*
* @private
* @returns {Object}
*/
_getSafetyMargin() {
if (this.props._aspectRatio === ASPECT_RATIO_WIDE) {
const safeOffset
= Math.max(getSafetyOffset() - CONTAINER_PADDING, 0);
return {
marginLeft: safeOffset,
marginRight: safeOffset
};
}
return undefined;
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code FormSectionHeader}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
*/
export function _mapStateToProps(state: Object) {
return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}
export default translate(connect(_mapStateToProps)(FormSectionHeader));

View File

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

View File

@@ -0,0 +1,133 @@
import {
BoxModel,
ColorPalette,
createStyleSheet
} from '../../base/styles';
export const ANDROID_UNDERLINE_COLOR = 'transparent';
export const CONTAINER_PADDING = 2 * BoxModel.padding;
export const HEADER_COLOR = ColorPalette.blue;
export const HEADER_PADDING = BoxModel.padding;
const TEXT_SIZE = 17;
/**
* The styles of the React {@code Components} of the feature
* {@code app-settings}.
*/
export default createStyleSheet({
/**
* The back button style.
*/
backIcon: {
alignSelf: 'center',
fontSize: 26,
padding: 8,
paddingRight: 22
},
/**
* Standardized style for a field container {@code View}.
*/
fieldContainer: {
alignItems: 'center',
flexDirection: 'row',
minHeight: 65
},
/**
* Standard container for a {@code View} containing a field label.
*/
fieldLabelContainer: {
alignItems: 'center',
flexDirection: 'row',
marginRight: 5
},
/**
* Field container style for all but last row {@code View}.
*/
fieldSeparator: {
borderBottomWidth: 1,
borderColor: 'rgba(0, 0, 0, 0.1)'
},
/**
* Style for the {@code View} containing each
* field values (the actual field).
*/
fieldValueContainer: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end'
},
formSectionTitle: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
marginTop: 5,
padding: 5
},
/**
* Page header {@code View}.
*/
headerContainer: {
alignItems: 'center',
backgroundColor: HEADER_COLOR,
flexDirection: 'row',
justifyContent: 'flex-start',
padding: HEADER_PADDING
},
/**
* The title {@code Text} of the header.
*/
headerTitle: {
color: ColorPalette.white,
fontSize: 22
},
/**
* Style of the ScrollView to be able to scroll the content.
*/
scrollView: {
flex: 1
},
/**
* The back button style on the settings screen.
*/
settingsBackButton: {
color: ColorPalette.white
},
/**
* The top level container {@code View}.
*/
settingsContainer: {
backgroundColor: ColorPalette.white,
flex: 1,
flexDirection: 'column',
margin: 0,
padding: CONTAINER_PADDING,
paddingTop: 0
},
/**
* Global {@code Text} color for the page.
*/
text: {
color: ColorPalette.black,
fontSize: TEXT_SIZE
},
/**
* Standard text input field style.
*/
textInputField: {
flex: 1,
fontSize: TEXT_SIZE,
textAlign: 'right'
}
});

View File

@@ -0,0 +1,37 @@
// @flow
import { parseStandardURIString } from '../base/util';
/**
* Normalizes a URL entered by the user.
* FIXME: Consider adding this to base/util/uri.
*
* @param {string} url - The URL to validate.
* @returns {string|null} - The normalized URL, or null if the URL is invalid.
*/
export function normalizeUserInputURL(url: string) {
/* eslint-disable no-param-reassign */
if (url) {
url = url.replace(/\s/g, '').toLowerCase();
const urlRegExp = new RegExp('^(\\w+://)?(.+)$');
const urlComponents = urlRegExp.exec(url);
if (!urlComponents[1] || !urlComponents[1].startsWith('http')) {
url = `https://${urlComponents[2]}`;
}
const parsedURI
= parseStandardURIString(url);
if (!parsedURI.host) {
return null;
}
return parsedURI.toString();
}
return url;
/* eslint-enable no-param-reassign */
}

View File

@@ -0,0 +1,4 @@
export * from './actions';
export * from './components';
import './reducer';

View File

@@ -0,0 +1,37 @@
// @flow
import { SET_ROOM } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { hideAppSettings } from './actions';
/**
* The Redux middleware to trigger settings screen show or hide
* when necessary.
*
* @param {Store} store - The Redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_ROOM:
return _closeAppSettings(store, next, action);
}
return next(action);
});
/**
* Hides the settings screen.
*
* @param {Store} store - The redux store.
* @param {Dispatch} next - The redux dispatch function.
* @param {Action} action - The redux action.
* @private
* @returns {Object} The new state.
*/
function _closeAppSettings({ dispatch }, next, action) {
dispatch(hideAppSettings());
return next(action);
}

View File

@@ -0,0 +1,31 @@
// @flow
import {
HIDE_APP_SETTINGS,
SHOW_APP_SETTINGS
} from './actionTypes';
import { ReducerRegistry } from '../base/redux';
const DEFAULT_STATE = {
visible: false
};
ReducerRegistry.register(
'features/app-settings', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case HIDE_APP_SETTINGS:
return {
...state,
visible: false
};
case SHOW_APP_SETTINGS:
return {
...state,
visible: true
};
}
return state;
});

View File

@@ -4,6 +4,7 @@ import { setRoom } from '../base/conference';
import { configWillLoad, loadConfigError, setConfig } from '../base/config';
import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { getProfile } from '../base/profile';
import { parseURIString } from '../base/util';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
@@ -82,7 +83,11 @@ function _appNavigateToMandatoryLocation(
});
}
return promise.then(() => dispatch(setConfig(config)));
const profile = getProfile(getState());
return promise.then(() => dispatch(setConfig(
_mergeConfigWithProfile(config, profile)
)));
}
}
@@ -245,3 +250,23 @@ function _loadConfig({ contextRoot, host, protocol, room }) {
throw error;
});
}
/**
* Merges the downloaded config with the current profile values. The profile
* values are named the same way as the config values in the config.js so
* a clean merge is possible.
*
* @param {Object|undefined} config - The downloaded config.
* @param {Object} profile - The persisted profile.
* @returns {Object}
*/
function _mergeConfigWithProfile(config, profile) {
if (!config) {
return;
}
return {
...config,
...profile
};
}

View File

@@ -12,8 +12,10 @@ import {
localParticipantJoined,
localParticipantLeft
} from '../../base/participants';
import { getProfile } from '../../base/profile';
import { Fragment, RouteRegistry } from '../../base/react';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import { PersistenceRegistry } from '../../base/storage';
import { toURLString } from '../../base/util';
import { OverlayContainer } from '../../overlay';
import { BlankPage } from '../../welcome';
@@ -72,6 +74,7 @@ export class AbstractApp extends Component {
super(props);
this.state = {
/**
* The Route rendered by this {@code AbstractApp}.
*
@@ -79,13 +82,37 @@ export class AbstractApp extends Component {
*/
route: undefined,
/**
* The state of the »possible« async initialization of
* the {@code AbstractApp}.
*/
appAsyncInitialized: false,
/**
* The redux store used by this {@code AbstractApp}.
*
* @type {Store}
*/
store: this._maybeCreateStore(props)
store: undefined
};
/**
* Make the mobile {@code AbstractApp} wait until the
* {@code AsyncStorage} implementation of {@code Storage} initializes
* fully.
*
* @private
* @see {@link #_initStorage}
* @type {Promise}
*/
this._init
= this._initStorage()
.catch(() => { /* AbstractApp should always initialize! */ })
.then(() =>
this.setState({
route: undefined,
store: this._maybeCreateStore(props)
}));
}
/**
@@ -95,29 +122,50 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillMount() {
const { dispatch } = this._getStore();
this._init.then(() => {
const { dispatch } = this._getStore();
dispatch(appWillMount(this));
dispatch(appWillMount(this));
// FIXME I believe it makes more sense for a middleware to dispatch
// localParticipantJoined on APP_WILL_MOUNT because the order of actions
// is important, not the call site. Moreover, we've got localParticipant
// business logic in the React Component (i.e. UI) AbstractApp now.
let localParticipant;
// FIXME I believe it makes more sense for a middleware to dispatch
// localParticipantJoined on APP_WILL_MOUNT because the order of
// actions is important, not the call site. Moreover, we've got
// localParticipant business logic in the React Component
// (i.e. UI) AbstractApp now.
let localParticipant = {};
if (typeof APP === 'object') {
localParticipant = {
avatarID: APP.settings.getAvatarId(),
avatarURL: APP.settings.getAvatarUrl(),
email: APP.settings.getEmail(),
name: APP.settings.getDisplayName()
};
}
dispatch(localParticipantJoined(localParticipant));
if (typeof APP === 'object') {
localParticipant = {
avatarID: APP.settings.getAvatarId(),
avatarURL: APP.settings.getAvatarUrl(),
email: APP.settings.getEmail(),
name: APP.settings.getDisplayName()
};
}
// If a URL was explicitly specified to this React Component, then open
// it; otherwise, use a default.
this._openURL(toURLString(this.props.url) || this._getDefaultURL());
// Profile is the new React compatible settings.
const profile = getProfile(this._getStore().getState());
if (profile) {
localParticipant.email
= profile.email || localParticipant.email;
localParticipant.name
= profile.displayName || localParticipant.name;
}
// We set the initialized state here and not in the contructor to
// make sure that {@code componentWillMount} gets invoked before
// the app tries to render the actual app content.
this.setState({
appAsyncInitialized: true
});
dispatch(localParticipantJoined(localParticipant));
// If a URL was explicitly specified to this React Component,
// then open it; otherwise, use a default.
this._openURL(toURLString(this.props.url) || this._getDefaultURL());
});
}
/**
@@ -130,32 +178,36 @@ export class AbstractApp extends Component {
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
// The consumer of this AbstractApp did not provide a redux store.
if (typeof nextProps.store === 'undefined'
const { props } = this;
// The consumer of this AbstractApp did provide a redux store
// before. Which means that the consumer changed their mind. In
// such a case this instance should create its own internal
// redux store. If the consumer did not provide a redux store
// before, then this instance is using its own internal redux
// store already.
&& typeof this.props.store !== 'undefined') {
this.setState({
store: this._maybeCreateStore(nextProps)
});
}
this._init.then(() => {
// The consumer of this AbstractApp did not provide a redux store.
if (typeof nextProps.store === 'undefined'
// Deal with URL changes.
let { url } = nextProps;
// The consumer of this AbstractApp did provide a redux
// store before. Which means that the consumer changed
// their mind. In such a case this instance should create
// its own internal redux store. If the consumer did not
// provide a redux store before, then this instance is
// using its own internal redux store already.
&& typeof props.store !== 'undefined') {
this.setState({
store: this._maybeCreateStore(nextProps)
});
}
url = toURLString(url);
if (toURLString(this.props.url) !== url
// Deal with URL changes.
let { url } = nextProps;
// XXX Refer to the implementation of loadURLObject: in
// ios/sdk/src/JitsiMeetView.m for further information.
|| this.props.timestamp !== nextProps.timestamp) {
this._openURL(url || this._getDefaultURL());
}
url = toURLString(url);
if (toURLString(props.url) !== url
// XXX Refer to the implementation of loadURLObject: in
// ios/sdk/src/JitsiMeetView.m for further information.
|| props.timestamp !== nextProps.timestamp) {
this._openURL(url || this._getDefaultURL());
}
});
}
/**
@@ -188,6 +240,24 @@ export class AbstractApp extends Component {
return undefined;
}
/**
* Delays this {@code AbstractApp}'s startup until the {@code Storage}
* implementation of {@code localStorage} initializes. While the
* initialization is instantaneous on Web (with Web Storage API), it is
* asynchronous on mobile/react-native.
*
* @private
* @returns {Promise}
*/
_initStorage() {
const localStorageInitializing = window.localStorage._initializing;
return (
typeof localStorageInitializing === 'undefined'
? Promise.resolve()
: localStorageInitializing);
}
/**
* Implements React's {@link Component#render()}.
*
@@ -195,10 +265,10 @@ export class AbstractApp extends Component {
* @returns {ReactElement}
*/
render() {
const { route } = this.state;
const { appAsyncInitialized, route } = this.state;
const component = (route && route.component) || BlankPage;
if (component) {
if (appAsyncInitialized && component) {
return (
<I18nextProvider i18n = { i18next }>
<Provider store = { this._getStore() }>
@@ -281,7 +351,11 @@ export class AbstractApp extends Component {
middleware = compose(middleware, devToolsExtension());
}
return createStore(reducer, middleware);
return (
createStore(
reducer,
PersistenceRegistry.getPersistedState(),
middleware));
}
/**
@@ -305,7 +379,10 @@ export class AbstractApp extends Component {
}
}
return this.props.defaultURL || DEFAULT_URL;
return (
this.props.defaultURL
|| getProfile(this._getStore().getState()).serverURL
|| DEFAULT_URL);
}
/**

View File

@@ -6,8 +6,11 @@ import { Linking } from 'react-native';
import '../../analytics';
import '../../authentication';
import { AspectRatioDetector } from '../../base/aspect-ratio';
import { Platform } from '../../base/react';
import {
AspectRatioDetector,
ReducedUIDetector
} from '../../base/responsive-ui';
import '../../mobile/audio-mode';
import '../../mobile/background';
import '../../mobile/callkit';
@@ -97,7 +100,9 @@ export class App extends AbstractApp {
_createElement(component, props) {
return (
<AspectRatioDetector>
{ super._createElement(component, props) }
<ReducedUIDetector>
{ super._createElement(component, props) }
</ReducedUIDetector>
</AspectRatioDetector>
);
}

View File

@@ -1,6 +1,7 @@
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import '../../base/responsive-ui';
import { getLocationContextRoot } from '../../base/util';
import '../../room-lock';

View File

@@ -1,10 +0,0 @@
/**
* The type of (redux) action which sets the aspect ratio of the app's user
* interface.
*
* {
* type: SET_ASPECT_RATIO,
* aspectRatio: Symbol
* }
*/
export const SET_ASPECT_RATIO = Symbol('SET_ASPECT_RATIO');

View File

@@ -1,22 +0,0 @@
// @flow
import { SET_ASPECT_RATIO } from './actionTypes';
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
/**
* Sets the aspect ratio of the app's user interface based on specific width and
* height.
*
* @param {number} width - The width of the app's user interface.
* @param {number} height - The height of the app's user interface.
* @returns {{
* type: SET_ASPECT_RATIO,
* aspectRatio: Symbol
* }}
*/
export function setAspectRatio(width: number, height: number): Object {
return {
type: SET_ASPECT_RATIO,
aspectRatio: width < height ? ASPECT_RATIO_NARROW : ASPECT_RATIO_WIDE
};
}

View File

@@ -1,2 +0,0 @@
export * from './AspectRatioAware';
export { default as AspectRatioDetector } from './AspectRatioDetector';

View File

@@ -3,9 +3,8 @@
import UIEvents from '../../../../service/UI/UIEvents';
import {
START_MUTED_SERVER_AUDIO_,
START_MUTED_SERVER_VIDEO_,
sendAnalyticsEvent
createStartMutedConfigurationEvent,
sendAnalytics
} from '../../analytics';
import { getName } from '../../app';
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
@@ -90,12 +89,8 @@ function _addConferenceListeners(conference, dispatch) {
const audioMuted = Boolean(conference.startAudioMuted);
const videoMuted = Boolean(conference.startVideoMuted);
sendAnalyticsEvent(
`${START_MUTED_SERVER_AUDIO_}.${
audioMuted ? 'muted' : 'unmuted'}`);
sendAnalyticsEvent(
`${START_MUTED_SERVER_VIDEO_}.${
videoMuted ? 'muted' : 'unmuted'}`);
sendAnalytics(createStartMutedConfigurationEvent(
'remote', audioMuted, videoMuted));
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${
videoMuted ? 'video' : ''}`);

View File

@@ -3,12 +3,11 @@
import UIEvents from '../../../../service/UI/UIEvents';
import {
_LOCAL,
_REMOTE,
AUDIO_ONLY_DISABLED,
PINNED_,
UNPINNED_,
sendAnalyticsEvent
ACTION_PINNED,
ACTION_UNPINNED,
createAudioOnlyDisableEvent,
createPinnedEvent,
sendAnalytics
} from '../../analytics';
import { CONNECTION_ESTABLISHED } from '../connection';
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
@@ -131,7 +130,7 @@ function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
const result = next(action);
if (getState()['features/base/conference'].audioOnly) {
sendAnalyticsEvent(AUDIO_ONLY_DISABLED);
sendAnalytics(createAudioOnlyDisableEvent());
logger.log('Audio only disabled');
dispatch(setAudioOnly(false));
}
@@ -186,26 +185,29 @@ function _conferenceJoined(store, next, action) {
function _pinParticipant(store, next, action) {
const state = store.getState();
const { conference } = state['features/base/conference'];
if (!conference) {
return next(action);
}
const participants = state['features/base/participants'];
const id = action.participant.id;
const participantById = getParticipantById(participants, id);
let pin;
if (typeof APP !== 'undefined') {
const pinnedParticipant = getPinnedParticipant(participants);
const actionName = action.participant.id ? PINNED_ : UNPINNED_;
let videoType;
const actionName = id ? ACTION_PINNED : ACTION_UNPINNED;
const local
= (participantById && participantById.local)
|| (!id && pinnedParticipant && pinnedParticipant.local);
if ((participantById && participantById.local)
|| (!id && pinnedParticipant && pinnedParticipant.local)) {
videoType = _LOCAL;
} else {
videoType = _REMOTE;
}
sendAnalyticsEvent(
`${actionName}.${videoType}`,
{ value: conference.getParticipantCount() });
sendAnalytics(createPinnedEvent(
actionName,
local ? 'local' : id,
{
local,
'participant_count': conference.getParticipantCount()
}));
}
// The following condition prevents signaling to pin local participant and
@@ -215,6 +217,8 @@ function _pinParticipant(store, next, action) {
// - If we don't have an ID (i.e. no participant identified by an ID), we
// check for local participant. If she's currently pinned, then this
// action will unpin her and that's why we won't signal here too.
let pin;
if (participantById) {
pin = !participantById.local && !participantById.isBot;
} else {
@@ -223,7 +227,6 @@ function _pinParticipant(store, next, action) {
pin = !localParticipant || !localParticipant.pinned;
}
if (pin) {
try {
conference.pinParticipant(id);
} catch (err) {
@@ -317,9 +320,11 @@ function _setLastN(store, next, action) {
function _setReceiveVideoQuality({ dispatch, getState }, next, action) {
const { audioOnly, conference } = getState()['features/base/conference'];
conference.setReceiverVideoConstraint(action.receiveVideoQuality);
if (audioOnly) {
dispatch(toggleAudioOnly());
if (conference) {
conference.setReceiverVideoConstraint(action.receiveVideoQuality);
if (audioOnly) {
dispatch(toggleAudioOnly());
}
}
return next(action);

View File

@@ -177,6 +177,7 @@ export function connectionEstablished(connection: Object) {
* @param {string} [message] - Error message.
* @param {Object} [credentials] - The invalid credentials that failed
* the authentication.
* @param {Object} [details] - The details about the connection failed event.
* @public
* @returns {{
* type: CONNECTION_FAILED,
@@ -188,7 +189,8 @@ export function connectionFailed(
connection: Object,
error: string,
message: ?string,
credentials: ?Object) {
credentials: ?Object,
details: ?Object) {
return {
type: CONNECTION_FAILED,
connection,
@@ -201,7 +203,8 @@ export function connectionFailed(
? credentials
: undefined,
message,
name: error
name: error,
details
}
};
}

View File

@@ -1,6 +1,60 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M854 470v84h-520l238 240-60 60-342-342 342-342 60 60-238 240h520z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"arrow_back"
],
"defaultCode": 58820,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "arrow_back",
"id": 45,
"order": 924,
"prevSize": 24,
"code": 58820,
"name": "arrow_back"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 45
},
{
"icon": {
"paths": [
"M658 316l-196 196 196 196-60 60-256-256 256-256z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"navigate_before"
],
"defaultCode": 58376,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "chevron_left, navigate_before",
"id": 152,
"order": 923,
"prevSize": 24,
"code": 58376,
"name": "navigate_before"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 152
},
{
"icon": {
"paths": [
@@ -24,9 +78,9 @@
"code": 59403,
"name": "public"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 605
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
{
"icon": {
@@ -53,7 +107,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 1
},
{
"icon": {
@@ -80,7 +134,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 1
"iconIdx": 2
},
{
"icon": {
@@ -107,7 +161,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 2
"iconIdx": 3
},
{
"icon": {
@@ -134,7 +188,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 3
"iconIdx": 4
},
{
"icon": {
@@ -161,7 +215,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 5
},
{
"icon": {
@@ -188,7 +242,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 5
"iconIdx": 6
},
{
"icon": {
@@ -217,7 +271,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 6
"iconIdx": 7
},
{
"icon": {
@@ -244,7 +298,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 7
"iconIdx": 8
},
{
"icon": {
@@ -271,7 +325,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 8
"iconIdx": 9
},
{
"icon": {
@@ -300,7 +354,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 9
"iconIdx": 10
},
{
"icon": {
@@ -329,7 +383,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 10
"iconIdx": 11
},
{
"icon": {
@@ -358,7 +412,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 11
"iconIdx": 12
},
{
"icon": {
@@ -387,7 +441,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 13
},
{
"icon": {
@@ -416,7 +470,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 14
},
{
"icon": {
@@ -442,7 +496,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 14
"iconIdx": 15
},
{
"icon": {
@@ -468,7 +522,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 15
"iconIdx": 16
},
{
"icon": {
@@ -494,7 +548,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 16
"iconIdx": 17
},
{
"icon": {
@@ -520,7 +574,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 17
"iconIdx": 18
},
{
"icon": {
@@ -546,7 +600,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 18
"iconIdx": 19
},
{
"icon": {
@@ -572,7 +626,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 19
"iconIdx": 20
},
{
"icon": {
@@ -598,7 +652,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 20
"iconIdx": 21
},
{
"icon": {
@@ -616,7 +670,7 @@
"attrs": [],
"properties": {
"id": 10,
"order": 900,
"order": 922,
"ligatures": "expand_less",
"prevSize": 32,
"code": 59679,
@@ -624,7 +678,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 21
"iconIdx": 22
},
{
"icon": {
@@ -650,7 +704,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 23
},
{
"icon": {
@@ -676,7 +730,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 23
"iconIdx": 24
},
{
"icon": {
@@ -702,7 +756,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 24
"iconIdx": 25
},
{
"icon": {
@@ -728,7 +782,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 25
"iconIdx": 26
},
{
"icon": {
@@ -754,7 +808,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 26
"iconIdx": 27
},
{
"icon": {
@@ -780,7 +834,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 27
"iconIdx": 28
},
{
"icon": {
@@ -806,7 +860,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 28
"iconIdx": 29
},
{
"icon": {
@@ -832,7 +886,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 29
"iconIdx": 30
},
{
"icon": {
@@ -858,7 +912,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 30
"iconIdx": 31
},
{
"icon": {
@@ -884,7 +938,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 31
"iconIdx": 32
},
{
"icon": {
@@ -910,7 +964,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 32
"iconIdx": 33
},
{
"icon": {
@@ -936,7 +990,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 33
"iconIdx": 34
},
{
"icon": {
@@ -962,7 +1016,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 34
"iconIdx": 35
},
{
"icon": {
@@ -988,7 +1042,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 35
"iconIdx": 36
},
{
"icon": {
@@ -1014,7 +1068,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 37
},
{
"icon": {
@@ -1040,7 +1094,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 38
},
{
"icon": {
@@ -1066,7 +1120,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 39
},
{
"icon": {
@@ -1092,7 +1146,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 40
},
{
"icon": {
@@ -1118,7 +1172,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 41
},
{
"icon": {
@@ -1144,7 +1198,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 41
"iconIdx": 42
},
{
"icon": {
@@ -1170,7 +1224,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 42
"iconIdx": 43
},
{
"icon": {
@@ -1199,7 +1253,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 43
"iconIdx": 44
},
{
"icon": {
@@ -1229,7 +1283,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 44
"iconIdx": 45
},
{
"icon": {
@@ -1259,7 +1313,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 45
"iconIdx": 46
},
{
"icon": {
@@ -1285,7 +1339,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 46
"iconIdx": 47
},
{
"icon": {
@@ -1311,7 +1365,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 47
"iconIdx": 48
},
{
"icon": {
@@ -1337,7 +1391,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 48
"iconIdx": 49
}
],
"height": 1024,

View File

@@ -0,0 +1,129 @@
// @flow
import i18next from 'i18next';
/**
* The builtin languages.
*/
const _LANGUAGES = {
// Bulgarian
'bg': {
languages: require('../../../../lang/languages-bg'),
main: require('../../../../lang/main-bg')
},
// German
'de': {
languages: require('../../../../lang/languages-de'),
main: require('../../../../lang/main-de')
},
// Esperanto
'eo': {
languages: require('../../../../lang/languages-eo'),
main: require('../../../../lang/main-eo')
},
// Spanish
'es': {
languages: require('../../../../lang/languages-es'),
main: require('../../../../lang/main-es')
},
// French
'fr': {
languages: require('../../../../lang/languages-fr'),
main: require('../../../../lang/main-fr')
},
// Armenian
'hy': {
languages: require('../../../../lang/languages-hy'),
main: require('../../../../lang/main-hy')
},
// Italian
'it': {
languages: require('../../../../lang/languages-it'),
main: require('../../../../lang/main-it')
},
// Norwegian Bokmal
'nb': {
languages: require('../../../../lang/languages-nb'),
main: require('../../../../lang/main-nb')
},
// Occitan
'oc': {
languages: require('../../../../lang/languages-oc'),
main: require('../../../../lang/main-oc')
},
// Polish
'pl': {
languages: require('../../../../lang/languages-pl'),
main: require('../../../../lang/main-pl')
},
// Portuguese (Brazil)
'ptBR': {
languages: require('../../../../lang/languages-ptBR'),
main: require('../../../../lang/main-ptBR')
},
// Russian
'ru': {
languages: require('../../../../lang/languages-ru'),
main: require('../../../../lang/main-ru')
},
// Slovak
'sk': {
languages: require('../../../../lang/languages-sk'),
main: require('../../../../lang/main-sk')
},
// Slovenian
'sl': {
languages: require('../../../../lang/languages-sl'),
main: require('../../../../lang/main-sl')
},
// Swedish
'sv': {
languages: require('../../../../lang/languages-sv'),
main: require('../../../../lang/main-sv')
},
// Turkish
'tr': {
languages: require('../../../../lang/languages-tr'),
main: require('../../../../lang/main-tr')
},
// Chinese (China)
'zhCN': {
languages: require('../../../../lang/languages-zhCN'),
main: require('../../../../lang/main-zhCN')
}
};
// Register all builtin languages with the i18n library.
for (const name in _LANGUAGES) { // eslint-disable-line guard-for-in
const { languages, main } = _LANGUAGES[name];
i18next.addResourceBundle(
name,
'languages',
languages,
/* deep */ true,
/* overwrite */ true);
i18next.addResourceBundle(
name,
'main',
main,
/* deep */ true,
/* overwrite */ true);
}

View File

@@ -1,4 +1,4 @@
/* @flow */
// @flow
import i18next from 'i18next';
import I18nextXHRBackend from 'i18next-xhr-backend';
@@ -58,7 +58,7 @@ const options = {
};
i18next
.use(I18nextXHRBackend)
.use(navigator.product === 'ReactNative' ? {} : I18nextXHRBackend)
.use(languageDetector)
.use({
name: 'resolveAppName',
@@ -68,17 +68,23 @@ i18next
.init(options);
// Add default language which is preloaded from the source code.
i18next.addResourceBundle(
DEFAULT_LANGUAGE,
'main',
MAIN_RESOURCES,
/* deep */ true,
/* overwrite */ true);
i18next.addResourceBundle(
DEFAULT_LANGUAGE,
'languages',
LANGUAGES_RESOURCES,
/* deep */ true,
/* overwrite */ true);
i18next.addResourceBundle(
DEFAULT_LANGUAGE,
'main',
MAIN_RESOURCES,
/* deep */ true,
/* overwrite */ true);
// Add builtin languages.
// XXX: Note we are using require here, because we want the side-effects of the
// import, but imports can only be placed at the top, and it would be too early,
// since i18next is not yet initialized at that point.
require('./BuiltinLanguages');
export default i18next;

View File

@@ -3,9 +3,11 @@
import JitsiMeetJS from './_';
export { JitsiMeetJS as default };
// XXX Re-export the types exported by JitsiMeetJS in order to prevent undefined
// imported JitsiMeetJS. It may be caused by import cycles but I have not
// confirmed the theory.
// XXX Re-export the properties exported by JitsiMeetJS in order to prevent
// undefined imported JitsiMeetJS. It may be caused by import cycles but I have
// not confirmed the theory.
export const analytics = JitsiMeetJS.analytics;
export const browser = JitsiMeetJS.util.browser;
export const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
export const JitsiConferenceEvents = JitsiMeetJS.events.conference;
export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
@@ -20,8 +22,6 @@ export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
export const JitsiTrackErrors = JitsiMeetJS.errors.track;
export const JitsiTrackEvents = JitsiMeetJS.events.track;
export const analytics = JitsiMeetJS.analytics;
export * from './actions';
export * from './actionTypes';
export * from './constants';

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