Compare commits

..

13 Commits

Author SHA1 Message Date
Aaron van Meerten
20b597abda Merge branch 'analytics_include_region_info' of github.com:jitsi/jitsi-meet into analytics_include_region_info 2017-05-23 15:01:18 -05:00
Aaron van Meerten
e68be6fa84 assign isn’t supported, so do a for loop 2017-05-23 15:00:42 -05:00
Aaron van Meerten
44d75b5253 add new local file for inclusion via ssi, intended for local variables describing the server
change analytics to pull from new collection jitsiAnalyticsPermanentProperties, which is to be user-defined via javascript in local.html
2017-05-23 15:00:42 -05:00
Aaron van Meerten
440865f3ca missing assignments
lots of line breaks for coding style
2017-05-23 15:00:42 -05:00
Aaron van Meerten
e91c62142d fixed trailing comma 2017-05-23 15:00:42 -05:00
Aaron van Meerten
1a892a689e moved to explicit assignment of properties from jitsiRegionInfo
removed no longer needed jshint ignore statement
2017-05-23 15:00:42 -05:00
Aaron van Meerten
04851b4baa add jitsi regional information to all analytics events via permanent properties if available
ignore listing on modules/analytics/analytics.js to avoid es6 linting errors
2017-05-23 15:00:42 -05:00
Aaron van Meerten
beec60be47 assign isn’t supported, so do a for loop 2017-05-23 14:59:02 -05:00
Aaron van Meerten
36d40cdec9 add new local file for inclusion via ssi, intended for local variables describing the server
change analytics to pull from new collection jitsiAnalyticsPermanentProperties, which is to be user-defined via javascript in local.html
2017-05-23 14:49:34 -05:00
Aaron van Meerten
5be3504fad missing assignments
lots of line breaks for coding style
2017-05-23 09:50:14 -05:00
Aaron van Meerten
bc9ef4421a fixed trailing comma 2017-05-23 09:37:06 -05:00
Aaron van Meerten
4ffe668dd2 moved to explicit assignment of properties from jitsiRegionInfo
removed no longer needed jshint ignore statement
2017-05-23 09:26:49 -05:00
Aaron van Meerten
3cc4d44376 add jitsi regional information to all analytics events via permanent properties if available
ignore listing on modules/analytics/analytics.js to avoid es6 linting errors
2017-05-22 14:40:18 -05:00
951 changed files with 24453 additions and 83220 deletions

View File

@@ -1,5 +1,44 @@
module.exports = {
'env': {
'browser': true,
'commonjs': true,
'es6': true
},
'extends': [
'eslint-config-jitsi'
]
'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
// without explicitly specifying them (using a comment) inside of our
// files.
'__filename': false
},
'parser': 'babel-eslint',
'parserOptions': {
'ecmaFeatures': {
'experimentalObjectRestSpread': true
},
'sourceType': 'module'
},
'plugins': [
'flowtype'
],
'rules': {
'new-cap': [
'error',
{
'capIsNew': false // Behave like JSHint's newcap.
}
],
// While it is considered a best practice to avoid using methods on
// console in JavaScript that is designed to be executed in the browser
// and ESLint includes the rule among its set of recommended rules, (1)
// the general practice is to strip such calls before pushing to
// production and (2) we prefer to utilize console in lib-jitsi-meet
// (and jitsi-meet).
'no-console': 'off',
'semi': 'error'
}
};

View File

@@ -12,55 +12,46 @@
; 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
; Ignore polyfills
.*/Libraries/polyfills/.*
.*/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/@atlaskit/.*/*.js.flow
.*/node_modules/@atlassian
.*/node_modules/bower/lib/node_modules/bower-json/test/.*
.*/node_modules/immutable/dist/immutable.js.flow
.*/node_modules/jsonlint/test/.*
; FIXME Remove once we update past commit
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
.*/node_modules/react-native/local-cli/link/link.js
.*/node_modules/react-native-keep-awake/.*
.*/node_modules/babel-core/.*
.*/node_modules/bower/.*
.*/node_modules/jsonlint/.*
.*/node_modules/styled-components/.*
.*/\.git/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
node_modules/react-native/flow
flow/
[options]
emoji=true
module.system=haste
munge_underscores=true
experimental.strict_type_args=true
; FIXME Remove once we update past commit
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
module.name_mapper='^./link/link$' -> 'emptyObject'
; FIXME: munge_underscores should be false but right now there are some errors
; if we change the value to false
; Treats class properties with underscore as private. Disabled because currently
; for us "_" can mean protected too.
; munge_underscores=false
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=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
suppress_comment=\\(.\\|\n\\)*\\$FlowDisableNextLine
unsafe.enable_getters_and_setters=true
@@ -77,4 +68,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.57.0
^0.38.0

19
.gitignore vendored
View File

@@ -7,9 +7,6 @@ all.css
.remote-sync.json
.sync-config.cson
# CocoaPods
Pods/
# The following are automatically generated by the react-native command line
# utility (either with the init or upgrade option which pull in the latest
# template files recommended by Facebook for React Native).
@@ -60,11 +57,11 @@ buck-out/
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# It is recommended to not store the screenshots in the git repo. Instead, use
# fastlane to re-generate the screenshots whenever they are needed. For more
# information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
#
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots

21
.jshintignore Normal file
View File

@@ -0,0 +1,21 @@
# 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 with the maximum configuration which
# supersedes JSHint.
flow-typed/
modules/API/
modules/remotecontrol/
modules/transport/
react/
# The following are checked by ESLint with the minimum configuration which does
# not supersede JSHint but take advantage of advanced language features such as
# Facebook Flow which are not supported by JSHint.
modules/translation/translation.js
analytics.js

20
.jshintrc Normal file
View File

@@ -0,0 +1,20 @@
{
// Refer to http://jshint.com/docs/options/ for an exhaustive list of options
"asi": false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"expr": true, // true: Tolerate `ExpressionStatement` as Programs
"loopfunc": true, // true: Tolerate functions being defined in loops
"curly": false, // true: Require {} for every new block or scope
"evil": true, // true: Tolerate use of `eval` and `new Function()`
"white": true,
"undef": true, // true: Require all non-global variables to be declared (prevents global leaks)
"browser": true, // Web Browser (window, document, etc)
"node": true, // Node.js
"trailing": true,
"indent": 4, // {int} Number of spaces to use for indentation
"latedef": true, // true: Require variables/functions to be defined before being used
"newcap": true, // true: Require capitalization of all constructor functions e.g. `new F()`
"maxlen": 80, // {int} Max number of characters per line
"latedef": false, //This option prohibits the use of a variable before it was defined
"laxbreak": true, //Ignore line breaks around "=", "==", "&&", etc.
"esnext": true //support ES2015
}

View File

@@ -21,7 +21,7 @@ license agreement as either a [corporation](https://jitsi.org/ccla) or an
in the agreement, unfortunately, we cannot accept your contribution.
## Creating Pull Requests
- Make sure your code passes the linter rules beforehand. The linter is executed
- Make sure your code passes the linter rules beforehand. The linter is exeuted
automatically when committing code.
- Perform **one** logical change per pull request.
- Maintain a clean list of commits, squash them if necessary.

View File

@@ -1,11 +1,11 @@
/**
* Notifies interested parties that hangup procedure will start.
*/
export const BEFORE_HANGUP = 'conference.before_hangup';
export const BEFORE_HANGUP = "conference.before_hangup";
/**
* Notifies interested parties that desktop sharing enable/disable state is
* changed.
*/
export const DESKTOP_SHARING_ENABLED_CHANGED
= 'conference.desktop_sharing_enabled_changed';
= "conference.desktop_sharing_enabled_changed";

View File

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

View File

@@ -1,15 +1,13 @@
# 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, [secure](#security) and scalable video conferences. You can see Jitsi Meet in action [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
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.
The Jitsi Meet client runs in your browser, without the need for installing anything on your computer. You can also try it out yourself at https://meet.jit.si .
You can also try it out yourself at https://meet.jit.si .
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
## Installation
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
@@ -17,9 +15,9 @@ For other systems, or if you wish to install all components manually, see the [d
## Download
You can download Debian/Ubuntu binaries:
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions/))
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-for-testing/))
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-nightly/))
* [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))
You can download source archives (produced by ```make source-package```):
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
@@ -30,8 +28,6 @@ You can get our mobile versions from here:
## Building the sources
Node.js >= 6 is required.
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs-legacy
@@ -90,19 +86,6 @@ cd jitsi-meet
npm unlink lib-jitsi-meet
npm install
```
## Running with webpack-dev-server for development
Use it at the CLI, type
```
node_modules/.bin/webpack-dev-server
```
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable, type
```
WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com node_modules/.bin/webpack-dev-server
```
The app should be running at https://localhost:8080/
## Contributing
@@ -113,24 +96,9 @@ see our [guidelines for contributing](CONTRIBUTING.md).
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
## Security
WebRTC today does not provide a way of conducting multiparty conversations with
end-to-end encryption. As a matter of fact, unless you consistently vocally
compare DTLS fingerprints with your peers, the same goes for one-to-one calls.
As a result when using a Jitsi Meet instance, your stream is encrypted on the
network but decrypted on the machine that hosts the bridge.
The Jitsi Meet architecture allows you to deploy your own version, including
all server components, and in that case your security guarantees will be roughly
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [Atlassian](https://atlassian.com).
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
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).
## Acknowledgements

View File

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

33
analytics.js Normal file
View File

@@ -0,0 +1,33 @@
/* global ga */
(function (ctx) {
function Analytics() {
/* eslint-disable */
/**
* Google Analytics
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
/* eslint-enable */
}
Analytics.prototype.sendEvent = function (action, data) {
// empty label if missing value for it and add the value,
// the value should be integer or null
var value = data.value;
value = value? Math.round(parseFloat(value)) : null;
var label = data.label || "";
ga('send', 'event', 'jit.si',
action + '.' + data.browserName, label, value);
};
if(typeof ctx.analyticsHandlers === "undefined")
ctx.analyticsHandlers = [];
ctx.analyticsHandlers.push(Analytics);
}(window));

View File

@@ -1,420 +0,0 @@
# Jitsi Meet SDK for Android
## Build
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
2. ```bash
cd android/
./gradlew :sdk:assembleRelease
```
3. Configure the Maven repositories in which you are going to publish the
artifacts/binaries during step 4. Modify
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
in adroid/sdk/build.gradle for Jitsi Meet SDK for Android and/or
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
in android/build.gradle for the third-party react-native modules which Jitsi
Meet SDK for Android depends on and are not publicly available in Maven
repositories. Generally, if you are modifying the JavaScript code of Jitsi
Meet SDK for Android only, you will very likely need to consider the former
only.
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
repository configured in step 3:
```bash
./gradlew :sdk:publish
cd ../
```
If you would like to publish a third-party react-native module which Jitsi
Meet SDK for Android depends on and is not publicly available in Maven
repositories, replace `sdk` with the name of the react-native module. For
example, to publish react-native-webrtc:
```bash
./gradlew :react-native-webrtc:publish
```
## Install
Add the Maven repository
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
## API
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
experience and makes it reusable by third-party apps.
To get started, extends your `android.app.Activity` from
`org.jitsi.meet.sdk.JitsiMeetActivity`:
```java
package org.jitsi.example;
import org.jitsi.meet.sdk.JitsiMeetActivity;
public class MainActivity extends JitsiMeetActivity {
}
```
Alternatively, you can use the `org.jitsi.meet.sdk.JitsiMeetView` class which
extends `android.view.View`:
```java
package org.jitsi.example;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
public class MainActivity extends AppCompatActivity {
private JitsiMeetView view;
@Override
public void onBackPressed() {
if (!JitsiMeetView.onBackPressed()) {
// Invoke the default handler if it wasn't handled by React.
super.onBackPressed();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new JitsiMeetView(this);
view.loadURL(null);
setContentView(view);
}
@Override
protected void onDestroy() {
super.onDestroy();
view.dispose();
view = null;
JitsiMeetView.onHostDestroy(this);
}
@Override
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(intent);
}
@Override
protected void onResume() {
super.onResume();
JitsiMeetView.onHostResume(this);
}
@Override
protected void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
}
}
```
### JitsiMeetActivity
This class encapsulates a high level API in the form of an Android `Activity`
which displays a single `JitsiMeetView`.
#### getDefaultURL()
See JitsiMeetView.getDefaultURL.
#### getPictureInPictureEnabled()
See JitsiMeetView.getPictureInPictureEnabled.
#### getWelcomePageEnabled()
See JitsiMeetView.getWelcomePageEnabled.
#### loadURL(URL)
See JitsiMeetView.loadURL.
#### setDefaultURL(URL)
See JitsiMeetView.setDefaultURL.
#### setPictureInPictureEnabled(boolean)
See JitsiMeetView.setPictureInPictureEnabled.
#### setWelcomePageEnabled(boolean)
See JitsiMeetView.setWelcomePageEnabled.
### JitsiMeetView
The `JitsiMeetView` class is the core of Jitsi Meet SDK. It's designed to
display a Jitsi Meet conference (or a welcome page).
#### dispose()
Releases all resources associated with this view. This method MUST be called
when the Activity holding this view is going to be destroyed, usually in the
`onDestroy()` method.
#### getDefaultURL()
Returns the default base URL used to join a conference when a partial URL (e.g.
a room name only) is specified to `loadURLString`/`loadURLObject`. If not set or
if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
#### getListener()
Returns the `JitsiMeetViewListener` instance attached to the view.
#### getPictureInPictureEnabled()
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
#### getWelcomePageEnabled()
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
empty view will be rendered when not in a conference. Defaults to false.
#### loadURL(URL)
Loads a specific URL which may identify a conference to join. If the specified
URL is null and the Welcome page is enabled, the Welcome page is displayed
instead.
#### loadURLString(String)
Loads a specific URL which may identify a conference to join. If the specified
URL is null and the Welcome page is enabled, the Welcome page is displayed
instead.
#### loadURLObject(Bundle)
Loads a specific URL which may identify a conference to join. The URL is
specified in the form of a Bundle of properties which (1) internally are
sufficient to construct a URL (string) while (2) abstracting the specifics of
constructing the URL away from API clients/consumers. If the specified URL is
null and the Welcome page is enabled, the Welcome page is displayed instead.
Example:
```java
Bundle config = new Bundle();
config.putBoolean("startWithAudioMuted", true);
config.putBoolean("startWithVideoMuted", false);
Bundle urlObject = new Bundle();
urlObject.putBundle("config", config);
urlObject.putString("url", "https://meet.jit.si/Test123");
view.loadURLObject(urlObject);
```
#### setDefaultURL(URL)
Sets the default URL. See `getDefaultURL` for more information.
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
effect.
#### setListener(listener)
Sets the given listener (class implementing the `JitsiMeetViewListener`
interface) on the view.
#### setPictureInPictureEnabled(boolean)
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
automatically enables/disables Picture-in-Picture based on native platform
support.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
#### setWelcomePageEnabled(boolean)
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
information.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
#### onBackPressed()
Helper method which should be called from the activity's `onBackPressed` method.
If this function returns `true`, it means the action was handled and thus no
extra processing is required; otherwise the app should call the parent's
`onBackPressed` method.
This is a static method.
#### onHostDestroy(activity)
Helper method which should be called from the activity's `onDestroy` method.
This is a static method.
#### onHostPause(activity)
Helper method which should be called from the activity's `onPause` method.
This is a static method.
#### onHostResume(activity)
Helper method which should be called from the activity's `onResume` or `onStop`
method.
This is a static method.
#### onNewIntent(intent)
Helper method for integrating the *deep linking* functionality. If your app's
activity is launched in "singleTask" mode this method should be called from the
activity's `onNewIntent` method.
This is a static method.
#### onUserLeaveHint()
Helper method for integrating automatic Picture-in-Picture. It should be called
from the activity's `onUserLeaveHint` method.
This is a static method.
#### JitsiMeetViewListener
`JitsiMeetViewListener` provides an interface apps can implement to listen to
the state of the Jitsi Meet conference displayed in a `JitsiMeetView`.
### JitsiMeetViewAdapter
A default implementation of the `JitsiMeetViewListener` interface. Apps may
extend the class instead of implementing the interface in order to minimize
boilerplate.
##### onConferenceFailed
Called when a joining a conference was unsuccessful or when there was an error
while in a conference.
The `data` `Map` contains an "error" key describing the error and a "url" key
with the conference URL.
#### onConferenceJoined
Called when a conference was joined.
The `data` `Map` contains a "url" key with the conference URL.
#### onConferenceLeft
Called when a conference was left.
The `data` `Map` contains a "url" key with the conference URL.
#### onConferenceWillJoin
Called before a conference is joined.
The `data` `Map` contains a "url" key with the conference URL.
#### onConferenceWillLeave
Called before a conference is left.
The `data` `Map` contains a "url" key with the conference URL.
#### onLoadConfigError
Called when loading the main configuration file from the Jitsi Meet deployment
fails.
The `data` `Map` contains an "error" key with the error and a "url" key with the
conference URL which necessitated the loading of the configuration file.
## ProGuard rules
When using the SDK on a project some proguard rules have to be added in order
to avoid necessary code being stripped. Add the following to your project's
rules file:
```
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
-dontwarn android.text.StaticLayout
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# WebRTC
-keep class org.webrtc.** { *; }
-dontwarn org.chromium.build.BuildHooksAndroid
# Jisti Meet SDK
-keep class org.jitsi.meet.sdk.** { *; }
```
## Picture-in-Picture
`JitsiMeetView` will automatically adjust its UI when presented in a
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
"full" UI.
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
mode iff the platform is supported i.e. Android >= Oreo.

66
android/app/BUCK Normal file
View File

@@ -0,0 +1,66 @@
import re
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
lib_deps = []
for jarfile in glob(['libs/*.jar']):
name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile)
lib_deps.append(':' + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
for aarfile in glob(['libs/*.aar']):
name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile)
lib_deps.append(':' + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
android_library(
name = 'all-libs',
exported_deps = lib_deps
)
android_library(
name = 'app-code',
srcs = glob([
'src/main/java/**/*.java',
]),
deps = [
':all-libs',
':build_config',
':res',
],
)
android_build_config(
name = 'build_config',
package = 'org.jitsi.meet',
)
android_resource(
name = 'res',
res = 'src/main/res',
package = 'org.jitsi.meet',
)
android_binary(
name = 'app',
package_type = 'debug',
manifest = 'src/main/AndroidManifest.xml',
keystore = '//android/keystores:debug',
deps = [
':app-code',
],
)

View File

@@ -1,40 +1,159 @@
apply plugin: 'com.android.application'
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"]
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
apply from: '../../node_modules/react-native/react.gradle'
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
applicationId 'org.jitsi.meet'
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt("${version}")
versionName "1.9.${version}"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionName "1.4.${version}"
ndk {
abiFilters 'armeabi-v7a', 'x86'
}
packagingOptions {
// The project react-native does not provide 64-bit binaries at the
// time of this writing. Unfortunately, packaging any 64-bit
// binaries into the .apk will crash the app at runtime on 64-bit
// platforms.
exclude '/lib/mips64/**'
exclude '/lib/arm64-v8a/**'
exclude '/lib/x86_64/**'
exclude 'lib/x86_64/libjingle_peerconnection_so.so'
exclude 'lib/arm64-v8a/libjingle_peerconnection_so.so'
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include 'armeabi-v7a', 'x86'
}
}
buildTypes {
release {
minifyEnabled false
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ['armeabi-v7a':1, 'x86':2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':sdk')
if (project.hasProperty('JITSI_SIGNING')
&& new File(project.property('JITSI_SIGNING')).exists()) {
apply from: project.property('JITSI_SIGNING');
}
dependencies {
compile project(':react-native-background-timer')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.react:react-native:+' // From node_modules
}
apply from: '../../node_modules/react-native-vector-icons/fonts.gradle'
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}

View File

@@ -50,10 +50,6 @@
-dontwarn com.facebook.react.**
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
-dontwarn android.text.StaticLayout
# okhttp
-keepattributes Signature

View File

@@ -1,40 +1,60 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet">
<application
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet"
android:versionCode="1"
android:versionName="1.0">
<!-- 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"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="23" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:name=".MainActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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: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>
</application>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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: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" />
</application>
</manifest>

Binary file not shown.

View File

@@ -1,114 +1,46 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet;
import android.os.Bundle;
import android.util.Log;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import com.calendarevents.CalendarEventsPackage;
import java.util.Map;
/**
* The one and only {@link Activity} that the Jitsi Meet app needs. The
* {@code Activity} is launched in {@code singleTask} mode, so it will be
* created upon application initialization and there will be a single instance
* of it. Further attempts at launching the application once it was already
* launched will result in {@link Activity#onNewIntent(Intent)} being called.
*
* This {@code Activity} extends {@link JitsiMeetActivity} to keep the React
* Native CLI working, since the latter always tries to launch an
* {@code Activity} named {@code MainActivity} when doing
* {@code react-native run-android}.
*/
public class MainActivity extends JitsiMeetActivity {
public class MainActivity extends ReactActivity {
/**
* {@inheritDoc}
*
* Overrides {@link ReactActivity#createRootActivityDelegate()} to customize
* the {@link ReactRootView} with a background color that is in accord with
* the JavaScript and iOS parts of the application and causes less perceived
* visual flicker than the default background color.
*/
@Override
protected JitsiMeetView initializeView() {
JitsiMeetView view = super.initializeView();
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
/**
* {@inheritDoc}
*
* Overrides {@link ReactActivityDelegate#createRootView()} to
* customize the {@link ReactRootView} with a background color that
* is in accord with the JavaScript and iOS parts of the application
* and causes less perceived visual flicker than the default
* background color.
*/
@Override
protected ReactRootView createRootView() {
ReactRootView rootView = super.createRootView();
// XXX In order to increase (1) awareness of API breakages and (2) API
// coverage, utilize JitsiMeetViewListener in the Debug configuration of
// the app.
if (BuildConfig.DEBUG && view != null) {
view.setListener(new JitsiMeetViewListener() {
private void on(String name, Map<String, Object> data) {
// Log with the tag "ReactNative" in order to have the log
// visible in react-native log-android as well.
Log.d(
"ReactNative",
JitsiMeetViewListener.class.getSimpleName() + " "
+ name + " "
+ data);
}
@Override
public void onConferenceFailed(Map<String, Object> data) {
on("CONFERENCE_FAILED", data);
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
on("CONFERENCE_JOINED", data);
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
on("CONFERENCE_LEFT", data);
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
on("CONFERENCE_WILL_JOIN", data);
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
on("CONFERENCE_WILL_LEAVE", data);
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
on("LOAD_CONFIG_ERROR", data);
}
});
}
return view;
rootView.setBackgroundColor(0xFF111111);
return rootView;
}
};
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
// want to enable some options.
// The welcome page defaults to disabled in the SDK at the time of this
// writing but it is clearer to be explicit about what we want anyway.
setWelcomePageEnabled(true);
super.onCreate(savedInstanceState);
protected String getMainComponentName() {
return "App";
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

View File

@@ -0,0 +1,65 @@
package org.jitsi.meet;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
/**
* {@inheritDoc}
*/
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* {@inheritDoc}
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage(),
new org.jitsi.meet.proximity.ProximityPackage()
);
}
};
/**
* {@inheritDoc}
*/
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
/**
* {@inheritDoc}
*/
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!getReactNativeHost()
.getReactInstanceManager()
.getDevSupportManager()
.getDevSupportEnabled()) {
// TODO Auto-generated method stub
}
}
}

View File

@@ -1,27 +1,10 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
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.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
@@ -29,35 +12,30 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Module implementing a simple API to select the appropriate audio device for a
* conference call.
*
* Audio calls should use {@code AudioModeModule.AUDIO_CALL}, which uses the
* 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 {@code AudioModeModule.VIDEO_CALL}, which uses
* 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
* {@code AudioModeModule.DEFAULT} mode should be used.
* <tt>AudioModeModule.DEFAULT</tt> mode should be used.
*/
class AudioModeModule extends ReactContextBaseJavaModule {
public class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Constants representing the audio mode.
* - DEFAULT: Used before and after every call. It represents the default
@@ -80,13 +58,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
: Intent.ACTION_HEADSET_PLUG;
/**
* The name of {@code AudioModeModule} to be used in the React Native
* bridge.
* React Native module name.
*/
private static final String MODULE_NAME = "AudioMode";
/**
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
* Tag used when logging messages.
*/
static final String TAG = MODULE_NAME;
@@ -108,53 +85,10 @@ class AudioModeModule extends ReactContextBaseJavaModule {
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos
= audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
devices.add(DEVICE_HEADPHONES);
break;
}
}
availableDevices = devices;
Log.d(TAG, "Available audio devices: " +
availableDevices.toString());
// Reset user selection
userSelectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
/**
* {@link Runnable} for running update operation on the main thread.
*/
private final Runnable updateAudioRouteRunner
private final Runnable mainThreadRunner
= new Runnable() {
@Override
public void run() {
@@ -169,30 +103,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
private int mode = -1;
/**
* Audio device types.
*/
private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
private static final String DEVICE_EARPIECE = "EARPIECE";
private static final String DEVICE_HEADPHONES = "HEADPHONES";
private static final String DEVICE_SPEAKER = "SPEAKER";
/**
* List of currently available audio devices.
*/
private Set<String> availableDevices = new HashSet<>();
/**
* Currently selected device.
*/
private String selectedDevice;
/**
* User selected device. When null the default is used depending on the
* mode.
*/
private String userSelectedDevice;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
@@ -209,20 +119,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Do an initial detection on Android >= M.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mainThreadHandler.post(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
/**
@@ -243,37 +139,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
}
/**
* Gets the list of available audio device categories, i.e. 'bluetooth',
* 'earpiece ', 'speaker', 'headphones'.
*
* @param promise a {@link Promise} which will be resolved with an object
* containing a 'devices' key with a list of devices, plus a
* 'selected' key with the selected one.
*/
@ReactMethod
public void getAudioDevices(final Promise promise) {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
WritableMap map = Arguments.createMap();
map.putString("selected", selectedDevice);
WritableArray devices = Arguments.createArray();
for (String device : availableDevices) {
if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
// Skip earpiece when in video call mode.
continue;
}
devices.pushString(device);
}
map.putArray("devices", devices);
promise.resolve(map);
}
});
}
/**
* Gets the name for this module to be used in the React Native bridge.
* Gets the name for this module, to be used in the React Native bridge.
*
* @return a string with the module name.
*/
@@ -285,81 +151,9 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the main thread.
*
* Only used on Android >= M.
*/
void onAudioDeviceChange() {
mainThreadHandler.post(onAudioDeviceChangeRunner);
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*
* Only used on Android < M. Runs on the main thread.
*/
void onBluetoothDeviceChange() {
if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
availableDevices.add(DEVICE_BLUETOOTH);
} else {
availableDevices.remove(DEVICE_BLUETOOTH);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*
* Only used on Android < M.
*/
void onHeadsetDeviceChange() {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
availableDevices.add(DEVICE_HEADPHONES);
} else {
availableDevices.remove(DEVICE_HEADPHONES);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
});
}
/**
* Sets the user selected audio device as the active audio device.
*
* @param device the desired device which will become active.
*/
@ReactMethod
public void setAudioDevice(final String device) {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (!availableDevices.contains(device)) {
Log.d(TAG, "Audio device not available: " + device);
userSelectedDevice = null;
return;
}
if (mode != -1) {
Log.d(TAG, "User selected device set to: " + device);
userSelectedDevice = device;
updateAudioRoute(mode);
}
}
});
mainThreadHandler.post(mainThreadRunner);
}
/**
@@ -467,7 +261,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Wired headset added / removed");
onHeadsetDeviceChange();
onAudioDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
@@ -480,8 +274,8 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* Updates the audio route for the given mode.
*
* @param mode the audio mode to be used when computing the audio route.
* @return {@code true} if the audio route was updated successfully;
* {@code false}, otherwise.
* @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);
@@ -490,9 +284,8 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(null);
audioManager.setSpeakerphoneOn(false);
audioManager.setMicrophoneMute(true);
setBluetoothAudioRoute(false);
selectedDevice = null;
userSelectedDevice = null;
return true;
}
@@ -509,42 +302,31 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return false;
}
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
boolean useSpeaker = (mode == VIDEO_CALL);
// Pick the desired device based on what's available and the mode.
String audioDevice;
if (bluetoothAvailable) {
audioDevice = DEVICE_BLUETOOTH;
} else if (headsetAvailable) {
audioDevice = DEVICE_HEADPHONES;
} else if (mode == AUDIO_CALL && earpieceAvailable) {
audioDevice = DEVICE_EARPIECE;
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 {
audioDevice = DEVICE_SPEAKER;
// 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;
}
}
// Consider the user's selection
if (userSelectedDevice != null
&& availableDevices.contains(userSelectedDevice)) {
audioDevice = userSelectedDevice;
}
// If the previously selected device and the current default one
// match, do nothing.
if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
return true;
}
selectedDevice = audioDevice;
Log.d(TAG, "Selected audio device: " + audioDevice);
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
// 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

@@ -1,22 +1,7 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
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;
@@ -33,7 +18,7 @@ import android.util.Log;
* Bluetooth headsets being connected / disconnected and notifies the module
* about device changes when this occurs.
*/
class BluetoothHeadsetMonitor {
public class BluetoothHeadsetMonitor {
/**
* {@link AudioModeModule} where this monitor reports.
*/
@@ -71,7 +56,7 @@ class BluetoothHeadsetMonitor {
headsetAvailable
= (headset != null)
&& !headset.getConnectedDevices().isEmpty();
audioModeModule.onBluetoothDeviceChange();
audioModeModule.onAudioDeviceChange();
}
};
@@ -137,8 +122,7 @@ class BluetoothHeadsetMonitor {
/**
* Returns the current headset availability.
*
* @return {@code true} if there is a Bluetooth headset connected;
* {@code false}, otherwise.
* @return true if there is a Bluetooth headset connected, false otherwise.
*/
public boolean isHeadsetAvailable() {
return headsetAvailable;

View File

@@ -1,20 +1,4 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
package org.jitsi.meet.proximity;
import android.content.Context;
import android.os.PowerManager;
@@ -31,10 +15,9 @@ import com.facebook.react.bridge.UiThreadUtil;
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
*/
class ProximityModule extends ReactContextBaseJavaModule {
public class ProximityModule extends ReactContextBaseJavaModule {
/**
* The name of {@code ProximityModule} to be used in the React Native
* bridge.
* React Native module name.
*/
private static final String MODULE_NAME = "Proximity";

View File

@@ -0,0 +1,48 @@
package org.jitsi.meet.proximity;
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 ProximityModule}.
*/
public class ProximityPackage 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 ProximityModule(reactContext));
return modules;
}
/**
* {@inheritDoc}
*/
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -1,6 +1,9 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>

View File

@@ -1,157 +1,24 @@
// Top-level build file where you can add configuration options common to all
// sub-projects/modules.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenLocal()
jcenter()
// React Native (JS, Obj-C sources, Android binaries) is installed from
// npm.
maven { url "$rootDir/../node_modules/react-native/android" }
}
// Third-party react-native modules which Jitsi Meet SDK for Android depends
// on and which are not available in third-party Maven repositories need to
// be deployed in a Maven repository of ours.
//
if (project.name.startsWith('react-native-')) {
apply plugin: 'maven-publish'
publishing {
publications {}
repositories {
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
}
}
}
afterEvaluate { project ->
if (project.name.startsWith('react-native-')) {
def npmManifest = project.file('../package.json')
def json = new groovy.json.JsonSlurper().parseText(npmManifest.text)
// React Native modules have an npm peer dependency on react-native,
// they do not have an npm dependency on it. Further below though we
// choose a react-native version (range) when we represent them as
// Maven artifacts. Effectively, we are forking the projects by not
// complying with the full range of their npm peer dependency and,
// consequently, we should qualify their version.
def versionQualifier = '-jitsi-1'
if ('react-native-webrtc'.equals(project.name))
versionQualifier = '-jitsi-1'
project.version = "${json.version}${versionQualifier}"
project.android {
compileSdkVersion rootProject.ext.compileSdkVersion
if (rootProject.ext.has('buildToolsVersion')) {
buildToolsVersion rootProject.ext.buildToolsVersion
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.source
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError false
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
}
publishing.publications {
aarArchive(MavenPublication) {
groupId rootProject.ext.moduleGroupId
artifactId project.name
version project.version
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
extension "aar"
}
artifact(androidSourcesJar)
artifact(androidJavadocsJar)
pom.withXml {
def pomXml = asNode()
pomXml.appendNode('name', project.name)
pomXml.appendNode('description', json.description)
pomXml.appendNode('url', json.homepage)
if (json.license) {
def license = pomXml.appendNode('licenses').appendNode('license')
license.appendNode('name', json.license)
license.appendNode('distribution', 'repo')
}
def dependencies = pomXml.appendNode('dependencies')
configurations.getByName('releaseCompileClasspath').getResolvedConfiguration().getFirstLevelModuleDependencies().each {
def artifactId = it.moduleName
def version = it.moduleVersion
// React Native signals breaking changes by
// increasing the minor version number. So the
// (third-party) React Native modules we utilize can
// depend not on a specific react-native release but
// a wider range.
if (artifactId.equals('react-native')) {
def versionNumber = VersionNumber.parse(version)
version = "${versionNumber.major}.${versionNumber.minor}"
}
def dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', it.moduleGroup)
dependency.appendNode('artifactId', artifactId)
dependency.appendNode('version', version)
}
}
}
}
}
}
}
ext {
buildToolsVersion = "26.0.2"
compileSdkVersion = 26
minSdkVersion = 16
targetSdkVersion = 26
// The Maven artifact groupdId of the third-party react-native modules which
// Jitsi Meet SDK for Android depends on and which are not available in
// third-party Maven repositories so we have to deploy to a Maven repository
// of ours.
moduleGroupId = 'com.facebook.react'
}
// Force the version of the Android build tools we have chosen on all
// subprojects. The forcing was introduced for react-native and the third-party
// modules that we utilize such as react-native-background-timer.
subprojects { subproject ->
afterEvaluate{
if ((subproject.plugins.hasPlugin('android')
|| subproject.plugins.hasPlugin('android-library'))
&& rootProject.ext.has('buildToolsVersion')) {
android {
buildToolsVersion rootProject.ext.buildToolsVersion
}
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}

View File

@@ -1,6 +1,5 @@
#Fri Sep 08 10:42:14 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

View File

@@ -1,8 +1,8 @@
keystore(
name = "debug",
properties = "debug.keystore.properties",
store = "debug.keystore",
visibility = [
"PUBLIC",
],
name = 'debug',
store = 'debug.keystore',
properties = 'debug.keystore.properties',
visibility = [
'PUBLIC',
],
)

View File

@@ -1,165 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
buildTypes {
debug {}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:27.0.2'
compile 'com.facebook.react:react-native:+'
compile project(':react-native-background-timer')
compile project(':react-native-fetch-blob')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-locale-detector')
compile project(':react-native-sound')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')
compile project(':react-native-calendar-events')
}
// Build process helpers
//
void runBefore(String dependentTaskName, Task task) {
Task dependentTask = tasks.findByPath(dependentTaskName);
if (dependentTask != null) {
dependentTask.dependsOn task
}
}
gradle.projectsEvaluated {
android.buildTypes.all { buildType ->
def buildNameCapitalized = "${buildType.name.capitalize()}"
def bundlePath = "${buildDir}/intermediates/bundles/${buildType.name}"
// Bundle fonts in react-native-vector-icons.
//
def currentFontTask = tasks.create(
name: "copy${buildNameCapitalized}Fonts",
type: Copy) {
from("${projectDir}/../../fonts/jitsi.ttf")
from("${projectDir}/../../node_modules/react-native-vector-icons/Fonts/")
into("${bundlePath}/assets/fonts")
}
currentFontTask.dependsOn("merge${buildNameCapitalized}Resources")
currentFontTask.dependsOn("merge${buildNameCapitalized}Assets")
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentFontTask)
runBefore("processX86${buildNameCapitalized}Resources", currentFontTask)
runBefore("processUniversal${buildNameCapitalized}Resources", currentFontTask)
runBefore("process${buildNameCapitalized}Resources", currentFontTask)
// Bundle JavaScript and React resources.
// (adapted from react-native/react.gradle)
//
// React JS bundle directories
def jsBundleDir = file("${bundlePath}/assets")
def resourcesDir = file("${bundlePath}/res/merged")
def jsBundleFile = file("${jsBundleDir}/index.android.bundle")
// Bundle task name for variant.
def bundleJsAndAssetsTaskName = "bundle${buildNameCapitalized}JsAndAssets"
def currentBundleTask = tasks.create(
name: bundleJsAndAssetsTaskName,
type: Exec) {
// Set up inputs and outputs so gradle can cache the result.
def reactRoot = file("${projectDir}/../../")
inputs.files fileTree(dir: reactRoot, excludes: ['android/**', 'ios/**'])
outputs.dir jsBundleDir
outputs.dir resourcesDir
// Set up the call to the react-native cli.
workingDir reactRoot
// Create JS bundle
def devEnabled = !buildNameCapitalized.toLowerCase().contains('release')
commandLine(
'node',
'node_modules/react-native/local-cli/cli.js',
'bundle',
'--assets-dest', resourcesDir,
'--bundle-output', jsBundleFile,
'--dev', "${devEnabled}",
'--entry-file', 'index.android.js',
'--platform', 'android',
'--reset-cache')
// Disable bundling on dev builds
enabled !devEnabled
}
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
currentBundleTask.dependsOn("merge${buildNameCapitalized}Resources")
currentBundleTask.dependsOn("merge${buildNameCapitalized}Assets")
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
runBefore("processX86${buildNameCapitalized}Resources", currentBundleTask)
runBefore("processUniversal${buildNameCapitalized}Resources", currentBundleTask)
runBefore("process${buildNameCapitalized}Resources", currentBundleTask)
}
}
publishing {
publications {
aarArchive(MavenPublication) {
groupId 'org.jitsi.react'
artifactId 'jitsi-meet-sdk'
version '1.9.0'
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
extension "aar"
}
pom.withXml {
def pomXml = asNode()
pomXml.appendNode('name', 'jitsi-meet-sdk')
pomXml.appendNode('description', 'Jitsi Meet SDK for Android')
def dependencies = pomXml.appendNode('dependencies')
configurations.getByName('releaseCompileClasspath').getResolvedConfiguration().getFirstLevelModuleDependencies().each {
// The (third-party) React Native modules that we depend on
// are in source code form and do not have groupId. That is
// why we have a dedicated groupId for them. But the other
// dependencies come through Maven and, consequently, have
// groupId.
def groupId = it.moduleGroup
def artifactId = it.moduleName
if (artifactId.startsWith('react-native-')
&& groupId.equals('jitsi-meet')) {
groupId = rootProject.ext.moduleGroupId
}
def dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', groupId)
dependency.appendNode('artifactId', artifactId)
dependency.appendNode('version', it.moduleVersion)
}
}
}
}
repositories {
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
}
}

View File

@@ -1,25 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/scorretge/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -1,30 +0,0 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet.sdk">
<!-- 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" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -1,50 +0,0 @@
/**
* Adapted from
* {@link https://github.com/Aleksandern/react-native-android-settings-library}.
*/
package org.jitsi.meet.sdk;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
class AndroidSettingsModule extends ReactContextBaseJavaModule {
public AndroidSettingsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "AndroidSettings";
}
@ReactMethod
public void open(Promise promise) {
Context context = getReactApplicationContext();
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(
Uri.fromParts("package", context.getPackageName(), null));
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// Some devices may give an error here.
// https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
promise.reject(e);
return;
}
promise.resolve(null);
}
}

View File

@@ -1,61 +0,0 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import java.util.HashMap;
import java.util.Map;
class AppInfoModule extends ReactContextBaseJavaModule {
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Gets a {@code Map} of constants this module exports to JS. Supports JSON
* types.
*
* @return a {@link Map} of constants this module exports to JS
*/
@Override
public Map<String, Object> getConstants() {
Context context = getReactApplicationContext();
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo;
PackageInfo packageInfo;
try {
String packageName = context.getPackageName();
applicationInfo
= packageManager.getApplicationInfo(packageName, 0);
packageInfo = packageManager.getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
applicationInfo = null;
packageInfo = null;
}
Map<String, Object> constants = new HashMap<>();
constants.put(
"name",
applicationInfo == null
? ""
: packageManager.getApplicationLabel(applicationInfo));
constants.put(
"version",
packageInfo == null ? "" : packageInfo.versionName);
return constants;
}
@Override
public String getName() {
return "AppInfo";
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
/**
* Defines the default behavior of {@code JitsiMeetActivity} and
* {@code JitsiMeetView} upon invoking the back button if no
* {@code JitsiMeetView} handles the invocation. For example, a
* {@code JitsiMeetView} may (1) handle the invocation of the back button
* during a conference by leaving the conference and (2) not handle the
* invocation when not in a conference.
*/
public class DefaultHardwareBackBtnHandlerImpl
implements DefaultHardwareBackBtnHandler {
/**
* The {@code Activity} to which the default handling of the back button
* is being provided by this instance.
*/
private final Activity activity;
/**
* Initializes a new {@code DefaultHardwareBackBtnHandlerImpl} instance to
* provide the default handling of the back button to a specific
* {@code Activity}.
*
* @param activity the {@code Activity} to which the new instance is to
* provide the default behavior of the back button
*/
public DefaultHardwareBackBtnHandlerImpl(Activity activity) {
this.activity = activity;
}
/**
* {@inheritDoc}
*
* Finishes the associated {@code Activity}.
*/
@Override
public void invokeDefaultOnBackPressed() {
// Technically, we'd like to invoke Activity#onBackPressed().
// Practically, it's not possible. Fortunately, the documentation of
// Activity#onBackPressed() specifies that "[t]he default implementation
// simply finishes the current activity,"
activity.finish();
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Module implementing a simple API to enable a proximity sensor-controlled
* wake lock. When the lock is held, if the proximity sensor detects a nearby
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
*/
class ExternalAPIModule extends ReactContextBaseJavaModule {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final Map<String, Method> JITSI_MEET_VIEW_LISTENER_METHODS
= new HashMap<>();
static {
// Figure out the mapping between the JitsiMeetViewListener methods
// and the events i.e. redux action types.
Pattern onPattern = Pattern.compile("^on[A-Z]+");
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
for (Method method : JitsiMeetViewListener.class.getDeclaredMethods()) {
// * The method must be public (because it is declared by an
// interface).
// * The method must be/return void.
if (!Modifier.isPublic(method.getModifiers())
|| !Void.TYPE.equals(method.getReturnType())) {
continue;
}
// * The method name must start with "on" followed by a
// capital/uppercase letter (in agreement with the camelcase
// coding style customary to Java in general and the projects of
// the Jitsi community in particular).
String name = method.getName();
if (!onPattern.matcher(name).find()) {
continue;
}
// * The method must accept/have exactly 1 parameter of a type
// assignable from HashMap.
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
continue;
}
// Convert the method name to an event name.
name
= camelcasePattern.matcher(name.substring(2))
.replaceAll("$1_$2")
.toUpperCase(Locale.ROOT);
JITSI_MEET_VIEW_LISTENER_METHODS.put(name, method);
}
}
/**
* 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 ExternalAPIModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Gets the name of this module to be used in the React Native bridge.
*
* @return The name of this module to be used in the React Native bridge.
*/
@Override
public String getName() {
return "ExternalAPI";
}
/**
* Dispatches an event that occurred on JavaScript to the view's listener.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
* @param scope
*/
@ReactMethod
public void sendEvent(String name, ReadableMap data, String scope) {
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native JitsiMeetView which hosts it.
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
if (view == null) {
return;
}
JitsiMeetViewListener listener = view.getListener();
if (listener == null) {
return;
}
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
if (method != null) {
try {
method.invoke(listener, toHashMap(data));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* Initializes a new {@code HashMap} instance with the key-value
* associations of a specific {@code ReadableMap}.
*
* @param readableMap the {@code ReadableMap} specifying the key-value
* associations with which the new {@code HashMap} instance is to be
* initialized.
* @return a new {@code HashMap} instance initialized with the key-value
* associations of the specified {@code readableMap}.
*/
private HashMap<String, Object> toHashMap(ReadableMap readableMap) {
HashMap<String, Object> hashMap = new HashMap<>();
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
i.hasNextKey();) {
String key = i.nextKey();
hashMap.put(key, readableMap.getString(key));
}
return hashMap;
}
}

View File

@@ -1,303 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import java.net.URL;
/**
* Base Activity for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and
* the Activity lifecycle methods already implemented.
*
* In this activity we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifetime methods associated with this Activity are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetView} static methods.
*/
public class JitsiMeetActivity extends AppCompatActivity {
/**
* The request code identifying requests for the permission to draw on top
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
*/
private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE);
/**
* The default behavior of this {@code JitsiMeetActivity} upon invoking the
* back button if {@link #view} does not handle the invocation.
*/
private DefaultHardwareBackBtnHandler defaultBackButtonImpl;
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified. The value is used only while
* {@link #view} equals {@code null}.
*/
private URL defaultURL;
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView view;
/**
* Whether Picture-in-Picture is enabled. The value is used only while
* {@link #view} equals {@code null}.
*/
private Boolean pictureInPictureEnabled;
/**
* Whether the Welcome page is enabled. The value is used only while
* {@link #view} equals {@code null}.
*/
private boolean welcomePageEnabled;
private boolean canRequestOverlayPermission() {
return
BuildConfig.DEBUG
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.M;
}
/**
*
* @see JitsiMeetView#getDefaultURL()
*/
public URL getDefaultURL() {
return view == null ? defaultURL : view.getDefaultURL();
}
/**
*
* @see JitsiMeetView#getPictureInPictureEnabled()
*/
public boolean getPictureInPictureEnabled() {
return
view == null
? pictureInPictureEnabled
: view.getPictureInPictureEnabled();
}
/**
*
* @see JitsiMeetView#getWelcomePageEnabled()
*/
public boolean getWelcomePageEnabled() {
return view == null ? welcomePageEnabled : view.getWelcomePageEnabled();
}
/**
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
* new {@link JitsiMeetView} instance.
*/
private void initializeContentView() {
JitsiMeetView view = initializeView();
if (view != null) {
this.view = view;
setContentView(this.view);
}
}
/**
* Initializes a new {@link JitsiMeetView} instance.
*
* @return a new {@code JitsiMeetView} instance.
*/
protected JitsiMeetView initializeView() {
JitsiMeetView view = new JitsiMeetView(this);
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
// is documented to need such an order in order to take effect:
view.setDefaultURL(defaultURL);
if (pictureInPictureEnabled != null) {
view.setPictureInPictureEnabled(
pictureInPictureEnabled.booleanValue());
}
view.setWelcomePageEnabled(welcomePageEnabled);
view.loadURL(null);
return view;
}
/**
* Loads the given URL and displays the conference. If the specified URL is
* null, the welcome page is displayed instead.
*
* @param url The conference URL.
*/
public void loadURL(@Nullable URL url) {
view.loadURL(url);
}
@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {
initializeContentView();
}
}
}
@Override
public void onBackPressed() {
if (!JitsiMeetView.onBackPressed()) {
// JitsiMeetView didn't handle the invocation of the back button.
// Generally, an Activity extender would very likely want to invoke
// Activity#onBackPressed(). For the sake of consistency with
// JitsiMeetView and within the Jitsi Meet SDK for Android though,
// JitsiMeetActivity does what JitsiMeetView would've done if it
// were able to handle the invocation.
if (defaultBackButtonImpl == null) {
super.onBackPressed();
} else {
defaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// In Debug builds React needs permission to write over other apps in
// order to display the warning and error overlays.
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
Intent intent
= new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
return;
}
initializeContentView();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (view != null) {
view.dispose();
view = null;
}
JitsiMeetView.onHostDestroy(this);
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
ReactInstanceManager reactInstanceManager;
if (!super.onKeyUp(keyCode, event)
&& BuildConfig.DEBUG
&& (reactInstanceManager
= JitsiMeetView.getReactInstanceManager())
!= null
&& keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog();
return true;
}
return false;
}
@Override
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(intent);
}
@Override
protected void onResume() {
super.onResume();
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
}
@Override
public void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
defaultBackButtonImpl = null;
}
@Override
protected void onUserLeaveHint() {
JitsiMeetView.onUserLeaveHint();
}
/**
*
* @see JitsiMeetView#setDefaultURL(URL)
*/
public void setDefaultURL(URL defaultURL) {
if (view == null) {
this.defaultURL = defaultURL;
} else {
view.setDefaultURL(defaultURL);
}
}
/**
*
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
if (view == null) {
this.pictureInPictureEnabled
= Boolean.valueOf(pictureInPictureEnabled);
} else {
view.setPictureInPictureEnabled(pictureInPictureEnabled);
}
}
/**
*
* @see JitsiMeetView#setWelcomePageEnabled(boolean)
*/
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
if (view == null) {
this.welcomePageEnabled = welcomePageEnabled;
} else {
view.setWelcomePageEnabled(welcomePageEnabled);
}
}
}

View File

@@ -1,584 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.FrameLayout;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.rnimmersive.RNImmersiveModule;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
public class JitsiMeetView extends FrameLayout {
/**
* Background color used by {@code JitsiMeetView} and the React Native root
* view.
*/
private static final int BACKGROUND_COLOR = 0xFF111111;
/**
* The {@link Log} tag which identifies the source of the log messages of
* {@code JitsiMeetView}.
*/
private final static String TAG = JitsiMeetView.class.getSimpleName();
/**
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
*/
private static ReactInstanceManager reactInstanceManager;
private static final Set<JitsiMeetView> views
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
private static List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new ExternalAPIModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext)
);
}
public static JitsiMeetView findViewByExternalAPIScope(
String externalAPIScope) {
synchronized (views) {
for (JitsiMeetView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
}
return null;
}
// XXX Strictly internal use only (at the time of this writing)!
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param application {@code Application} instance which is running.
*/
private static void initReactInstanceManager(Application application) {
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.addPackage(new com.calendarevents.CalendarEventsPackage())
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
.addPackage(new com.rnimmersive.RNImmersivePackage())
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
.addPackage(new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return JitsiMeetView.createNativeModules(reactContext);
}
})
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
/**
* Loads a specific URL {@code String} in all existing
* {@code JitsiMeetView}s.
*
* @param urlString he URL {@code String} to load in all existing
* {@code JitsiMeetView}s.
* @return If the specified {@code urlString} was submitted for loading in
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
* {@code false}.
*/
private static boolean loadURLStringInViews(String urlString) {
synchronized (views) {
if (!views.isEmpty()) {
for (JitsiMeetView view : views) {
view.loadURLString(urlString);
}
return true;
}
}
return false;
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onBackPressed} so we can do the required internal
* processing.
*
* @return {@code true} if the back-press was processed; {@code false},
* otherwise. If {@code false}, the application should call the parent's
* implementation.
*/
public static boolean onBackPressed() {
if (reactInstanceManager == null) {
return false;
} else {
reactInstanceManager.onBackPressed();
return true;
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onDestroy} so we can do the required internal
* processing.
*
* @param activity {@code Activity} being destroyed.
*/
public static void onHostDestroy(Activity activity) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostDestroy(activity);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onPause} so we can do the required internal processing.
*
* @param activity {@code Activity} being paused.
*/
public static void onHostPause(Activity activity) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostPause(activity);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onResume} so we can do the required internal processing.
*
* @param activity {@code Activity} being resumed.
*/
public static void onHostResume(Activity activity) {
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onResume} so we can do the required internal processing.
*
* @param activity {@code Activity} being resumed.
* @param defaultBackButtonImpl a {@code DefaultHardwareBackBtnHandler} to
* handle invoking the back button if no {@code JitsiMeetView} handles it.
*/
public static void onHostResume(
Activity activity,
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onNewIntent} so we can do the required internal
* processing. Note that this is only needed if the activity's "launchMode"
* was set to "singleTask". This is required for deep linking to work once
* the application is already running.
*
* @param intent {@code Intent} instance which was received.
*/
public static void onNewIntent(Intent intent) {
// XXX At least twice we received bug reports about malfunctioning
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
// functioning as expected in our testing. But that was to be expected
// because the app does not exercise loadURL. In order to increase the
// test coverage of loadURL, channel deep linking through loadURL.
Uri uri;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null
&& loadURLStringInViews(uri.toString())) {
return;
}
if (reactInstanceManager != null) {
reactInstanceManager.onNewIntent(intent);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onUserLeaveHint} so we can do the required internal
* processing.
*
* This is currently not mandatory.
*/
public static void onUserLeaveHint() {
sendEvent("onUserLeaveHint", null);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param params {@code WritableMap} optional ancillary data for the event.
*/
private static void sendEvent(
String eventName,
@Nullable WritableMap params) {
if (reactInstanceManager != null) {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
* {@link #loadURLObject(Bundle)}.
*/
private URL defaultURL;
/**
* The unique identifier of this {@code JitsiMeetView} within the process
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
* postis which we use on Web for the similar purposes of the iframe-based
* external API.
*/
private final String externalAPIScope;
/**
* {@link JitsiMeetViewListener} instance for reporting events occurring in
* Jitsi Meet.
*/
private JitsiMeetViewListener listener;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
* {@code true} iff the Android platform supports Picture-in-Picture
* natively.
*/
private Boolean pictureInPictureEnabled;
/**
* React Native root view.
*/
private ReactRootView reactRootView;
/**
* Whether the Welcome page is enabled.
*/
private boolean welcomePageEnabled;
public JitsiMeetView(@NonNull Context context) {
super(context);
setBackgroundColor(BACKGROUND_COLOR);
if (reactInstanceManager == null) {
initReactInstanceManager(((Activity) context).getApplication());
}
// Hook this JitsiMeetView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();
synchronized (views) {
views.add(this);
}
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* This method MUST be called when the Activity holding this view is
* destroyed, typically in the {@code onDestroy} method.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
}
}
/**
* Gets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
* set or if set to {@code null}, the default built in JavaScript is used:
* {@link https://meet.jit.si}
*
* @return The default base {@code URL} or {@code null}.
*/
public URL getDefaultURL() {
return defaultURL;
}
/**
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
*
* @return The {@code JitsiMeetViewListener} set on this
* {@code JitsiMeetView}.
*/
public JitsiMeetViewListener getListener() {
return listener;
}
/**
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
* natively supported on Android API >= 26 (Oreo), so it should not be
* enabled on older platform versions.
*
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
* otherwise.
*/
public boolean getPictureInPictureEnabled() {
return
PictureInPictureModule.isPictureInPictureSupported()
&& (pictureInPictureEnabled == null
|| pictureInPictureEnabled.booleanValue());
}
/**
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
* page is rendered when this {@code JitsiMeetView} is not at a URL
* identifying a Jitsi Meet conference/room.
*
* @return {@code true} if the Welcome page is enabled; otherwise,
* {@code false}.
*/
public boolean getWelcomePageEnabled() {
return welcomePageEnabled;
}
/**
* Loads a specific {@link URL} which may identify a conference to join. If
* the specified {@code URL} is {@code null} and the Welcome page is
* enabled, the Welcome page is displayed instead.
*
* @param url The {@code URL} to load which may identify a conference to
* join.
*/
public void loadURL(@Nullable URL url) {
loadURLString(url == null ? null : url.toString());
}
/**
* Loads a specific URL which may identify a conference to join. The URL is
* specified in the form of a {@link Bundle} of properties which (1)
* internally are sufficient to construct a URL {@code String} while (2)
* abstracting the specifics of constructing the URL away from API
* clients/consumers. If the specified URL is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlObject The URL to load which may identify a conference to join.
*/
public void loadURLObject(@Nullable Bundle urlObject) {
Bundle props = new Bundle();
// defaultURL
if (defaultURL != null) {
props.putString("defaultURL", defaultURL.toString());
}
// externalAPIScope
props.putString("externalAPIScope", externalAPIScope);
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",
getPictureInPictureEnabled());
// url
if (urlObject != null) {
props.putBundle("url", urlObject);
}
// welcomePageEnabled
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
// XXX The method loadURLObject: is supposed to be imperative i.e.
// a second invocation with one and the same URL is expected to join
// the respective conference again if the first invocation was followed
// by leaving the conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one
// and the same URL will not trigger componentWillReceiveProps in the
// JavaScript source code. The workaround implemented bellow introduces
// imperativeness in React Component props by defining a unique value
// per loadURLObject: invocation.
props.putLong("timestamp", System.currentTimeMillis());
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
reactInstanceManager,
"App",
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
/**
* Loads a specific URL {@link String} which may identify a conference to
* join. If the specified URL {@code String} is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlString The URL {@code String} to load which may identify a
* conference to join.
*/
public void loadURLString(@Nullable String urlString) {
Bundle urlObject;
if (urlString == null) {
urlObject = null;
} else {
urlObject = new Bundle();
urlObject.putString("url", urlString);
}
loadURLObject(urlObject);
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
// FIXME The singleton pattern employed by RNImmersiveModule is not
// advisable because a react-native mobule is consumable only after its
// BaseJavaModule#initialize() has completed and here we have no
// knowledge of whether the precondition is really met.
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
try {
immersive.emitImmersiveStateChangeEvent();
} catch (RuntimeException re) {
// FIXME I don't know how to check myself whether
// BaseJavaModule#initialize() has been invoked and thus
// RNImmersiveModule is consumable. A safe workaround is to
// swallow the failure because the whole full-screen/immersive
// functionality is brittle anyway, akin to the icing on the
// cake, and has been working without onWindowFocusChanged for a
// very long time.
Log.e(
TAG,
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
re);
}
}
}
/**
* Sets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. Must be
* called before {@link #loadURL(URL)} for it to take effect.
*
* @param defaultURL The {@code URL} to be set as the default base URL.
* @see #getDefaultURL()
*/
public void setDefaultURL(URL defaultURL) {
this.defaultURL = defaultURL;
}
/**
* Sets a specific {@link JitsiMeetViewListener} on this
* {@code JitsiMeetView}.
*
* @param listener The {@code JitsiMeetViewListener} to set on this
* {@code JitsiMeetView}.
*/
public void setListener(JitsiMeetViewListener listener) {
this.listener = listener;
}
/**
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
* natively supported only since certain platform versions, specifying
* {@code true} will have no effect on unsupported platform versions.
*
* @param pictureInPictureEnabled To enable Picture-in-Picture,
* {@code true}; otherwise, {@code false}.
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
}
/**
* Sets whether the Welcome page is enabled. Must be called before
* {@link #loadURL(URL)} for it to take effect.
*
* @param welcomePageEnabled {@code true} to enable the Welcome page;
* otherwise, {@code false}.
*/
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
this.welcomePageEnabled = welcomePageEnabled;
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import java.util.Map;
/**
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
* all methods in the interface if they are only interested in some.
*/
public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
@Override
public void onConferenceFailed(Map<String, Object> data) {
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import java.util.Map;
/**
* Interface for listening to events coming from Jitsi Meet.
*/
public interface JitsiMeetViewListener {
/**
* Called when joining a conference fails or an ongoing conference is
* interrupted due to a failure.
*
* @param data Map with an "error" key describing the problem, and a "url"
* key with the conference URL.
*/
void onConferenceFailed(Map<String, Object> data);
/**
* Called when a conference was joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceJoined(Map<String, Object> data);
/**
* Called when the conference was left, typically after hanging up.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceLeft(Map<String, Object> data);
/**
* Called before the conference is joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillJoin(Map<String, Object> data);
/**
* Called before the conference is left.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillLeave(Map<String, Object> data);
/**
* Called when loading the main configuration file from the Jitsi Meet
* deployment fails.
*
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL which necessitated the loading of the configuration
* file.
*/
void onLoadConfigError(Map<String, Object> data);
}

View File

@@ -1,67 +0,0 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Log;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class PictureInPictureModule extends ReactContextBaseJavaModule {
private final static String TAG = "PictureInPicture";
static boolean isPictureInPictureSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
* Supported on Android API >= 26 (Oreo) only.
*
* @param promise a {@code Promise} which will resolve with a {@code null}
* value upon success, and an {@link Exception} otherwise.
*/
@ReactMethod
public void enterPictureInPicture(Promise promise) {
if (isPictureInPictureSupported()) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(new Exception("No current Activity!"));
return;
}
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1));
boolean r
= currentActivity.enterPictureInPictureMode(builder.build());
if (r) {
promise.resolve(null);
} else {
promise.reject(
new Exception("Failed to enter Picture-in-Picture"));
}
return;
}
promise.reject(new Exception("Picture-in-Picture not supported"));
}
@Override
public String getName() {
return TAG;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
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.Collections;
import java.util.List;
public class ReactPackageAdapter implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -1,208 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
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 org.json.JSONArray;
import org.json.JSONObject;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
/**
* Module exposing WiFi statistics.
*
* Gathers rssi, signal in percentage, timestamp and the addresses
* of the wifi device.
*/
class WiFiStatsModule extends ReactContextBaseJavaModule {
/**
* The name of {@code WiFiStatsModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "WiFiStats";
/**
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
*/
static final String TAG = MODULE_NAME;
/**
* The scale used for the signal value.
* A level of the signal, given in the range
* of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
*/
public final static int SIGNAL_LEVEL_SCALE = 101;
/**
* {@link Handler} for running all operations on the main thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* 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 WiFiStatsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* 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;
}
/**
* Returns the {@link InetAddress} represented by this int.
*
* @param value the int representation of the ip address.
* @return the {@link InetAddress}.
* @throws UnknownHostException - if IP address is of illegal length.
*/
public static InetAddress toInetAddress(int value)
throws UnknownHostException {
return InetAddress.getByAddress(
new byte[] {
(byte) value,
(byte) (value >> 8),
(byte) (value >> 16),
(byte) (value >> 24)
});
}
/**
* Public method to retrieve WiFi stats.
*
* @param promise a {@link Promise} which will be resolved if WiFi stats are
* retrieved successfully, and it will be rejected otherwise.
*/
@ReactMethod
public void getWiFiStats(final Promise promise) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
Context context
= getReactApplicationContext().getApplicationContext();
WifiManager wifiManager
= (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
if (!wifiManager.isWifiEnabled()) {
promise.reject(new Exception("Wifi not enabled"));
return;
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo.getNetworkId() == -1) {
promise.reject(new Exception("Wifi not connected"));
return;
}
int rssi = wifiInfo.getRssi();
int signalLevel
= WifiManager.calculateSignalLevel(
rssi, SIGNAL_LEVEL_SCALE);
JSONObject result = new JSONObject();
result.put("rssi", rssi)
.put("signal", signalLevel)
.put("timestamp",
String.valueOf(System.currentTimeMillis()));
JSONArray addresses = new JSONArray();
InetAddress wifiAddress
= toInetAddress(wifiInfo.getIpAddress());
try {
Enumeration<NetworkInterface> e
= NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface networkInterface = e.nextElement();
boolean found = false;
// first check whether this is the desired interface
Enumeration<InetAddress> as
= networkInterface.getInetAddresses();
while (as.hasMoreElements()) {
InetAddress a = as.nextElement();
if(a.equals(wifiAddress)) {
found = true;
break;
}
}
if (found) {
// interface found let's put addresses
// to the result object
as = networkInterface.getInetAddresses();
while (as.hasMoreElements()) {
InetAddress a = as.nextElement();
if (a.isLinkLocalAddress())
continue;
addresses.put(a.getHostAddress());
}
}
}
} catch (SocketException e) {
Log.wtf(TAG,
"Unable to NetworkInterface.getNetworkInterfaces()"
);
}
result.put("addresses", addresses);
promise.resolve(result.toString());
Log.d(TAG, "WiFi stats: " + result.toString());
} catch (Throwable e) {
Log.e(TAG, "Failed to obtain wifi stats", e);
promise.reject(
new Exception("Failed to obtain wifi stats"));
}
}
};
mainThreadHandler.post(r);
}
}

View File

@@ -1,3 +0,0 @@
<resources>
<string name="app_name">Jitsi Meet SDK</string>
</resources>

View File

@@ -1,21 +1,13 @@
rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
include ':app'
include ':react-native-background-timer'
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
include ':react-native-fetch-blob'
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-locale-detector'
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
include ':react-native-sound'
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-webrtc'
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
include ':react-native-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')

79
app.js
View File

@@ -1,49 +1,66 @@
/* application specific logic */
// FIXME: remove once atlaskit work with React 16
// It seems that @atlaskit/icon is importing PropTypes from React, but it
// happens through some glyph coffee script template. It could be that more
// things are broken there (not only the icon).
import './react/features/base/react/prop-types-polyfill.js';
import "jquery";
import "jquery-contextmenu";
import "jquery-ui";
import "strophe";
import "strophe-disco";
import "jQuery-Impromptu";
import "autosize";
import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';
import 'autosize';
import 'aui';
import 'aui-experimental';
import 'aui-css';
import 'aui-experimental-css';
window.toastr = require("toastr");
import UI from "./modules/UI/UI";
import settings from "./modules/settings/Settings";
import conference from './conference';
import API from './modules/API';
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import remoteControl from './modules/remotecontrol/RemoteControl';
import settings from './modules/settings/Settings';
import translation from './modules/translation/translation';
import UI from './modules/UI/UI';
window.APP = {
API,
conference,
import translation from "./modules/translation/translation";
import remoteControl from "./modules/remotecontrol/RemoteControl";
const APP = {
// Used by do_external_connect.js if we receive the attach data after
// connect was already executed. status property can be 'initialized',
// 'ready', or 'connecting'. We are interested in 'ready' status only which
// connect was already executed. status property can be "initialized",
// "ready" or "connecting". We are interested in "ready" status only which
// means that connect was executed but we have to wait for the attach data.
// In status 'ready' handler property will be set to a function that will
// In status "ready" handler property will be set to a function that will
// finish the connect process when the attach data or error is received.
connect: {
handler: null,
status: 'initialized'
status: "initialized",
handler: null
},
// Used for automated performance tests.
// Used for automated performance tests
connectionTimes: {
'index.loaded': window.indexLoadedTime
"index.loaded": window.indexLoadedTime
},
keyboardshortcut,
remoteControl,
UI,
settings,
conference,
translation,
UI
/**
* The log collector which captures JS console logs for this app.
* @type {LogCollector}
*/
logCollector: null,
/**
* Indicates if the log collector has been started (it will not be started
* if the welcome page is displayed).
*/
logCollectorStarted : false,
/**
* After the APP has been initialized provides utility methods for dealing
* with the conference room URL(address).
* @type ConferenceUrl
*/
ConferenceUrl : null,
connection: null,
API,
remoteControl
};
// TODO The execution of the mobile app starts from react/index.native.js.
@@ -52,4 +69,6 @@ window.APP = {
// because we are at the beginning of introducing React into the Web app, allow
// the execution of the Web app to start from app.js in order to reduce the
// complexity of the beginning step.
import './react';
require('./react');
module.exports = APP;

File diff suppressed because it is too large Load Diff

423
config.js
View File

@@ -1,374 +1,101 @@
/* eslint-disable no-unused-vars, no-var */
var config = {
// Configuration
//
// Alternative location for the configuration.
// configLocation: './config.json',
// Custom function which given the URL path should return a room name.
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
// Connection
//
/* jshint maxlen:false */
var config = { // eslint-disable-line no-unused-vars
// configLocation: './config.json', // see ./modules/HttpConfigFetch.js
hosts: {
// XMPP domain.
domain: 'jitsi-meet.example.com',
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
muc: 'conference.jitsi-meet.example.com'
// When using authentication, domain for guest users.
// anonymousdomain: 'guest.example.com',
// Domain for authenticated users. Defaults to <domain>.
// authdomain: 'jitsi-meet.example.com',
// Jirecon recording component domain.
// jirecon: 'jirecon.jitsi-meet.example.com',
// Call control component (Jigasi).
// call_control: 'callcontrol.jitsi-meet.example.com',
// Focus component domain. Defaults to focus.<domain>.
// focus: 'focus.jitsi-meet.example.com',
//anonymousdomain: 'guest.example.com',
//authdomain: 'jitsi-meet.example.com', // defaults to <domain>
muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
//jirecon: 'jirecon.jitsi-meet.example.com',
//call_control: 'callcontrol.jitsi-meet.example.com',
//focus: 'focus.jitsi-meet.example.com', // defaults to 'focus.jitsi-meet.example.com'
},
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
// useIPv6: true, // ipv6 support. use at your own risk
useNicks: false,
bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
//defaultSipNumber: '', // Default SIP number
// BOSH URL. FIXME: use XEP-0156 to discover it.
bosh: '//jitsi-meet.example.com/http-bind',
// The name of client node advertised in XEP-0115 'c' stanza
clientNode: 'http://jitsi.org/jitsimeet',
// The real JID of focus participant - can be overridden here
// focusUserJid: 'focus@auth.jitsi-meet.example.com',
// Testing / experimental features.
//
testing: {
// Enables experimental simulcast support on Firefox.
enableFirefoxSimulcast: false,
// P2P test mode disables automatic switching to P2P when there are 2
// participants in the conference.
p2pTestMode: false
},
// Disables ICE/UDP by filtering out local and remote UDP candidates in
// signalling.
// webrtcIceUdpDisable: false,
// Disables ICE/TCP by filtering out local and remote TCP candidates in
// signalling.
// webrtcIceTcpDisable: false,
// Media
//
// Audio
// Disable measuring of audio levels.
// disableAudioLevels: false,
// Start the conference in audio only mode (no video is being received nor
// sent).
// startAudioOnly: false,
// Every participant after the Nth will start audio muted.
// startAudioMuted: 10,
// Start calls with audio muted. Unlike the option above, this one is only
// applied locally. FIXME: having these 2 options is confusing.
// startWithAudioMuted: false,
// Video
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,
// w3c spec-compliant video constraints to use for video capture. Currently
// used by browsers that return true from lib-jitsi-meet's
// util#browser#usesNewGumFlow. The constraints are independency from
// this config's resolution value. Defaults to requesting an ideal aspect
// ratio of 16:9 with an ideal resolution of 1080p.
// constraints: {
// video: {
// aspectRatio: 16 / 9,
// height: {
// ideal: 1080,
// max: 1080,
// min: 240
// }
// }
// },
// Enable / disable simulcast support.
// disableSimulcast: false,
// Suspend sending video if bandwidth estimation is too low. This may cause
// problems with audio playback. Disabled until these are fixed.
disableSuspendVideo: true,
// Every participant after the Nth will start video muted.
// startVideoMuted: 10,
// Start calls with video muted. Unlike the option above, this one is only
// applied locally. FIXME: having these 2 options is confusing.
// startWithVideoMuted: false,
// If set to true, prefer to use the H.264 video codec (if supported).
// Note that it's not recommended to do this because simulcast is not
// supported when using H.264. For 1-to-1 calls this setting is enabled by
// default and can be toggled in the p2p section.
// preferH264: true,
// If set to true, disable H.264 video codec by stripping it out of the
// SDP.
// disableH264: false,
// Desktop sharing
// Enable / disable desktop sharing
// disableDesktopSharing: false,
// The STUN servers that will be used in the peer to peer connections
p2pStunServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
{ urls: "stun:stun2.l.google.com:19302" }
],
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: null,
// Whether desktop sharing should be disabled on Chrome.
desktopSharingChromeDisabled: true,
// The media sources to use when using screen sharing with the Chrome
// extension.
desktopSharingChromeSources: [ 'screen', 'window', 'tab' ],
desktopSharingChromeSources: ['screen', 'window', 'tab'],
// Required version of Chrome extension
desktopSharingChromeMinExtVersion: '0.1',
// The ID of the jidesha extension for Firefox. If null, we assume that no
// extension is required.
desktopSharingFirefoxExtId: null,
// Whether desktop sharing should be disabled on Firefox.
desktopSharingFirefoxDisabled: false,
// The maximum version of Firefox which requires a jidesha extension.
// Example: if set to 41, we will require the extension for Firefox versions
// up to and including 41. On Firefox 42 and higher, we will run without the
// extension.
// If set to -1, an extension will be required for all versions of Firefox.
desktopSharingFirefoxMaxVersionExtRequired: 51,
// The URL to the Firefox extension for desktop sharing.
desktopSharingFirefoxExtensionURL: null,
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
// desktopSharingFrameRate: {
// min: 5,
// max: 5
// },
// Disables ICE/UDP by filtering out local and remote UDP candidates in signalling.
webrtcIceUdpDisable: false,
// Disables ICE/TCP by filtering out local and remote TCP candidates in signalling.
webrtcIceTcpDisable: false,
// Try to start calls with screen-sharing instead of camera video.
// startScreenSharing: false,
// Recording
// Whether to enable recording or not.
// enableRecording: false,
// Type for recording: one of jibri or jirecon.
// recordingType: 'jibri',
// Misc
// Default value for the channel "last N" attribute. -1 for unlimited.
channelLastN: -1,
// Disables or enables RTX (RFC 4588) (defaults to false).
// disableRtx: false,
// Use XEP-0215 to fetch STUN and TURN servers.
// useStunTurn: true,
// Enable IPv6 support.
// useIPv6: true,
// Enables / disables a data communication channel with the Videobridge.
// Values can be 'datachannel', 'websocket', true (treat it as
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't
// open any channel).
// openBridgeChannel: true,
// UI
//
// Use display name as XMPP nickname.
// useNicks: false,
// Require users to always specify a display name.
// requireDisplayName: true,
// Whether to use a welcome page or not. In case it's false a random room
// will be joined when no room is specified.
openSctp: true, // Toggle to enable/disable SCTP channels
disableStats: false,
disableAudioLevels: false,
channelLastN: -1, // The default value of the channel attribute last-n.
enableRecording: false,
enableWelcomePage: true,
// Enabling the close page will ignore the welcome page redirection when
// a call is hangup.
// enableClosePage: false,
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
// disable1On1Mode: false,
// The minimum value a video's height (or width, whichever is smaller) needs
//enableClosePage: false, // enabling the close page will ignore the welcome
// page redirection when call is hangup
disableSimulcast: false,
// requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
// startAudioMuted: 10, // every participant after the Nth will start audio muted
// startVideoMuted: 10, // every participant after the Nth will start video muted
// defaultLanguage: "en",
// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret.
// callStatsID: "", // Application ID for callstats.io API
// callStatsSecret: "", // Secret for callstats.io API
/*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
'During that time service will not be available. ' +
'Apologise for inconvenience.',*/
disableThirdPartyRequests: false,
// The minumum value a video's height (or width, whichever is smaller) needs
// to be in order to be considered high-definition.
minHDHeight: 540,
// Default language for the user interface.
// defaultLanguage: 'en',
// If true all users without a token will be considered guests and all users
// If true - all users without token will be considered guests and all users
// with token will be considered non-guests. Only guests will be allowed to
// edit their profile.
enableUserRolesBasedOnToken: false,
// Message to show the users. Example: 'The service will be down for
// maintenance at 01:00 AM GMT,
// noticeMessage: '',
// Stats
//
// Whether to enable stats collection or not in the TraceablePeerConnection.
// This can be useful for debugging purposes (post-processing/analysis of
// the webrtc stats) as it is done in the jitsi-meet-torture bandwidth
// estimation tests.
// gatherStats: false,
// To enable sending statistics to callstats.io you must provide the
// Application ID and Secret.
// callStatsID: '',
// callStatsSecret: '',
// enables callstatsUsername to be reported as statsId and used
// by callstats as repoted remote id
// enableStatsID: false
// enables sending participants display name to callstats
// enableDisplayNameInStats: false
// Privacy
//
// If third party requests are disabled, no other server will be contacted.
// This means avatars will be locally generated and callstats integration
// will not function.
// disableThirdPartyRequests: false,
// Peer-To-Peer mode: used (if enabled) when there are just 2 participants.
//
p2p: {
// Enables peer to peer mode. When enabled the system will try to
// establish a direct connection when there are exactly 2 participants
// in the room. If that succeeds the conference will stop sending data
// through the JVB and use the peer to peer connection instead. When a
// 3rd participant joins the conference will be moved back to the JVB
// connection.
enabled: true,
// Use XEP-0215 to fetch STUN and TURN servers.
// useStunTurn: true,
// The STUN servers that will be used in the peer to peer connections
stunServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' }
],
// Sets the ICE transport policy for the p2p connection. At the time
// of this writing the list of possible values are 'all' and 'relay',
// but that is subject to change in the future. The enum is defined in
// the WebRTC standard:
// https://www.w3.org/TR/webrtc/#rtcicetransportpolicy-enum.
// If not set, the effective value is 'all'.
// iceTransportPolicy: 'all',
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
// is supported).
preferH264: true
// If set to true, disable H.264 video codec by stripping it out of the
// SDP.
// disableH264: false,
// How long we're going to wait, before going back to P2P after the 3rd
// participant has left the conference (to filter out page reload).
// backToP2PDelay: 5
},
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
// analyticsScriptUrls: [
// "libs/analytics-ga.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
// The Google Analytics Tracking ID
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
deploymentInfo: {
// shard: "shard1",
// region: "europe",
// userRegion: "asia"
}
// List of undocumented settings used in jitsi-meet
/**
alwaysVisibleToolbar
autoEnableDesktopSharing
autoRecord
autoRecordToken
debug
debugAudioLevels
deploymentInfo
dialInConfCodeUrl
dialInNumbersUrl
dialOutAuthUrl
dialOutCodesUrl
disableRemoteControl
displayJids
enableLocalVideoFlip
etherpad_base
externalConnectUrl
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
iAmSipGateway
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
tokenAuthUrl
*/
// List of undocumented settings used in lib-jitsi-meet
/**
_peerConnStatusOutOfLastNTimeout
_peerConnStatusRtcMuteTimeout
abTesting
avgRtpStatsN
callStatsConfIDNamespace
callStatsCustomScriptUrl
desktopSharingSources
disableAEC
disableAGC
disableAP
disableHPF
disableNS
enableLipSync
enableTalkWhileMuted
forceJVB121Ratio
hiddenDomain
ignoreStartMuted
nick
startBitrate
*/
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
disableSuspendVideo: true,
// disables or enables RTX (RFC 4588) (defaults to false).
disableRtx: false,
// Sets the preferred resolution (height) for local video. Defaults to 360.
resolution: 720,
// Enables peer to peer mode. When enabled system will try to establish
// direct connection given that there are exactly 2 participants in
// the room. If that succeeds the conference will stop sending data through
// the JVB and use the peer to peer connection instead. When 3rd participant
// joins the conference will be moved back to the JVB connection.
//enableP2P: true
// How long we're going to wait, before going back to P2P after
// the 3rd participant has left the conference (to filter out page reload)
//backToP2PDelay: 5
};
/* eslint-enable no-unused-vars, no-var */

View File

@@ -8,12 +8,12 @@ import {
connectionFailed
} from './react/features/base/connection';
import {
isFatalJitsiConnectionError,
JitsiConnectionErrors,
JitsiConnectionEvents
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
const logger = require('jitsi-meet-logger').getLogger(__filename);
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Checks if we have data to use attach instead of connect. If we have the data
@@ -27,39 +27,28 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @param {string} [roomName] the name of the conference.
*/
function checkForAttachParametersAndConnect(id, password, connection) {
if (window.XMPPAttachInfo) {
APP.connect.status = 'connecting';
if(window.XMPPAttachInfo){
APP.connect.status = "connecting";
// When connection optimization is not deployed or enabled the default
// value will be window.XMPPAttachInfo.status = "error"
// If the connection optimization is deployed and enabled and there is
// a failure the value will be window.XMPPAttachInfo.status = "error"
if (window.XMPPAttachInfo.status === 'error') {
connection.connect({
id,
password
});
if(window.XMPPAttachInfo.status === "error") {
connection.connect({id, password});
return;
}
const attachOptions = window.XMPPAttachInfo.data;
if (attachOptions) {
var attachOptions = window.XMPPAttachInfo.data;
if(attachOptions) {
connection.attach(attachOptions);
delete window.XMPPAttachInfo.data;
} else {
connection.connect({
id,
password
});
connection.connect({id, password});
}
} else {
APP.connect.status = 'ready';
APP.connect.handler
= checkForAttachParametersAndConnect.bind(
null,
id, password, connection);
APP.connect.status = "ready";
APP.connect.handler = checkForAttachParametersAndConnect.bind(null,
id, password, connection);
}
}
@@ -73,71 +62,55 @@ function checkForAttachParametersAndConnect(id, password, connection) {
*/
function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
const { issuer, jwt } = APP.store.getState()['features/jwt'];
connectionConfig.bosh += `?room=${roomName}`;
connectionConfig.bosh += '?room=' + roomName;
const connection
let connection
= new JitsiMeetJS.JitsiConnection(
null,
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
connectionConfig);
return new Promise((resolve, reject) => {
return new Promise(function (resolve, reject) {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
ConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
ConnectionEvents.CONNECTION_FAILED,
handleConnectionFailed);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
ConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
/* eslint-disable max-params */
/**
*
*/
function connectionFailedHandler(error, message, credentials, details) {
/* eslint-enable max-params */
APP.store.dispatch(
connectionFailed(
connection, error, message, credentials, details));
function connectionFailedHandler(error, errMsg) {
APP.store.dispatch(connectionFailed(connection, error, errMsg));
if (isFatalJitsiConnectionError(error)) {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
ConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
}
}
/**
*
*/
function unsubscribe() {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
ConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished);
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
ConnectionEvents.CONNECTION_FAILED,
handleConnectionFailed);
}
/**
*
*/
function handleConnectionEstablished() {
APP.store.dispatch(connectionEstablished(connection));
unsubscribe();
resolve(connection);
}
/**
*
*/
function handleConnectionFailed(err) {
unsubscribe();
logger.error('CONNECTION FAILED:', err);
logger.error("CONNECTION FAILED:", err);
reject(err);
}
@@ -158,24 +131,24 @@ function connect(id, password, roomName) {
*
* @returns {Promise<JitsiConnection>}
*/
export function openConnection({ id, password, retry, roomName }) {
const usernameOverride
= jitsiLocalStorage.getItem('xmpp_username_override');
const passwordOverride
= jitsiLocalStorage.getItem('xmpp_password_override');
export function openConnection({id, password, retry, roomName}) {
let usernameOverride
= jitsiLocalStorage.getItem("xmpp_username_override");
let passwordOverride
= jitsiLocalStorage.getItem("xmpp_password_override");
if (usernameOverride && usernameOverride.length > 0) {
id = usernameOverride; // eslint-disable-line no-param-reassign
id = usernameOverride;
}
if (passwordOverride && passwordOverride.length > 0) {
password = passwordOverride; // eslint-disable-line no-param-reassign
password = passwordOverride;
}
return connect(id, password, roomName).catch(err => {
if (retry) {
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
const { issuer, jwt } = APP.store.getState()['features/jwt'];
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED
if (err === ConnectionErrors.PASSWORD_REQUIRED
&& (!jwt || issuer === 'anonymous')) {
return AuthHandler.requestAuth(roomName, connect);
}

View File

@@ -16,17 +16,14 @@ import parseURLParams from '../react/features/base/config/parseURLParams';
*/
if (typeof createConnectionExternally === 'function') {
// URL params have higher priority than config params.
// URL params have higher proirity than config params.
let url
= parseURLParams(window.location, true, 'hash')[
'config.externalConnectUrl']
|| config.externalConnectUrl;
const isRecorder
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
let roomName;
if (url && (roomName = getRoomName()) && !isRecorder) {
if (url && (roomName = getRoomName())) {
url += `?room=${roomName}`;
const token = parseURLParams(window.location, true, 'search').jwt;

View File

@@ -1,229 +0,0 @@
/* Fonts and line heights */
/**
* RESET
*/
html,
body,
p,
div,
h1,
h2,
h3,
h4,
h5,
h6,
img,
pre,
form,
fieldset {
margin: 0;
padding: 0;
}
ul,
ol,
dl {
margin: 0;
}
img,
fieldset {
border: 0;
}
@-moz-document url-prefix() {
img {
font-size: 0;
}
img:-moz-broken {
font-size: inherit;
}
}
/* https://github.com/necolas/normalize.css */
/* Customised to remove styles for unsupported browsers */
details,
main,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
vertical-align: baseline;
}
audio:not([controls]) {
display: none;
height: 0;
}
[hidden],
template {
display: none;
}
input[type="button"],
input[type="submit"],
input[type="reset"] {
-webkit-appearance: button;
}
/**
* TYPOGRAPHY - 14px base font size, agnostic font stack
*/
body {
color: #333;
font-family: Arial, sans-serif;
font-size: 14px;
line-height: 1.42857142857143;
}
/* International Font Stacks*/
[lang|=en] {
font-family: Arial, sans-serif;
}
[lang|=ja] {
font-family: "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, " Pゴシック", Verdana, Arial, sans-serif;
}
/* Default margins */
p,
ul,
ol,
dl,
h1,
h2,
h3,
h4,
h5,
h6,
blockquote,
pre {
margin: 10px 0 0 0;
}
/* No top margin to interfere with box padding */
p:first-child,
ul:first-child,
ol:first-child,
dl:first-child,
h1:first-child,
h2:first-child,
h3:first-child,
h4:first-child,
h5:first-child,
h6:first-child,
blockquote:first-child,
pre:first-child {
margin-top: 0;
}
/* Headings: desired line height in px / font size = unitless line height */
h1 {
color: #333;
font-size: 32px;
font-weight: normal;
line-height: 1.25;
text-transform: none;
margin: 30px 0 0 0;
}
h2 {
color: #333;
font-size: 24px;
font-weight: normal;
line-height: 1.25;
text-transform: none;
margin: 30px 0 0 0;
}
h3 {
color: #333;
font-size: 20px;
font-weight: normal;
line-height: 1.5;
text-transform: none;
margin: 30px 0 0 0;
}
h4 {
font-size: 16px;
font-weight: bold;
line-height: 1.25;
text-transform: none;
margin: 20px 0 0 0;
}
h5 {
color: #333;
font-size: 14px;
font-weight: bold;
line-height: 1.42857143;
text-transform: none;
margin: 20px 0 0 0;
}
h6 {
color: #707070;
font-size: 12px;
font-weight: bold;
line-height: 1.66666667;
text-transform: uppercase;
margin: 20px 0 0 0;
}
h1:first-child,
h2:first-child,
h3:first-child,
h4:first-child,
h5:first-child,
h6:first-child {
margin-top: 0;
}
/* Nice styles for using subheadings */
h1 + h2,
h2 + h3,
h3 + h4,
h4 + h5,
h5 + h6 {
margin-top: 10px;
}
/* Other typographical elements */
small {
color: #707070;
font-size: 12px;
line-height: 1.33333333333333;
}
code,
kbd {
font-family: monospace;
}
var,
address,
dfn,
cite {
font-style: italic;
}
cite:before {
content: "\2014 \2009";
}
blockquote {
border-left: 1px solid #ccc;
color: #707070;
margin-left: 19px;
padding: 10px 20px;
}
blockquote > cite {
display: block;
margin-top: 10px;
}
q {
color: #707070;
}
q:before {
content: open-quote;
}
q:after {
content: close-quote;
}
abbr {
border-bottom: 1px #707070 dotted;
cursor: help;
}
a {
color: #3572b0;
text-decoration: none;
}
a:focus,
a:hover,
a:active {
text-decoration: underline;
}

View File

@@ -41,11 +41,16 @@ body, input, textarea, keygen, select, button {
button, input, select, textarea {
margin: 0;
vertical-align: baseline;
color: $inputColor;
font-size: 1em;
}
button, select, input[type="button"],
input[type="reset"], input[type="submit"] {
height: 32px;
line-height: 32px;
padding-left: 4px;
padding-right: 4px;
cursor: pointer;
}
@@ -80,8 +85,8 @@ form {
display: block;
position: absolute;
top: 15;
width: $watermarkWidth;
height: $watermarkHeight;
width: 186px;
height: 74px;
background-size: contain;
background-repeat: no-repeat;
z-index: $zindex2;
@@ -119,13 +124,25 @@ form {
font-size: 12px;
}
/**
* Tooltips
**/
.tipsy {
z-index: $tooltipsZ;
&-inner {
background-color: $tooltipBg;
max-width: 350px;
}
&-arrow {
border-color: $tooltipBg;
}
}
/**
* Dialogs fade
*/
.aui-blanket {
background: #000;
transition: opacity 0.2s, visibility 0.2s;
transition-delay: 0.1s;
visibility: visible;
}
@@ -133,28 +150,3 @@ form {
-webkit-user-select: text;
user-select: text;
}
/**
* Re-style default OS scrollbar.
*/
::-webkit-scrollbar {
background: transparent;
width: 7px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-track-piece {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, .5);
border-radius: 4px;
}

View File

@@ -31,28 +31,6 @@
a:active {
color: black;
}
&::-webkit-scrollbar {
background: #06a5df;
width: 7px;
}
&::-webkit-scrollbar-button {
display: none;
}
&::-webkit-scrollbar-track {
background: black;
}
&::-webkit-scrollbar-track-piece {
background: black;
}
&::-webkit-scrollbar-thumb {
background: #06a5df;
border-radius: 4px;
}
}
#chat_container.is-conversation-mode #chatconversation {
@@ -234,6 +212,28 @@
line-height: 30px;
}
:not(.default-scrollbar)::-webkit-scrollbar {
background: #06a5df;
width: 7px;
}
:not(.default-scrollbar)::-webkit-scrollbar-button {
display: none;
}
:not(.default-scrollbar)::-webkit-scrollbar-track {
background: black;
}
:not(.default-scrollbar)::-webkit-scrollbar-track-piece {
background: black;
}
:not(.default-scrollbar)::-webkit-scrollbar-thumb {
background: #06a5df;
border-radius: 4px;
}
#usermsg::-webkit-scrollbar-track-piece {
background: #3a3a3a;
}

View File

@@ -1,6 +1,8 @@
%connection-info {
text-align: left;
font-size: 12px;
font-weight: 400;
color: $popoverFontColor;
td {
padding: 2px 0;
@@ -9,14 +11,11 @@
.connection-info
{
float: left;
padding: 5px;
padding-left: 0;
@extend %connection-info;
/**
* Apply negative margin to reduce the appearance of padding in AtlasKit
* InlineDialog.
*/
margin: -15px;
> table {
white-space: nowrap;
@extend %connection-info;
@@ -33,22 +32,12 @@
&__download
{
@extend .connection-info__icon;
}
&__status
{
font-weight: bold;
color: $downloadConnectionIconColor;
}
&__upload
{
@extend .connection-info__icon;
}
.showmore {
display: block;
margin: 10px auto;
text-align: center;
width: 90px;
color: $uploadConnectionIconColor;
}
}

View File

@@ -1,7 +1,7 @@
#contacts_container {
cursor: default;
#contacts {
> ul#contacts {
font-size: 12px;
bottom: 0px;
margin: 0;
@@ -13,36 +13,38 @@
.clickable {
cursor: pointer;
}
.icon-security,
.icon-security-locked {
font-size: 16px;
}
}
#contacts {
.contact-list-item {
align-items: center;
border-radius: 3px;
color: $baseLight;
display: flex;
font-size: 14px;
height: 36px;
>li {
display: block;
list-style-type: none;
padding: 0 10%;
text-align: left;
white-space: nowrap;
color: $baseLight;
font-size: 16px;
padding: 0 10%;
height: 27px;
&:hover,
&:active {
background: $toolbarSelectBackground;
}
.contact-list-item-name {
overflow: hidden;
> p {
display: inline-block;
vertical-align: middle;
margin: 0px;
width: 100%;
line-height: 1.5em;
text-overflow: ellipsis;
}
.avatar {
cursor: pointer;
height: 24px;
margin: 0 8px 0 4px;
width: 24px;
overflow: hidden;
}
}
}
@@ -56,4 +58,4 @@
border-radius: 20px;
max-height: 30px;
max-width: 30px;
}
}

64
css/_dial-out.scss Normal file
View File

@@ -0,0 +1,64 @@
/**
* The dialog content element.
*/
.dial-out-content {
margin-top: 5px;
/**
* The style of the flag icon.
*/
.dial-out-flag-icon {
position: absolute;
left: 5px;
top: 10px;
}
/**
* The style of the dial code element.
*/
.dial-out-code {
padding-left: 25px !important;
}
/**
* The dial-out dialog error element.
*/
.dial-out-error {
color: $errorColor;
}
/**
* The style of the dial input element.
*/
.dial-out-input {
padding-left: 70px;
}
/**
* Re-styling the default dropdown inside the dial-out-content.
*/
.dropdown {
left: $formPadding;
position: absolute !important;
width: 65px
}
/**
* Re-styling the default form-control inside the dial-out-content.
*/
.form-control {
padding-bottom: 8px !important;
}
.dropdown {
display: inline-block;
position: relative;
overflow: hidden;
}
.dropdown-trigger-icon {
position: absolute;
right: 0;
top: 4px;
}
}

View File

@@ -11,13 +11,13 @@
right: 0;
padding: 10px 5px;
@extend %align-right;
z-index: $filmstripVideosZ;
&__toolbar {
@include flex();
flex-direction: column-reverse;
flex-wrap: nowrap;
position: relative;
z-index: $zindex1; // Set z-index to make element visible.
width: $filmstripToggleButtonWidth;
button {
@@ -48,65 +48,24 @@
&__videos {
@extend %align-right;
position:relative;
height:196px;
padding: 0;
/* The filmstrip should not be covered by the left toolbar. */
padding-left: $defaultToolbarSize + 5;
bottom: 0;
width:auto;
border: $thumbnailsBorder solid transparent;
z-index: $filmstripVideosZ;
transition: bottom 2s;
overflow: visible !important;
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: $defaultToolbarSize + 5;
transition: bottom 2s;
}
/**
* The local video identifier.
*/
&#filmstripLocalVideo {
align-self: flex-end;
display: block;
/**
* The invite button style.
*/
.filmstrip__invite {
padding-bottom: 5px;
margin-left: 2px;
}
/**
* The invite button group style.
* TOFIX: use AtlasKit.ButtonGroup if it starts supporting different
* flex grow options for the buttons.
*/
.invite-button-group {
display: inline-flex;
justify-content: space-between;
width: 100%;
& button {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
}
& > * {
flex-grow: 0;
flex-shrink: 0;
margin-left: 2px;
}
}
}
/*!!! Removes the gap between the local video container and the remote
videos. */
font-size: 0pt;
&.hidden {
bottom: -196px;
}
.remote-videos-container {
display: flex;
}
.videocontainer {
display: none;
position: relative;
@@ -137,24 +96,6 @@
display: none;
}
.presence-label {
color: $participantNameColor;
font-size: 12px;
font-weight: 100;
left: 0;
margin: 0 auto;
overflow: hidden;
pointer-events: none;
position: absolute;
right: 0;
text-align: center;
text-overflow: ellipsis;
top: calc(50% + 30px);
white-space: nowrap;
width: 100%;
z-index: $zindex3;
}
/**
* Hovered video thumbnail.
*/
@@ -187,22 +128,6 @@
&__videos-filmstripOnly {
margin-top: auto;
margin-bottom: auto;
.filmstrip__videos {
&#filmstripLocalVideo {
bottom: 0px;
}
}
}
.remote-videos-container {
transition: opacity 1s;
}
&.hide-videos {
.remote-videos-container {
opacity: 0;
pointer-events: none;
}
padding-right: $defaultToolbarSize;
}
}

35
css/_flag-icon.scss Executable file
View File

@@ -0,0 +1,35 @@
.flag-icon-background {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
}
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 1.33333333em;
line-height: 1em;
}
.flag-icon:before {
content: "\00a0";
}
.flag-icon-au {
background-image: url(../images/countries/au.svg);
}
.flag-icon-ca {
background-image: url(../images/countries/ca.svg);
}
.flag-icon-de {
background-image: url(../images/countries/de.svg);
}
.flag-icon-gb {
background-image: url(../images/countries/gb.svg);
}
.flag-icon-fr {
background-image: url(../images/countries/fr.svg);
}
.flag-icon-us {
background-image: url(../images/countries/us.svg);
}

View File

@@ -24,33 +24,7 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-arrow_back:before {
content: "\e5c4";
}
.icon-event_note:before {
content: "\e616";
}
.icon-menu:before {
content: "\e5d2";
}
.icon-navigate_before:before {
content: "\e408";
}
.icon-navigate_next:before {
content: "\e409";
}
.icon-public:before {
content: "\e80b";
}
.icon-restore:before {
content: "\e8b3";
}
.icon-timer:before {
content: "\e425";
}
.icon-thumb-menu:before {
content: "\e5d4";
}
.icon-mic-camera-combined:before {
content: "\e903";
}
@@ -114,9 +88,6 @@
.icon-mic-disabled:before {
content: "\e912";
}
.icon-ninja:before {
content: "\e909";
}
.icon-raised-hand:before {
content: "\e91e";
}
@@ -150,6 +121,12 @@
.icon-volume:before {
content: "\e91a";
}
.icon-connection-lost:before {
content: "\e900";
}
.icon-connection:before {
content: "\e61a";
}
.icon-recDisable:before {
content: "\e613";
}
@@ -171,12 +148,3 @@
.icon-telephone:before {
content: "\e0cd";
}
.icon-add:before {
content: "\e145";
}
.icon-info:before {
content: "\e922";
}
.icon-gsm-bars:before {
content: "\e926";
}

View File

@@ -56,6 +56,9 @@
margin-bottom: 0px;
width: 100%;
border-radius: 0px;
> .aui-progress-indicator-value {
border-radius: 0px;
}
}
}
&__title {

47
css/_jitsi_popover.scss Normal file
View File

@@ -0,0 +1,47 @@
.jitsipopover {
position: absolute;
top: 0;
left: 0;
z-index: $jitsipopoverZ;
display: none;
max-width: 300px;
min-width: 100px;
text-align: left;
color: $popoverFontColor;
background-color: $popoverBg;
background-clip: padding-box;
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;
margin-top: -$popoverMenuPadding;
&__menu-padding {
height: $popoverMenuPadding;
width: 100px;
position: absolute;
bottom: -$popoverMenuPadding;
}
&__showmore {
display: block;
text-align: center;
width: 90px;
margin: 10px auto;
}
> .arrow {
position: absolute;
display: block;
left: 50%;
bottom: -5px;
margin-left: -5px;
width: 0;
height: 0;
border-color: transparent;
border-top-color: $popoverBg;
border-style: solid;
border-width: 5px;
border-bottom-width: 0;
}
}

View File

@@ -1,10 +1,25 @@
.shortcuts-list {
list-style-type: none;
padding: 0;
&__item {
display: flex;
justify-content: space-between;
margin-bottom: em(7, 14);
}
#keyboard-shortcuts {
display: none;
position: absolute;
bottom: 20px;
left: $defaultToolbarSize;
overflow: hidden;
padding: 20px;
margin-left: 10px;
z-index: $zindex10;
border-radius: $borderRadius;
background-attachment: scroll;
background-size: auto auto;
color: rgba(255, 255, 255, .8);
background-color: rgba(0, 0, 0, .8);
}
#keyboard-shortcuts .item-action {
color: #209EFF;
font-size: 14pt;
padding-right: 5px;
}
#keyboard-shortcuts-list {
list-style-type: none;
}

View File

@@ -1,33 +1,124 @@
/**
* Mousemove padding styles are used to add invisible elements to the popover
* to allow mouse movement from the popover trigger to the popover itself
* without triggering a mouseleave event.
*/
.popover-mousemove-padding-bottom {
bottom: -15px;
height: 20px;
position: absolute;
right: 0;
width: 100%;
.popover {
position: absolute;
top: 0;
left: 0;
z-index: $popoverZ;
display: none;
max-width: 300px;
min-width: 100px;
padding: 1px;
text-align: left;
color: #333333;
background-color: #ffffff;
background-clip: padding-box;
border: 1px solid #cccccc;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-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;
}
.popover-mousemove-padding-right {
height: 100%;
position: absolute;
right: -20;
top: 0;
width: 40px;
.popover.top {
margin-top: -10px;
}
/**
* An invisible element is added to the top of the popover to ensure the mouse
* stays over the popover when the popover's height is shrunk, which would then
* normally leave the mouse outside of the popover itself and cause a mouseleave
* event.
*/
.popover-mouse-padding-top {
height: 30px;
position: absolute;
right: 0;
top: -25px;
width: 100%;
.popover.right {
margin-left: 10px;
}
.popover.bottom {
margin-top: 10px;
}
.popover.left {
margin-left: -10px;
}
.popover-title {
margin: 0;
padding: 8px 14px;
font-size: 11pt;
font-weight: normal;
line-height: 18px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.popover-content {
padding: 9px 14px;
font-size: 10pt;
white-space:pre-wrap;
text-align: center;
}
.popover > .arrow,
.popover > .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.popover > .arrow {
border-width: 11px;
}
.popover > .arrow:after {
border-width: 10px;
content: "";
}
.popover.top > .arrow {
left: 50%;
margin-left: -11px;
border-bottom-width: 0;
border-top-color: #999999;
border-top-color: rgba(0, 0, 0, 0.25);
bottom: -11px;
}
.popover.top > .arrow:after {
content: " ";
bottom: 1px;
margin-left: -10px;
border-bottom-width: 0;
border-top-color: #ffffff;
}
.popover.right > .arrow {
top: 50%;
left: -11px;
margin-top: -11px;
border-left-width: 0;
border-right-color: #999999;
border-right-color: rgba(0, 0, 0, 0.25);
}
.popover.right > .arrow:after {
content: " ";
left: 1px;
bottom: -10px;
border-left-width: 0;
border-right-color: #ffffff;
}
.popover.bottom > .arrow {
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: #999999;
border-bottom-color: rgba(0, 0, 0, 0.25);
top: -11px;
}
.popover.bottom > .arrow:after {
content: " ";
top: 1px;
margin-left: -10px;
border-top-width: 0;
border-bottom-color: #ffffff;
}
.popover.left > .arrow {
top: 50%;
right: -11px;
margin-top: -11px;
border-right-width: 0;
border-left-color: #999999;
border-left-color: rgba(0, 0, 0, 0.25);
}
.popover.left > .arrow:after {
content: " ";
right: 1px;
border-right-width: 0;
border-left-color: #ffffff;
bottom: -10px;
}

View File

@@ -3,17 +3,22 @@
**/
.popupmenu {
min-width: 75px;
text-align: left;
padding: 0;
white-space: nowrap;
margin: 2px 0;
bottom: 0;
height: auto;
&:first-child {
margin-top: 2px;
}
&__item {
list-style-type: none;
text-align: left;
height: 35px;
&:hover {
background-color: rgba(9, 30, 66, 0.04);
background-color: $popupMenuSelectedItemBackground;
}
}
@@ -23,13 +28,15 @@
display: block;
box-sizing: border-box;
text-decoration: none;
color: #fff;
padding: 5px;
height: 100%;
font-size: 9pt;
width: 100%;
cursor: pointer;
padding: 0 5px;
cursor: hand;
&.disabled {
color: gray !important;
pointer-events: none;
}
}
@@ -39,12 +46,6 @@
vertical-align: middle;
}
&__link {
i {
cursor: pointer;
}
}
&__contents {
display: flex;
@@ -58,9 +59,9 @@
width: 100%;
.popupmenu__slider {
bottom: 50%;
position: absolute;
top: 50%;
transform: translate(0, -50%);
width: 100%;
}
}
@@ -72,6 +73,7 @@
display: inline-block;
min-width: 20px;
height: 100%;
text-align: center;
> * {
@include absoluteAligning();
@@ -83,15 +85,6 @@
}
}
/**
* Override reset css styling modifying all lists and set negative margin to
* reduce the visibility of padding on AtlasKit
* InlineDialogs.
*/
ul.popupmenu {
margin: -15px;
}
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important;
}

View File

@@ -1,81 +1,4 @@
.recordingSpinner {
display: none;
vertical-align: top;
}
.live-stream-dialog {
/**
* Set font-size to be consistent with Atlaskit FieldText.
*/
font-size: 14px;
.broadcast-dropdown,
.broadcast-dropdown-trigger {
text-align: left;
}
.form-footer {
text-align: right;
}
.live-stream-cta {
a {
cursor: pointer;
}
}
.google-api {
margin-top: 10px;
min-height: 36px;
text-align: center;
width: 100%;
}
/**
* The Google sign in button must follow Google's design guidelines.
* See: https://developers.google.com/identity/branding-guidelines
*/
.google-sign-in {
background-color: #4285f4;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
font-family: Roboto, arial, sans-serif;
font-size: 14px;
padding: 1px;
.google-cta {
color: white;
display: inline-block;
/**
* Hack the line height for vertical centering of text.
*/
line-height: 32px;
margin: 0 15px;
}
.google-logo {
background-color: white;
border-radius: 2px;
display: inline-block;
padding: 8px;
height: 18px;
width: 18px;
}
}
.google-panel {
align-items: center;
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
padding-bottom: 10px;
}
.stream-key-form {
.helper-link {
display: inline-block;
cursor: pointer;
margin-top: 5px;
}
}
}
}

View File

@@ -2,7 +2,7 @@
* Toolbar side panel main container element.
*/
#sideToolbarContainer {
background-color: $sideToolbarContainerBg;
background-color: rgba(0,0,0,0.8);
height: 100%;
left: $defaultToolbarSize;
max-width: $sidebarWidth;
@@ -22,10 +22,8 @@
/**
* Form elements and blocks.
*/
input,
a,
.sideToolbarBlock,
.form-control {
input, select, a,
.sideToolbarBlock, .form-control, .button-control {
display: block;
margin-top: 15px;
margin-left: 10%;
@@ -36,11 +34,19 @@
* Specify styling of elements inside a block.
*/
.sideToolbarBlock {
input, a {
input, button, a, select {
margin-left: 0;
margin-top: 5px;
width: 100%;
}
input[type='checkbox'] {
display: inline;
width: auto !important;
> label {
margin-top: 5px;
width: 80%;
}
}
}
/**
@@ -74,35 +80,42 @@
font-size: $toolbarTitleFontSize;
}
/**
* Subtitle specific properties.
*/
div.subTitle {
color: $defaultSideBarFontColor !important;
font-size: 11px;
font-weight: 500;
margin-left: 10%;
text-align: left;
}
/**
* First element after a title.
*/
.first {
margin-top: 0 !important;
}
/**
* Buttons in the side toolbar container.
*/
.button-control {
margin: 9px 0;
width: 100%;
}
}
}
.settings-menu {
display: flex;
flex-direction: column;
padding-left: 10%;
padding-right: 10%;
#device_settings {
width : auto !important;
text-align: center;
}
.moderator-checkbox {
display: inline-block;
margin: 0 5px 0;
width: auto;
}
.moderator-option {
margin-top: 15px;
}
.subTitle {
color: $defaultSideBarFontColor;
font-size: 11px;
font-weight: 500;
}
#deviceOptionsWrapper {
button {
float: none;
}
}

107
css/_toastr.scss Normal file
View File

@@ -0,0 +1,107 @@
/*
* Toastr
* Copyright 2012-2014 John Papa and Hans Fjällemark.
* All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
*
* Author: John Papa and Hans Fjällemark
* Project: https://github.com/CodeSeven/toastr
*
* Last updated: October 13, 2016
*/
.toast-title,
.toast-message .nickname {
font-weight: bold;
margin: 0 0 3px;
@include text-truncate;
}
.toast-message {
-ms-word-wrap: break-word;
word-wrap: break-word;
}
.toast-message a,
.toast-message label,
.toast-message .connected,
.toast-message .disconnected {
color: $notificationLinkColor;
text-decoration: none;
}
.toast-message a:hover {
text-decoration: underline;
}
.toast-message br {
display: none;
}
// close button
.toast-close-button {
color: $notificationColor;
background: transparent;
font-size: 15px;
line-height: 1.2;
height: 20px;
width: 20px;
padding: 0;
border: 0;
margin: -6px -10px 0 0;
float: right;
cursor: pointer;
@include opacity(0.4);
/* Additional properties for button version
iOS requires the button element instead of an anchor tag.
If you want the anchor version, it requires `href="#"`. */
-webkit-appearance: none;
}
.toast-close-button:hover,
.toast-close-button:focus {
@include opacity(1);
}
.toast {
color: $notificationColor;
background-color: $notificationBackground;
font-size: $notificationFontSize;
padding: $notificationPadding;
border: 1px solid lighten($notificationBackground, 10%);
@include border-radius($notificationBorderRadius);
@include box-shadow(1px, 1px, 2px, rgba(0,0,0,0.3));
@include opacity($notificationOpacity);
}
.toast:hover {
@include opacity(1);
}
#toast-container {
position: fixed;
z-index: $notificationZ;
}
#toast-container.notification-bottom-right {
$videoOffset: 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
bottom: 135px;
right: $filmstripToggleButtonWidth + $videoOffset;
}
#toast-container * {
@include box-sizing(border-box);
}
#toast-container .toast {
width: $notificationWidth;
margin: 0 0 8px;
}

View File

@@ -19,6 +19,74 @@
vertical-align: middle;
}
/**
* Toolbar button styles.
*/
.button {
color: #FFFFFF;
cursor: pointer;
z-index: $zindex1;
display: inline-block;
font-size: $toolbarFontSize !important;
height: 50px;
line-height: 50px !important;
position: relative;
text-align: center;
top:0px;
vertical-align: middle;
width: 50px;
&_hangup {
color: $hangupColor;
font-size: $hangupFontSize !important;
}
&[disabled] {
opacity: 0.5;
}
&:hover, &:active {
cursor: pointer;
text-decoration: none;
}
&:not(.toggled) {
&:hover, &:active {
// sum opacity with background layer should give us 0.8
background: $toolbarSelectBackground;
}
}
&.toggled {
background: $toolbarToggleBackground;
&.icon-camera {
@extend .icon-camera-disabled;
}
&.icon-full-screen {
@extend .icon-exit-full-screen;
}
&.icon-microphone {
@extend .icon-mic-disabled;
}
&.icon-visibility {
@extend .icon-visibility-off;
}
}
&.unclickable {
cursor: default;
&:hover, &:active, &.selected {
background: none;
cursor: default;
}
}
}
.toolbar-container {
display: block;
left:0;
@@ -36,107 +104,28 @@
* Common toolbar styles.
*/
.toolbar {
background-color: $toolbarBackground;
height: 100%;
pointer-events: auto;
position: relative;
z-index: $toolbarZ;
/**
* Ensure nested elements that don't have a button class, maybe because they
* are wrapped in a React Element, still display in a line.
* Splitter button in the toolbar.
*/
> div {
&__splitter {
background: $splitterColor;
display: inline-block;
}
/**
* Always on top overrides.
*/
&.always-on-top {
/**
* Toolbar button styles for always on top.
*/
.button {
font-size: $alwaysOnTopToolbarFontSize;
height: $alwaysOnTopToolbarSize;
line-height: $alwaysOnTopToolbarSize;
width: $alwaysOnTopToolbarSize;
&_hangup, &_hangup:hover {
font-size: $alwaysOnTopToolbarFontSize;
}
}
}
/**
* Toolbar button styles.
*/
.button {
color: $toolbarButtonColor;
cursor: pointer;
z-index: $zindex1;
display: inline-block;
font-size: $toolbarFontSize;
height: $defaultToolbarSize;
line-height: $defaultToolbarSize;
position: relative;
text-align: center;
top:0px;
height: 50%;
margin: 0 $splitterToolbarButtonMargin;
vertical-align: middle;
width: $defaultToolbarSize;
&[disabled] {
opacity: 0.5;
}
&:hover, &:active {
color: $toolbarButtonColor;
cursor: pointer;
text-decoration: none;
}
&_hangup, &_hangup:hover {
color: $hangupColor;
font-size: $hangupFontSize;
}
&:not(.toggled) {
&:hover, &:active {
// sum opacity with background layer should give us 0.8
background: $toolbarSelectBackground;
}
}
&.toggled {
background: $toolbarToggleBackground;
&.icon-camera {
@extend .icon-camera-disabled;
}
&.icon-full-screen {
@extend .icon-exit-full-screen;
}
&.icon-microphone {
@extend .icon-mic-disabled;
}
}
&.unclickable {
cursor: default;
&:hover, &:active, &.selected {
background: none;
cursor: default;
}
}
width: 1px;
}
/**
* Primary toolbar styles.
*/
&_primary {
background-color: $toolbarBackground;
position: absolute;
left: 50%;
top: 30px;
@@ -146,21 +135,13 @@
border-radius: 3px;
opacity: 0;
&.always-on-top {
height: $alwaysOnTopToolbarSize;
top: 10px;
}
@include transform(translateX(-50%));
> a:first-child.button,
> div:first-child .button {
.button:first-child {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
> a:last-child.button,
> div:last-child .button {
.button:last-child {
border-bottom-right-radius: 3px;
border-top-right-radius: 3px;
}
@@ -174,7 +155,6 @@
* Secondary toolbar styles.
*/
&_secondary {
background-color: $secondaryToolbarBg;
position: absolute;
align-items: center;
box-sizing: border-box;
@@ -188,25 +168,14 @@
height: 100%;
justify-content: flex-start;
left: 0;
padding-top: 24px;
pointer-events: none;
padding-top: 10px;
top: 0;
transform: translateX(-100%);
width: $defaultToolbarSize;
-webkit-transform: translateX(-100%);
.button {
font-size: $secToolbarFontSize;
height: $secToolbarLineHeight;
line-height: $secToolbarLineHeight;
}
> * {
pointer-events: auto
}
.button.toggled:not(.icon-raised-hand):not(.button-active) {
background: $secondaryToolbarBg;
background: $toolbarSelectBackground;
cursor: pointer;
text-decoration: none;
@@ -225,27 +194,19 @@
* Styles the toolbar in filmstrip-only mode.
*/
&_filmstrip-only {
background-color: $toolbarBackground;
border-radius: 3px;
bottom: 0;
display: inline-block;
height: auto;
width: $defaultFilmStripOnlyToolbarSize;
position: absolute;
right: 0;
width: $defaultToolbarSize;
.button {
height: 37px;
line-height: 37px !important;
width: 37px;
}
.button-popover-message {
width: 100px;
}
.toolbar-button-wrapper:first-child .button {
.button:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.toolbar-button-wrapper:last-child .button {
.button:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
@@ -261,14 +222,6 @@
}
}
.filmstrip-only {
.toolbox,
.toolbox-toolbars {
align-items: center;
display: flex;
}
}
.subject {
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
border-bottom-left-radius: 12px;
@@ -293,15 +246,11 @@
}
}
#toolbar_button_profile {
height: $toolbarAvatarSize + 2*$toolbarAvatarPadding;
}
a.button>#avatar {
border-radius: 50%;
padding-bottom: $toolbarAvatarPadding;
padding-top: $toolbarAvatarPadding;
width: $toolbarAvatarSize;
padding-bottom: 10px;
padding-top: 10px;
width: 30px;
}
#feedbackButton {

View File

@@ -1,11 +1,3 @@
.flip-x {
transform: scaleX(-1);
}
.hidden {
display: none;
}
/**
* Hides an element.
*/
@@ -13,10 +5,6 @@
display: none !important;
}
.invisible {
visibility: hidden;
}
/**
* Shows an element.
*/
@@ -47,4 +35,4 @@
display: -ms-flexbox !important;
display: -webkit-flex !important;
display: flex !important;
}
}

View File

@@ -28,20 +28,21 @@ $defaultColor: #F1F1F1;
$defaultSideBarFontColor: #44A5FF;
$defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$tooltipBg: rgba(0,0,0, 0.7);
/**
* Toolbar
*/
$alwaysOnTopToolbarFontSize: 1em;
$alwaysOnTopToolbarSize: 30px;
$defaultToolbarSize: 50px;
$defaultFilmStripOnlyToolbarSize: 37px;
$secToolbarFontSize: 1.9em;
$secToolbarLineHeight: 45px;
$toolbarAvatarPadding: 10px;
$toolbarAvatarSize: 40px;
$splitterToolbarButtonMargin: 18px;
$toolbarBackground: rgba(0, 0, 0, 0.5);
$toolbarBadgeBackground: #165ECC;
$toolbarBadgeColor: #FFFFFF;
$toolbarFontSize: 1.9em;
$toolbarSelectBackground: rgba(0, 0, 0, .6);
$toolbarTitleColor: #FFFFFF;
$toolbarTitleFontSize: 19px;
$toolbarToggleBackground: #12499C;
/**
* Main controls
@@ -64,7 +65,6 @@ $connectionIndicatorBg: #165ecc;
$audioLevelShadow: rgba(9, 36, 77, 0.9);
$videoStateIndicatorColor: $defaultColor;
$videoStateIndicatorBackground: $toolbarBackground;
$videoStateIndicatorSize: 40px;
/**
* Feedback Modal
@@ -87,6 +87,20 @@ $modalMockAKInputBackground: #fafbfc;
$modalMockAKInputBorder: 1px solid #f4f5f7;
$modalTextColor: #333;
/**
* Notifications
*/
$notificationFontSize: 13px;
$notificationColor: #FFFFFF;
$notificationBackground: $tooltipBg;
$notificationTitleColor: $notificationColor;
$notificationMessageColor: $notificationColor;
$notificationLinkColor: $notificationColor;
$notificationOpacity: 0.9;
$notificationPadding: 15px 20px;
$notificationBorderRadius: 4px;
$notificationWidth: 215px;
/**
* Misc.
*/
@@ -109,11 +123,13 @@ $reloadZ: 20;
$poweredByZ: 100;
$ringingZ: 300;
$sideToolbarContainerZ: 300;
$toolbarZ: 350;
$toolbarZ: 400;
$tooltipsZ: 401;
$dropdownMaskZ: 900;
$dropdownZ: 901;
$centeredVideoLabelZ: 1010;
$jitsipopoverZ: 1010;
$centeredVideoLabelZ: 1011;
$notificationZ: 1012;
$popoverZ: 1015;
$overlayZ: 1016;
@@ -138,25 +154,11 @@ $formPadding: 16px;
/**
* Unsupported browser
*/
$primaryUnsupportedBrowserButtonBgColor: #0052CC;
$unsupportedBrowserButtonBgColor: rgba(9, 30, 66, 0.04);
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
$unsupportedBrowserButtonBgColor: #ff9a00;
$unsupportedBrowserTextColor: #4a4a4a;
$unsupportedBrowserTextSmallFontSize: 17px;
$unsupportedBrowserTitleColor: #fff;
$unsupportedBrowserTitleFontSize: 24px;
$unsupportedDesktopBrowserTextColor: rgba(255, 255, 255, 0.7);
$unsupportedDesktopBrowserTextFontSize: 21px;
/**
* The size of the default watermark.
*/
$watermarkWidth: 186px;
$watermarkHeight: 74px;
/**
* Welcome page variables.
*/
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
$welcomePageTitleColor: #fff;

View File

@@ -1,285 +0,0 @@
/**
* Override other styles to support vertical filmstrip mode.
*/
.vertical-filmstrip {
/*
* Firefox sets flex items to min-height: auto and min-width: auto,
* preventing flex children from shrinking like they do on other browsers.
* Setting min-height and min-width 0 is a workaround for the issue so
* Firefox behaves like other browsers.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
*/
@mixin minHWAutoFix() {
min-height: 0;
min-width: 0;
}
#etherpad,
#sharedvideo {
text-align: left;
}
.filmstrip {
align-items: flex-end;
box-sizing: border-box;
display: flex;
flex-direction: column-reverse;
height: 100%;
/**
* fixed positioning is necessary for remote menus and tooltips to pop
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
* a library called popper which will position its elements fixed if
* any parent is also fixed.
*/
position: fixed;
/**
* z-index adjusting is needed because the video state indicator has to
* display over the filmstrip when no videos are displayed but still be
* clickable but its inline dialogs must display over the video state
* indicator when videos are displayed.
*/
z-index: #{$tooltipsZ + 1};
/**
* Hide videos by making them slight to the right.
*/
.filmstrip__videos {
right: 0;
&.hidden {
bottom: auto;
right: -196px;
}
/**
* An id selector is used to match id specificity with existing
* filmstrip styles.
*/
&#remoteVideos {
/**
* Remove horizontal filmstrip padding used to prevent videos
* from overlapping the left-side toolbar. An id selector is
* used to match id specificity with filmstrip styles.
*/
padding-left: 0;
transition: right 2s;
}
}
/**
* Re-styles the local Video and invite button to better fit the
* vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
.filmstrip__invite {
padding-bottom: 0px;
padding-top: 5px;
z-index: $dropdownZ;
}
}
/**
* Remove unnecssary padding that is normally used to prevent horizontal
* filmstrip from overlapping the left edge of the screen.
*/
#filmstripLocalVideo,
#filmstripRemoteVideos {
padding: 0;
}
#filmstripRemoteVideos {
@include minHWAutoFix();
display: flex;
flex: 1;
flex-direction: column;
height: auto;
justify-content: flex-end;
#filmstripRemoteVideosContainer {
flex-direction: column-reverse;
/**
* Add padding as a hack for Firefox not to show scrollbars when
* unnecessary.
*/
padding: 1px 0;
overflow-x: hidden;
}
}
/**
* Rotate the hide filmstrip icon so it points towards the right edge
* of the screen.
*/
&__toolbar {
transform: rotate(-90deg);
}
/**
* Move the remote video menu trigger to the bottom left of the
* video thumbnail.
*/
.remotevideomenu,
.remote-video-menu-trigger {
bottom: 0;
left: 0;
top: auto;
right: auto;
}
.remote-video-menu-trigger {
margin-bottom: 7px;
}
#remoteVideos {
@include minHWAutoFix();
flex-direction: column;
flex-grow: 1;
}
.videocontainer {
/**
* Move status icons to the bottom right of the thumbnail.
*/
&__toolbar {
text-align: right;
.right {
float: none;
margin: auto;
}
}
}
}
&.filmstrip-only {
.filmstrip {
flex-direction: row-reverse;
}
.filmstrip__videos-filmstripOnly {
height: 100%;
}
/**
* In filmstrip only mode, the toolbar is normally displayed in the
* vertical center of the filmstrip strip. In vertical filmstrip mode,
* that alignment makes the toolbar appear floating and detached from
* the filmstrip. So, instead anchor the toolbar next to the local
* video.
*/
.toolbar_filmstrip-only {
bottom: 0;
top: auto;
transform: none;
}
}
/**
* These styles are for the video labels that display on the top right. The
* styles adjust the labels' positioning as the filmstrip itself or
* filmstrip's remote videos appear and disappear.
*
* The class with-filmstrip is for when the filmstrip is visible.
* The class without-filmstrip is for when the filmstrip has been toggled to
* be hidden.
* The class opening is for when the filmstrip is transitioning from hidden
* to visible.
*/
.video-state-indicator.moveToCorner {
transition: right 0.5s;
&.with-filmstrip {
&#recordingLabel {
right: 200px;
}
&#videoResolutionLabel {
right: 150px;
}
}
&.with-filmstrip.opening {
transition: 0.9s;
transition-timing-function: ease-in-out;
}
&.without-filmstrip {
transition: 1.2s ease-in-out;
transition-delay: 0.1s;
}
}
/**
* Apply hardware acceleration to prevent flickering on scroll. The
* selectors are specific to icon wrappers to prevent fixed position dialogs
* and tooltips from getting a new location context due to translate3d.
*/
.connection-indicator,
.remote-video-menu-trigger,
.indicator-icon-container {
transform: translate3d(0, 0, 0);
}
.indicator-container {
float: none;
}
/**
* FIXME: disable pointer to allow any elements moved below to still be
* clickable. The real fix would to make sure those moved elements are
* actually part of the toolbar instead of positioning being faked.
*/
.videocontainer__toolbar {
pointer-events: none;
> div {
pointer-events: none;
}
.toolbar-icon {
pointer-events: all;
}
}
}
/**
* Workarounds for Edge, IE11, and Firefox not handling scrolling properly
* with flex-direction: column-reverse. The remove videos in filmstrip should
* start scrolling from the bottom of the filmstrip, but in those browsers the
* scrolling won't happen. Per W3C spec, scrolling should happen from the
* bottom. As such, use css hacks to get around the css issue, with the intent
* being to remove the hacks as the spec is supported.
*/
@mixin undoColumnReverseVideos() {
.vertical-filmstrip {
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
flex-direction: column;
}
}
}
/** Firefox detection hack **/
@-moz-document url-prefix() {
@include undoColumnReverseVideos();
}
/** IE11 detection hack **/
@media screen and (-ms-high-contrast: active),
screen and (-ms-high-contrast: none) {
@include undoColumnReverseVideos();
}
/** Edge detection hack **/
@supports (-ms-ime-align:auto) {
@include undoColumnReverseVideos();
}

View File

@@ -12,27 +12,6 @@
overflow: hidden;
}
#largeVideoBackgroundContainer,
.large-video-background {
height: 100%;
filter: blur(40px);
left: 0;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
&.fit-full-height #largeVideoBackground {
height: 100%;
width: auto;
}
.fit-full-width #largeVideoBackground {
height: auto;
width: 100%;
}
}
.videocontainer {
position: relative;
text-align: center;
@@ -52,85 +31,32 @@
&__toptoolbar {
position: absolute;
left: 0;
pointer-events: none;
z-index: $zindex10;
z-index: $zindex3;
width: 100%;
box-sizing: border-box; // Includes the padding in the 100% width.
/**
* FIXME (lenny): Disabling pointer-events is a pretty big sin that
* sidesteps the problems. There are z-index wars occurring within
* videocontainer and AtlasKit Tooltips rely on their parent z-indexe
* being higher than whatever they need to appear over. So set a higher
* z-index for the tooltip containers but make any empty space not block
* mouse overs for various mouseover triggers.
*/
pointer-events: none;
* {
pointer-events: auto;
}
.indicator-container {
display: inline-block;
float: left;
pointer-events: all;
}
/**
* Need to overwrite the background for the top toolbar dark theme div
* wrapper needed before we're able to move all top toolbar indicators
* creation to react.
*/
.ckAJgx {
background: none;
}
}
&__toolbar {
bottom: 0;
height: $thumbnailToolbarHeight;
padding: 0 5px 0 5px;
height: $thumbnailToolbarHeight;
}
&__toptoolbar {
$toolbarIconMargin: 5px;
$toolbarPadding: 5px;
top: 0;
padding: $toolbarPadding;
padding-bottom: 0;
/**
* Override text-align center as icons need to be left justified.
*/
text-align: left;
/**
* Intentionally use margin on the icon itself as AtlasKit InlineDialog
* positioning depends on the trigger (indicator icon).
*/
.indicator {
margin-left: 5px;
margin-top: $toolbarIconMargin;
}
.indicator-container:nth-child(1) .indicator {
margin-left: $toolbarIconMargin;
}
.indicator-container {
display: inline-block;
vertical-align: top;
.popover-trigger {
display: inline-block;
}
}
.connection-indicator,
.indicator {
span.indicator {
position: relative;
font-size: 8px;
text-align: center;
line-height: $thumbnailIndicatorSize;
display: none;
padding: 0;
margin-right: em(5, 8);
float: left;
@include circle($thumbnailIndicatorSize);
box-sizing: border-box;
z-index: $zindex3;
@@ -138,6 +64,10 @@
color: $thumbnailPictogramColor;
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
&:last-child {
margin-right: 0;
}
.indicatoricon {
@include absoluteAligning();
}
@@ -149,35 +79,31 @@
left: 0;
@include transform(translate(0, -50%));
&_empty,
&_lost
&_empty
{
color: #8B8B8B;/*#FFFFFF*/
overflow: hidden;
}
&_lost
{
color: #8B8B8B;
overflow: visible;
}
&_full
{
@include topLeft();
color: #FFFFFF;/*#15A1ED*/
overflow: hidden;
}
&_ninja
{
font-size: 1.5em;
}
}
.icon-gsm-bars {
cursor: pointer;
.icon-connection,
.icon-connection-lost {
font-size: 1em;
}
}
.hide-connection-indicator {
display: none;
}
}
&__hoverOverlay {
@@ -208,8 +134,8 @@
-o-transform: scale(-1, 1);
}
#localVideoWrapper video,
#localVideoWrapper object {
#localVideoWrapper>video,
#localVideoWrapper>object {
border-radius: $borderRadius !important;
cursor: hand;
object-fit: cover;
@@ -222,10 +148,6 @@
text-align: center;
}
#largeVideoWrapper {
box-shadow: 0 0 20px -2px #444;
}
#largeVideo,
#largeVideoWrapper
{
@@ -234,8 +156,8 @@
#sharedVideo,
#etherpad,
#localVideoWrapper video,
#localVideoWrapper object,
#localVideoWrapper>video,
#localVideoWrapper>object,
#localVideoWrapper,
#largeVideoWrapper,
#largeVideoWrapper>video,
@@ -261,7 +183,6 @@
/**
* Positions video thumbnail display name and editor.
*/
#alwaysOnTop .displayname,
.videocontainer .displayname,
.videocontainer .editdisplayname {
display: inline-block;
@@ -281,15 +202,6 @@
z-index: $zindex2;
}
#alwaysOnTop .displayname {
font-size: 15px;
position: inherit;
width: 100%;
left: 0px;
top: 0px;
margin-top: 10px;
}
/**
* Positions video thumbnail display name editor.
*/
@@ -344,6 +256,7 @@
padding: 0;
border: 0;
margin: 0px 5px 0px 0px;
float: left;
}
/**
@@ -356,73 +269,37 @@
/**
* Toolbar icons positioned on the right.
*/
.moderator-icon {
display: inline-block;
&.right {
float: right;
margin: 0px 0px 0px 5px;
}
.toolbar-icon {
margin: 0;
}
.toolbar-icon.right {
float: right;
margin: 0px 0px 0px 5px;
}
.raisehandindicator {
#raisehandindicator {
background: $raiseHandBg;
}
.connection-indicator {
background: $connectionIndicatorBg;
&.status-high {
background: green;
}
&.status-med {
background: #FFD740;
}
&.status-lost {
background: gray;
}
&.status-low {
background: #BF2117;
}
&.status-other {
background: $connectionIndicatorBg;
}
#connectionindicator {
background: $connectionIndicatorBg;
}
.remote-video-menu-trigger,
.remotevideomenu
{
display: inline-block;
position: absolute;
top: 0px;
right: 0;
z-index: $zindex2;
margin: 7px;
z-index: $zindex3;
width: 18px;
height: 13px;
color: #FFF;
font-size: 10pt;
>i{
cursor: hand;
}
}
.remote-video-menu-trigger {
margin-top: 7px;
font-size: 8pt;
}
/**
* Audio indicator on video thumbnails.
*/
.videocontainer>span.audioindicator,
.videocontainer>.audioindicator-container {
.videocontainer>span.audioindicator {
position: absolute;
display: inline-block;
left: 6px;
@@ -528,20 +405,6 @@
width: auto;
}
#videoNotAvailableScreen {
text-align: center;
#avatarContainer {
height: 50vh;
display:inline-block;
margin-top: 25vh;
#avatar {
border-radius: 50%;
height: 100%;
}
}
}
.sharedVideoAvatar {
height: 100%;
width: 100%;
@@ -592,8 +455,8 @@
filter: grayscale(100%);
}
#remotePresenceMessage,
#remoteConnectionMessage {
display: none;
position: absolute;
width: auto;
z-index: $zindex2;
@@ -601,11 +464,6 @@
font-size: 14px;
text-align: center;
color: #FFF;
left: 50%;
transform: translate(-50%, 0);
}
#remotePresenceMessage .presence-label,
#remoteConnectionMessage {
opacity: .80;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
0px 1px 1px rgba(0,0,0,0.3),
@@ -618,10 +476,6 @@
padding-left: 10px;
padding-right: 10px;
}
#remotePresenceMessage .no-presence,
#remoteConnectionMessage {
display: none;
}
#localConnectionMessage {
display: none;
@@ -640,3 +494,107 @@
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
}
.audio-only-label {
display: flex;
height: auto;
justify-content: center;
z-index: $centeredVideoLabelZ;
}
.audio-only-label,
.video-state-indicator {
background: $videoStateIndicatorBackground;
color: $videoStateIndicatorColor;
cursor: default;
font-size: 13px;
height: 40px;
line-height: 20px;
text-align: center;
min-width: 40px;
padding: 10px 5px;
border-radius: 50%;
position: absolute;
box-sizing: border-box;
}
#videoResolutionLabel,
.centeredVideoLabel {
z-index: $centeredVideoLabelZ;
}
.centeredVideoLabel {
bottom: 45%;
border-radius: 2px;
display: none;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
&.moveToCorner {
bottom: auto;
}
}
.moveToCorner {
position: absolute;
top: 30px;
right: 30px;
}
.moveToCorner + .moveToCorner {
right: 80px;
}
.video-state-indicator-menu {
display: none;
padding: 10px;
position: absolute;
right: -10px;
top: 20px;
.video-state-indicator-menu-options {
background: $popoverBg;
border-radius: 3px;
color: $popoverFontColor;
margin-top: 20px;
padding: 5px 0;
position: relative;
div {
cursor: pointer;
padding: 10px;
padding-right: 30px;
text-align: left;
white-space: nowrap;
&.active {
background: $toolbarToggleBackground;
}
&:hover:not(.active) {
background: $popupMenuSelectedItemBackground;
}
i {
margin-right: 5px;
vertical-align: middle;
}
}
}
.video-state-indicator-menu-options::after {
content: " ";
border-color: transparent transparent $popoverBg transparent;
border-style: solid;
border-width: 5px;
position: absolute;
right: 15px;
top: -10px;
}
}
.video-state-indicator:hover,
.video-state-indicator *:hover {
.video-state-indicator-menu {
display: block;
}
}

View File

@@ -1,87 +1,200 @@
.welcome {
font-family: $welcomePageFontFamily;
height: 100%;
overflow: auto;
position: relative;
#disable_welcome {
display:none;
}
.header {
align-items: center;
background: $welcomePageHeaderBackground;
display: flex;
flex-direction: column;
overflow: hidden;
.disable_welcome_position
{
margin: -139px auto 0px auto;
padding-left: 39px;
padding-top: 7px;
width: 269px;
height: 31px;
display:block;
}
#disable_welcome + label
{
background-image: url(../images/welcome_page/disable-welcome.png);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-size: 16px;
color: #acacac;
z-index: $zindex2;
}
#disable_welcome:checked + label
{
background-image: url(../images/welcome_page/disable-welcome-selected.png);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-size: 16px;
color: #acacac;
z-index: $zindex2;
}
#enter_room_form {
border-radius: 1px;
background-color: #FFFFFF;
border: none;
-moz-border-radius: 1px;
-webkit-border-radius: 1px;
-webkit-appearance: none;
height: 55px;
box-shadow: none;
float: left;
}
.domain-name
{
float: left;
height: 55px;
line-height: 55px;
font-size: 18px;
font-weight: 500;
padding-left: 20px;
color: $defaultDarkColor;
}
.enter-room {
&__field {
font-size: 15px;
border: none;
-webkit-appearance: none;
width: 228px;
height: 55px;
line-height: 55px;
font-weight: 500;
box-shadow: none;
float: left;
background-color: #FFFFFF;
position: relative;
z-index: $zindex2;
}
&__reload {
display: block;
width: 30px;
color: #acacac;
font-size: 1.9em;
line-height: 55px;
z-index: $zindex3;
float: left;
cursor: pointer;
text-align: center;
.header-text {
display: flex;
flex-direction: column;
justify-content: space-around;
margin-top: 120px;
margin-bottom: 20px;
min-height: 286px;
width: 645px;
}
.header-text-title {
color: $welcomePageTitleColor;
font-size: 48px;
letter-spacing: -1px;
line-height: 58px;
margin-bottom: 20px;
}
.header-text-description {
color: $welcomePageDescriptionColor;
font-size: 20px;
line-height: 28px;
opacity: 0.8;
}
.header-image {
background-image: url(../images/welcome_page/curves.png);
background-size: contain;
height: 209px;
position: absolute;
width: 1070px;
}
#new_enter_room {
align-items: center;
display: flex;
margin-bottom: 20px;
position: relative;
z-index: 2;
.enter-room-input {
display: inline-block;
margin-right: 15px;
width: 350px;
}
}
}
.welcome-page-button {
font-size: 16px;
&__button {
width: 73px;
height: 45px;
background-color: #21B9FC;
moz-border-radius: 1px;
-webkit-border-radius: 1px;
color: #ffffff;
font-weight: 600;
border: none;
margin-top: 5px;
font-size: 19px;
padding-top: 6px;
outline: none;
float:left;
position: relative;
z-index: $zindex2;
}
}
.welcome.with-content {
.header {
min-height: 552px;
}
.header-image {
left: -61px;
top: 401px;
}
#enter_room_container {
margin: 70px auto 0px auto;
display: table;
}
.welcome.without-content {
.header {
height: 100%;
}
.header-image {
bottom: -20px;
left: 0;
}
#enter_room{
float:left;
padding-right: 5px;
}
#welcome_page_header
{
background-image: url(../images/welcome_page/pattern-header.png);
height: 290px;
width: 100%;
position: absolute;
}
#welcome_page_main
{
background-image:url(../images/welcome_page/pattern-body.png);
width: 100%;
position: absolute;
margin-top: 290px;
}
#brand_header
{
background-image:url(../images/welcome_page/header-big.png);
width: 583px;
height: 274px;
margin: -110px auto 0px auto;
}
#header_text
{
width: 885px;
height: 100px;
color: #ffffff;
font-size: 24px;
text-align: center;
margin: 0px auto 0px auto;
}
#features
{
margin-top: 30px;
position: relative;
}
.feature_row
{
position: relative;
width: 976px;
margin: 0px auto 30px auto;
padding-right: 75px;
}
.feature_holder
{
float:left;
width: 169px;
padding-left: 75px;
padding-bottom: 30px;
}
.feature_icon
{
background-image:url(../images/welcome_page/bubble.png);
background-repeat: no-repeat;
width: 169px;
height: 169px;
color: #ffffff;
font-size: 22px;
/*font-weight: bold;*/
text-align: center;
display: table-cell;
padding: 50px 26px 0px 20px;
}
.feature_description
{
width: 190px;
color: #ffffff;
font-size: 16px;
padding-top: 30px;
line-height: 22px;
font-weight: 200;
}

View File

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

View File

@@ -23,6 +23,19 @@
color: $inputControlEmColor;
}
&__hint {
margin-top: 0;
font-size: $hintFontSize;
span {
vertical-align: middle;
}
&_error {
color: $errorColor;
}
}
&__container {
position: relative;
width: 100%;

View File

@@ -3,10 +3,13 @@
display: inline-block;
width: 100%;
padding: 5px 7px;
color: $inputColor;
border-radius: $borderRadius;
line-height: 32px;
height: 32px;
text-align: left;
border:1px solid $inputBorderColor;
background-color: $inputBackground;
margin-bottom: 8px;
&:last-child {

View File

@@ -28,9 +28,12 @@
@import 'font-awesome';
/* Fonts END */
@import 'flag-icon';
/* Modules BEGIN */
@import 'aui_reset';
@import 'dial-out';
@import 'toastr';
@import 'base';
@import 'utils';
@import 'overlay/overlay';
@@ -40,19 +43,18 @@
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'modals/invite/info';
@import 'modals/speaker_stats/speaker_stats';
@import 'modals/video-quality/video-quality';
@import 'videolayout_default';
@import 'notice';
@import 'popup_menu';
@import 'recording';
@import 'login_menu';
@import 'popover';
@import 'jitsi_popover';
@import 'contact_list';
@import 'chat';
@import 'ringing/ringing';
@import 'welcome_page';
@import 'welcome_page_content';
@import 'toolbars';
@import 'side_toolbar_container';
@import 'jquery.contextMenu';
@@ -60,17 +62,16 @@
@import 'redirect_page';
@import 'components/form-control';
@import 'components/link';
@import 'shortcuts/main';
@import 'components/button-control';
@import 'components/input-control';
@import 'components/input-slider';
@import "modals/invite/invite";
@import "connection-info";
@import 'aui-components/dropdown';
@import '404';
@import 'policy';
@import 'popover';
@import 'filmstrip';
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'vertical_filmstrip_overrides';
/* Modules END */

View File

@@ -1,10 +1,6 @@
.dialog {
box-sizing: border-box;
height: auto;
min-height: 131px;
overflow: visible;
visibility: visible;
width: 400px;
height: auto;
h1, h2, h3, h4, h5, h6 {
color: $auiDialogColor;
@@ -14,27 +10,10 @@
&-icon {
color: $auiDialogColor;
text-indent: -999em;
&-small {
width: 14px;
height: 14px;
&:before {
color: inherit;
font-family: "FontAwesome";
font-size: 16px;
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: normal;
left: 0;
line-height: 1;
margin-top: -8px;
position: absolute;
text-indent: 0;
speak: none;
top: 50%;
}
}
}
@@ -43,10 +22,6 @@
right: 20px;
position: absolute;
top: -49px;
&:before {
content: "\f00d";
}
}
&-dialog2 {
@@ -56,16 +31,8 @@
}
&-header {
border-bottom: 1px solid $auiBorderColor;
border-radius: 5px 5px 0 0;
box-sizing: border-box;
color: #333;
display: table;
font-weight: normal;
height: em(58, 12);
margin-top: -69px;
padding: 0 20px;
width: 100%;
border-bottom: 1px solid $auiBorderColor;
h2 {
font-size: em(20, 12);
@@ -74,40 +41,19 @@
}
&-main {
display: table-cell;
padding-right: 0;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
}
&-footer {
border-top: 1px solid $auiBorderColor;
border-radius: 0 0 5px 5px;
box-sizing: border-box;
height: 51px;
overflow: hidden;
padding: 10px 20px;
width: 100%;
&:empty {
height: 5px;
padding: 0;
}
}
&-content {
background-color: $auiDialogBg;
box-sizing: border-box;
color: $auiDialogColor;
font-size: em(14, 12);
overflow: auto;
max-height: 100%;
padding: 20px;
min-height: 0;
background-color: $auiDialogContentBg;
color: $auiDialogColor;
p,span, h3 {
font-weight: $labelFontWeight;
@@ -126,61 +72,20 @@
}
}
.input-control {
background-color: $auiDialogContentBg;
color: $auiDialogColor;
}
.form-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
}
@media all and (max-width: 420px) {
.aui-dialog2-small .aui-dialog2-content {
height: 100%;
}
}
.modal-dialog-form {
margin-top: 5px !important;
color: $modalTextColor;
.input-control {
background: $modalMockAKInputBackground;
border: $modalMockAKInputBorder;
color: inherit;
}
&-error {
margin-bottom: 8px;
}
/**
* Override Atlaskit dropdown styling when in a modal because the dropdown
* backgrounds clash with the modal backgrounds.
*/
.htclLc[data-role=droplistContent] {
border: 1px solid #455166;
}
}
.modal-dialog-footer {
font-size: $modalButtonFontSize;
}
/**
* Styling inline dialog errors.
*/
.inline-dialog-error {
margin-top: 16px;
&-text {
color: $dialogErrorText;
margin-bottom: 8px;
text-align: center;
}
&-button {
display: block;
margin: 16px auto 0 auto;
}
}

View File

@@ -26,22 +26,16 @@
width: 30%;
}
}
&-spinner {
justify-content: center;
display: flex;
height: 100%;
align-items: center;
}
}
.desktop-picker-source {
color: $defaultDarkFontColor;
margin-top: 10px;
text-align: center;
&.is-selected {
.desktop-source-preview-image-container {
background: rgba(255,255,255,0.3);
background: rgba(0, 0, 0, 0.1);
border-radius: $borderRadius;
}
}

View File

@@ -1,4 +1,6 @@
.device-selection {
color: $feedbackInputTextColor;
.device-selectors {
font-size: 14px;
@@ -20,15 +22,19 @@
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
.device-selector-trigger {
background-color: #0E1624;
border: 1px solid #455166;
border-radius: 5px;
background-color: rgba(9, 30, 66, 0.04);
border-radius: 3px;
color: #505f79;
display: flex;
height: 2.3em;
justify-content: space-between;
line-height: 2.3em;
overflow: hidden;
padding: 0 8px;
&:hover {
background-color: rgba(9,30,66,.08);
}
}
.device-selector-trigger-disabled {
.device-selector-trigger {
@@ -102,21 +108,19 @@
.audio-output-preview {
font-size: 14px;
margin-top: 10px;
a {
color: 4C9AFF;
cursor: pointer;
text-decoration: none;
}
}
.audio-input-preview {
background: #7b7b7b;
background: #f4f5f7;
border-radius: 5px;
height: 6px;
.audio-input-preview-level {
background: #4C9AFF;
background: #0052cc;
border-radius: 5px;
height: 100%;
-webkit-transition: width .1s ease-in-out;

View File

@@ -45,59 +45,83 @@
animation-timing-function: ease-in-out
}
.feedback-dialog {
.details {
margin-top: 20px;
padding-left: 60px;
padding-right: 60px;
.feedback.aui-dialog2{
.aui-dialog2{
&-header {
background-color: $feedbackContentBg;
border-bottom-color: transparent;
padding-top: 30px;
h2 {
color: $feedbackTextColor;
text-align: center;
}
}
textarea {
min-height: 100px;
&-content {
background-color: $feedbackContentBg;
text-align: center;
padding: 10px 40px 20px 40px;
.input-control {
background-color: $feedbackInputBg;
color: $feedbackInputTextColor;
&::-webkit-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $feedbackInputPlaceholderColor;
}
&:-ms-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
}
.rating {
line-height: 1.2;
text-align: center;
margin-top: 10px;
.star-label {
height: 16px;
font-size: 14px;
color: $rateStarLabelColor;
}
.star-btn {
display: inline-block;
color: $rateStarDefault;
font-size: $rateStarSize;
position: relative;
cursor: pointer;
outline: none;
text-decoration: none;
@include transition(all .2s ease);
&.starHover,
&.active,
&:hover {
color: $rateStarActivity;
};
}
}
.details {
padding-left: 60px;
padding-right: 60px;
margin-top: 20px;
textarea {
min-height: 100px;
}
}
}
&-footer {
background-color: $feedbackContentBg;
border-top-color: transparent;
.button-control {
color: $feedbackCancelFontColor;
}
}
}
.input-control {
background-color: $feedbackInputBg;
color: $feedbackInputTextColor;
&::-webkit-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $feedbackInputPlaceholderColor;
}
&:-ms-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
}
.rating {
line-height: 1.2;
margin-top: 10px;
text-align: center;
.star-label {
color: $rateStarLabelColor;
font-size: 14px;
height: 16px;
}
.star-btn {
color: $rateStarDefault;
cursor: pointer;
display: inline-block;
font-size: $rateStarSize;
outline: none;
position: relative;
text-decoration: none;
@include transition(all .2s ease);
&.active,
&:hover,
&.starHover {
color: $rateStarActivity;
};
}
}
}
}

View File

@@ -1,26 +0,0 @@
/**
* Styles errors and links in the AddPeopleDialog.
*/
.modal-dialog-form {
.add-people-form-wrap {
.error {
padding-left: 5px;
a {
padding-left: 5px;
}
}
.add-telephone-icon {
transform: scaleX(-1);
}
}
}
/**
* Styles errors in the MultiSelectAutocomplete.
*/
.autocomplete-error {
min-width: 260px;
}

View File

@@ -1,141 +0,0 @@
.info-dialog {
cursor: default;
display: flex;
.info-dialog-action-link {
display: inline-block;
line-height: 1.5em;
a {
cursor: pointer;
vertical-align: middle;
}
}
.info-dialog-action-link:before {
color: $linkFontColor;
content: '\2022';
font-size: 1.5em;
padding: 0 10px;
vertical-align: middle;
}
.info-dialog-action-link:first-child:before {
content: '';
padding: 0;
}
.info-dialog-action-links {
font-weight: bold;
margin-top: 10px;
white-space: nowrap;
}
.info-dialog-action-separator {
display: inline-block;
}
.info-dialog-copy-element {
opacity: 0;
pointer-events: none;
position: fixed;
-webkit-user-select: text;
user-select: text;
}
.info-dialog-column {
margin-right: 10px;
overflow: hidden;
a,
a:active,
a:focus,
a:hover {
text-decoration: none;
}
}
.info-dialog-conference-url {
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
}
.info-dialog-dial-in {
white-space: nowrap;
.conference-id,
.phone-number {
user-select: text;
}
}
.info-dialog-icon {
color: #6453C0;
font-size: 16px;
}
.info-dialog-title {
font-weight: bold;
margin-bottom: 10px;
}
.info-password,
.info-dialog-password,
.info-password-form {
display: flex;
}
.info-password-field {
margin-left: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-password-none,
.info-password-remote {
opacity: 0.5;
}
.info-password-input {
background-color: transparent;
border: none;
color: inherit;
padding-left: 0;
}
.info-password-local {
user-select: text;
}
.conference-id {
margin-left: 5px;
}
}
.dial-in-page {
align-items: center;
display: flex;
flex-direction: column;
font-size: 24px;
height: 100%;
justify-content: center;
width: 100%;
* {
user-select: text;
}
.dial-in-numbers-list {
font-size: 24px;
margin-top: 20px;
}
.dial-in-conference-id {
text-align: center;
width: 30%;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Sets the default cursor the remove password link. The link doesn't use
* the href attribute, so we need to set the cursor manually.
*/
#inviteDialogRemovePassword {
cursor: hand;
}
.invite-dialog {
.dial-in-numbers {
.dial-in-numbers-conference-id {
color: orange;
margin-left: 3px;
}
/*
* dial-in-numbers-copy styling is needed for the feature of copying
* text to the clipboard. The styling keeps the element invisible
* to the user but still programmatically selectable for copying.
*/
.dial-in-numbers-copy {
opacity: 0;
pointer-events: none;
position: fixed;
-webkit-user-select: text;
user-select: text;
}
.dial-in-numbers-trigger {
position: relative;
width: 100%;
.dial-in-numbers-trigger-icon {
position: absolute;
right: 0;
top: 4px;
}
}
.is-disabled,
.is-loading {
.dial-in-numbers-trigger-icon {
display: none;
}
}
}
.form-control {
padding: 0;
}
.inviteLink {
color: $readOnlyInputColor;
}
.lock-state {
display: flex;
}
.password-overview {
margin-top: 10px;
.form-control {
margin-top: 10px;
}
.password-overview-status,
.remove-password {
display: flex;
justify-content: space-between;
}
.password-overview-toggle-edit,
.remove-password-link {
cursor: pointer;
text-decoration: none;
}
.remove-password {
margin-top: 15px;
}
}
.remove-password-current {
color: $inputControlEmColor;
}
}

View File

@@ -42,6 +42,10 @@
width: 55%;
}
.speaker-stats-item:nth-child(even) {
background: whitesmoke;
}
.speaker-stats-item__name,
.speaker-stats-item__time {
overflow: hidden;

View File

@@ -1,196 +0,0 @@
.video-quality-dialog {
.hide-warning {
height: 0;
visibility: hidden;
}
.video-quality-dialog-title {
margin-bottom: 10px;
}
.video-quality-dialog-contents {
align-items: center;
display: flex;
flex-direction: column;
padding: 10px;
min-width: 250px;
.video-quality-dialog-slider-container {
width: 100%;
text-align: center;
}
.video-quality-dialog-slider {
width: calc(100% - 5px);
@mixin sliderTrackStyles() {
height: 15px;
border-radius: 10px;
background: rgb(14, 22, 36);
}
&::-ms-track {
@include sliderTrackStyles();
}
&::-moz-range-track {
@include sliderTrackStyles();
}
&::-webkit-slider-runnable-track {
@include sliderTrackStyles();
}
@mixin sliderThumbStyles() {
top: 50%;
border: none;
position: relative;
opacity: 0;
}
&::-ms-thumb {
@include sliderThumbStyles();
}
&::-moz-range-thumb {
@include sliderThumbStyles();
}
&::-webkit-slider-thumb {
@include sliderThumbStyles();
}
}
.video-quality-dialog-labels {
box-sizing: border-box;
display: flex;
margin-top: 5px;
position: relative;
width: 90%;
}
.video-quality-dialog-label-container {
position: absolute;
text-align: center;
transform: translate(-50%, 0%);
&::before {
content: '';
border-radius: 50%;
left: 0;
height: 6px;
margin: 0 auto;
pointer-events: none;
position: absolute;
right: 0;
top: -16px;
width: 6px;
}
}
.video-quality-dialog-label-container.active {
color: $videoQualityActive;
&::before {
background: $videoQualityActive;
height: 12px;
top: -19px;
width: 12px;
}
}
.video-quality-dialog-label-container:first-child {
position: relative;
}
.video-quality-dialog-label {
display: table-caption;
word-spacing: unset;
}
}
&.video-not-supported {
.video-quality-dialog-labels {
color: gray;
}
.video-quality-dialog-slider {
@mixin sliderTrackDisabledStyles() {
background: rgba(14, 22, 36, 0.1);
}
&::-ms-track {
@include sliderTrackDisabledStyles();
}
&::-moz-range-track {
@include sliderTrackDisabledStyles();
}
&::-webkit-slider-runnable-track {
@include sliderTrackDisabledStyles();
}
}
}
}
.video-state-indicator {
background: $videoStateIndicatorBackground;
cursor: default;
font-size: 13px;
height: $videoStateIndicatorSize;
line-height: 20px;
text-align: left;
min-width: $videoStateIndicatorSize;
border-radius: 50%;
position: absolute;
box-sizing: border-box;
i {
line-height: $videoStateIndicatorSize;
}
/**
* Give the label padding so it has more volume and can be easily clicked.
*/
.video-quality-label-status {
line-height: $videoStateIndicatorSize;
min-width: $videoStateIndicatorSize;
text-align: center;
}
}
.centeredVideoLabel.moveToCorner {
z-index: $tooltipsZ;
}
#videoResolutionLabel {
z-index: #{$tooltipsZ + 1};
}
.centeredVideoLabel {
bottom: 45%;
border-radius: 2px;
display: none;
padding: 10px;
transform: translate(-50%, 0);
z-index: $centeredVideoLabelZ;
&.moveToCorner {
bottom: auto;
transform: none;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
}
}
.moveToCorner {
position: absolute;
top: 30px;
right: 30px;
}
.moveToCorner + .moveToCorner {
right: 80px;
}

View File

@@ -11,16 +11,9 @@
}
#reloadProgressBar {
background: #e9e9e9;
border-radius: 3px;
height: 5px;
margin: 5px auto;
overflow: hidden;
width: 180px;
.progress-indicator-fill {
margin: 5px auto;
> .aui-progress-indicator-value {
background: $reloadProgressBarBg;
height: 100%;
transition: width .5s;
}
}

4
css/shortcuts/_main.scss Normal file
View File

@@ -0,0 +1,4 @@
/* Import shortcuts blocks */
@import 'regular-key';
@import 'shortcuts-list';

View File

@@ -0,0 +1,11 @@
.regular-key {
display: table-cell;
width: 25px;
height: 20px;
padding: 0;
text-align: center;
vertical-align: middle;
font-family: $baseFontFamily;
color: $defaultDarkColor;
font-size: 12px;
}

View File

@@ -0,0 +1,12 @@
.shortcuts-list {
padding: 0;
&__description {
margin-left: em(16, 14);
vertical-align: top;
}
&__item {
margin-bottom: em(7, 14);
}
}

View File

@@ -14,11 +14,11 @@ $sliderThumbBackground: #3572b0;
/**
* Buttons
*/
$buttonBackground: #44A5FF;
$buttonHoverBackground: #2c4062;
$buttonBorder: transparent;
$buttonHoverBorder: transparent;
$buttonColor: #eceef1;
$buttonBackground: #f5f5f5;
$buttonHoverBackground: #e9e9e9;
$buttonBorder: #ccc;
$buttonHoverBorder: #999;
$buttonColor: #333;
$buttonLightBackground: #f5f5f5;
$buttonLightHoverBackground: #e9e9e9;
@@ -44,27 +44,35 @@ $defaultBackground: #474747;
$filmstripOnlyOverlayBg: #000;
$reloadProgressBarBg: #0074E0;
/**
* Connection indicator
**/
$downloadConnectionIconColor: #4abd04;
$uploadConnectionIconColor: #ffa800;
/**
* Dialog colors
**/
$auiDialogColor: #eceef1;
$auiDialogBg: #253858;
$auiDialogContentBg: #344563;
$auiBorderColor: #253858;
$auiDialogColor: #333;
$auiDialogBg: #f5f5f5;
$auiDialogContentBg: $baseLight;
$auiBorderColor: #ccc;
$dialogTitleFontWeight: 400;
$dialogErrorText: #344563;
/**
* Inlay colors
**/
$inlayColorBg: lighten($defaultBackground, 20%);
$inlayBorderColor: lighten($baseLight, 10%);
$inlayBorderColor: lighten($auiDialogContentBg, 10%);
$inlayIconBg: #000;
$inlayIconColor: #fff;
$inlayFilmstripOnlyColor: #474747;
$inlayFilmstripOnlyBg: #fff;
// Main controls
$inputBackground: $controlBackground;
$inputBorderColor: #ccc;
$inputColor: $controlColor;
$placeHolderColor: #a7a7a7;
$readOnlyInputColor: #a7a7a7;
$defaultDarkSelectionColor: #ccc;
@@ -85,17 +93,7 @@ $popoverFontColor: #ffffff;
$popupMenuSelectedItemBackground: rgba(256, 256, 256, .2);
// Toolbar
$secondaryToolbarBg: rgba(0, 0, 0, 0.5);
// TOFIX: Once moved to react rename to match the side panel class name.
$sideToolbarContainerBg: rgba(0, 0, 0, 0.75);
$toolbarBackground: rgba(0, 0, 0, 0.5);
$toolbarBadgeBackground: #165ECC;
$toolbarBadgeColor: #FFFFFF;
$toolbarButtonColor: #FFFFFF;
$toolbarSelectBackground: rgba(0, 0, 0, .6);
$toolbarTitleColor: #FFFFFF;
$toolbarToggleBackground: #12499C;
$splitterColor: #ccc;
/**
* Forms
@@ -105,8 +103,3 @@ $selectFontColor: $controlColor;
$selectBg: $controlBackground;
$selectActiveBg: darken($controlBackground, 5%);
$selectActiveItemBg: darken($controlBackground, 20%);
/**
* TODO: Replace by themed component.
*/
$videoQualityActive: #4C9AFF;

View File

@@ -5,7 +5,7 @@
width: auto;
&__title {
border-bottom: 1px solid $inlayBorderColor;
border-bottom: 1px solid $auiBorderColor;
color: $unsupportedBrowserTitleColor;
font-weight: 400;
letter-spacing: 0.5px;

View File

@@ -1,19 +1,13 @@
.unsupported-mobile-browser {
background-color: #fff;
height: 100vh;
overflow: auto;
position: relative;
padding: 35px 0;
width: 100vw;
a {
text-decoration: none
}
&__body {
color: $unsupportedBrowserTextColor;
margin: auto;
max-width: 40em;
padding: 35px 0 40px 0;
text-align: center;
width: 75%;
@@ -22,9 +16,8 @@
}
}
&__text,
.unsupported-dial-in {
font-size: 1.2em;
&__text {
font-size: 1.8em;
line-height: em(29px, 21px);
margin-bottom: 0.65em;
@@ -46,14 +39,20 @@
&__button {
border: 0;
height: 2.2857142857142856em;
line-height: 2.2857142857142856em;
margin: 18px auto 20px;
height: 42px;
margin: 0 auto;
max-width: 300px;
width: auto;
@include border-radius(3px);
width: 98%;
@include border-radius(8px);
background-color: $unsupportedBrowserButtonBgColor;
color: #505F79;
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;
@@ -61,29 +60,10 @@
&_primary {
background-color: $primaryUnsupportedBrowserButtonBgColor;
color: #FFFFFF;
&:active {
background-color: $primaryUnsupportedBrowserButtonBgColor;
}
}
}
.unsupported-dial-in {
display: none;
&.has-numbers {
align-items: center;
display: flex;
flex-direction: column;
}
.dial-in-numbers-list {
color: $unsupportedBrowserTextColor;
}
.dial-in-numbers-body {
vertical-align: top;
}
}
}

View File

@@ -103,6 +103,27 @@ case "$1" in
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
fi
fi
# UPGRADE to server side focus check if focus is configured
if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JICOFO_AUTH_DOMAIN\"" $PROSODY_HOST_CONFIG; then
echo -e "\nVirtualHost \"$JICOFO_AUTH_DOMAIN\"" >> $PROSODY_HOST_CONFIG
echo -e " authentication = \"internal_plain\"\n" >> $PROSODY_HOST_CONFIG
sed -i "s/Component \"conference.$JVB_HOSTNAME\" \"muc\"/Component \"conference.$JVB_HOSTNAME\" \"muc\"\nadmins = { \"$JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN\" }\n/g" $PROSODY_HOST_CONFIG
echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
echo -e " component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_HOST_CONFIG
PROSODY_CREATE_JICOFO_USER="true"
# UPGRADE to server side focus on old config(/etc/prosody/prosody.cfg.lua)
elif [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JICOFO_AUTH_DOMAIN\"" $PROSODY_CONFIG_OLD; then
echo -e "\nVirtualHost \"$JICOFO_AUTH_DOMAIN\"" >> $PROSODY_CONFIG_OLD
echo -e " authentication = \"internal_plain\"\n" >> $PROSODY_CONFIG_OLD
if ! grep -q "admins = { }" $PROSODY_CONFIG_OLD; then
echo -e "admins = { \"$JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN\" }\n" >> $PROSODY_CONFIG_OLD
else
sed -i "s/admins = { }/admins = { \"$JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN\" }\n/g" $PROSODY_CONFIG_OLD
fi
echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_CONFIG_OLD
echo -e " component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_CONFIG_OLD
PROSODY_CREATE_JICOFO_USER="true"
fi
if [ "$PROSODY_CREATE_JICOFO_USER" = "true" ]; then
# create 'focus@auth.domain' prosody user
@@ -112,37 +133,15 @@ case "$1" in
fi
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
# prosodyctl takes care for the permissions
# echo for using all default values
echo | prosodyctl cert generate $JVB_HOSTNAME
ln -sf /var/lib/prosody/$JVB_HOSTNAME.key /etc/prosody/certs/$JVB_HOSTNAME.key
ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
fi
if [ ! -f /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt ]; then
# prosodyctl takes care for the permissions
# echo for using all default values
echo | prosodyctl cert generate $JICOFO_AUTH_DOMAIN
AUTH_KEY_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.key"
AUTH_CRT_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.crt"
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.key $AUTH_KEY_FILE
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt $AUTH_CRT_FILE
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt /usr/local/share/ca-certificates/$JICOFO_AUTH_DOMAIN.crt
update-ca-certificates
# don't fail on systems with custom config ($PROSODY_HOST_CONFIG is missing)
if [ -f $PROSODY_HOST_CONFIG ]; then
# now let's add the ssl cert for the auth. domain (we use # as a sed delimiter cause filepaths are confused with default / delimiter)
sed -i "s#VirtualHost \"$JICOFO_AUTH_DOMAIN\"#VirtualHost \"$JICOFO_AUTH_DOMAIN\"\n ssl = {\n key = \"$AUTH_KEY_FILE\";\n certificate = \"$AUTH_CRT_FILE\";\n \}#g" $PROSODY_HOST_CONFIG
fi
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
HOST="$( (hostname -s; echo localhost) | head -n 1)"
DOMAIN="$( (hostname -d; echo localdomain) | head -n 1)"
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \
"/O=$DOMAIN/OU=$HOST/CN=$JVB_HOSTNAME/emailAddress=webmaster@$HOST.$DOMAIN" \
-keyout /var/lib/prosody/$JVB_HOSTNAME.key \
-out /var/lib/prosody/$JVB_HOSTNAME.crt
fi
ln -sf /var/lib/prosody/$JVB_HOSTNAME.key /etc/prosody/certs/$JVB_HOSTNAME.key
ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
invoke-rc.d prosody restart

View File

@@ -64,11 +64,6 @@ case "$1" in
echo "Failed to install luajwtjitsi - try installing it manually"
fi
# Install basexx
if ! luarocks install basexx; then
echo "Failed to install basexx - try installing it manually"
fi
if [ -x "/etc/init.d/prosody" ]; then
invoke-rc.d prosody restart
fi

24
debian/jitsi-meet.README.source vendored Normal file
View File

@@ -0,0 +1,24 @@
jitsi-meet for Debian
---------------------
The jitsi-meet package is built from the sources of Jitsi Meet.
Jitsi Meet is downloaded from https://github.com/jitsi/jitsi-meet and the git files are removed. you can recreate the source with 'git clone https://github.com/jitsi/jitsi-meet.git'.
Use something like the script below to update from Git
#!/bin/bash
VERSION=1.0.1
echo "*.min.js export-ignore" > .gitattributes
echo "jquery-2.1.1.* export-ignore" >> .gitattributes
echo "jquery-ui.js export-ignore" >> .gitattributes
echo ".gitignore export-ignore" >> .gitattributes
sed -i "s/1.0.1/$VERSION/g" debian/changelog
git archive --worktree-attributes --format tar --prefix jitsi-meet-$VERSION/ -o ../jitsi-meet_${VERSION}.orig.tar master
tar --transform "s,^,jitsi-meet-$VERSION/," -rf ../jitsi-meet_${VERSION}.orig.tar
cd ..
bzip2 jitsi-meet_${VERSION}.orig.tar

22
debian/patches/jquery-package vendored Normal file
View File

@@ -0,0 +1,22 @@
Description: Update the used js files for jquery to generic ones, to be able to use local system installed version (through symlinks).
Index: jitsi-meet/index.html
===================================================================
--- jitsi-meet.orig/index.html
+++ jitsi-meet/index.html
@@ -10,14 +10,14 @@
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="https://api.callstats.io/static/callstats.min.js"></script>
- <script src="libs/jquery-2.1.1.min.js"></script>
+ <script src="libs/jquery.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/1.5.0/sha.js"></script>
<script src="config.js?v=11"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=2"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
- <script src="libs/jquery-ui.js"></script>
+ <script src="libs/jquery-ui.min.js"></script>
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->

1
debian/patches/series vendored Normal file
View File

@@ -0,0 +1 @@
jquery-package

View File

@@ -2,12 +2,11 @@
1. Go to https://icomoon.io/app/
2. Go to "Manage Projects" from the menu on the top left.
3. Use "Import project" and select <code>fonts/selection.json</code> from Jitsi Meet.
4. Click "load".
5. Add the new icons using the "Add icons from library" button...
6. Go to "generate font" and make sure the identifiers for the new icons are correct.
7. Download the result in a zip file using the "download" button.
8. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
9. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/_font.scss</code> in Jitsi Meet (do *not* copy the whole file)
10. Copy the <code>selection.json</code> file to <code>react/features/base/font-icons</code> overwriting <code>jitsi.json</code>
4. Import icons (e.g. svg files) using the "import items" button.
5. Go to "generate font" and make sure the identifiers for the new icons are correct.
6. Download the result in a zip file using the "download" button.
7. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
8. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/font.css</code> in Jitsi Meet (do *not* copy the whole file)
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6

View File

@@ -9,57 +9,54 @@ To embed Jitsi Meet in your application you need to add the Jitsi Meet API libra
```javascript
<script src="https://meet.jit.si/external_api.js"></script>
```
## API
### `api = new JitsiMeetExternalAPI(domain, options)`
### `api = new JitsiMeetExternalAPI(domain, room, [width], [height], [htmlElement], [configOverwite], [interfaceConfigOverwrite], [noSsl], [jwt])`
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object.
Its constructor gets a number of options:
* **domain**: domain used to build the conference URL, "meet.jit.si" for
example.
* **options**: object with properties - the optional arguments:
* **roomName**: (optional) name of the room to join.
* **width**: (optional) width for the iframe which will be created. If a number is specified it's treated as pixel units. If a string is specified the format is number followed by 'px', 'em', 'pt' or '%'.
* **height**: (optional) height for the iframe which will be created. If a number is specified it's treated as pixel units. If a string is specified the format is number followed by 'px', 'em', 'pt' or '%'.
* **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
* **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
* **jwt**: (optional) [JWT](https://jwt.io/) token.
* **onload**: (optional) handler for the iframe onload event.
* **room**: name of the room to join.
* **width**: (optional) width for the iframe which will be created.
* **height**: (optional) height for the iframe which will be created.
* **htmlElement**: (optional) HTL DOM Element where the iframe will be added as
a child.
* **configOverwite**: (optional) JS object with overrides for options defined in
[config.js].
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options
defined in [interface_config.js].
* **noSsl**: (optional, defaults to true) Boolean indicating if the server
should be contacted using HTTP or HTTPS.
* **jwt**: (optional) [JWT](https://jwt.io/) token.
Example:
```javascript
var domain = "meet.jit.si";
var options = {
roomName: "JitsiMeetAPIExample",
width: 700,
height: 700,
parentNode: document.querySelector('#meet')
}
var api = new JitsiMeetExternalAPI(domain, options);
var room = "JitsiMeetAPIExample";
var width = 700;
var height = 700;
var htmlElement = document.querySelector('#meet');
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
```
You can overwrite options set in [config.js] and [interface_config.js].
For example, to enable the filmstrip-only interface mode, you can use:
```javascript
var options = {
interfaceConfigOverwrite: {filmStripOnly: true}
};
var api = new JitsiMeetExternalAPI(domain, options);
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, undefined, undefined, interfaceConfigOverwrite);
```
You can also pass a jwt token to Jitsi Meet:
```javascript
var options = {
jwt: "<jwt_token>",
noSsl: false
};
var api = new JitsiMeetExternalAPI(domain, options);
var jwt = "<jwt_token>";
var noSsl = false;
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite, noSsl, jwt);
```
### Controlling the embedded Jitsi Meet Conference
@@ -141,43 +138,14 @@ 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:
* **avatarChanged** - event notifications about avatar
changes. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant that changed his avatar.
"avatarURL": avatarURL // the new avatar URL.
}
```
* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
```javascript
{
"available": available // new available status - boolean
}
```
* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
```javascript
{
"muted": muted // new muted status - boolean
}
```
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
```javascript
{
"on": on //whether screen sharing is on
}
```
* **incomingMessage** - Event notifications about incoming
messages. The listener will receive an object with the following structure:
```javascript
{
"from": from, // The id 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
"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
}
```
@@ -185,7 +153,7 @@ messages. The listener will receive an object with the following structure:
messages. The listener will receive an object with the following structure:
```javascript
{
"message": txt // the text of the message
"message": txt // the text of the message
}
```
@@ -193,54 +161,36 @@ messages. The listener will receive an object with the following structure:
changes. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant that changed his display name
"displayname": displayName // the new display name
"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
{
"id": id, // the id of the participant
"displayName": displayName // the display name of the participant
"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
{
"id": id // the id of the participant
"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
"id": id, // the id of the local participant
"displayName": displayName, // the display name of the local participant
"avatarURL": avatarURL // the avatar URL of the local participant
"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
}
```
* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
```javascript
{
"available": available // new available status - boolean
}
```
* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
```javascript
{
"muted": muted // new muted status - boolean
"roomName": room // the room name of the conference
}
```
@@ -284,49 +234,6 @@ You can get the number of participants in the conference with the following API
var numberOfParticipants = api.getNumberOfParticipants();
```
You can get the avatar URL of a participant in the conference with the following API function:
```javascript
var avatarURL = api.getAvatarURL(participantId);
```
You can get the display name of a participant in the conference with the following API function:
```javascript
var displayName = api.getDisplayName(participantId);
```
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
```javascript
var iframe = api.getIFrame();
```
You can check whether the audio is muted with the following API function:
```javascript
isAudioMuted().then(function(muted) {
...
});
```
You can check whether the video is muted with the following API function:
```javascript
isVideoMuted().then(function(muted) {
...
});
```
You can check whether the audio is available with the following API function:
```javascript
isAudioAvailable().then(function(available) {
...
});
```
You can check whether the video is available with the following API function:
```javascript
isVideoAvailable().then(function(available) {
...
});
```
You can remove the embedded Jitsi Meet Conference with the following API function:
```javascript
api.dispose()

View File

@@ -1,5 +0,0 @@
# Jitsi Meet Cloud API
The Jitsi Meet Cloud API is a specification for services which can support the integration of Jitsi Meet into other applications, for mapping conferences for dial-in support, and for supporting directory search and user invitations to conferences.
The swagger for these services is provided in [cloud-api.swagger](cloud-api.swagger) in this same repository and directory.

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