Compare commits

...

86 Commits

Author SHA1 Message Date
yanas
53e784094a Merge pull request #1308 from jitsi/ss_resize_remote
Fix for the size of remote desktop sharing videos
2017-02-08 15:31:06 -06:00
hristoterezov
0e92e48376 fix(ss): resize for remote videos 2017-02-08 14:58:42 -06:00
Lyubomir Marinov
4c9943ac38 Fix an image path on the mobile landing page 2017-02-08 12:41:51 -06:00
Дамян Минков
4bd0fd145d Merge pull request #1293 from jitsi/prosody_plugin_muc_all_owners
prosody plugin to make all users owners/moderators
2017-02-08 13:17:42 +02:00
Lyubomir Marinov
01ae82eb28 No Temasys alert on mobile Web 2017-02-07 21:54:08 -06:00
Lyubomir Marinov
e21eae0933 Prepare for webpack 2 2017-02-07 15:44:37 -06:00
Lyubomir Marinov
2f047c50dc Revert "No Temasys alert on mobile Web"
This reverts commit b09e86352f.
2017-02-07 15:21:34 -06:00
yanas
e397e1a80c Merge pull request #1303 from jitsi/no-temasys-alert-on-mobile
No Temasys alert on mobile (Web)
2017-02-07 15:11:09 -06:00
Lyubomir Marinov
b09e86352f No Temasys alert on mobile Web 2017-02-07 15:08:38 -06:00
Lyubomir Marinov
8687b69167 Consistency
Be consistent about formatting within 1 and the same file.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
6c5468d904 Simplify the source code
If half the file is written in ES6, it is easier to read if the rest of
the file is in ES6 as well. If ES6 is used, then const is better than
let. If source code is shorter yet as readable as the long version, then
prefer the short version.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
d6b0f8d4c5 Use functions, do not re-implement them
We have the functions reload and redirect which modify window.location.
Use them and do not directly modify window.location so that we have
fewer places of direct window.location modifications and it is easier to
refactor them.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
a8cd4ff12c 1, not 2 names for 1 and the same abstraction
window.location calls it reload so util/helpers shouldn't call it
redirect because UI/util/UIUtil has it is own redirect which is the
assign of window.location.
2017-02-07 08:29:40 -06:00
Lyubomir Marinov
8509efc8af Make the Web app aware of its context root 2017-02-07 08:27:23 -06:00
Saúl Ibarra Corretgé
23a0053dad doc: add section about embedding to the README 2017-02-06 17:41:19 -06:00
Saúl Ibarra Corretgé
5849980092 external_api: fix jsdoc 2017-02-06 17:41:19 -06:00
Saúl Ibarra Corretgé
e81fc2b254 doc: fix external API documentation
- use proper punctuation
- fix markdown syntax
- always use syntax highlighting
- document missing commands
- miscellaneous grammar fixes
2017-02-06 17:41:19 -06:00
Lyubomir Marinov
2ad869a036 Comply w/ coding style
- Use 1 name for 1 abstraction. Instead of useFullScreen and enabled use
  fullScreen.
- Comments are correct English sentences so no double spaces between
  senteces, no capitalization of the work On midsentence.
- Write as little source code as possible if readability is preserved.
- Utilize Facebook's Flow.
- The name of a private function must start with _ and the jsdoc should
  state that the function is private.
2017-02-06 15:32:03 -06:00
Saúl Ibarra Corretgé
7a8c84e990 [RN] Implement full screen mode while in a conference
The implementation varies across platforms, with the same goal: allow the app to
use the entire screen real state while in a conference.

On Android we use immersive mode, which  will hide the status and navigation bars.

https://developer.android.com/training/system-ui/immersive.html

On iOS the status bar is hidden, with a slide effect.
2017-02-06 13:51:17 -06:00
Lyubomir Marinov
0de01e93dd react-native 0.41.2 2017-02-06 12:24:38 -06:00
Lyubomir Marinov
6fa93e5b44 file-loader 0.10.0 2017-02-06 12:24:38 -06:00
Lyubomir Marinov
2144ec1e3f eslint 3.15.0 2017-02-06 12:24:38 -06:00
bgrozev
68d2f60ace Merge pull request #1276 from jitsi/respect-disable-rtx
fix: Respect the disableRtx config option.
2017-02-06 11:52:03 -06:00
George Politis
3d671ae71f docs: Documents the disableRtx config option. 2017-02-06 11:19:33 -06:00
Lyubomir Marinov
8ed47f9d99 [flow] Lint with Flow (in addition to JSHint and ESLint) 2017-02-03 16:36:14 -06:00
Lyubomir Marinov
b50f858556 [flow] Expand the coverage of flow-monitored files 2017-02-03 16:36:14 -06:00
Lyubomir Marinov
5de1a74429 [flow] Take advantage of flow-typed 2017-02-03 16:36:14 -06:00
Lyubomir Marinov
2063ad467d flow-typed 2017-02-03 16:36:14 -06:00
Lyubomir Marinov
679acbae16 Use babel-eslint in the whole project 2017-02-03 16:36:14 -06:00
Aaron van Meerten
a5b706a99e Added a prosody plugin for making all users into muc owners in prosody
Included a patch to prosody-trunk which allows owners to kick each other
2017-02-03 11:41:08 -06:00
yanas
542e61357e Adds new combined camera and mic icon 2017-02-02 17:10:02 -06:00
Lyubomir Marinov
3743602c67 [RN] Fix the parsing of the domain out of a URL 2017-02-02 13:40:49 -06:00
Lyubomir Marinov
ee651840bf Fixes related to coding style 2017-02-02 10:54:24 -06:00
damencho
0765c60d77 Moves feedback button as a component.
When callstats is not configured hide the button.
2017-02-02 10:06:17 -06:00
Lyubomir Marinov
7fa17322a1 Consistency in naming and jsdocs 2017-02-02 09:46:09 -06:00
Lyubomir Marinov
cfa3047330 [flow] Type annotations 2017-02-02 09:45:34 -06:00
Lyubomir Marinov
9e033deb7b Remove unnecessary source code 2017-02-02 09:42:14 -06:00
Lyubomir Marinov
f6c914f6f0 [flow] A minimal demonstration of flow in action 2017-02-01 13:38:37 -06:00
Lyubomir Marinov
06ff02c2a5 [flow] Ignore packages in node_modules that cause errors and we do not want to fix 2017-02-01 13:38:37 -06:00
Lyubomir Marinov
63fd263890 flow 0.36.0
The first step towards enabling Facbook's flow in the project. The flow
configuration is pristine as generated by react-native upgrade.
2017-02-01 13:38:37 -06:00
Lyubomir Marinov
94f3d4b279 [RN] Expand domains supported by Universal Links 2017-01-31 22:47:47 -06:00
Lyubomir Marinov
fdc96044ad [RN] App-specific URL scheme 2017-01-31 22:47:47 -06:00
Lyubomir Marinov
91487ffc94 Fix a case of endless recursion 2017-01-31 22:47:47 -06:00
Saúl Ibarra Corretgé
7a57dcc08a doc: add documentation on how to build the mobile apps 2017-01-31 22:32:15 -06:00
Дамян Минков
913a54713d Adds testing repo to readme. 2017-01-31 21:42:45 -06:00
damencho
39a8681e8e Improves safe checks for missing stats. 2017-01-30 17:01:25 -06:00
George Politis
a7015b0d1a fix: Respect the disableRtx config option. 2017-01-30 16:20:23 +00:00
Lyubomir Marinov
5305f23332 Consistent naming of functions
Until we make a decision on access modifier hints and adopt a respective
coding style, consistency is king.
2017-01-28 20:15:10 -06:00
Lyubomir Marinov
acbf3adab7 Simplify: Remove react/features/base/react-native 2017-01-28 20:13:54 -06:00
Lyubomir Marinov
366b2f1374 Simplify: Remove react/features/base/navigator 2017-01-28 19:56:35 -06:00
Lyubomir Marinov
2189ab7ee6 Consistent naming of react-redux's mapStateToProps
Until we make a decision on access modifier hints and adopt a respective
coding style, consistency is king.
2017-01-28 17:34:57 -06:00
Lyubomir Marinov
349c04d8d1 Consistent naming of functions
Until we make a decision on access modifier hints and adopt a respective
coding style, consistency is king.
2017-01-28 17:28:13 -06:00
Lyubomir Marinov
c7c6249ad7 [RN] Fix room name case sensitivity
It turns out that it is not enough to give lib-jitsi-meet the room name
in lower case. BOSH also needs the room name in lower case.
2017-01-28 17:26:09 -06:00
Lyubomir Marinov
5319227a8f Fix jsdocs 2017-01-28 12:11:24 -06:00
Lyubomir Marinov
3aff812ee2 Consistent naming of Component props mapped from the Redux state
Until we make a decision on access modifier hints and adopt a respective
coding style, consistency is king.
2017-01-27 21:36:20 -06:00
Lyubomir Marinov
88eabf23f4 Remove obsolete UnsupportedMobileBrowser functionality
The desired behavior of the button 'Start a conference' / 'Join the
conversation' is to launch the mobile app if installed; otherwise, do
nothing i.e. continue to display UnsupportedMobileBrowser.

Anyway, we may change our minds about allowing the user to continue in a
supported mobile browser so preserve the source code that enables that
but give it more appropriate naming.
2017-01-27 21:29:09 -06:00
Любомир Маринов
a70beaf7b6 Merge pull request #1265 from jitsi/api
api/api.html -> examples/api.html && rm doc/api
2017-01-27 20:11:23 -06:00
Lyubomir Marinov
c91bffa73c Merge branch 'saghul-android-audiomode' 2017-01-26 23:48:42 -06:00
Lyubomir Marinov
ea163dbbba Merge branch 'deduplicate-index-js' 2017-01-26 23:48:09 -06:00
Lyubomir Marinov
18bc99d6b5 Split long methods into multiple shorter ones 2017-01-26 23:35:56 -06:00
Lyubomir Marinov
bab94a207d Remove unnecessary source code 2017-01-26 21:08:50 -06:00
Lyubomir Marinov
ef39baab47 Comply w/ coding style
- Maximum of 80 characters per line.
- Group first and then sort in alphabetical order.
- Fields should begin with a lowercase letter.
2017-01-26 21:07:35 -06:00
Saúl Ibarra Corretgé
2edaaac7bf [RN] Implement AudioMode module on Android
This module chooses the most appropriate audio default based on the specified
mode.
2017-01-26 19:18:10 -06:00
Saúl Ibarra Corretgé
113e50c074 [RN] Bump Andoroid minimum and target SDK versions
Use a minimum SDK version of 19, that is Anroid 4.4 (KitKat) and a target SDK of
23, that is, Android 6.0 (Marshmallow).
2017-01-26 19:18:10 -06:00
Дамян Минков
900a675864 Merge pull request #1269 from jitsi/border-radius-fix
Fixes border radius.
2017-01-26 17:55:01 -06:00
Lyubomir Marinov
49b3b49f3e Remove duplication
The files react/index.native.js and react/index.web.js ended up having
very similar source code related to initializing the Redux store. Remove
the duplication.

Additionally, I always wanted the App React Component to be consumed
without the need to provide a Redux store to it.
2017-01-26 17:24:11 -06:00
yanas
23935d3d39 Fixes border radius. 2017-01-26 17:04:29 -06:00
Дамян Минков
3fd33d0f50 Merge pull request #1262 from jitsi/ui-fixes
Ui fixes
2017-01-26 10:15:39 -06:00
hristoterezov
8c3317b8e9 doc(iframe_api): api/api.html -> examples/api.html && rm doc/api 2017-01-26 10:02:34 -06:00
Lyubomir Marinov
99a9fc054f eslint 3.14.1 2017-01-26 09:39:47 -06:00
Lyubomir Marinov
967dcfc3d2 react-native-keep-awake 2.0.2 2017-01-26 09:39:20 -06:00
Lyubomir Marinov
0051b3b79c Clean up obsolete file references 2017-01-26 07:58:56 -06:00
Lyubomir Marinov
cbcee201f0 Comply w/ coding style 2017-01-26 07:58:46 -06:00
Ilya Daynatovich
1fa4a53a48 Remove rule 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
6a0b92638c Introduce interceptComponent function 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
2e81b8493e Introduce unsupported browser page 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
57ba702dda Clean up routing logic 2017-01-26 07:27:31 -06:00
Lyubomir Marinov
62bafcaf63 Introduce Platform in React
React Native provides a Platform abstraction which React does not
provide.
2017-01-26 07:27:31 -06:00
Lyubomir Marinov
7de5c9c1d2 Comply w/ coding style 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
8248b14555 Integrate Mobile landing in the Redux app 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
23ef0c8d9d justify text on landing page 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
58a4f59fd8 Implement Landing component 2017-01-26 07:27:31 -06:00
Ilya Daynatovich
0c851934fb layout for mobile landing 2017-01-26 07:27:31 -06:00
Emil Ivov
8d58dbee38 Restores api.md to original location 2017-01-25 19:14:30 -06:00
yanas
cd5e84e4ef Fixes network problem message position 2017-01-25 16:54:51 -06:00
yanas
09ba14eb04 Fixes video thumbnail border and large video background color 2017-01-25 16:53:58 -06:00
124 changed files with 3351 additions and 1103 deletions

View File

@@ -3,6 +3,7 @@ build/*
# Third-party source code which we (1) do not want to modify or (2) try to
# modify as little as possible.
flow-typed/*
libs/*
# ESLint will by default ignore its own configuration file. However, there does

View File

@@ -4,7 +4,10 @@ module.exports = {
'commonjs': true,
'es6': true
},
'extends': 'eslint:recommended',
'extends': [
'eslint:recommended',
'plugin:flowtype/recommended'
],
'globals': {
// The globals that (1) are accessed but not defined within many of our
// files, (2) are certainly defined, and (3) we would like to use
@@ -12,12 +15,16 @@ module.exports = {
// files.
'__filename': false
},
'parser': 'babel-eslint',
'parserOptions': {
'ecmaFeatures': {
'experimentalObjectRestSpread': true
},
'sourceType': 'module'
},
'plugins': [
'flowtype'
],
'rules': {
'new-cap': [
'error',

50
.flowconfig Normal file
View File

@@ -0,0 +1,50 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
.*/Libraries/react-native/ReactNative.js
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
.*/node_modules/babel-core/.*
.*/node_modules/bower/.*
.*/node_modules/jsonlint/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/
[options]
module.system=haste
experimental.strict_type_args=true
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-7]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-7]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
unsafe.enable_getters_and_setters=true
[version]
^0.37.0

2
.gitignore vendored
View File

@@ -4,7 +4,6 @@ deploy-local.sh
libs/
all.css
*css.map
unsupported_browser.css
.remote-sync.json
.sync-config.cson
@@ -48,6 +47,7 @@ local.properties
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
#

View File

@@ -1,7 +1,12 @@
# The following do not need to be checked because they do not represent JS
# source code.
build/
debian/
libs/
node_modules/
# The following are checked by ESLint which supersedes JSHint.
flow-typed/
react/
analytics.js
webpack.config.babel.js

View File

@@ -8,7 +8,6 @@ OUTPUT_DIR = .
STYLES_BUNDLE = css/all.bundle.css
STYLES_DESTINATION = css/all.css
STYLES_MAIN = css/main.scss
STYLES_UNSUPPORTED_BROWSER = css/unsupported_browser.scss
WEBPACK = ./node_modules/.bin/webpack
all: update-deps compile deploy clean
@@ -47,7 +46,6 @@ deploy-lib-jitsi-meet:
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) css/unsupported_browser.scss css/unsupported_browser.css ; \
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
rm $(STYLES_BUNDLE)
@@ -59,6 +57,5 @@ source-package:
mkdir -p source_package/jitsi-meet/css && \
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \
cp css/unsupported_browser.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
rm -rf source_package

View File

@@ -1,5 +1,5 @@
Jitsi Meet - Secure, Simple and Scalable Video Conferences
====
# 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.
You can also try it out yourself at https://meet.jit.si .
@@ -16,6 +16,7 @@ For other systems, or if you wish to install all components manually, see the [d
You can download Debian/Ubuntu binaries:
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianStableRepository))
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianTestingRepository))
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
## Building the sources
@@ -34,7 +35,7 @@ To build the Jitsi Meet application, just type
make
```
## Working with the library sources(lib-jitsi-meet).
## Working with the library sources (lib-jitsi-meet)
By default the library is build from its git repository sources. The default dependency path in package.json is :
```json
@@ -81,6 +82,14 @@ npm unlink lib-jitsi-meet
npm install
```
## Embedding in external applications
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
## Mobile app
Jitsi Meet is also available as a React Native application for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
## Discuss
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on Github.

View File

@@ -139,6 +139,7 @@ if (project.hasProperty('JITSI_SIGNING')
}
dependencies {
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')

View File

@@ -3,9 +3,12 @@
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!-- WebRTC -->
<!-- XXX: ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
@@ -13,8 +16,8 @@
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
android:minSdkVersion="19"
android:targetSdkVersion="23" />
<application
android:allowBackup="true"
@@ -35,11 +38,19 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="beta.hipchat.me" android:scheme="https" />
<data android:host="beta.meet.jit.si" android:scheme="https" />
<data android:host="chaos.hipchat.me" android:scheme="https" />
<data android:host="enso.me" android:scheme="https" />
<data android:host="hipchat.me" android:scheme="https" />
<data android:host="meet.jit.si" android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="org.jitsi.meet" />
</intent-filter>
</activity>
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity" />

View File

@@ -16,7 +16,7 @@ public class MainApplication extends Application implements ReactApplication {
* {@inheritDoc}
*/
@Override
protected boolean getUseDeveloperSupport() {
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@@ -29,7 +29,9 @@ public class MainApplication extends Application implements ReactApplication {
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage()
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage()
);
}
};

View File

@@ -0,0 +1,322 @@
package org.jitsi.meet.audiomode;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.HashMap;
import java.util.Map;
/**
* Module implementing a simple API to select the appropriate audio device for a
* conference call.
*
* Audio calls should use <tt>AudioModeModule.AUDIO_CALL</tt>, which uses the
* builtin earpiece, wired headset or bluetooth headset. The builtin earpiece is
* the default audio device.
*
* Video calls should should use <tt>AudioModeModule.VIDEO_CALL</tt>, which uses
* the builtin speaker, earpiece, wired headset or bluetooth headset. The
* builtin speaker is the default audio device.
*
* Before a call has started and after it has ended the
* <tt>AudioModeModule.DEFAULT</tt> mode should be used.
*/
public class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Constants representing the audio mode.
* - DEFAULT: Used before and after every call. It represents the default
* audio routing scheme.
* - AUDIO_CALL: Used for audio only calls. It will use the earpiece by
* default, unless a wired or Bluetooth headset is connected.
* - VIDEO_CALL: Used for video calls. It will use the speaker by default,
* unless a wired or Bluetooth headset is connected.
*/
private static final int DEFAULT = 0;
private static final int AUDIO_CALL = 1;
private static final int VIDEO_CALL = 2;
/**
*
*/
private static final String ACTION_HEADSET_PLUG
= (Build.VERSION.SDK_INT >= 21)
? AudioManager.ACTION_HEADSET_PLUG
: Intent.ACTION_HEADSET_PLUG;
/**
* React Native module name.
*/
private static final String MODULE_NAME = "AudioMode";
/**
* Tag used when logging messages.
*/
static final String TAG = MODULE_NAME;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private final AudioManager audioManager;
/**
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
* old (< M) Android versions.
*/
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
/**
* {@link Handler} for running all operations on the main thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* {@link Runnable} for running update operation on the main thread.
*/
private final Runnable mainThreadRunner
= new Runnable() {
@Override
public void run() {
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
/**
* Audio mode currently in use.
*/
private int mode = -1;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
*
* @param reactContext the {@link ReactApplicationContext} where this module
* is created.
*/
public AudioModeModule(ReactApplicationContext reactContext) {
super(reactContext);
audioManager
= (AudioManager)
reactContext.getSystemService(Context.AUDIO_SERVICE);
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
}
/**
* Gets a mapping with the constants this module is exporting.
*
* @return a {@link Map} mapping the constants to be exported with their
* values.
*/
@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
constants.put("AUDIO_CALL", AUDIO_CALL);
constants.put("DEFAULT", DEFAULT);
constants.put("VIDEO_CALL", VIDEO_CALL);
return constants;
}
/**
* Gets the name for this module, to be used in the React Native bridge.
*
* @return a string with the module name.
*/
@Override
public String getName() {
return MODULE_NAME;
}
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the main thread.
*/
void onAudioDeviceChange() {
mainThreadHandler.post(mainThreadRunner);
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
/**
* Public method to set the current audio mode.
*
* @param mode the desired audio mode.
* @param promise a {@link Promise} which will be resolved if the audio mode
* could be updated successfully, and it will be rejected otherwise.
*/
@ReactMethod
public void setMode(final int mode, final Promise promise) {
if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
promise.reject("setMode", "Invalid audio mode " + mode);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
if (updateAudioRoute(mode)) {
AudioModeModule.this.mode = mode;
promise.resolve(null);
} else {
promise.reject(
"setMode",
"Failed to set the requested audio mode");
}
}
};
mainThreadHandler.post(r);
}
/**
* Setup the audio route change detection mechanism. We use the
* {@link android.media.AudioDeviceCallback} API on Android >= 23 only.
*/
private void setupAudioRouteChangeDetection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setupAudioRouteChangeDetectionM();
} else {
setupAudioRouteChangeDetectionPreM();
}
}
/**
* Audio route change detection mechanism for Android API >= 23.
*/
@TargetApi(Build.VERSION_CODES.M)
private void setupAudioRouteChangeDetectionM() {
android.media.AudioDeviceCallback audioDeviceCallback =
new android.media.AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
Log.d(TAG, "Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
Log.d(TAG, "Audio devices removed");
onAudioDeviceChange();
}
};
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
}
/**
* Audio route change detection mechanism for Android API < 23.
*/
private void setupAudioRouteChangeDetectionPreM() {
Context context = getReactApplicationContext();
// Detect changes in wired headset connections.
IntentFilter wiredHeadSetFilter = new IntentFilter(ACTION_HEADSET_PLUG);
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Wired headset added / removed");
onAudioDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
// Detect Bluetooth device changes.
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(this, context);
}
/**
* Updates the audio route for the given mode.
*
* @param mode the audio mode to be used when computing the audio route.
* @return true if the audio route was updated successfully, false
* otherwise.
*/
private boolean updateAudioRoute(int mode) {
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(null);
audioManager.setSpeakerphoneOn(false);
audioManager.setMicrophoneMute(true);
setBluetoothAudioRoute(false);
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
null,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
}
boolean useSpeaker = (mode == VIDEO_CALL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// On Android >= M we use the AudioDeviceCallback API, so turn on
// Bluetooth SCO from the start.
if (audioManager.isBluetoothScoAvailableOffCall()) {
audioManager.startBluetoothSco();
}
} else {
// On older Android versions we must set the Bluetooth route
// manually. Also disable the speaker in that case.
setBluetoothAudioRoute(
bluetoothHeadsetMonitor.isHeadsetAvailable());
if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
useSpeaker = false;
}
}
// XXX: isWiredHeadsetOn is not deprecated when used just for knowing if
// there is a wired headset connected, regardless of audio being routed
// to it.
audioManager.setSpeakerphoneOn(
useSpeaker
&& !(audioManager.isWiredHeadsetOn()
|| audioManager.isBluetoothScoOn()));
return true;
}
}

View File

@@ -0,0 +1,48 @@
package org.jitsi.meet.audiomode;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Implements {@link ReactPackage} for {@link AudioModeModule}.
*/
public class AudioModePackage implements ReactPackage {
/**
* {@inheritDoc}
*/
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* {@inheritDoc}
*
* @return List of native modules to be exposed by React Native.
*/
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AudioModeModule(reactContext));
return modules;
}
/**
* {@inheritDoc}
*/
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,189 @@
package org.jitsi.meet.audiomode;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
/**
* Helper class to detect and handle Bluetooth device changes. It monitors
* Bluetooth headsets being connected / disconnected and notifies the module
* about device changes when this occurs.
*/
public class BluetoothHeadsetMonitor {
/**
* {@link AudioModeModule} where this monitor reports.
*/
private final AudioModeModule audioModeModule;
/**
* The {@link Context} in which {@link #audioModeModule} executes.
*/
private final Context context;
/**
* Reference to a proxy object which allows us to query connected devices.
*/
private BluetoothHeadset headset;
/**
* Flag indicating if there are any Bluetooth headset devices currently
* available.
*/
private boolean headsetAvailable = false;
/**
* {@link Handler} for running all operations on the main thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* Helper for running Bluetooth operations on the main thread.
*/
private final Runnable updateDevicesRunnable
= new Runnable() {
@Override
public void run() {
headsetAvailable
= (headset != null)
&& !headset.getConnectedDevices().isEmpty();
audioModeModule.onAudioDeviceChange();
}
};
public BluetoothHeadsetMonitor(
AudioModeModule audioModeModule,
Context context) {
this.audioModeModule = audioModeModule;
this.context = context;
AudioManager audioManager
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
return;
}
if (getBluetoothHeadsetProfileProxy()) {
registerBluetoothReceiver();
// Initial detection.
updateDevices();
}
}
private boolean getBluetoothHeadsetProfileProxy() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
return false;
}
// XXX: The profile listener listens for system services of the given
// type being available to the application. That is, if our Bluetooth
// adapter has the "headset" profile.
BluetoothProfile.ServiceListener listener
= new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(
int profile,
BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
headset = (BluetoothHeadset) proxy;
updateDevices();
}
}
@Override
public void onServiceDisconnected(int profile) {
// The logic is the same as the logic of onServiceConnected.
onServiceConnected(profile, /* proxy */ null);
}
};
return
adapter.getProfileProxy(
context,
listener,
BluetoothProfile.HEADSET);
}
/**
* Returns the current headset availability.
*
* @return true if there is a Bluetooth headset connected, false otherwise.
*/
public boolean isHeadsetAvailable() {
return headsetAvailable;
}
private void onBluetoothReceiverReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
// XXX: This action will be fired when a Bluetooth headset is
// connected or disconnected to the system. This is not related to
// audio routing.
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -99);
switch (state) {
case BluetoothHeadset.STATE_CONNECTED:
case BluetoothHeadset.STATE_DISCONNECTED:
Log.d(
AudioModeModule.TAG,
"BT headset connection state changed: " + state);
updateDevices();
break;
}
} else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
// XXX: This action will be fired when the connection established
// with a Bluetooth headset (called a SCO connection) changes state.
// When the SCO connection is active we route audio to it.
int state
= intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
switch (state) {
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
Log.d(
AudioModeModule.TAG,
"BT SCO connection state changed: " + state);
updateDevices();
break;
}
}
}
private void registerBluetoothReceiver() {
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBluetoothReceiverReceive(context, intent);
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
context.registerReceiver(receiver, filter);
}
/**
* Detects if there are new devices connected / disconnected and fires the
* {@link AudioModeModule#onAudioDeviceChange()} callback.
*/
private void updateDevices() {
mainThreadHandler.post(updateDevicesRunnable);
}
}

View File

@@ -1,6 +1,8 @@
rootProject.name = 'jitsi-meet-react'
include ':app'
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-vector-icons'

View File

@@ -10,7 +10,7 @@ import Recorder from './modules/recorder/Recorder';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import {reportError} from './modules/util/helpers';
import { reload, reportError } from './modules/util/helpers';
import UIEvents from './service/UI/UIEvents';
import UIUtil from './modules/UI/util/UIUtil';
@@ -203,10 +203,8 @@ function maybeRedirectToWelcomePage(options) {
// save whether current user is guest or not, before navigating
// to close page
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
if (options.feedbackSubmitted)
window.location.pathname = "close.html";
else
window.location.pathname = "close2.html";
assignWindowLocationPathname(
options.feedbackSubmitted ? "close.html" : "close2.html");
return;
}
@@ -219,11 +217,42 @@ function maybeRedirectToWelcomePage(options) {
if (config.enableWelcomePage) {
setTimeout(() => {
APP.settings.setWelcomePageEnabled(true);
window.location.pathname = "/";
assignWindowLocationPathname('./');
}, 3000);
}
}
/**
* Assigns a specific pathname to window.location.pathname taking into account
* the context root of the Web app.
*
* @param {string} pathname - The pathname to assign to
* window.location.pathname. If the specified pathname is relative, the context
* root of the Web app will be prepended to the specified pathname before
* assigning it to window.location.pathname.
* @return {void}
*/
function assignWindowLocationPathname(pathname) {
const windowLocation = window.location;
if (!pathname.startsWith('/')) {
// XXX To support a deployment in a sub-directory, assume that the room
// (name) is the last non-directory component of the path (name).
let contextRoot = windowLocation.pathname;
contextRoot
= contextRoot.substring(0, contextRoot.lastIndexOf('/') + 1);
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
pathname.startsWith('./') && (pathname = pathname.substring(2));
pathname = contextRoot + pathname;
}
windowLocation.pathname = pathname;
}
/**
* Create local tracks of specified types.
* @param {Object} options
@@ -323,7 +352,7 @@ class ConferenceConnector {
case ConferenceErrors.NOT_ALLOWED_ERROR:
{
// let's show some auth not allowed page
window.location.pathname = "authError.html";
assignWindowLocationPathname('authError.html');
}
break;
@@ -388,7 +417,7 @@ class ConferenceConnector {
APP.UI.notifyMaxUsersLimitReached();
break;
case ConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
window.location.reload();
reload();
break;
default:
this._handleConferenceFailed(err, ...params);
@@ -1445,7 +1474,7 @@ export default {
APP.UI.addListener(UIEvents.LOGOUT, () => {
AuthHandler.logout(room).then(url => {
if (url) {
window.location.href = url;
UIUtil.redirect(url);
} else {
this.hangup(true);
}

View File

@@ -78,5 +78,7 @@ var config = { // eslint-disable-line no-unused-vars
// edit their profile.
enableUserRolesBasedOnToken: false,
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
disableSuspendVideo: true
disableSuspendVideo: true,
// disables or enables RTX (RFC 4588).
disableRtx: true
};

View File

@@ -70,7 +70,7 @@
display: none;
position: relative;
background-size: contain;
border: $thumbnailVideoBorder solid $thumbnailBorderColor;
border: $thumbnailVideoBorder solid transparent;
border-radius: $borderRadius;
margin: 0 $thumbnailVideoMargin;

View File

@@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-mic-camera-combined:before {
content: "\e903";
}
.icon-feedback:before {
content: "\e91d";
}

View File

@@ -1,6 +1,6 @@
.inlay {
margin-top: 14%;
@include border-radius(3px);
@include border-radius(4px);
padding: 40px 38px 44px;
color: #fff;
background: $inlayColorBg;

View File

@@ -10,7 +10,7 @@
color: $popoverFontColor;
background-color: $popoverBg;
background-clip: padding-box;
border-radius: 3px;
border-radius: $borderRadius;
/*-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);*/
/*box-shadow: 0 5px 10px rgba(0, 0, 0, 0.4);*/
white-space: normal;

View File

@@ -1,18 +1,18 @@
@charset "UTF-8";
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: v2.1.1
*
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://swisnl.github.io/jQuery-contextMenu/
*
* Copyright (c) 2011-2016 SWIS BV and contributors
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
* Date: 2016-02-28T09:53:18.890Z
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: v2.1.1
*
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://swisnl.github.io/jQuery-contextMenu/
*
* Copyright (c) 2011-2016 SWIS BV and contributors
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
* Date: 2016-02-28T09:53:18.890Z
*/
@font-face {
font-family: "context-menu-icons";
@@ -88,7 +88,7 @@
list-style-type: none;
background: #fff;
border: 1px solid #bebebe;
border-radius: 3px;
border-radius: $borderRadius;
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
}
@@ -156,8 +156,8 @@
transform: translateY(-50%);
}
/**
* Inputs
/**
* Inputs
*/
.context-menu-item.context-menu-input {
padding: 5px 10px;

View File

@@ -127,3 +127,10 @@ $inputControlEmColor: #f29424;
//buttons
$linkFontColor: #489afe;
$linkHoverFontColor: #287ade;
/**
* Landing
*/
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
$unsupportedBrowserButtonBgColor: #ff9a00;
$unsupportedBrowserTextColor: #4a4a4a;

View File

@@ -19,7 +19,7 @@
&__background {
@include topLeft();
background-color: black;
border-radius: $borderRadius - 1;
border-radius: $borderRadius;
width: 100%;
height: 100%;
}
@@ -474,6 +474,7 @@
#localConnectionMessage {
display: none;
position: absolute;
left: 0;
width: 100%;
top:50%;
z-index: 2;

View File

@@ -67,5 +67,7 @@
@import '404';
@import 'policy';
@import 'filmstrip';
@import 'unsupported-browser/unsupported-desktop-browser';
@import 'unsupported-browser/unsupported-mobile-browser';
/* Modules END */

View File

@@ -85,9 +85,6 @@ $popupMenuSelectedItemBackground: rgba(256, 256, 256, .2);
// Toolbar
$splitterColor: #ccc;
// Thumbnail
$thumbnailBorderColor: rgba(71, 71, 71, .7);
/**
* Forms
*/

View File

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

View File

@@ -0,0 +1,69 @@
.unsupported-mobile-browser {
background-color: #fff;
height: 100vh;
padding: 35px 0;
width: 100vw;
&__body {
color: $unsupportedBrowserTextColor;
margin: auto;
max-width: 40em;
text-align: center;
width: 75%;
a:active {
text-decoration: none;
}
}
&__text {
font-size: 1.8em;
line-height: em(29px, 21px);
margin-bottom: 0.65em;
&_small {
font-size: 1.5em;
margin-bottom: 1em;
margin-top: em(21, 18);
strong {
font-size: em(21, 18);
}
}
}
&__logo {
height: 108px;
width: 77px;
}
&__button {
border: 0;
height: 42px;
margin: 0 auto;
max-width: 300px;
width: 98%;
@include border-radius(8px);
background-color: $unsupportedBrowserButtonBgColor;
font-size: 1.5em;
font-weight: 300;
letter-spacing: 0.5px;
text-shadow: 0px 1px 2px $unsupportedBrowserTextColor;
// Disable standard button effects.
box-shadow: none;
outline: none;
&:active {
background-color: $unsupportedBrowserButtonBgColor;
}
&_primary {
background-color: $primaryUnsupportedBrowserButtonBgColor;
&:active {
background-color: $primaryUnsupportedBrowserButtonBgColor;
}
}
}
}

View File

@@ -1,138 +0,0 @@
@import 'variables';
body {
width:100%;
height:100%;
background-color: white;
color: #424242;
font-family: $baseFontFamily;
font-size: 28px;
margin:0;
padding:0;
}
#wrap{
display: block;
position: absolute;
width:500px;
height: 565px;
overflow:hidden;
text-align: center;
margin: auto;
top: 0; left: 0; bottom: 0; right: 0;
}
.firefox{
font-size: 11pt;
color: #c8c8c8;
width: 468px;
text-align: center;
margin: 30px auto 0px auto;
padding-left: 15px;
}
#text{
display:inline-block;
font-size: 28px;
/* width: 568px; */
vertical-align:middle;
padding-top: 25px;
}
a {
color: #087dba;
text-decoration:none;
}
.browser {
width: 138px;
height: 163px;
margin-top: 5px;
background-color: #e8e8e8;
border: 1px solid #cfcfcf;
border-radius: 10px;
}
.browser_wrapper
{
width: 138px;
/* height: 188px; */
vertical-align: middle;
color: #929391;
font-size: 20px;
float: left;
margin-left: 15px;
margin-top: 5px;
}
.browser_text
{
height: 2em;
}
.supported_browsers
{
margin: 0px auto 0px auto;
/* width: 660px; */
}
.clear
{
clear: both;
}
.button
{
background-color: #62c82a;
border: 1px solid #3c8117;
border-radius: 10px;
color: #FFFFFF;
font-size: 12px;
text-align: center;
width: 115px;
height: 26px;
padding-top: 13px;
margin: 15px auto 0px auto;
}
.logo
{
margin: 20px auto 0px auto;
}
#chrome_logo
{
width: 78px;
height: 78px;
background-image: url('../images/chrome.png');
}
#chromium_logo
{
width: 77px;
height: 78px;
background-image: url('../images/chromium.png');
}
#firefox_logo
{
width: 86px;
height: 80px;
background-image: url('../images/firefox.png');
}
#opera_logo
{
width: 73px;
height: 78px;
background-image: url('../images/opera.png');
}
#safari_logo
{
width: 78px;
height: 79px;
background-image: url('../images/safari.png');
}
#ie_logo
{
width: 80px;
height: 78px;
background-image: url('../images/ie.png');
}

View File

@@ -4,7 +4,6 @@
*.ico /usr/share/jitsi-meet/
libs /usr/share/jitsi-meet/
css/all.css /usr/share/jitsi-meet/css/
css/unsupported_browser.css /usr/share/jitsi-meet/css/
sounds /usr/share/jitsi-meet/
fonts /usr/share/jitsi-meet/
images /usr/share/jitsi-meet/

223
doc/api.md Normal file
View File

@@ -0,0 +1,223 @@
# Jitsi Meet API
You can use the Jitsi Meet API to embed Jitsi Meet in to your application.
## Installation
To embed Jitsi Meet in your application you need to add the Jitsi Meet API library:
```javascript
<script src="https://meet.jit.si/external_api.js"></script>
```
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object:
```javascript
<script>
var domain = "meet.jit.si";
var room = "JitsiMeetAPIExample";
var width = 700;
var height = 700;
var api = new JitsiMeetExternalAPI(domain, room, width, height);
</script>
```
You can use the above lines to indicate where exactly you want the Jitsi Meet conference to be placed in your HTML code,
or you can specify the parent HTML element for the Jitsi Meet conference in the `JitsiMeetExternalAPI`
constructor:
```javascript
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
```
If you don't specify the room the user will enter in new conference with a random room name.
You can overwrite options set in [config.js]() and [interface_config.js](). For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
```javascript
var configOverwrite = {disableSimulcast: true};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
```
## Controlling the embedded Jitsi Meet Conference
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
```javascript
api.executeCommand(command, ...arguments)
```
The `command` parameter is String object with the name of the command. The following commands are currently supported:
* **displayName** - Sets the display name of the local participant. This command requires one argument - the new display name to be set.
```javascript
api.executeCommand('displayName', 'New Nickname');
```
* **toggleAudio** - Mutes / unmutes the audio for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleAudio')
```
* **toggleVideo** - Mutes / unmutes the video for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleVideo')
```
* **toggleFilmStrip** - Hides / shows the film strip. No arguments are required.
```javascript
api.executeCommand('toggleFilmStrip')
```
* **toggleChat** - Hides / shows the chat. No arguments are required.
```javascript
api.executeCommand('toggleChat')
```
* **toggleContactList** - Hides / shows the contact list. No arguments are required.
```javascript
api.executeCommand('toggleContactList')
```
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
```javascript
api.executeCommand('toggleShareScreen')
```
* **hangup** - Hangups the call. No arguments are required.
```javascript
api.executeCommand('hangup')
```
* **email** - Changes the local email address. This command requires one argument - the new email address to be set.
```javascript
api.executeCommand('email', 'example@example.com')
```
* **avatarUrl** - Changes the local avatar URL. This command requires one argument - the new avatar URL to be set.
```javascript
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647')
```
You can also execute multiple commands using the `executeCommands` method:
```javascript
api.executeCommands(commands)
```
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands asvalues:
```javascript
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
```javascript
api.addEventListener(event, listener)
```
The `event` parameter is a String object with the name of the event.
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
The following events are currently supported:
* **incomingMessage** - Event notifications about incoming
messages. The listener will receive an object with the following structure:
```javascript
{
"from": from, // JID of the user that sent the message
"nick": nick, // the nickname of the user that sent the message
"message": txt // the text of the message
}
```
* **outgoingMessage** - Event notifications about outgoing
messages. The listener will receive an object with the following structure:
```javascript
{
"message": txt // the text of the message
}
```
* **displayNameChanged** - event notifications about display name
changes. The listener will receive an object with the following structure:
```javascript
{
"jid": jid, // the JID of the participant that changed his display name
"displayname": displayName // the new display name
}
```
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
```javascript
{
"jid": jid // the JID of the participant
}
```
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
```javascript
{
"jid": jid // the JID of the participant
}
```
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
```javascript
{
"roomName": room // the room name of the conference
}
```
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
```javascript
{
"roomName": room // the room name of the conference
}
```
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
You can also add multiple event listeners by using `addEventListeners`.
This method requires one argument of type Object. The object argument must
have the names of the events as keys and the listeners of the events as values.
```javascript
function incomingMessageListener(object)
{
// ...
}
function outgoingMessageListener(object)
{
// ...
}
api.addEventListeners({
incomingMessage: incomingMessageListener,
outgoingMessage: outgoingMessageListener})
```
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
```javascript
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
```javascript
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
You can get the number of participants in the conference with the following API function:
```javascript
var numberOfParticipants = api.getNumberOfParticipants();
```
You can remove the embedded Jitsi Meet Conference with the following API function:
```javascript
api.dispose()
```
NOTE: It's a good practice to remove the conference before the page is unloaded.
[config.js]: https://github.com/jitsi/jitsi-meet/blob/master/config.js
[interface_config.js]: https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js

View File

@@ -1,215 +0,0 @@
Jitsi Meet API
============
You can use Jitsi Meet API to embed Jitsi Meet in to your application.
Installation
==========
To embed Jitsi Meet in your application you need to add Jitsi Meet API library
```javascript
<script src="https://meet.jit.si/external_api.js"></script>
```
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object
```javascript
<script>
var domain = "meet.jit.si";
var room = "JitsiMeetAPIExample";
var width = 700;
var height = 700;
var api = new JitsiMeetExternalAPI(domain, room, width, height);
</script>
```
You can paste that lines in your html code where you want to be placed the Jitsi Meet conference
or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI
constructor.
```javascript
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
```
If you don't specify room the user will enter in new conference with random room name.
You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
```javascript
var configOverwrite = {enableSimulcast: false};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
```
Controlling embedded Jitsi Meet Conference
=========
You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object.
You can send command to Jitsi Meet conference using ```executeCommand```.
```
api.executeCommand(command, ...arguments)
```
The ```command``` parameter is String object with the name of the command.
Currently we support the following commands:
* **displayName** - sets the display name of the local participant. This command requires one argument -
the new display name to be set
```
api.executeCommand('displayName', 'New Nickname');
```
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
```
api.executeCommand('toggleAudio')
```
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
```
api.executeCommand('toggleVideo')
```
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
```
api.executeCommand('filmStrip')
```
* **toggleChat** - hides / shows the chat. No arguments are required.
```
api.executeCommand('toggleChat')
```
* **toggleContactList** - hides / shows the contact list. No arguments are required.
```
api.executeCommand('toggleContactList')
```
* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required.
```
api.executeCommand('toggleShareScreen')
```
* **hangup** - Hangups the call. No arguments are required.
```
api.executeCommand('hangup')
```
* **email** - Hangups the call. No arguments are required.
```
api.executeCommand('email', 'example@example.com')
```
* **avatarUrl** - Hangups the call. No arguments are required.
```
api.executeCommand('avatarUrl', 'avatarUrl')
```
You can also execute multiple commands using the method ```executeCommands```.
```
api.executeCommands(commands)
```
The ```commands``` parameter is object with keys the names of the commands and values the arguments for the
commands.
```
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method.
```
api.addEventListener(event, listener)
```
The ```event``` parameter is String object with the name of the event.
The ```listener``` paramenter is Function object with one argument that will be notified when the event occurs
with data related to the event.
Currently we support the following events:
* **incomingMessage** - event notifications about incoming
messages. The listener will receive object with the following structure:
```
{
"from": from,//JID of the user that sent the message
"nick": nick,//the nickname of the user that sent the message
"message": txt//the text of the message
}
```
* **outgoingMessage** - event notifications about outgoing
messages. The listener will receive object with the following structure:
```
{
"message": txt//the text of the message
}
```
* **displayNameChanged** - event notifications about display name
change. The listener will receive object with the following structure:
```
{
jid: jid,//the JID of the participant that changed his display name
displayname: displayName //the new display name
}
```
* **participantJoined** - event notifications about new participant.
The listener will receive object with the following structure:
```
{
jid: jid //the jid of the participant
}
```
* **participantLeft** - event notifications about participant that left room.
The listener will receive object with the following structure:
```
{
jid: jid //the jid of the participant
}
```
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference.
The listener will receive object with the following structure:
```
{
roomName: room //the room name of the conference
}
```
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference.
The listener will receive object with the following structure:
```
{
roomName: room //the room name of the conference
}
```
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
You can also add multiple event listeners by using ```addEventListeners```.
This method requires one argument of type Object. The object argument must
have keys with the names of the events and values the listeners of the events.
```
function incomingMessageListener(object)
{
...
}
function outgoingMessageListener(object)
{
...
}
api.addEventListeners({
incomingMessage: incomingMessageListener,
outgoingMessage: outgoingMessageListener})
```
If you want to remove a listener you can use ```removeEventListener``` method with argument the name of the event.
```
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use ```removeEventListeners``` method with argument
array with the names of the events.
```
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
You can get the number of participants in the conference with the following code:
```
var numberOfParticipants = api.getNumberOfParticipants();
```
You can remove the embedded Jitsi Meet Conference with the following code:
```
api.dispose()
```
It is a good practice to remove the conference before the page is unloaded.

94
doc/mobile.md Normal file
View File

@@ -0,0 +1,94 @@
# Jitsi Meet mobile apps
Jitsi Meet can also be built as a standalone mobile application for
iOS and Android. It uses the [React Native]() framework.
First make sure the [React Native dependencies]() are installed.
**NOTE**: This document assumes the app is being built on a macOS system.
**NOTE**: The app must be built for an actual device since the simulators don't
work properly with the native plugins we require.
## iOS
1. Install some extra dependencies
- Install ios-deploy globally (in case you want to use the React Native CLI
to deploy the app to the device)
```bash
npm install -g ios-deploy
```
2. Build the app
There are 2 ways to build the app: using the CLI or using Xcode.
Using the CLI:
```bash
react-native run-ios --device
```
When the app is launched from the CLI the output can be checked with the
following command:
```bash
react-native log-ios
```
Using Xcode
- Open **ios/jitsi-meet-react.xcworkspace** in Xcode. Make sure it's the
workspace file!
- Select your device from the top bar and hit the "play" button.
When the app is launched from Xcode the Debug console will show the output
logs the application creates.
3. Other remarks
It's likely you'll need to change the bundle ID for deploying to a device
because the default bundle ID points to the application signed by Atlassian.
This can be changed in the "General" tab. Under "Identity" set
"Bundle Identifier" to a different value, and adjust the "Team" in the
"Signing" section to match your own.
## Android
The [React Native dependencies]() page has very detailed information on how to
setup [Android Studio]() and the required components for getting the necessary
build environment. Make sure you follow it closely.
1. Building the app
The app can be built using the CLI utility as follows:
```bash
react-native run-android
```
It will be launched on the connected Android device.
## Debugging
The official documentation on [debugging]() is quite extensive, it is the
preferred method for debugging.
**NOTE**: When using Chrome Developer Tools for debugging the JavaScript code
is being interpreted by Chrome's V8 engine, instead of JSCore which
React Native uses. It's important to keep this in mind due to potential
differences in supported JavaScript features.
[Android Studio]: https://developer.android.com/studio/index.html
[debugging]: https://facebook.github.io/react-native/docs/debugging.html
[React Native]: https://facebook.github.io/react-native/
[React Native dependencies]: https://facebook.github.io/react-native/docs/getting-started.html#installing-dependencies

6
flow-typed/npm/flow-bin_v0.x.x.js vendored Normal file
View File

@@ -0,0 +1,6 @@
// flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583
// flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x
declare module "flow-bin" {
declare module.exports: string;
}

89
flow-typed/npm/react-redux_v5.x.x.js vendored Normal file
View File

@@ -0,0 +1,89 @@
// flow-typed signature: 0ed284c5a2e97a9e3c0e87af3dedc09d
// flow-typed version: bdf1e66252/react-redux_v5.x.x/flow_>=v0.30.x
import type { Dispatch, Store } from 'redux'
declare module 'react-redux' {
/*
S = State
A = Action
OP = OwnProps
SP = StateProps
DP = DispatchProps
*/
declare type MapStateToProps<S, OP: Object, SP: Object> = (state: S, ownProps: OP) => SP | MapStateToProps<S, OP, SP>;
declare type MapDispatchToProps<A, OP: Object, DP: Object> = ((dispatch: Dispatch<A>, ownProps: OP) => DP) | DP;
declare type MergeProps<SP, DP: Object, OP: Object, P: Object> = (stateProps: SP, dispatchProps: DP, ownProps: OP) => P;
declare type StatelessComponent<P> = (props: P) => ?React$Element<any>;
declare class ConnectedComponent<OP, P, Def, St> extends React$Component<void, OP, void> {
static WrappedComponent: Class<React$Component<Def, P, St>>;
getWrappedInstance(): React$Component<Def, P, St>;
static defaultProps: void;
props: OP;
state: void;
}
declare type ConnectedComponentClass<OP, P, Def, St> = Class<ConnectedComponent<OP, P, Def, St>>;
declare type Connector<OP, P> = {
(component: StatelessComponent<P>): ConnectedComponentClass<OP, P, void, void>;
<Def, St>(component: Class<React$Component<Def, P, St>>): ConnectedComponentClass<OP, P, Def, St>;
};
declare class Provider<S, A> extends React$Component<void, { store: Store<S, A>, children?: any }, void> { }
declare type ConnectOptions = {
pure?: boolean,
withRef?: boolean
};
declare type Null = null | void;
declare function connect<A, OP>(
...rest: Array<void> // <= workaround for https://github.com/facebook/flow/issues/2360
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
declare function connect<A, OP>(
mapStateToProps: Null,
mapDispatchToProps: Null,
mergeProps: Null,
options: ConnectOptions
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
declare function connect<S, A, OP, SP>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: Null,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<SP & { dispatch: Dispatch<A> } & OP>>;
declare function connect<A, OP, DP>(
mapStateToProps: Null,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<DP & OP>>;
declare function connect<S, A, OP, SP, DP>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<SP & DP & OP>>;
declare function connect<S, A, OP, SP, DP, P>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: MergeProps<SP, DP, OP, P>,
options?: ConnectOptions
): Connector<OP, P>;
}

56
flow-typed/npm/redux_v3.x.x.js vendored Normal file
View File

@@ -0,0 +1,56 @@
// flow-typed signature: ba132c96664f1a05288f3eb2272a3c35
// flow-typed version: c4bbd91cfc/redux_v3.x.x/flow_>=v0.33.x
declare module 'redux' {
/*
S = State
A = Action
*/
declare type Dispatch<A: { type: $Subtype<string> }> = (action: A) => A;
declare type MiddlewareAPI<S, A> = {
dispatch: Dispatch<A>;
getState(): S;
};
declare type Store<S, A> = {
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
dispatch: Dispatch<A>;
getState(): S;
subscribe(listener: () => void): () => void;
replaceReducer(nextReducer: Reducer<S, A>): void
};
declare type Reducer<S, A> = (state: S, action: A) => S;
declare type Middleware<S, A> =
(api: MiddlewareAPI<S, A>) =>
(next: Dispatch<A>) => Dispatch<A>;
declare type StoreCreator<S, A> = {
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
};
declare type StoreEnhancer<S, A> = (next: StoreCreator<S, A>) => StoreCreator<S, A>;
declare function createStore<S, A>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
declare function createStore<S, A>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
declare function applyMiddleware<S, A>(...middlewares: Array<Middleware<S, A>>): StoreEnhancer<S, A>;
declare type ActionCreator<A, B> = (...args: Array<B>) => A;
declare type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };
declare function bindActionCreators<A, C: ActionCreator<A, any>>(actionCreator: C, dispatch: Dispatch<A>): C;
declare function bindActionCreators<A, K, C: ActionCreators<K, A>>(actionCreators: C, dispatch: Dispatch<A>): C;
declare function combineReducers<O: Object, A>(reducers: O): Reducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare function compose<S, A>(...fns: Array<StoreEnhancer<S, A>>): Function;
}

Binary file not shown.

View File

@@ -14,6 +14,7 @@
<glyph unicode="&#xe900;" glyph-name="connection-lost" horiz-adv-x="1414" d="M0 299.153h196.337v-187.951h-196.337v187.951zM271.842 480.372h196.337v-369.169h-196.337v369.169zM543.656 661.562h196.337v-550.36h-196.337v550.36zM815.47 842.766v-731.564h119.56c-14.589 33.025-23.125 71.503-23.232 111.943 0.132 86.42 38.697 163.851 99.656 216.468l0.348 403.153h-196.332zM1087.292 1024v-533.672c28.874 10.572 62.222 16.73 97.009 16.825 35.717-0.129 69.823-6.614 101.322-18.371l-1.999 535.218h-196.332zM1192.868 439.852c-0.009 0-0.020 0-0.031 0-122.247 0-221.351-98.447-221.372-219.896 0-0.007 0-0.014 0-0.021 0-121.467 99.111-219.935 221.372-219.935 0.011 0 0.021 0 0.032 0 122.248 0.014 221.345 98.477 221.345 219.935 0 0.007 0 0.013 0 0.020-0.021 121.441-99.11 219.883-221.345 219.897zM1194.706 372.607c87.601-0.006 158.614-69.787 158.614-155.866 0-0.006 0-0.012 0-0.019-0.022-86.062-71.026-155.822-158.614-155.828-87.588 0.006-158.593 69.766-158.615 155.826 0 0.007 0 0.014 0 0.020 0 86.079 71.013 155.86 158.613 155.866zM1286.795 355.682l48.348-52.528-236.375-217.567-48.348 52.528 236.375 217.567z" />
<glyph unicode="&#xe901;" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe902;" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
<glyph unicode="&#xe903;" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
<glyph unicode="&#xe904;" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
<glyph unicode="&#xe905;" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" />
<glyph unicode="&#xe906;" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,35 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M756.704 395.862l267.296-202.213v635.075l-267.296-202.213v191.923c0 12.085-11.296 21.863-25.216 21.863h-706.272c-13.92 0-25.216-9.777-25.216-21.863v-612.25c0-12.085 11.296-21.863 25.216-21.863h706.272c13.92 0 25.216 9.777 25.216 21.863v189.679zM371.338 647.772c47.817 0 86.529-40.232 86.529-89.811v-184.835c0-49.651-38.713-89.883-86.529-89.883-47.788 0-86.515 40.232-86.515 89.883v184.835c0 49.579 38.756 89.811 86.515 89.811v0zM356.754 709.93v32.78h33.718v-33.412c73.858-9.606 131.235-73.73 131.235-151.351v-88.232h-30.636v88.232c0 67.57-53.696 122.534-119.734 122.534-66.024 0-119.691-54.964-119.691-122.534v-88.232h-30.636v88.232c0 79.215 59.674 144.502 135.744 151.969v0.014z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 0,
"tags": [
"Combined Shape"
]
},
"attrs": [
{}
],
"properties": {
"order": 109,
"id": 0,
"name": "mic-camera-combined",
"prevSize": 32,
"code": 59651
},
"setIdx": 0,
"setId": 2,
"iconIdx": 0
},
{
"icon": {
"paths": [
@@ -26,8 +55,8 @@
"prevSize": 32,
"code": 59677
},
"setIdx": 0,
"setId": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
{
@@ -55,8 +84,8 @@
"prevSize": 32,
"code": 59676
},
"setIdx": 0,
"setId": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 1
},
{
@@ -81,9 +110,9 @@
"code": 59649,
"name": "avatar"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 12
"setIdx": 1,
"setId": 1,
"iconIdx": 2
},
{
"icon": {
@@ -107,9 +136,9 @@
"code": 59653,
"name": "hangup"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 123
"setIdx": 1,
"setId": 1,
"iconIdx": 3
},
{
"icon": {
@@ -133,9 +162,9 @@
"code": 59654,
"name": "chat"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 149
"setIdx": 1,
"setId": 1,
"iconIdx": 4
},
{
"icon": {
@@ -159,9 +188,9 @@
"code": 59650,
"name": "download"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 165
"setIdx": 1,
"setId": 1,
"iconIdx": 5
},
{
"icon": {
@@ -185,9 +214,9 @@
"code": 59655,
"name": "edit"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 186
"setIdx": 1,
"setId": 1,
"iconIdx": 6
},
{
"icon": {
@@ -211,9 +240,9 @@
"code": 59656,
"name": "share-doc"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 207
"setIdx": 1,
"setId": 1,
"iconIdx": 7
},
{
"icon": {
@@ -237,9 +266,9 @@
"code": 59657,
"name": "telephone"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 216
"setIdx": 1,
"setId": 1,
"iconIdx": 8
},
{
"icon": {
@@ -263,9 +292,9 @@
"code": 59652,
"name": "kick"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 243
"setIdx": 1,
"setId": 1,
"iconIdx": 9
},
{
"icon": {
@@ -289,9 +318,9 @@
"code": 59679,
"name": "menu-up"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 257
"setIdx": 1,
"setId": 1,
"iconIdx": 10
},
{
"icon": {
@@ -315,9 +344,9 @@
"code": 59680,
"name": "menu-down"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 258
"setIdx": 1,
"setId": 1,
"iconIdx": 11
},
{
"icon": {
@@ -341,9 +370,9 @@
"code": 59659,
"name": "full-screen"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 351
"setIdx": 1,
"setId": 1,
"iconIdx": 12
},
{
"icon": {
@@ -367,9 +396,9 @@
"code": 59660,
"name": "exit-full-screen"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 352
"setIdx": 1,
"setId": 1,
"iconIdx": 13
},
{
"icon": {
@@ -393,9 +422,9 @@
"code": 59658,
"name": "star-full"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 364
"setIdx": 1,
"setId": 1,
"iconIdx": 14
},
{
"icon": {
@@ -419,9 +448,9 @@
"code": 59661,
"name": "security"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 474
"setIdx": 1,
"setId": 1,
"iconIdx": 15
},
{
"icon": {
@@ -445,9 +474,9 @@
"code": 59662,
"name": "security-locked"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 475
"setIdx": 1,
"setId": 1,
"iconIdx": 16
},
{
"icon": {
@@ -471,9 +500,9 @@
"code": 59663,
"name": "reload"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 483
"setIdx": 1,
"setId": 1,
"iconIdx": 17
},
{
"icon": {
@@ -497,9 +526,9 @@
"code": 59664,
"name": "microphone"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 493
"setIdx": 1,
"setId": 1,
"iconIdx": 18
},
{
"icon": {
@@ -523,9 +552,9 @@
"code": 59665,
"name": "mic-empty"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 494
"setIdx": 1,
"setId": 1,
"iconIdx": 19
},
{
"icon": {
@@ -549,9 +578,9 @@
"code": 59666,
"name": "mic-disabled"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 495
"setIdx": 1,
"setId": 1,
"iconIdx": 20
},
{
"icon": {
@@ -575,9 +604,9 @@
"code": 59678,
"name": "raised-hand"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 540
"setIdx": 1,
"setId": 1,
"iconIdx": 21
},
{
"icon": {
@@ -601,9 +630,9 @@
"code": 59675,
"name": "contactList"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 550
"setIdx": 1,
"setId": 1,
"iconIdx": 22
},
{
"icon": {
@@ -627,9 +656,9 @@
"code": 59667,
"name": "link"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 560
"setIdx": 1,
"setId": 1,
"iconIdx": 23
},
{
"icon": {
@@ -653,9 +682,9 @@
"code": 59668,
"name": "shared-video"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 591
"setIdx": 1,
"setId": 1,
"iconIdx": 24
},
{
"icon": {
@@ -679,9 +708,9 @@
"code": 59669,
"name": "settings"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 666
"setIdx": 1,
"setId": 1,
"iconIdx": 25
},
{
"icon": {
@@ -705,9 +734,9 @@
"code": 59670,
"name": "star"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 718
"setIdx": 1,
"setId": 1,
"iconIdx": 26
},
{
"icon": {
@@ -731,9 +760,9 @@
"code": 59681,
"name": "switch-camera"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 742
"setIdx": 1,
"setId": 1,
"iconIdx": 27
},
{
"icon": {
@@ -757,9 +786,9 @@
"code": 59671,
"name": "share-desktop"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 784
"setIdx": 1,
"setId": 1,
"iconIdx": 28
},
{
"icon": {
@@ -783,9 +812,9 @@
"code": 59672,
"name": "camera"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 799
"setIdx": 1,
"setId": 1,
"iconIdx": 29
},
{
"icon": {
@@ -809,9 +838,9 @@
"code": 59673,
"name": "camera-disabled"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 800
"setIdx": 1,
"setId": 1,
"iconIdx": 30
},
{
"icon": {
@@ -835,9 +864,9 @@
"code": 59674,
"name": "volume"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 822
"setIdx": 1,
"setId": 1,
"iconIdx": 31
},
{
"icon": {
@@ -885,7 +914,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 32
},
{
"icon": {
@@ -956,7 +985,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 33
},
{
"icon": {
@@ -983,7 +1012,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 34
},
{
"icon": {
@@ -1011,7 +1040,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 35
},
{
"icon": {
@@ -1039,7 +1068,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 36
}
],
"height": 1024,

182
images/logo-blue.svg Normal file
View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 744.09448819 1052.3622047"
id="svg3526"
version="1.1"
inkscape:version="0.91 r13725"
inkscape:export-filename="/Users/ystamcheva/Dropbox/Designs/appLogo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
sodipodi:docname="logo-blue.svg">
<defs
id="defs3528" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="817.30793"
inkscape:cy="496.00851"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1440"
inkscape:window-height="851"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1" />
<metadata
id="metadata3531">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g4181">
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g33">
<path
d="m 257.311,591.057 c 27.544,0 85.707,-16.445 124.179,-27.332 5.595,-1.575 10.81,-3.049 15.549,-4.371 0.767,-0.211 1.51,-0.403 2.213,-0.579 -2.161,-2.139 -5.755,-5.387 -11.612,-10.295 -25.628,-17.369 -49.827,-25.456 -76.146,-25.456 -5.741,0 -11.707,0.352 -18.208,1.088 -22.283,2.535 -40.848,7.845 -49.767,10.39 -4.521,1.296 -5.883,1.683 -7.292,1.683 -2.688,0 -4.997,-1.599 -5.9,-4.069 -0.904,-2.483 -0.13,-5.223 1.969,-6.981 l 0.127,-0.102 c 15.379,-12.883 44.032,-36.866 98.39,-47.582 9.428,-1.853 19.514,-2.796 29.968,-2.796 24.334,0 49.53,5.026 74.869,14.925 34.511,13.474 58.094,30.771 77.062,44.67 10.211,7.489 19.03,13.959 26.705,17.516 1.961,0.912 2.979,1.169 3.453,1.236 0.349,-0.452 1.106,-1.7 2.219,-4.974 0.298,-0.867 2.453,-10.019 -13.007,-62.071 -8.985,-30.217 -19.822,-61.077 -25.465,-74.778 -10.916,-26.509 -8.237,-45.296 -4.877,-56.284 -9.248,3.399 -18.701,8.688 -28.646,15.993 l -0.62,0.458 c -4.969,3.684 -10.031,7.853 -15.482,12.725 -32.074,28.718 -56.104,43.69 -71.455,44.504 l -0.423,0.021 -0.421,-0.036 c -13.524,-1.148 -34.019,-20.834 -42.403,-30.801 -1.743,-1.169 -3.729,-1.699 -6.35,-1.699 -2.632,0 -5.583,0.553 -8.438,1.095 -2.077,0.394 -4.218,0.795 -6.341,1.01 -6.767,0.679 -16.252,2.867 -25.406,4.974 -4.413,1.014 -8.967,2.063 -13.13,2.922 -0.079,0.013 -1.866,0.382 -5.06,1.224 -22.624,6.693 -39.673,14.372 -48.012,21.628 -0.091,0.079 -0.36,0.288 -0.789,0.603 -5.64,4.009 -19.199,15.447 -23.29,34.907 l -0.043,0.162 c -8.541,35.837 4.408,80.28 21.615,105.666 8.093,11.932 16.814,19.376 23.944,20.42 1.775,0.252 3.905,0.386 6.321,0.386 z"
id="path35"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g37">
<path
d="m 383.729,400.995 c 0.549,0.108 1.191,0.162 1.9,0.162 14.785,0 47.804,-21.408 53.912,-31.205 l 0.486,-0.78 0.694,-0.611 c 2.083,-2.056 8.099,-12.885 11.019,-19.367 -31.312,-9.394 -34.767,-26.347 -37.821,-41.41 -0.355,-1.749 -0.667,-3.324 -0.946,-4.732 -0.357,-1.842 -0.731,-3.713 -1.052,-5.159 -46.646,15.471 -60.905,24.154 -68.687,30.611 -4.027,3.345 -6.398,12.858 5.215,39.189 5.932,13.422 26.386,31.591 35.28,33.302 z"
id="path39"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<path
style="fill:#17a0db;fill-opacity:1"
inkscape:connector-curvature="0"
id="path43"
d="m 183.03568,710.19014 c -5.799,-6.834 -8.258,-15.447 -7.293,-25.624 4.105,-49.397 -1.525,-61.33 -4.132,-64.162 -0.629,-0.685 -0.969,-0.685 -1.238,-0.685 -0.101,0 -0.195,0.006 -0.296,0.016 -4.84,1.157 -37.441,23.198 -44.638,89.005 -3.471,31.758 2.611,72.542 7.794,97.348 4.165,-14.646 10.742,-30.779 23.483,-47.384 11.862,-15.444 24.801,-27.623 40.852,-38.298 -4.99,-2.075 -10.346,-5.274 -14.532,-10.216 z" />
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g45">
<path
d="m 485.028,154.141 c -3.896,25.701 -10.239,50.115 -22.077,75.883 12.904,-14.609 20.445,-30.481 22.971,-48.296 1.051,-7.38 2.045,-14.439 -0.894,-27.587 z"
id="path47"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g49">
<path
d="m 413.102,273.797 c 23.135,-20.915 37.22,-55.455 43.078,-75.971 -20.149,19.407 -44.636,29.82 -60.351,36.512 -5.412,2.308 -10.08,4.295 -12.878,5.926 -1.178,0.685 -2.367,1.374 -3.571,2.069 -9.533,5.515 -23.924,13.85 -26.022,18.987 l -0.06,0.167 -0.078,0.165 c -6.529,13.72 -10.208,34.352 -11.387,46.184 15.135,-9.242 30.738,-15.41 43.699,-20.529 12.03,-4.753 22.432,-8.863 27.57,-13.51 z"
id="path51"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g53">
<path
d="m 436.439,291.877 c -0.141,0.357 -0.292,0.695 -0.455,1.017 -3.833,11.143 1.446,26.3 11.227,32.017 2.602,1.522 5.132,2.452 7.559,2.772 0.334,0.014 0.666,0.027 1.001,0.027 7.601,0 13.801,-5.56 18.4,-16.519 2.896,-8.34 3.308,-18.23 1.125,-27.158 -1.696,-6.936 -6.084,-15.215 -8.88,-19.343 -5.219,3.582 -15.533,11.462 -22.615,17.716 -4.946,4.777 -6.733,7.785 -7.362,9.471 z"
id="path55"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g57">
<path
d="m 501.845,575.103 c 8.403,-2.29 15.076,-4.165 19.998,-5.623 -10.137,-7.061 -21.871,-15.846 -37.823,-28.253 -39.096,-30.404 -81.019,-45.826 -124.587,-45.826 -23.861,0 -44.647,4.592 -61.098,10.151 4.101,-0.255 8.271,-0.377 12.554,-0.377 5.088,0 10.42,0.179 15.842,0.541 16.949,1.136 60.616,8.845 100.106,55.931 7.956,9.469 16.507,17.307 40.828,17.307 8.679,0 18.796,-0.967 30.913,-2.959 0.749,-0.209 1.882,-0.518 3.267,-0.892 z"
id="path59"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g61">
<path
d="m 557.268,369.949 c -7.755,-12.043 -17.498,-19.524 -25.41,-19.524 -1.464,0 -2.862,0.258 -4.154,0.765 -4.239,1.672 -10.952,21.042 -7.979,35.126 2.023,9.582 13.67,41.96 19.262,57.52 2.142,5.958 3.18,8.869 3.527,9.951 0.275,0.853 0.67,2.077 1.17,3.621 4.517,13.765 16.111,49.145 19.562,77.793 7.175,-30.554 11.239,-67.36 9.647,-111.409 -0.723,-20.199 -6.274,-39.323 -15.625,-53.843 z"
id="path63"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g65">
<path
d="m 412.08,575.289 c -0.153,-0.2 -0.3,-0.397 -0.445,-0.585 -0.614,0.1 -1.616,0.319 -3.185,0.776 l -0.657,0.197 c -8.011,2.95 -22.707,7.908 -39.694,13.64 -20.387,6.87 -43.477,14.659 -62.808,21.595 -24.596,9.165 -32.572,12.781 -35.073,14.048 -0.454,1.218 -0.963,2.772 -1.53,4.486 -5.817,17.705 -19.139,58.23 -84.831,86.562 13.568,13.744 43.101,38.415 101.24,38.415 5.035,0 10.258,-0.188 15.494,-0.566 43.896,-3.121 85.158,-22.544 116.206,-54.673 28.233,-29.21 44.259,-65.641 44.507,-100.76 -6.871,-0.571 -18.519,-2.281 -29.301,-7.4 -0.125,-0.061 -12.447,-6.002 -19.923,-15.735 z"
id="path67"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g69">
<path
d="m 162.104,639.109 c -0.122,10.334 -1.489,20.245 -2.82,29.907 -0.716,5.216 -1.464,10.615 -2.014,16.041 -0.746,10.914 1.612,14.717 2.659,15.829 0.571,0.629 1.513,1.346 3.536,1.346 1.558,0 3.418,-0.432 5.383,-1.251 19.507,-8.176 38.032,-22.367 46.937,-30.243 -13.668,-6.095 -34.689,-19.26 -53.681,-31.629 z"
id="path71"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
style="fill:#17a0db;fill-opacity:1"
transform="translate(43.272677,-6.8248629)"
id="g73">
<path
d="m 484.26,598.224 c -0.552,7.258 -1.737,20.949 -3.631,31.378 -2.295,12.629 -6.095,23.31 -8.305,28.889 3.945,3.648 7.878,7.228 10.429,9.488 10.265,-6.718 43.961,-32.297 67.208,-90.368 -7.447,5.03 -17.906,9.456 -31.465,13.332 -13.797,3.929 -27.204,6.229 -34.236,7.281 z"
id="path75"
inkscape:connector-curvature="0"
style="fill:#17a0db;fill-opacity:1" />
</g>
<g
transform="translate(43.272677,-6.8248629)"
id="g85"
style="fill:#17a0db;fill-opacity:1">
<path
style="fill:#17a0db;fill-opacity:1"
inkscape:connector-curvature="0"
id="path87"
d="M 627.562,350.519 C 613.5,321.124 593.893,306.283 580.351,297.677 568.965,290.444 555.872,285.188 541.339,282 c -1.622,-10.158 -4.397,-20.542 -8.198,-30.646 24.507,-36.531 30.407,-77.605 17.008,-119.213 C 539.858,100.151 531.868,79.524 524.996,67.213 510.15,40.585 489.58,34.997 474.936,34.997 c -15.09,0 -29.538,6.412 -39.667,17.61 -10.37,11.462 -15.213,26.462 -13.634,42.228 1.349,13.446 -2.178,37.872 -4.519,46.594 -0.04,0.117 -4.202,11.776 -35.168,26.784 -0.746,0.268 -2.332,0.811 -4.773,1.629 -17.812,5.965 -50.913,17.062 -72.963,46.219 -16.847,20.407 -20.985,40.629 -25.766,64.036 -2.858,13.955 -5.846,32.187 -5.105,53.745 -55.35,12.291 -95.226,37.338 -118.609,74.54 -24.203,38.52 -28.402,86.272 -12.468,141.993 l 0.14,0.414 c 0.292,1.014 0.6,2.024 0.921,3.03 -2.718,-0.466 -5.465,-0.858 -8.285,-1.169 -2.469,-0.284 -5.015,-0.42 -7.54,-0.42 -27.636,0 -57.043,17.371 -78.666,46.474 -16.427,22.098 -36.156,61.131 -36.852,121.593 -0.523,44.905 4.279,86.306 14.283,123.054 7.461,27.381 15.784,44.202 18.979,50.09 l 67.793,127.079 31.06,-140.731 c 10.6,-47.935 21.066,-68.283 34.571,-81.732 31.425,18.938 68.541,28.901 107.941,28.901 43.919,0 89.715,-12.667 128.934,-35.662 25.477,-14.954 47.193,-33.324 64.629,-54.658 0.236,0 0.469,0 0.704,0 l 1.857,-0.038 c 10.782,-0.365 25.522,-5.697 40.434,-14.63 12.421,-7.433 31.147,-21.108 49.946,-44.064 18.945,-23.155 34.402,-51.324 45.926,-83.731 13.5,-37.939 21.717,-82.115 24.404,-131.272 3.253,-45.723 -2.078,-83.533 -15.881,-112.384 z m -31.124,109.427 c -2.415,44.805 -9.745,84.66 -21.764,118.441 -9.713,27.302 -22.502,50.739 -38.005,69.69 -26.696,32.611 -52.783,41.355 -55.551,41.465 l -0.22,0 c -2.528,0 -4.012,-1.032 -11.095,-5.988 -1.979,-1.379 -4.969,-3.467 -7.436,-5.075 -14.813,28.811 -39.145,53.701 -70.659,72.185 -32.098,18.824 -69.432,29.202 -105.1,29.202 -42.352,0 -79.532,-13.979 -107.842,-40.493 -38.621,24.556 -61.833,45.044 -80.652,130.273 l -3.562,16.157 -7.787,-14.59 C 84.8,867.621 78.058,854.32 71.708,830.982 62.852,798.444 58.598,761.384 59.071,720.842 c 0.944,-80.909 44.373,-121.518 68.427,-121.518 0.792,0 1.578,0.039 2.328,0.128 22.8,2.551 37.699,12.402 64.745,30.291 2.796,1.853 5.74,3.8 8.843,5.838 9.69,6.36 23.387,14.125 26.835,14.791 6.562,-0.381 12.986,-15.079 14.853,-28.713 0.114,-0.829 0.226,-1.598 0.334,-2.315 0.147,-1.612 0.227,-3.03 0.27,-4.194 -1.144,-0.399 -2.333,-0.869 -3.547,-1.403 l -0.27,-0.091 c -17.012,-5.857 -41.868,-34.625 -54.378,-76.385 -12.081,-42.21 -9.691,-77.122 7.099,-103.83 27.221,-43.328 86.307,-53.515 105.849,-56.861 6.109,-1.214 12.498,-2.351 18.999,-3.378 3.035,-0.762 5.11,-1.399 6.449,-1.978 0.58,-0.403 0.835,-0.833 0.439,-2.403 l 0.53,-0.148 -0.513,0.115 c -0.237,-1.065 -0.565,-2.311 -0.941,-3.753 -0.521,-1.997 -1.103,-4.256 -1.705,-6.936 -6.05,-27.141 -2.962,-49.884 0.863,-68.559 4.297,-21.019 6.678,-32.656 16.605,-44.279 13.152,-18.103 36.803,-26.025 50.953,-30.77 3.948,-1.322 7.359,-2.462 9.331,-3.412 43.344,-20.789 57.145,-42.646 61.091,-57.318 3.127,-11.642 8.084,-42.253 5.931,-63.63 -0.239,-2.425 0.326,-4.421 1.695,-5.935 1.215,-1.341 2.942,-2.104 4.748,-2.104 4.061,0 9.623,0 30.377,64.478 10.949,33.996 2.785,65.868 -24.244,94.74 -0.347,0.375 -0.7,0.742 -1.04,1.095 -0.738,0.76 -1.848,1.909 -1.999,2.326 0.006,0 -0.048,1.042 1.755,4.031 11.425,18.864 17.633,42.323 15.832,59.763 -0.429,4.062 -1.206,7.971 -1.879,11.411 -0.4,1.968 -0.879,4.377 -1.126,6.241 0.111,0 0.226,0 0.347,0 3.088,-0.327 7.867,-0.7 13.628,-0.7 13.556,0 32.969,2.077 48.503,11.951 9.382,5.952 21.255,15.137 29.981,33.404 10.281,21.472 14.096,51.453 11.369,89.114 z" />
</g>
</g>
<path
style="fill:#ffffff"
d=""
id="path3618"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -2,6 +2,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--#include virtual="base.html" -->
<script>
window.indexLoadedTime = window.performance.now();

View File

@@ -8,10 +8,11 @@
*/
#import "AppDelegate.h"
#import "RCTAssert.h"
#import "RCTBundleURLProvider.h"
#import "RCTLinkingManager.h"
#import "RCTRootView.h"
#import <React/RCTAssert.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTRootView.h>
/**
* A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.

View File

@@ -20,6 +20,19 @@
<string>1.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>org.jitsi.meet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>org.jitsi.meet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -5,8 +5,10 @@
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:beta.meet.jit.si</string>
<string>applinks:beta.hipchat.me</string>
<string>applinks:chaos.hipchat.me</string>
<string>applinks:enso.me</string>
<string>applinks:hipchat.me</string>
<string>applinks:meet.jit.si</string>
</array>
</dict>

View File

@@ -88,8 +88,10 @@ function changeParticipantNumber(APIInstance, number) {
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param filmStripOnly if the value is true only the small videos will be
* visible.
* @param configOverwrite object containing configuration options defined in
* config.js to be overridden.
* @param interfaceConfigOverwrite object containing configuration options
* defined in interface_config.js to be overridden.
* @param noSsl if the value is true https won't be used
* @constructor
*/

View File

@@ -54,7 +54,7 @@ const IndicatorFontSizes = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth: function () {
getAvailableVideoWidth() {
return window.innerWidth;
},
@@ -70,7 +70,7 @@ const IndicatorFontSizes = {
*
* @param el the element
*/
getTextWidth: function (el) {
getTextWidth(el) {
return (el.clientWidth + 1);
},
@@ -79,7 +79,7 @@ const IndicatorFontSizes = {
*
* @param el the element
*/
getTextHeight: function (el) {
getTextHeight(el) {
return (el.clientHeight + 1);
},
@@ -88,14 +88,14 @@ const IndicatorFontSizes = {
*
* @param id the identifier of the audio element.
*/
playSoundNotification: function (id) {
playSoundNotification(id) {
document.getElementById(id).play();
},
/**
* Escapes the given text.
*/
escapeHtml: function (unsafeText) {
escapeHtml(unsafeText) {
return $('<div/>').text(unsafeText).html();
},
@@ -105,11 +105,11 @@ const IndicatorFontSizes = {
* @param {string} safe string which contains escaped html
* @returns {string} unescaped html string.
*/
unescapeHtml: function (safe) {
unescapeHtml(safe) {
return $('<div />').html(safe).text();
},
imageToGrayScale: function (canvas) {
imageToGrayScale(canvas) {
var context = canvas.getContext('2d');
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data;
@@ -156,7 +156,7 @@ const IndicatorFontSizes = {
* @param key the tooltip data-i18n key
* @param position the position of the tooltip in relation to the element
*/
setTooltip: function (element, key, position) {
setTooltip(element, key, position) {
if (element !== null) {
element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
element.setAttribute('data-i18n', '[content]' + key);
@@ -170,7 +170,7 @@ const IndicatorFontSizes = {
*
* @param element the element to remove the tooltip from
*/
removeTooltip: function (element) {
removeTooltip(element) {
element.removeAttribute('data-tooltip', '');
element.removeAttribute('data-i18n','');
element.removeAttribute('content','');
@@ -183,7 +183,7 @@ const IndicatorFontSizes = {
* @returns {string|*}
* @private
*/
_getTooltipText: function (element) {
_getTooltipText(element) {
let title = element.getAttribute('content');
let shortcut = element.getAttribute('shortcut');
if(shortcut) {
@@ -198,7 +198,7 @@ const IndicatorFontSizes = {
* @param container the container to which new child element will be added
* @param newChild the new element that will be inserted into the container
*/
prependChild: function (container, newChild) {
prependChild(container, newChild) {
var firstChild = container.childNodes[0];
if (firstChild) {
container.insertBefore(newChild, firstChild);
@@ -214,7 +214,7 @@ const IndicatorFontSizes = {
* @returns {boolean} {true} to indicate that the given toolbar button
* is enabled, {false} - otherwise
*/
isButtonEnabled: function (name) {
isButtonEnabled(name) {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1
|| interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) !== -1;
},
@@ -226,7 +226,7 @@ const IndicatorFontSizes = {
* @returns {boolean} {true} to indicate that the given setting section
* is enabled, {false} - otherwise
*/
isSettingEnabled: function (name) {
isSettingEnabled(name) {
return interfaceConfig.SETTINGS_SECTIONS.indexOf(name) !== -1;
},
@@ -235,7 +235,7 @@ const IndicatorFontSizes = {
*
* @returns {boolean}
*/
isAuthenticationEnabled: function() {
isAuthenticationEnabled() {
return interfaceConfig.AUTHENTICATION_ENABLE;
},
@@ -303,7 +303,7 @@ const IndicatorFontSizes = {
}
},
hideDisabledButtons: function (mappings) {
hideDisabledButtons(mappings) {
var selector = Object.keys(mappings)
.map(function (buttonName) {
return UIUtil.isButtonEnabled(buttonName)
@@ -313,7 +313,7 @@ const IndicatorFontSizes = {
$(selector).hide();
},
redirect (url) {
redirect(url) {
window.location.href = url;
},
@@ -368,7 +368,7 @@ const IndicatorFontSizes = {
* @param {Object} attrs object with properties
* @returns {String} string of html element attributes
*/
attrsToString: function (attrs) {
attrsToString(attrs) {
return Object.keys(attrs).map(
key => ` ${key}="${attrs[key]}"`
).join(' ');

View File

@@ -64,7 +64,7 @@ ConnectionIndicator.getStringFromArray = function (array) {
ConnectionIndicator.prototype.generateText = function () {
var downloadBitrate, uploadBitrate, packetLoss, i;
if(this.bitrate === null) {
if(!this.bitrate) {
downloadBitrate = "N/A";
uploadBitrate = "N/A";
}
@@ -75,7 +75,7 @@ ConnectionIndicator.prototype.generateText = function () {
this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
}
if(this.packetLoss === null) {
if(!this.packetLoss) {
packetLoss = "N/A";
} else {
@@ -132,7 +132,7 @@ ConnectionIndicator.prototype.generateText = function () {
if (this.showMoreValue) {
var downloadBandwidth, uploadBandwidth, transport;
if (this.bandwidth === null) {
if (!this.bandwidth) {
downloadBandwidth = "N/A";
uploadBandwidth = "N/A";
} else {
@@ -341,7 +341,7 @@ ConnectionIndicator.prototype.updateConnectionStatusIndicator
*/
ConnectionIndicator.prototype.updateConnectionQuality =
function (percent, object) {
if (percent === null) {
if (!percent) {
this.connectionIndicatorContainer.style.display = "none";
this.popover.forceHide();
return;

View File

@@ -23,6 +23,8 @@ export default class LargeVideoManager {
this.eventEmitter = emitter;
this.state = VIDEO_CONTAINER_TYPE;
// FIXME: We are passing resizeContainer as parameter which is calling
// Container.resize. Probably there's better way to implement this.
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE), emitter);
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);

View File

@@ -164,12 +164,20 @@ export class VideoContainer extends LargeContainer {
return getStreamOwnerId(this.stream);
}
constructor (onPlay, emitter) {
/**
* Creates new VideoContainer instance.
* @param resizeContainer {Function} function that takes care of the size
* of the video container.
* @param emitter {EventEmitter} the event emitter that will be used by
* this instance.
*/
constructor (resizeContainer, emitter) {
super();
this.stream = null;
this.videoType = null;
this.localFlipX = true;
this.emitter = emitter;
this.resizeContainer = resizeContainer;
this.isVisible = false;
@@ -199,8 +207,8 @@ export class VideoContainer extends LargeContainer {
this.avatarHeight = $("#dominantSpeakerAvatar").height();
var onPlayCallback = function (event) {
if (typeof onPlay === 'function') {
onPlay(event);
if (typeof resizeContainer === 'function') {
resizeContainer(event);
}
this.wasVideoRendered = true;
}.bind(this);
@@ -336,8 +344,14 @@ export class VideoContainer extends LargeContainer {
* @param {string} videoType video type
*/
setStream (stream, videoType) {
if (this.stream === stream) {
// Handles the use case for the remote participants when the
// videoType is received with delay after turning on/off the
// desktop sharing.
if(this.videoType !== videoType) {
this.videoType = videoType;
this.resizeContainer();
}
return;
} else {
// The stream has changed, so the image will be lost on detach
@@ -361,6 +375,9 @@ export class VideoContainer extends LargeContainer {
this.$video.css({
transform: flipX ? 'scaleX(-1)' : 'none'
});
// Reset the large video background depending on the stream.
this.setLargeVideoBackground(this.avatarDisplayed);
}
/**
@@ -395,8 +412,7 @@ export class VideoContainer extends LargeContainer {
// default background set.
// In order to fix this code we need to introduce video background or
// find a workaround for the video flickering.
$("#largeVideoContainer").css("background",
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
this.setLargeVideoBackground(show);
this.$avatar.css("visibility", show ? "visible" : "hidden");
this.avatarDisplayed = show;
@@ -465,4 +481,19 @@ export class VideoContainer extends LargeContainer {
stayOnStage () {
return false;
}
/**
* Sets the large video container background depending on the container
* type and the parameter indicating if an avatar is currently shown on
* large.
*
* @param {boolean} isAvatar - Indicates if the avatar is currently shown
* on the large video.
* @returns {void}
*/
setLargeVideoBackground (isAvatar) {
$("#largeVideoContainer").css("background",
(this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar)
? "#000" : interfaceConfig.DEFAULT_BACKGROUND);
}
}

View File

@@ -1,6 +1,6 @@
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { redirect } from '../util/helpers';
import { replace } from '../util/helpers';
/**
* The modules stores information about the URL used to start the conference and
@@ -68,6 +68,6 @@ export default class ConferenceUrl {
*/
reload() {
logger.info("Reloading the conference using URL: " + this.originalURL);
redirect(this.originalURL);
replace(this.originalURL);
}
}

View File

@@ -2,12 +2,13 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Create deferred object.
*
* @returns {{promise, resolve, reject}}
*/
export function createDeferred () {
let deferred = {};
export function createDeferred() {
const deferred = {};
deferred.promise = new Promise(function (resolve, reject) {
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
@@ -18,63 +19,58 @@ export function createDeferred () {
/**
* Reload page.
*/
export function reload () {
export function reload() {
window.location.reload();
}
/**
* Redirects to new URL.
* Redirects to a specific new URL by replacing the current location (in the
* history).
*
* @param {string} url the URL pointing to the location where the user should
* be redirected to.
*/
export function redirect (url) {
export function replace(url) {
window.location.replace(url);
}
/**
* Prints the error and reports it to the global error handler.
*
* @param e {Error} the error
* @param msg {string} [optional] the message printed in addition to the error
*/
export function reportError (e, msg = "") {
export function reportError(e, msg = "") {
logger.error(msg, e);
if(window.onerror)
window.onerror(msg, null, null,
null, e);
window.onerror && window.onerror(msg, null, null, null, e);
}
/**
* Creates a debounced function that delays invoking func until after wait
* milliseconds have elapsed since the last time the debounced
* function was invoked
* milliseconds have elapsed since the last time the debounced function was
* invoked.
*
* @param fn
* @param wait
* @param options
* @returns {function(...[*])}
*/
export function debounce(fn, wait = 0, options = {}) {
let leading = options.leading || false;
let trailing = true;
let isCalled = false;
if (typeof options.trailing !== 'undefined') {
trailing = options.trailing;
}
const leading = options.leading || false;
const trailing
= (typeof options.trailing === 'undefined') || options.trailing;
let called = false;
return (...args) => {
if(!isCalled) {
if (leading) {
fn(...args);
}
if (!called) {
leading && fn(...args);
setTimeout(() => {
isCalled = false;
if (trailing) {
fn(...args);
}
called = false;
trailing && fn(...args);
}, wait);
isCalled = true;
called = true;
}
};
}

View File

@@ -34,8 +34,9 @@
"postis": "^2.2.0",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.40.0",
"react-native-keep-awake": "^1.0.7",
"react-native": "0.41.2",
"react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2",
"react-native-prompt": "^1.0.0",
"react-native-vector-icons": "^4.0.0",
"react-native-webrtc": "jitsi/react-native-webrtc",
@@ -59,12 +60,15 @@
"babel-preset-stage-1": "^6.16.0",
"clean-css": "^3.0.0",
"css-loader": "*",
"eslint": "^3.13.1",
"eslint": "^3.15.0",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsdoc": "*",
"eslint-plugin-react": "*",
"eslint-plugin-react-native": "^2.2.1",
"expose-loader": "*",
"file-loader": "*",
"file-loader": "^0.10.0",
"flow-bin": "^0.37.0",
"haste-resolver-webpack-plugin": "^0.2.2",
"imports-loader": "*",
"jshint": "2.9.4",
@@ -78,7 +82,7 @@
},
"license": "Apache-2.0",
"scripts": {
"lint": "jshint . && eslint .",
"lint": "jshint . && eslint . && flow",
"validate": "npm ls"
},
"pre-commit": [

View File

@@ -0,0 +1,13 @@
-- Copyright (c) 2015 &yet <https://andyet.com>
-- https://github.com/otalk/mod_muc_allowners/blob/9a86266a25ed32ade150742cc79f5a1669765a8f/mod_muc_allowners.lua
--
-- Used under the terms of the MIT License
-- https://github.com/otalk/mod_muc_allowners/blob/9a86266a25ed32ade150742cc79f5a1669765a8f/LICENSE
local muc_service = module:depends("muc");
local room_mt = muc_service.room_mt;
room_mt.get_affiliation = function (room, jid)
return "owner";
end

View File

@@ -0,0 +1,21 @@
--- muc.lib.lua 2016-10-26 18:26:53.432377291 +0000
+++ muc.lib.lua 2016-10-26 18:41:40.754426072 +0000
@@ -1256,15 +1256,16 @@
if actor == true then
actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
else
+ local actor_affiliation = self:get_affiliation(actor);
+
-- Can't do anything to other owners or admins
local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
- if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
+ if (occupant_affiliation == "owner" and actor_affiliation ~= "owner") or (occupant_affiliation == "admin" and actor_affiliation ~= "admin" and actor_affiliation ~= "owner") then
return nil, "cancel", "not-allowed";
end
-- If you are trying to give or take moderator role you need to be an owner or admin
if occupant.role == "moderator" or role == "moderator" then
- local actor_affiliation = self:get_affiliation(actor);
if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
return nil, "cancel", "not-allowed";
end

View File

@@ -1,12 +1,16 @@
module.exports = {
'extends': '../.eslintrc.js',
'parser': 'babel-eslint',
'parserOptions': {
'ecmaFeatures': {
'jsx': true
}
},
'plugins': [
// ESLint's rule no-duplicate-imports does not understand Flow's import
// type. Fortunately, eslint-plugin-import understands Flow's import
// type.
'import',
'jsdoc',
'react',
'react-native'
@@ -274,7 +278,6 @@ module.exports = {
'no-confusing-arrow': 2,
'no-const-assign': 2,
'no-dupe-class-members': 2,
'no-duplicate-imports': 2,
'no-new-symbol': 2,
'no-restricted-imports': 0,
'no-this-before-super': 2,
@@ -299,6 +302,8 @@ module.exports = {
'template-curly-spacing': 2,
'yield-star-spacing': 2,
'import/no-duplicates': 2,
// JsDoc plugin rules group. The following rules are in addition to
// valid-jsdoc rule.
'jsdoc/check-param-names': 0,

View File

@@ -4,8 +4,8 @@ import { loadConfig, setConfig } from '../base/lib-jitsi-meet';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
import {
_getRoomAndDomainFromUrlString,
_getRouteToRender,
_parseURIString,
init
} from './functions';
import './reducer';
@@ -31,9 +31,10 @@ export function appInit() {
*/
export function appNavigate(urlOrRoom) {
return (dispatch, getState) => {
const oldDomain = getDomain(getState());
const state = getState();
const oldDomain = getDomain(state);
const { domain, room } = _getRoomAndDomainFromUrlString(urlOrRoom);
const { domain, room } = _parseURIString(urlOrRoom);
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
// currently in a conference and ask her if she wants to close the
@@ -41,13 +42,7 @@ export function appNavigate(urlOrRoom) {
// domain.
if (typeof domain === 'undefined' || oldDomain === domain) {
// If both domain and room vars became undefined, that means we're
// actually dealing with just room name and not with URL.
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? urlOrRoom
: room));
dispatchSetRoomAndNavigate();
} else if (oldDomain !== domain) {
// Update domain without waiting for config to be loaded to prevent
// race conditions when we will start to load config multiple times.
@@ -59,7 +54,8 @@ export function appNavigate(urlOrRoom) {
loadConfig(`https://${domain}`)
.then(
config => configLoaded(/* err */ undefined, config),
err => configLoaded(err, /* config */ undefined));
err => configLoaded(err, /* config */ undefined))
.then(dispatchSetRoomAndNavigate);
}
/**
@@ -83,11 +79,22 @@ export function appNavigate(urlOrRoom) {
return;
}
// We set room name only here to prevent race conditions on app
// start to not make app re-render conference page for two times.
dispatch(setRoom(room));
dispatch(setConfig(config));
_navigate(getState());
}
/**
* Dispatches _setRoomAndNavigate in the Redux store.
*
* @returns {void}
*/
function dispatchSetRoomAndNavigate() {
// If both domain and room vars became undefined, that means we're
// actually dealing with just room name and not with URL.
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? urlOrRoom
: room));
}
};
}
@@ -125,9 +132,10 @@ export function appWillUnmount(app) {
}
/**
* Navigates to route corresponding to current room name.
* Navigates to a route in accord with a specific Redux state.
*
* @param {Object} state - Redux state.
* @param {Object} state - The Redux state which determines/identifies the route
* to navigate to.
* @private
* @returns {void}
*/
@@ -147,15 +155,7 @@ function _navigate(state) {
*/
function _setRoomAndNavigate(newRoom) {
return (dispatch, getState) => {
const oldRoom = getState()['features/base/conference'].room;
dispatch(setRoom(newRoom));
const state = getState();
const room = state['features/base/conference'].room;
if (room !== oldRoom) {
_navigate(state);
}
_navigate(getState());
};
}

View File

@@ -1,10 +1,14 @@
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import {
localParticipantJoined,
localParticipantLeft
} from '../../base/participants';
import { RouteRegistry } from '../../base/react';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import {
appNavigate,
@@ -34,7 +38,7 @@ export class AbstractApp extends Component {
}
/**
* Initializes a new App instance.
* Initializes a new AbstractApp instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
@@ -44,11 +48,18 @@ export class AbstractApp extends Component {
this.state = {
/**
* The Route rendered by this App.
* The Route rendered by this AbstractApp.
*
* @type {Route}
*/
route: undefined
route: undefined,
/**
* The Redux store used by this AbstractApp.
*
* @type {Store}
*/
store: this._maybeCreateStore(props)
};
}
@@ -59,7 +70,7 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillMount() {
const dispatch = this.props.store.dispatch;
const dispatch = this._getStore().dispatch;
dispatch(appWillMount(this));
@@ -68,6 +79,32 @@ export class AbstractApp extends Component {
this._openURL(this._getDefaultURL());
}
/**
* Notifies this mounted React Component that it will receive new props.
* Makes sure that this AbstractApp has a Redux store to use.
*
* @inheritdoc
* @param {Object} nextProps - The read-only React Component props that this
* instance will receive.
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
// The consumer of this AbstractApp did not provide a Redux store.
if (typeof nextProps.store === 'undefined'
// 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)
});
}
}
/**
* Dispose lib-jitsi-meet and remove local participant when component is
* going to be unmounted.
@@ -75,7 +112,7 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillUnmount() {
const dispatch = this.props.store.dispatch;
const dispatch = this._getStore().dispatch;
dispatch(localParticipantLeft());
@@ -93,7 +130,7 @@ export class AbstractApp extends Component {
if (route) {
return (
<Provider store = { this.props.store }>
<Provider store = { this._getStore() }>
{
this._createElement(route.component)
}
@@ -141,6 +178,36 @@ export class AbstractApp extends Component {
return React.createElement(component, { ...thisProps, ...props });
}
/**
* Initializes a new Redux store instance suitable for use by
* this AbstractApp.
*
* @private
* @returns {Store} - A new Redux store instance suitable for use by
* this AbstractApp.
*/
_createStore() {
// Create combined reducer from all reducers in ReducerRegistry.
const reducer = ReducerRegistry.combineReducers();
// Apply all registered middleware from the MiddlewareRegistry and
// additional 3rd party middleware:
// - Thunk - allows us to dispatch async actions easily. For more info
// @see https://github.com/gaearon/redux-thunk.
let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
// Try to enable Redux DevTools Chrome extension in order to make it
// available for the purposes of facilitating development.
let devToolsExtension;
if (typeof window === 'object'
&& (devToolsExtension = window.devToolsExtension)) {
middleware = compose(middleware, devToolsExtension());
}
return createStore(reducer, middleware);
}
/**
* Gets the default URL to be opened when this App mounts.
*
@@ -188,6 +255,22 @@ export class AbstractApp extends Component {
return 'https://meet.jit.si';
}
/**
* Gets the Redux store used by this AbstractApp.
*
* @protected
* @returns {Store} - The Redux store used by this AbstractApp.
*/
_getStore() {
let store = this.state.store;
if (typeof store === 'undefined') {
store = this.props.store;
}
return store;
}
/**
* Gets a Location object from the window with information about the current
* location of the document. Explicitly defined to allow extenders to
@@ -203,6 +286,30 @@ export class AbstractApp extends Component {
return undefined;
}
/**
* Creates a Redux store to be used by this AbstractApp if such as store is
* not defined by the consumer of this AbstractApp through its
* read-only React Component props.
*
* @param {Object} props - The read-only React Component props that will
* eventually be received by this AbstractApp.
* @private
* @returns {Store} - The Redux store to be used by this AbstractApp.
*/
_maybeCreateStore(props) {
// The application Jitsi Meet is architected with Redux. However, I do
// not want consumers of the App React Component to be forced into
// dealing with Redux. If the consumer did not provide an external Redux
// store, utilize an internal Redux store.
let store = props.store;
if (typeof store === 'undefined') {
store = this._createStore();
}
return store;
}
/**
* Navigates to a specific Route.
*
@@ -210,6 +317,10 @@ export class AbstractApp extends Component {
* @returns {void}
*/
_navigate(route) {
if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
return;
}
let nextState = {
...this.state,
route
@@ -223,13 +334,7 @@ export class AbstractApp extends Component {
// (2) A replace function would be provided to the Route in case it
// chose to redirect to another path.
this._onRouteEnter(route, nextState, pathname => {
// FIXME In order to minimize the modifications related to the
// removal of react-router, the Web implementation is provided
// bellow because the replace function is used on Web only at the
// time of this writing. Provide a platform-agnostic implementation.
// It should likely find the best Route matching the specified
// pathname and navigate to it.
window.location.pathname = pathname;
this._openURL(pathname);
// Do not proceed with the route because it chose to redirect to
// another path.
@@ -264,6 +369,6 @@ export class AbstractApp extends Component {
* @returns {void}
*/
_openURL(url) {
this.props.store.dispatch(appNavigate(url));
this._getStore().dispatch(appNavigate(url));
}
}

View File

@@ -4,6 +4,7 @@ import { Linking } from 'react-native';
import { Platform } from '../../base/react';
import '../../audio-mode';
import '../../full-screen';
import '../../wake-lock';
import { AbstractApp } from './AbstractApp';

View File

@@ -14,6 +14,27 @@ export class App extends AbstractApp {
*/
static propTypes = AbstractApp.propTypes
/**
* Initializes a new App instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
*/
constructor(props) {
super(props);
this.state = {
...this.state,
/**
* The context root of window.location i.e. this Web App.
*
* @type {string}
*/
windowLocationContextRoot: this._getWindowLocationContextRoot()
};
}
/**
* Inits the app before component will mount.
*
@@ -22,7 +43,7 @@ export class App extends AbstractApp {
componentWillMount(...args) {
super.componentWillMount(...args);
this.props.store.dispatch(appInit());
this._getStore().dispatch(appInit());
}
/**
@@ -35,6 +56,22 @@ export class App extends AbstractApp {
return window.location;
}
/**
* Gets the context root of this Web App from window.location.
*
* @private
* @returns {string} The context root of window.location i.e. this Web App.
*/
_getWindowLocationContextRoot() {
const pathname = this._getWindowLocation().pathname;
const contextRootEndIndex = pathname.lastIndexOf('/');
return (
contextRootEndIndex === -1
? '/'
: pathname.substring(0, contextRootEndIndex + 1));
}
/**
* Navigates to a specific Route (via platform-specific means).
*
@@ -44,7 +81,7 @@ export class App extends AbstractApp {
*/
_navigate(route) {
let path = route.path;
const store = this.props.store;
const store = this._getStore();
// The syntax :room bellow is defined by react-router. It "matches a URL
// segment up to the next /, ?, or #. The matched string is called a
@@ -53,6 +90,7 @@ export class App extends AbstractApp {
= path.replace(
/:room/g,
store.getState()['features/base/conference'].room);
path = this._routePath2WindowLocationPathname(path);
// Navigate to the specified Route.
const windowLocation = this._getWindowLocation();
@@ -69,4 +107,22 @@ export class App extends AbstractApp {
windowLocation.pathname = path;
}
}
/**
* Converts a specific Route path to a window.location.pathname.
*
* @param {string} path - A Route path to be converted to/represeted as a
* window.location.pathname.
* @private
* @returns {string} A window.location.pathname-compatible representation of
* the specified Route path.
*/
_routePath2WindowLocationPathname(path) {
let pathname = this.state.windowLocationContextRoot;
pathname.endsWith('/') || (pathname += '/');
pathname += path.startsWith('/') ? path.substring(1) : path;
return pathname;
}
}

View File

@@ -1,25 +1,136 @@
import { isRoomValid } from '../base/conference';
import { RouteRegistry } from '../base/navigator';
import { RouteRegistry } from '../base/react';
import { Conference } from '../conference';
import { WelcomePage } from '../welcome';
/**
* The RegExp pattern of the authority of a URI.
*
* @private
* @type {string}
*/
const _URI_AUTHORITY_PATTERN = '(//[^/?#]+)';
/**
* The RegExp pattern of the path of a URI.
*
* @private
* @type {string}
*/
const _URI_PATH_PATTERN = '([^?#]*)';
/**
* The RegExp patther of the protocol of a URI.
*
* @private
* @type {string}
*/
const _URI_PROTOCOL_PATTERN = '([a-z][a-z0-9\\.\\+-]*:)';
/**
* Fixes the hier-part of a specific URI (string) so that the URI is well-known.
* For example, certain Jitsi Meet deployments are not conventional but it is
* possible to translate their URLs into conventional.
*
* @param {string} uri - The URI (string) to fix the hier-part of.
* @private
* @returns {string}
*/
function _fixURIStringHierPart(uri) {
// Rewrite the specified URL in order to handle special cases such as
// hipchat.com and enso.me which do not follow the common pattern of most
// Jitsi Meet deployments.
// hipchat.com
let regex
= new RegExp(
`^${_URI_PROTOCOL_PATTERN}//hipchat\\.com/video/call/`,
'gi');
let match = regex.exec(uri);
if (!match) {
// enso.me
regex
= new RegExp(
`^${_URI_PROTOCOL_PATTERN}//enso\\.me/(?:call|meeting)/`,
'gi');
match = regex.exec(uri);
}
if (match) {
/* eslint-disable no-param-reassign, prefer-template */
uri
= match[1] /* protocol */
+ '//enso.hipchat.me/'
+ uri.substring(regex.lastIndex); /* room (name) */
/* eslint-enable no-param-reassign, prefer-template */
}
return uri;
}
/**
* Fixes the scheme part of a specific URI (string) so that it contains a
* well-known scheme such as HTTP(S). For example, the mobile app implements an
* app-specific URI scheme in addition to Universal Links. The app-specific
* scheme may precede or replace the well-known scheme. In such a case, dealing
* with the app-specific scheme only complicates the logic and it is simpler to
* get rid of it (by translating the app-specific scheme into a well-known
* scheme).
*
* @param {string} uri - The URI (string) to fix the scheme of.
* @private
* @returns {string}
*/
function _fixURIStringScheme(uri) {
const regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}+`, 'gi');
const match = regex.exec(uri);
if (match) {
// As an implementation convenience, pick up the last scheme and make
// sure that it is a well-known one.
let protocol = match[match.length - 1].toLowerCase();
if (protocol !== 'http:' && protocol !== 'https:') {
protocol = 'https:';
}
/* eslint-disable no-param-reassign */
uri = uri.substring(regex.lastIndex);
if (uri.startsWith('//')) {
// The specified URL was not a room name only, it contained an
// authority.
uri = protocol + uri;
}
/* eslint-enable no-param-reassign */
}
return uri;
}
/**
* Gets room name and domain from URL object.
*
* @param {URL} url - URL object.
* @private
* @returns {{
* domain: (string|undefined),
* room: (string|undefined)
* }}
* domain: (string|undefined),
* room: (string|undefined)
* }}
*/
function _getRoomAndDomainFromUrlObject(url) {
function _getRoomAndDomainFromURLObject(url) {
let domain;
let room;
if (url) {
domain = url.hostname;
room = url.pathname.substr(1);
// The room (name) is the last component of pathname.
room = url.pathname;
room = room.substring(room.lastIndexOf('/') + 1);
// Convert empty string to undefined to simplify checks.
if (room === '') {
@@ -36,44 +147,6 @@ function _getRoomAndDomainFromUrlObject(url) {
};
}
/**
* Gets conference room name and connection domain from URL.
*
* @param {(string|undefined)} url - URL.
* @returns {{
* domain: (string|undefined),
* room: (string|undefined)
* }}
*/
export function _getRoomAndDomainFromUrlString(url) {
// Rewrite the specified URL in order to handle special cases such as
// hipchat.com and enso.me which do not follow the common pattern of most
// Jitsi Meet deployments.
if (typeof url === 'string') {
// hipchat.com
let regex = /^(https?):\/\/hipchat.com\/video\/call\//gi;
let match = regex.exec(url);
if (!match) {
// enso.me
regex = /^(https?):\/\/enso\.me\/(?:call|meeting)\//gi;
match = regex.exec(url);
}
if (match && match.length > 1) {
/* eslint-disable no-param-reassign, prefer-template */
url
= match[1] /* URL protocol */
+ '://enso.hipchat.me/'
+ url.substring(regex.lastIndex);
/* eslint-enable no-param-reassign, prefer-template */
}
}
return _getRoomAndDomainFromUrlObject(_urlStringToObject(url));
}
/**
* Determines which route is to be rendered in order to depict a specific Redux
* store.
@@ -94,24 +167,74 @@ export function _getRouteToRender(stateOrGetState) {
}
/**
* Parses a string into a URL (object).
* Parses a specific URI which (supposedly) references a Jitsi Meet resource
* (location).
*
* @param {(string|undefined)} url - The URL to parse.
* @private
* @returns {URL}
* @param {(string|undefined)} uri - The URI to parse which (supposedly)
* references a Jitsi Meet resource (location).
* @returns {{
* domain: (string|undefined),
* room: (string|undefined)
* }}
*/
function _urlStringToObject(url) {
let urlObj;
export function _parseURIString(uri) {
let obj;
if (url) {
try {
urlObj = new URL(url);
} catch (ex) {
// The return value will signal the failure & the logged exception
// will provide the details to the developers.
console.log(`${url} seems to be not a valid URL, but it's OK`, ex);
if (typeof uri === 'string') {
let str = uri;
str = _fixURIStringScheme(str);
str = _fixURIStringHierPart(str);
obj = {};
let regex;
let match;
// protocol
regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
obj.protocol = match[1].toLowerCase();
str = str.substring(regex.lastIndex);
}
// authority
regex = new RegExp(`^${_URI_AUTHORITY_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
let authority = match[1].substring(/* // */ 2);
str = str.substring(regex.lastIndex);
// userinfo
const userinfoEndIndex = authority.indexOf('@');
if (userinfoEndIndex !== -1) {
authority = authority.substring(userinfoEndIndex + 1);
}
obj.host = authority;
// port
const portBeginIndex = authority.lastIndexOf(':');
if (portBeginIndex !== -1) {
obj.port = authority.substring(portBeginIndex + 1);
authority = authority.substring(0, portBeginIndex);
}
obj.hostname = authority;
}
// pathname
regex = new RegExp(`^${_URI_PATH_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
obj.pathname = match[1] || '/';
str = str.substring(regex.lastIndex);
}
}
return urlObj;
return _getRoomAndDomainFromURLObject(obj);
}

View File

@@ -1,5 +1,11 @@
/* global APP, JitsiMeetJS, loggingConfig */
import { isRoomValid } from '../base/conference';
import { RouteRegistry } from '../base/react';
import { interceptComponent } from '../base/util';
import { Conference } from '../conference';
import { WelcomePage } from '../welcome';
import URLProcessor from '../../../modules/config/URLProcessor';
import KeyboardShortcut
from '../../../modules/keyboardshortcut/keyboardshortcut';
@@ -9,7 +15,33 @@ import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage';
const Logger = require('jitsi-meet-logger');
export * from './functions.native';
export { _parseURIString } from './functions.native';
/**
* Determines which route is to be rendered in order to depict a specific Redux
* store.
*
* @param {(Object|Function)} stateOrGetState - Redux state or Regux getState()
* method.
* @returns {Route}
*/
export function _getRouteToRender(stateOrGetState) {
const state
= typeof stateOrGetState === 'function'
? stateOrGetState()
: stateOrGetState;
// If mobile browser page was shown, there is no need to show it again.
const { room } = state['features/base/conference'];
const component = isRoomValid(room) ? Conference : WelcomePage;
const route = RouteRegistry.getRouteByComponent(component);
// Intercepts route components if any of component interceptor rules
// is satisfied.
route.component = interceptComponent(state, component);
return route;
}
/**
* Temporary solution. Later we'll get rid of global APP and set its properties

View File

@@ -1,3 +1,5 @@
/* @flow */
import { NativeModules } from 'react-native';
import { APP_WILL_MOUNT } from '../app';
@@ -22,19 +24,19 @@ MiddlewareRegistry.register(store => next => action => {
// The react-native module AudioMode is implemented on iOS at the time of
// this writing.
if (AudioMode) {
let audioMode;
let mode;
switch (action.type) {
case APP_WILL_MOUNT:
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
audioMode = AudioMode.DEFAULT;
mode = AudioMode.DEFAULT;
break;
case CONFERENCE_WILL_JOIN: {
const conference = store.getState()['features/base/conference'];
audioMode
mode
= conference.audioOnly
? AudioMode.AUDIO_CALL
: AudioMode.VIDEO_CALL;
@@ -42,14 +44,16 @@ MiddlewareRegistry.register(store => next => action => {
}
default:
audioMode = null;
mode = null;
break;
}
if (audioMode !== null) {
AudioMode.setMode(audioMode).catch(err => {
console.error(`Failed to set audio mode ${audioMode}: ${err}`);
});
if (mode !== null) {
AudioMode.setMode(mode)
.catch(err =>
console.error(
`Failed to set audio mode ${String(mode)}: `
+ `${err}`));
}
}

View File

@@ -1,3 +1,7 @@
/* @flow */
import type { Dispatch } from 'redux';
import { conferenceWillLeave } from '../conference';
import JitsiMeetJS from '../lib-jitsi-meet';
@@ -14,10 +18,10 @@ const JitsiConnectionEvents = JitsiMeetJS.events.connection;
/**
* Opens new connection.
*
* @returns {Promise<JitsiConnection>}
* @returns {Function}
*/
export function connect() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const connectionOptions
= state['features/base/connection'].connectionOptions;
@@ -28,7 +32,14 @@ export function connect() {
connectionOptions.token,
{
...connectionOptions,
bosh: connectionOptions.bosh + (room ? `?room=${room}` : '')
bosh:
connectionOptions.bosh
// XXX The Jitsi Meet deployments require the room
// argument to be in lower case at the time of this
// writing but, unfortunately, they do not ignore
// case themselves.
+ (room ? `?room=${room.toLowerCase()}` : '')
});
connection.addEventListener(
@@ -50,7 +61,7 @@ export function connect() {
* @param {string} message - Disconnect reason.
* @returns {void}
*/
function connectionDisconnected(message) {
function connectionDisconnected(message: string) {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
connectionDisconnected);
@@ -103,7 +114,7 @@ export function connect() {
* @returns {Function}
*/
export function disconnect() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const conference = state['features/base/conference'].conference;
const connection = state['features/base/connection'].connection;
@@ -141,7 +152,7 @@ export function disconnect() {
* domain: string
* }}
*/
export function setDomain(domain) {
export function setDomain(domain: string) {
return {
type: SET_DOMAIN,
domain
@@ -160,7 +171,7 @@ export function setDomain(domain) {
* message: string
* }}
*/
function _connectionDisconnected(connection, message) {
function _connectionDisconnected(connection, message: string) {
return {
type: CONNECTION_DISCONNECTED,
connection,
@@ -198,7 +209,7 @@ function _connectionEstablished(connection) {
* error: string
* }}
*/
function _connectionFailed(connection, error) {
function _connectionFailed(connection, error: string) {
return {
type: CONNECTION_FAILED,
connection,

View File

@@ -1,10 +1,15 @@
/* global APP, JitsiMeetJS */
/* @flow */
import type { Dispatch } from 'redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { SET_DOMAIN } from './actionTypes';
import './reducer';
declare var APP: Object;
declare var JitsiMeetJS: Object;
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -14,7 +19,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @returns {Promise<JitsiConnection>}
*/
export function connect() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
// XXX Lib-jitsi-meet does not accept uppercase letters.
@@ -88,7 +93,7 @@ export function disconnect() {
* domain: string
* }}
*/
export function setDomain(domain) {
export function setDomain(domain: string) {
return {
type: SET_DOMAIN,
domain

View File

@@ -1,3 +1,5 @@
/* @flow */
/**
* Returns current domain.
*
@@ -5,7 +7,7 @@
* state.
* @returns {(string|undefined)}
*/
export function getDomain(stateOrGetState) {
export function getDomain(stateOrGetState: Function | Object) {
const state
= typeof stateOrGetState === 'function'
? stateOrGetState()

View File

@@ -1,3 +1,5 @@
/* @flow */
import { ReducerRegistry, setStateProperty } from '../redux';
import {
@@ -9,20 +11,22 @@ import {
/**
* Reduces the Redux actions of the feature base/connection.
*/
ReducerRegistry.register('features/base/connection', (state = {}, action) => {
switch (action.type) {
case CONNECTION_DISCONNECTED:
return _connectionDisconnected(state, action);
ReducerRegistry.register(
'features/base/connection',
(state: Object = {}, action: Object) => {
switch (action.type) {
case CONNECTION_DISCONNECTED:
return _connectionDisconnected(state, action);
case CONNECTION_ESTABLISHED:
return _connectionEstablished(state, action);
case CONNECTION_ESTABLISHED:
return _connectionEstablished(state, action);
case SET_DOMAIN:
return _setDomain(state, action);
}
case SET_DOMAIN:
return _setDomain(state, action);
}
return state;
});
return state;
});
/**
* Reduces a specific Redux action CONNECTION_DISCONNECTED of the feature
@@ -34,7 +38,7 @@ ReducerRegistry.register('features/base/connection', (state = {}, action) => {
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _connectionDisconnected(state, action) {
function _connectionDisconnected(state: Object, action: Object) {
if (state.connection === action.connection) {
return setStateProperty(state, 'connection', undefined);
}
@@ -52,7 +56,7 @@ function _connectionDisconnected(state, action) {
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _connectionEstablished(state, action) {
function _connectionEstablished(state: Object, action: Object) {
return setStateProperty(state, 'connection', action.connection);
}
@@ -62,9 +66,10 @@ function _connectionEstablished(state, action) {
*
* @param {string} domain - The domain with which the returned options are to be
* populated.
* @private
* @returns {Object}
*/
function _constructConnectionOptions(domain) {
function _constructConnectionOptions(domain: string) {
// FIXME The HTTPS scheme for the BOSH URL works with meet.jit.si on both
// mobile & Web. It also works with beta.meet.jit.si on Web. Unfortunately,
// it doesn't work with beta.meet.jit.si on mobile. Temporarily, use the
@@ -88,7 +93,7 @@ function _constructConnectionOptions(domain) {
boshProtocol || (boshProtocol = 'https:');
return {
bosh: `${boshProtocol}//${domain}/http-bind`,
bosh: `${String(boshProtocol)}//${domain}/http-bind`,
hosts: {
domain,
focus: `focus.${domain}`,
@@ -106,7 +111,7 @@ function _constructConnectionOptions(domain) {
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _setDomain(state, action) {
function _setDomain(state: Object, action: Object) {
return {
...state,
connectionOptions: {

View File

@@ -1,8 +1,6 @@
import { NativeModules } from 'react-native';
import { RTCPeerConnection, RTCSessionDescription } from 'react-native-webrtc';
import { Platform } from '../../react';
import { POSIX } from '../../react-native';
// XXX At the time of this writing extending RTCPeerConnection using ES6 'class'
// and 'extends' causes a runtime error related to the attempt to define the
// onaddstream property setter. The error mentions that babelHelpers.set is
@@ -106,6 +104,7 @@ _RTCPeerConnection.prototype.setRemoteDescription = function(
/**
* Logs at error level.
*
* @private
* @returns {void}
*/
function _LOGE(...args) {
@@ -119,6 +118,8 @@ function _LOGE(...args) {
*
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
* which specifies the configuration of the remote end of the connection.
* @private
* @private
* @returns {Promise}
*/
function _setRemoteDescription(sessionDescription) {
@@ -160,12 +161,13 @@ function _setRemoteDescription(sessionDescription) {
*
* @param {RTCSessionDescription} sdp - The RTCSessionDescription which
* specifies the configuration of the remote end of the connection.
* @private
* @returns {Promise}
*/
function _synthesizeIPv6Addresses(sdp) {
// The synthesis of IPv6 addresses is implemented on iOS only at the time of
// this writing.
if (Platform.OS !== 'ios') {
if (!NativeModules.POSIX) {
return Promise.resolve(sdp);
}
@@ -184,6 +186,7 @@ function _synthesizeIPv6Addresses(sdp) {
*
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
* for which IPv6 addresses will be synthesized.
* @private
* @returns {{
* ips: Map,
* lines: Array
@@ -194,6 +197,7 @@ function _synthesizeIPv6Addresses0(sessionDescription) {
let start = 0;
const lines = [];
const ips = new Map();
const getaddrinfo = NativeModules.POSIX.getaddrinfo;
do {
const end = sdp.indexOf('\r\n', start);
@@ -232,7 +236,7 @@ function _synthesizeIPv6Addresses0(sessionDescription) {
if (v && typeof v === 'string') {
resolve(v);
} else {
POSIX.getaddrinfo(ip).then(
getaddrinfo(ip).then(
value => {
if (value.indexOf(':') === -1
|| value === ips.get(ip)) {
@@ -278,6 +282,7 @@ function _synthesizeIPv6Addresses0(sessionDescription) {
* @param {Map} ips - A Map of IPv4 addresses found in the specified
* sessionDescription to synthesized IPv6 addresses.
* @param {Array} lines - The lines of the specified sessionDescription.
* @private
* @returns {RTCSessionDescription} A RTCSessionDescription that represents the
* result of the synthesis of IPv6 addresses.
*/

View File

@@ -11,8 +11,8 @@ import {
* Initial state of 'features/base/lib-jitsi-meet'.
*
* @type {{
* initializationError: null,
* initialized: boolean
* initializationError: null,
* initialized: boolean
* }}
*/
const INITIAL_STATE = {

View File

@@ -1,3 +1,7 @@
/* @flow */
import type { Dispatch } from 'redux';
import {
SET_AUDIO_MUTED,
SET_CAMERA_FACING_MODE,
@@ -17,7 +21,7 @@ import './reducer';
* muted: boolean
* }}
*/
export function setAudioMuted(muted) {
export function setAudioMuted(muted: boolean) {
return {
type: SET_AUDIO_MUTED,
muted
@@ -33,7 +37,7 @@ export function setAudioMuted(muted) {
* cameraFacingMode: CAMERA_FACING_MODE
* }}
*/
export function setCameraFacingMode(cameraFacingMode) {
export function setCameraFacingMode(cameraFacingMode: CAMERA_FACING_MODE) {
return {
type: SET_CAMERA_FACING_MODE,
cameraFacingMode
@@ -50,7 +54,7 @@ export function setCameraFacingMode(cameraFacingMode) {
* muted: boolean
* }}
*/
export function setVideoMuted(muted) {
export function setVideoMuted(muted: boolean) {
return {
type: SET_VIDEO_MUTED,
muted
@@ -63,7 +67,7 @@ export function setVideoMuted(muted) {
* @returns {Function}
*/
export function toggleAudioMuted() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const muted = getState()['features/base/media'].audio.muted;
return dispatch(setAudioMuted(!muted));
@@ -76,7 +80,7 @@ export function toggleAudioMuted() {
* @returns {Function}
*/
export function toggleCameraFacingMode() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
let cameraFacingMode
= getState()['features/base/media'].video.facingMode;
@@ -95,7 +99,7 @@ export function toggleCameraFacingMode() {
* @returns {Function}
*/
export function toggleVideoMuted() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const muted = getState()['features/base/media'].video.muted;
return dispatch(setVideoMuted(!muted));

View File

@@ -140,9 +140,10 @@ class VideoTrack extends AbstractVideoTrack {
// eslint-disable-next-line valid-jsdoc
/**
* @inheritdoc
*
* Animate the setting of the video track to be rendered by this instance.
*
* @inheritdoc
* @protected
*/
_setVideoTrack(videoTrack) {
// If JitsiTrack instance didn't change, that means some other track's

View File

@@ -1,3 +1,5 @@
/* @flow */
import { CONFERENCE_LEFT } from '../conference';
import { MiddlewareRegistry } from '../redux';
import { setTrackMuted, TRACK_ADDED } from '../tracks';
@@ -21,11 +23,11 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_LEFT:
resetInitialMediaState(store);
_resetInitialMediaState(store);
break;
case TRACK_ADDED:
action.track.local && syncTrackMutedState(store, action.track);
action.track.local && _syncTrackMutedState(store, action.track);
break;
}
@@ -36,9 +38,10 @@ MiddlewareRegistry.register(store => next => action => {
* Resets initial media state.
*
* @param {Store} store - Redux store.
* @private
* @returns {void}
*/
function resetInitialMediaState(store) {
function _resetInitialMediaState(store) {
const { dispatch, getState } = store;
const state = getState()['features/base/media'];
@@ -53,9 +56,10 @@ function resetInitialMediaState(store) {
*
* @param {Store} store - Redux store.
* @param {Track} track - Local media track.
* @private
* @returns {void}
*/
function syncTrackMutedState(store, track) {
function _syncTrackMutedState(store, track) {
const state = store.getState()['features/base/media'];
const muted = state[track.mediaType].muted;

View File

@@ -31,9 +31,10 @@ const AUDIO_INITIAL_MEDIA_STATE = {
* @param {AudioMediaState} state - Media state of local audio.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @private
* @returns {AudioMediaState}
*/
function audio(state = AUDIO_INITIAL_MEDIA_STATE, action) {
function _audio(state = AUDIO_INITIAL_MEDIA_STATE, action) {
switch (action.type) {
case SET_AUDIO_MUTED:
return {
@@ -70,9 +71,10 @@ const VIDEO_INITIAL_MEDIA_STATE = {
* @param {VideoMediaState} state - Media state of local video.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @private
* @returns {VideoMediaState}
*/
function video(state = VIDEO_INITIAL_MEDIA_STATE, action) {
function _video(state = VIDEO_INITIAL_MEDIA_STATE, action) {
switch (action.type) {
case SET_CAMERA_FACING_MODE:
return {
@@ -102,6 +104,6 @@ function video(state = VIDEO_INITIAL_MEDIA_STATE, action) {
* @returns {Object}
*/
ReducerRegistry.register('features/base/media', combineReducers({
audio,
video
audio: _audio,
video: _video
}));

View File

@@ -1,75 +0,0 @@
/**
* Object describing application route.
*
* @typedef {Object} Route
* @property {Component} component - React Component constructor.
* @property {string} path - URL route, required for web routing.
*/
/**
* A registry for Navigator routes, allowing features to register themselves
* without needing to create additional inter-feature dependencies.
*/
class RouteRegistry {
/**
* Initializes a new RouteRegistry instance.
*/
constructor() {
/**
* The set of registered routes.
*
* @private
*/
this._routeRegistry = new Set();
}
/**
* Returns all registered routes.
*
* @returns {Route[]}
*/
getRoutes() {
// We use the destructuring operator to 'clone' the route object to
// prevent modifications from outside (e.g. React Native's Navigator
// extends it with additional properties).
return [ ...this._routeRegistry ].map(r => {
return { ...r };
});
}
/**
* Returns registered route by name if any.
*
* @param {Object} component - The React Component (class) of the route to
* retrieve.
* @returns {Route|null}
*/
getRouteByComponent(component) {
const route
= [ ...this._routeRegistry ].find(r => r.component === component);
// We use destructuring operator to 'clone' route object to prevent
// modifications from outside (e.g. React Native's Navigator extends
// it with some additional properties).
return route ? { ...route } : null;
}
/**
* Adds a route to this registry.
*
* @param {Route} route - Route definition object.
* @returns {void}
*/
register(route) {
if (this._routeRegistry.has(route)) {
throw new Error(`Route ${route.component} is registered already!`);
}
this._routeRegistry.add(route);
}
}
/**
* The public singleton instance of the RouteRegistry class.
*/
export default new RouteRegistry();

View File

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

View File

@@ -48,9 +48,10 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE
* @param {Participant} action.participant - Information about participant to be
* added/modified.
* @param {JitsiConference} action.conference - Conference instance.
* @private
* @returns {Participant|undefined}
*/
function participant(state, action) {
function _participant(state, action) {
switch (action.type) {
case DOMINANT_SPEAKER_CHANGED:
// Only one dominant speaker is allowed.
@@ -146,7 +147,7 @@ function participant(state, action) {
ReducerRegistry.register('features/base/participants', (state = [], action) => {
switch (action.type) {
case PARTICIPANT_JOINED:
return [ ...state, participant(undefined, action) ];
return [ ...state, _participant(undefined, action) ];
case PARTICIPANT_LEFT:
return state.filter(p => p.id !== action.participant.id);
@@ -155,7 +156,7 @@ ReducerRegistry.register('features/base/participants', (state = [], action) => {
case PARTICIPANT_ID_CHANGED:
case PARTICIPANT_UPDATED:
case PIN_PARTICIPANT:
return state.map(p => participant(p, action));
return state.map(p => _participant(p, action));
default:
return state;

View File

@@ -1,3 +0,0 @@
import { NativeModules } from 'react-native';
export default NativeModules.POSIX;

View File

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

View File

@@ -1,3 +1,5 @@
/* @flow */
// Re-export react-native's Platform because we want to provide a minimal
// equivalent on Web.
import { Platform } from 'react-native';

View File

@@ -1,3 +1,5 @@
/* @flow */
const userAgent = navigator.userAgent;
let OS;

View File

@@ -0,0 +1,119 @@
/* @flow */
import { Component } from 'react';
/**
* Object describing application route.
*
* @typedef {Object} Route
* @property {Component} component - React Component constructor.
* @property {string} path - URL route, required for web routing.
*/
type Route = {
component: Class<Component<*>>, // eslint-disable-line no-undef
path: string
};
/**
* A registry for Navigator routes, allowing features to register themselves
* without needing to create additional inter-feature dependencies.
*/
class RouteRegistry {
_elements: Array<Route>;
/**
* Initializes a new RouteRegistry instance.
*/
constructor() {
/**
* The set of registered routes.
*
* @private
* @type {Route[]}
*/
this._elements = [];
}
/**
* Determines whether two specific Routes are equal i.e. they describe one
* and the same abstract route.
*
* @param {Object} a - The Route to compare to b.
* @param {Object} b - The Route to compare to a.
* @returns {boolean} True if the specified a and b describe one and the
* same abstract route; otherwise, false.
*/
areRoutesEqual(a: Route, b: Route) {
if (a === b) { // reflexive
return true;
}
if (!a) {
return !b;
}
if (!b) {
return !a;
}
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return (
aKeys.length === bKeys.length /* symmetric */
&& aKeys.every(key => a[key] === b[key]));
}
/**
* Returns all registered routes.
*
* @returns {Route[]}
*/
getRoutes() {
// We use the destructuring operator to 'clone' the route object to
// prevent modifications from outside (e.g. React Native's Navigator
// extends it with additional properties).
return this._elements.map(r => {
return { ...r };
});
}
/* eslint-disable no-undef */
/**
* Returns registered route by name if any.
*
* @param {Component} component - The React Component (class) of the route
* to retrieve.
* @returns {Route|null}
*/
getRouteByComponent(component: Class<Component<*>>) {
/* eslint-enable no-undef */
const route = this._elements.find(r => r.component === component);
// We use destructuring operator to 'clone' route object to prevent
// modifications from outside (e.g. React Native's Navigator extends
// it with some additional properties).
return route ? { ...route } : null;
}
/**
* Adds a route to this registry.
*
* @param {Route} route - Route definition object.
* @returns {void}
*/
register(route: Route) {
if (this._elements.includes(route)) {
throw new Error(
`Route ${String(route.component)} is registered already!`);
}
this._elements.push(route);
}
}
/**
* The public singleton instance of the RouteRegistry class.
*/
export default new RouteRegistry();

View File

@@ -1,11 +1,16 @@
/* global APP, interfaceConfig */
/* @flow */
import React, { Component } from 'react';
declare var APP: Object;
declare var interfaceConfig: Object;
/**
* The CSS style of the element with CSS class <tt>rightwatermark</tt>.
*
* @private
*/
const RIGHT_WATERMARK_STYLE = {
const _RIGHT_WATERMARK_STYLE = {
backgroundImage: 'url(images/rightwatermark.png)'
};
@@ -14,13 +19,22 @@ const RIGHT_WATERMARK_STYLE = {
* etc.
*/
export class Watermarks extends Component {
state = {
brandWatermarkLink: String,
jitsiWatermarkLink: String,
showBrandWatermark: Boolean,
showJitsiWatermark: Boolean,
showJitsiWatermarkForGuests: Boolean,
showPoweredBy: Boolean
};
/**
* Initializes a new Watermarks instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
constructor(props: Object) {
super(props);
let showBrandWatermark;
@@ -87,7 +101,7 @@ export class Watermarks extends Component {
target = '_new'>
<div
className = 'watermark rightwatermark'
style = { RIGHT_WATERMARK_STYLE } />
style = { _RIGHT_WATERMARK_STYLE } />
</a>
);
}

View File

@@ -1,3 +1,7 @@
/* @flow */
/* eslint-disable flowtype/space-before-type-colon */
/**
* Prevents further propagation of the events to be handler by a specific event
* handler/listener in the capturing and bubbling phases.
@@ -7,8 +11,12 @@
* @returns {Function} An event handler/listener to be used in place of the
* specified eventHandler in order to stop the events from propagating.
*/
export function stopEventPropagation(eventHandler) {
return ev => {
export function stopEventPropagation<T>(eventHandler: (ev: Event) => T)
: (ev: Event) => T {
/* eslint-enable flowtype/space-before-type-colon */
return (ev: Event): T => {
const r = eventHandler(ev);
// React Native does not propagate the press event so, for the sake of

View File

@@ -1,4 +1,5 @@
export * from './components';
export * from './functions';
export { default as Platform } from './Platform';
export { default as RouteRegistry } from './RouteRegistry';
export { default as Symbol } from './Symbol';

View File

@@ -1,33 +1,45 @@
/* @flow */
import { applyMiddleware } from 'redux';
import type { Middleware } from 'redux';
/**
* A registry for Redux middleware, allowing features to register their
* middleware without needing to create additional inter-feature dependencies.
*/
class MiddlewareRegistry {
_elements: Array<Middleware<*, *>>;
/**
* Creates a MiddlewareRegistry instance.
*/
constructor() {
/**
* The set of registered middleware.
*
* @private
* @type {Middleware[]}
*/
this.middlewareRegistry = new Set();
this._elements = [];
}
/**
* Applies all registered middleware into a store enhancer.
* (@link http://redux.js.org/docs/api/applyMiddleware.html).
*
* @param {Function[]} additional - Any additional middleware that need to
* @param {Middleware[]} additional - Any additional middleware that need to
* be included (such as middleware from third-party modules).
* @returns {Function}
* @returns {Middleware}
*/
applyMiddleware(...additional) {
return applyMiddleware(
...this.middlewareRegistry,
applyMiddleware(...additional: Array<Middleware<*, *>>) {
// XXX The explicit definition of the local variable middlewares is to
// satisfy flow.
const middlewares = [
...this._elements,
...additional
);
];
return applyMiddleware(...middlewares);
}
/**
@@ -35,11 +47,11 @@ class MiddlewareRegistry {
*
* The method is to be invoked only before {@link #applyMiddleware()}.
*
* @param {Function} middleware - A Redux middleware.
* @param {Middleware} middleware - A Redux middleware.
* @returns {void}
*/
register(middleware) {
this.middlewareRegistry.add(middleware);
register(middleware: Middleware<*, *>) {
this._elements.push(middleware);
}
}

View File

@@ -1,10 +1,21 @@
/* @flow */
import { combineReducers } from 'redux';
import type { Reducer } from 'redux';
/**
* The type of the dictionary/map which associates a reducer (function) with the
* name of he Redux state property managed by the reducer.
*/
declare type NameReducerMap<S, A> = { [name: string]: Reducer<S, A> };
/**
* A registry for Redux reducers, allowing features to register themselves
* without needing to create additional inter-feature dependencies.
*/
class ReducerRegistry {
_elements: NameReducerMap<*, *>;
/**
* Creates a ReducerRegistry instance.
*/
@@ -12,8 +23,11 @@ class ReducerRegistry {
/**
* The set of registered reducers, keyed based on the field each reducer
* will manage.
*
* @private
* @type {NameReducerMap}
*/
this.reducerRegistry = {};
this._elements = {};
}
/**
@@ -23,9 +37,9 @@ class ReducerRegistry {
* included (such as reducers from third-party modules).
* @returns {Function}
*/
combineReducers(additional = {}) {
combineReducers(additional: NameReducerMap<*, *> = {}) {
return combineReducers({
...this.reducerRegistry,
...this._elements,
...additional
});
}
@@ -37,11 +51,11 @@ class ReducerRegistry {
*
* @param {string} name - The field in the state object that will be managed
* by the provided reducer.
* @param {Function} reducer - A Redux reducer.
* @param {Reducer} reducer - A Redux reducer.
* @returns {void}
*/
register(name, reducer) {
this.reducerRegistry[name] = reducer;
register(name: string, reducer: Reducer<*, *>) {
this._elements[name] = reducer;
}
}

View File

@@ -53,39 +53,6 @@ export function destroyLocalTracks() {
.map(t => t.jitsiTrack)));
}
/**
* Returns true if the provided JitsiTrack should be rendered as a mirror.
*
* We only want to show a video in mirrored mode when:
* 1) The video source is local, and not remote.
* 2) The video source is a camera, not a desktop (capture).
* 3) The camera is capturing the user, not the environment.
*
* TODO Similar functionality is part of lib-jitsi-meet. This function should be
* removed after https://github.com/jitsi/lib-jitsi-meet/pull/187 is merged.
*
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
* @private
* @returns {boolean}
*/
function _shouldMirror(track) {
return (
track
&& track.isLocal()
&& track.isVideoTrack()
// XXX Type of the return value of
// JitsiLocalTrack#getCameraFacingMode() happens to be named
// CAMERA_FACING_MODE as well, it's defined by lib-jitsi-meet. Note
// though that the type of the value on the right side of the
// equality check is defined by jitsi-meet-react. The type
// definitions are surely compatible today but that may not be the
// case tomorrow.
&& track.getCameraFacingMode() === CAMERA_FACING_MODE.USER
&& !track.isScreenSharing()
);
}
/**
* Create an action for when a new track has been signaled to be added to the
* conference.
@@ -240,6 +207,7 @@ function _disposeAndRemoveTracks(tracks) {
* through.
* @param {MEDIA_TYPE} mediaType - The <tt>MEDIA_TYPE</tt> of the first
* <tt>JitsiLocalTrack</tt> to be returned.
* @private
* @returns {JitsiLocalTrack} The first <tt>JitsiLocalTrack</tt>, if any, in the
* specified <tt>tracks</tt> of the specified <tt>mediaType</tt>.
*/
@@ -287,12 +255,46 @@ function _getLocalTracksToChange(currentTracks, newTracks) {
};
}
/**
* Returns true if the provided JitsiTrack should be rendered as a mirror.
*
* We only want to show a video in mirrored mode when:
* 1) The video source is local, and not remote.
* 2) The video source is a camera, not a desktop (capture).
* 3) The camera is capturing the user, not the environment.
*
* TODO Similar functionality is part of lib-jitsi-meet. This function should be
* removed after https://github.com/jitsi/lib-jitsi-meet/pull/187 is merged.
*
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
* @private
* @returns {boolean}
*/
function _shouldMirror(track) {
return (
track
&& track.isLocal()
&& track.isVideoTrack()
// XXX Type of the return value of
// JitsiLocalTrack#getCameraFacingMode() happens to be named
// CAMERA_FACING_MODE as well, it's defined by lib-jitsi-meet. Note
// though that the type of the value on the right side of the
// equality check is defined by jitsi-meet-react. The type
// definitions are surely compatible today but that may not be the
// case tomorrow.
&& track.getCameraFacingMode() === CAMERA_FACING_MODE.USER
&& !track.isScreenSharing()
);
}
/**
* Set new local tracks replacing any existing tracks that were previously
* available. Currently only one audio and one video local tracks are allowed.
*
* @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} [newTracks=[]] - List of new
* media tracks.
* @private
* @returns {Function}
*/
function _updateLocalTracks(newTracks = []) {

View File

@@ -1,3 +1,5 @@
/* @flow */
import { LIB_DISPOSED, LIB_INITIALIZED } from '../lib-jitsi-meet';
import {
MEDIA_TYPE,
@@ -67,7 +69,7 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {Track} The local <tt>Track</tt> associated with the specified
* <tt>mediaType</tt> in the specified <tt>store</tt>.
*/
function _getLocalTrack(store, mediaType) {
function _getLocalTrack(store, mediaType: MEDIA_TYPE) {
return getLocalTrack(store.getState()['features/base/tracks'], mediaType);
}
@@ -82,7 +84,7 @@ function _getLocalTrack(store, mediaType) {
* @private
* @returns {void}
*/
function _setMuted(store, action, mediaType) {
function _setMuted(store, action, mediaType: MEDIA_TYPE) {
const localTrack = _getLocalTrack(store, mediaType);
localTrack && setTrackMuted(localTrack.jitsiTrack, action.muted);

View File

@@ -1,2 +1,3 @@
export * from './interceptComponent';
export * from './loadScript';
export * from './roomnameGenerator';

View File

@@ -0,0 +1,57 @@
import { Platform } from '../react';
import { UnsupportedMobileBrowser } from '../../unsupported-browser';
/**
* Array of rules defining whether we should intercept component to render
* or not.
*
* @private
* @returns {ReactElement|void}
* @type {Function[]}
*/
const _RULES = [
/**
* This rule describes case when user opens application using mobile
* browser. In order to promote the app, we choose to suggest the mobile
* app even if the browser supports the app (e.g. Google Chrome with
* WebRTC support on Android).
*
* @returns {UnsupportedMobileBrowser|void} If the rule is satisfied then
* we should intercept existing component by UnsupportedMobileBrowser.
*/
() => {
const OS = Platform.OS;
if (OS === 'android' || OS === 'ios') {
return UnsupportedMobileBrowser;
}
}
];
/**
* Utility method that responsible for intercepting of route components based on
* the set of defined rules.
*
* @param {Object|Function} stateOrGetState - Either Redux state object or
* getState() function.
* @param {ReactElement} currentComponent - Current route component to render.
* @returns {ReactElement} If any of rules is satisfied returns intercepted
* component.
*/
export function interceptComponent(stateOrGetState, currentComponent) {
let result;
const state
= typeof stateOrGetState === 'function'
? stateOrGetState()
: stateOrGetState;
for (const rule of _RULES) {
result = rule(state);
if (result) {
break;
}
}
return result || currentComponent;
}

View File

@@ -1,3 +1,5 @@
/* @flow */
/**
* Alphanumeric characters.
* @const
@@ -18,7 +20,7 @@ const HEX_DIGITS = '0123456789abcdef';
* @returns {string} A string of random alphanumeric characters with the
* specified length.
*/
export function randomAlphanumString(length) {
export function randomAlphanumString(length: number) {
return _randomString(length, ALPHANUM);
}
@@ -28,7 +30,7 @@ export function randomAlphanumString(length) {
* @param {Array|string} arr - Source.
* @returns {Array|string} Array element or string character.
*/
export function randomElement(arr) {
export function randomElement(arr: [any] | string) {
return arr[randomInt(0, arr.length - 1)];
}
@@ -48,7 +50,7 @@ export function randomHexDigit() {
* @returns {string} A string of random hexadecimal digits with the specified
* length.
*/
export function randomHexString(length) {
export function randomHexString(length: number) {
return _randomString(length, HEX_DIGITS);
}
@@ -59,7 +61,7 @@ export function randomHexString(length) {
* @param {number} max - The maximum value for the generated number.
* @returns {number} Random int number.
*/
export function randomInt(min, max) {
export function randomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
@@ -69,6 +71,7 @@ export function randomInt(min, max) {
* @param {number} length - The length of the string to return.
* @param {string} characters - The characters from which the returned string is
* to be constructed.
* @private
* @returns {string} A string of random characters with the specified length.
*/
function _randomString(length, characters) {

View File

@@ -1,3 +1,5 @@
/* @flow */
import { randomElement } from './randomUtil';
/*
@@ -150,7 +152,7 @@ const _CONJUNCTION_ = [
* Maps a string (category name) to the array of words from that category.
* @const
*/
const CATEGORIES = {
const CATEGORIES: { [key: string]: Array<string> } = {
_ADJECTIVE_,
_ADVERB_,
_PLURALNOUN_,
@@ -210,7 +212,7 @@ export function generateRoomWithoutSeparator() {
// that names from patterns with more options).
let name = randomElement(PATTERNS);
while (hasTemplate(name)) {
while (_hasTemplate(name)) {
for (const template in CATEGORIES) { // eslint-disable-line guard-for-in
const word = randomElement(CATEGORIES[template]);
@@ -226,10 +228,11 @@ export function generateRoomWithoutSeparator() {
* templates/categories.
*
* @param {string} s - String containing categories.
* @private
* @returns {boolean} True if the specified string contains at least one of the
* templates/categories; otherwise, false.
*/
function hasTemplate(s) {
function _hasTemplate(s) {
for (const template in CATEGORIES) {
if (s.indexOf(template) >= 0) {
return true;

View File

@@ -13,11 +13,14 @@ import { styles } from './styles';
/**
* The timeout in milliseconds after which the toolbar will be hidden.
*
* @private
* @type {number}
*/
const TOOLBAR_TIMEOUT_MS = 5000;
const _TOOLBAR_TIMEOUT_MS = 5000;
/**
* The conference page of the application.
* The conference page of the mobile (i.e. React Native) application.
*/
class Conference extends Component {
/**
@@ -220,7 +223,7 @@ class Conference extends Component {
this._clearToolbarTimeout();
if (toolbarVisible) {
this._toolbarTimeout
= setTimeout(this._onClick, TOOLBAR_TIMEOUT_MS);
= setTimeout(this._onClick, _TOOLBAR_TIMEOUT_MS);
}
}
}
@@ -229,11 +232,12 @@ class Conference extends Component {
* Maps (parts of) the Redux state to the associated Conference's props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _passwordRequired: boolean
* }}
*/
function mapStateToProps(state) {
function _mapStateToProps(state) {
return {
/**
* The indicator which determines whether a password is required to join
@@ -255,4 +259,4 @@ function mapStateToProps(state) {
};
}
export default reactReduxConnect(mapStateToProps)(Conference);
export default reactReduxConnect(_mapStateToProps)(Conference);

View File

@@ -5,17 +5,22 @@ import { connect as reactReduxConnect } from 'react-redux';
import { connect, disconnect } from '../../base/connection';
import { Watermarks } from '../../base/react';
import { FeedbackButton } from '../../feedback';
/**
* For legacy reasons, inline style for display none.
* @type {{display: string}}
*
* @private
* @type {{
* display: string
* }}
*/
const DISPLAY_NONE_STYLE = {
const _DISPLAY_NONE_STYLE = {
display: 'none'
};
/**
* Implements a React Component which renders initial conference layout
* The conference page of the Web application.
*/
class Conference extends Component {
@@ -67,7 +72,7 @@ class Conference extends Component {
<div
className = 'notice'
id = 'notice'
style = { DISPLAY_NONE_STYLE }>
style = { _DISPLAY_NONE_STYLE }>
<span
className = 'noticeText'
id = 'noticeText' />
@@ -83,9 +88,9 @@ class Conference extends Component {
className = 'toolbar'
id = 'extendedToolbar'>
<div id = 'extendedToolbarButtons' />
<a
className = 'button icon-feedback'
id = 'feedbackButton' />
<FeedbackButton />
<div id = 'sideToolbarContainer' />
</div>
<div id = 'videospace'>
@@ -134,16 +139,14 @@ class Conference extends Component {
<span
className = 'videocontainer'
id = 'localVideoContainer'>
<div
className = 'videocontainer__background' />
<div className = 'videocontainer__background' />
<span id = 'localVideoWrapper' />
<audio
autoPlay = { true }
id = 'localAudio'
muted = { true } />
<div className = 'videocontainer__toolbar' />
<div
className = 'videocontainer__toptoolbar' />
<div className = 'videocontainer__toptoolbar' />
<div
className
= 'videocontainer__hoverOverlay' />

View File

@@ -155,12 +155,13 @@ function _toBoolean(value, undefinedValue) {
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The React Component props passed to the associated
* (instance of) ParticipantView.
* @private
* @returns {{
* _avatar: string,
* _videoTrack: Track
* }}
*/
function mapStateToProps(state, ownProps) {
function _mapStateToProps(state, ownProps) {
const participantId = ownProps.participantId;
const participant
= getParticipantById(
@@ -177,4 +178,4 @@ function mapStateToProps(state, ownProps) {
};
}
export default connect(mapStateToProps)(ParticipantView);
export default connect(_mapStateToProps)(ParticipantView);

View File

@@ -4,7 +4,7 @@ import BoshAddressChoice from '../../../modules/config/BoshAddressChoice';
import HttpConfigFetch from '../../../modules/config/HttpConfigFetch';
import ConferenceUrl from '../../../modules/URL/ConferenceUrl';
import { RouteRegistry } from '../base/navigator';
import { RouteRegistry } from '../base/react';
import { Conference } from './components';
@@ -40,7 +40,8 @@ function _initConference() {
* Promise wrapper on obtain config method. When HttpConfigFetch will be moved
* to React app it's better to use load config instead.
*
* @param {string} location - URL of the domain.
* @param {string} location - URL of the domain from which the config is to be
* obtained.
* @param {string} room - Room name.
* @private
* @returns {Promise}

View File

@@ -0,0 +1,47 @@
/* @flow */
import React, { Component } from 'react';
declare var config: Object;
/**
* Implements a Web/React Component which renders a feedback button.
*/
export class FeedbackButton extends Component {
state = {
callStatsID: String
};
/**
* Initializes a new FeedbackButton instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Object) {
super(props);
this.state = {
callStatsID: config.callStatsID
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
// If callstats.io-support is not configured, skip rendering.
if (!this.state.callStatsID) {
return null;
}
return (
<a
className = 'button icon-feedback'
id = 'feedbackButton' />
);
}
}

View File

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

View File

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

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